Skip to content

Commit

Permalink
Merge pull request #131 from faultyserver/bug/self_closures
Browse files Browse the repository at this point in the history
Closes #81. Capture `self` as part of a closure.
  • Loading branch information
faultyserver authored Jan 27, 2018
2 parents 1a58184 + f133615 commit fe72e5a
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

# The myst executable
/myst
/bytecode_generator
# DWARF file from compiling on macOS
/myst.dwarf
24 changes: 24 additions & 0 deletions spec/interpreter/invocation_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,28 @@ describe "Interpreter - Invocation" do

it_invokes TYPE_DEFS, "Foo.foo", val(:static_foo)
it_invokes TYPE_DEFS, "%Foo{}.foo", val(:instance_foo)


it "restores the value of `self` after executing with a receiver" do
itr = Interpreter.new
original_self = itr.current_self

parse_and_interpret %q(
"hello, world".size
), interpreter: itr

itr.current_self.should eq(original_self)
end

it "restores the value of `self` after executing a closure" do
itr = Interpreter.new
original_self = itr.current_self

parse_and_interpret %q(
@sum = 0
[1, 2, 3].each{ |e| @sum += e }
), interpreter: itr

itr.current_self.should eq(original_self)
end
end
27 changes: 23 additions & 4 deletions spec/interpreter/nodes/anonymous_function_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,32 @@ describe "Interpreter - AnonymousFunction" do

it "acts like a closure" do
itr = parse_and_interpret %q(
fn
->(a) { a + 1 }
def foo
:called_foo
end
func = fn
->() { foo }
end
func()
)

functor = itr.stack.pop.as(TFunctor)
functor.closure?.should eq(true)
itr.stack.last.should eq(val(:called_foo))
end

it "captures the value of `self` as part of the closure" do
itr = parse_and_interpret %q(
@sum = 0
func = fn
->(a) { @sum += a }
end
func(6)
@sum
)

itr.stack.last.should eq(val(6))
end

it "allows clauses of various arities" do
Expand Down
10 changes: 10 additions & 0 deletions spec/interpreter/nodes/block_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,14 @@ describe "Interpreter - Block" do

itr.stack.last.should eq(val(6))
end

it "captures the value of `self` as part of the closure" do
itr = parse_and_interpret %q(
@sum = 0
[1, 2, 3].each{ |e| @sum += e }
@sum
)

itr.stack.last.should eq(val(6))
end
end
14 changes: 10 additions & 4 deletions src/myst/interpreter/invocation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ module Myst

def invoke
@selfstack_size_at_entry = @itr.self_stack.size
# If the invocation has a receiver, use it as the current value of `self`
# for the duration of the Invocation.
@itr.push_self(@receiver.not_nil!) if @receiver
case
when @receiver
# If the invocation has a receiver, use it as the current value of `self`
# for the duration of the Invocation.
@itr.push_self(@receiver.not_nil!)
when @func.closure?
# If the invoked functor is a closure, use the closed value of `self`.
@itr.push_self(@func.closed_self)
end

result = @func.clauses.each do |clause|
@itr.push_scope_override(@func.new_scope)
Expand All @@ -36,7 +42,7 @@ module Myst

# After the invocation, restore the current value of `self` to whatever
# it had been previously.
@itr.pop_self if @receiver
@itr.pop_self(to_size: @selfstack_size_at_entry)
result || @itr.__raise_runtime_error("No clause matches with given arguments: #{@args.inspect}")
rescue ex : BreakException
if ex.caught?
Expand Down
2 changes: 1 addition & 1 deletion src/myst/interpreter/nodes/anonymous_function.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module Myst
current_scope
end

functor = TFunctor.new([] of Callable, scope, closure: true)
functor = TFunctor.new([] of Callable, scope, closure: true, closed_self: current_self)

node.clauses.each do |clause|
functor.add_clause(TFunctorDef.new(clause))
Expand Down
2 changes: 1 addition & 1 deletion src/myst/interpreter/nodes/def.cr
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module Myst
scope.assign(node.name, functor)
end
else
functor = TFunctor.new([] of Callable, scope, closure: true)
functor = TFunctor.new([] of Callable, scope, closure: true, closed_self: current_self)
end

functor.add_clause(TFunctorDef.new(node))
Expand Down
3 changes: 2 additions & 1 deletion src/myst/interpreter/value.cr
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,9 @@ module Myst
property clauses : Array(Callable)
property lexical_scope : Scope
property? closure : Bool
property! closed_self : Value?

def initialize(@clauses=[] of Callable, @lexical_scope : Scope=Scope.new, @closure : Bool=false)
def initialize(@clauses=[] of Callable, @lexical_scope : Scope=Scope.new, @closure : Bool=false, @closed_self : Value?=nil)
end

def add_clause(definition : Callable)
Expand Down

0 comments on commit fe72e5a

Please sign in to comment.