パーサーコンビネータ

コンピュータプログラミングにおいて、パーサーコンビネータは、複数のパーサーを入力として受け取り、新しいパーサーを出力として返す高階関数です。ここで言うパーサーとは、文字列を入力として受け取り、何らかの構造(通常は構文解析木、または文字列内で解析が正常に終了した位置を表すインデックスの集合)を出力する関数です。パーサーコンビネータは、モジュール化された区分的な構築とテストを容易にする再帰下降構文解析戦略を可能にします。この構文解析手法は、組み合わせ構文解析と呼ばれます。

コンビネータを用いたパーサは、データベースへの自然言語ユーザインターフェースなどのドメイン固有言語用のコンパイラやプロセッサのプロトタイプ作成において広く利用されてきた。これらの言語では、複雑で多様な意味処理が構文処理と密接に統合されている。1989年、リチャード・フロストとジョン・ローンチベリーは、パーサコンビネータを用いて自然言語インタプリタを構築することを実証した[ 1 ] 。グラハム・ハットンも1992年に基本的な構文解析に高階関数を使用しました[ 2 ] 。また、1996年にはモナド構文解析を使用しました[ 3 ]。SD・スウィエストラも2001年に構文解析器コンビネータの実用的な側面を示しました[ 4 ] 。 2008年には、フロスト、ハフィズ、キャラハン[ 5 ]が関数型プログラミング言語Haskellで、左再帰に対応するという長年の問題を解決し、多項式時間と空間で完全なトップダウン構文解析ツールとして機能する構文解析器コンビネータのセットを説明しました。

基本的な考え方

第一級関数を持つプログラミング言語では、パーサーコンビネータを用いて基本的なパーサーを組み合わせ、より複雑な規則に対応するパーサーを構築することができます。例えば、文脈自由文法(CFG)の生成規則には1つ以上の選択肢があり、各選択肢は非終端記号および/または終端記号の列で構成される場合もあれば、単一の非終端記号または終端記号、あるいは空文字列で構成される場合もあります。これらの選択肢それぞれに単純なパーサーが利用可能な場合、パーサーコンビネータを用いて各パーサーを組み合わせ、選択肢のいずれかまたはすべてを認識できる新しいパーサーを生成することができます。

演算子オーバーロードをサポートする言語では、パーサコンビネータは中置演算子の形をとることができ、異なるパーサを結合して完全な規則を形成するために使用されます。これにより、パーサコンビネータは、形式文法の規則に構造が類似したコードで、埋め込み形式でパーサを定義することを可能にします。したがって、実装は、可読性などの利点をすべて備えた実行可能な仕様と考えることができます。

コンビネータ

議論を比較的分かりやすくするために、パーサーコンビネータを認識器のみの観点から考察します。入力文字列の長さが で#input、そのメンバーにインデックス を介してアクセスする場合j、認識器とは、インデックス で始まるトークンのシーケンスの認識に成功したインデックスの集合を出力として返すパーサーですj。空の結果セットは、認識器がインデックス で始まるシーケンスを認識できなかったことを示しますj

  • 認識empty器は空文字列を認識します。このパーサーは常に成功し、入力インデックスを含むシングルトンセットを返します。
  • 認識器は終端記号 を認識します。入力文字列のインデックス のトークンが の場合、このパーサーは を含むシングルトン集合を返します。それ以外の場合は、空集合を返します。term xxjxj + 1

2 つの認識子とが与えられればpq代替ルールのマッチング用とルールの順序付け用の 2 つの主要なパーサー コンビネータを定義できます。

  • 「代替」パーサーコンビネータ ⊕ は、各認識子を同じインデックスに適用しj、認識子の終了インデックスの結合を返します。
  • 「シーケンス」コンビネータ ⊛ は、最初の認識器をp入力インデックス に適用しj、各終了インデックスに対して、qそれを開始インデックスとして2番目の認識器を適用します。これは、 のすべての呼び出しから返される終了インデックスの和集合を返しますq

文字列を解析する際に、同じインデックスで終了するにもかかわらず、複数の異なる方法が存在する場合があり、これは文法が曖昧であることを示します。単純な認識器はこのような曖昧さを認識しません。つまり、終了インデックスの候補はそれぞれ結果セットに一度だけリストされます。より完全な結果セットを得るには、解析木などのより複雑なオブジェクトを返す必要があります。

非常に曖昧な文脈自由文法を考えてみましょう。s ::= ‘x’ s s | ε先に定義したコンビネータを用いることで、この文法の実行可能な表記を、現代の関数型プログラミング言語(例えば、Haskell)において のようにモジュール的に定義することができます。入力シーケンスのインデックス にs = term ‘x’ <*> s <*> s <+> empty認識器を適用すると、結果セット が返されます。これは、インデックス 2 から始まり、2 から 5 までの任意のインデックスで終わる一致があったことを示します。 s2x x x x x{2,3,4,5}

欠点と解決策

パーサーコンビネータは、すべての再帰下降パーサーと同様に、文脈自由文法に制限されず、したがってLL( k ) 解析のFirst kおよび Follow kセットの曖昧さをグローバルに検索しません。したがって、曖昧さは、入力によってトリガーされるまで実行時までわかりません。このような場合、再帰下降パーサーは、(おそらく文法設計者に知られずに)可能性のある曖昧なパスの 1 つをデフォルトとすることがあり、その結果、言語の使用時に意味の混乱(エイリアシング)が発生します。これにより、曖昧なプログラミング言語のユーザーにバグが発生しますが、これはコンパイル時には報告されず、人的エラーではなく曖昧な文法によって導入されます。これらのバグを排除する唯一の解決策は、曖昧さを排除して文脈自由文法を使用することです。

パーサコンビネータの単純な実装には、トップダウン構文解析に共通するいくつかの欠点があります。ナイーブな組み合わせ構文解析では、曖昧な文脈自由文法を解析する際に指数関数的な時間と空間を必要とします。1996年、フロストとシドロウスキーは、メモ化をパーサコンビネータと組み合わせて用いることで、時間計算量を多項式にまで削減できることを示しました。[ 6 ]その後、フロストはモナドを用いてコンビネータを構築し、計算全体を通してメモテーブルを体系的かつ正確にスレッド化できるようにしました。[ 7 ]

あらゆるトップダウン再帰下降構文解析と同様に、従来の構文解析器コンビネータ(上記コンビネータなど)は、左再帰文法(例s ::= s <*> term ‘x’|empty:)の処理中に終了しません。直接左再帰規則を持つ曖昧な文法に対応する認識アルゴリズムは、2006 年に Frost と Hafiz によって説明されました。 [ 8 ]このアルゴリズムは、深さ制限を課すことで、そうでなければ増え続ける左再帰解析を削減します。そのアルゴリズムは、2007 年に Frost、Hafiz、Callaghan によって完全な構文解析アルゴリズムに拡張され、直接左再帰だけでなく間接左再帰にも多項式時間で対応し、高度に曖昧な文法の潜在的に指数関数的な数の構文解析ツリーのコンパクトな多項式サイズの表現を生成しました。[ 9 ]この拡張アルゴリズムは、「計算されたコンテキスト」を「現在のコンテキスト」と比較することで間接左再帰に対応します。同じ著者らは、同じアルゴリズムに基づいてHaskell言語で書かれたパーサーコンビネータのセットの実装についても説明した。[ 5 ] [ 10 ]

注記

参考文献