せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【Ruby】StringScanner を使った字句解析

 
元気になったよー。

 
XMLパーサー作ったよー

の師匠の返事が

tokenメソッドの責務がよくわからない
StringScannerを使うのはいいけど、
正規表現に頼った方法やとスキャンがいっぱい走るし
あんまり筋がいいとは思わない。

だったので、改めてStringScannerについて考えてみる


 

元のコード

字句解析の箇所だけ抜粋

require 'strscan'

def lexical_analysis(data)
  s = StringScanner.new(data)
  tokens = []
  while !s.eos?
    if s.scan(/<\/\S+?>/)
      tokens << [s[0].gsub(/[<\/>]/,''), :end_form]
    elsif s.scan(/<[^>]+?\/>/)
      tokens << [s[0].gsub(/[<\/>]/,''), :special_form]
    elsif s.scan(/<\S+?>/)
      tokens << [s[0].gsub(/[<\/>]/,''), :start_form]
    elsif s.scan(/[^<]+/)
      tokens << [s[0], :text]
    end
  end
  tokens
end
data='<a>huga<b>hello<hoge/>dada</b><c>pupu</c><d/></a>'
p lexical_analysis(data)

 
■実行結果

[["a", :start_form], ["huga", :text], ["b", :start_form], ["hello", :text], ["hoge", :special_form], ["dada", :text], ["b", :end_form], ["c", :start_form], ["pupu", :text], ["c", :end_form], ["d", :special_form], ["a", :end_form]]

 
確かにこれだと何回もscanが走る。
たとえば今のコードだと

<hoge>

ってコードがあった場合、
s.scan(/<\/\S+?>/)  が走ってダメ
s.scan(/<[^>]+?\/>/) が走ってダメ
s.scan(/<\S+?>/)   が走ってようやくOK

と、何回もscanしている

そうだ!
”<>”で囲まれているか否かだけをscanで判定して、
囲まれている場合はif文で条件分岐すればどうだろう。
scanの回数が減る。

 

tokenメソッドの責務がよくわからない

というのはどういう意味だろう。
字句解析をして、タグ要素とテキスト要素に分ける処理は間違っていないはず。

要素として正しい形か判定していないということかな。。?
エラー判定はどこまで細かくするかだけど、、
<>の最初と最後以外に”/”が存在したらエラーにする。

 

修正したコード

require 'strscan'

def lexical_analysis(data)
  s = StringScanner.new(data)
  tokens = []
  while !s.eos?
    if s.scan(/<[^>]+>/)
    	if s[0].index("</")
    		tokens << [s[0].gsub(/[<\/>]/,''), :end_form]
    	elsif s[0].index("/>")
    		tokens << [s[0].gsub(/[<\/>]/,''), :special_form]
    	elsif s[0].index("/")
    		raise "incorrect xml tag"
    	else
    		tokens << [s[0].gsub(/[<>]/,''), :start_form]
    	end
    else s.scan(/[^<]+/)
    	tokens << [s[0], :text]
    end
  end
  tokens
end
data='<a>huga<b>hello<hoge/>dada</b><c>pupu</c><d/></a>'
p lexical_analysis(data)

scanも減ってすっきりした