構文解析(1)

最近,構文解析が流行っているらしい.rakeっぽく作ればいけるかも?と適当に作ってみた.

まったく理解していないけど一応動くものは出来たっぽい?中国語の部屋状態.

四則演算はるびまのsyntax.rbそっくりになってしまった.そして判りにくい.こんなんならraccのままでいいや.


お約束の四則演算

include Parse

#rep(:int_p => ['0','1','2','3','4','5','6','7','8','9']) {|v| v.join().to_i }
#rep(:int_p => ['0'..'9']) {|v| v.join().to_i }
sel(:int_p => /\A\d+/) {|v| v.to_i }

seq(:group => ['(', :expression, ')']) {|v| v[1]}

sel :factor => [:int_p, :group]

seq(:term => [:factor, rep0(
  seq('*', :factor),
  seq('/', :factor)
 )]) {|v|
  c = v[0]
  v[1].each {|x|
    case x[0]
    when '*'; c *= x[1]
    when '/'; c /= x[1]
    end
  }
  c
}

seq(:expression => [:term, rep0(
  seq('+', :term),
  seq('-', :term)
 )]) {|v|
  c = v[0]
  v[1].each {|x|
    case x[0]
    when '+'; c += x[1]
    when '-'; c -= x[1]
    end
  }
  c
}

seq :target => :expression


csvファイル

include Parse

# 新しいルールを作る。同じ規則を作っても内部的には別の規則にされる
def other(*args,&block)
  all( neg( *args), /\A./, &block)
end

def get2nd(a,b)
  seq(a, b){|v|v[1]}
end

sel :space_p => [' ', "\t"]
rep0 :space_opt => :space_p


rep(:csv => 
  seq([:value, rep0(get2nd(',', :value)), "\n"]) {|v| [v[0],*v[1]] }
)
sel :value => [
    seq( :space_opt, '"', :quoted_value, '"', :space_opt) {|v| v[2]},
    :naked_value
  ]
rep0(:quoted_value => [ seq('""'){'"'}, other('"') ]) {|v| v.join }
rep0(:naked_value => other(',','"',"\n")) {|v| v.join }


かなりいい加減な本体.

