Skip to content

Commit

Permalink
Working closuressss
Browse files Browse the repository at this point in the history
  • Loading branch information
Josep M. Bach committed Sep 24, 2012
1 parent eed83a6 commit 321c119
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 97 deletions.
6 changes: 4 additions & 2 deletions examples/fn.clj
@@ -1,6 +1,8 @@
; Add two numbers. ; Add two numbers.
(def x 42)
(def z 4)
(def adder (def adder
(fn [x y] (fn [x y]
(+ x y))) (+ x y z)))


(adder 123 123) (println (adder 123 123))
215 changes: 122 additions & 93 deletions lib/lambra/bytecode_compiler.rb
@@ -1,65 +1,65 @@
module Lambra module Lambra
class BytecodeCompiler class BytecodeCompiler
class Scope # class Scope
attr_reader :variables, :generator # attr_reader :variables, :generator
alias g generator # alias g generator


def initialize(generator, parent=nil) # def initialize(generator, parent=nil)
@parent = parent # @parent = parent
@variables = [] # @variables = []
@generator = generator # @generator = generator
end # end


def slot_for(name) # def slot_for(name)
if existing = @variables.index(name) # if existing = @variables.index(name)
existing # existing
else # else
@variables << name # @variables << name
@variables.size - 1 # @variables.size - 1
end # end
end # end


def push_variable(name, current_depth = 0, g = self.g) # def push_variable(name, current_depth = 0, g = self.g)
if existing = @variables.index(name) # if existing = @variables.index(name)
if current_depth.zero? # if current_depth.zero?
g.push_local existing # g.push_local existing
else # else
g.push_local_depth current_depth, existing # g.push_local_depth current_depth, existing
end # end
else # else
@parent.push_variable(name, current_depth + 1, g) # @parent.push_variable(name, current_depth + 1, g)
end # end
end # end


def set_variable(name, current_depth = 0, g = self.g) # def set_variable(name, current_depth = 0, g = self.g)
if existing = @variables.index(name) # if existing = @variables.index(name)
if current_depth.zero? # if current_depth.zero?
g.set_local existing # g.set_local existing
else # else
g.set_local_depth current_depth, existing # g.set_local_depth current_depth, existing
end # end
else # else
@parent.set_variable(name, current_depth + 1, g) # @parent.set_variable(name, current_depth + 1, g)
end # end
end # end


def set_local(name) # def set_local(name)
slot = slot_for(name) # slot = slot_for(name)
g.set_local slot # g.set_local slot
end # end
end # end


attr_reader :generator, :scope attr_reader :generator #, :scope
alias g generator alias g generator
alias s scope # alias s scope


SPECIAL_FORMS = %w(def fn) SPECIAL_FORMS = %w(def fn)
PRIMITIVE_FORMS = %w(println + - / *) PRIMITIVE_FORMS = %w(println + - / *)


def initialize(parent=nil) def initialize(generator=nil)
@generator = Rubinius::Generator.new @generator = generator || Rubinius::Generator.new
parent_scope = parent ? parent.scope : nil # parent_scope = parent ? parent.scope : nil
@scope = Scope.new(@generator, parent_scope) # @scope = Scope.new(@generator, parent_scope)
end end


def compile(ast, debugging=false) def compile(ast, debugging=false)
Expand All @@ -74,7 +74,10 @@ def compile(ast, debugging=false)
g.file = :"(lambra)" g.file = :"(lambra)"
end end


g.set_line ast.line || 1 line = ast.line || 1
g.set_line line

g.push_state Rubinius::AST::ClosedScope.new(line)


ast.accept(self) ast.accept(self)


Expand Down Expand Up @@ -110,33 +113,52 @@ def visit_SpecialForm(car, cdr)
name = cdr.shift.name name = cdr.shift.name


cdr.first.accept(self) cdr.first.accept(self)
s.set_local name
local = g.state.scope.new_local(name)
g.set_local local.slot
when 'fn' when 'fn'
args = cdr.shift # a Vector args_vector = cdr.shift
argcount = args.elements.size arguments = Lambra::AST::ClosureArguments.new(args_vector.line, args_vector.column, args_vector)

closure = Lambra::AST::Closure.new(arguments.line, arguments.column, arguments, cdr.shift)
# Get a new compiler
block = BytecodeCompiler.new(self) closure.accept(self)

end
# Configures the new generator end
# TODO Move this to a method on the compiler
block.generator.for_block = true def visit_Closure(o)
block.generator.total_args = argcount set_line(o)
block.generator.required_args = argcount
block.generator.post_args = argcount state = g.state
block.generator.cast_for_multi_block_arg unless argcount.zero? state.scope.nest_scope o
block.generator.set_line args.line

blk_compiler = BytecodeCompiler.new(new_block_generator g, o.arguments)
block.visit_arguments(args.elements) blk = blk_compiler.generator
cdr.shift.accept(block)
block.generator.ret blk.push_state o

