The old way
In SableCC3 the code generation was based on a simple Macro Expansion class. This implementation is very simple but powerful. Here is an example taken from the AnalysisAdapter.java:
Macro :
Macro:DepthFirstAdapterCaseHeader
@Override
public void case$0$($0$ node)
{
in$0$(node);
$
Resulting code with $0$ replace by AAst :
@Override
public void caseAAst(AAst node)
{
inAAst(node);
and hereafter is the statement generating this code :
macros.apply(file, "DepthFirstAdapterCaseHeader",
new String[] {info.name});
As you can see, it is very simple to use.
However it has also some drawbacks :
- Code generation is flat. I mean that you can't nest macros in a clean way.
- The macro expander is stupid; there is no way of making part of the code generation conditional. For example, a class can optionally define a package. So the import or package statement cannot be hard coded in the macro but must be passed as parameter to the macro. This causes extra overhead in the code.
- The above point creates a strong dependency between the generation code (GenParser.java...) and the target language macros. Any incompatibilities of the target language with Java makes it necessary to modify the generation code.
Jelly
To resolve these issues Benoît Callebaut proposes the following:
Using the Jelly library from Apache. It has built-in conditional statements and is XML based Moreover, it is possible to define custom tag libraries. So it is possible to write scripts (similar to SableCC's macros) using higher level tags. So we can stay language independent and only change the tag library implementation instead of the macros and/or the code generation classes.
Etienne Gagnon looked at the Jelly library and reported the following:
- The XML syntax, while useful for brain-dead software parsers, is quite painful to the human eye (e.g. <j:jelly xmlns:j="jelly:core" xmlns:define="jelly:define" xmlns:my="myTagLib">).
- This library doesn't resolve one of my gripes with the SableCC 2 & 3 approach: I would like early detection of incorrect macro usage (i.e. when compiling SableCC itself), instead of lazy detection at the time of the macros.apply() call (when running tests), or worse, silent failure (or undetected failure, because not tested).
Etienne's Solution
Etienne proposes, instead, a flexible framework made of 4 parts:
- A data structure that encodes the results of SableCC's computations (lexers, parsers, tree transformations, etc.).
- Improved code template macros with named parameters.
- A code generator that builds a strictly typed library, encapsulating template macro invocation.
- A code generation plugin per target, which mainly consists of iterators over the data structure and macro calls (using the target-specific library).
This approach has the benefit of allowing for robust code generation. It is also flexible enough to allow users that want to use external processors to create a specific generator that outputs the data structure in an appropriate format for such tools (such as Jelly #Jelly or Velocity #81).
With this model, adding a new target language (or any other output) to SableCC would not require making any changes to SableCC's source code. It would only require creating new template macros and a new target plugin.
The coherency of the link between the plugin code and the data structure would be inherent in the type system. The coherency of the link between the plugin code and template macros calls is again insured at plugin compile time by the strict type system.
Why separate the plugin from templates? Mainly to allow code generation to be guided by the data structure, instead of by template format.
Addendum
Note from Benoit: I did a small test with Velocity. It is a little bit like a super boosted MacroExpander: Do variable replacement ex : $foo is replaced with the string value of $foo Supports Macros and macro libraries. Macros expands the "directives" of Velocity Supports directives(starts with #): for example : #if,#foreach Supports method call on Java objects and JavaBeans properties
