-
Notifications
You must be signed in to change notification settings - Fork 19
Xsemantics Syntax
Here we describe the syntax of Xsemantics. You may also want to refer to the Examples for better understanding the syntax of judgments, rules, etc.
You can specify import
s like in Java (you don't need the terminating ;
). Just like in Java,
imports can end with a *
in order to import all the classes of that
Java package, although the use of these "wildcard imports" is deprecated.
Since version 1.3.0 (i.e., after the move to Xbase 2.4) you can also specify
static imports (wildcard can be used for static imports); Since version 1.4.3
imports can be specified before defining the system
; this should
be the preferred way. Note, for backward compatibility, imports can be specified
after the system definition, but it is better not to mix the two forms.
For example:
import static org.eclipse.xtext.EcoreUtil2.*
...
// simply use, for example
getContainerOfType(o, typeof(EClass))
// instead of
EcoreUtil2.getContainerOfType(o, typeof(EClass))
You can also specify static extension imports (you may know such feature from Xtend, and it has exactly the same semantics), so that you can use all the imported methods as extension methods; for example
import static extension org.eclipse.xtext.EcoreUtil2.*
...
// use EcoreUtil2 static methods as extension methods
o.getContainerOfType(typeof(EClass))
Content assist for Java types is provided, which automatically inserts the corresponding import statement.
The menu "Organize Imports" is also available for Xsemantics files.
A copyright header string can be specified that will be added in the generated Java code as a header comment:
copyright "(C) This is a test\ncopyright header."
You can then specify fields in an Xsemantics system, using basically the same syntax of Xbase variable declarations. Here are some examples:
var MyClass foo // non final field
// for final field you must specify an initialization expression
val MyOtherUtilityClass bar = new MyOtherUtilityClass()
// the type can be omitted: it will be inferred from the initialization expression
// (in that case, the initialization expression is mandatory)
val bar2 = new MyOtherUtilityClass()
Fields can also be declared as extension
(you may know such feature
from Xtend, and it has exactly the same semantics), so that you can
use all the methods of the corresponding type as extension methods.
For example, assuming this class
import java.util.List;
public class MyExtensions {
public void printList(List<Object> l) {
// ...
}
}
We can write
/* a utility field */
var extension MyExtensions myextensions = new MyExtensions
...
rule EObjectEClass
G |- EObject o : EClass c
from {
val list = newArrayList()
list.printList // printList comes from MyExtensions
}
Fields can be annotated with Java annotations, e.g.,
@Inject
var MyClass foo
As a shortcut for injected fields, you can declare inject
fields
(extension
fields):
inject MyClass foo
inject extension MyOtherUtilityClass bar
Remember that these will correspond to fields in the generated Java class (ref:CodeGeneration[]), that will be instantiated through Guice injection.
In particular,
inject MyClass foo
can be seen as a shortcut for
@Inject
var MyClass foo
NOTE: Until version 1.7.1, only inject
fields could be specified; the more general
syntax for field definition has been introduced in version 1.7.2.
Then, you have to declare the judgments of your system; a judgment consists of
- a name, which has to be unique in the system;
- a judgment symbol that can be chosen from some predefined symbols;
- the parameters of the judgment; parameters of a judgments are separated by a relation symbol that can be chosen from some predefined symbols;
The parameters can be
- input parameters, in that case they are declared as Java parameters;
- output parameters, in that case you use the keyword
output
followed by the Java type of the output parameter.
For instance, these are some examples of judgments, using the classes from EMF Ecore, and other Java classes
system my.test.System
import org.eclipse.emf.ecore.*
import java.util.*
judgments {
type |- EObject obj : output EClassifier
subtype |- EObject left <: EObject right
interpret |- EObject exp ~> output EObject
// nonsense judgment, just a demo
foobar ||- List<EObject> objects |> output String <~ EClass context
}
A judgment can have at least one input parameter, and at most three output parameters (the latter limitation does not seem to have an impact, since it is rare to want more than three ouput parameters, but of course, it is easy to remove this limitation in case).
A judgment without output parameters is also called predicate judgment.
Once you declared the judgments of your system, you can start declaring the rules.
Each rule consists of
- a name, which has to be unique in the system;
- a rule conclusion;
- the premises of the rule;
The rule conclusion consists of
- the name of the environment of a rule (any identifier, see also ref:CodeGeneration[the section about rule environment]);
- a judgment symbol;]
- the parameters of the rules, which are separated by a relation symbol that can be chosen from some predefined symbols;
The things that make a rule belong to a specific judgment are, as you can imagine, the judgment symbol, the relation symbols (which separate the parameters); moreover the types of the parameters of a rule must be (Java) subtypes of the corresponding types of the judgment (or exactly the same Java types). Two rules belonging to the same judgment must differ for at least one input parameter's type.
rule MyFirstTypeRule // a rule for the judgment 'type'
G |- EObject o : EClassifier result
from { /* premises */ }
rule MySecondTypeRule // a rule for the judgment 'type'
// OK since EClass is a subclass of EObject
// and EClass extends EClassifier
G |- EClass o : EClass result
from { /* premises */ }
rule MyFirstWrongTypeRule // a rule for the judgment 'type'
// ERROR: Notifier is not a subtype of EObject
G |- Notifier n : EClass result
from { /* premises */ }
rule MySecondWrongTypeRule // a rule for the judgment 'type'
// ERROR: it differs from MyFirstTypeRule only for the
// output parameter type: EClassImpl
G |- EObject o : EClass result
from { /* premises */ }
rule MyFirstSubtypeRule // a rule for the judgment 'subtype'
G |- EObject o1 <: EObject o2
from { /* premises */ }
rule MySecondSubtypeRule // a rule for the judgment 'subtype'
G |- EClass c1 <: EClass c2
from { /* premises */ }
rule MyThirdSubtypeRule // a rule for the judgment 'subtype'
G |- EObject o1 <: EClass c2
from { /* premises */ }
The premises of a rule which are specified in a from
block can be
any Xbase expression, or a rule invocation (see also ref:RuleInvocations[]).
If you think of a rule
declaration as a function declaration, then a rule invocation corresponds
to function invocation, thus you must specify the environment to pass
to the rule, and the arguments, both input and output arguments.
At runtime, the system will select the most appropriate rule according to the runtime types of the passed argument (similar to polymorphic dispatch mechanism).
In the premises you can assign values to the output parameters; and when you invoke another rule, upon return, the output arguments will have the values assigned in the invoked rule.
If one of the premises fails, then the whole rule will fail, and in turn all the stack of rule invocation will fail. If the premise is a boolean expression, it will fail if the expression evaluates to false (see also ref:CodeGeneration[the section about boolean expressions] for a more detailed explanation). If the premise is a rule invocation, it will fail if the invoked rule fails.
For instance, consider these implementations:
rule MyFirstTypeRule // a rule for the judgment 'type'
G |- EObject o : EClassifier result
from {
o.eClass != null
result = o.eClass
}
This rules requires that the feature eClass0
of the passed EObject
is not null, and the assigns the output parameter result
.
rule MyFirstSubtypeRule // a rule for the judgment 'subtype'
G |- EObject o1 <: EObject o2
from {
G |- o1 <: o2.eClass
}
This rule simply invokes another rule (in this case of the same judgment), passing arguments (note that arguments can be any Xbase expression).
rule MyThirdSubtypeRule // a rule for the judgment 'subtype'
G |- EObject o1 <: EClass c2
from {
var EClass o1Type
G |- o1 : o1Type
G |- o1Type <: c2
}
This rule declares a local variable o1Type
which will be passed (as an output
argument) to a rule invocation (of another judgment: indeed rules can invoke
any other rule in the system, even belonging to a different judgment).
If the rule invocation succeeds, the local variable o1Type
will contain
the output result of the invoked rule. Then, it will invoke another rule.
rule MySecondSubtypeRule // a rule for the judgment 'subtype'
G |- EClass c1 <: EClass c2
from { /* premises */ }
The rule conclusion elements corresponding to output parameters can also be Xbase expressions; for instance we could have written (compare it with the above code for the same rule).
rule MyFirstTypeRule // a rule for the judgment 'type'
G |- EObject o : o.eClass
from {
o.eClass != null
}
Moreover, if a rule does not require any premise, we can use a special form of rules, called indeed _axiom_s
// assuming we don't want to do any check
axiom MyFirstTypeRule // a rule for the judgment 'type'
G |- EObject o : o.eClass
.
The premises of an Xsemantics rule are considered in logical and relation and are verified in the same order they are specified in the block.
section2:OrBlocks[Or Blocks]
If we want to have premises (or blocks of premises) in logical or relation,
we can use the operator or
, which separates blocks of
premises; if (and only if) one of the premises of the first block fails, we
go to the second block, etc.
section2:ExpressionsInPremises[Expressions in Premises]
Xsemantics considers an Xbase expression as a boolean premise when it is used as a statement, i.e., it is not the right expression in an assignment, it is not the condition of a loop, it is not the expression of an if, etc.. This holds when the expression is used in a block, NOT in a closure block. It is crucial to understand when a boolean expression is considered as a premise since when a premise fails the whole rule fails.
...
from {
a != b // boolean premise
var c = (a != b) // NOT boolean premise
if (a != b) ... // NOT boolean premise
list.forEach \[ a != b \] // NOT boolean premise
// but, be careful:
list.forEach \[
a != b // NOT boolean premise
{ a != b } // boolean premise
\]
}
section2:RuleInvocations[Rule Invocations]
Rule invocations have already been introduced. When rule invocations concern predicate judgments, then they can be used also in contexts where a boolean expression is expected.
Due to a (hopefully) temporary limitation of the Xsemantics parser, a rule invocation is not an expression, thus you need to included it in a block.
For instance
if ( G |- t1 <: t2 ) {... // SYNTAX ERROR
will issue an error, while this form is accepted:
if ( {G |- t1 <: t2} ) {... // OK, provided it refers to a predicate judgment
This limitation does not hold for lambda expressions, thus you can simply write:
mytypes.forall\[ t | G |- t <: MyType \]
mytypes.exists\[ t | G |- t <: MyType \]
with the expected semantics ("for all elements of mytypes
the predicate
judgment must hold", similarly "does an element, satisfying the judgement, exist?").
section2:Environment[Rule Environment]
The concept of rule environment is taken from the type theory
(usually is denoted by the capital greek letter Gamma, that is why
we usually use the letter G for the examples, but you can use any
valid identifier). It can be used to pass additional argument to rules.
If you want to be sure to pass an empty environment when invoking a rule
you can use the keyword empty
.
Furthermore, when passing an environment during a rule invocation, you
can specify additional environment mapping, using the syntax
key <- value
, where you can use any Xbase expression;
you can also pass an environment with additional
mappings separated by a comma (or even build an environment from scratch
by specifyin all the mappings, still separated by a comma); for instance
G, x <- 'foo', y <- 10
or x <- o.eClass, y <- (o.eClass.name == 'foo')
,
etc.
Note that when you pass an environment to a rule with additional mappings, you actually pass a brand new environment, thus you will not modify the current rule environment; if a mapping already exists in the current rule environment, in the brand new environment (and only there) the existing mapping will be overwritten. Thus, the rule environment passed to a rule acts in a stack manner.
Inside the premises, you can access the environment using the predefined
function env
with the following syntax
env(<environment ID>, <key>, <expected Java type>)
for instance,
rule MyFirstTypeRule // a rule for the judgment 'type'
G |- EObject o : o.eClass
from {
// search for the mapping for o.eClass.name
// and request it to be an instance of EClassifier
var i = env(G, o.eClass.name, EClassifier)
}
The env
function will fail (and make the rule fail) if no
mapping is found in the passed environment or if the mapped value
is not an instance of the specified Java class.
An example using the environment can be found in ref:MoreInvolvedExpressionsSystem[].
section2:ErrorSpecification[Error Specification]
When a premise of a rule fails, the whole chain of rule invocation will fail, and the Java code generated by Xsemantics will generate some informative errors, which by default, will contain a string representation of the failed rules (or boolean expressions) which have failed and a string representation of the arguments (see also ref:StringRepresentation[]).
However, usually you may want to customize error information genetated by your system; you can do this with a custom error specification which consists of
ul[
item[error
and a string for the error]
item[source
and an codeRef[org.eclipse.emf.ecore.EObject] specifying the object the error refers to
(optional)]
item[feature
and an codeRef[org.eclipse.emf.ecore.EStructuralFeature] specifying the feature of the object
containing the error (optional)]
item[data
and any Java codeRef[java.lang.Object] containing any additional information you want to
store in the error (optional), since version 1.7.0]
]
Error specifications can appear
ul[ item[after a judgment, so that all rules belonging to that judgment will have errors generated according to the error specification] item[after a rule conclusion, in that case this will have the precedence over the judgment's error specification] item[after an explicit failure (ref:ExplicitFailure[])] ]
judgments {
type |- EObject obj : output EClassifier
error "cannot type " + stringRep(obj)
source obj
subtype |- EObject left <: EObject right
error stringRep(left) + " is not a subtype of " + stringRep(right)
}
rule EClassCase
G |- EClass eClass : EClassifier result
error "cannot type EClass " + eClass.name
source eClass
feature EcorePackage::eINSTANCE.ENamedElement_Name
data "Some additional data for the error"
from {...}
section2:ExplicitFailure[Explicit Failure]
By using the keyword fail
you can implement an explicit failure
of the current premise block; After the fail
it would be good
to use also an error specification (ref:ErrorSpecification[]). Indeed,
explicit failure is really useful in conjunction with an error specification
(to specify an informative error) and an or
. For instance,
o.eClass.EAllSuperTypes.contains(s)
or
fail
error "my custom error"
source o
section2:PreviousFailure[Accessing Previous Failure of an Or Block]
Since version 1.7.0, in an or expression or block (see ref:OrBlocks[]) it is possible to access error information generated
in the previous or block. This can be achieved by using the implicit variable previousFailure
which is of type codeRef[org.eclipse.xsemantics.runtime.RuleFailedException] and can be assumed NOT null
.
Of course, such variable is available only in or blocks after the first one, unless the first or block
is itself contained in an outer or block.
This can be used together with all the other error features shown above; here's a simple example
{
...
} or {
...
fail
error "this error, caused by " + previousFailure.message
If you inject in your system definition, as extension, a codeRef[org.eclipse.xsemantics.runtime.TraceUtils] field (see ref:Fields[]) you can also use it to process the previousFailure; for instance
inject extension TraceUtils traceUtils
...
{...}
or
{
fail
error "this is the previous error trace: " +
previousFailure.failureTraceAsString
source object
// note the TraceUtils.failureTraceAsString
// used as an extension method
}
Other examples follow (also using the data
element of an error specification, ref:ErrorSpecification[]:
{
...
}
or
{
fail
error "data from failure: " +
previousFailure.getErrorInformations.head.getData
}
inject extension TraceUtils traceUtils
{
...
}
or
{
// the failure we're interested in is the last one
// we retrieve that using TraceUtils injected as extension
fail
error "data from failure: " +
previousFailure.failureAsList.last.errorInformations.head.^data
}
section2:StringRepresentation[String Representation]
The classes generated by Xsemantics, and its runtime system, rely on the class codeRef[org.eclipse.xsemantics.runtime.StringRepresentation] for generating a string corresponding to an object. This will do its best to generate a good string representation; in particular, if the object to represent is an EObject which actually corresponds to a node in the model of the AST, it will rely on codeRef[org.eclipse.xtext.nodemodel.util.NodeModelUtils] Xtext class to use the string which corresponds to the object in the program.
However, you can bind your own implementation of codeRef[org.eclipse.xsemantics.runtime.StringRepresentation] in the runtime module
public Class<? extends StringRepresentation> bindStringRepresentation() {
return MyDslStringRepresentation.class;
}
In your custom implementation you can provide a string representation for the
classes of your model; codeRef[org.eclipse.xsemantics.runtime.StringRepresentation] relies
on polymorphic dispatch, and you only need to implement methods stringRep
for specific classes of your model (actually for any Java class), e.g.,
public class MyDslStringRepresentation extends StringRepresentation {
protected String stringRep(MyModelClass e) {
return e.getName();
}
protected String stringRep(MyOtherModelClass e) {
return e.getFoo() + " - " e.getBar();
}
protected String stringRep(String s) {
return "'" + s + "'";
}
}
Note that these methods are protected: if one needs to use
codeRef[org.eclipse.xsemantics.runtime.StringRepresentation] directly, this class
provides the public method String string(Object o)
.
In an Xsemantics system, you can get a string representation (through the
injected codeRef[org.eclipse.xsemantics.runtime.StringRepresentation]) by simply
calling stringRep()
; this is useful for error specifications
(ref:ErrorSpecification[]), for instance
judgments {
type |- EObject obj : output EClassifier
error "cannot type " + stringRep(obj)
}
section2:GetAll[Graph Closures (getAll)]
Typically, in a DSL, when performing checks or interpretation, we need to
follow recursively a certain feature (e.g., superType
of
an entity in the domain model example); however,
the AST for an Xtext language might be a graph due to references,
and thus it might contain cycles (without implying that the program is incorrect).
Thus, such recursive inspection of the graph might lead to an infinite loop.
In a manual Java implementation we thus would have to keep track of the visited
nodes while inspecting the graph (to compute the closure of the graph).
To compute the ``closure'' of a graph, Xsemantics provides a predefined function which allows to collect nodes in a graph according to EMF features avoiding loops:
getAll(eObject, feature to collect, feature to follow, expected type)
An invocation of getAll
will return a list of expected type'', built by collecting all the elements from
feature to collect'' of the
specified eObject'', and recursively collecting such elements by following the feature
feature to follow'', but avoid possible loops in the EMF graph
representing the AST.
An example of use of this predefined function can be found in ref:FJTyping[].
section:Auxiliary[Auxiliary Functions]
Before declaring judgments, one can also declare some auxiliary function signatures (auxiliary descriptions), which have the following shape:
// auxiliary descriptions
auxiliary {
myFun(<parameters>) : <returntype>
myFun2(<parameters>) /* no return type */
}
Then, before defining the rules for your system, you can specify auxiliary function implementations for the corresponding auxiliary descriptions.
In an auxiliary function definition the return type must not be declared (the one specified in the corresponding auxiliary description will be used).
// auxiliary functions examples
auxiliary myFun(EObject o, String s) { ... }
auxiliary myFun(EClass o, String s) { ... }
The things that make an auxiliary function belong to a specific auxiliary description are the name and the types of the parameters, which must be (Java) subtypes of the corresponding types of the description (or exactly the same Java types). Two auxiliary functions belonging to the same description must differ for at least one parameter's type.
The body of an auxiliary function uses the same syntax of rule premises (ref:Premises[]).
An auxiliary function can then be called in premises as a standard Java method.
Auxiliary functions are used in the FJ example (ref:FJTypeSystem[]).
section:CheckRules[Rules for Validator: checkrule]
In a Xsemantics system you can also write special rules, _checkrule_s, which
do not belong to any judgment, and are then used to generate @Check
methods
of the generated Java validator.
Such rules are of the shape
checkrule RuleName for
JavaType objectToCheck
from {
// premises
}
The from
block is just the same as in standard rules, but remember
that these check rules do not receive any rule environment.
Xsemantics will generate a Java validator with a @Check
method for each
checkrule
(see also ref:GeneratedValidator[]);
just like in Java validator for Xtext languages, you can have
many checkrule
s for the same JavaType (provided the rule name is unique).
section:SystemExtension[Extend another System]
When defyining a system it is also possible to extend another
Xsemantics system, using extends
followed by
the fully qualified name of the system to extend.
For instance, given this system
system my.test.System
import org.eclipse.emf.ecore.*
import java.util.*
judgments {
type |- EObject obj : output EClassifier
subtype |- EObject left <: EObject right
interpret |- EObject exp ~> output EObject
}
axiom EObjectType
G |- EObject obj : obj.eClass
one can specify another system in terms of this one:
system my.test.ExtendedSystem extends my.test.System
...
An extended system implicitly inherits from the super system all judgments and rules (and check rules).
In the extended system one can override
a judgment and
any rules and check rules; in all cases the name and the kind of the redefined
element must be the same. An axiom in a super system can be turned into
a rule in the extended system and vice versa.
If in an extended system we try to declare a judgment, rule, checkrule
with the same name of the ones present in the super system, without
override
, we will get an error.
For instance, we can override a judgment of the super system changing the names of parameters and error specifications; similarly for rules and check rules (and provide a different implementation):
system my.test.ExtendedSystem extends my.test.System
import org.eclipse.emf.ecore.*
import java.util.*
judgments {
// we override with different param name and error specification
override type |- EObject eObject : output EClassifier
error "cannot type"
source eObject
}
// we override the axiom in the supersystem
// and turn it into a rule with premises
override rule EObjectType
G |- EObject o : o.eClass
from {
// some premises
}
An example of system extension will be shown in ref:MoreInvolvedExpressionsSystem[].
section:CodeGeneration[Generated Code]
Xsemantics will generate two Java classes for each xsemantics system:
ul[
item[A Java class with the same name of the system, containing
all the implementations of the system's rules and auxiliary functions;]
item[A codeRef[org.eclipse.xtext.validation.AbstractDeclarativeValidator]
subclass,
with the same name of the system with suffix Validator
(in particular, if the
validatorExtends] clause, ref:validatorExtends[
,
is not used,
then the generated validator inherits from
codeRef[org.eclipse.xtext.validation.AbstractDeclarativeValidator],
otherwise it will inherit from the one specified with validatorExtends]) ;
]
The generated classes rely on Google injection, so you must not instantiate them directly, but only via injection.
Note that the Java code generated by Xsemantics will depend only
on the plugin org.eclipse.xsemantics.runtime
. Thus, in case you want to deploy
your Xtext language implemented making use of Xsemantics, your language will
not need to depend on the whole Xsemantics SDK.
section2:GeneratedPackag_The Generated Java Packages_
Xsemantics will generate the Java classes into Java packages according to the fully qualified name specified for your system.
For example, given this system fully qualified name
system org.eclipse.xsemantics.example.fj.typing.FjTypeSystem
The generated Java class for the rule system (ref:GeneratedSystem) will
be generated in the Java package org.eclipse.xsemantics.example.fj.typing
and
the generated Java validator (ref:GeneratedValidator) will be generated in the
package org.eclipse.xsemantics.example.fj.typing.validation
.
If you plan to reuse these Java classes in other Eclipse projects (e.g., in the UI project or in the test project), please make sure to export such Java packages in the MANIFEST.MF.
section2:GeneratedSystem[The Generated Java Rule System]
The generated Java class containing the rules of your system will have public methods for the judgments and auxiliary functions of your system. For instance, if you have
ul[ item[a judgment called myjudgment] item[which takes two input parameters of Java types MyClass1 and MyClass2] item[and an output parameter of Java type MyOutputClass] ]
judgments {
myjudgment |- MyClass1 arg1 : MyClass2 arg2 : output MyOutputClass
}
the generated Java system will feature three public methods
public Result<MyOutputClass> myjudgment(MyClass1 arg1, MyClass2 arg2)
public Result<MyOutputClass> myjudgment(RuleEnvironment env,
MyClass1 arg1, MyClass2 arg2)
public Result<MyOutputClass> myjudgment(
RuleEnvironment env, RuleApplication trace,
MyClass1 arg1, MyClass2 arg2)
The class codeRef[org.eclipse.xsemantics.runtime.Result] is part of Xsemantics runtime,
and it is a wrapper for the actual result value (that can be retrieved
with method getValue()
) and a possible failure
(in the shape of a codeRef[org.eclipse.xsemantics.runtime.RuleFailedException]).
A similar generation patterns is used for auxiliary descriptions (but the returned type will NOT be wrapped into a codeRef[org.eclipse.xsemantics.runtime.Result] and codeRef[org.eclipse.xsemantics.runtime.RuleFailedException] is declared to be thrown).
The value in the result will be null
if the judgment rule failed;
in that case you might want to inspect the codeRef[org.eclipse.xsemantics.runtime.RuleFailedException].
In particular, you might want to use the utility methods of
codeRef[org.eclipse.xsemantics.runtime.TraceUtils] to get the stack of all
the rule failures (also already formatted as an indented string).
In case the judgment has two output parameters, the class
codeRef[org.eclipse.xsemantics.runtime.Result2] will be used which acts as a pair
(thus the two values can be obtained with methods getFirst()
and
getSecond()
, respectively); similarly for three output
parameters, codeRef[org.eclipse.xsemantics.runtime.Result3] (with the
additional method getThird()
).
If the judgment has no output parameter (predicate judgment), the type of the result will
be codeRef[java.lang.Boolean], i.e., Result<Boolean>
.
For such judgments some additional utility methods are generated, which allow you to just check whether the predicate succeded (in case of failures you have no additional information; but if you pass a trace you can still inspect how the judgment succeded). For instance, consider this predicate judgment
judgments {
mypredjudgment |- MyClass1 arg1 : MyClass2 arg2
}
the generated Java system will feature three additional public methods (note the suffix "Succeded"):
// same generated methods as before, plus:
public Boolean myjudgmentSucceeded(MyClass1 arg1, MyClass2 arg2)
public Boolean myjudgmentSucceeded(RuleEnvironment env,
MyClass1 arg1, MyClass2 arg2)
public Boolean myjudgmentSucceeded(
RuleEnvironment env, RuleApplication trace,
MyClass1 arg1, MyClass2 arg2)
The first generated method basically only takes the arguments specified in the
judgment. With the second version, you can also pass an environment
(generated Java code can transparently deal with null environments).
The third one is useful for testing/debugging purposes: if you pass
an instance of codeRef[org.eclipse.xsemantics.runtime.RuleApplicationTrace] if the
method terminates with success you can then inspect the trace of all the rules
invoked together with the values used in the rules. If the judgment fails,
you will see also the rules that failed in the rule application trace.
By using the method traceAsString(RuleApplicationTrace)
of
codeRef[org.eclipse.xsemantics.runtime.TraceUtils] you will have a string representation
indented, with the following idea
final result provided by rule MyRule
rule 1 used by MyRule to get to the result
rule 2 used by rule 1
rule 3 used by rule 2
...
rule 1a used by MyRule to get to the result
rule 2a used by rule 1a
...
An example of usage of traces is shown in ref:ExpressionsCodeGeneration[] for the Expressions language example.
The generated Java system will contain also issue codes (as public string constants) that will be used by the generated validator when a rule fails. This can be useful to then implement quickfixes for specific failures.
section2:GeneratedValidator[The Generated Java Validator]
From a system definition Xsemantics also generates a Java
class (which extends codeRef[org.eclipse.xtext.validation.AbstractDeclarativeValidator]).
If you plan to use this generated validator, because you defined
some _checkrule_s (ref:CheckRules[]), it is better to specify
the validatorExtends
clause in order to have
a validator which extends the validator that Xtext generated for you
in the src-gen
folder.
This is detailed in ref:validatorExtends[]. If this clause is not
specified, using the generated validator requires some more work,
as detaild in ref:withoutValidatorExtends[].
section3:validatorExtends[The validatorExtends clause]
Recently, the
validatorExtends
clause was introduced in order
to specify the base class of the generated validator.
Using this clause (right after the system
definition),
the generated validator will be already easily usable: all that remains
to do is to modify your DSL validator in your src
folder
so that it extends the validator by Xsemantics.
Note that validatorExtends
cannot be used
together with system extension (ref:SystemExtension[]): in case of
system extension, the generated validator will automatically extend
the generated validator of the super system.
The validatorExtends
clause requires a
codeRef[org.eclipse.xtext.validation.AbstractDeclarativeValidator]
subclass (the content assist will filter completions accordingly).
The class which should be specified is the class which Xtext
generated for you in the src-gen
folder
(which should be of the shape AbstractMyDslJavaValidator
).
Then, the validator class in the src
folder should be modified
so that it extends the validator class generated by Xsemantics.
For instance, if your validator initially looks like:
public class MyDslJavaValidator extends AbstractMyDslJavaValidator {
In your system definition you should specify
system org.my.dsl.MyDslSemantics
validatorExtends org.my.dsl.validation.AbstractMyDslJavaValidator
And then, your original Java validator (in the src
folder)
should be modified so that it
extends the validator generated by Xsemantics:
public class MyDslJavaValidator extends MyDslSemanticsValidator {
The examples described in ref:Expressions[] and in
ref:Lambda[] use the validatorExtends
clause.
section3:withoutValidatorExtends[Without the validatorExtends clause]
If the
validatorExtends
clause is not used in the
Xsemantics system definition,
the validator generated by Xsemantics cannot be used (i.e., injected) directly,
since it does
not implement the method getEPackages
; so, considering the project
you have just created, the advised way of using it is to copy the getEPackages
from the abstract validator generated by Xtext, e.g., AbstractMyDslJavaValidator
,
into your MyDslJavaValidator
, and then make your validator inherit
from the validator generated by Xsemantics, e.g., MyDslSystemValidator
.
Summarizing, for this example your validator should look like this:
public class MyDslJavaValidator extends MyDslSystemValidator {
// copied from AbstractMyDslJavaValidator
@Override
protected List<EPackage> getEPackages() {
List<EPackage> result = new ArrayList<EPackage>();
result.add(org.xtext.example.mydsl.myDsl.MyDslPackage.eINSTANCE);
return result;
}
}
The example described in ref:FJ[] uses this technique.
A better way to use the generated validator is to rely on the
validatorExtends] clause as detailed in ref:validatorExtends[
.
section2:ErrorGeneration[Error Marker Generation]
The generated Java validator will also automatically generate error markers in case the corresponding rule invocations fail. Error markers will be generated according to the error information found in the trace of a failure (which is contained in a codeRef[org.eclipse.xsemantics.runtime.RuleFailedException]).
When generating error markers the validator will use only the error information
that related to an codeRef[org.eclipse.emf.ecore.EObject] which corresponds to
an actual node in the AST. Note that this error information can be customized in
a Xsemantics system by explicit error specifications (see ref:ErrorSpecification[]
and ref:ExplicitFailure[]), thus, you should keep in mind the above strategy when
using source
(and feature
) in an error specification.
Furthermore, the validator will not use ALL the error information referring to nodes in the AST: by default it will use the inner most error information referring to an actual node in the AST. This corresponds to using the error of the first rule invocation that failed, and this is usually what actually is important for generating an error.
For instance, if you consider the example of expressions (ref:Expressions[]) and the following expression which cannot be typed
i = (1 + 5) - (10 * true)
the generated validator will generated an error marker to the inner most
subexpression which made the typing failed, i.e., 10 * true
.
Remember that in an error specification (see ref:ErrorSpecification[]) you can
also specify the feature
for error generation, and in that case the
validator will use also the specified feature for generating the error marker.
The generated validator relies on an injected codeRef[org.eclipse.xsemantics.runtime.validation.XsemanticsValidatorFilter] to filter the error information according to the strategy defined above; you can customize the filtering by injecting your implementation. The filter relies on codeRef[org.eclipse.xsemantics.runtime.TraceUtils] which provides many utility methods to access the error trace. An example of customization is shown in ref:LambdaCustomization[].
If one needs the full control on how error markers are generated, a custom codeRef[org.eclipse.xsemantics.runtime.validation.XsemanticsValidatorErrorGenerator] can be injected.
section:Caching[Automatic Caching]
The automatic caching mechanism has been introduced in version 1.5.0 (it can be seen as an experimental feature).
Xsemantics provides automatic caching mechanisms that can be enabled in a system specification. The interface for Xsemantics cache is codeRef[org.eclipse.xsemantics.runtime.XsemanticsCache].
The default implementation of this interface, codeRef[org.eclipse.xsemantics.runtime.XsemanticsCacheDefaultImpl] uses internally the class codeRef[org.eclipse.xtext.util.OnChangeEvictingCache] that is part of the Xtext utility library. This class implements a cache that stores its values in the scope of a resource. The values will be automatically discarded as soon as the contents of the resource changes semantically.
When caching is enabled in Xsemantics system specification,
then Xsemantics will generated Java code that automatically
uses the cache.
caching is enabled on a per-judgment and per-auxiliary description basis,
using the keyword cached
.
Note that by default
caching is based on the Java hashing features, thus it makes sense
only when used with actual object instances, not with references.
It is responsibility
of the programmer to be aware of which judgments and auxiliary
functions to cache, depending on the nature of the involved input
parameters.
You can specify also a boolean condition
to enable
caching only when such condition holds. For judgments, it is also possible
to specify whether generated methods (ref:GeneratedSystem[]) for judgments
should be cached as well (otherwise caching is enabled only for the methods of
the single rules).
This is an example using all these features.
auxiliary {
eclasses(EObject o) : List<EClass> cached
auxnocacheentryPoints(EObject o) : Boolean cached { condition=(!(o instanceof EClass)) }
}
judgments {
type |- EObject o : output EClass cached
nocacheentryPoints ||- EObject o : output EClass cached { entryPoints=NONE }
withCacheCondition |= EObject o : output EClass
cached {
condition = (!environment.isEmpty() && !(o instanceof EClass))
}
withCacheConditionBlock |= EObject o :> output EClass
cached {
condition = { (!environment.isEmpty() && !(o instanceof EClass)) }
}
}
Xsemantics also ships with a version of FJ (see ref:FJ[]) where caching is enabled. This is actually implemented as a separate example (fjcached), which is an extension of the original FJ example (This is also another example of system extension, ref:SystemExtension[]).