In message <199902030923.SAA10248.keiju / bc.mbn.or.jp>
keiju / rational.com (=?ISO-2022-JP?B?GyRCQFBETTc9PHkbKEI=?= ) writes:

> >> でも, 私の実力ではrubyの字句を完全に識別できるような複雑な正規表現は
> >> 書けないし(^^;;;
> >
> >結構簡単に書けると思いますけど.文字列とコメントにだけ気をつけてやれば
> >良いわけで.

こんなん書きましたけど,here document を考えたら突然どうにもならん程難
しいような気がしてきました.parser と協調しないと無理そう.


> ># というりくつで適当に書いたのが前に出した scanner generator.ちなみに
> ># あの後ちょっといじりました.誰か興味あります?
> 
> こっちには興味ありますので, uploadしてもらえるとありがたいです.

# メーリングリストサーバにメールを出すのは upload ととらえられなくもな
# いが結局それがクライアントたる参加者のもとに配られることを考えると,
# などというばか話はおいといて (^^;

[ruby-list:11458] で出したものをほんのちょっと書き換えただけです.だけ
なのに diff -u でも 80 行近くになるのはどうも.... まるのままつけても
134 行とかなのに.

    * GOTO ですぐに実行中のアクションから抜けていたのをやめました.
    * RETURN のデフォルトモードを現在のモードにしました.
      (前のは :default にしてた)
    * <default> の前に書いたコードは本当に「書いたまま」生成される
      Scanner に取り込まれるようになりました.
      (前のは initialize の中にコピーしてた)
    * これにともない `init' というメソッドを定義しておくとこれが
      initialize から呼び出されるようにしました.

テンプレートをいれちゃうより継承させた方がいいのかな....

# まだ名前が無い.まあいちおう `rlex.rb' という名前で使ってたりはする
# んですが.サンプルは実際に使っているものです.何に使っているのかはお
# いとくとして.


-- 
柳川和久 @ 東大阪市 . 大阪府                               February 3, 1999
The more haste, the less speed.

#!/usr/local/bin/ruby if ARGV[0] inf = File.open(ARGV[0]) ARGV.shift else inf = $stdin end if ARGV[0] out = File.open(ARGV[0], "w") ARGV.shift else out = $stdout end out.puts <<SCANNER_HEAD # Scanner generated by #{$0} class ScanReturn < Exception def initialize(val) @val = val end attr_reader :val end class Scanner def initialize(__in__ = $stdin) case __in__ when IO @__line__ = "" @__inf__ = __in__ when String @__line__ = __in__ @__inf__ = nil else raise TypeError, "IO or String is expected." end @__mode__ = :default init if self.respond_to? :init end SCANNER_HEAD while inf.gets break if /^<([_a-zA-Z]\w*)>/ # mode declarations next if /^\s*$/ # until first mode declaration, any string is copied literally. out.puts " #{$_.chomp!}" end if $1 out.puts out.puts " def #{$1}" out.puts " case @__line__" end mode = [] while inf.gets case $_.chomp! when /(?:^\s*$)|(?:^#.*)/ # ignore when /^<([_a-zA-Z]\w*)>/ # mode declarations mode << $1 out.puts " else" out.puts " c = @__line__[0]" out.puts " @__line__ = @__line__[1..-1]" out.puts " return_with_val c" out.puts " end" out.puts " end" out.puts out.puts " def #{$1}" out.puts " case @__line__" when /^\s+.*/ # actions # action strings are copied literally. out.puts " #{$_}" else # patterns out.puts " when /\\A(#{$_})/" out.puts " @__line__ = $'" out.puts " matched = $1" end end out.puts " else" out.puts " c = @__line__[0]" out.puts " @__line__ = @__line__[1..-1]" out.puts " return_with_val c" out.puts " end" out.puts " end" out.puts <<SCANNER_TAIL def scan while true begin @__line__ = @__inf__.gets if @__line__.empty? and not @__inf__.nil? return nil if @__line__.nil? while not @__line__.empty? send @__mode__ end rescue ScanReturn return $!.val end end end def goto(__mode__) raise TypeError, "no such mode." if not self.respond_to? __mode__ @__mode__ = __mode__ end def return_with_val(__val__, __mode__ = @__mode__) raise TypeError, "no such mode." if not self.respond_to? __mode__ @__mode__ = __mode__ raise ScanReturn.new(__val__) end alias GOTO goto alias RETURN return_with_val end if $0 == __FILE__ lex = Scanner.new while tok = lex.scan p tok end end SCANNER_TAIL # sample ################################################################### def init @str = "" @lineno = 1 end <default> [_a-zA-Z]\w* RETURN [:ident, matched, @lineno] \[\]|:|;|\. RETURN [matched, matched, @lineno] \n @lineno += 1 \s+ # silently ignored " @str = "\"" GOTO :in_string . RETURN [matched, matched, @lineno] <in_string> \\[a-zA-Z"] @str << "#{matched}" " @str << "\"" RETURN [:string, @str, @lineno], :default \n @lineno += 1 @str << "\n" [^"\\] @str << matched