字句解析
まずは、数字や変数名、記号などに分割する字句解析のプログラムを作ってみましょう。
def lex case $src when /^[\r\n\t ]*([0-9]+)(.*$)/ when /^[\r\n\t ]*([+*\-\/()])(.*$)/ when /^[\r\n\t ]*(.*)(.*$)/ return nil end $src = $2 $1 end $src = "1+2*3" tokens = [] while (token = lex) != nil tokens.push(token) end p tokens
今回は(字句解析は英語でlex,tokinizer,scanner等というので)lexという関数名で字句解析器を作ります。このプログラムではソースの文字列を正規表現で、空白を除去し、数、記号、それ以外を読み込みます。
/^[\r\n\t ]*([0-9]+)(.*$)/
以上の正規表現は数に対応していて[\r\n\t ]*で改行やタブ、スペースの0回以上の連続を取り除き、([0-9]+)で0〜9の文字の1つ以上の連続を$1として取り出し、(.*$)で残りの文字列すべてを$2の文字列として取り出すという意味になっています。case文が終わったあとに$srcに$2を入れることで空白と数が削除され次のトークンを読み出す準備をします。$1は数の文字列になります。
/^[\r\n\t ]*([+*\-\/()]+)(.*$)/
上の正規表現は([+*\-\/()])が先ほどの数を表す([0-9]+)を置き換えたものになっています。これは +, *, -, /, (, ) のいずれか1つという意味で、四則演算に使われる記号を取り出す正規表現になっています。
/^[\r\n\t ]*(.*)(.*$)/
最後の正規表現はその他すべてにマッチするのでプログラムの終わりを示します。きちんとした処理をする場合はここにきた場合に空白以外の文字列が残っていればエラー処理する必要がありますが今回は飛ばします。次にオブジェクトとして実装した字句解析器を示します。
class Lexer def initialize src @src = src end def lex case @src when /^[\r\n\t ]*([0-9]+)(.*$)/ when /^[\r\n\t ]*([+*\-\/()])(.*$)/ when /^[\r\n\t ]*(.*)(.*$)/ return nil end @src = $2 $1 end def tokens ts = [] while (token = lex) != nil ts.push(token) end ts end end lexer = Lexer.new("1+2*3") tokens = lexer.tokens p tokens
Lexerクラスが字句解析クラスで、コンストラクタにプログラムソースを指定し、tokensメソッドでトークン配列を取り出します。最初の例でもオブジェクト指向版でも、結果として以下の結果が得られます。
["1", "+", "2", "*", "3"]