Thursday 4 June 2015

Tutorial: Language Development with XText: A Logo example - Part II: Getting to Know Logo and Our Solution Approach


A Little of Context

This language was developed as an example language to teach DSLs in the context of Public University subject in Argentina for which some of us are teachers.

We wanted to show a small yet powerful interpreted language.
And one of the goal was to shows something that will produce some visual effect, so that the student could really appreciate the execution of the language.

But we didn't want to actually spend a lot of time on the graphical part, creating a logo from scratch
So we just went ahead and searched on Google for an existing Logo project, which was JVM compatible.
We will just then create the XText language and use an interpreter to read or semantic model and call that Logo to perform the drawing.

That was exactly what we did.

Tortue

So as backend logo we used Tortue a GPL open source project


Here's a screenshot from their site


It actually comes as a full standalone java app.
But we will just use the classes as if it was an API.
And also make him draw into a canvas that we will embed into the running eclipse.

The Language Grammar

The logo language is kind of mid-sized. It's not a so small as a common DSL, but it is neither much complex or big as a typed GPL language.
Still it is a good exercise since it has many elements of a full blown GPL imperative language like C, pascal, Java, etc.

We will see here a couple of example programs to understand the language:

Moving the Turtle


This is a really simple program to start getting familiar with the language

PENDOWN

FORWARD 80
RIGHT 90
FORWARD 80
RIGHT 90
FORWARD 80
RIGHT 90
FORWARD 80

PENUP

If you execute this

It will draw the following:



So PENDOWN and PENUP are special Drawing Sentences that control when the turtle should start drawing and when it is not.
They have side-effect, meaning that they change the state of the turtle to "drawing" or just "moving without drawing".
The the others: RIGHT, LEFT, FORWARD, are Move operations for controlling the turtle.

So, to start seeing some xtext code here is a rule that put together all these moves 

MOVE:
FORWARD | LEFT | RIGHT | SET_X | SET_Y

;

This is part of our new language grammar in Xtext.
We will see each actual rule in the next section.

Using variables

Now we can change the previous program to avoid repeating the size of each side of the square, using a variable.

PENDOWN
MAKE lado = 80

FORWARD lado
RIGHT 90
FORWARD lado
RIGHT 90
FORWARD lado
RIGHT 90
FORWARD lado

PENUP

The variable "lado" (spanish for "side") is declared through the keyword "MAKE" which assigns a name and an initial value to it.

Then we can see that all other operations like FORWARD could either use a simple value like a Number or a Variable Reference.

So now we can see the rules for these operations:

FORWARD: 'FORWARD' amount=EXPRESSION;
LEFT: 'LEFT' amount=EXPRESSION;
RIGHT: 'RIGHT' amount=EXPRESSION;
SET_X: 'SETX' amount=EXPRESSION;
SET_Y: 'SETY' amount=EXPRESSION;

We can see there that all of these rules expect a special keyword and then an EXPRESSION.
Which in turn is as we identified:

EXPRESSION:  
VARIABLE_REF | VALUE
;

A value is simply a double number

VALUE:
val=DOUBLE
;

And a Variable reference is a little bit more complicated:

VARIABLE_REF: toVar=[REFERENCIABLE];

It is a reference (the syntax for that in xtext is to use the square brackets) to a REFERENCIABLE.
A Referenciable is:

REFERENCIABLE:
MAKE | PARAM

;

Make should be familiar for us, since it's the rule that we used in the example to declare a new variable.
So this means that a referencia is a one of those variables or a PARAM, which we haven't seen yet.
But we will soon !

Repeat Loops

We can still simplify the example by reducing duplicated code, using a repeat loop.

PENDOWN
MAKE lado = 100

REPEAT 4
FORWARD lado
RIGHT 90
END REPEAT

PENUP

It will repeat all the sentences 4 times. You should know this :P

Lets see the grammar rules for this:

REPEAT:
'REPEAT' times=EXPRESSION
(commands+=SENTENCE)+
'END REPEAT'

;

By now you probably already understood that Repeats can also be used with a fixed number or with variable to define how many times it should repeat.
Because "times" is of type EXPRESSION. Which we already see could be a VARIABLE_REF or a Variable.
This means you can do this

MAKE sides = 4

REPEAT sides
FORWARD lado
RIGHT 90
END REPEAT

Now, what's a "SENTENCE" ?
We will go a little further fast with this one.

SENTENCE:
MAKE | CONTENT
| PROCEDURE_CALL
| OPERATION
| CONTROL_SENTENCES
| MOVE

| DRAWING_SENTENCE

Sentence is probably the most generic rule, since it could be one of the MOVE sentences that we already saw, to control the turtle movement, but also a DRAWING_SENTENCE, that we also saw. It could also be a MAKE, because you can define new variables within a REPEAT, for example.
And then more stuff we haven't seen yet like: PROCEDURE_CALL, CONTROL_SENTENCES, CONTENT, and OPERATION.

We will see each of them in further sections

Procedures

Our sample program draws a square, but if we want to draw many squares, we don't want to copy and paste that code, so, for reusing certain functionality we can define procedures, than can be called anytime.

TO makeASquare
PENDOWN
MAKE lado = 100

