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