Racc で HQ9+
そうしたら HQ9+言語を作ってみよう、言語自体の説明は「奇妙な言語」p.32 にある。
- H があったら「Hello world!」
- Q があったら Quine、実行中のソースの一覧
- 9 があったら 99本のビール文書
- + があったらカウンタの値を 1増やす
class HQ9Plus rule# class HQ9Plus program : | program 'H' {puts "Hello world!\n"} | program 'Q' {puts @source} | program '9' {puts ninety9_bottles_beer} | program '+' {@count += 1; @racc_debug_out.puts @count if @yydebug} # (A) end # class HQ9Plus ---- inner def initialize(source) @source = source @count = 0 end # def initialize(source) def scan @source.each_char{ |hq9p| yield [hq9p, nil] if /[HQ9\+]/ =~ hq9p } # (B) yield nil end # def scan def parse(opts = nil) @yydebug = opts.yydebug if opts.respond_to? :yydebug yyparse self, :scan end # def parse(opts = nil) private def ninety9_bottles_beer (0..99).to_a.reverse.inject('') do |nnbb, b| case b when 0 then before, after = 'no more bottles', '99 bottles' when 1 then before, after = '1 bottle', 'no more bottles' when 2 then before, after = '2 bottles', '1 bottle' else before, after = "#{b} bottles", "#{b-1} bottles" end # case b action = (b == 0 \ ? 'Go to the store and buy some more' \ : 'Take one down and pass it around' ) nnbb << "#{before.capitalize} of beer on the wall, #{before} of beer.\n" nnbb << "#{action}, #{after} of beer on the wall.\n" nnbb << "\n" unless b == 0 nnbb end # (0..99).to_a.reverse.inject('') do |nnbb, b| end # def ninety9_bottles_beer ---- header # -*- coding: utf-8; -*- Version = '$Id: hq9plus_string.y 3239 2009-01-31 06:47:53Z hs9587 $' # $Date: 2009-01-31 15:47:53 +0900 (土, 31 1 2009) $ ---- footer require 'optparse' require 'ostruct' opts = OpenStruct.new opts.yydebug = false ARGV.options do |opt| opt.on('--[no-]yydebug', 'debug mode (from racc -g) on/off'){ |v| opts.yydebug = v } opt.release = 'H Q 9 +' opt.banner += "\n\tHQ9+ interpreter.\nOptions:" opt.parse! end # ARGV.options do |opt| HQ9Plus.new((ARGV.size > 0 ? ARGF : $stdin).read).parse(opts)
ninety9_bottles_beerメソッド(殆ど「奇妙な言語」のロジックの儘です)が少し面倒臭いだけで特に問題ないと思う。
Quine には @source を使うようにしていて良かった。intialize でカウンタも初期化しておく。
デバッグモードの時は増やしたカウンタの値を出力するようにした、 @racc_debug_outインスタンス変数はデバッグ出力の際の出力先を保持している。普通は標準エラー出力になるでしょう。
規則部分で終端記号を文字(String)自体にしてるので、scanメソッドではとくに case分けせず文字をそのまま送ってやれば良いのだった (B) ちなみにHello world! 言語のときのスキャナで「if 'H' == h」 の様な比較をしていたのはここの正規表現マッチと平仄を合わせる為もあった。