従来のパーサーは入力全体を消費し、単一の解析ツリーを生成します。私は連続的なストリームを消費し、解析フォレストを生成するものを探しています[編集:その用語のこの使用が型破りである可能性がある理由に関するコメントの議論を参照してください]。私の直感は、そのようなパーサーを最初に必要とする人(または考える必要)とは言えないが、私は何度も検索してきました何ヶ月も無駄に。
私はXY問題に悩まされていることを認識しています。私の最終的な目的は、ほとんどのテキストを無視してテキストのストリームを解析し、認識されたセクションから解析ツリーのストリームを生成することです。
だから私の質問は条件付きです:これらの特性を持つパーサーのクラスが存在する場合、それは何と呼ばれますか?そして存在しない場合、なぜそうでないか代替は何ですか?おそらく、従来のパーサーに自分のやりたいことを実行させる方法が足りないのかもしれません。
入力全体が消費される前に(部分的な)結果を返すパーサーは、インクリメンタルパーサーと呼ばれます。入力で後でのみ決定される文法に局所的な曖昧さが存在する場合、増分解析は難しい場合があります。もう1つの問題は、まだ到達していない解析ツリーの部分を偽造することです。
可能なすべての解析ツリーのフォレストを返す(つまり、あいまいな文法の可能な派生ごとに解析ツリーを返す)パーサーは呼び出されます…これらにまだ名前があるかどうかはわかりません。 Marpaパーサージェネレーター がこれを実行できることは知っていますが、EarleyまたはGLRベースのパーサーはこれをオフにできるはずです。
しかし、あなたはそれを望んでいないようです。複数の埋め込みドキュメントを含むストリームがあり、その間にゴミがあります。
garbagegarbage{key:42}garbagegarbage[1,2,3]{id:0}garbage...
ガベージをスキップして、(遅延して)各ドキュメントのASTのシーケンスを生成するパーサーが必要なようです。これは、最も一般的な意味でインクリメンタルパーサーと見なすことができます。しかし、実際には次のようなループを実装します。
while stream is not empty:
try:
yield parse_document(stream at current position)
except:
advance position in stream by 1 character or token
parse_docment
関数は、従来の非インクリメンタルパーサーになります。解析を成功させるために十分な入力ストリームを確実に読み取れるようにすることには、若干の困難があります。これをどのように処理できるかは、使用しているパーサーのタイプによって異なります。可能性としては、特定の解析エラーでのバッファの増加、または遅延トークン化の使用が含まれます。
入力ストリームのため、遅延トークン化はおそらく最もエレガントなソリューションです。レクサーフェーズでトークンの固定リストを生成する代わりに、パーサーはレクサーコールバックから次のトークンを遅延して要求します[1]。その後、レクサーは必要なだけのストリームを消費します。このように、パーサーは、ストリームの実際の終わりに到達したとき、または実際の解析エラーが発生したとき(つまり、まだガベージにあるときに解析を開始したとき)にのみ失敗する可能性があります。
[1]一部の 最長トークンマッチングの問題 を回避できるため、コールバック駆動のレクサーは他のコンテキストでも良いアイデアです。
検索するドキュメントの種類がわかっている場合は、スキップを最適化して、有望な場所でのみ停止することができます。例えば。 JSONドキュメントは常に文字{
または[
で始まります。したがって、ガベージはこれらの文字を含まない任意の文字列です。
これを行うパーサーに特定の名前はありません。しかし、これを行うアルゴリズムの1つである 導関数による解析 を強調します。
一度に1つのトークンで入力を消費します。入力の最後に解析フォレストが作成されます。または、解析の途中で解析フォレスト全体を取得することもできます(partial解析)。
派生物による構文解析は、文脈自由文法を処理し、あいまいな文法の構文解析フォレストを生成します。
確かにエレガントな理論ですが、まだ初期段階にあるだけであり、広く展開されていません。 Matt Mightには、Scala/Racketなどのさまざまな実装へのリンクのリストがあります。
理論は、導関数による認識から始める(つまり、languagesの導関数を取ることから始めて、いくつかの入力を認識することを目標とする方が簡単です。それが有効かどうかを判断し、次に派生物で解析するようにプログラムを変更します(つまり、languagesの派生物をとるのではなく、それを変更します) parsersの導関数を取り、解析フォレストを計算します)。
理想からは程遠いが、私はそれが複数回行われたのを見てきました。各入力行で解析を試みます。失敗した場合は、その行を保持して次の行を追加します。疑似コード:
buffer = ''
for each line from input:
buffer = buffer + line
if can parse buffer:
emit tree
buffer = ''
大きな問題は、一部の言語では、次の行を読む前に式が完了しているかどうかを確認できないことです。その場合、あなたはあなたが次のものを読んで、それが有効な始まりであるか、または有効な継続であるかを確認することができるようです...しかし、そのためには正確な言語構文が必要です
さらに悪いことに、これらの言語では、たとえ1つの長いステートメントでなくても、ファイルの終わりまで解析できない病理学的なケースを作成することは難しくありません。
問題の迅速な解決策は、ドキュメントの考えられるすべての始まりを認識するREGEXまたはFSA(有限状態オートマトン)を定義することです(誤検知は許可されますが、実際にはドキュメントに対応していません)。その後、入力に対して非常に高速に実行して、ドキュメントがほとんどエラーなしで開始できる次の場所を特定できます。文書の開始時に誤った位置がいくつか発生する可能性がありますが、それらはパーサーによって認識され、破棄されます。
したがって、Finite State Automatonは、探していたパーサー名かもしれません。 :)
特に語彙に多くの解釈がある場合、実際的な問題を理解することは常に困難です。 Word解析フォレストは、いくつかの解析ツリーを持つあいまいな文のコンテキストフリー(CF)解析のために作成されました(afaik)。文の束の解析や他のタイプの文法にいくらか一般化することができます。したがって、この場合には関係のない、Earley、GLR、Marpa、および派生パーサー(他にも多数あります)に関するすべての回答。
しかし、それは明らかにあなたが考えていることではありません。 明確なドキュメントのシーケンスである一意の文字列を解析し、それぞれの解析ツリーまたはある種の構造化表現を取得する必要があります。文書の構文がどのように定義されているかは、正式な言語の観点から見れば、実際には言っていません。あなたが持っているのは、ドキュメントの最初から開始したときに解析ジョブを実行するアルゴリズムとテーブルです。だからそれである。
実際の問題は、ドキュメントのストリームにドキュメントを分離するかなりのゴミが含まれていることです。そして、このゴミを十分に速くスキャンするのは難しいようです。現在のテクニックは、最初から開始し、最初の文字からスキャンを試みて、ドキュメント全体がスキャンされるまで、失敗した場合は常に次の文字から再開することです。次に、ドキュメントをスキャンした直後の最初の文字から繰り返し説明します。
これは、@-amonが 彼の回答 の後半で提案した解決策でもあります。
パーサーのコードがドキュメントの先頭で非常に効率的に開始されるように最適化されている可能性は低いため、これは非常に高速なソリューションではない可能性があります(私にはテストする方法がありません)。通常の使用では、これは1回だけ行われるため、最適化の観点からはホットスポットではありません。したがって、このソリューションであなたの適度な幸福はそれほど驚くべきことではありません。
したがって、本当に必要なものは、大量のゴミで始まるドキュメントの最初をすばやく見つけることができるアルゴリズムですそして、幸運です:そのようなアルゴリズム存在します。そして、私はあなたがそれを知っていると確信しています。それは、正規表現の検索と呼ばれています。
あなたがしなければならないのは、これらのドキュメントがどのように始まるかを見つけるためにドキュメントの仕様を分析することです。構文仕様が正式にどのように構成されているのかわからないので、正確に説明することはできません。おそらく、それらはすべて有限リストからのいくつかのWordで始まり、おそらくいくつかの句読点または数字が混在しています。それはあなたがチェックすることです。
あなたがしなければならないのは、有限状態オートマトン(FSA)を定義すること、または同等に、ほとんどのプログラマーにとって、ドキュメントの最初の数文字を認識できる正規表現(REGEX)を定義することです。大きい(時間とスペースがかかる場合があるため)。これは、ドキュメントの仕様から比較的簡単に実行できるはずです。おそらく、ドキュメントの仕様を読み取るプログラムで自動的に実行できます。
正規表現を作成したら、それを入力ストリームで実行して、次のように最初の(または次の)ドキュメントの先頭にすばやく移動できます。
私が想定し:
-docstart
は、すべてのドキュメントの先頭に一致する正規表現です
-search(regex, stream)
は、stream
と一致する部分文字列をregex
で検索する関数です。戻ったとき、ストリームは、最初に一致する部分文字列の先頭から始まるサフィックスサブストリームに削減されるか、空のストリームに一致が見つからない場合に削減されます。
-parse(stream)
は、ストリームの先頭(残りのもの)からドキュメントを解析しようとし、解析ツリーを任意の形式で返すか、失敗します。返されると、ストリームは、解析されたドキュメントの終わりの直後の位置から始まるサフィックスサブストリームに縮小されます。解析が失敗した場合は、例外を呼び出します。
forest = empty_forest
search(docstart, stream)
while stream is not empty:
try:
forest = forest + parse(stream)
except
remove first character from stream
search(docstart, stream)
次の検索で同じ一致が再び見つからないように、最初の文字を削除する必要があることに注意してください。
もちろん、ストリームの短縮はイメージです。それは単にストリームのインデックスであるかもしれません。
最後の注意点は、すべての始まりを認識している限り、正規表現はあまり正確である必要はないということです。ドキュメントの先頭になり得ない文字列(誤検知)を時々認識する場合、唯一のペナルティは、パーサーへの1回の無駄な呼び出しのコストです。
そのため、有用であれば、正規表現の簡略化に役立つ可能性があります。
上記のソリューションは、ほとんどの場合非常にうまく機能します。ただし、本当に大量のガベージとテラバイトのファイルを処理する必要がある場合は、より高速に実行できる他のアルゴリズムがある可能性があります。
このアイデアは Boyer-Moore文字列検索アルゴリズム から派生しています。このアルゴリズムは、文字列の構造分析を使用してほとんどのストリームの読み取りをスキップし、フラグメントを見ることなくジャンプするため、ストリームで単一の文字列を非常に高速に検索できます。これは、単一の文字列を検索する最速のアルゴリズムです。
難しさは、単一の文字列ではなく正規表現を検索するためのその適応が非常にデリケートに見え、検討している正規表現の機能によっては機能しない場合があることです。これは、解析しているドキュメントの構文に依存する場合があります。しかし、私が見つけた文書を注意深く読む時間がなかったので、これについてはあまり信用しないでください。
私はあなたを残します 1つ または 2つのポインタ 明らかに 査読済みの研究論文 を含むものを含めて、Web上で見つけましたが、検討する必要がありますこれは、投機的で、おそらく調査的なものであり、パフォーマンスに大きな問題があった場合にのみ検討してください。そして、それを実行するシェルフプログラムはおそらくないでしょう。