Rubyで書いたLISP級マクロ付きインタプリタ

LISP級マクロ付きのインタプリタRubyで書いてみました。
書き方は、じっくり書いていきますが、haXeで書くより綺麗に書けたと思います。
次はphpでざくざくっと書いてみようと思います。

以下ソースです。

class Lexer

  def initialize src
    @src = src.sub("\r\n","\v").sub("\r", "\v").sub("\n", "\v")
  end

  def lex
    case @src
      when /^[\v\t ]*([0-9]+)(.*$)/
        @src = $2
        return $1.to_i
      when /^[\v\t ]*[\v][\v\t ]*([(\[{])(.*$)/
        @src = $2
        return "n" + $1
      when /^[\v\t ]*([()\[\]{},;]|[+*\-\/=<>]+)(.*$)/
        @src = $2
        return $1
      when /^[\v\t ]*'([a-zA-Z_][a-zA-Z_0-9]*)(.*$)/
        @src = $2
        return $1.intern
      when /^[\v\t ]*([a-zA-Z_][a-zA-Z_0-9]*)(.*$)/
        @src = $2
        return $1
      when /^[\v\t ]*(.*)(.*$)/
        return nil
    end
  end

  def tokens
    ts = []
    while (token = lex) != nil
      ts.push(token)
    end
    ts
  end

end

class Parser
  def initialize
    @opLs = {">"=>190, "<"=>190, "+" => 10, "-" => 10, "*" => 20, "/" => 20, "," => 2, "else" => 1}
    @opRs = {"=" => 5}
    @opPs = {"(" => ")","[" => "]", "{"=>"}","n(" => ")","n[" => "]", "n{"=>"}"}
    @opMs = {"(" => 200, "[" => 200, "{"=>200}
    @opSs = {"fun" => "(", "if" => "(","mac" => "("}
    @opBs = {"++"=> 30, ";" => 1}
  end

  def parse(src)
    lexer = Lexer.new(src)
    @tokens = lexer.tokens
    t = expn(0)
    while @tokens.length > 0
      t = ["@", t, expn(0)]
    end
    t
  end

  def expn p
    return "vid" if @opPs.value?(@tokens.first)
    t = @tokens.shift

    if @opSs.include?(t) && @opSs[t] == @tokens.first
      op = @tokens.shift
      e = expn(0)
      raise "error" unless((op2 = @tokens.shift) == @opPs[op])
      t = ["S"+t+op+op2, e, expn(0)]
    elsif @opPs.include?(t)
      t = t[1..-1] if (t[0] == "n")
      t2 = expn(0)
      raise "error" unless((t3 = @tokens.shift) == @opPs[t])
      t = ["P"+t+t3, t2]
    end

    while true
      if @opMs.include?(@tokens.first) && (tagp = @opMs[@tokens.first]) >= p
        op = @tokens.shift
        e = expn(0)
        raise "error" unless((op2=@tokens.shift) == @opPs[op])
        t = ["M"+op+op2, t, e]
      elsif @opBs.include?(@tokens.first) && (tagp = @opBs[@tokens.first]) >= p
        op = @tokens.shift
        t = ["B"+op, t]
        p = tagp
      elsif @opLs.include?(@tokens.first) && (tagp = @opLs[@tokens.first]) > p
        op = @tokens.shift
        t = ["L"+op, t, expn(tagp)]
      elsif @opRs.include?(@tokens.first) && (tagp = @opRs[@tokens.first]) >= p
        op = @tokens.shift
        #p "R"+op
        t = ["R"+op, t, expn(tagp)]
      else
        break
      end
    end
    t
  end
end

class Env
  def initialize parent = nil
    @env = {}
    @env["parent"] = parent unless(parent == nil)
  end
  def include?(name)
    @env.include?(name)
  end
  def [](name)
    return @env[name] if(@env.include?(name))
    return @env["parent"][name] if(@env.include?("parent"))
    return nil
  end
  
  def []=(name, value)
    rc = put(@env, name, value)
    if rc == nil
      @env[name] = value
      value
    else
      rc
    end
  end

  def put(env, name, value)
   if env.include?(name)
      env[name] = value
      value
    elsif env.include?("parent")
      put(env["parent"], name, value)
    else
      nil
    end
  end

end

class Calc
  def initialize
    @parser = Parser.new
  end
  def eval src
    exp = @parser.parse(src)
    p exp
    @env = Env.new
    @env["macros"] = []
    exp = macroExpand(exp, @env)
    p exp
    execute exp
  end

  def macroExpand(a, e)
    macros = e["macros"]
    macros.each{|obj|
      @env = Env.new(@env)
      rc = macroMatch(obj[1], a)
      if rc
        r = execute(obj[2])
        @env = @env["parent"]
        return r
      end
      @env = @env["parent"]
    }
    if a.instance_of?(Array)
      if a[0] == "Smac()"
        macros.push(a)
        return nil
      elsif a[0] == "M()" && a[1] == "add" &&  a[2].instance_of?(Array) && a[2][0] == "L,"
          return ["L+", macroExpand(a[2][1], e), macroExpand(a[2][2],e)]
      else
          return [a[0],macroExpand(a[1], e),macroExpand(a[2],e)]
      end
    else
      a
    end
  end


  def macroMatch (a,b)
    if(a.instance_of?(Array))
        return a[0]==b[0] && macroMatch(a[1],b[1]) && macroMatch(a[2], b[2])
    elsif a.instance_of?(Symbol)
      @env[a.to_s] = b
      return true
    else
      return a == b
    end
  end
  
  def execute exp
    if exp.instance_of?(Array)
      case exp[0]
      when "L+"; execute(exp[1]) + execute(exp[2])
      when "L-"; execute(exp[1]) - execute(exp[2])
      when "L*"; execute(exp[1]) * execute(exp[2])
      when "L/"; execute(exp[1]) / execute(exp[2])
      when "R="; @env[exp[1]] = execute(exp[2])
      when "B;"; execute(exp[1])
      when "B++"; dt = @env[exp[1]]; @env[exp[1]] = dt + 1; dt
      when "@"; execute(exp[1]); execute(exp[2])
      when "P()"; execute(exp[1])
      when "P{}"; execute(exp[1])
      when "P[]"; execute(exp[1])
      when "M()";
        case exp[1]
        when "p"; p(execute(exp[2]));
        else
          
          fun = execute(exp[1])
          back = @env
          @env = Env.new
          bind(fun[1], exp[2])
          @env["parent"] = fun[3]
          a = execute(fun[2])
          @env = back
          a
        end
      when "Sfun()"; ["fun", exp[1], exp[2], @env]
      when "fun"; exp
      when "Sif()"
        l1 = execute(exp[1])
        if exp[2].instance_of?(Array) && exp[2][0]=="Lelse"
          unless l1 == 0
            execute(exp[2][1])
          else
            execute(exp[2][2])
          end
        else
          unless l1 == 0
            execute(exp[2])
          else
            nil
          end
        end
      end
    elsif exp.instance_of?(String)
      case exp
      when "vid"; exp
      else @env[exp]
      end
    else
      exp
    end
  end

  def bind(p, l)
    case p
    when String; @env[p] = l;
    when Array
      raise "error" unless(p.length == 3)
      raise "error" unless(l.length == 3)
      raise "error" unless(p[0] == "L,")
      raise "error" unless(l[0] == "L,")
      bind(p[1], l[1])
      bind(p[2], l[2])
    else; raise "error"
    end
  end
  
end

calc = Calc.new
p calc.eval("mac(mul('a,'b))a*b mul(2,3)")