シンプル(=手抜き)なSLQ条件句パーサ

searchconditionparser.y

class SearchConditionParser
options no_result_var
rule
  search_condition      : boolean_primary                       { [val[0]] }
                        | search_condition AND boolean_primary  { (val[0] << val[2]).flatten }

  comparison_predicate  : id COMP_OP value                      { Predicate.new(val[1].to_sym, val[0], val[2]) }

  between_predicate     : id BETWEEN value AND value            { Predicate.new(:between, val[0], [val[2], val[3]]) }

  in_predicate          : id IN '(' value_list ')'              { Predicate.new(:in, val[0], val[3]) }

  like_predicate        : id LIKE value                         { Predicate.new(:like, val[0], val[2]) }

  null_predicate        : id IS NULL                            { Predicate.new(:'=', val[0], nil) }

  boolean_primary       : predicate
                        | '(' search_condition ')'              { val[1] }

  predicate             : comparison_predicate
                        | between_predicate
                        | in_predicate
                        | like_predicate
                        | null_predicate

  id                    : IDENTIFIER
                        | IDENTIFIER '.' id

  value                 : STRING
                        | NUMBER
                        | NULL

  value_list            : value                                 { [val[0]] }
                        | value_list ',' value                  { val[0] << val[2] }

end

---- header

require 'strscan'

---- inner
Predicate = Struct.new("Predicate", :operator, :column, :value)

def initialize(obj)
  src = obj.is_a?(IO) ? obj.read : obj.to_s
  @ss = StringScanner.new(src)
end

def scan
  piece = nil

  until @ss.eos?
    if (tok = @ss.scan /\s+/)
      # nothing to do
    elsif (tok = @ss.scan /(?:>=|<=|<>|!=|^=|=|>|<)/)
      yield :COMP_OP, tok
    elsif (tok = @ss.scan /(?:IS|IN|BETWEEN|LIKE|NOT|AND|OR)/i)
      yield tok.upcase.to_sym, tok
    elsif (tok = @ss.scan /NULL/i)
      yield :NULL, nil
    elsif (tok = @ss.scan /'[^']*'/i)
      yield :STRING, tok.slice(1...-1)
    elsif (tok = @ss.scan /\d+/)
      yield :NUMBER, tok.to_i
    elsif (tok = @ss.scan /[,\(\).]/)
      yield tok, tok
    elsif (tok = @ss.scan /[a-z_]\w*/i)
      yield :IDENTIFIER, tok
    else
      raise RuntimeError, 'must not happen'
    end
  end

  yield false, '$'
end

def parse
  yyparse self, :scan
end

実行結果

require 'searchconditionparser.tab'
require 'pp'

pp SearchConditionParser.new(%q{
  a = 100 and b != 200 and c in ('ABC', 'DEF')
  AND a like 'A%' AND a is null
}).parse


~/work$ racc searchconditionparser.y && ruby sp.rb
[#,
#,
#,
#,
#]