This document indicates the steps to follow in order to reproduce the tutorial.
The result of this tutorial is available in the folder part3-mmfirst-solution in githug repo or as a download in the following zip.
In the end, you should obtain a debugger that should look like the following:
1. Prerequisite
This part follows the work done in part2. While you can start from your own work, I suggest to replace it and start from the solution of part2.
-
If you haven’t followed the previous tutorials tutorial_scenario_part1.html or tutorial_scenario_part1postprocess.html:
-
Install a Java JDK (minimum 17)
-
grab and unzip the latest release version of GEMOC Studio https://gemoc.org/download.html
-
-
If you have followed one the previous tutorial tutorial_scenario_part1.html or tutorial_scenario_part1postprocess.html:
-
Either use a brand new workspace or clean it by deleting the projects in it since the projects we will create will have the same names.
-
Then
-
download the file https://dvojtise.github.io/mde-crashcourse-logo/zips/part2-mmfirst-solution.zip
-
in Eclipse,
-
File → Import… → General → Existing projects into Workspace → Next
-
Select archive file → Browse and select the file part2-mmfirst-solution.zip you’ve downloaded
-
Finish
-
In case of trouble and error in your projects:
-
In the problem view, configure it in order to show only errors and warning on the selected project. This will help analyze the error (for example create missing empty folders or fix java minimum version)
-
you may have to regenerate the xText editor code (right click on the Logo.xtext file → Run as → Generate Xtext Artifacts)
2. General principles
We’ll create an execution semantic for the behavior or our Logo language.
It will provide features such as:
-
debugger
-
animation
In order to keep the tutorial as simple as possible, we consider that we can modify the metamodel directly. This is correct for most languages you create by your own, but in some situation you may prefer to keep a stricter separation between the static and dynamic parts of the language. GEMOC offers several strategies to achieve this but they are out of the scope of this tutorial. |
3. Define the dynamic semantic
3.1. Create a GEMOC Sequential project
-
File → New → GEMOC Java XDSML Project
-
name:
fr.inria.sed.logo.xdsml
-
-
Next
-
use the default template: Simple Sequential K3 language project
-
Ecore file location: select the ecore file in fr.inria.sed.logo.model/model/Logo.ecore
-
-
Finish
3.2. Create the project for domain specific action (DSA):
-
right click on the fr.inria.sed.logo.xdsml project → GEMOC Java xDSM_L → _create DSA project for language
-
next
-
Select template: User Ecore Basic Aspects
-
-
next
-
Aspect package prefix:
fr.inria.sed.logo.k3dsa
-
Aspect package suffix:
.aspects
-
Aspect file name:
LogoAspects
-
-
Finish
-
add missing dependency from project fr.inria.sed.logo.xdsml to fr.inria.sed.logo.k3dsa. (This action can be removed when modeldebugging bug #51 is fixed).
3.3. Write a GEMOC based method to "hello world"
In the k3dsa project,
add a plugin dependency to org.eclipse.gemoc.commons.eclipse.messagingsystem.api
open the logoAspects.xtend file.
add the following imports:
import fr.inria.diverse.k3.al.annotationprocessor.Main
Add a run method with @Main annotation in the class LogoProgramAspect.
@Main
def void run(){
// println('hello world')
val MessagingSystemManager msManager = new MessagingSystemManager
val ms = msManager.createBestPlatformMessagingSystem("Logo","Simple Logo interpreter")
ms.debug("Hello world on "+_self.eResource.URI, "Logo")
}
Launch the Modeling workbench.
In the modeling workbench, you can import some model examples from your previous projects or
It contains several logo files that will produce nice drawings. |
-
Run → Debug configurations…
-
Right click on Gemoc Sequential eXecutable Model → new configuration
-
Name: <your model file name>
-
model to execute: browse and select the model file
-
Languages: _fr.inria.sed.logo.Logo
-
animator: (optionnal) the .aird file that has a diagram for your model
-
Main method: select xxx.LogoProgramAspect.run(xxx)
-
Main model element path: the LogoProgramImpl
-
-
Debug
-
The console named "Simple Logo interpreter" will contain your output if you used the GEMOC MessagingSystem,
otherwise, printl
will go to the standard output which is shown by the Default MessagingSystem console.
you may have to switch between the console in order to retrieve the one with your message. |
3.4. Define Runtime Data structure
-
new Ecore Modeling Project
-
project name: fr.inria.sed.logo.vm.model
-
Main package name: vm
-
NsUris:
http://www.inria.fr/sed/logo/vm
-
If not already available in your Eclipse, installing OCLinEcore allows to edit the ecore model in a textual editor instead of using the tree editor or the graphical editor. In our case, this will help to to copy/paste actions.
A new editor is now available with a right click on |
3.4.1. Create a data structure to capture the runtime state of the turtle running the logo program.
The runtime will be turtle that also store the path it had drawn.
The path is stored as an ordered list of segments.
Some attributes need to be encoded as Double in order to get a simple but realistic simulation.

Instead of manually creating the various elements in the tree editor or in Sirius editor, you can directly
copy/paste this source in the
|
-
right click on the vm.genmodel file → reload…
-
rigth click on the root element
-
generate Model code
on the plugin.xml of the k3dsa project, add a dependency to fr.inria.sed.logo.vm.model.
3.4.2. Link the RuntimeData to the Logo program
Create an "anchor" element in the Logo program Logo.ecore. Ie. add an class RuntimeContext and a composition to it from the root model element. This runtimecontext is annotated with "aspect" annotation in order to indicate that it can change during the execution.
This is not mandatory for all execution scenarios but will help obtain all GEMOC features |
For some language you may directly weave runtime data in the language ecore. This might be useful to help navigation in the models and data. |
class LogoProgram
{
property instructions : Instruction[*|1] { ordered composes };
property runtimeContext : RuntimeContext[?] { composes };
{
annotation aspect;
}
}
abstract class RuntimeContext;
add a plugin dependencies from fr.inria.sed.logo.vm.model to fr.inria.sed.logo.model
import ecore : 'http://www.eclipse.org/emf/2002/Ecore#/' ;
import logo : '../../fr.inria.sed.logo.model/model/Logo.ecore#/' ;
package vm : vm = 'http://www.inria.fr/sed/logo/vm'
{
class InterpreterRuntimeContext extends logo::RuntimeContext
{
property turtle : Turtle[1] { composes };
property stack : ParamMap[*|1] { ordered composes }
}
regenerate model code of Logo and its VM (IE. from logo.genmodel and vm.genmodel files.)
when generating model from vm.genmodel, make sure to correctly reference and reuse the logo.genmodel. Otherwise you’ll get 2 copies of the java code for the logo.ecore model that may conflict with each other. |
you should end up with somthing similar to:
On every elements in the runtime data, add an EAnnotation "aspect". This will drive the display of the Variable view and the Multidimentional Timeline.
right click on an element, New Child → EAnnotation and then in the properties view, set the source to aspect.
Tip: once one annotation has been created you can use copy-paste to duplicate it.
import ecore : 'http://www.eclipse.org/emf/2002/Ecore' ;
import logo : '../../fr.inria.sed.logo.model/model/Logo.ecore#/' ;
package vm : vm = 'http://www.inria.fr/sed/logo/vm'
{
class InterpreterRuntimeContext extends logo::RuntimeContext
{
annotation aspect;
property turtle : Turtle[1] { composes }{ annotation aspect; }
property stack : ParamMap[*|1] { ordered composes } {annotation aspect; }
}
class Turtle
{
annotation aspect;
property reachedPoints : Point[*|1] { ordered composes }{annotation aspect; }
property position : Point[?]{annotation aspect; }
property segments : Segment[*|1] { ordered composes }{ annotation aspect;}
attribute penUp : Boolean[1]{ annotation aspect; }
attribute heading : ecore::EDouble[1] {annotation aspect; }
}
class Point
{
annotation aspect;
attribute x : ecore::EDouble[1]{annotation aspect;}
attribute y : ecore::EDouble[1]{annotation aspect;}
}
class Segment
{
annotation aspect;
property origin : Point[1]{annotation aspect;}
property destination : Point[1]{annotation aspect;}
}
class ParamMapEntry
{
annotation aspect;
attribute key : String[1] {annotation aspect;}
attribute value : ecore::EInt[1] = '0' { annotation aspect; }
}
class ParamMap
{
annotation aspect;
property entries : ParamMapEntry[*|1] { ordered composes }{ annotation aspect; }
}
}
3.5. Initialize RuntimeContext on start
In the k3dsa project.
@Aspect(className=LogoProgram)
class LogoProgramAspect {
@Step
@InitializeModel
def void initializeModel(EList<String> args){
val context = VmFactory.eINSTANCE.createInterpreterRuntimeContext
context.turtle = VmFactory.eINSTANCE.createTurtle
val point = VmFactory.eINSTANCE.createPoint
point.x = 0
point.y = 0
context.turtle.reachedPoints.add(point)
context.turtle.position = point
_self.runtimeContext = context
}
3.6. Write a simple navigation
for better performances and cleaner code, the logger accessor can be moved to the context as a "singleton"
|
@Aspect(className=LogoProgram)
class LogoProgramAspect {
@Step
@Main
def void run(){
val context = _self.runtimeContext as InterpreterRuntimeContext
context.logger.debug("Running "+_self.eResource.URI, "Logo")
_self.instructions.forEach[i | i.run(_self.runtimeContext as InterpreterRuntimeContext)]
}
}
@Aspect(className=Instruction)
class InstructionAspect {
@Step
def void run(InterpreterRuntimeContext context){
context.logger.error("run of " +_self +" should never occur, please write method run for this class",
"Logo")
}
}
@Aspect(className=Expression)
class ExpressionAspect {
def Integer eval(InterpreterRuntimeContext context){
context.logger.error("eval of " +_self +" should never occur, please write method run for this class",
"Logo")
return 0;
}
}
@Aspect(className=If)
class IfAspect extends ControlStructureInstructionAspect {
@Step
def void run(InterpreterRuntimeContext context){
context.logger.debug("run of " +_self, "Logo")
if(_self.condition.eval(context) == 1) {
_self.thenPart.run(context)
} else {
_self.elsePart.run(context)
}
}
}
@Aspect(className=Constant)
class ConstantAspect extends ExpressionAspect {
def Integer eval(InterpreterRuntimeContext context){
context.logger.debug("eval of " +_self, "Logo")
return _self.integerValue
}
}
We put @Step only on |
3.7. implements eval methods of classes that inherit from Expression
This is quite simple, most of them maps to very simple code in java/xtend.
@Aspect(className=Plus)
class PlusAspect extends ExpressionAspect {
def Integer eval(InterpreterRuntimeContext context){
return _self.lhs.eval(context) + _self.rhs.eval(context)
}
}
@Aspect(className=Minus)
class MinusAspect extends ExpressionAspect {
def Integer eval(InterpreterRuntimeContext context){
return _self.lhs.eval(context) - _self.rhs.eval(context)
}
}
For boolean expressions, we simpliflied the problem in the metamodel by returning only integer, where 0 is false and 1 is true.
@Aspect(className=Equals)
class EqualsAspect extends ExpressionAspect {
def Integer eval(InterpreterRuntimeContext context){
if( _self.lhs.eval(context) == _self.rhs.eval(context)) return 1
else return 0
}
}
@Aspect(className=Greater)
class GreaterAspect extends ExpressionAspect {
def Integer eval(InterpreterRuntimeContext context){
if( _self.lhs.eval(context) > _self.rhs.eval(context)) return 1
else return 0
}
}
3.8. Make the turtle move
Ie. modify the runtime context (turtle, segment, …)
First add some helpers as aspect directly on the vm.
package fr.inria.sed.logo.k3dsa.logo.vm.aspects
import fr.inria.diverse.k3.al.annotationprocessor.Aspect
import fr.inria.sed.logo.vm.model.vm.Turtle
import fr.inria.sed.logo.vm.model.vm.VmFactory
@Aspect(className=Turtle)
class TurtleAspect {
def void rotate(Integer angle) {
_self.heading = (_self.heading + angle) % 360
}
def void move(double dx, double dy){
// create new Point for destination
val point = VmFactory.eINSTANCE.createPoint
point.x = _self.position.x + dx
point.y = _self.position.y + dy
_self.reachedPoints.add(point)
if(!_self.penUp){
val drawnSegment = VmFactory.eINSTANCE.createSegment
drawnSegment.origin = _self.position
drawnSegment.destination = point
_self.segments.add(drawnSegment)
}
_self.position = point
}
def void forward(Integer steps){
val headingAsRadian = Math.toRadians(_self.heading)
_self.move(_self.scale(steps, Math.sin(headingAsRadian)), _self.scale(steps, Math.cos(headingAsRadian)))
}
/**
* scale the "steps" expressed using integer by a factor
*/
def double scale(Integer steps, Double factor){
return (steps.doubleValue * factor) as Double
}
}
Then use them.
import static extension fr.inria.sed.logo.k3dsa.logo.vm.aspects.TurtleAspect.*
@Aspect(className=Forward)
class ForwardAspect extends PrimitiveInstructionAspect {
@Step
def void run(InterpreterRuntimeContext context){
context.turtle.forward(_self.steps.eval(context))
}
}
@Aspect(className=Forward)
class BackwardAspect extends PrimitiveInstructionAspect {
@Step
def void run(InterpreterRuntimeContext context){
context.turtle.forward(- _self.steps.eval(context))
}
}
@Aspect(className=Left)
class LeftAspect extends PrimitiveInstructionAspect {
@Step
def void run(InterpreterRuntimeContext context){
context.turtle.rotate(- _self.angle.eval(context))
}
}
@Aspect(className=Right)
class RightAspect extends PrimitiveInstructionAspect {
@Step
def void run(InterpreterRuntimeContext context){
context.turtle.rotate(_self.angle.eval(context))
}
}
3.9. Get dedicated custom GUI (using EngineAddon)
documentation about engine addon creation https://download.eclipse.org/gemoc/docs/nightly/_contributing.html#_developing_new_engines |
There are many ways to create a GUI for the simulator. One of them is to create a language specific engine addon. It will be started automatically when the engine starts. It will then be notified by the engine about any relevant event. It has access to many informations including a full access to the model and runtime data model.
-
open the plugin.xml file of the project
fr.inria.sed.logo.xdsml
-
Right click on the XDSML_Definition (fr.inria.sed.logo.Logo) → New → EngineAddon_Definition
-
Click on the link (blue) _engineAddon_class to create the missing class
-
Package: fr.inria.sed.logo.xdsml.ui.turtleboard
-
Name: TurtleBoardEngineAddon
-
-
Due to: https://github.com/eclipse/gemoc-studio-modeldebugging/issues/44 remove import, and then apply quick fix to retrieve the correct import ( org.eclipse.gemoc.xdsmlframework.api.engine_addon.IEngineAddon ).
-
in the TurtleBoardEngineAddon java class
-
Right click in the editor
-
source → override/implements methods
-
select
engineStarted
,engineAboutToDispose
, andstepExecuted
-
implement the methods to call a GUI reading the model in the engine
-
copy the simple AWT UI implementation from https://github.com/dvojtise/mde-crashcourse-logo/tree/master/part3-mmfirst-solution/fr.inria.sed.logo.xdsml/src/fr/inria/sed/logo/xdsml/ui/turtleboard also copy the
engineStarted
,engineAboutToDispose
, andstepExecuted
content. -
You can observe in TurtleBoardEngineAddon.java How to access the model and runtime data.
-
-
-
Callbacks to addons methods create pauses in the execution. You must take care to not crash in an addon, otherwise the execution will crash too. You must take care to long running process and consider using threads/jobs for them (unless this is an intended behavior of you UI). |
In the modeling workbench, launch an execution on a simple logo model to obser this simple GUI.
More complexe GUI can be written, for example by creating a view integrated in eclipse.
3.10. implement ProcedureCall
3.10.1. add a stack of parameter maps in the runtime context
class InterpreterRuntimeContext extends logo::RuntimeContext
{
property turtle : Turtle[1] { composes };
attribute stack : ParamMap(String, ecore::EIntegerObject)[*|1] { ordered !unique };
}
datatype ParamMap(K, V) : 'java.util.HashMap' { serializable };
You can write this kind of code with generics directly in the tree editor, for this you must open the vm.ecore files with the "sample reflective editor" and in the top menu, then click on sample reflective editor and Show generics |
Add some helpers methods to manipulate this stack.
/* paramMap helpers */
def void pushParamMap(HashMap<String, Integer> paramMap) {
_self.stack.add(paramMap)
}
def HashMap<String, Integer> peekParamMap(){
_self.stack.last
}
def HashMap<String, Integer> popParamMap(){
_self.stack.last
_self.stack.remove(_self.stack.size -1)
}
3.10.2. Use the parameter map to implement the Procedure Call
import static extension fr.inria.sed.logo.k3dsa.logo.vm.aspects.InterpreterRuntimeContextAspect.*
@Aspect(className=ProcCall)
class ProcCallAspect extends PrimitiveInstructionAspect {
@Step
def void run(InterpreterRuntimeContext context){
context.logger.debug("run of " +_self, "Logo")
val HashMap<String, Integer> params = newHashMap;
(0..(_self.actualArgs.size-1)).forEach[i |
val currentArg = _self.actualArgs.get(i).eval(context)
params.put(_self.declaration.args.get(i).name,currentArg)
]
context.pushParamMap(params)
_self.declaration.instructions.forEach[instruction | instruction.run(context)]
context.popParamMap()
}
}
@Aspect(className=ParameterCall, with=#[InstructionAspect] )
class ParameterCallAspect extends ExpressionAspect {
def Integer eval(InterpreterRuntimeContext context){
context.logger.debug("eval of " +_self, "Logo")
return context.peekParamMap.get(_self.parameter.name);
}
}
3.11. Add Sirius Debug support.
This will create a dedicated layer that take into account debug interactions
-
Right click on the fr.inria.sed.logo.xdsml project → GEMOC language → Create animator project for language
-
Add a debug layer to an existing diagram description → Next → Finish
-
3.12. Add Sirius animation
Using the same principle of layers, one can create dedicated diagram or layer for the runtime data.
for example: the FSM example available as default in the "Examples" in the studio, uses this technique to highlight the current State with a green arrow.
Anyway for our turtle, this will be less impressive than the dedicated blackboard UI.
Add new animator service class
package fr.inria.sed.logo.design.services;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.gemoc.executionframework.extensions.sirius.services.AbstractGemocAnimatorServices;
public class LogoblockdiagramAnimatorServices extends AbstractGemocAnimatorServices {
@Override
protected List<StringCouple> getRepresentationRefreshList() {
final List<StringCouple> res = new ArrayList<StringCouple>();
res.add(new StringCouple("LogoBlockDiagram", "Animation"));
return res;
}
}
In the logo.odesign file:
on the LogoBlockViewpoint,
-
New extension
-
New Java Extension, set the name to the java class:
fr.inria.sed.logo.design.services.LogoblockdiagramAnimatorServices
-
on the LogoBlockDiagram
-
New diagram element
-
additional layer
-
Id:
Animation
(must be the same as the one declared inres.add(new StringCouple("LogoBlockDiagram", "Animation"));
-
-
-
New diagram element
-
Container
-
Id:
ProgramRuntimeContainer
-
domain class:
logo::LogoProgram
-
sematic expression:
[self/]
-
children representation:
List
-
-
on the container
-
New Style :
Gradient
-
Label:
aql:'RuntimeOverview'
-
-
New diagram element
-
Sub node:
-
Id:
TurtlePosition
-
domain class:
vm::Turtle
-
-
-
new Style
-
Basic shape
-
Label:
aql:'position x='+self.position.x+' y='+self.position.y
-
-
-
New diagram element
-
Sub node:
-
Id:
TurtleHeading
-
domain class:
vm::Turtle
-
-
-
new Style
-
Basic shape
-
Label:
aql:'heading '+self.heading
-
-
-
New diagram element
-
Sub node:
-
Id:
TurtlePen
-
domain class:
vm::Turtle
-
-
-
new Style
-
Basic shape
-
Label:
aql:'pen '+self.penUp
-
-
Repeat this last part in order to also display the heading and the pen status (activated or not)