RubyでLispっぽいの書く(1)

大学の課題でScheme処理系をRubyで実装します。東大のminiPythonなんかが微妙に有名になってるけど電気通信大学情報工学科課程も負けてないじゃないかやったね!
まあそれは嘘で、大学の課題で「Rubyである程度大きくて実用的なプログラム書いてください」とかあったんで、RubyLispっぽい処理系でも書こうと思っただけです。レポート締め切りが2/4。レポート書く作業含めるとそんなに時間に余裕があるわけでもねえな。あと他の講義の試験とかあるし。


とりいそぎいいかげんな構文解析だけ。

C:\hoge\docs\rbdsp>cat list-1.txt list-2.txt list-3.txt list-4.txt list-5.txt
(1 2 3 4 5) ; ここまで1つめ
(+ 2
3 4 5) ; ここまで2つめ
(print "Hello, World!") ; ここまで3つめ
(3 . 2) ; ここまで4つめ
(3 . 2 4)  ; ここまで5つめ

C:\hoge\docs\rbdsp>ruby rbdsp.rb list-1.txt
(1 2 3 4 5)
C:\hoge\docs\rbdsp>ruby rbdsp.rb list-2.txt
(+ 2 3 4 5)
C:\hoge\docs\rbdsp>ruby rbdsp.rb list-3.txt
(print "Hello, World!")
C:\hoge\docs\rbdsp>ruby rbdsp.rb list-4.txt
(3 . 2)
C:\hoge\docs\rbdsp>ruby rbdsp.rb list-5.txt
. のあとに色々入れすぎ

S式を読んでS式を出力するだけ。構文解析で50行くらいか? 読めるのは整数と文字列とシンボルとリストとコンスセル。

class Pair
  def initialize(car, cdr)
    @car = car
    @cdr = cdr
  end
  def is_pair(x)
    x.is_a(Pair)
  end
  def car
    @car
  end
  def cdr
    @cdr
  end
  def setcar!(a)
    @car = a
  end
  def setcdr!(d)
    @cdr = d
  end 
  def set!(a, d)
    @car = a
    @cdr = d
  end
end
class List < Pair
  NILS = List
  def initialize(xs)
    cdr = NILS
    xs[1..xs.length].reverse_each{|x|
      cdr = Pair.new(x, cdr)
    }
    set!(xs[0], cdr)
  end
  def list?(x)
    x.is_a(List)
  end
  def append!(xs)
    cdr = self
    while(cdr.cdr!=NILS)
      cdr = cdr.cdr
    end
    cdr.setcdr!(xs)
    self
  end
  def print_list
    print "("
    cdr = self
    while(true)
      printObject(cdr.car)
      cddr = cdr.cdr
      if(cddr==NILS)
        break
      elsif(!cddr.is_a?(Pair))
        print " . "
        printObject(cddr)
        break
      else
        print " "
      end
      cdr = cddr
    end
    print ")"
  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)
    @input = input.gsub(S_COMMENT, '')
  end
  def parse()
    mat = S_ITEM.match(@input)
    @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)
      ls = []
      while((pret = parse)!=S_RPAR)
        if(pret==S_DOT)
          cdr = parse
          rpar = parse
          if(rpar!=S_RPAR)
            raise ParseException.new('. のあとに色々入れすぎ')
          end
          return List.new(ls).append!(cdr)
        end
        ls.push(pret)
      end
      List.new(ls)
    elsif(item =~ S_RPAR)
      S_RPAR
    elsif(item =~ S_DOT)
      S_DOT
    end
  end
end
def printObject(x)
  if(x.is_a?(Integer))
    print x
  elsif(x.is_a?(String))
    print '"', x, '"'
  elsif(x.is_a?(Symbol))
    print x
  elsif(x.is_a?(List))
    x.print_list
  else
    print x
  end
end
input = ARGF.read
pars = Parser.new(input)

begin
  item = pars.parse
  printObject(item)
rescue ParseException => pex
  print pex
end

test