Created
September 8, 2011 05:48
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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