Я делаю простой язык программирования. Имеет следующую грамматику:
program: declaration+;
declaration: varDeclaration
| statement
;
varDeclaration: 'var' IDENTIFIER ('=' expression)?';';
statement: exprStmt
| assertStmt
| printStmt
| block
;
exprStmt: expression';';
assertStmt: 'assert' expression';';
printStmt: 'print' expression';';
block: '{' declaration* '}';
//expression without left recursion
/*
expression: assignment
;
assignment: IDENTIFIER '=' assignment
| equality;
equality: comparison (op=('==' | '!=') comparison)*;
comparison: addition (op=('>' | '>=' | '<' | '<=') addition)* ;
addition: multiplication (op=('-' | '+') multiplication)* ;
multiplication: unary (op=( '/' | '*' ) unary )* ;
unary: op=( '!' | '-' ) unary
| primary
;
*/
//expression with left recursion
expression: IDENTIFIER '=' expression
| expression op=('==' | '!=') expression
| expression op=('>' | '>=' | '<' | '<=') expression
| expression op=('-' | '+') expression
| expression op=( '/' | '*' ) expression
| op=( '!' | '-' ) expression
| primary
;
primary: intLiteral
| booleanLiteral
| stringLiteral
| identifier
| group
;
intLiteral: NUMBER;
booleanLiteral: value=('True' | 'False');
stringLiteral: STRING;
identifier: IDENTIFIER;
group: '(' expression ')';
TRUE: 'True';
FALSE: 'False';
NUMBER: [0-9]+ ;
STRING: '"' ~('\n'|'"')* '"' ;
IDENTIFIER : [a-zA-Z]+ ;
Эта левая рекурсивная грамматика полезна, поскольку она гарантирует, что каждый узел в дереве разбора имеет не более 2 дочерних элементов. Например,
var a = 1 + 2 + 3
превратится в два вложенных выражения сложения, а не в одно выражение сложения с тремя дочерними элементами. Такое поведение полезно, потому что облегчает написание интерпретатора, поскольку я могу просто сделать (очень упрощенно):
public Object visitAddition(AdditionContext ctx) {
return visit(ctx.addition(0)) + visit(ctx.addition(1));
}
вместо перебора всех дочерних узлов.
Однако у этой левой рекурсивной грамматики есть один недостаток, заключающийся в том, что она принимает недопустимые операторы.
Например:
var a = 3;
var b = 4;
a = b == b = a;
действительно под этой грамматикой, хотя ожидаемое поведение будет
b == b
анализируется первым, поскольку ==
имеет более высокий приоритет, чем присвоение (=
).
- Поскольку
b == b
анализируется первым, выражение становится бессвязным. Сбой разбора.
Вместо этого происходит следующее нежелательное поведение: последняя строка анализируется как (a = b) == (b = a).
Как я могу предотвратить разбор левых рекурсий, например, a = b == b = a
?
Нерекурсивная грамматика распознает этот ввод правильно и выдает ошибку синтаксического анализа, которая является желаемым поведением.