Import for custom class in XbaseCompiler - xtext

I'm writing a DSL using XBase, and I've added a new parser rule which returns an XExpression in the grammar:
DatastepExpression returns xbase::XExpression: {DatastepExpression} 'data' name=ID '{' '}';
and added the appropriate function to the XbaseTypeComputer subclass:
protected def _computeTypes(DatastepExpression expression, ITypeComputationState state) {
var type = getTypeForName(typeof(FileDataset), state)
state.acceptActualType(type)
}
Now I'm trying to add in the method to the XbaseCompiler subclass:
override protected doInternalToJavaStatement(XExpression expr, ITreeAppendable it, boolean isReferenced) {
switch expr {
DatastepExpression: {
newLine
append('''FileDataset «expr.name»;''')
}
default:
super.doInternalToJavaStatement(expr, it, isReferenced)
}
}
where FileDataset is a custom class in my language API.
How do I get this class to appear in the imports at the top of the generated files?
At the moment, when I create a new file in my language (in the runtime Eclipse) the generated Java file contains the FileDataset variable declaration but it has a red wavy line underneath and error "FileDataset cannot be resolved to a type"

ITreeAppendable has methods to append instances of Class, JvmType or LightweightTypeReference. A plain
it.append(FileDataset).append(' ').append(expr.name)
should do the trick.

Related

How do I change access modifiers applied to the class and methods generated by antlr4?

I don't want antlr's generated classes and methods exposed to the public API.
There's a 6-year old answer Antlr generated classes access modifier to internal. But I hope there's a more modern and true way to do it.
AFAIK, you can't. You could use the superClass option in the grammar so that you extend a custom parser class where you define only those methods you want to be public:
grammar T;
options {
superClass=MyParser;
}
parse
: id+ EOF
;
id
: ID
;
ID
: [a-zA-Z] [a-zA-Z0-9_]*
;
and your MyParser class could look like this:
public abstract class MyParser extends Parser {
public MyParser(TokenStream input) {
super(input);
}
abstract TParser.ParseContext parse() throws RecognitionException;
}
and then instantiate your parser like this:
TLexer lexer = new TLexer(CharStreams.fromString("foo"));
MyParser parser = new TParser(new CommonTokenStream(lexer));
parser.parse(); // OK
parser.id(); // No can do
but I need to actually hide the whole class. This class should be internal, it's being called in inner methods in the lib
That is not something you can do AFAIK. You'll have to add that yourself with some post-build script (simple search/replace), I think.

how to implement string concatenation alike extension function with unknown arguments number using Saxon-HE?

I want to add a custom xpath extension function to the Saxon-HE transformer. This custom function should accept one or more arguments. Let's use the string concatenation analogy for concatenating one or more string arguments. Following the sample on the saxon page, i wrote the following code:
ExtensionFunction myconcat = new ExtensionFunction() {
public QName getName() {
return new QName("http://mycompany.com/", "myconcat");
}
public SequenceType getResultType() {
return SequenceType.makeSequenceType(
ItemType.STRING, OccurrenceIndicator.ONE
);
}
public net.sf.saxon.s9api.SequenceType[] getArgumentTypes() {
return new SequenceType[]{
SequenceType.makeSequenceType(
ItemType.STRING, OccurrenceIndicator.ONE_OR_MORE)};
}
public XdmValue call(XdmValue[] arguments) throws SaxonApiException {
//concatenate the strings here....
String result = "concatenated string";
return new XdmAtomicValue(result);
}
};
i have expected that the following xpath expression would work in an xsl file
<xsl:value-of select="myconcat('a','b','c','...')">
Unfortunately i got the following exception:
XPST0017: Function myconcat must have 1 argument
What is the right way of creating a custom function for this use case?
Thanks.
The standard mechanisms for creating extension functions don't allow a variable number of arguments (it's not really pukka to have such functions in the XPath view of the world - concat() is very much an exception).
You can do it by creating your own implementation of the class FunctionLibrary and adding your FunctionLibrary to the static context of the XSLT engine - but you're deep into Saxon internals if you attempt that, so be prepared for a rough ride.

Xtext - Couldn't resolve reference to JvmOperation

