web-dev-qa-db-ja.com

レクサーがパーサーに返すトークンのデータ型は何ですか?

タイトルで述べたように、レクサーがパーサーを返す/与えるデータ型はどれですか?ウィキペディアの 字句解析 の記事を読んだとき、それは次のように述べています:

コンピュータサイエンスでは、字句解析は一連の文字(コンピュータプログラムやWebページなど)を一連のトークン(stringsに変換するプロセスです。 )識別された「意味」を使用して)。

ただし、上記の説明とはまったく異なり、別のサイトで質問した別の質問( Code Review 興味がある場合)が回答されたとき、回答者は次のように述べています。

レクサーは通常stringを読み取り、これを語彙素のストリーム...に変換します。語彙素は、numbersのストリームである必要があります。

そして彼はこのビジュアルを与えました:

nl_output => 256
output    => 257
<string>  => 258

記事の後半で、彼は既存のレクサーであるFlexについて言及し、それを使って「ルール」を書くことは、レクサーを手動で書くよりも簡単だと述べました。彼は私にこの例を与え始めました:

Space              [ \r\n\t]
QuotedString       "[^"]*"
%%
nl_output          {return 256;}
output             {return 257;}
{QuotedString}     {return 258;}
{Space}            {/* Ignore */}
.                  {error("Unmatched character");}
%%

私の洞察をさらに深め、より多くの情報を得るために、私は Flex に関するウィキペディアの記事を読みました。 Flexの記事では、トークンを使用して次の方法で構文ルールのセットを定義できることが示されています。

digit         [0-9]
letter        [a-zA-Z]

%%
"+"                  { return PLUS;       }
"-"                  { return MINUS;      }
"*"                  { return TIMES;      }
"/"                  { return SLASH;      }
"("                  { return LPAREN;     }
")"                  { return RPAREN;     }
";"                  { return SEMICOLON;  }
","                  { return COMMA;      }
"."                  { return PERIOD;     }
":="                 { return BECOMES;    }
"="                  { return EQL;        }
"<>"                 { return NEQ;        }
"<"                  { return LSS;        }
">"                  { return GTR;        }
"<="                 { return LEQ;        }
">="                 { return GEQ;        }
"begin"              { return BEGINSYM;   }
"call"               { return CALLSYM;    }
"const"              { return CONSTSYM;   }
"do"                 { return DOSYM;      }
"end"                { return ENDSYM;     }
"if"                 { return IFSYM;      }
"odd"                { return ODDSYM;     }
"procedure"          { return PROCSYM;    }
"then"               { return THENSYM;    }
"var"                { return VARSYM;     }
"while"              { return WHILESYM;   }

Flexレクサーがkeyword\tokensの文字列を返しているように見えます。しかし、特定の数に等しい定数を返す可能性があります。

レクサーが数値を返す場合、文字列リテラルをどのように読み取りますか?単一のキーワードでは数値を返すのは問題ありませんが、文字列をどのように処理しますか?レクサーは文字列を2進数に変換する必要はなく、パーサーは数値を文字列に変換します。レクサーが文字列を返し、パーサーに任意の数値文字列リテラルを実際の数値に変換させる方が、はるかに論理的で簡単です。

または、レクサーが両方を返す可能性はありますか?私はc ++で単純なレクサーを作成しようとしています。これにより、関数の戻り値の型が1つだけになります。このように私に私の質問をするように導きます。

質問を段落に要約するには:字句解析器を記述し、それがoneデータ型(文字列または数値)のみを返すことができると仮定すると、より論理的な選択ですか?

21
Christian Dean

一般に、字句解析と構文解析を介して言語を処理している場合、字句トークンの定義があります。例:

NUMBER ::= [0-9]+
ID     ::= [a-Z]+, except for keywords
IF     ::= 'if'
LPAREN ::= '('
RPAREN ::= ')'
COMMA  ::= ','
LBRACE ::= '{'
RBRACE ::= '}'
SEMICOLON ::= ';'
...

そして、あなたはパーサーのための文法を持っています:

STATEMENT ::= IF LPAREN EXPR RPAREN STATEMENT
            | LBRACE STATEMENT BRACE
            | EXPR SEMICOLON
EXPR      ::= ID
            | NUMBER
            | ID LPAREN EXPRS RPAREN
...

レクサーは入力ストリームを受け取り、トークンのストリームを生成します。トークンのストリームはパーサーによって消費され、解析ツリーが作成されます。場合によっては、トークンのtypeを知るだけで十分です(たとえば、LPAREN、RBRACE、FOR)。しかし、場合によっては、実際のvalueが必要になりますトークンに関連付けられています。たとえば、IDトークンに遭遇した場合、後で参照しようとしている識別子を特定しようとするときに、IDを構成する実際の文字が必要になります。

したがって、通常は次のようなものがあります。

enum TokenType {
  NUMBER, ID, IF, LPAREN, RPAREN, ...;
}

class Token {
  TokenType type;
  String value;
}

