SmaCC Transformation Toolkit rewrite rules
The rewrite rules do the work of the transformation. They have two parts: a match and a transformation. The match checks the current AST node to determine whether the transformation should run. If the match does not succeed, the next rewrite rule is run. If the current rewrite rule is the last one, the child nodes are then processed.
As an example, consider a rewrite rule that converts blocks defined by GtSmaCCTransformationExampleParser
into JavaScript:
GtSmaCCTransformationExampleBlockNode
. When a block node is matched, it runs the transformation code. That code replaces the begin and end keywords with {}. Once the begin and end keywords have been replaced, the rule does self continue. That looks for other rules that match the node and, when no other rules are found, processes the children of the block node, which are its statements. The code inside the rules is evaluated in the context of SmaCCRewriteMatchContext
, so self understands all messages defined on that class.
The matching part of a rewrite rule can also contain code that controls whether the transformation runs. The transformation runs only if that code returns true. If it returns anything other than true, or throws an error, the transformation is not run.
For example, consider a rule that converts the . terminating a statement in GtSmaCCTransformationExampleParser
. In JavaScript, those should be converted to ;. Instead of writing several rules that match only subclasses of GtSmaCCTransformationExampleStatementNode
that contain a period, we can simply match any statement and check whether its period token is not nil. For statements that do not have a period, this throws an error, but that is handled and treated as not matching:
Rewrite rules can also use patterns for both matching and transforming code. For example, we can create a specific rule that matches an increment by one and converts it to ++ in JavaScript:
Many of the rules above use SmaCCRewriteMatchContext>>#replace:with:
to perform edit operations. That method takes an AST node or token and replaces its source with the string passed as the second argument. If the argument is a node, that node's source interval is updated so that future edit operations on the node use the new source. Tokens do not update their source interval when used in edit operations. However, if the token is part of the current matched node, the matched node's source interval may be updated.
There are many other edit operation methods, such as SmaCCRewriteMatchContext>>#delete:
, SmaCCRewriteMatchContext>>#insert:after:
, and SmaCCRewriteMatchContext>>#move:before:
. The edit operations can be found in the source editing protocol of SmaCCRewriteMatchContext
. Most of them take AST nodes or tokens to identify positions. The edited source for a node can be retrieved with SmaCCRewriteMatchContext>>#sourceFor:
.
When using operations such as SmaCCRewriteMatchContext>>#insert:after:
, order matters. Order can be controlled either by the location of the rule in the script file or by moving the edit operation before or after a self continue or self processChildren statement. The SmaCCRewriteMatchContext>>#continue
method continues processing the current matching node in the script file. If you do not want to continue with the current matching node, you can process another node by using SmaCCRewriteMatchContext>>#processChild:
, SmaCCRewriteMatchContext>>#processChildren
, or SmaCCRewriteMatchContext>>#processChildren:
. If none of these methods is called, processing of the current matching node and its children stops.