4

coffeescript grammar の Antlr/Xtext パーサーを作成しています。それはまだ始まったばかりで、元の文法のサブセットを移動しただけで、表現に行き詰まっています。それは恐ろしい「ルール式に非 LL(*) 決定があります」というエラーです。ここでいくつかの関連する質問を見つけました。Help with left factoring a grammar to remove left recursion and ANTLR Grammar for Expression . How to remove global backtracking from your grammarも試しましたが、実際には使用できない非常に単純なケースを示しています。ANTLR 文法のヒント: LL() と左因数分解に関する投稿は、より多くの洞察を与えてくれましたが、まだ理解できていません。

私の質問は、次の文法を修正する方法です (申し訳ありませんが、単純化できず、エラーを保持できませんでした)。トラブルメーカーがtermルールだと思うので、全体を変更するのではなく、ローカルで修正していただければ幸いです (元の文法のルールに近づけるように努めています)。この種の誤った文法を頭の中で「デバッグ」する方法のヒントへのポインターも歓迎します。

grammar CoffeeScript;

options {
  output=AST;
}

tokens {
  AT_SIGIL; BOOL; BOUND_FUNC_ARROW; BY; CALL_END; CALL_START; CATCH; CLASS; COLON; COLON_SLASH; COMMA; COMPARE; COMPOUND_ASSIGN; DOT; DOT_DOT; DOUBLE_COLON; ELLIPSIS; ELSE; EQUAL; EXTENDS; FINALLY; FOR; FORIN; FOROF; FUNC_ARROW; FUNC_EXIST; HERECOMMENT; IDENTIFIER; IF; INDENT; INDEX_END; INDEX_PROTO; INDEX_SOAK; INDEX_START; JS; LBRACKET; LCURLY; LEADING_WHEN; LOGIC; LOOP; LPAREN; MATH; MINUS; MINUS; MINUS_MINUS; NEW; NUMBER; OUTDENT; OWN; PARAM_END; PARAM_START; PLUS; PLUS_PLUS; POST_IF; QUESTION; QUESTION_DOT; RBRACKET; RCURLY; REGEX; RELATION; RETURN; RPAREN; SHIFT; STATEMENT; STRING; SUPER; SWITCH; TERMINATOR; THEN; THIS; THROW; TRY; UNARY; UNTIL; WHEN; WHILE;
}

COMPARE : '<' | '==' | '>';
COMPOUND_ASSIGN : '+=' | '-=';
EQUAL : '=';
LOGIC : '&&' | '||';
LPAREN  :   '(';
MATH : '*' | '/';
MINUS : '-';
MINUS_MINUS : '--';
NEW : 'new';
NUMBER  :   ('0'..'9')+;
PLUS : '+';
PLUS_PLUS : '++';
QUESTION : '?';
RELATION : 'in' | 'of' | 'instanceof'; 
RPAREN  :   ')';
SHIFT : '<<' | '>>';
STRING  :   '"' (('a'..'z') | ' ')* '"';
TERMINATOR : '\n';
UNARY : '!' | '~' | NEW;
// Put it at the end, so keywords will be matched earlier
IDENTIFIER :    ('a'..'z' | 'A'..'Z')+;

WS  :   (' ')+ {skip();} ;

root
  : body
  ;

body
  : line
  ;

line
  : expression
  ;

assign
  : assignable EQUAL expression
  ;

expression
  : value
  | assign
  | operation
  ;

identifier
  : IDENTIFIER
  ;

simpleAssignable
  : identifier
  ;

assignable
  : simpleAssignable
  ;

value
  : assignable
  | literal
  | parenthetical
  ;

literal
  : alphaNumeric
  ;

alphaNumeric
  : NUMBER 
  | STRING;

parenthetical
  : LPAREN body RPAREN
  ;

// term should be the same as expression except operation to avoid left-recursion
term
  : value
  | assign
  ;

questionOp
  : term QUESTION?
  ;

mathOp
  : questionOp (MATH questionOp)*
  ;

additiveOp
  : mathOp ((PLUS | MINUS) mathOp)*
  ;

shiftOp
  : additiveOp (SHIFT additiveOp)*
  ;

relationOp
  : shiftOp (RELATION shiftOp)*
  ;

compareOp
  : relationOp (COMPARE relationOp)*
  ;

logicOp
  : compareOp (LOGIC compareOp)*
  ;

operation
  : UNARY expression
  | MINUS expression
  | PLUS expression
  | MINUS_MINUS simpleAssignable
  | PLUS_PLUS simpleAssignable
  | simpleAssignable PLUS_PLUS
  | simpleAssignable MINUS_MINUS
  | simpleAssignable COMPOUND_ASSIGN expression
  | logicOp
  ;

更新: 最終的な解決策は、重要な空白を処理する複雑さを避けるために、外部レクサーで Xtext を使用します。これが私の Xtext バージョンのスニペットです。

CompareOp returns Operation:
  AdditiveOp ({CompareOp.left=current} operator=COMPARE right=AdditiveOp)*;

私の戦略は、使用可能な AST を使用せずに、最初に動作する Antlr パーサーを作成することです。(まあ、これが実現可能なアプローチである場合は、別の質問に値するでしょう。)したがって、現時点ではトークンについては気にしません。トークンは開発を容易にするために含まれています。

