字句解析器と構文解析器
前節の最後の例では「15/5」を手でトークンに分けてから構文解析器にかけましたが、トークンに分ける作業は本来字句解析器の仕事です。字句解析器と構文解析器は連携して使えるようにしなければなりません。
字句解析器の出力を構文解析器の入力として使えるようにするために、先に作成した字句解析器(MyLexer)を修正したMyLexer2を作ります。修正点は次の2点です。
- 字句解析器の定義ファイル(plex)で「
require_once "myparser.php";」として、字句解析器が構文解析器を参照できるようにする - 構文解析器のトークンと構文解析器のそれとを一致させる
2番目の修正を施すと、mylexer2.plexは次のようになります。
<?php
require_once "myparser.php";
class MyLexer2
(略)
/*!lex2php
PLUS { $this->token = MyParser::PLUS; }
MINUS { $this->token = MyParser::MINUS; }
TIMES { $this->token = MyParser::TIMES; }
DIVIDE { $this->token = MyParser::DIVIDE; }
NUMERAL { $this->token = MyParser::NUMERAL; }
LPAREN { $this->token = MyParser::LPAREN; }
RPAREN { $this->token = MyParser::RPAREN; }
WHITESPACE { return false; }
*/
};
?>
字句解析器と構文解析器は次のように組み合わせて使います。
与えられた文字列を本稿で作成した字句解析・構文解析器で処理し、結果を表示する関数doTest()を用意します。同じ文字列をPHPの関数eval()で処理した結果も並べて表示させましょう。
//Parserの読み込み・生成
require_once "myparser.php";
$parser = new MyParser();
//Lexerの準備
require_once "PHP/LexerGenerator.php";
new PHP_LexerGenerator('mylexer2.plex');
require_once "mylexer2.php";
//与えられた文字列を処理する関数
function doTest($str) {
echo "$str \n";
global $parser;
$lexer=new MyLexer2($str);
while ($lexer->yylex()) {//字句解析の結果を構文解析器に渡す
$parser->doParse($lexer->token, $lexer->value);
}
$parser->doParse(0, 0);
//PHPの関数evalで結果を確認
echo 'EVAL:';
eval('echo '.$str.';');
echo "\n\n";
}
//テスト
doTest('10 + 2.1 * ( 3 + 1 )');
doTest('10 + ( 2.1 / 0.5 ) * ( 3 + 1 )');
doTest('10 + ( 2.1 / ( 0.5 + 0.5 ) ) * ( 3 + 1 )');
doTest('10 + + 1');
実行結果は次のようになり、正しく処理されていることが分かります(「10 + + 1」はここで作成した簡易電卓では処理できません。エラーが発生すべき場所で「SYNTAX ERROR!」と表示されています)。
php test_lexer_parser.php (略) 10 + 2.1 * ( 3 + 1 ) [token:5, value:10] [token:1, value:+] [token:5, value:2.1] [token:3, value:*] [token:6, value:(] [token:5, value:3] [token:1, value:+] [token:5, value:1] [token:7, value:)] ANS.:18.4 EVAL:18.4 10 + ( 2.1 / 0.5 ) * ( 3 + 1 ) (略) ANS.:26.8 EVAL:26.8 10 + ( 2.1 / ( 0.5 + 0.5 ) ) * ( 3 + 1 ) (略) ANS.:18.4 EVAL:18.4 10 + + 1 [token:5, value:10] [token:1, value:+] [token:1, value:+] SYNTAX ERROR! [token:5, value:1] ANS.:1 EVAL:11
おわりに
簡易電卓を作るという例を通じて、PHPで字句解析と構文解析する方法を紹介しました。ここで作成した簡易電卓は本当に簡易的なものですが、PHP上で新しい言語を実装するという技術は、さまざまなところで応用できるでしょう。より高度な電卓を作りたい場合には、参考文献(3)を参照してください。lexやyaccを用いて変数や関数の導入する方法が紹介されています(本稿で紹介したのは、この文献で紹介した電卓の、最も最初のバージョンに相当します)。
10回にわたるこの連載では、PEARのパッケージの中から、特に基本的だと思われるものを中心に紹介してきました。紹介したパッケージの多くは開発途中のものなので、目的は同じで使い方も同じものがいくつもあって混乱したり、ドキュメントが不十分なために使い方がよく分からなかったり、他の言語の対応物に比べて機能や性能が劣っているためにがっかりすることもありましたが、それらをある程度整理して、使える形で紹介することができたのではないかと思っています。個々の解説記事をそのまま役立ててもらいたいのはもちろんですが、連載の全体が、PHPというプラットフォームを選択するかどうかを判断する一つの材料になれば幸いです。
参考文献
- 『プログラミング言語C++』 Bjarne Stroustrup著、長尾高弘 訳、アジソンウェスレイパブリッシャーズジャパン、1998年12月
- 『コンパイラ入門』 山下義行 著、サイエンス社、2008年07月
- 『UNIXプログラミング環境』 Brian W. Kernighan・Rob Pike 著、Prentice Hall Ptr、石田晴久 訳、アスキー、1985年09月
