ランダム生成の改良?

昨日のプログラムのランダム生成は余りにもめちゃめちゃな出力なので、少し、文法に則ってるかチェックするようにしました。チェックの結果ダメならやり直さなければならないのですが、やり直すロジックはかなり面倒です。SICPでこの手の処理を継続で書いていたなと思い出して、継続を使ってみました。
文法のチェックは、とりあえずif/while/case/defなどより前にendが来ないことと、then, elseがif文じゃないところには来ないことをチェックするようにしました。ちゃんとやろうとすると、rubyの処理系のparse.yになってしまうのは間違い無いでしょう。

実行してみたのですが、確かに少しまともになったのですが、面白くないんです。チェックを厳しくすると元のプログラムの一部にどんどん似てきます。大量のプログラムを学習させると違うのかなー?

12/23追記
結構とまらないことがあります。おそらく、endが生成される前にどんどん、if/while/case等が生成されて終われなくなっていると思われます。プログラムが1000バイトを超えたときは、強制的にendを生成するようにしました。こうすると、今のところちゃんと止まるようです。

# プログラムランダム合成のテスト

require 'continuation'
require 'find'
require 'ripper'

SEEDPROG = "pcompose.rb"
PREVSTAT = [nil, nil, nil]
PREVSTATSIZE = PREVSTAT.size

class MaTable
  def initialize
    @cont = {}
  end
  def []=(keys, value)
    cc = @cont
    lkey = keys.last
    keys[0..-2].each do |k|
      cc[k] ||= {}
      cc = cc[k]
    end
    cc[lkey] ||= []
    cc[lkey].push value if !cc[lkey].include?(value)
  end

  def [](keys)
    cc = @cont
    keys[0..-2].each do |k|
      if cc[k] then
        cc = cc[k]
      end
    end
    cc[keys.last]
  end
end

class Chooser
  def initalize
    @retry = nil
  end

  def fail
    @retry.call
  end

  def choose(ch)
    callcc {|c| 
      @retry = c
    }
    if ch.empty? then
      return nil
    else
      ctok = ch.pop
      return ctok
    end
  end
end

class SyntaxChecker
  def initialize
    @endlevel = nil
    @curstat = []
  end

  EndNeedToken = ['class', 'module', 'begin', 'while', 'case', 'if', 
                  'do', 'def']

  def check(chooser, ctok)
    case ctok
    when 'end'
      # if, while等より前にendはこない
      if (@endlevel == 0 or @endlevel == nil) then
        chooser.fail
      end

    when 'if'
      @curstat.push :ifcond

    when 'then'
      if @curstat.last != :ifcond then
        chooser.fail
      else
        @curstat.pop
        @curstat.push :ifthen
      end

    when 'else' 
      if @curstat.last != :ifthen then
        chooser.fail
      else
        @curstat.pop
        @curstat.push :ifelse
      end
    end

    if EndNeedToken.include?(ctok) then
      if @endlevel == nil then
        @endlevel = 1
      else
        @endlevel += 1
      end
    end

    if ctok == 'end' then
      @endlevel -= 1
      @curstat.pop
    end
    
    if @endlevel == 0 then
      return :exit
    else
      return true
    end
  end
end

mtable = MaTable.new

#tokens = Ripper.tokenize(File.read(SEEDPROG))

tokens = []
Find.find('.') do |f|
  if /\.rb/ =~ f then
    tokens = Ripper.tokenize(File.read(f))

    # 推移テーブルの作成
    prevtok = PREVSTAT.dup
    tokens.each do |tok|
      mtable[prevtok] = tok
      prevtok.push(tok)
      prevtok.shift
    end
  end
end

# プログラムの生成
chooser = Chooser.new
checker = SyntaxChecker.new

otok = PREVSTAT.dup
tokstack = PREVSTAT.dup
chstack = []

loop do
  toks =  mtable[otok]
  if toks == nil then
    tokstack.pop
    otok = tokstack[-(PREVSTATSIZE), PREVSTATSIZE]
    chooser.fail
  end
  toks = toks.dup.sort_by {rand }
  # 巻きモード
  if tokstack.size > 1000 then
    if toks.include?('end') then
      toks.delete('end')
      toks.push 'end'
    end
  end
  
  chstack.push toks

  while !chstack.empty? do
    ctok = chooser.choose(chstack.last)

    if ctok == nil then
      chstack.pop
      tokstack.pop
    else
      break
    end
  end

  if checker.check(chooser, ctok) == :exit
    tokstack.push ctok
    break
  end
  
  tokstack.push ctok
  otok = tokstack[-(PREVSTATSIZE), PREVSTATSIZE]
end

print tokstack.join