blk.state.push_super state.super
g.push_const :Function blk.state.push_eval state.eval
# Invoke the create block instruction
# with the generator of the block compiler blk.state.push_name blk.name
g.create_block block.finalize
g.send :new, 1 o.arguments.accept(blk_compiler)
blk.state.push_block
o.body.accept(blk_compiler)
blk.state.pop_block
blk.ret
blk_compiler.finalize

g.create_block blk
end

def visit_ClosureArguments(o)
args = o.arguments
args.each_with_index do |a, i|
g.shift_array
local = g.state.scope.new_local(a.name)
g.set_local local.slot
g.pop
end end
g.pop unless args.empty?
end end


def visit_PrimitiveForm(car, cdr) def visit_PrimitiveForm(car, cdr)
Expand All @@ -152,18 +174,10 @@ def visit_PrimitiveForm(car, cdr)
g.send :call, cdr.count g.send :call, cdr.count
end end


def visit_arguments(args)
args.each_with_index do |a, i|
g.shift_array
s.set_local a.name
g.pop
end
g.pop unless args.empty?
end

def visit_Symbol(o) def visit_Symbol(o)
set_line(o) set_line(o)
s.push_variable o.name local = g.state.scope.search_local(o.name)
local.get_bytecode(g)
end end


def visit_Number(o) def visit_Number(o)
Expand Down Expand Up @@ -265,8 +279,9 @@ def visit_Map(o)
end end


def finalize def finalize
g.local_names = s.variables g.local_names = g.state.scope.local_names
g.local_count = s.variables.size g.local_count = g.state.scope.local_count
g.pop_state
g.close g.close
g g
end end
Expand Down Expand Up @@ -295,5 +310,19 @@ def special_form?(name)
def primitive_form?(name) def primitive_form?(name)
PRIMITIVE_FORMS.include?(name.to_s) PRIMITIVE_FORMS.include?(name.to_s)
end end

def new_block_generator(g, arguments)
blk = g.class.new
blk.name = g.state.name || :__block__
blk.file = g.file
blk.for_block = true

blk.required_args = arguments.count
blk.post_args = arguments.count
blk.total_args = arguments.count
blk.cast_for_multi_block_arg unless arguments.count.zero?

blk
end
end end
end end
93 changes: 93 additions & 0 deletions lib/lambra/syntax/ast.rb
@@ -1,5 +1,66 @@
module Lambra module Lambra
module AST module AST
module Scope
attr_accessor :parent

def self.included(base)
base.send :include, Rubinius::Compiler::LocalVariables
end

def nest_scope(scope)
scope.parent = self
end

# A nested scope is looking up a local variable. If the variable exists
# in our local variables hash, return a nested reference to it. If it
# exists in an enclosing scope, increment the depth of the reference
# when it passes through this nested scope (i.e. the depth of a
# reference is a function of the nested scopes it passes through from
# the scope it is defined in to the scope it is used in).
def search_local(name)
if variable = variables[name]
variable.nested_reference
elsif block_local?(name)
new_local name
elsif reference = @parent.search_local(name)
reference.depth += 1
reference
end
end

def block_local?(name)
@locals.include?(name) if @locals
end

def new_local(name)
variable = Rubinius::Compiler::LocalVariable.new allocate_slot
variables[name] = variable
end

def new_nested_local(name)
new_local(name).nested_reference
end

# If the local variable exists in this scope, set the local variable
# node attribute to a reference to the local variable. If the variable
# exists in an enclosing scope, set the local variable node attribute to
# a nested reference to the local variable. Otherwise, create a local
# variable in this scope and set the local variable node attribute.
def assign_local_reference(var)
if variable = variables[var.name]
var.variable = variable.reference
elsif block_local?(var.name)
variable = new_local var.name
var.variable = variable.reference
elsif reference = @parent.search_local(var.name)
reference.depth += 1
var.variable = reference
else
variable = new_local var.name
var.variable = variable.reference
end
end
end


module Visitable module Visitable
def accept(visitor) def accept(visitor)
Expand All @@ -22,6 +83,12 @@ def to_sexp
end end
end end


class Sequence < Node
def to_sexp
@elements.map(&:to_sexp)
end
end

class Number < Node class Number < Node
def to_sexp def to_sexp
[sexp_name, @value] [sexp_name, @value]
Expand Down Expand Up @@ -89,5 +156,31 @@ def to_sexp
*@elements.map(&:to_sexp)] *@elements.map(&:to_sexp)]
end end
end end

class Closure < Node
include Scope

attr_reader :arguments, :body

def initialize(line, column, closure_arguments, body)
@line = line
@column = column
@arguments = closure_arguments
@body = body
end
end

class ClosureArguments < Node
attr_reader :arguments
def initialize(line, column, vector)
@line = line
@column = column
@arguments = vector.elements
end

def count
@arguments.count
end
end
end end
end end

0 comments on commit 321c119

Please sign in to comment.