I'm using the C++ version of ANTLR4 to develop a DSL for a music product. I used to (30 years ago!) do this kind of thing by hand so it's mostly a pleasure to have something like ANTLR, particularly now that I don't have to insert code in the actual grammar definition itself.
I want to do type checking of actual vs formal args in a function call. In the grammar segment below, the 'actualParameter' can return the type of the expression. However, the 'actualParameterList' needs to return an array (say) of these types so that the code for functionCall can compare to the formal parameter list.
If I was handwriting this, the calls to visit or visitChildren would take an extra parameter after context such that I could create a new array at the appropriate place and then have child nodes fill in the details.
I suppose that instead of just calling visitChildren inside the 'visitActualParameterList' I could create the array there and manually call each child rather than just a simple visitChildren but that feels like a hack, and it becomes very sensitive to minor changes in the grammar.
Is there a better approach?
functionCall: Identifier LeftParen actualParameterList? RightParen
;
actualParameterList:
actualParameter anotherActualParameter
;
actualParameter:
expression
;
anotherActualParameter:
Comma actualParameter anotherActualParameter
|
;
You're on the right path. I would suggest something like:
functionCall: Identifier LPAREN actualParameterList RPAREN
;
actualParameterList:
actualParameter (',' actualParameter)*
;
actualParameter:
expression
;
LPAREN : '(';
RPAREN : ')';
Using this, in the Visitor for actualParameterList
you can check each child to see if it's of type actualParameterContext
and if so, explicitly call Visit on that child, which will get you into your expression evaluation code (presumably handled in the visitor for actualParameter
). This alleviates the need, as you say, to just generically visit children. It's very precise when you can check the type like this.
Here's an example of this pattern from my own code (in C# but surely you'll see the pattern in action):
for (int c = 0; c < context.ChildCount; c++)
{
if (context.GetChild(c) is SystemParser.ServerContext) // make sure correct type
{
string serverinfo = Visit(context.GetChild(c)); // visit the specific child and save return value, string in this case
sb.Append(serverinfo); // use result to fill array or do whatever
}
}
Now that you can see the pattern, back to your code. The syntax:
actualParameter (',' actualParameter)*
means that a parameter list has one actualParameter
followed by zero or more additional ones with the *
operator. I just threw the comma in there for visual clarity.
As you suggest, Visitor is the perfect pattern for this because you can explicitly visit any node you need to. It won't give you an array, but you can fill an array or any other necessary structure with the results of the visiting the children as you saw in the snip from my code. My Visitor returns strings, and I just appended to a StringBuilder. You can use the same pattern to build whatever you need.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With