現在ログインしていません。
新規アカウント作成
ログイン

Antlrの字句定義

Antlr4の文法ファイル(g4)で字句定義方法のメモ 字句定義は構文定義の前提として、そもそもその文法ではどのような単語や記号を使うのかを定義したものです。 英語のドキュメントでは字句は Lexicon、字句定義したものを解析するプログラムは Lexer と表現されています。

字句定義は g4 ファイルに記載します。このファイルは UTF-8 で作成することをお勧めします。

基本構文

字句の定義は通常複数必要です。

それぞれの定義には字句定義名(Token name)をつけます。

下記は1つの字句定義の例です。

ID 
: [a-zA-Z]+ 

■リスト1:

この例では ID が字句定義名、[a-zA-Z]+が字句定義の内容を示しています。 字句定義名の後ろには : が必要であり、字句定義の最後には ; が必要です。

つまり。1つの字句定義は次の構造は次の通りです。

字句定義名 : 定義内容 ;
■リスト2:

字句定義名は必ず英語の大文字である必要があります。 構文定義ではルール名は英語の小文字から始まることになっているので、大文字か小文字かで字句定義なのか構文定義なのかが区別されます。

字句定義名の2文字目以降には日本語のひらがなや漢字も使用できます。よくある記号ではアンダースコアが使用できます。

有効な字句定義名の例

  • ID
  • Operator
  • NEWLINE

コメント

文法ファイル内の記載でもコメントは人間用のメモ書きであり、字句定義とはみなされません。 コメントの書き方はC#やJavaと同じ形式で、3種類の書き方があります。

/* と */ に囲まれた1行または複数行はまとめてコメントになります。 // の後ろの部分はその1行の中ではコメントになります。

字句定義

リテラル

最も簡単な字句定義は字句をリテラルで定義することです。リテラルはシングルクォーテーションで囲んで表現します。

次の例ではDimStatementという字句定義名で Dim という単語を定義します。

DimStatement 
: 'Dim' 

■リスト3:

リテラルの内容には記号やエスケープシーケンスも指定できます。

エスケープシーケンスは \t や \n などの他、\uXXXX または \u{XXXXXX} の形式でUnicodeの文字(正確にはコードポイント)を指定することもできます。 しかし、「あ」や「中」などの文字を直接入力することもできるので、日本の文字に関しては日本人利用者は直接入力した方が早いです。

なお、字句定義の中では半角スペースは無視されるため、この定義は次のように書いても同じはずです。(試していませんが)

DimStatement 
: 'D' 'i' 'm' 

■リスト4:

この例では半角スペースを付けて分ける意味がまったくありませんが、複雑になってくると見やすいように定義の中に半角スペースを入れることが多くなります。

複数の字句

字句定義内で | を使用すると、 または という意味になります。

次の例では Operator は + または - または * または / または = であることを定義しています。

Operator
: '+' | '-' | '*' | '/' | '='
        ;
■リスト5:

正規表現

複雑な字句の定義は正規表現と同じように記述できるようです。 次の例では、WS は 1つ以上の半角スペース または 1つ以上のタブ であることを定義しています。

字句定義内で [ ] で囲んだ部分は正規表現と同様にこの中にどれか1つという意味になります。 + はそれが1つ以上連続することを示します。

WS
   : [ \t] +
   ;
■リスト6:

字句定義の参照

字句定義内では別に定義した字句定義を使用することができます。

下記の例はVBの仕様で、: を使うと1行の中に複数の命令を記述できるというものを表現しています。

つまり、論知的な改行(NEWLINE)の定義は \r や \r\n のような通常の改行コード以外に : もあるという意味です。

COLON
   : ':'
   ;

NEWLINE
   : WS? ('\r'? '\n' | COLON ' ') WS?
   ;

WS
   : [ \t] +
   ;
■リスト7:

字句定義内ではまたフラグメント(flagment)と呼ばれる断片を参照することもできます。 フラグメントは字句定義名の前に fragment を付けて表現します。

フラグメントとは、字句定義にしたいわけではないのだけど、複雑な字句定義をするときに定義を分割して書きたいというときのその分割されたものをさしているようです。

たとえば、NUMBERという字句定義で10進数、8進数、16進数をまとめる場合は次のようにフラグメントを使用すると便利なようです。

NUMBER: DIGITS | OCTAL_DIGITS | HEX_DIGITS;
fragment DIGITS: '1'..'9' '0'..'9'*;
fragment OCTAL_DIGITS: '0' '0'..'7'+;
fragment HEX_DIGITS: '0x' ('0'..'9' | 'a'..'f' | 'A'..'F')+;
■リスト8:

What does "fragment" mean in ANTLR?

サンプル

HTMLのコメント

HTML_COMMENT
    : '<!--' .*? '-->'
    ;
■リスト9:

大文字と小文字を区別しないIfの定義。 先に大文字と小文字を区別しない F と I の定義を作っておきその組み合わせで IF を定義しています。

IF
   : I F
   ;

fragment F
   : ('f' | 'F')
   ;

fragment I
   : ('i' | 'I')
   ;

■リスト10:

モード

モードを使うと、1つの文法ファイルの中で異なる字句定義をすることができるようです。

現段階では私の想像ですが、たとえば、HTMLの字句定義を行いたい場合、Yahoo!やGoogleなど実際のWebサイトのHTMLは純粋なHTMLだけではなく、CSSやJavaScriptなどいろいろな文法がまざっています。これをすべてHTMLとして字句定義するとものすごく複雑になってしまうので、<style>を発見したらCSSモード、<script>を発見したJavaScriptモードなど、字句解析のモードを切り替えて使うのではないかと思います。

VBやC#などの1つのプログラミング言語の中でも、文字列の中はルールが違ったりするので、そういうシーンでも使うことになるのではないかと思います。 VBのXMLリテラルやC#のunsafeでも特別なモードに切り替える必要がありそうですね。

具体的なモードののことはよくわからないですが、どうも次のようにしてモードを定義するようです。モードの名前はある程度自由なようです。この例ではSCRIPTというモードを定義しています。

mode SCRIPT;
■リスト11:

モードの終了は別のモードが開始されるかファイルの末尾に到達するまでではないかと思います。なのでモードの定義はファイルの下の方に集まることになると推測します。

モードの中の字句定義は通常と同じです。

モードの切り替えは pushMode(モード名)、モードの終了は popMode と記述するようです。

次の例は <scriptを発見したら SCRIPTモードに切り替えるという意味のようです。

SCRIPT_OPEN
    : '<script' .*? '>' ->pushMode(SCRIPT)
■リスト12:

次の例は、</script>を発見したら、モードを終了するという意味のようです。

SCRIPT_BODY
    : .*? '</script>' -> popMode
    ;
■リスト13:

アクション

私はよくわからないのですが、文法定義にはアクションというプログラムを含めることが可能とのことです。

アクションは{ } で囲んで、対象のプログラミング言語で記述します。VBで構文解析をプログラムしようとする場合は、AntlrにまずC#のソースコードを生成させる必要があるので、アクションはC#で記述することになるようです。

アクションの内容は @header と @member という名前付きアクションの中で使用するということです。

Antlrが生成するコード内に自作のコードを挿入できるという意味だと思いますが、ひとまずはそういうことができるということだけ覚えておきたいと思います。