I have a DSL that includes blocks that need to be wrapped as methods returned inside an anonymous class created by the generated code. For example:
model {
task {
val x = 2*5;
Math.pow(2, x)
}
}
should compile to (note task becoming an instance of Runnable, with the body of the task becoming the body of the Runnable.run() method):
import java.util.Collection;
#SuppressWarnings("all")
public class MyFile {
public Collection<Runnable> tasks() {
ArrayList<Runnable> tasks = new ArrayList<>();
tasks.add(getTask0());
return tasks;
}
public static Runnable getTask0() {
Runnable _runnable = new Runnable() {
public void run() {
final int x = (2 * 5);
Math.pow(2, x);
}
}
return _runnable;
}
}
Following the discussion in this question, I was able to get this particular example to work. (Github repo includes unit tests.) But I had to do it by representing the Task element in the grammar as a sequence of XExpressions (source), which my XbaseCompiler subclass had to iterate over (source).
Instead, it would have been nice to be able to just have Task contain an XBlockExpression in a property action, and then in the compiler just do doInternalToJavaStatement(expr.action, it, isReferenced). My sense is that this is really the "right" solution in my case, but when I tried it, this would result in an empty body of the generated run method, as if the block was not processed at all. What's going on, and am I missing some required bits of setup/wiring things together/bindings that are necessary for this to work?
you ususally try to avoid that by using a better inference strategy e.g.
Grammar
Model:
{Model}"model" "{"
vars+=Variable*
tasks+=Task*
"}"
;
Variable:
"var" name=ID ":" type=JvmParameterizedTypeReference
;
Task:
{Task} "task" content=XBlockExpression
;
Inferrer
class MyDslJvmModelInferrer extends AbstractModelInferrer {
#Inject extension JvmTypesBuilder
def dispatch void infer(Model element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toClass("test.Model2")) [
for (v : element.vars) {
members+=v.toField(v.name, v.type.cloneWithProxies) [
]
}
var i = 0;
for (t : element.tasks) {
val doRunName = "doRun"+i
members += t.toMethod("task"+i, Runnable.typeRef()) [
body = '''
return new «Runnable» () {
public void run() {
«doRunName»();
}
};
'''
]
members += t.toMethod(doRunName, Void.TYPE.typeRef()) [
body = t.content
]
i = i + 1
}
]
}
}
and that basically is it.
you may follow https://bugs.eclipse.org/bugs/show_bug.cgi?id=481992
If you really want to adapt the xbase typesystem that may be a lot more of work e.g. (just covering a minimal case)
Grammar
Model:
{Model}"model" "{"
vars+=Variable*
tasks+=Task*
"}"
;
Variable:
"var" name=ID ":" type=JvmParameterizedTypeReference
;
Task:
{Task} "task" content=XTaskContent
;
XTaskContent returns xbase::XExpression:
{XTaskContent} block=XBlockExpression
;
Inferrer
class MyDslJvmModelInferrer extends AbstractModelInferrer {
#Inject extension JvmTypesBuilder
def dispatch void infer(Model element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toClass("test.Model")) [
for (v : element.vars) {
members+=v.toField(v.name, v.type.cloneWithProxies) [
]
}
var i = 0;
for (t : element.tasks) {
members += t.toMethod("task"+i, Runnable.typeRef()) [
body = t.content
]
i = i + 1
}
]
}
}
Type Computer
class MyDslTypeComputer extends XbaseTypeComputer {
override computeTypes(XExpression expression, ITypeComputationState state) {
if (expression instanceof XTaskContent) {
_computeTypes(expression as XTaskContent, state);
} else {
super.computeTypes(expression, state)
}
}
protected def void _computeTypes(XTaskContent object, ITypeComputationState state) {
state.withExpectation(getPrimitiveVoid(state)).computeTypes(object.block)
state.acceptActualType(getTypeForName(Runnable, state), ConformanceFlags.CHECKED_SUCCESS )
}
}
Compiler
class MyDslCompiler extends XbaseCompiler {
override protected internalToConvertedExpression(XExpression obj, ITreeAppendable appendable) {
if (obj instanceof XTaskContent) {
appendable.append("new ").append(Runnable).append("() {").newLine
appendable.increaseIndentation
appendable.append("public void run()").newLine
reassignThisInClosure(appendable, null)
internalToJavaStatement(obj.block, appendable, false)
appendable.newLine
appendable.decreaseIndentation
appendable.newLine.append("}")
} else {
super.internalToConvertedExpression(obj, appendable)
}
}
}
Bindings
class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
def Class<? extends ITypeComputer> bindITypeComputer() {
return MyDslTypeComputer
}
def Class<? extends XbaseCompiler> bindXbaseCompiler() {
return MyDslCompiler
}
}
Related
In Dart we can use generic classes [class]. We can also specialize those classes [class]. However at runtime the specialization is not used. (In C++ this is called template programming)
Example: The following code will result in the output
Hallo world
How are you
class MyClass<T> {
foo( print('Hallo world'); );
}
class MyClassInt implements MyClass<int> {
#override
foo( print('How are you'); );
}
main() {
MyClass<int> a = Myclass<int>();
MyClassInt b = MyClassInt();
a.foo();
b.foo();
}
How can the specialization (here type [int]) be done, that it is called at runtime, i.e.
main() {
MyClass<int> a = Myclass<int>();
a.foo();
}
should result in the outcome "How are you".
As mentioned by jamesdlin, Dart does not support specialization. But you can do something like this to make the illusion:
class MyClass<T> {
factory MyClass() {
if (T == int) {
return MyClassInt() as MyClass<T>;
} else {
return MyClass._();
}
}
// Hidden real constructor for MyClass
MyClass._();
void foo() {
print('Hallo world');
}
}
class MyClassInt implements MyClass<int> {
#override
void foo() {
print('How are you');
}
}
void main() {
final a = MyClass<int>();
final b = MyClassInt();
final c = MyClass<String>();
a.foo(); // How are you
b.foo(); // How are you
c.foo(); // Hallo world
}
How to get a tab space for beautification after writing a statement in Xtext.
Here is my code is in Xtext grammar :
Block:
'_block'
name=ID
'_endblock'
;
and UI template is
override complete_BLOCK(EObject model, RuleCall ruleCall, ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
super.complete_BLOCK(model, ruleCall, context, acceptor)
acceptor.accept(createCompletionProposal("_block \n
_endblock","_block",null,context));
}
How do I indent for a tab space after writing a block statement?
to implement a formatter
open the mwe2 file
add formatter = {generateStub = true} to the language = StandardLanguage { section of the workflow
regenerate the language
open the MyDslFormatter Xtend class and implement it
to call the formatter
mark the section to format or dont mark to format everything
call rightclick -> Source -> Format or the Shortcut Cmd/Crtl + Shift + F
here is a very naive no failsafe impl of an auto edit strategy
package org.xtext.example.mydsl1.ui;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.xtext.ui.editor.autoedit.DefaultAutoEditStrategyProvider;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class YourAutoEditStrategyProvider extends DefaultAutoEditStrategyProvider {
public static class BlockStrategy implements IAutoEditStrategy {
private static final String BLOCK = "_block";
protected int findEndOfWhiteSpace(IDocument document, int offset, int end) throws BadLocationException {
while (offset < end) {
char c= document.getChar(offset);
if (c != ' ' && c != '\t') {
return offset;
}
offset++;
}
return end;
}
#Override
public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
if ("\n".equals(c.text)) {
if (d.getLength()> BLOCK.length()) {
try {
if ((BLOCK+" ").equals(d.get(c.offset-BLOCK.length()-1, BLOCK.length()+1)) || (BLOCK).equals(d.get(c.offset-BLOCK.length(), BLOCK.length()))) {
int p= (c.offset == d.getLength() ? c.offset - 1 : c.offset);
IRegion info= d.getLineInformationOfOffset(p);
int start= info.getOffset();
// find white spaces
int end= findEndOfWhiteSpace(d, start, c.offset);
int l = 0;
StringBuilder buf= new StringBuilder(c.text);
if (end > start) {
// append to input
buf.append(d.get(start, end - start));
l += (end - start);
}
buf.append("\t");
buf.append("\n");
buf.append(d.get(start, end - start));
c.text= buf.toString();
c.caretOffset = c.offset+2+l;
c.shiftsCaret=false;
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
}
}
}
#Inject
private Provider<BlockStrategy> blockStrategy;
#Override
protected void configure(IEditStrategyAcceptor acceptor) {
super.configure(acceptor);
acceptor.accept(blockStrategy.get(), IDocument.DEFAULT_CONTENT_TYPE);
}
}
and dont forget to bind
class MyDslUiModule extends AbstractMyDslUiModule {
override bindAbstractEditStrategyProvider() {
YourAutoEditStrategyProvider
}
}
I am writing on a Xtext grammar that uses XExpressions and also operates on Eclasses. Now I want to also be able to access Eclasses from the XExpression, for example I write an expression like this:
Eclass1.attribute1 = Eclass2.attribute1
I would like to know, how I can use the Eclass from within the XExpression?
Grammar
grammar org.xtext.example.mydsl.Mydsl with
org.eclipse.xtext.xbase.Xbase
import "http://www.eclipse.org/emf/2002/Ecore" as ecore
generate mydsl "http://www.xtext.org/example/mydsl/Mydsl"
Model:
(operations += Operation)*;
terminal ATTR : ID ('.' ID)+;
Operation:
'operation' left=[ecore::EClass|ATTR] 'and' right=
[ecore::EClass|ATTR] 'defined' 'as' condition=XExpression
;
Inferrer/ Infer method
def dispatch void infer(Model element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toClass("example.mydsl")) [
for (operation : element.operations) {
left = operation.left
right = operation.right
if (left.eIsProxy()) {
left = EcoreUtil.resolve(left, operation) as EClass
}
if (right.eIsProxy()) {
right = EcoreUtil.resolve(right, operation) as EClass
}
//field for right class left out, but works the same
members += left.toField(left.name,typeRef(left.EPackage.name+"."+left.name))
members += operation.toMethod("conditionExpr",
typeRef(Void.TYPE)) [
body = operation.condition
]
}
]
}
RuntimeModule
class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
def Class<? extends ImplicitlyImportedFeatures> bindImplicitlyImportedTypes() {
return MyImportFeature
}
}
MyImportFeature
class MyImportFeature extends ImplicitlyImportedFeatures{
override protected getStaticImportClasses() {
(super.getStaticImportClasses() + #[PackageFromWorkSpace]).toList
}
}
I Am not sure if i get your question.
Ususally EMF generates constants for EAttributes so if you want to access the attributes themselfs
so you could either do
MyDslPackage.Literals.GREETING__NAME
or
MyDslPackage.eINSTANCE.getGreeting_Name()
can you give some more hints on what you actually want to do
update: here is a snippet on how to get a java class from a reference to an eclass
Thingy:{
val EClass eclazz = f.clazz
val uri = EcorePlugin.getEPackageNsURIToGenModelLocationMap(true).get(eclazz.EPackage.nsURI)
val rs = new ResourceSetImpl
val r = rs.getResource(uri, true)
r.load(null)
val p = r.contents.head
if (p instanceof GenModel) {
val genClass = p.findGenClassifier(eclazz)
if (genClass instanceof GenClass) {
println(genClass.qualifiedInterfaceName)
members+=f.toField(eclazz.name, genClass.qualifiedInterfaceName.typeRef)
}
}
}
I have a simple DSL that should generate async code for expressions (this is the simplest example I could come up with to illustrate my point). I just added to the scripting example an new async statement:
grammar org.xtext.scripting.Scripting with org.eclipse.xtext.xbase.Xbase
generate scripting "http://www.xtext.org/scripting/Scripting"
import "http://www.eclipse.org/xtext/xbase/Xbase" as xbase
Script returns xbase::XBlockExpression:
{Script}
(expressions+=XExpressionOrVarDeclaration ';'?)*;
XExpression returns xbase::XExpression:
super | Async
;
Async:
'async' expression=XExpression
;
The idea would be that the async code is executed in another thread.
My question is, how can I generate code for the Async.expression using the ScriptingJvmModelInferrer?
In the simplest case I would just wrap the code from the Async.expression like this?
AsyncRunner.exec(new Runnable() {
#Override
public void run() {
// the Async.expression would end up here
}
})
Where is the hook to do that?
You have to make 3 changes:
Extend the compiler to deal with your language. The key point is to handle the Async expression.
class ScriptingCompiler extends XbaseCompiler {
override protected doInternalToJavaStatement(XExpression expr, ITreeAppendable it, boolean isReferenced) {
switch expr {
Async : {
newLine
append('''
AsyncRunner.exec(new Runnable() {
#Override
public void run() {''')
expr.expression.doInternalToJavaStatement(it, false)
newLine
append('}});')
}
default :
super.doInternalToJavaStatement(expr, it, isReferenced)
}
}
override protected internalToConvertedExpression(XExpression obj, ITreeAppendable it) {
if (hasName(obj))
append(getName(obj))
else
super.internalToConvertedExpression(obj, it)
}
}
The type of the expression has to be specified
class ScriptingTypeComputer extends XbaseWithAnnotationsTypeComputer {
override computeTypes(XExpression expression, ITypeComputationState state) {
if(expression instanceof Async) {
super.computeTypes(expression.expression, state);
} else {
super.computeTypes(expression, state)
}
}
}
Both extensions have to be injected:
class ScriptingRuntimeModule extends AbstractScriptingRuntimeModule {
def Class<? extends XbaseCompiler> bindXbaseCompiler() {
return ScriptingCompiler
}
def Class<? extends ITypeComputer> bindITypeComputer() {
return ScriptingTypeComputer
}
}
If you extend Xbase you ususally don't apapt the JvmModelInferrer for Compilation but you extend XbaseTypeComputer and XbaseCompiler.doInternalToJavaStatement/internalToConvertedExpression (depending on what you actually introduce)
I have a certain part of my XText grammer that defines a block for classes that shall print all its expressions. The XText grammar part for this looks as follows:
Print:
{Print}
'print' '{'
print += PrintLine*
'}';
PrintLine:
obj += XExpression;
Now I use the following inferrer code to create a print() method:
Print: {
members += feature.toMethod('print', typeRef(void)) [
body = '''
«FOR printline : feature.print»
System.out.println(«printline.obj»);
«ENDFOR»
'''
]
}
Ok, I go ahead and test it with the following code in a class:
print {
"hallo"
4
6 + 7
}
And the result is the following:
public void print() {
System.out.println([org.eclipse.xtext.xbase.impl.XStringLiteralImpl#20196ba8 (value: hallo)]);
System.out.println([org.eclipse.xtext.xbase.impl.XNumberLiteralImpl#7d0b0f7d (value: 4)]);
System.out.println([<XNumberLiteralImpl> + <XNumberLiteralImpl>]);}
Of course, I was hoping for:
public void print() {
System.out.println("hallo");
System.out.println(4);
System.out.println(6+7);
}
I understand that I might have to call the compiler somehow in the inferrer for «printline.obj», but I am really not sure how.
i think you are doing this on a wrong basis. this sounds to me like an extension of xbase, not only a simple use.
import "http://www.eclipse.org/xtext/xbase/Xbase" as xbase
Print:
{Print}
'print'
print=XPrintBlock
;
XPrintBlock returns xbase::XBlockExpression:
{xbase::XBlockExpression}'{'
expressions+=XPrintLine*
'}'
;
XPrintLine returns xbase::XExpression:
{PrintLine} obj=XExpression
;
Type Computer
class MyDslTypeComputer extends XbaseTypeComputer {
def dispatch computeTypes(XPrintLine literal, ITypeComputationState state) {
state.withNonVoidExpectation.computeTypes(literal.obj)
state.acceptActualType(getPrimitiveVoid(state))
}
}
Compiler
class MyDslXbaseCompiler extends XbaseCompiler {
override protected doInternalToJavaStatement(XExpression obj, ITreeAppendable appendable, boolean isReferenced) {
if (obj instanceof XPrintLine) {
appendable.trace(obj)
appendable.append("System.out.println(")
internalToJavaExpression(obj.obj,appendable);
appendable.append(");")
appendable.newLine
return
}
super.doInternalToJavaStatement(obj, appendable, isReferenced)
}
}
XExpressionHelper
class MyDslXExpressionHelper extends XExpressionHelper {
override hasSideEffects(XExpression expr) {
if (expr instanceof XPrintLine || expr.eContainer instanceof XPrintLine) {
return true
}
super.hasSideEffects(expr)
}
}
JvmModelInferrer
def dispatch void infer(Print print, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(
print.toClass("a.b.C") [
members+=print.toMethod("demo", Void.TYPE.typeRef) [
body = print.print
]
]
)
}
Bindings
class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
def Class<? extends ITypeComputer> bindITypeComputer() {
MyDslTypeComputer
}
def Class<? extends XbaseCompiler> bindXbaseCompiler() {
MyDslXbaseCompiler
}
def Class<? extends XExpressionHelper> bindXExpressionHelper() {
MyDslXExpressionHelper
}
}