元の文法が LR であることは承知しています。LLに変身するときどこまで近づけるかわかりません。

UPDATE2 と解決策: Bart の回答から得られた洞察を使用して、問題を単純化できました。これは、関数呼び出しを使用して簡単な式を処理するための実用的な文法です。前のコメントexpressionは私の洞察を示しています。

grammar FunExp;

ID: ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*;
NUMBER: '0'..'9'+;
WS: (' ')+ {skip();};

root
  : expression
  ;

// atom and functionCall would go here,
// but they are reachable via operation -> term
// so they are omitted here
expression
  : operation
  ;

atom
  : NUMBER
  | ID
  ;

functionCall
  : ID '(' expression (',' expression)* ')'
  ;

operation
  : multiOp
  ;

multiOp
  : additiveOp (('*' | '/') additiveOp)*
  ;

additiveOp
  : term (('+' | '-') term)*
  ;

term
  : atom
  | functionCall
  | '(' expression ')'
  ;
4

1 に答える 1

4

文法からレクサーとパーサーを生成すると、次のエラーがコンソールに表示されます。

エラー (211): CoffeeScript.g:52:3: [fatal]ルール式には、alts 1、3 から到達可能な再帰的なルール呼び出しのため、非 LL(*) 決定があります。左因数分解するか、構文述語を使用するか、backtrack=true オプションを使用して解決します。

warning(200): CoffeeScript.g:52:3:決定は、複数の選択肢を使用して "{NUMBER, STRING}" などの入力と一致する可能性があります: 1、3

その結果、その入力に対して選択肢 3 が無効になりました

(重要な部分を強調しました)

これは最初のエラーにすぎませんが、最初のエラーから始めて、少し運が良ければ、最初のエラーを修正すると、最初のエラーより下のエラーも消えます。

NUMBER上記のエラーは、文法から生成されたパーサーを使用して aまたは a のいずれかを解析しようとすると、パーサーがルールSTRINGで終わるときに 2 つの方向に進む可能性があることを意味します。expression

表現
  : 値 // 選択肢 1
  | | 割り当て // 選択肢 2
  | | 操作 // 選択肢 3
  ;

つまり、選択肢 1 と選択肢 3 の両方が aNUMBERまたは aSTRINGを解析できます。これは、パーサーがこれら 2 つの選択肢を一致させるためにたどることができる「パス」からわかるように:

選択肢 1:

表現
  価値
    リテラル
      alphaNumeric : {NUMBER, STRING}

選択肢 3:

表現
  手術
    論理演算
      関係演算
        shiftOp
          添加剤操作
            数学演算
              質問操作
                学期
                  価値
                    リテラル
                      alphaNumeric : {NUMBER, STRING}

警告の最後の部分で、ANTLR は aNUMBERまたは aSTRINGが解析されるときは常に選択肢 3 を無視し、選択肢 1 がそのような入力に一致することを通知します (選択肢 3 の前に定義されているため)。

したがって、CoffeeScript の文法はこの点であいまいである (そして、このあいまいさを何らかの方法で処理する) か、それの実装が間違っています (私は後者だと思います :))。文法のこのあいまいさを修正する必要があります。つまり、expressionの選択肢 1 と 3 の両方が同じ入力に一致しないようにします。


私はあなたの文法で他に 3 つのことに気付きました:

1

次のレクサー ルールを使用します。

新しい: '新しい';
...
ユニリー: '!' | | '~' | 新着;

トークンはテキストの前に定義されているため、トークンがUNARYテキストと一致することはないことに注意してください。これを一致させたい場合は、ルールを削除して次のようにします。'new'NEWUNARYNEW

ユニリー: '!' | | '~' | '新着';

2

多くの場合、次のように複数の種類のトークンを 1 つのトークンに集めていますLOGIC

ロジック : '&&' | '||';

そして、次のようなパーサー ルールでそのトークンを使用します。

論理演算
  :compareOp(ロジックcompareOp)*
  ;

LOGICしかし、後の段階でそのような式を評価する場合、このトークンが何に一致したか ('&&'または)がわからない'||'ため、トークンの内部テキストを調べてそれを見つける必要があります。次のようなことをした方がよいでしょう (少なくとも、後の段階で何らかの評価を行っている場合)。

と : '&&';
または: '||';

...

論理演算
  : compareOp ( AND compareOp // 評価が簡単です。AND 式であることがわかります
              | | OR compareOp // 評価が簡単です。OR 式であることがわかります
              )*
  ;

3

次のように空白をスキップしています(タブはありませんか?):

WS : (' ')+ {skip();} ;

しかし、CoffeeScript は、Python のようにスペース (およびタブ) でコード ブロックをインデントしませんか? しかし、おそらく後の段階でそれを行うつもりですか?


あなたが見ている文法はジソン文法であることがわかりました(これは多かれ少なかれJavaScriptのバイソン実装です)。しかし、bison、したがって jison はLR パーサーを生成し、ANTLR はLL パーサーを生成します。したがって、元の文法の規則に近づこうとすると、問題が増えるだけです。

于 2011-11-25T10:16:58.763 に答える