I translate one language into another with ANTLR4. For example when I read numerical literals I can return an Integer
or Double
.
@Override
public Integer visitIntegerValue(Parser.IntegerValueContext ctx) {
return Integer.valueOf(ctx.getText());
}
@Override
public Double visitDoubleValue(Parser.DoubleValueContext ctx) {
return Double.valueOf(ctx.getText());
}
Ultimately if you extend this approach further and introduce other constructs like strings and conditions, the only reasonable type for the visitor is class Visitor extends BaseVisitor<Object>
, but it leads to a code heavily spiced with instanceof
. For example
@Override
public CollectionQuery visitCondition(Parser.ConditionContext ctx) {
Property property = (Property) visit(ctx.property());
String operator = (String) visit(ctx.operator());
Object value = visit(ctx.amount());
Object condition;
if (value instanceof String && operator.equals("$regex")) {
condition = Pattern.compile((String) value, Pattern.CASE_INSENSITIVE);
}
...
}
While I don't mind this kind of 'dynamism', I would like to know if this is a maintainable way to proceed or are there other techniques that I should use instead, like creating proper hierarchy of target language constructs.
One suggestion is to have a visitor per return type:
public class IntegerVisitor extends BaseListener<Integer> {
@Override
public Integer visitIntegerValue(Parser.IntegerValueContext ctx) {
return Integer.valueOf(ctx.getText());
}
}
public class DoubleVisitor extends BaseListener<Double> {
@Override
public Double visitDoubleValue(Parser.DoubleValueContext ctx) {
return Integer.valueOf(ctx.getText());
}
}
This makes more sense when you're visiting vastly different things (for example if you were parsing using a java grammar you might have a MethodVisitor
and a ClassVisitor
etc. See an example here: See an example here
To get an impression how a custom post-processing could look like.
some ANTLR code
topMostRule : childRule+ EOL;
childRule : variantOne | variantTwo;
variantOne : 'A';
variantTwo : '1';
...
Pseudo code for a custom postprocessing (more C# like than Java / not the real method names ANTLR uses):
public class MyCustomPostprocessor
{
private IntermediateResults lookupTable; // use private fields for lookup tables etc.
public ResultType process(Parser.TopMostRuleContext ctx)
{
// inspect the children
var children = new List<object>();
foreach (var rule in ctx.ChildRules)
{
switch (rule.Type)
{
case typeof (Parser.ChildRuleContext):
var result = process(rule);
children.Add(result);
else
throw new NotImplementedException("Don't know what to do with " + rule.Type.ToString());
}
// use the information gathered so far to form the result
return new ResultType (children);
}
}
public object process (Parser.ChildRuleContext)
{
foreach (var rule in ctx.ChildRules)
{
switch (rule.Type)
{
case typeof (Parser.VariantOneContext):
var result = process(rule);
return result;
case typeof (Parser.VariantTwoContext):
var result = process(rule);
return result;
else
throw new NotImplementedException("Don't know what to do with " + rule.Type.ToString());
}
}
}
public string process (Parser.VariantOneContext ctx)
{
return ctx.GetText();
}
public int process (Parser.VariantTwoContext ctx)
{
return Int.Parse(ctx.GetText());
}
}
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