レクサーのトークンを考え出す


14

私が作成したマークアップ言語のパーサーを書いています(Pythonで書いていますが、これはこの質問にはあまり関係ありません-実際、これが悪い考えのように思える場合は、より良いパスの提案が欲しいです) 。

私はここでパーサーについて読んでいます:http : //www.ferg.org/parsing/index.html、そして、私が正しく理解すれば、コンテンツをトークンに分割するべきレクサーの作成に取り組んでいます。理解できないのは、どのトークンタイプを使用するか、またはどのように作成するかです。たとえば、リンクした例のトークンタイプは次のとおりです。

  • ストリング
  • 識別子
  • ホワイトスペース
  • コメント
  • EOF
  • {や(などの多くの記号は、独自のトークンタイプとしてカウントされます

私が抱えている問題は、より一般的なトークンの種類が私にとって少しarbitrary意的であるように見えることです。たとえば、なぜSTRINGは独自のトークンタイプとIDENTIFIERであるのですか。文字列は、STRING_START +(IDENTIFIER | WHITESPACE)+ STRING_STARTとして表すことができます。

これは私の言語の難しさに関係しているかもしれません。たとえば、変数宣言はとして記述され{var-name var value}、でデプロイされ{var-name}ます。それはのように思える'{'し、'}'自分自身のトークンにする必要がありますが、VAR_NAMEとVAR_VALUE資格トークンタイプで、またはこれらの識別子の下で、両方の秋のでしょうか?さらに、VAR_VALUEには実際に空白を含めることができます。後の空白var-nameは、宣言内の値の開始を示すために使用されます。他の空白は値の一部です。この空白は独自のトークンになりますか?空白は、このコンテキストでのみその意味を持ちます。さらに、{変数宣言の始まりではないかもしれません..それはコンテキストに依存します(その言葉が再びあります!)。 {:名前宣言を開始し、{ 値の一部として使用することもできます。

私の言語は、ブロックがインデントで作成されるという点でPythonに似ています。Pythonがlexerを使用してINDENTおよびDEDENTトークンを作成する方法について読んでいました(多かれ少なかれ、他の多くの言語で何{をし、何を}するのか)。Pythonはコンテキストフリーであると主張しています。つまり、少なくとも字句解析者は、トークンの作成中にストリーム内のどこにいるかを気にする必要はありません。Pythonのレクサーは、前の文字を知らずに特定の長さのINDENTトークンを構築していることをどのように認識しますか(たとえば、前の行が改行だったので、INDENTのスペースの作成を開始します)。私もこれを知る必要があるので尋ねます。

私の最後の質問は最も愚かなものです。なぜレクサーが必要なのでしょうか?パーサーは文字ごとに行って、それがどこにあり、何を期待しているかを把握できるように思えます。レクサーは単純さの利点を追加しますか?


2
aheeadに移動して、スキャナーレスパーサーを作成してみてください。それがまったく機能する場合(一部の解析アルゴリズムでは結果が曖昧すぎる可能性があると思います)、すべての「空白もここで許可されています」と「待って、解析していました」の下に実際の文法が表示されない可能性があります識別子または番号?」。私は経験から話しています。

カスタムホイールを再発明する理由 特注のレクサーを必要とする言語を設計するのではなく、LISPやFORTHなど、ビルトインレクサーに付属している既存の言語を使用することを検討しましたか?
ジョンR.ストローム

2
アカデミック目的の@ JohnR.Strohm。とにかく、言語自体はおそらく実用的ではないでしょう。
爆発

回答:


11

最終的な段落のヒントとしての質問は、実際にはレクサーに関するものではなく、レクサーとパーサーの間のインターフェイスの正しい設計に関するものです。ご想像のとおり、レクサーとパーサーの設計に関する本がたくさんあります。私はたまたまDick Gruneのパーサー本が好きですが、それは良い入門書ではないかもしれません。AppelのCベースの本は非常に嫌いです。なぜなら、コードはあなた自身のコンパイラに有用に拡張できないからです(Cのふりをする決定に内在するメモリ管理の問題はMLに似ているため)。私自身の紹介はPJ Brownによる本でし一般的な紹介としては適切ではありません(特に通訳には非常に適しています)。しかし、質問に戻ります。

答えは、前方または後方に見える制約を使用する必要なしに、レクサーでできる限りのことを行うことです。

これは、(もちろん言語の詳細に応じて)文字列を「文字の後にnot-のシーケンスが続き、次に別の文字」として認識する必要があることを意味します。単一のユニットとしてパーサーに返します。これには理由がありますが、重要なのは

  1. これにより、パーサーが維持する必要がある状態の量が減り、メモリ消費が制限されます。
  2. これにより、レクサーの実装は基本的な構成要素の認識に集中し、パーサーを解放して個々の構文要素を使用してプログラムを構築する方法を記述できます。

多くの場合、パーサーはレクサーからトークンを受信するとすぐにアクションを実行できます。たとえば、IDENTIFIERを受信するとすぐに、パーサーはシンボルテーブルのルックアップを実行して、シンボルが既に認識されているかどうかを確認できます。パーサーが文字列定数をQUOTE(IDENTIFIER SPACES)* QUOTEとしても解析する場合、多くの無関係なシンボルテーブルルックアップを実行します。これで、文字列を見ていないことが確実になります。

私が言おうとしていることを言い換えると、レクサーは物のつづりに注意し、パーサーは物の構造に注意する必要があります。

文字列がどのように見えるかについての私の説明は、正規表現によく似ていることに気付くかもしれません。これは偶然ではありません。字句アナライザは、正規表現を使用する小さな言語Jon Bentleyの優れたプログラミングパールの本の意味)で頻繁に実装されます。私はテキストを認識するときに正規表現の観点から考えることに慣れています。

空白に関する質問については、レクサーでそれを認識してください。言語がかなり自由な形式であることを意図している場合は、WHITESPACEトークンをパーサーに返さないでください。破棄するだけなので、パーサーのプロダクションルールは本質的にノイズでスパムされます。それらを離れて。

構文的に重要な空白をどのように扱うべきかについて、それがどういう意味かについては、あなたの言語についてもっと知らなくても本当にうまくいくと判断できるかどうかはわかりません。私の簡単な判断は、空白が重要な場合とそうでない場合を回避し、ある種の区切り文字(引用符など)を使用することです。ただし、言語を好みの方法で設計できない場合は、このオプションを使用できない場合があります。

言語解析システムを設計する方法は他にもあります。確かに、レクサーとパーサーを組み合わせたシステムを指定できるコンパイラー構築システムがあります(ANTLRのJavaバージョンがこれを行うと思います)が、使用したことはありません。

最後に歴史的なメモ。数十年前は、2つのプログラムが同時にメモリーに収まらないため、パーサーに渡す前に、レクサーができる限り多くのことを行うことが重要でした。レクサーでより多くの処理を行うと、より多くのメモリが使用可能になり、パーサーがスマートになります。私は何年もWhitesmiths Cコンパイラを使用していましたが、正しく理解すれば、64KBのRAM(小さなモデルのMS-DOSプログラムでした)でしか動作せず、 ANSI Cに非常に近かった。


そもそもジョブをレクサーとパーサーに分割する理由の1つは、メモリサイズに関する歴史的なメモです。
-stevegt

3

最後の質問にお答えしますが、これは実際には愚かではありません。パーサーは、文字ごとに複雑な構造を構築できます。思い出すと、Harbison and Steeleの文法(「C-A reference manual」)には、単一の文字を端末として使用し、その単一の文字から識別子、文字列、数字などを非端末として作成するプロダクションがあります。

正式な言語の観点から、正規表現ベースのレクサーが認識し、「文字列リテラル」、「識別子」、「数値」、「キーワード」などとして分類できるものはすべて、LL(1)パーサーでも認識できます。したがって、パーサージェネレーターを使用してすべてを認識しても、理論上の問題はありません。

アルゴリズムの観点から、正規表現認識エンジンは、どのパーサーよりもはるかに高速に実行できます。認知の観点からは、おそらくプログラマーが正規表現レクサーとパーサージェネレーターで記述されたパーサーの間の作業を分割する方が簡単です。

実用的な考慮事項により、人々はレクサーとパーサーを別々にする決定を下すと思います。


はい-C標準自体も同じことをします。まるで正しく思い出すように、KernighanとRitchieの両方のエディションがそうでした。
ジェームズヤングマン

3

文法を実際に理解せずにレクサー/パーサーを作成しようとしているようです。通常、人々がレクサーとパーサーを書いているとき、彼らはいくつかの文法に適合するようにそれらを書いています。 構文解析器はこれらのトークンを使用してrules / non-terminalsに一致する一方で、字句解析器は文法でトークンを返す必要があります。バイト単位で入力を簡単に解析できる場合は、レクサーとパーサーが過剰である可能性があります。

レクサーは物事を簡単にします。

文法の概要:文法は、構文または入力がどのように見えるかに関する一連の規則です。たとえば、おもちゃの文法は次のとおりです(simple_commandは開始記号です)。

simple_command:
 WORD DIGIT AND_SYMBOL
simple_command:
     addition_expression

addition_expression:
    NUM '+' NUM

この文法は次のことを意味し
ます-simple_commandは次のいずれかで構成されます
A)WORDに続いてDIGITに続いてAND_SYMBOL(これらは定義する「トークン」)
B)「addition_expression」(これはルールまたは「非終端」)ます。

additional_expressionは次のもので構成されます。
NUMの後に「+」、続いてNUMが続きます(NUMは定義する「トークン」、「+」はリテラルのプラス記号です)。

したがって、simple_commandは「開始記号」(開始する場所)であるため、トークンを受け取ると、simple_commandに収まるかどうかを確認します。入力の最初のトークンがWORDで、次のトークンがDIGITで、次のトークンがAND_SYMBOLである場合、simple_commandと一致し、何らかのアクションを実行できます。それ以外の場合は、simple_commandの他のルールであるaddition_expressionと一致させようとします。したがって、最初のトークンがNUMで、その後に「+」、その後にNUMが続く場合、simple_commandと一致し、何らかのアクションを実行します。これらのいずれでもない場合、構文エラーがあります。

これは非常に基本的な文法入門です。より完全に理解するには、このwiki記事をご覧くださいを、文脈のない文法チュートリアルをWebで検索してください。

レクサー/パーサーの配置を使用して、パーサーがどのように見えるかの例を次に示します。

bool simple_command(){
   if (peek_next_token() == WORD){
       get_next_token();
       if (get_next_token() == DIGIT){
           if (get_next_token() == AND_SYMBOL){
               return true;
           } 
       }
   }
   else if (addition_expression()){
       return true;
   }

   return false;
}

bool addition_expression(){
    if (get_next_token() == NUM){
        if (get_next_token() == '+'){
             if (get_next_token() == NUM){
                  return true;
             }
        }
    }
    return false;
}

わかりましたので、そのコードはcodeいので、トリプルネストされたifステートメントはお勧めしません。しかし、ポイントは、素敵なモジュラー「get_next_token」および「peek_next_token」関数を使用する代わりに、文字ごとに上記のことをしようとすることを想像してください。真剣に、それを試してみてください。結果が気に入らないでしょう。ここで、上記の文法は、ほとんどすべての有用な文法よりも約30倍複雑でないことに注意してください。レクサーを使用するメリットはありますか?

正直なところ、レクサーとパーサーは世界で最も基本的なトピックではありません。最初に文法について読んで理解し、次にレクサー/パーサーについて少し読んでから、飛び込むことをお勧めします。


文法について学ぶための推奨事項はありますか?
爆発薬

答えを編集して、非常に基本的な文法の紹介と、さらに学習するための提案を追加しました。文法はコンピューターサイエンスで非常に重要なトピックなので、学ぶ価値があります。
ケーシーパットン

1

私の最後の質問は最も愚かなものです。なぜレクサーが必要なのでしょうか?パーサーは文字ごとに行って、それがどこにあり、何を期待しているかを把握できるように思えます。

これは愚かではなく、単なる真実です。

しかし、実用性はどういうわけかあなたのツールと目的に少し依存します。たとえば、レクサーなしでyaccを使用し、識別子にユニコード文字を許可する場合、すべての有効な文字を明示的に列挙する大きくてandいルールを作成する必要があります。一方、レクサーでは、文字が文字カテゴリのメンバーであるかどうかをライブラリルーチンに尋ねることができます。

字句解析器を使用するかどうかは、言語と文字レベルの間に抽象化レベルがあることです。現在、文字レベルはバイトレベルより上の抽象化であり、ビットレベルより上の抽象化であることに注意してください。

したがって、最後に、ビットレベルで解析することもできます。


0
STRING_START + (IDENTIFIER | WHITESPACE) + STRING_START.

いいえ、できません。どう?"("?あなたによると、それは有効な文字列ではありません。そして逃げる?

一般に、空白を処理する最良の方法は、トークンの区切りを超えて空白を無視することです。多くの人が非常に異なる空白を好むため、空白ルールを強制することはせいぜい論争の的となっています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.