Racc で HQ9+

そうしたら HQ9+言語を作ってみよう、言語自体の説明は「奇妙な言語」p.32 にある。

  1. H があったら「Hello world!」
  2. Q があったら Quine、実行中のソースの一覧
  3. 9 があったら 99本のビール文書
  4. + があったらカウンタの値を 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」 の様な比較をしていたのはここの正規表現マッチと平仄を合わせる為もあった。