パーサジェネレータ Raccメモ

Rubyのパーサジェネレータ Racc のメモ。Ruby 1.8以上ならばランタイムライブラリが標準ライブラリとして含まれている。

環境

% rvm -v
rvm 1.25.22 (stable) by Wayne E. Seguin <wayneeseguin@gmail.com>, Michal Papis <mpapis@gmail.com> [https://rvm.io/]

% ruby -v
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux]

インストール

Rubygemsでインストールする(バージョン2.1.0だと標準ライブラリーかも?)

% rvmsudo gem install racc

チュートリアル:calc.y にべき乗、変数を付け加える

GitHub:tenderlove/racc sample/calc.yよりサンプルプログラムを入手する。以後は以下にしたがい使い方を覚える。

コンパイルして実行してみる。

% racc -o calc.rb calc.y
% ruby calc.rb

strscanでスキャナ(字句解析器)を作成する

上のチュートリアルの「パーサとスキャナの分離」まで終わっているとする。Calcp#parse()をRuby標準ライブラリstrscanを使って書き直す。構文の定義ファイルをcalc.rbとする。

require './calc.rb'
require "strscan"

class Calcp
  def initialize
    @vartab = {}
  end

  def do_assign(varname, val)
    @vartab[varname] = val
  end

  def do_refvar(varname)
    @vartab[varname]
  end

  def parse(str)
    @q = []

    s = StringScanner.new(str)
    
    until s.eos?
      case
      when s.scan(/\s+/)
      when s.scan(/\d+/)
        @q.push [:NUMBER, s[0].to_i]
      when s.scan(/[a-zA-Z]+/)
        @q.push [:IDENT, s[0]]
      when s.scan(/.|\n/o)
        @q.push [s[0], s[0]]
      end
    end
    @q.push [false, '$end']
    do_parse
  end
  
  def next_token
    @q.shift
  end
end

parser = Calcp.new
puts
puts 'type "Q" to quit.'
puts
while true
  puts
  print '? '
  str = gets.chop!
  break if /q/i =~ str
  begin
    puts "= #{parser.parse(str)}"
  rescue ParseError
    puts $!
  end
end

元のCalcp#parse()では、正規表現にマッチした部分を捨てる処理があったが、strscanの場合はStringScannerオブジェクト自身がどこまで文字列を読み込んだのか覚えているのでその処理が不要になる。