Raccのメモ
CパーサをRaccで書いてる途中に、よく無道編*1を字引く箇所をメモる。
記法やイディオム
アクションの書き方
funcall : IDENTIFIER '(' arguments ')' { #result : 左辺記号の値 #val : 右辺記号の値の配列 result = FuncallNode.new(val[0], val[2]) }
各パートの挿入
parse.y
class SomeModule::SomeParser prechigh ... preclow rule ... end ---- header ... ---- inner ... ---- footer ...
parse.tab.rb
#{header} module SomeModule class SomeParser #{inner} ... end end #{footer}
レクサのイディオム
行指向の例。
def lex(line, queue) buffer = line.strip until buffer.empty? case #空白 when /\A\s+/ ; #コメント when /\A\#.*/ ; #識別子 when /\A[a-zA-Z_]\w*/ queue << [:IDENT, $&.intern] #整数リテラル when /\A\d+/ queue << [:INTEGER, $&.to_i] #文字列リテラル when /\A"(?:[^"\\]+|\\.)*"/ queue << [:STRING, eval($&)] #その他の一文字 when /\A./ queue << [$&, $&] else raise Exception, 'must not happen' end buffer = $' end queue << [:EOL, nil] #行終端記号 end
トークン詰めの終わり。
queue << [false, nil] #[false, 何か]を詰める
演算子の優先順位のイディオム
prechigh nonassoc UNIMINUS left '*' '/' left '+' '-' preclow rule uni_expr : '-' expr =UNIMINUS end
result省略オプション
逆にresult=...とは書けなくなる。
class SomeParser options no_result_var rule funcall : IDENTIFIER '(' arguments ')' { FuncallNode.new(val[0], val[2]) } end
デバック
デバック用パーサの生成
---- inner def initialize @yydebug = true #do_parse()の前にtrueに end
と、@yydebugをtrueにして、
$ racc -g parse.y
でreadやshift、reduceの様子がstderr出力される。
.outputファイルの出力
中身は状態リストなど。
$ racc -v parse.y #=> parse.output
conflictの情報
.outputの最初に書いてある。
state 15 contains 1 shift/reduce conflicts
で、state 15を見る。
state 15 7) funcall : IDENT args _ 9) args : args _ "," primary "," shift, and go to state 20 "," [reduce using rule 7 (funcall)] $default reduce using rule 7 (funcall)
下部分は、"先読みトークン 動作"である。
先読みトークンはLALR(1)の1。
$defaultはその他のトークンを示す。
","が先読みできた場合、shiftとreduceがダブっている。
yaccに習ってデフォではshiftを優先するので、reduceは
[...]で囲われて示されている(パーサに捨てられた方)。