Xtext оставил проблему с рекурсией в языке выражений

#java #grammar #xtext #antlr3 #left-recursion

Вопрос:

Недавно я работал над небольшой демонстрационной грамматикой выражений в Xtext:

Грамматика

 grammar org.example.expressions.Expressions with org.eclipse.xtext.common.Terminals

generate expressions "http://www.example.org/expressions/Expressions"

ExpressionsModel:
    elements =AbstractElement*;

AbstractElement:
    Variable | EvalExpression;

Variable:
    'var' name=ID '=' expression=Expression;

EvalExpression:
    'eval' expression=Expression;

/*-----------------------------------EXPRESSIONS-----------------------------------*/
//Note that expressions on the same precedence get grouped into a single rule, but not necessarily into the same AST node.
//Examples of this pattern are compound rules such as the AddOrSubExpression, or the ComparisonOrInExpression

Expression:
    ConditionalExpression;

ConditionalExpression returns Expression:
    OrExpression ({ConditionalExpression.condition=current} '?' trueExp=OrExpression ':' falseExp=OrExpression)*;

OrExpression returns Expression:
    AndExpression ({BinaryExpression.left=current} operator='||' right=AndExpression)*;

AndExpression returns Expression:
    BinOrExpression ({BinaryExpression.left=current} operator='amp;amp;' right=BinOrExpression)*;

BinOrExpression returns Expression:
    BinXorExpression ({BinaryExpression.left=current} operator='|' right=BinXorExpression)*;

BinXorExpression returns Expression:
    BinAndExpression ({BinaryExpression.left=current} operator='^' right=BinAndExpression)*;

BinAndExpression returns Expression:
    EqualityExpression ({BinaryExpression.left=current} operator='amp;' right=EqualityExpression)*;

EqualityExpression returns Expression:
    ComparisonOrInExpression ({BinaryExpression.left=current} operator=('==' | '!=') right=ComparisonOrInExpression)*;

ComparisonOrInExpression returns Expression:
    BitShiftExpression (({BinaryExpression.left=current} operator=('<' | '>' | '<=' | '>=') right=BitShiftExpression) // ComparisonExpression
    | ({InExpression.left=current} 'in' (right=BitShiftExpression | ('[' openRangeList =OpenRangeValue (','
    openRangeList =OpenRangeValue)* ']'))) // InExpression
    )*;

OpenRangeValue:
    lowBound=Expression ('..' highBound=Expression)?;

BitShiftExpression returns Expression:
    AddOrSubExpression ({BinaryExpression.left=current} operator=('<<' | '>>') right=AddOrSubExpression)*;

AddOrSubExpression returns Expression:
    MulOrDivExpression ({BinaryExpression.left=current} operator=(' ' | '-') right=MulOrDivExpression)*;

MulOrDivExpression returns Expression:
    PowExpression ({BinaryExpression.left=current} operator=('*' | '/' | '%') right=PowExpression)*;

PowExpression returns Expression:
    BitSliceExpression ({BinaryExpression.left=current} operator='**' right=BitSliceExpression)*;

BitSliceExpression returns Expression:
    FieldOperationExpression ({BitSliceExpression.operand=current} '[' highBound=Expression ':' lowBound=Expression ']')*;

FieldOperationExpression returns Expression:
    UnaryExpression (({CollectionAccess.operand=current} '[' index=Expression ']') // CollectionAccessExpression
    | ({FunctionCallExpression.targetObject=current} '.' name=ID '(' (params =Expression (','
    params =Expression)*)? ')') // FieldAccessFunction
    | ({FieldAccessExpression.targetObject=current} '.' fieldIdentifier=ID))*; // FieldAccessExpression

UnaryExpression returns Expression:
    PrimaryExpression
    | {UnaryExpression} operator=UnaryOperator operand=PrimaryExpression;