したがって、レクサーがトークンを返すと、それがどのタイプであるか(解析に必要)と、それが生成された文字のシーケンス(後で文字列と数値リテラル、識別子を解釈するために必要になる)がわかります。等。)。非常に単純な集約型を返すため、2つの値を返すように感じるかもしれませんが、実際には両方の部分が必要です。結局のところ、次のプログラムを別の方法で扱う必要があります。

if (2 > 0) {
  print("2 > 0");
}
if (0 > 2) {
  print("0 > 2");
}

これらは、トークンの同じシーケンスtypesを生成します。IF、LPAREN、NUMBER、GREATER_THAN、NUMBER、RPAREN、LBRACE、ID、LPAREN、STRING、RPAREN、SEMICOLON、RBRACE。つまり、それらはparseも同じです。しかし、実際に解析ツリーで何かをしているときは、最初の数値の値が「2」(または「0」)であり、2番目の数値の値が「0」(または「2」)であることを気にするでしょう。 ')、文字列の値は' 2> 0 '(または' 0> 2 ')です。

10
Joshua Taylor

タイトルで述べたように、レクサーがパーサーを返す/与えるデータ型はどれですか?

「トークン」、明らかに。レクサーはトークンのストリームを生成するため、tokensのストリームを返す必要があります。

彼は既存のレクサーであるフレックスについて言及し、それを使って「ルール」を書くことは、レクサーを手動で書くよりも簡単だと述べました。

機械で生成されたレクサーには、すばやく生成できるという利点があります。これは、語彙の文法が大きく変わると考えられる場合に特に便利です。それらには、多くの場合、実装の選択において多くの柔軟性が得られないという欠点があります。

とはいえ、それが「より単純」であるかどうかは誰が気にしますか?レクサーを書くことは通常難しいことではありません!

レクサーを作成し、1つのデータ型(文字列または数値)のみを返すことができると想定すると、どちらがより論理的な選択になりますか?

どちらでもない。レクサーには通常、トークンを返す「次の」操作があるため、トークンを返す必要があります。トークンは文字列でも数値でもありません。それはトークンです。

私が書いた最後のレクサーは「完全な忠実度」のレクサーでした。つまり、プログラム内のすべての空白とコメント(「トリビア」と呼ぶ)の位置を追跡するトークンとトークンを返しました。私のレクサーでは、トークンは次のように定義されています。

  • 主要な雑学の配列
  • トークンの種類
  • 文字単位のトークン幅
  • 末尾の雑学の配列

雑学は次のように定義されました:

  • 雑学クイズ-空白、改行、コメントなど
  • 文字での雑学の幅

だから私たちのようなものがあった場合

    foo + /* comment */
/* another comment */ bar;

これは、トークンの種類IdentifierPlusIdentifierSemicolon、幅3、1、3、1の4つのトークンとしてLexします。最初の識別子幅4のWhitespaceと後続幅の1のトリビアWhitespaceで構成される先行トリビアがあります。Plusには、1つの空白で構成される先行トリビアと後続トリビアがありません。コメントと改行。最終的な識別子には、コメントとスペースの先行トリビアがあります。

このスキームでは、ファイル内のすべての文字がレクサーの出力に含まれます。これは、構文の色付けなどに役立つ便利なプロパティです。

もちろん、トリビアが必要ない場合は、単純にトークンを2つ作成できます。種類と幅です。

トークンとトリビアには幅だけが含まれ、ソースコード内の絶対位置は含まれていないことに気付くでしょう。それは意図的です。そのようなスキームには利点があります:

  • メモリとワイヤ形式がコンパクト
  • 編集時に再字句解析を可能にします。これは、レクサーがIDE内で実行されている場合に役立ちます。つまり、トークンの編集を検出した場合、編集の前にレクサーをいくつかのトークンにバックアップし、以前のトークンストリームと同期されるまで、再び字句解析を開始します。文字を入力すると、その文字が変更された後のすべてのトークンのpositionですが、通常は幅が1つまたは2つのトークンしか変更されないため、再利用できますそのすべての状態。
  • 各トークンの正確な文字オフセットは、トークンストリームを反復処理して現在のオフセットを追跡することで簡単に導出できます。正確な文字オフセットを取得すると、必要なときにテキストを簡単に抽出できます。

これらのシナリオのいずれも気にしない場合、トークンは、種類と幅ではなく、種類とオフセットとして表すことができます。

しかし、ここでの重要なポイントは次のとおりです。プログラミングは、有用な抽象化を行う技術です。トークンを操作しているので、トークンに対して有用な抽象化を行うと、実装の詳細の基礎を自分で選択できるようになります。

6
Eric Lippert

一般的に、トークンを表す番号(または使いやすさのために列挙値)とオプションの値(文字列、または汎用/テンプレート値)を持つ小さな構造を返します。別のアプローチは、追加のデータを運ぶ必要がある要素の派生型を返すことです。どちらもやや不快ですが、実用的な問題に対する十分な解決策です。

3
Telastyn