I am trying to create a small DSL language using XText.
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.xbase.Xbase
import "http://www.eclipse.org/xtext/common/JavaVMTypes" as jvmTypes
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
StartState:
'startState'
'evaluate' ref=JvmTypeReference "-" op=[jvmTypes::JvmOperation]
'end';
Following is the ScopeProvider Implementation:
public class MyScopeProvider extends AbstractDeclarativeScopeProvider {
IScope scope_StartState_op(StartState call, EReference reference) {
JvmType type = call.getRef().getType();
List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
if (type instanceof JvmGenericType) {
JvmGenericType gt = (JvmGenericType) type;
for (JvmMember member : gt.getMembers()) {
if (member instanceof JvmOperation) {
descriptions.add(EObjectDescription.create(member.getSimpleName(), member));
}
}
}
return new SimpleScope(descriptions);
}}
I type following into the resulting editor
startState evaluate controller.Controller - perform end
Here I am able to get the code completion working for method (perform which is in controller.Controller class) as expected. But I am in need of help to resolve the following error which happens after the code completion.
Couldn't resolve reference to JvmOperation 'perform'.
Also, I tried following Peter's Blog with no success
Posting answer to my own question. The grammer should look like the following.
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.xbase.Xtype
import "http://www.eclipse.org/xtext/common/JavaVMTypes" as jvmTypes
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
StartState:
'startState'
'evaluate' ref=JvmTypeReference "-" op=[jvmTypes::JvmOperation]
'end';
There is no change to the scope provider and the usage.

Xtext Grammar mixins result in Guice injection error

I am writing an Xtext grammar that can access documentation that are declared before a functions.
Our current grammar defines hidden(ML_COMMENT, SL_COMMENT,...) with:
ML_COMMENT: '/*' -> '*/'
SL_COMMENT: '//' -> EOL
I have now created a second Xtext project, with the following grammar:
grammar my.DocumentationGrammar with my.OriginalGrammar hidden(WS, FUNCTION_BODY, EOL, SL_COMMENT)
import "http://www.originalGrammar.my"
generate documentationGrammar "http://www.documentationGrammar.my"
/* Parser rules */
TranslationUnit:
eds+=DoxExternalDefinition*
;
DoxExternalDefinition:
def = Definition
| lib = CtrlLibUsage
| comment=ML_COMMENT
;
FunctionDefinition:
aml=AccessModifiersList ts=TypeSpecifier? f=Function '(' pl=ParameterTypeList? ')' /* cs=CompoundStatement */ // the compound statement is ignored
;
//terminal DOXYGEN_COMMENT: ML_COMMENT;
terminal FUNCTION_BODY: '{' -> '}';
I have created the dependency in the plugin and added this to the
bean = StandaloneSetup {
scanClassPath = true
platformUri = "${runtimeProject}/.."
// The following two lines can be removed, if Xbase is not used.
registerGeneratedEPackage = "org.eclipse.xtext.xbase.XbasePackage"
registerGenModelFile = "platform:/resource/org.eclipse.xtext.xbase/model/Xbase.genmodel"
// we need to register the super genmodel
registerGeneratedEPackage = "my.OriginalGrammar.OriginalGrammarPackage"
registerGenModelFile = "platform:/resource/my.OriginalGrammar/model/generated/OriginalGrammar.genmodel"
}
Now in my third plugin project, I want to access this parser in a Standalone fashion. So I created the following Parser file (based on this example: http://davehofmann.de/blog/?p=101) :
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Path;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.parser.IParser;
import org.eclipse.xtext.parser.ParseException;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.XtextResourceSet;
import my.DocumentationGrammar.DocumentationGrammarStandaloneSetup;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class DoxygenParser {
#Inject
private IParser parser;
private Injector injector;
public DoxygenParser() {
setupParser();
}
private void setupParser() {
injector = new DocumentationGrammarStandaloneSetup().createInjectorAndDoEMFRegistration();
injector.injectMembers(this);
}
/**
* Parses data provided by an input reader using Xtext and returns the root node of the resulting object tree.
* #param reader Input reader
* #return root object node
* #throws IOException when errors occur during the parsing process
*/
public EObject parse(Reader reader) throws IOException
{
IParseResult result = parser.parse(reader);
if(result.hasSyntaxErrors())
{
throw new ParseException("Provided input contains syntax errors.");
}
return result.getRootASTElement();
}
}
However, when I try to run it, I receive Guice Injection errors saying that
com.google.inject.ProvisionException: Guice provision errors:
1) Error injecting constructor, org.eclipse.emf.common.util.WrappedException: java.lang.RuntimeException: Cannot create a resource for 'classpath:/my/documentationGrammar/DocumentationGrammar.xtextbin'; a registered resource factory is needed
I know that the parser "should" be correct, since when I use the OriginalGrammarStandaloneSetup it works perfectly fine.
You have to make sure that your sublanguage also invokes the standalone setup of your super language. Usually this is generated in the DocumentationGrammarStandaloneSetupGenerated class, but please make sure that this was properly setup by Xtext. In the end, there should be something like
if (!Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("xtextbin"))
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(
"xtextbin", new BinaryGrammarResourceFactoryImpl());
in the callchain of your setup's createInjectorAndDoEMFRegistration method.

