「Raccでおてがる構文解析」Ruby/Rails勉強会@関西 第36回
発表してきました。スライドとデモで使用したソースコード掲載します。
Racc でおてがる構文解析
View more presentations from morphine57.
- デモ:日本語でプログラミング
以下のような日本語のコードを読み込んで実行します
// 例1 '-- 例1 --' を 表示する。 もし 1 と 2 は 等価 の場合 '真'を 表示する。 そうでない場合 '偽'を 表示する。 。 // 例2 '-- 例2 --' を 表示する。 もし 'foo' と 'foo' は 等価 かつ 'bar' と 'bar' は 等価 の場合 '同じです'を 表示する。 。
j_parser.y
# # j_parser.y # class JapaneseParser prechigh left '<' '>' '=' '!' TO left AND left OR preclow rule program :stmt_list {result = Node::RootNode.new(val[0])} stmt_list : stmt {result = [val[0]]} | stmt_list stmt { result ||= [] result << val[1] } stmt : assign EOL {result = Node::StatementNode.new(val[0].lineno, val[0])} | if_stmt EOL {result = Node::StatementNode.new(val[0].lineno, val[0])} | do_print EOL {result = Node::StatementNode.new(val[0].lineno, val[0])} assign : expr WO IDENT NIDAINYU {result = Node::AssignNode.new(val[1][0], val[1], val[0])} do_print : expr WO HYOJI {result = Node::DoPrintNode.new(val[1][0], val[0])} if_stmt : IF expr THEN stmt_list elsif_list else_stmt {result = Node::IfNode.new(val[0][0], val[1], val[3], val[4], val[5])} elsif_list : | elsif_list elsif_stmt {result << val[1]} | elsif_stmt {result = val} elsif_stmt : ELSIF expr THEN stmt_list {result = Node::ElsifNode.new(val[0][0], val[1], val[3])} else_stmt : | ELSE stmt_list {result = Node::ElseNode.new(val[0][0], val[1])} expr : IDENT {result = [val[0][1]]} | NUMBER {result = Node::NumberLiteralNode.new(val[0][0], val[0][1])} | STRING {result = Node::StringLiteralNode.new(val[0][0], val[0][1])} | expr AND expr {result = Node::LogicalOprNode.new(val[1][0], val[0], val[1][1], val[2])} | expr OR expr {result = Node::LogicalOprNode.new(val[1][0], val[0], val[1][1], val[2])} | expr TO expr HA TOKA {result = Node::ComparisonOprNode.new(val[1][0], val[0], '==', val[2])} | expr '<' expr {result = Node::ComparisonOprNode.new(val[1][0], val[0], '<', val[2])} | expr '>' expr {result = Node::ComparisonOprNode.new(val[1][0], val[0], '>', val[2])} | expr '<' '=' expr {result = Node::ComparisonOprNode.new(val[1][0], val[0], '<=', val[3])} | expr '>' '=' expr {result = Node::ComparisonOprNode.new(val[1][0], val[0], '>=', val[3])} | expr '<' '>' expr {result = Node::ComparisonOprNode.new(val[1][0], val[0], '!=', val[3])} | expr '!' '=' expr {result = Node::ComparisonOprNode.new(val[1][0], val[0], '!=', val[3])} end ---- header require 'strscan' require 'node' ---- inner RESERVED = { 'もし' => :IF, 'そうでない場合' => :ELSE, 'そうではなくもし' => :ELSIF, 'の場合' => :THEN, 'かつ' => :AND, 'または' => :OR, '非' => :NOT, 'を' => :WO, 'に代入する' => :NIDAINYU, '表示する' => :HYOJI, 'と' => :TO, 'は' => :HA, '等価' => :TOKA, } def initialize() end def parse(str) @yydebug = true @tokens = [] @localvars = [] s = StringScanner.new(str) lineno = 1 until s.eos? case when s.scan(/\n/) lineno += 1 s.rest.scan(/.*?\n/)[0] when s.scan(/。/) @tokens << [:EOL, [lineno, s[0]]] when s.skip(/\A[\t ]+| /) # skip when s.scan(/(--|\/\/)(.*)\n/) lineno += 1 when s.scan(/\/\*(\/\*.*?\*\/|.)+?\*\//m) lineno += s[0].scan(/\n/).size when s.scan(/\d(\d|\.)*/) @tokens << [:NUMBER, [lineno, s[0]]] next when s.scan(/\'\\\\\'/) @tokens << [:STRING, [lineno, '\\']] next when s.scan(/\'((\\'|.|\n)*?)\'/)#' @tokens << [:STRING, [lineno, s[1]]] next when s.scan(/\'\'/) @tokens << [:STRING, [lineno, nil]] next when s.scan(/[^a-zA-Z0-9 \n。]+/i) word = s[0].upcase @tokens << [RESERVED[word] || :IDENT, [lineno, s[0]]] next else c = s.getch @tokens << [c, [lineno,c]] end end @tokens << [false, '$end$'] do_parse end def next_token @tokens.shift end def on_error(t, v, values) if v line = v[0] v = v[1] else line = 'last' end raise Racc::ParseError, "#{line}: syntax error on #{v.inspect}" end ---- footer src = File.read(ARGV[0]) parser = JapaneseParser.new() syntax_tree = parser.parse(src) syntax_tree.evaluate
node.rb
#!ruby require 'pp' module Node def exec_list(nodes) v = nil nodes.each{|i|v = i.evaluate} v end class Node include ::Node attr_accessor :lineno, :parent_node def initialize(lineno) @lineno = lineno end def evaluate # abstract method end end class RootNode < Node attr_accessor :tree def initialize(tree) super nil @tree = tree end def evaluate exec_list @tree end end class StatementNode < Node attr_accessor :stmt def initialize(lineno, stmt) super lineno @stmt = stmt @stmt.parent_node = self end def evaluate @stmt.evaluate end end class IfNode < Node attr_accessor :cond, :tstmt_list, :elsif_list, :else_stmt def initialize(lineno, cond, tstmt_list, elsif_list, else_stmt) super lineno @cond = cond @tstmt_list = tstmt_list @elsif_list = elsif_list @else_stmt = else_stmt @cond.parent_node = self @tstmt_list.map{|s|s.parent_node = self} @elsif_list.map{|s|s.parent_node = self} if @elsif_list @else_stmt.parent_node = self if @else_stmt end def evaluate if @cond.evaluate exec_list(@tstmt_list) elsif @elsif_list exec_list @elsif_list else @else_stmt.evaluate if @else_stmt end end end class ElsifNode < Node attr_accessor :cond, :stmt_list def initialize(lineno, cond, stmt_list) super lineno @cond = cond @stmt_list = stmt_list @cond.parent_node = self @stmt_list.map{|s|s.parent_node = self} if @stmt_list end def evaluate if @cond.evaluate exec_list @stmt_list end end end class ElseNode < Node attr_accessor :stmt_list def initialize(lineno, stmt_list) super lineno @stmt_list = stmt_list @stmt_list.map{|s|s.parent_node = self} if @stmt_list end def evaluate exec_list @stmt_list end end class LogicalOprNode < Node attr_accessor :lcond, :opr, :rcond def initialize(lineno, lcond, opr, rcond) super lineno @lcond = lcond @opr = opr @rcond = rcond @lcond.parent_node = self @rcond.parent_node = self end def evaluate @lcond.evaluate && @rcond.evaluate end end class ComparisonOprNode < Node attr_accessor :expr, :opr, :rexpr def initialize(lineno, lexpr, opr, rexpr) super lineno @lexpr = lexpr @opr = opr @rexpr = rexpr @lexpr.parent_node = self @rexpr.parent_node = self end def evaluate if @rexpr.is_a? StringLiteralNode eval("\"#{@lexpr.evaluate}\" #{@opr} \"#{@rexpr.evaluate}\"") elsif @rexpr.is_a? NumberLiteralNode eval("#{@lexpr.evaluate} #{@opr} #{@rexpr.evaluate}") end end end class AssignNode < Node attr_accessor :lexpr, :rexpr def initialize(lineno, lexpr, rexpr) super lineno @lexpr = lexpr @rexpr = rexpr @lexpr.parent_node = self @rexpr.parent_node = self end def evaluate end end class DoPrintNode < Node attr_accessor :expr def initialize(lineno, expr) super lineno @expr = expr @expr.parent_node = self end def evaluate p @expr.evaluate end end class ArithmeticNode < Node attr_accessor :lexpr, :opr, :rexpr def initialize(lineno, lexpr, opr, rexpr) super lineno @lexpr = lexpr @opr = opr @rexpr = rexpr @lexpr.parent_node = self @rexpr.parent_node = self end def evaluate end end class StringLiteralNode < Node attr_accessor :str def initialize(lineno, str) super lineno @str = str end def evaluate @str.dup end end class NumberLiteralNode < Node attr_accessor :num def initialize(lineno, num) super lineno @num = num end def evaluate @num.dup end end end # module Node