Skip to content

Commit

Permalink
add tail call optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
Geoffrey Litt committed Jan 9, 2018
1 parent 59aa8df commit 56fe633
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 18 deletions.
2 changes: 2 additions & 0 deletions glitt/env.rb
Expand Up @@ -36,4 +36,6 @@ def get(key)
def simple_get(key) def simple_get(key)
@data[key] @data[key]
end end

attr_accessor :data # for debugging
end end
3 changes: 3 additions & 0 deletions glitt/printer.rb
Expand Up @@ -10,6 +10,9 @@ def pr_str(ast, print_readably: true)
when nil then "nil" when nil then "nil"
when Array when Array
"(#{ast.map { |obj| pr_str(obj, print_readably: print_readably) }.join(' ')})" "(#{ast.map { |obj| pr_str(obj, print_readably: print_readably) }.join(' ')})"
when Proc then "#<function>"
when Hash then "#<function>" # for now hashes always represent
# user functions defined with fn*
else ast.to_s else ast.to_s
end end
end end
63 changes: 45 additions & 18 deletions glitt/step5_tco.rb
Expand Up @@ -15,6 +15,7 @@ def truthy?(boolean)
end end


class UndefinedSymbolError < StandardError; end class UndefinedSymbolError < StandardError; end
class UndefinedFunctionError < StandardError; end


# Resolve symbols in the environment, including handling lists. # Resolve symbols in the environment, including handling lists.
# This is a pre-stage to the actual "apply" stage; we're just # This is a pre-stage to the actual "apply" stage; we're just
Expand All @@ -38,6 +39,7 @@ def READ(str)
# This is the heart of the language! # This is the heart of the language!
def EVAL(ast, env) def EVAL(ast, env)
loop do loop do
# puts "EVAL(#{ast},#{env})"
if ast.is_a? Array if ast.is_a? Array
if ast.empty? if ast.empty?
# Nothing to do to evaluate an empty list # Nothing to do to evaluate an empty list
Expand All @@ -53,7 +55,7 @@ def EVAL(ast, env)
# Define a new value in the environment # Define a new value in the environment
# Example usage of def!: # Example usage of def!:
# (def! a 6) ;=> 6 # (def! a 6) ;=> 6
env.set(ast[1], EVAL(ast[2], env)) return env.set(ast[1], EVAL(ast[2], env))
when :"let*" when :"let*"
# Define a new environment and evaluate an expression in that env. # Define a new environment and evaluate an expression in that env.
# Example usage of let*: # Example usage of let*:
Expand All @@ -67,29 +69,30 @@ def EVAL(ast, env)
new_env.set(pair[0], EVAL(pair[1], new_env)) new_env.set(pair[0], EVAL(pair[1], new_env))
end end


# Finally, evaluate the last argument in the new env and return result # Finally, evaluate the last argument in the new env
EVAL(ast[2], new_env) ast = ast[2]
env = new_env
when :do when :do
# evaluate all the elements of the list in order, returning the last one # evaluate all the elements of the list in order, returning the last one
ast[1..-2].each { |element| EVAL(element, env) } ast[1..-2].each { |element| EVAL(element, env) }
EVAL(ast[-1], env) ast = ast[-1]
when :if when :if
if truthy?(EVAL(ast[1], env)) if truthy?(EVAL(ast[1], env))
EVAL(ast[2], env) ast = ast[2]
else else
ast.length >= 3 ? EVAL(ast[3], env) : nil ast = ast[3] if ast.length >= 4
end end
when :or when :or
if truthy?(EVAL(ast[1], env)) || truthy?(EVAL(ast[2], env)) if truthy?(EVAL(ast[1], env)) || truthy?(EVAL(ast[2], env))
true return true
else else
false return false
end end
when :and when :and
if truthy?(EVAL(ast[1], env)) && truthy?(EVAL(ast[2], env)) if truthy?(EVAL(ast[1], env)) && truthy?(EVAL(ast[2], env))
true return true
else else
false return false
end end
when :"fn*" when :"fn*"
# Function definition. # Function definition.
Expand All @@ -99,23 +102,47 @@ def EVAL(ast, env)
# We take advantage of Ruby closures here; # We take advantage of Ruby closures here;
# we get access to variables like env and ast inside the function # we get access to variables like env and ast inside the function
# we return here. # we return here.
-> (*exprs) do
# Create a new environment with variables bound to the function args
new_env = Env.new(outer: env, binds: ast[1], exprs: exprs)


# Evaluate the function body in the context of that new environment return {
EVAL(ast[2], new_env) ast: ast[2],
end params: ast[1],
env: env,
fn: -> (*exprs) do
# Create a new environment with variables bound to the function args
new_env = Env.new(outer: env, binds: ast[1], exprs: exprs)

# Evaluate the function body in the context of that new environment
EVAL(ast[2], new_env)
end
}
else else
# Finally, handle generic function application # Finally, handle generic function application
evaluated = eval_ast(ast, env) evaluated = eval_ast(ast, env)
function, *args = evaluated function, *args = evaluated
function.call(*args)
if function.is_a? Proc
return function.call(*args)
# Handle user-defined functions in a tail-recursion-friendly way.
elsif function.is_a? Hash
# Set ast to the function body, to prepare to evaluate it
# on our next loop iteration.
ast = function[:ast]

# Replace the env with a new one with variables bound,
# we'll use it on our next loop iteration
env = Env.new(
outer: function[:env],
binds: function[:params],
exprs: args
)
else
raise UndefinedFunctionError, "#{function} is not a function."
end
end end
end end
else else
# Atoms simply get resolved in the environment # Atoms simply get resolved in the environment
eval_ast(ast, env) return eval_ast(ast, env)
end end
end end
end end
Expand Down

0 comments on commit 56fe633

Please sign in to comment.