Lex
Lex(レック、レックス)は、1975年にエリック・シュミットとマイク・レスクによって開発された、レキシカルアナライザ(字句解析プログラム、字句解析器)を生成するプログラムである。unixにおける標準のレキシカルアナライザとなっており、POSIX標準ともなっている。コンパイラの作成のためにパーサジェネレータのyaccとともに使用されることも多い。
Lexはレキシカルアナライザジェネレータである。すなわちレキシカルアナライザ(字句解析プログラム、字句解析器)の生成ツールであり、Lexの名称も英語のLexical analysisからきている。
字句解析はテキスト中の文字列の変換、カウント、抽出などさまざまな目的に使われ、その応用領域は、コンパイラやコンバータの作成を筆頭に、自然言語処理や簡単な整形まで幅広い。
このうち、コンパイラにおけるレキシカルアナライザの位置づけを、以下に説明する。
プログラムを中間言語あるいは機械語に変換するコンパイラは、一般的にソースを入力し構文木を出力する構文解析部(1)と、その構文木を入力し中間言語コードまたは機械語を出力するコード生成部(2)からなる。
├構文解析部(1)
│ ├字句解析器(1.1) ← [ソース]
│ │呼出し↑↓
│ │   ↑ [トークン列]
│ │   ↑↓
│ └構文解析器(1.2)
│     ↓
│   [構文木]
│     ↓
└コード生成部(2)   → [中間言語コードまたは機械語]
このうち(1)の前半は、ソースを入力しトークン(語彙素)列を出力する字句解析器(レキシカルアナライザ、トークナイザ、スキャナ)(1.1)である。
後半は、そのトークン列を入力し、構文規則にしたがって構文解析をし、構文木を出力する構文解析器(パーサ、パーザ)(1.2)である。
(1.1)のレキシカルアナライザを生成するのが、レキシカルアナライザジェネレータである。
構文解析器(1.2)に解析を数字や英字や空白などの1文字単位で行わせると、複雜になりすぎる。しかし、人間が英文から英単語や数字などの記号列を、区切り文字(たとえば空白、タブ、改行、コンマ、終止符、カッコ)やその列を目印に抽出して、意味を判断しているのと、同様の発想ができる。すなわち、区切り記号列でソースを切っていくと、「print」のような語、「1999」のような10進数、「"Hello, world"」といった文字リテラル、「++」といった演算子、「}」や「;」など意味のある区切り文字など、各種の文字列が取り出せる。これをトークンという。ここまでの下位の文法処理を上記字句解析器(1.1)に行わせ、一方、構文解析器(1.2)はトークンから出発して句、文、ブロック、プログラムなどを認識する上位の文法処理に専念させる。この分業化により、それぞれの定義と処理を簡潔にできる。
この字句解析器(1.1)の合理的な開発を目的とし、機械可読にした規則定義を与えれば字句解析器を自動生成してくれる便利なツールがレキシカルアナライザジェネレータであり、LexやFlexなどがそれに属する。
[規則定義]
↓
レキシカルアナライザジェネレータ(Lex, Flexなど)
↓
[字句解析器(1.1)]
同様に、構文解析器(1.2)の合理的な開発を目的とし、構文規則定義を与えれば構文解析器を自動生成してくれる便利なツールとして、Yacc(Yet Another Compiler Compiler)などのパーサジェネレータ(コンパイラコンパイラ)がある。
Yaccなどの構文解析器生成ツールを利用するなら、Lexに字句文法定義を与えて生成させたC言語ソースである字句解析器が(Yaccがトークンをユーザから得るための)yylex関数を含んでいるので、CコンパイラでYacc出力と一緒にこれをリンクして組み込む。これにより、ソーステキストの字句解析と構文解析を両方行って、規則のアクション部(あるいはさらにそれに呼ばれるユーザ作成のC言語関数)に書かれた計算の結果や、コンパイルの生成に使われる抽象構文木の構造体データ、あるいは各種表示が出力される変換プログラムが完成する。
YaccとLexはよく似た文法定義をもち、セットで使われ、セットで解説されることが多い。LexとYaccの機能はIEEE POSIX 1003.1-2008(かつては1003.2)で標準化されている。
YaccはほとんどのUNIXシステムで、デフォルトのレキスカルアナライザジェネレータとして利用可能だった。
Lexは多くのシステム、多くのプログラミング言語に移植されている。たとえばJava言語による字句解析器を生成するLex系ツールに、、それを書き直したがある。このJFlexとJava言語用構文解析器と組みあわせて用いることも行われている。
Lexと同等の機能を有し性能が改善されているFlex(英語版)というものもあるが、開発者がLexの開発者とは別人のVern Paxsonであり、ライセンスもLexが Plan 9: MIT Licenseなのに対しFlexのほうはBSDライセンスであり、つまりは全く別のソフトウェアであり、しっかり区別したほうがよい。
Flexについては別記事 Flex (字句解析器・生成器) を立ち上げ、そちらで説明すること。
Lexのファイル構造は意識的にyaccのそれに似せて定義されている。
ファイルは3つの部分に分割されており、それぞれ定義領域、規則領域、Cコード領域である。各領域はパーセント記号2つ(%%)のみを含んだ行で区切られる。
定義領域は正規表現を用いてマクロを定義するところであり、かつCのヘッダーファイルを取り込む場所でもある。
規則領域は最も重要な領域でありCの命令との関連付けを行う。Lexの規則と一致するパターンがあるとそれに関連付けされたCコードを実行する。
Cコード領域には生成したソースにそのままコピーされるCの命令や関数が含まれている。
これらの命令は規則領域での規則により呼ばれたコードを含む場合もある。大規模なプログラムではここに分割しておきコンパイル時にリンクするほうが便利である。
通常の正規表現の特殊文字にくわえ、「<tt>"</tt>」 「<tt>/</tt>」 「<tt>%</tt>」 「<tt>(</tt>」 「<tt>)</tt>」 「<tt>+</tt>」 「<tt>/</tt>」 「<tt><</tt>」 「<tt>></tt>」 「<tt>?</tt>」 「<tt>{</tt>」 「<tt>|</tt>」 「<tt>}</tt>」 も特殊文字である
行頭の「<tt>%</tt>」は、上述のように領域を区切ったり、Lex特有の指示をあたえるために使う
文字クラス、否定文字クラスの内側で「<tt>\</tt>」が特殊文字になる
陽に指定したときを除いて、否定文字クラスは、改行にも照合する
「<tt>"</tt>」で引用できる。引用の終了も「<tt>"</tt>」
「<tt>パターン{</tt>最少<tt>,</tt>最多<tt>}</tt>」や「<tt>パターン{</tt>回数<tt>}</tt>」でパターンの繰返し回数を指定できる (Grepの拡張正規表現と同じ)
「<tt>+</tt>」は「<tt>{1,}</tt>」の、「<tt>?</tt>」は「<tt>{,1}</tt>」の、それぞれの省略形である (Grepの拡張正規表現と同じ)
「<tt>|</tt>」で択一できる。「<tt>(</tt>」と「<tt>)</tt>」で範囲を広げて指定できる (Grepの拡張正規表現と同じ)
「<tt>\b</tt>」「<tt>\f</tt>」「<tt>\n</tt>」「<tt>\r</tt>」「<tt>\t</tt>」「<tt>\v</tt>」「<tt>\</tt>8進数」は、Cの文字定数の定義と同じ
「</tt><</tt>開始条件<tt>></tt>正規表現」で、指定の名前の開始条件にあるときの正規表現だけに照合できる。開始条件は、コンマで区切って複数指定できる
「r<tt>/</tt>s」は、正規表現「s」の後続する正規表現「r」を表わす。アクションで参照できるのが「r」のみになるところが、重要である
「<tt>{</tt>名前<tt>}</tt>」で、定義領域で定義した名前のマクロに展開される