PrimaryExpression returns Expression:
    '(' Expression ')'
    | FunctionCallExpression
    | CompileHasExpression
    | StaticRefrencePathExpression
    | {Expression} NullRefrenceExperession
    | StringConstantExpression
    | BoolConstantExpression
    | IntConstantExpression;

FunctionCallExpression:
    name=ID '(' (params =Expression (',' params =Expression)*)? ')';

CompileHasExpression:
    'compile' 'has' staticRefPath=StaticRefrencePathExpression;

StaticRefrencePathExpression:
    (globalScope?='::')? (identifiers =ID ('::' identifiers =ID)*);

NullRefrenceExperession:
    'null';

IntConstantExpression:
    value=INT;

StringConstantExpression:
    value=STRING;

BoolConstantExpression:
    value=BoolValue;

/*-----------------------------------ENUM_TERMINALS-----------------------------------*/
enum UnaryOperator:
    ARITH_NEGATE='-' | BOOL_NEGATE='!' | BIN_NEGATE='~' | BIN_AND='amp;' | BIN_OR='|' | BIN_XOR='^';

enum BoolValue:
    TRUE='true' | FALSE='false';
 

Все здесь, казалось, работало хорошо, но когда я добавил выражение BitSliceExpression и попытался обработать грамматику, я получил следующую ошибку:

Ошибка:

 error(211): ../org.example.expressions/src-gen/org/example/expressions/parser/antlr/internal/InternalExpressions.g:1626:3: [fatal] rule ruleFieldOperationExpression has non-LL(*) decision due to recursive rule invocations reachable from alts 1,4.  Resolve by left-factoring or using syntactic predicates or using backtrack=true option.
 

После небольшого тестирования я пришел к выводу, что эта проблема вызвана неоднозначностью между выражением BitSliceExpression и выражением collectionaccess

 //BitSliceExpression example:
[5:3]
//CollectionAccessExpression example:
[5]
 

У меня очень мало знаний о Xtext, Antlr3, и алгоритмов синтаксического анализа в целом
но я могу только предположить, что, поскольку выражение является рекурсивное правило, синтаксический анализатор не слишком смотрю мимо него и проверить, точка с запятой литерал, который является отличительной чертой между
BitSliceExpression и CollectionAccessExpression во время выполнения, и, таким образом, неоднозначность рождается.

Я потратил много времени на изучение этого вопроса, но из-за моих ограниченных знаний я не смог найти надлежащего решения, и я был бы очень признателен за помощь.

(Пожалуйста, сообщите мне, требуется ли дополнительная информация о моем проекте)

Комментарии:

1. Проблема в том, что добавленное правило конфликтует с выражением Fieldoperation, о чем говорится в сообщении об ошибке, а не с выражением Collectionaccess. Проблема в том , что выражение Fieldoperation имеет значение alt для '[' Expression ']' , но вы также определяете его в выражении BitSliceExpression, поэтому оно не может различать эти два параметра. Одним из решений является простое изменение правила выражения fieldoperation: FieldOperationExpression: UnaryExpression ( '[' Expression ( ':' Expression )? ']' | '.' RULE_ID '(' ( Expression ( ',' Expression )* )? ')' | '.' RULE_ID )* ; (игнорирование кода построения дерева).

2. eclipse.org/Xtext/documentation/…

3. На самом деле правило замены, которое я дал, работает, но мое объяснение было неверным. Конфликт заключается в общих '[' Expression строках предложений между выражением BitSliceExpression и выражением Fieldoperation. Однако ваша грамматика будет отлично работать в Antlr4-я проверил ее.

4. Я попробовал этот метод, и он сработал, но создание соответствующих ветвей AST очень важно в моем проекте. Кроме того, я предпочитаю использовать Xtext (который использует Antlr3) из-за всех функций IDE, которые он предоставляет. Есть ли какой-нибудь другой способ решить эту проблему?

5. Пока придерживайтесь XText и просто измените конструкцию AST для каждого alt правила.