RubyでSchemeぽいの書く(2)

明日数値計算の試験なので(まったく関係無い)Scheme実装がよく進んだ。

C:\hoge\docs\rbdsp>ruby rbdsp.rb
> (+ 1 2 3 4)
10
> (eq? 3 (+ 1 2))
#t
> (define ls (list 1 2 3))
ls
> ls
(1 2 3)
> (eq? ls (list 1 2 3))
#f
> ^Z
good bye

このあたりまでわりとあっさり。構文解析が適当なのと、もちろん実行効率は何も考えてませんが。

class Pair
  def initialize(car, cdr)
    @car = car
    @cdr = cdr
  end
  def is_pair(x)
    x.instance_of?(Pair)
  end
  def car
    @car
  end
  def cdr
    @cdr
  end
  def setcar(a)
    List.new(a, @cdr)
  end
  def setcdr(d)
    if(!d.instance_of?(List))
      Pair.new(@car, d)
    else
      List.new(@car, d)
    end
  end 
  def set!(a, d)
    @car = a
    @cdr = d
  end
  def surface
    "("+@car.surface+" . "+@cdr.surface+")"
  end
end
class List < Pair
  NILS = List.new(0,0)
  def initialize(x, xs)
    set!(x, xs)
  end
  def list?(x)
    x.instance_of?(List)
  end
  def append(xs)
    if(xs.instance_of?(List))
      if(@cdr==NILS)
        List.new(@car, xs)
      else
      List.new(@car, @cdr.append(xs))
      end
    else
      Pair.new(@car, xs)
    end
  end
  def surface
    str = "("
    cdr = self
    while(true)
      str += cdr.car.surface
      cddr = cdr.cdr
      if(cddr==NILS)
        break
      else
        str += " "
      end
      cdr = cddr
    end
    str += ")"
  end
  def each &block
    array = []
    cdr = self
    while(cdr!=NILS)
      array.push(block.call(cdr.car))
      cdr = cdr.cdr
    end
    array.to_list
  end
  def length
    len = 0
    each{|x| len+=1}
    len
  end
  include Enumerable
end
class Array
  def to_list
    xs = List::NILS
    self.reverse_each{|x|
      xs = List.new(x, xs)
    }
    xs
  end
  def surface
    "#("+map{|x| x.surface}.join(" ")+")"
  end
end
class Symbol
  def surface
    to_s
  end
end
class String
  def surface
    '"'+self+'"'
  end
end
class Integer
  def surface
    to_s
  end
end
class TrueClass
  def surface
    "#t"
  end
end
class FalseClass
  def surface
    "#f"
  end
end
class NilClass
  def surface
    "#<undef>"
  end
end
class Proc
  def surface
    "#<procedure>"
  end
end
class ParseException < Exception
end
class Parser
  S_INT = /\-?\d+/
  S_STR = /"(?:\.|[^"])*"/
  S_SYM = /[^\s\r\n\(\)\#\'\`\.]+/
  S_LPAR = /\(/
  S_RPAR = /\)/
  S_DOT = /\./
  S_ITEM = /(?:#{S_INT}|#{S_STR}|#{S_SYM}|#{S_LPAR}|#{S_RPAR}|#{S_DOT})/m
  S_COMMENT = /;.*/
  def initialize
    @input = ''
  end
  def input(input)
    @input += input.gsub(S_COMMENT, '')
  end
  def parse
    mat = S_ITEM.match(@input)
    if(!mat)
      return nil
    end
    @input = mat.post_match
    item = mat[0]
    if(item =~ S_INT)
      item.to_i
    elsif(item =~ S_STR)
      item[1..item.length-2]
    elsif(item =~ S_SYM)
      item.intern
    elsif(item =~ S_LPAR)
      array = []
      while((pret = parse)!=S_RPAR)
        if(pret==S_DOT)
          cdr = parse
          rpar = parse
          if(rpar!=S_RPAR)
            raise ParseException.new('. のあとに色々入れすぎ')
          end
          return array.to_list.append(cdr)
        end
        array.push(pret)
      end
      array.to_list
    elsif(item =~ S_RPAR)
      S_RPAR
    elsif(item =~ S_DOT)
      S_DOT
    end
  end
end
class EvalException < Exception
end
class Machine
  ADD = proc{|m, xs|
    sum = 0
    xs.each{|x| sum+=m.eval(x)}
    sum
  }
  SUB = proc{|m, xs|
    if(xs.length==0)
      0
    elsif(xs.length==1)
      -1 *m.eval(xs.car)
    else
      sum = m.eval(xs.car)
      xs.cdr.each{|x| sum-=m.eval(x)}
      sum
    end
  }
  MUL = proc{|m, xs|
    sum = 1
    xs.each{|x| sum*=m.eval(x)}
    sum
  }
  DIV = proc{|m, xs|
    if(xs.length==0)
      1
    else
      sum = m.eval(xs.car)
      xs.cdr.each{|x| sum/=m.eval(x)}
      sum
    end
  }
  PRINT = proc{|m, xs|
    xs.each{|x| print m.eval(x).surface}
    puts
  }
  LIST = proc{|m, xs|
    xs
  }
  LISTP = proc{|m, xs|
    xs.instance_of?(List)
  }
  LAMBDA = proc{|m, xs|
  }
  DEFINE = proc{|m, xs|
    if(xs.length==2)
      sym = xs.car
      if(sym.instance_of?(Symbol))
        val = m.eval(xs.cdr.car)
        m.define(sym, val)
        return
      end
    end
    raise EvalException.new("defineの引数変")
  }
  EQP = proc{|m, xs|
    if(xs.length==2)
      v1 = m.eval(xs.car)
      v2 = m.eval(xs.cdr.car)
      return v1==v2
    end
    raise EvalException.new("eq?の引数変")
  }
  def initialize
    @symtable = Hash.new
    @symtable.store(:+, ADD)
    @symtable.store(:-, SUB)
    @symtable.store(:*, MUL)
    @symtable.store(:/, DIV)
    @symtable.store(:print, PRINT)
    @symtable.store(:define, DEFINE)
    @symtable.store(:eq?, EQP)
    @symtable.store(:list, LIST)
    @symtable.store(:list?, LISTP)
  end
  def define(sym, val)
    @symtable.store(sym, val)
  end
  def eval(item)
    if(item.instance_of?(List))
      proc_sym = item.car
      proc_obj = @symtable[proc_sym]
      proc_obj.call(self, item.cdr)
    elsif(item.instance_of?(Symbol))
      sym_obj = @symtable[item]
      sym_obj
    else
      item
    end
  end
end

begin
  machine = Machine.new

  #input = ARGF.read
  #pars = Parser.new(input)
  pars = Parser.new
  print "> "
  ARGF.each{|line|
    pars.input(line)
    while(item = pars.parse)
      #print "; ", item.surface, "\n"
      print machine.eval(item).surface, "\n"
    end
    print "> "
  }
  puts "good bye"
rescue ParseException => pex
  print pex
end

test