#parsing #antlr #antlr4
#синтаксический анализ #antlr #antlr4
Вопрос:
Я настроил грамматику простых выражений, найденную в книге «The Definitive ANTLR 4 Reference». Новая грамматика выглядит следующим образом:
grammar Expr;
prog: stat ;
stat: expr NEWLINE # printExpr
| ID '=' expr NEWLINE # assign
| NEWLINE # blank
;
expr: expr op=('*'|'/') expr # MulDiv
| expr op=(' '|'-') expr # AddSub
| INT # int
| ID # id
| '(' expr ')' # parens
| 'min' '(' expr ',' expr ')' # Min
| 'max' '(' expr ',' expr ')' # Max
| 'len' '(' string_constant ')' # Len
;
MUL : '*' ; // assigns token name to '*' used above in grammar
DIV : '/' ;
ADD : ' ' ;
SUB : '-' ;
ID : [a-zA-Z] ; // match identifiers
INT : [0-9] ; // match integers
NEWLINE:'r'? 'n' ; // return newlines to parser (is end-statement signal)
WS : [ t] -> skip ; // toss out whitespace
string_constant : '"' (ESC | ~('"' | '\') )* '"' ;
ESC : '\' (["\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;
Есть пара новых математических функций (min и max) и длина строки.
Здесь есть посетитель дерева, который я использовал для анализа:
public class EvalVisitor extends ExprBaseVisitor<Integer>
{
/** "memory" for our calculator; variable/value pairs go here */
Map<String, Integer> memory = new HashMap<String, Integer>();
/** ID '=' expr NEWLINE */
@Override public Integer visitAssign(ExprParser.AssignContext ctx)
{
String id = ctx.ID().getText(); // id is left-hand side of '='
int value = visit(ctx.expr()); // compute value of expression on right
memory.put(id, value); // store it in our memory
return value;
}
/** expr NEWLINE */
@Override public Integer visitPrintExpr(ExprParser.PrintExprContext ctx)
{
Integer value = visit(ctx.expr()); // evaluate the expr child
System.out.println(value); // print the result
return 0; // return dummy value
}
/** INT */
@Override public Integer visitInt(ExprParser.IntContext ctx)
{ return Integer.valueOf(ctx.INT().getText()); }
/** ID */
@Override
public Integer visitId(ExprParser.IdContext ctx)
{
String id = ctx.ID().getText();
if ( memory.containsKey(id) )
return memory.get(id);
return 0;
}
/** expr op=('*'|'/') expr */
@Override public Integer visitMulDiv(ExprParser.MulDivContext ctx)
{
int left = visit(ctx.expr(0)); // get value of left subexpression
int right = visit(ctx.expr(1)); // get value of right subexpression
if ( ctx.op.getType() == ExprParser.MUL )
return left * right;
return left / right; // must be DIV
}
/** expr op=(' '|'-') expr */
@Override public Integer visitAddSub(ExprParser.AddSubContext ctx)
{
int left = visit(ctx.expr(0)); // get value of left subexpression
int right = visit(ctx.expr(1)); // get value of right subexpression
if ( ctx.op.getType() == ExprParser.ADD )
return left right;
return left - right; // must be SUB
}
/** '(' expr ')' */
@Override
public Integer visitParens(ExprParser.ParensContext ctx)
{ return visit(ctx.expr()); }
/** 'min' '(' expr ',' expr ')' */
@Override public Integer visitMin(@NotNull ExprParser.MinContext ctx)
{
int left = visit(ctx.expr(0)); // get value of left subexpression
int right = visit(ctx.expr(1)); // get value of right subexpression
return Math.min(left, right);
}
/** 'max' '(' expr ',' expr ')' */
@Override public Integer visitMax(@NotNull ExprParser.MaxContext ctx)
{
int left = visit(ctx.expr(0)); // get value of left subexpression
int right = visit(ctx.expr(1)); // get value of right subexpression
return Math.max(left, right);
}
/** 'len' '(' string_constant ')' */
@Override public Integer visitLen(@NotNull ExprParser.LenContext ctx)
{
String str = ctx.string_constant().getText();
return str.length()-2;
}
}
Посетитель может проанализировать выражение:
len("hello")
Но не может проанализировать выражение:
len("hello%")
Полученное сообщение выглядит следующим образом:
> java -jar Calc.jar
len("hello")
len("hello%")
line 2:10 token recognition error at: '%'
5
5
Есть ли какая-либо ошибка в определении string_constant ?
С уважением, Йона (than)
Ответ №1:
string_constant
вместо этого должно быть правило лексера:
STRING_CONSTANT
: '"' ( ESC | ~('"' | '\') )* '"'
;
и ESC
fragment
:
fragment ESC : '\' (["\/bfnrt] | UNICODE) ;
Комментарии:
1. Спасибо, Барт, с предложенным изменением все работает.