2019年/10月/31日

首页回退

用Ruby实现一个小型Lisp

理论上Lisp是最好学习的语言,因为它没有语法,只有数据结构,Paul Graham在他的《黑客与画家》一书中表达了一个观点,语言进化会越来越接近Lisp 看看用Ruby编写的简单Lisp的样子

环境:

class Env < Hash
  def initialize(keys=[], vals=[], outer=nil)
    @outer = outer
    keys.zip(vals).each{|p| store(*p)}
  end
  def [](name)  super(name) || @outer[name]  end
  def set(name, value) key?(name) ? store(name, value) : @outer.set(name, value)  end
end

把算子放进环境:

def add_globals(env)
  ops = [:+, :-, :*, :/, :>, :<, :>=, :<=, :==]
  ops.each{|op|  env[op] = lambda{|a, b| a.send(op, b)}}
  env.update({ :length => lambda{|x| x.length}, :cons => lambda{|x, y| [x]+y},
  :car => lambda{|x| x[0]}, :cdr => lambda{|x| x[1..-1]}, :append => lambda{|x,y| x+y},
  :list => lambda{|*xs| xs}, :list? => lambda{|x| x.is_a? Array}, :null? => lambda{|x| x==nil},
  :symbol? => lambda{|x| x.is_a? Symbol}, :not => lambda{|x| !x}, :display => lambda{|x| p x}})
end

求值:

def eval(x, env)
  return env[x] if x.is_a? Symbol
  return x if !x.is_a? Array
  case x[0]
    when :quote then x[1..-1]
    when :if
      _, test, conseq, alt = x
      eval(eval(test, env) ? conseq : alt, env)
    when :set! then env.set(x[1], eval(x[2], env))
    when :define then env[x[1]] = eval(x[2], env)
    when :lambda
      _, vars, exp = x
      Proc.new{|*args| eval(exp, Env.new(vars, args, env))}
    when :begin
      x[1..-1].inject([nil, env]){|val_env, exp| [eval(exp, val_env[1]), val_env[1]]}[0]
    when :callcc
      f = eval(x[1], env)
      callcc {|cont| f.call( lambda{|x| cont.call(x)} ) }
    else
      exps = x.map{|exp| eval(exp, env)}
      exps[0].call(*exps[1..-1])
  end
end

原子:

def atom(s)
  return "[" if s=='('
  return "]" if s==')'
  return s if s =~ /^-?\d+$/ || s =~ /^-?\d*\.\d+$/
  ':'+s
end

词法:

def parse(src)
  tokens = src.gsub('(', ' ( ').gsub(')', ' ) ').split
  Kernel.eval(tokens.map{|s| atom(s)}.join(' ').gsub(' ]',']').gsub(/([^\[]) /,'\1, '))
end

入口:

if ARGV.size > 0
  src = open(ARGV[0], 'r'){|f| f.read }
  p(eval(parse(src), add_globals(Env.new)))
else
  print "usage: rispy.rb file.scm\n”
end