基本的なクラスをパース
クラス定義
contents =<<-EOF
class Hoge
def say
end
end
EOF
Ripper で s 式に変える
Ripper でパース
require 'ripper'
require 'pp'
require 'rexml/document'
require 'stringio'
source = Ripper.sexp(contents)
pp source
出力結果
[:program,
[[:class,
[:const_ref, [:@const, "Hoge", [1, 8]]],
nil,
[:bodystmt,
[[:def,
[:@ident, "say", [2, 8]],
[:params, nil, nil, nil, nil, nil, nil, nil],
[:bodystmt, [[:void_stmt]], nil, nil, nil]]],
nil,
nil,
nil]]]]
s 式を xml に変換する
ごりごりとノードを作って xml に変換していく。@
のイベントは属性にしたかったけど、複数登場する場合があるので子ノードとして追加するようにした。
def analyze(source, node = nil)
node ||= REXML::Document.new
return node unless source
if source.first.kind_of?(Symbol) && source.first.to_s =~ /^@/
node.add_element(source.first.to_s.sub('@','')).text = source[1]
elsif source.first.kind_of? Symbol
new_node = node.add_element(source.first.to_s)
for i in 1...source.length
analyze(source[i], new_node)
end
elsif source.kind_of? Array
source.each {|v| analyze(v, node) }
end
node
end
doc = analyze(source)
output = StringIO.new
formatter = REXML::Formatters::Pretty.new
formatter.compact = true
formatter.write(doc, output)
puts output.string
出力結果
<program>
<class>
<const_ref>
<const>Hoge</const>
</const_ref>
<bodystmt>
<def>
<ident>say</ident>
<params/>
<bodystmt>
<void_stmt/>
</bodystmt>
</def>
</bodystmt>
</class>
</program>
xml に変換できたので、xpath を使って定義を抜き出す
xpath って途中省略とかできた気がするんだけど、思うように取り出せないのでパスをゴリ書き。
ndoc = REXML::Document.new(output.string)
classes = ndoc.get_elements('//class')
classes.each do |clazz|
print clazz.elements['const_ref/const'].text
parent = clazz.elements['var_ref/const']
puts parent ? ' < ' + parent.text : ''
include = clazz.elements['bodystmt/command/ident']
if include && include.text == 'include'
print "\t include - "
puts include.parent.get_elements('args_add_block/var_ref/const').map {|v| v.text }.join(', ')
end
clazz.get_elements('bodystmt/def').each do |m|
print "\t " + m.elements['ident'].text
params = m.get_elements('paren/params/ident')
puts params.length > 0 ? ' - ' + params.map {|v| v.text }.join(', ') : ' - void'
end
end
出力結果
Hoge
say - void
もうちょっと複雑なクラスをパースしてみる
複数のクラスで継承しててメソッドも複数。
contents =<<-EOF
class Hoge < Fuga
include HogeModule, FugaModule
def initialize(name, age)
name
end
def say
end
end
class Fuga
def foo
end
end
EOF
S 式にパースする
[:program,
[[:class,
[:const_ref, [:@const, "Hoge", [1, 8]]],
[:var_ref, [:@const, "Fuga", [1, 15]]],
[:bodystmt,
[[:command,
[:@ident, "include", [2, 4]],
[:args_add_block,
[[:var_ref, [:@const, "HogeModule", [2, 12]]],
[:var_ref, [:@const, "FugaModule", [2, 24]]]],
false]],
[:def,
[:@ident, "initialize", [3, 8]],
[:paren,
[:params,
[[:@ident, "name", [3, 19]], [:@ident, "age", [3, 25]]],
nil,
nil,
nil,
nil,
nil,
nil]],
[:bodystmt, [[:var_ref, [:@ident, "name", [4, 6]]]], nil, nil, nil]],
[:def,
[:@ident, "say", [6, 8]],
[:params, nil, nil, nil, nil, nil, nil, nil],
[:bodystmt, [[:void_stmt]], nil, nil, nil]]],
nil,
nil,
nil]],
[:class,
[:const_ref, [:@const, "Fuga", [9, 8]]],
nil,
[:bodystmt,
[[:def,
[:@ident, "foo", [10, 8]],
[:params, nil, nil, nil, nil, nil, nil, nil],
[:bodystmt, [[:void_stmt]], nil, nil, nil]]],
nil,
nil,
nil]]]]
xml に変換する
<program>
<class>
<const_ref>
<const>Hoge</const>
</const_ref>
<var_ref>
<const>Fuga</const>
</var_ref>
<bodystmt>
<command>
<ident>include</ident>
<args_add_block>
<var_ref>
<const>HogeModule</const>
</var_ref>
<var_ref>
<const>FugaModule</const>
</var_ref>
</args_add_block>
</command>
<def>
<ident>initialize</ident>
<paren>
<params>
<ident>name</ident>
<ident>age</ident>
</params>
</paren>
<bodystmt>
<var_ref>
<ident>name</ident>
</var_ref>
</bodystmt>
</def>
<def>
<ident>say</ident>
<params/>
<bodystmt>
<void_stmt/>
</bodystmt>
</def>
</bodystmt>
</class>
<class>
<const_ref>
<const>Fuga</const>
</const_ref>
<bodystmt>
<def>
<ident>foo</ident>
<params/>
<bodystmt>
<void_stmt/>
</bodystmt>
</def>
</bodystmt>
</class>
</program>
xpath で定義を抜き出す
Hoge < Fuga
include - HogeModule, FugaModule
initialize - name, age
say - void
Fuga
foo - void