Skip to content
Larry Bordowitz edited this page Jul 8, 2019 · 4 revisions

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.

Imports

You can specify imports 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.

Copyright

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."

Fields

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.

Judgments

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

.

Premises

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 checkrules 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[]).

Clone this wiki locally