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.
Related
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.
In Xtext, how do I follow a reference from grammar B to grammar A, within a validator of grammar B (which is in the ui-plugin)? Consider the following example.
Grammar A is org.xtext.people.People
grammar org.xtext.people.People with org.eclipse.xtext.common.Terminals
generate people "http://www.xtext.org/people/People"
People:
people+=Person*;
Person:
'person' name=ID ';';
and an instance
person Alice {citizenship "MN"; id "12345"; }
person Bob {citizenship "CH"; id "54321";}
person Malice {citizenship "XXX"; id "66666"; }
At an airport, entries of people are recorded.
enter Alice;
enter Bob;
enter Malice;
Entries are modelled with a second grammar B org.xtext.entries.Entries
grammar org.xtext.entries.Entries with org.eclipse.xtext.common.Terminals
generate entries "http://www.xtext.org/entries/Entries"
import "http://www.xtext.org/people/People"
Entries:
entries+=Entry*;
Entry:
'enter' person=[Person] ';';
After ensuring that the Eclipse project org.xtext.entries has the project org.xtext.people on it's classpath, and ensuring that the org.xtext.entries plugin has the org.xtext.people as a dependency, all works as expected.
There is a travel ban on people from country XXX, although certain deserving people are excluded. Only the CIA knows who is excluded from the ban. Entries must not be allowed for people from XXX unless excluded.
The updated grammar is
grammar org.xtext.entries.Entries with org.eclipse.xtext.common.Terminals
generate entries "http://www.xtext.org/entries/Entries"
import "http://www.xtext.org/people/People"
Entries:
entries+=Entry*;
Entry:
travelBanOverride=TravelBanOverride?
'enter' person=[Person] ';';
TravelBanOverride: '#TravelBanOverride' '(' code=STRING ')';
with validator
package org.xtext.entries.validation
import org.eclipse.xtext.validation.Check
import org.xtext.entries.entries.EntriesPackage
import org.xtext.entries.entries.Entry
import org.xtext.entries.CIA
class EntriesValidator extends AbstractEntriesValidator {
public static val BAN = 'BAN'
public static val ILLEGAL_OVERRIDE = 'ILLEGAL_OVERRIDE'
#Check
def checkBan(Entry entry) {
if (entry.person.citizenship == "XXX") {
if (entry.travelBanOverride === null) {
error('Violation of Travel Ban', EntriesPackage.Literals.ENTRY__PERSON, BAN)
}
else {
val overridecode = entry.travelBanOverride.code;
val valid = CIA.valid(entry.person.name, entry.person.id, overridecode)
if (!valid) {
error('Illegal override code', EntriesPackage.Literals.ENTRY__TRAVEL_BAN_OVERRIDE, ILLEGAL_OVERRIDE)
}
}
}
}
}
where the driver for the external CIA web-app is modelled for example by
package org.xtext.entries;
public class CIA {
public static boolean valid(String name, String id, String overrideCode) {
System.out.println("UNValid["+name+","+overrideCode+"]");
return name.equals("Malice") && id.equals("66666") && overrideCode.equals("123");
}
}
The validations work as expected.
I now wish to provided a quick-fix for BAN, that checks for an override code from the CIA.
package org.xtext.entries.ui.quickfix
import org.eclipse.xtext.ui.editor.quickfix.DefaultQuickfixProvider
import org.eclipse.xtext.ui.editor.quickfix.Fix
import org.xtext.entries.validation.EntriesValidator
import org.eclipse.xtext.validation.Issue
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionAcceptor
import org.xtext.entries.entries.Entry
import org.xtext.entries.Helper
class EntriesQuickfixProvider extends DefaultQuickfixProvider {
#Fix(EntriesValidator.BAN)
def tryOverride(Issue issue, IssueResolutionAcceptor acceptor) {
acceptor.accept(issue, 'Try override', 'Override if CIA says so.', 'override.png')
[element ,context |
val entry = element as Entry
// val person = entry.person // no such attribute
//val person = Helper.get(entry); // The method get(Entry) from the type Helper refers to the missing type Object
]
}
}
The first commented line does not compile: there is no attribute person. The second commented line is an attempt to solve the problem by getting a helper class in org.xtext.entries to get the person, but this does not compile either, giving a "The method get(Entry) from the type Helper refers to the missing type Object" error message.
For completeness, here is that helper.
package org.xtext.entries
import org.xtext.people.people.Person
import org.xtext.entries.entries.Entry
class Helper {
static def Person get(Entry entry) {
return entry.person;
}
}
Further, entry.travelBanOverride compiles fine, but entry.person does not. Clicking on Entry in Eclipse takes one to the expected code, which has both travelBanOverride and person.
The issue does not occur with a Java class in the same project and package.
package org.xtext.entries.ui.quickfix;
import org.xtext.entries.entries.Entry;
import org.xtext.people.people.Person;
public class Test {
public static void main(String[] args) {
Entry entry = null;
Person p = entry.getPerson();
}
}
Rewriting the quickfix in Java solves the problem.
package org.xtext.entries.ui.quickfix;
import org.eclipse.xtext.ui.editor.quickfix.DefaultQuickfixProvider;
import org.eclipse.xtext.ui.editor.quickfix.Fix;
import org.xtext.entries.validation.EntriesValidator;
import org.eclipse.xtext.validation.Issue;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionAcceptor;
import org.xtext.entries.entries.Entry;
import org.xtext.entries.Helper;
import org.eclipse.xtext.ui.editor.model.edit.IModificationContext;
import org.eclipse.xtext.ui.editor.model.edit.ISemanticModification;
import org.eclipse.emf.ecore.EObject;
import org.xtext.entries.entries.Entry;
import org.xtext.people.people.Person;
public class EntriesQuickfixProvider extends DefaultQuickfixProvider {
#Fix(EntriesValidator.BAN)
public void tryOverride(final Issue issue, IssueResolutionAcceptor acceptor) {
acceptor.accept(issue,
"Try to override",
"Override",
"override.gif",
new ISemanticModification() {
public void apply(EObject element, IModificationContext context) {
Entry entry = (Entry) element;
System.out.println(entry.getPerson());
}
}
);
}
}
How do I follow a reference from grammar B (Entries) to grammar A (People), within a validator of grammar B?
My mistake is the following.
After ensuring that the Eclipse project org.xtext.entries has the
project org.xtext.people on it's classpath, and ensuring that the
org.xtext.entries plugin has the org.xtext.people as a dependency, all
works as expected.
The org.xtext.entries.ui ui-plugin must also have the org.xtext.people on its Java (Eclipse project) build path. Exporting and making a plugin-dependency it not enough.
Note that this setting should be made early, before crafting the quick-fix, because the Xtend editor has refreshing issues.
I'm trying to create a grammar for my dsl and this is a sample from it.
Model :
'#Model' modelName=ID '{'
fields+=Field*
toBeImportedIn+=ModelExportList*
'}'
;
/* Some other Rules here */
WebServiceConsumer :
'#WebServiceConsumer' '(' serviceName=ID ',' webServiceURL=STRING ','
modelName=[Model])'
;
When I try to test my grammar like this, I got an error : "Couldn't resolve reference to Model 'myModel'."
#Model myModel{}
#WebServiceConsumer(serviceName,"URL goes here",myModel)
you can reference things that have an attribute called name by default. you can bypass this behaviour by implementing your own IQualifiedNameProvider e.g.
package org.xtext.example.mydsl;
import org.eclipse.xtext.naming.DefaultDeclarativeQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.xtext.example.mydsl.myDsl.Element;
import org.xtext.example.mydsl.myDsl.Package;
public class MyDslQNP extends DefaultDeclarativeQualifiedNameProvider{
QualifiedName qualifiedName(Element e) {
Package p = (Package) e.eContainer();
return QualifiedName.create(p.getName(), e.getId());
}
}
and dont forget to bind
public class MyDslRuntimeModule extends org.xtext.example.mydsl.AbstractMyDslRuntimeModule {
override Class<? extends IQualifiedNameProvider> bindIQualifiedNameProvider() {
return MyDslQNP;
}
}
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.
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.