module Parse

  #-------------------------------------------------------------------
  # private:

  class Rule
    def initialize(type,args,params,&block)
      @type,@args,@params,@act = type, args, params, block
    end
    # 実際には読むだけだと思うが…
    attr_accessor :type
    attr_accessor :args
    attr_accessor :params
    attr_accessor :act
  end

  @@gen = 0
  @@rules = {}

  # 無名の規則用の名前を提供する
  # to generate a name for anonymous rules(なぜ英語?)
  def gen
    @@gen += 1
    :"_rule#{@@gen}"
  end

  def _to_array( args)
    (args.kind_of?(Array))? args: [args]
  end

  def _to_unarray( args)
    # seq(a,b,c,d)とseq([a,b,c,d])を等価に扱うための処理
    (args.kind_of?(Array) and args.size==1)? args[0]: args
  end

  def _to_hash( args)
    (args.kind_of?(Hash))? args: Hash[gen(),args]
  end

  def _rule( type, args, params, &block)
    k = nil
    _to_hash( _to_unarray(args)).each {|k,v|
      @@rules[k] ||= []
      @@rules[k] << Rule.new(type, _to_array(v), params, &block)
    }
    k # 複数のハッシュを一度に登録すると返るキーが不定になる…
  end

  # sequence
  def _check_seq(r, pos, lv, params, str)
    m_type, m_len, work = nil, 0, []
    r.each{|y|
      m, l, w = _parse( y, pos+m_len, lv+1, str)
      unless m
        m_type, m_len, work = nil, 0, []
        break
      end
      m_len += l
      m_type = :SEQ
      work << w
    }
    return m_type, m_len, work
  end

  # 先読み
  # look ahead
  def _check_lah(r, pos, lv, params, str)
    # 読み込んでも前に進まない.
    m_type, m_len, work = _check_sel(r, pos, lv, params, str)
    m_len = 0
    return m_type, m_len, work
  end

  # repeat
  def _check_rep(r, pos, lv, params, str)

    # c_min回以上、c_max回までの繰り返し
    # c_maxが-1なら無限に繰り返す

    c_min, c_max = params[0], params[1]
    c = 0
    m_len = 0
    work = []

    while true
      m, l, w = _parse( r, pos+m_len, lv+1, str)
      break unless m
      m_len += l
      work << w
      c += 1
      break if c==c_max
    end
    return :REP, m_len, work if c_min<=c
    return nil, 0, []
  end

  # alteration
  def _check_sel( r, pos, lv, params, str)

    # 一番長くマッチしたものを返す。
    # 同じ長さがある場合は最初に見つかったもの。
    # /\Aabc\b/のように区切り文字込みで検索するなら,
    # 最初に見つかったものを返せばいいが、
    # "abc"で検索する場合、"abcd"に対して、"ab"も"abc"もヒットしてしまう

    m_type, m_len, work = nil, -1, []
    r.each{|x|
      m, l, w = _parse( x, pos, lv, str)
      m_type,m_len,work = m,l,w if m and m_len<l
    }
    return m_type, m_len, work
  end

  def _check_neg( r, pos, lv, params, str)
    m_type, m_len, work = _check_sel( r, pos, lv, params, str)
    return :NEG, 0, [] unless m_type
    # 1つでも一致したらfalse
    return nil, 0, []
  end

  def _check_and( r, pos, lv, params, str)
    m_type, m_len, work = nil, -1, []
    r.each{|x|
      m, l, w = _parse( x, pos, lv, str)
      unless m
        m_type = nil
        break
      end
      m_type,m_len,work = m,l,w if m and m_len<l
        # 長い方を優先
    }
    return m_type, m_len, work
  end

  def _parse( r, pos, lv, str)
    # いまはループする文法を余裕で書ける
    # return nil, 0, [] if lv>32
      # 四則演算程度でもすぐにスタックが深くなる…
 
    m_type, m_len, work = nil, 0, []
    act = nil

    case r
    when Rule
      m_type, m_len, work = send( r.type, r.args, pos, lv, r.params, str)
      act = r.act
    when Array
      m_type, m_len, work = _check_sel( r, pos, lv, [], str)
    when Symbol
      m_type, m_len, work = _parse( @@rules[r], pos, lv+1, str)

    when String
      m_type, m_len, work = :STRING, r.size, r if r==str[pos,r.size]
    when Regexp
      m_type, m_len, work = :STRING, $~[0].size, $~[0] if r =~ str[pos..-1]
    when Range
      x = r.find {|x| x==str[pos,x.size] }
      m_type, m_len, work = :STRING, x.size, x if x
    else
      # error
    end

    if m_type
      if act
        # 念のためにdupったのを送る
        tmp = act.call(work.dup) # (work,pos,m_len) を送る?
        work = tmp if tmp
          # nilを返しても無視される.
          # 無効にしたい場合は[]を返すか,:EMPTYのようなダミーを返す.
      end
      pos += m_len
    end

    return m_type, m_len, work
  end

  def _print_rule(k,v)
    v.each {|x|
      case x
      when Rule
        r = x
        if r.params.empty?
          p [k, r.type, r.args]
        else
          p [k, r.type, r.args, r.params]
        end
      else
        p [k, :_leaf, x]
      end
    }
  end

  #-------------------------------------------------------------------
  # public:

  def sel(*args, &block) _rule( :_check_sel, args, [], &block); end
  def seq(*args, &block) _rule( :_check_seq, args, [], &block); end
  def rep(*args, &block) _rule( :_check_rep, args, [1,-1], &block); end
  def rep0(*args, &block) _rule( :_check_rep, args, [0,-1], &block); end
  def opt(*args, &block) _rule( :_check_rep, args, [0,1], &block); end
  def pre(*args, &block) _rule( :_check_lah, args, [], &block); end

  def all(*args, &block) _rule( :_check_and, args, [], &block); end
  def neg(*args, &block) _rule( :_check_neg, args, [], &block); end
    # and, notが使えなかったのでall,negにした
    # all: 全部trueなら有効.1つでもfalseがあれば無効
    # neg: 全部falseなら有効.1つでもtrueがあれば無効

  def parse(str, target=:target)
    m_type, m_len, result = _parse( target, 0, 0, str)
    result = nil unless m_len == str.size
      # 文末まで到達していないのでエラー扱いにする
    result
  end

  def print_rules
    @@rules.each {|k,v| _print_rule(k,v) }
  end
end