LALRPOP における型推論と構文糖衣

LALRPOP では、アクションコードを省略可能な構文糖衣を提供しており、これによりパーサ定義をより簡潔に記述できる。特に、他の非終端記号から値をそのまま受け取るようなケースでは、明示的なアクションコードを書く必要がない。

例えば、次のような Term の定義があるとする:

pub Term: i32 = {
    <n:Num> => n,
    "(" <t:Term> ")" => t,
};

この場合、アクションコードは単にマッチした非終端記号の値を返しているだけである。このようなパターンは非常に一般的なため、LALRPOP では次のようにアクションコードを完全に省略できる:

pub Term = { Num, "(" <Term> ")" };

アクションコードが存在しない場合、LALRPOP は自動的にマッチされた要素の値を返すコードを生成する。最初の選択肢 Num では、単一の要素のみがマッチされるため、TermNum と同じ値を返す。2 番目の選択肢 "(" <Term> ")" では、角括弧で囲まれた Term のみが選択されているため、その値が返される。もし複数の要素が選択されていれば、それらはタプルとして返される。何も選択されていない場合(例: "(" Term ")")、すべてのマッチ要素がタプルとして返され、その型は (&'input str, i32, &'input str) のようになる。

型注釈も省略可能である。アクションコードを自前で記述していない場合、LALRPOP は型を自動推論する。上記の例では、TermNum と同じ型を持つことが明らかであるため、型は i32 と推論される。

同様に、正規表現によるトークン定義も短縮できる。元々の記述は以下の通り:

Num: i32 = <s:r"[0-9]+"> => i32::from_str(s).unwrap();

これを次のように書き換えられる:

Num: i32 = r"[0-9]+" => i32::from_str(<>).unwrap();

ここで <> は、マッチされた値の合成変数をカンマ区切りで挿入する構文糖衣である。この例では、正規表現 r"[0-9]+"&'input str 型の値を生成するため、LALRPOP はその値を表す合成変数を自動的に生成し、<> の位置に挿入する。ただし、カスタムアクションコードを使用しているため、型注釈は依然として必要である。

<> の動作は、角括弧による値の選択とも連携する。以下にいくつかの展開例を示す:

記述展開後の等価表現
A => bar(<>)<a:A> => bar(a)
A B => bar(<>)<a:A> <b:B> => bar(a, b)
A B => (<>)<a:A> <b:B> => (a, b)
<p:A> B => bar(<>)<p:A> B => bar(p)
A B => bar(<>)<a:A> <b:B> => bar(a, b)
<p:A> <q:B> => bar(<>)<p:A> <q:B> => bar(p, q)
<p:A> B => Foo<p:A> B => Foo
<p:A> <q:B> => Foo<p:A> <q:B> => Foo

さらに、<> は構造体リテラルとも併用できる。例えば、フィールド名がパース結果の変数名と一致していれば、次のように自然に記述できる:

Expr = {
    <lhs:Factor> "+" <rhs:Term> => AddExpr { lhs, rhs },
};

このように、LALRPOP の構文糖衣と型推論機能を活用することで、パーサ定義を簡潔かつ安全に記述できる。

タグ: rust lalrpop parser-generator type-inference

6月24日 00:49 投稿