Hello.
I need to parse some expression and then evaluate it multiple times against some context (say, list of variable values).
I created an Expression class based on the string representation of the expression. The constructor creates a lexer, a parser, and extracts the root ExpressionContext to a class field.
After that, some 'eval' method creates a new EvalVisitor and run visitExpression method against the root expression context.
I have this kind of rule in the grammar:
expression : conjunction (OR conjunction)* ;
and it's being processed by this method in the EvalVisitor:
@Override
public Boolean visitExpression(ExpressionContext ctx) {
for (ConjunctionContext conjunction : ctx.conjunction()) {
if (visitConjunction(conjunction)) return true;
}
return false;
}
The problem is ctx.conjunction() method each time calls ParserRuleContext.getRuleContexts(Class), that iterates children and creates a new list.
So, in fact, my visitor creates almost a copy of the AST on each call. Profiling shows that 90% CPU time I spend in getRuleContext and getRuleContexts methods.
Rough caching into Map<ExpressionContext, List<ConjunctionContext>> on the Expression class level gives about 7 times performance boost. Seems if I cache all context retrieval methods I'll get 10-20 times boost.
But this approach seems not very elegant to me. As an alternative, I can create my own domain model, build it with a visitor and then use it for all other purposes.
Which approach is the correct one? Maybe something third?
Thanks!