Skip to content

Instantly share code, notes, and snippets.

@uiur
Created September 8, 2011 05:48
Show Gist options
  • Save uiur/1202724 to your computer and use it in GitHub Desktop.
Save uiur/1202724 to your computer and use it in GitHub Desktop.
Tiny Lisp interpreter written in Ruby: http://www.aoky.net/articles/peter_norvig/lispy.htm
List = Array
class Env < Hash
attr_accessor :outer
def initialize(keys=[], vals=[], outer=nil)
alist = [keys, vals].transpose
self.update(Hash[*alist.flatten(1)])
@outer = outer
end
def find(var)
if self.include?(var)
self
else
self.outer && self.outer.find(var)
end
end
end
def add_globals(env)
env.update(
{'+' => ->a,b{a+b}, '*' => ->a,b{a*b}, '/' => ->a,b{a/b}, '%' => ->a,b{a%b},
'-' => ->a,b{a-b}, '<' => ->a,b{a<b}, '>' => ->a,b{a>b}, '<=' => ->a,b{a<=b},
'>=' => ->a,b{a>=b}, '=' => ->a,b{a==b}, 'cons' => ->a,b{[a,b]}, 'car' => ->x{x[0]}, 'cdr' => ->x{x[1]}
})
end
$global_env = add_globals(Env.new)
def evaluate(x, env=$global_env)
if x.is_a?(String)
env.find(x)[x]
elsif not x.is_a?(List)
x
elsif x[0] == 'quote' # (quote exp)
x[1] # exp
elsif x[0] == 'if' # (if test conseq alt)
(test, conseq, alt) = x[1..-1]
if evaluate(test, env)
evaluate(conseq, env)
else
evaluate(alt, env)
end
elsif x[0] == 'set!' # (set! var exp)
(var, exp) = x[1..-1]
env[var] = evaluate(exp, env)
elsif x[0] == 'define' # (define var exp)
(var, exp) = x[1..-1]
env[var] = evaluate(exp, env)
elsif x[0] == 'lambda' # (lambda (var*) exp)
(vars, exp) = x[1..-1]
return lambda {|*args| evaluate(exp, Env.new(vars, args, env))}
elsif x[0] == 'begin' # (begin exp*)
x[1..-1].each do |exp|
val = evaluate(exp, env)
end
val
else # (proc exp*)
exps = x.map { |exp| evaluate(exp, env) }
proc = exps.shift
return proc.call(*exps)
end
end
def read(s)
return read_from(tokenize(s))
end
def tokenize(s)
s.gsub(/\(/, ' ( ').gsub(/\)/, ' ) ').split
end
def read_from(tokens)
if tokens.length == 0
raise SyntaxError, 'unexpected EOF while reading'
end
token = tokens.shift
if '(' == token
l = []
until tokens[0] == ')'
l << read_from(tokens)
end
tokens.shift
return l
elsif ')' == token
raise SyntaxError, 'unexpected )'
else
return atom(token)
end
end
def atom(token)
begin
Integer(token)
rescue ArgumentError
begin
Float(token)
rescue ArgumentError
token
end
end
end
def to_string(exp)
if exp.is_a?(List)
'(' + exp.map{|e| to_string(e)}.join(' ') + ')'
else
exp.to_s
end
end
require "readline"
def lepl
while line = Readline.readline("lisrb> ", true)
val = evaluate(read line)
puts to_string(val) unless val.nil?
end
end
if __FILE__ == $0
lepl
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment