Programming Macrocoder
Book 3
Semantic analysis
In the previous books we worked with languages that were so simple that they required no semantic analysis at all. To understand what semantic analysis is and why we need it, we shall move to a new example of a custom language.
Let's create a language called Genealogy that allows us to define a family tree. The language syntax will have to look like this:
As we know, Macrocoder takes care of the syntax checking. For example, we will not be able to write person Mike father of John
because father of
is not one of the keywords we defined in our language. However, besides that, we can expect syntactically perfect source code that contains errors. For example, we could write:
Although this code respects the syntax, it contains an obvious error: if Mike is John's father, John can not be Mike's father!
These kind of errors are called semantic errors because their detection depends on the logic meaning (called semantics) of the concepts expressed by the syntax.
This is the complete list of the semantic rules we need to enforce:
person x
is allowed for every x
);In this book we shall leave code generation temporarily aside and concentrate on semantic analysis.
By using the concepts learned from the previous chapters, we can definine the Genealogy language, so far still without any semantic check. We begin with the grammar (file grammar.fcg
):
Then we can define the very simple CORE::Person
class in the core.fcl
file:
Then we need a createCore
phase that creates the CORE
objects from the GRAMMAR
(file grammar_createCore.fcl
):
Finally we add a print
phase in the core_print.fcl
file just to see something when we will try it:
If we run the example of figure 1.1, we will obtain the following output:
Obviously, no semantic checks are implemented so far. Therefore, if we refer to unexisting parents or we define the same person twice, it will not complain.
The complete rules and target projects for at this stage can be downloaded at this link: Genealogy1.zip.
The first semantic check we will implement is to verify that the same person name is not used more than once.
This feature is obtained by using a lookup table, implemented by the Macrocoder composed type lookup_s of ...
. That composite type creates an indexed table where the key is a LocString
and the value is a link to an object whose type has been specified. Let's do the magic (file core_register.fcl
):
Let's look at the code:
register
phase; in this case, father-first
or children-first
does not matter;in phase
statement at root level, i.e. out of any class definition; this means that we added the in phase
to the lifeset class itself; in this way, the attribute knownPeople
will be instanced once together with the lifeset singleton and it will be accessibile as lset.knownPeople
;knownPeople
attribute has been declared as shared
; this means that the contents of this attribute will be updated during the register
phase by the children and not by the owner itself;Person
class has been extended to implement phase register
; during that phase, it reaches the lifeset singleton lset
, from there i reaches the global instance of the lookup table knownPeople
and registers itself by specifying personName
as the key and this
as the referenced object.The method set
of the lookup_s
composite type detects and reports any duplication automatically.
Let's try to submit a target source containing a couple of duplications:
The last two lines are dupes of names declared before. If we run Macrocoder now, it will report:
Note that the dupe names are colored in blue: by clicking on them, the editor will jump directly on the spot where those names have been defined in the target source code.
The complete rules and target projects for at this stage can be downloaded at this link: Genealogy2.zip.
Next semantic check will report unexising fathers. To do that, we shall reuse the knownPeople
lookup table we already created in the previous step. We can create another source file named core_resolve.fcl
:
The action is all at lines 8 and 9: if the fatherName
is not empty (i.e., if the user specified a father for this person), the method will look for an object having key fatherName
in the global lookup table lset.knownPeople
. If the key is not found, Macrocoder will complain with an error.
In the following example, the person at the last line refers to an unexisting father named "Marcus":
The last two lines are dupes of names declared before. If we run Macrocoder now, it will report:
The last check we need to implement is loop verification. The semantic check has to ensure that no one is defined as parent of one of his upper relatives, no matter how far in the parenthood chain.
This is obtained with a very simple modification. The changed lines are evidenced:
The changes are very simple:
dependent link
attribute has been added; a dependent link is a link that automatically verifies that no loops occur;Person
object returned by the get
method of the lookup table, which was previously discarded, now is assigned to the dependent link myParent
;With this modification, at the end of phase resolve
every instance of Person
that has a parent, will have a pointer to the parent Person
object stored in myParent
. Being the myParent
link defined as "dependent", Macrocoder will make sure that no loops occur among these instances.
In the following example, all the four listed people are involved in a loop:
When started, Macrocoder will abort the execution reporting the loops:
Note that at line 10 of figure 1.4.1 the set
method of the dependent link has been called with two parameters:
myParent.set (lset.knownPeople.get (fatherName), fatherName)
- first parameter queries the golbal lookup table knownPeople and retrieves the parent Person instance which becomes the target of the link (i.e. the object at which the link points to);myParent.set (lset.knownPeople.get (fatherName), fatherName)
- second parameter is a LocString that contains the text that has to be displayed to represent this object in the error message in case a loop is detetcted; thanks to this string, a meaningful description, useful to fix the loop, of the involved objects can be given to the target user.In this book we have covered the following concepts about Macrocoder programming: