字句解析

まずは、数字や変数名、記号などに分割する字句解析のプログラムを作ってみましょう。

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"]