「Raccでおてがる構文解析」Ruby/Rails勉強会@関西 第36回

発表してきました。スライドとデモで使用したソースコード掲載します。



  • デモ:日本語でプログラミング

以下のような日本語のコードを読み込んで実行します

// 例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