ちょっと前に話題になったものです.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