In a series of internal workshops on Xbase, I've often encountered one question: Can we extend the expressions of Xbase? In this blogpost, I am going to show you how to do that. It is based on the domain model example shipped with Xtext 2.0, which you can instantiate by choosing File > New > Example... > Xtext > Xtext Domain-Model Example.
The Grammar
By default, Xbase only knows integer literals. In this example we are adding decimal literals.
If you want to add a new concept to Xbase expressions syntactically, you have to hook into the right rule of Xbase. As the name suggests a literal can be added in the Xbase rule
XLiteral
. So in the Dominamodel.xtext
you have to add
import "http://www.eclipse.org/xtext/xbase/Xbase"
...
XLiteral returns XExpression:
XClosure |
XBooleanLiteral |
XIntLiteral |
XNullLiteral |
XStringLiteral |
XTypeLiteral |
DecimalLiteral; // our new alternative
DecimalLiteral:
intPart=INT '.' decimalPart=INT;
As the
EPackage
of the domainmodel language is generated this will introduce a new EClass
DecimalLiteral as soon as you regenerate the language infrastructure.Type Provider
For the sake of simplicity, our decimal literal will be mapped to
java.lang.Double
. We have to adapt the type provider accordingly
public DomainmodelTypeProvider {
@Inject
private TypeReferences typeReferences;
...
protected JvmTypeReference _type(DecimalLiteral literal,
boolean rawType) {
return typeReferences.getTypeForName(Double.class,
literal);
}
}
Compiler
The compiler has to understand what to do with decimal literals. We have to add a method that appends the concrete syntax of the Java double literal for a
DecimalLiteral
public class DomainmodelCompiler {
...
protected void _toJavaExpression(DecimalLiteral expr,
IAppendable b) {
b.append(expr.getIntPart() + "."
+ expr.getDecimalPart());
}
protected void _toJavaStatement(DecimalLiteral expr,
IAppendable b,
boolean isReferenced) {
generateComment(expr, b, isReferenced);
}
}
We can now use decimal literals in your expressions and they are correctly converted to
java.lang.Double
s. The following domain model should now compile:
entity DecimalLiteralTest {
op approxPi() : double {
3.1415926535
}
}
Note that autoboxing/unboxing works in Xbase the same as in Java. Still we do not have any operations for decimal literals. The next section shows how to add them.
Operators
Operators in Xbase are implemented as Java methods following a naming convention. For example, the operator
+
will be mapped to a call to a method named operator_plus()
. There are several places where Xbase tries to find this method- in the current type as
operator_plus(OperandType1, OperandType2)
- in the class of the first operand, as
OperandType1.operator_plus(OperandType2)
- in the extension scope as
operator_plus(OperandType1, OperandType2)
The first alternative is ruled out, as we want to provide global operations. The second does not apply as we cannot change the final class
java.lang.Double
, so we're eventually dealing with alternative 3. Let's write a new extension library class holding the operations as static methods. These methods need to be available at runtime, too, as the generated Java code will call them. That's why it is a good idea to put such extension classes into a separate plug-in org.eclipse.xtext.example.domainmodel.lib.
package org.eclipse.xtext.example.domainmodel.lib;
public class DecimalExtensions {
public static Double operator_plus(Double x,
Double y) {
return x + y;
}
public static Double operator_minus(Double x,
Double y) {
return x - y;
}
public static Double operator_multiply(Double x,
Double y) {
return x * y;
}
public static Double operator_divide(Double x,
Double y) {
return x / y;
}
}
Globally available extension classes and literal classes have to be registered in the
StaticMethodsFeatureForTypeProvider
. We override it
package org.eclipse.xtext.example.domainmodel.scoping;
...
public class DomainmodelStaticMethodsProvider
extends StaticMethodsFeatureForTypeProvider {
@Override
protected IterablegetVisibleTypesContainingStaticMethods
(JvmTypeReference reference) {
IterableresultFromSuper =
super.getVisibleTypesContainingStaticMethods
(reference);
if (reference != null && reference.getType() != null
&& "java.lang.Double"
.equals(reference.getType().getIdentifier())) {
return Iterables.concat(Collections.singletonList
("org.eclipse.xtext.example.domainmodel.lib.DecimalExtensions"),
resultFromSuper);
}
return resultFromSuper;
}
}
and bind our customized implementation
public class DomainmodelRuntimeModule
extends AbstractDomainmodelRuntimeModule {
...
public Class
bindStaticMethodsFeatureForTypeProvider() {
return DomainmodelStaticMethodsProvider.class;
}
}
If you add the plug-in org.eclipse.xtext.example.domainmodel.lib to the classpath of the project in the runtime workspace, the following domain model should compile:
entity DecimalLiteralTest2 {
op circleArea(double radius) : double {
3.1415926535 * radius * radius
}
}