私は C のような文法を解析するパーサーを書いていました。
まず、次のようなコードを解析できるようになりました。
a = 1;
b = 2;
ここで、行末のセミコロンをオプションにしたいと思います。
元の YACC ルールは次のとおりです。
stmt: expr ';' { ... }
新しい行が自分で書いたレクサーによって処理される場所 (コードは簡略化されています):
rule(/\r\n|\r|\n/) { increase_lineno(); return :PASS }
ここでの :PASS 命令は、LEX で何も返さないことと同じです。これは、現在一致しているテキストを破棄して次のルールにスキップします。これは通常、空白で行われるのと同じです。
このため、単純に YACC ルールを次のように変更することはできません。
stmt: expr end_of_stmt { ... }
;
end_of_stmt: ';'
| '\n'
;
そこで、それに応じてパーサーによってレクサーの状態を動的に変更することにしました。
このような:
stmt: expr { state = :STATEMENT_END } ';' { ... }
そして、新しい行を新しい状態に一致させるレクサー ルールを追加します。
rule(/\r\n|\r|\n/, :STATEMENT_END) { increase_lineno(); state = nil; return ';' }
これは、レクサーが :STATEMENT_END 状態にあるときを意味します。最初に通常どおり行番号を増やし、次に状態を初期状態に設定し、それ自体がセミコロンであるふりをします。
次のコードで実際に動作しないのは奇妙です:
a = 1
b = 2
私はそれをデバッグし、実際には「;」を取得していないことを確認しました 数値 1 の後の改行をスキャンすると期待どおりになり、状態で指定されたルールは実際には実行されません。
そして、新しい状態を設定するコードは、既に新しい行をスキャンして何も返さなかった後に実行されます。つまり、これらの作業は次の順序で行われます。
- スキャン
a
、=
および1
- 改行をスキャンしてスキップするので、次の値を取得します
b
- 挿入されたコード(
{ state = :STATEMENT_END }
)が実行されます - エラーの発生 --
b
ここでは予期しない
これは私が期待するものです:
- スキャン
a
、=
および1
- ルールに一致することがわかった
expr
ので、に減らしますstmt
- 挿入されたコードを実行して、新しいレクサー状態を設定します
- 改行をスキャンし
;
、新しい状態一致ルールに従ってを返します - 次の行をスキャンして解析し続けます
イントロスペクションの後、YACC が LALR(1) を使用するために発生した可能性があることがわかりました。このパーサーは、最初に 1 つのトークンを前方に読み取ります。そこまでスキャンすると、まだ状態が設定されていないため、正しいトークンを取得できません。
私の質問は次のとおりです。期待どおりに機能させるにはどうすればよいですか? これについてはわかりません。
ありがとう。