How can I access alternate labels in ANTLR4 while generically traversing a parse tree?

How can I access alternate labels in ANTLR4 while generically traversing a parse tree? Or alternatively, is there any way of replicating the functionality of the ^ operator of ANTLR3, as that would do the trick.
I'm trying to write an AST pretty printer for any ANTLR4 grammar adhering to a simple methodology (like naming productions with alternate labels). I'd like to be able to pretty print a term like 3 + 5 as (int_expression (plus (int_literal 3) (int_literal 5))), or something similar, given a grammar like the following:
int_expression
: int_expression '+' int_expression # plus
| int_expression '-' int_expression # minus
| raw_int # int_literal
;
raw_int
: Int
;
Int : [0-9]+ ;
I am unable to effectively give names to the plus and minus productions, because pulling them out into their own production causes the tool to complain that the rules are mutually left-recursive. If I can't pull them out, how can I give these productions names?
Note 1: I was able to get rid of the + argument methodologically by putting "good" terminals (e.g., the Int above) in special productions (productions starting with a special prefix, like raw_). Then I could print only those terminals whose parent productions are named "raw_..." and elide all others. This worked great for getting rid of +, while keeping 3 and 5 in the output. This could be done with a ! in ANTLR3.
Note 2: I understand that I could write a specialized pretty printer or use actions for each production of a given language, but I'd like to use ANTLR4 to parse and generate ASTs for a variety of languages, and it seems like I should be able to write such a simple pretty printer generically. Said another way, I only care about getting ASTs, and I'd rather not have to encumber each grammar with a tailored pretty printer just to get an AST. Perhaps I should just go back to ANTLR3?
I suggest implementing the pretty printer as a listener implementation with a nested visitor class to get the names of the various context objects.
private MyParser parser; // you'll have to assign this field
private StringBuilder builder = new StringBuilder();
#Override
public void enterEveryRule(#NotNull ParserRuleContext ctx) {
if (!builder.isEmpty()) {
builder.append(' ');
}
builder.append('(');
}
#Override
public void visitTerminalNode(#NotNull TerminalNode node) {
// TODO: print node text to builder
}
#Override
public void visitErrorNode(#NotNull TerminalNode node) {
// TODO: print node text to builder
}
#Override
public void exitEveryRule(#NotNull ParserRuleContext ctx) {
builder.append(')');
}
protected String getContextName(#NotNull ParserRuleContext ctx) {
return new ContextNameVisitor().visit(ctx);
}
protected class ContextNameVisitor extends MyParserBaseVisitor<String> {
#Override
public String visitChildren() {
return parser.getRuleNames()[ctx.getRuleIndex()];
}
#Override
public String visitPlus(#NotNull PlusContext ctx) {
return "plus";
}
#Override
public String visitMinus(#NotNull MinusContext ctx) {
return "minus";
}
#Override
public String visitInt_literal(#NotNull MinusContext ctx) {
return "int_literal";
}
}
The API doesn't contain a method to access the alternate labels.
However there is a workaround. ANTLR4 uses the alternate labels to generate java class names and those java classes can be accessed at run time.
For example: to access alternate labels in ANTLR4 while generically traversing a parse tree (with a listener) you can use the following function:
// Return the embedded alternate label between
// "$" and "Context" from the class name
String getCtxName(ParserRuleContext ctx) {
String str = ctx.getClass().getName();
str = str.substring(str.indexOf("$")+1,str.lastIndexOf("Context"));
str = str.toLowerCase();
return str;
}
Example use:
#Override
public void exitEveryRule(ParserRuleContext ctx) {
System.out.println(getCtxName(ctx));
}

Resources