ついでに、subexp call も実装してみよう。
subexp call には、subexp を定義する要素と、呼び出す要素が必要である。それぞれ、[:subexp, name, r1, r2, ...] と [:call, name] と表現しよう。
定義するほうに、複数の正規表現を指定できるのはいちいち [:cat, r1, r2, ...] と書いて連結するのが面倒だからである。複数書いたら、連結したものを意味することにしよう。
同様に、繰り返しとキャプチャも [:rep, r1, r2, ...], [:cap, name, r1, r2, ...] と複数指定できるようにして、やはりこれも連結したものを意味することにしよう。
そうやって実装したのが以下である。
call を実現するには名前から正規表現を lookup する必要があるので、そのための辞書を事前に作る必要がある。また、ここではやっていないけれど、左再帰の検出も必要だし、Balancing Group Definition よりも手間がかかるといわれれば否定はできない。
def match(re, str, b=0) ary = str.split(//) try(re, ary, b, Hash.new([].freeze), collect_subexps(re)) {|e, cap| return [b...e, cap] } nil end def collect_subexps(re, subexps={}) case re[0] when :lit when :cat; _, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :alt; _, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :rep; _, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :cap; _, n, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :subexp; _, n, *rs = re; rs.each {|r| collect_subexps(r, subexps) } subexps[n] = re when :call else raise "unexpected: #{re.inspect}" end subexps end def try(re, ary, pos, cap, subexps, &b) case re[0] when :lit; _, ch = re; try_lit(ch, ary, pos, cap, subexps, &b) when :cat; _, *rs = re; try_cat(rs, ary, pos, cap, subexps, &b) when :alt; _, *rs = re; try_alt(rs, ary, pos, cap, subexps, &b) when :rep; _, *rs = re; try_rep(rs, ary, pos, cap, subexps, &b) when :cap; _, n, *rs = re; try_cap(n, rs, ary, pos, cap, subexps, &b) when :subexp; _, n, *rs = re; try_subexp(n, rs, ary, pos, cap, subexps, &b) when :call; _, n = re; try_call(n, ary, pos, cap, subexps, &b) else raise "unexpected: #{re.inspect}" end end def try_lit(ch, ary, pos, cap, subexps) if pos < ary.length && ary[pos] == ch yield pos + 1, cap end end # r1 r2 ... def try_cat(rs, ary, pos, cap, subexps, &block) if rs.empty? yield pos, cap else r, *rest = rs try(r, ary, pos, cap, subexps) {|pos2, cap2| try_cat(rest, ary, pos2, cap2, subexps, &block) } end end # r1 | r2 | ... def try_alt(rs, ary, pos, cap, subexps, &block) rs.each {|r| try(r, ary, pos, cap, subexps, &block) } end # (r1 r2 ...)* def try_rep(rs, ary, pos, cap, subexps, &block) try_cat(rs, ary, pos, cap, subexps) {|pos2, cap2| if pos < pos2 try_rep(rs, ary, pos2, cap2, subexps, &block) end } yield pos, cap end # (?<n>r1 r2 ...) def try_cap(n, rs, ary, pos, cap, subexps, &block) try_cat(rs, ary, pos, cap, subexps) {|pos2, cap2| cap3 = cap2.dup cap3[n] = cap3[n] + [pos...pos2] yield pos2, cap3 } end # (?<n>r1 r2 ...) without capture def try_subexp(n, rs, ary, pos, cap, subexps, &block) try_cat(rs, ary, pos, cap, subexps, &block) end # \g<n> def try_call(n, ary, pos, cap, subexps, &block) r = subexps.fetch(n) try(r, ary, pos, cap, subexps, &block) end # balanced parentheses re = [:subexp, :S, [:rep, [:lit, "("], [:call, :S], [:lit, ")"]]] p match(re, "((()((())())(())))))") #=> [0...18, {}]
さて、subexp call や Balancing Group Definition で括弧の対にマッチできるからといって、その木構造を取り出せないのではおもしろくない。
取り出せるようにしてみよう。
subexp call は、いってしまえば recursive decent parser なので、呼び出し関係にそった木を作ればいい。
[:tree, tag, r1, r2, ...] と [:leaf, r1, r2, ...] でやってみよう。前者で [tag, ...] というノードを作り、後者でマッチした場所をキャプチャした文字列を葉とする。
実装としてはスタックが必要だが、(.NET 式の配列な) キャプチャを使えばいいだろう。ひとつあればいいので、:T という名前のキャプチャを使うことにしよう。(いちおう、:tree と :leaf 以外で変にいじられないことは仮定しておこう)
require 'pp' def match(re, str, b=0) ary = str.split(//) try(re, ary, b, Hash.new([].freeze), collect_subexps(re)) {|e, cap| return [get_substr(ary, b...e), cap] } nil end def collect_subexps(re, subexps={}) case re[0] when :fail when :lit when :cat; _, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :alt; _, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :rep; _, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :repn; _, min, max, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :cap; _, n, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :subexp; _, n, *rs = re; rs.each {|r| collect_subexps(r, subexps) } subexps[n] = re when :call when :leaf; _, *rs = re; rs.each {|r| collect_subexps(r, subexps) } when :tree; _, tag, *rs = re; rs.each {|r| collect_subexps(r, subexps) } else raise "unexpected: #{re.inspect}" end subexps end def try(re, ary, pos, cap, subexps, &b) case re[0] when :fail when :lit; _, ch = re; try_lit(ch, ary, pos, cap, subexps, &b) when :cat; _, *rs = re; try_cat(rs, ary, pos, cap, subexps, &b) when :alt; _, *rs = re; try_alt(rs, ary, pos, cap, subexps, &b) when :rep; _, *rs = re; try_repn(0, nil, rs, ary, pos, cap, subexps, &b) when :repn; _, min, max, *rs = re; try_repn(min, max, rs, ary, pos, cap, subexps, &b) when :cap; _, n, *rs = re; try_cap(n, rs, ary, pos, cap, subexps, &b) when :subexp; _, n, *rs = re; try_subexp(n, rs, ary, pos, cap, subexps, &b) when :call; _, n = re; try_call(n, ary, pos, cap, subexps, &b) when :leaf; _, *rs = re; try_leaf(rs, ary, pos, cap, subexps, &b) when :tree; _, tag, *rs = re; try_tree(tag, rs, ary, pos, cap, subexps, &b) else raise "unexpected: #{re.inspect}" end end def try_lit(ch, ary, pos, cap, subexps) if pos < ary.length && ary[pos] == ch yield pos + 1, cap end end # r1 r2 ... def try_cat(rs, ary, pos, cap, subexps, &block) if rs.empty? yield pos, cap else r, *rest = rs try(r, ary, pos, cap, subexps) {|pos2, cap2| try_cat(rest, ary, pos2, cap2, subexps, &block) } end end # r1 | r2 | ... def try_alt(rs, ary, pos, cap, subexps, &block) rs.each {|r| try(r, ary, pos, cap, subexps, &block) } end # (r1 r2 ...)* # min=0, max=nil # (r1 r2 ...)+ # min=1, max=nil # (r1 r2 ...){min,max} def try_repn(min, max, rs, ary, pos, cap, subexps, &block) if max.nil? || 0 < max try_cat(rs, ary, pos, cap, subexps) {|pos2, cap2| if pos < pos2 min2 = min == 0 ? 0 : (min-1) max2 = max ? (max-1) : nil try_repn(min2, max2, rs, ary, pos2, cap2, subexps, &block) end } end if min == 0 yield pos, cap end end # (?<n>r1 r2 ...) def try_cap(n, rs, ary, pos, cap, subexps, &block) try_cat(rs, ary, pos, cap, subexps) {|pos2, cap2| cap3 = cap2.dup cap3[n] = cap3[n] + [get_substr(ary, pos...pos2)] yield pos2, cap3 } end # (?<n>r1 r2 ...) without capture def try_subexp(n, rs, ary, pos, cap, subexps, &block) try_cat(rs, ary, pos, cap, subexps, &block) end # \g<n> def try_call(n, ary, pos, cap, subexps, &block) r = subexps.fetch(n) try(r, ary, pos, cap, subexps, &block) end def try_leaf(rs, ary, pos, cap, subexps, &block) try_cap(:T, rs, ary, pos, cap, subexps, &block) end def try_tree(tag, rs, ary, pos, cap, subexps, &block) len = cap[:T].length try_cat(rs, ary, pos, cap, subexps) {|pos2, cap2| cap3 = cap2.dup cap3[:T] = cap3[:T][0,len] + [[tag, *cap3[:T][len..-1]]] yield pos2, cap3 } end def get_substr(ary, range) s = ary[range].join('') s.instance_variable_set(:@pos, range) s end re = [:subexp, :S, [:rep, [:tree, :S, [:lit, "("], [:call, :S], [:lit, ")"]]]] p match(re, "((())()))))") #=> ["((())())", {:T=>[[:S, [:S, [:S]], [:S]]]}]
とりあえず対になった括弧をやってみると、((())()) が [:S, [:S, [:S]], [:S]] となり、ちゃんと parse できているようだ。
もうちょっと複雑なものとして、(基本的な) 正規表現を parse してみよう。
re = [:alt, [:cat, [:fail], [:subexp, :R, [:alt, [:tree, :ALT, [:call, :T], [:repn, 1, nil, [:lit, "|"], [:call, :T]]], [:call, :T]]], [:subexp, :T, [:alt, [:tree, :CAT, [:repn, 2, nil, [:call, :C]]], [:call, :C]]], [:subexp, :C, [:alt, [:tree, :REP, [:cat, [:call, :V], [:lit, "*"]]], [:call, :V]]], [:subexp, :V, [:alt, [:tree, :LIT, [:leaf, [:alt, [:lit, "a"], [:lit, "b"]]]], [:cat, [:lit, "("], [:call, :R], [:lit, ")"]]]]], [:call, :R]] pp match(re, "a(bb(ab)*aa|a)*") #=> #["a(bb(ab)*aa|a)*", # {:T=> # [[:CAT, # [:LIT, "a"], # [:REP, # [:ALT, # [:CAT, # [:LIT, "b"], # [:LIT, "b"], # [:REP, [:CAT, [:LIT, "a"], [:LIT, "b"]]], # [:LIT, "a"], # [:LIT, "a"]], # [:LIT, "a"]]]]]}]
ちゃんと parse できている。(混乱を防ぐため、parse 結果の木では tag を大文字にしてあるが、小文字にしてもよい)
recursive decent parser なので、あたりまえにできることができているだけのことではあるが。
では、Balancing Group Definition で木を取り出せるか。
なかなか難しい?
とりあえずスタックを複数使えるため、自明な木構造が定義できないのは厄介だ。
ひとつ指定して使うと限定すれば?
[ruby-dev:43149] ささだ研の Ruby に関する研究の話をする会 にいった。
yugui さんに会ったので、Ruby 1.9.1 について尋ねて、boron でやっている chkbuild の 1.9.1 はもう止めていいということになった。
SQL の natural left outer join は associative か?
つまり、(t1 natural left outer join t2) natural left outer join t3 と t1 natural left outer join (t2 natural left outer join t3) と同じか?
なんか違う感じがする、というわけでしばらく考えて具体的な例を作ってみた。
% sqlite3 SQLite version 3.5.9 Enter ".help" for instructions sqlite> .mode column sqlite> .header on sqlite> create temp table t1 (id1, ab, ca); sqlite> create temp table t2 (id2, ab, bc); sqlite> create temp table t3 (id3, bc, ca); sqlite> insert into t1 values("t1", 1, 1); sqlite> insert into t2 values("t2", 1, 1); sqlite> insert into t3 values("t3", 1, 2); sqlite> select * from t1; id1 ab ca ---------- ---------- ---------- t1 1 1 sqlite> select * from t2; id2 ab bc ---------- ---------- ---------- t2 1 1 sqlite> select * from t3; id3 bc ca ---------- ---------- ---------- t3 1 2 sqlite> select * from (t1 natural left outer join t2) natural left outer join t3; id1 ab ca id2 bc id3 ---------- ---------- ---------- ---------- ---------- ---------- t1 1 1 t2 1 sqlite> select * from t1 natural left outer join (t2 natural left outer join t3); id1 ab ca id2 bc id3 ---------- ---------- ---------- ---------- ---------- ---------- t1 1 1
つまり、かっこのつけかたによって、t2 の情報が残るか残らないかが変化する
ところで、かっこをつけないと、上の二つのどちらとも異なり、
sqlite> select * from t1 natural left outer join t2 natural left outer join t3; id1 ab ca id2 bc id3 ca ---------- ---------- ---------- ---------- ---------- ---------- ---------- t1 1 1 t2 1 t3 2
というように、ca がふたつでてくるのは変な気がする。
[latest]