REPEAT 4
FORWARD lado
RIGHT 90
END REPEAT
PENUP
END TO

makeASquare
makeASquare

This program first defines a procedure named "makeASquare" and all of its sentences.
Then the program itself has two procedure calls to the same procedure.

So we have two new concepts for the language.

A procedure definition, which is:

TO: 
'TO' name=ID (parameters+=PARAM)*
(commands+=SENTENCE)+
'END TO'
;

Notice that it could have a list of PARAM's. And the body is a list of SENTENCE's (we've already seen that it's basically "anything" -but not a procedure definition, you cannot define one within other-)

A param is:

PARAM:
':' name=ID
;

So now we now that from a REPEAT or a FORWARD, RIGHT, etc, you can use a value (double) or reference either a variable (MAKE), or a PARAM, in case you are within a procedure !.

The other new concept is the procedure call:

PROCEDURE_CALL:
to=[TO] (params+=EXPRESSION)*
;

Easy, a reference to a procedure (TO), and then an optional list of EXPRESSION, which will be evaluated and passed as the values for each parameter.

Remember EXPRESSION is either a value or a reference (to MAKE or PARAM).

Some Other Syntax Elements

There are a couple of other secondary elements in the language for example to control the font and the line color

PENCOLOR GREEN
makeASquare

Will produce:


CLEAR
HOME
CANVASCOLOR BLACK
FONTSIZE 36
FONTSTYLE BOLD
DRAWSTRING "THIS IS TORTUE TEXT"

Clear of course will erase the canvas. And home will move the turtle to the starting point (center)
Canvas color will set the background color.

Drawstring is as you probably guessed to write a text.
Here's a cool sample app which combines it with PEN COLOR

CLEAR
FONTSIZE 36
FONTSTYLE BOLD

SETX 140

MAKE color = 10
REPEAT 24
PENCOLOR color 0 color 50
LEFT 15
PENUP
FORWARD 45
PENDOWN
DRAWSTRING "THIS IS TORTUE TEXT"
SUM color = color + 10
END REPEAT

This produces the following image:


Cool ah ?
Thanks Tortue :P

Operations

There are a couple of simple mathematical operations to work with numbers.

DIVIDE blue = color / 2

All of this operations have two effects:
  1. Compute a value (on the right)
  2. Assigns that value into a variable (using a reference)
Here are the others:

SUM forwardamount = i + 1
MULTIPLY green = J * I

Here is a sample program which uses SUM

CLEAR
HOME

MAKE i = 1
MAKE color = 200
REPEAT 500
MAKE red = color
DIVIDE blue = color / 2
MAKE green = color
PENCOLOR red green blue 255
MAKE forwardamount = 0
SUM forwardamount = i + 1
FORWARD forwardamount
RIGHT 70
SUM i = i + 1
SUM color = color + 1
IF color > 255
// MAKE color = 200
SUM color = 0 + 200   
END IF
END REPEAT

It draws the following:

Lets check the grammar:

OPERATION:
  SUM
| SUBTRACT
| MULTIPLY
| DIVIDE
;

And they are all pretty similar:

SUM: 
  'SUM' targetVariable=[MAKE] '=' valOne=EXPRESSION '+' valTwo=EXPRESSION;

SUBTRACT:
  'SUBTRACT' targetVariable=[MAKE] '=' valOne=EXPRESSION  '-' valTwo=EXPRESSION;

MULTIPLY:
  'MULTIPLY' targetVariable=[MAKE] '=' valOne=EXPRESSION  '*' valTwo=EXPRESSION;

DIVIDE:
 'DIVIDE' targetVariable=[MAKE] '=' valOne=EXPRESSION  '/' valTwo=EXPRESSION;


Basically it has a variable reference. Notice that we don't use REFERENCIABLE here, so this means that you can only use MAKE variables and not PARAM. This means that you cannot reassign PARAMeters.

Then on the right part they are all binary operations, so they have two operands. Each operand is not exclusively a double number but an EXPRESSION.
So that you can SUM 2 with 2, but also 2 + a,  or  "a + b"

If

You should be getting the idea, this is just more of the same.

IF color > 255
// MAKE color = 200
SUM color = 0 + 200   
END IF

This is the grammar for the if

IF:
'IF' condition=BOOLEAN_EXPRESSION
(commands+=SENTENCE)+
'END IF'
;

And something cool appears here, the BOOLEAN_EXPRESSION's.

BOOLEAN_EXPRESSION:
EQUALS
| GREATER_THAN
| LESSER_THAN

EQUALS: op1=EXPRESSION '=' op2=EXPRESSION;
GREATER_THAN: op1=EXPRESSION '>' op2=EXPRESSION;
LESSER_THAN: op1=EXPRESSION '<' op2=EXPRESSION;

Notice that the rules are pretty similar to the ones for mathematical operations. The operands are in both cases EXPRESSION's.

And that's it.
This is pretty much all the syntax of the language

Full Tortue Syntax and Commands

For the complete syntax reference of Tortue you can visit:

Next

In the next part we will start to look at the interpreter and how to give some execution meaning to this grammar :)

No comments:

Post a Comment