ちょっと前に話題になったものです.parser generator はおいといて,lex
もどきをつくってみました.すんごい手抜きだけど,そう悪くないかも.

# 行頭に `#' がある行だけはコメントになるはずです.
# 最初のモード宣言までは好きな ruby の式が書けます.まるのまま出力にコ
# ピーされる.

@var = nil                              # Scanner というクラスの
                                        # initialize になるのでそのつ
                                        # もりで.

# <name> というのが行頭にあるとモード宣言とみなします.`name' はそのま
# まメソッド名になるのでちゃんとするように.必ずただ一つの <default>
# が必要です.無かったり,複数あったりするとどうなるかわかりません.

<default>

# 行頭が `#' と空白文字以外の場合はその行全てがパターン.

pattern

# 空白文字で始まる行は pattern に対する action.

    expression
    RETURN value, :new_mode

# RETURN は value を返し新たなモード(デフォルトは default)に遷移しま
# す.

<new_mode>
pattern
    expression
    GOTO :default

# GOTO は状態の遷移のみを行います.

    * マッチした文字列は matched という変数でアクセスできます.
    * みての通りばかばかしい程簡単な構造です.
    * そのため入力が期待している形に外れると激しく破綻します.

シンプルというか,冗談みたい (^^;

# ところで最後につけたサンプルから作った scanner は何故か余分に true
# を返してくるようなのですが,何か変でしょうか?

===========================================================================
  柳川和久 @ 東大阪市 . 大阪府
  kjana / os.xaxon.ne.jp                                  December 25, 1998
「くだらないダジャレで気が抜けて成仏するのではなく?」
「ではなく!!」

#!/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 ScanGoto < Exception end 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 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 # as initialize sequence. out.puts " #{$_.chomp!}" end if $1 out.puts " end" out.puts out.puts " def #{$1}" out.puts " case @__line__" else out.puts " end" 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 begin @__line__ = @__inf__.gets if @__line__.empty? and not @__inf__.nil? return nil if @__line__.nil? while not @__line__.empty? send @__mode__ end true rescue ScanGoto retry rescue ScanReturn return $!.val end end def goto(__mode__) raise TypeError, "no such mode." if not self.respond_to? __mode__ @__mode__ = __mode__ raise ScanGoto.new end def return_with_val(__val__, __mode__ = :default) 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 input @part = "" <default> [a-zA-Z_]\w* RETURN [:id, matched] \d*\.\d+(?:[eE][+-]?\d+)? RETURN [:float, matched] \d+ RETURN [:int, matched] " @part << "\"" GOTO :string \s+ # silently ignored . RETURN matched <string> \\" @part << "\"" " @part << "\"" part = @part @part = "" RETURN [:string, part], :default [^"\\]+ @part << matched