Reputation: 2920
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.
Upvotes: 7
Views: 4727
Reputation: 11347
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
Upvotes: 4
Reputation: 151
I suggest creating a Value class for wrapping the different kinds of objects, and then use this value class as generic type for the visitor.
Visitor<Value> visitor;
public class Value {
private Object value;
public Value(Object object) {
this.value = object
if (!(isDouble() || isInteger))
throw new IllegalArgumentException();
}
public boolean isDouble() {
return value instanceof Double;
}
public Double asDouble() {
return (Double) value;
}
public boolean isInteger() {
return value instanceof Integer;
}
public Integer asInteger() {
return (Integer) value;
}
@Override
public int hashCode() {
// generate hascode
}
@Override
public boolean equals(Object object) {
// equals
}
}
Upvotes: 1
Reputation: 5205
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());
}
}
Upvotes: 2