Skip to content

Instantly share code, notes, and snippets.

@baroquebobcat
Last active December 20, 2015 01:29
Show Gist options
  • Select an option

  • Save baroquebobcat/6049682 to your computer and use it in GitHub Desktop.

Select an option

Save baroquebobcat/6049682 to your computer and use it in GitHub Desktop.
Really rough attempt at hacking non local return into Mirah's closure building
diff --git a/src/org/mirah/typer/closures.mirah b/src/org/mirah/typer/closures.mirah
index e07327f..46e31a0 100644
--- a/src/org/mirah/typer/closures.mirah
+++ b/src/org/mirah/typer/closures.mirah
@@ -17,6 +17,8 @@ package org.mirah.typer
import mirah.lang.ast.*
import java.util.Collections
+import java.util.List
+import java.util.ArrayList
# This class transforms a Block into an anonymous class once the Typer has figured out
# the interface to implement (or the abstract superclass).
@@ -30,30 +32,221 @@ class ClosureBuilder
@scoper = typer.scoper
end
- def prepare(block: Block, parent_type: ResolvedType)
+ def insert_closure(block: Block, parent_type: ResolvedType)
+ parent = CallSite(block.parent)
+ # TODO: This will fail if the block's class changes.
+ new_node = nil # not sure if necessary
+ if has_non_local_return block
+ nlr_klass = build_nlr_exception block
+ new_node = nlr_prepare block, parent_type, nlr_klass
+
+ # traverse up to enclosing body but one,
+ # wrap that in a rescue
+ node_in_body = block.findAncestor do |node|
+ node.parent.parent.kind_of?(MethodDefinition) || node.parent.parent.kind_of?(Script)
+ end
+ puts node_in_body
+ new_call = wrap_with_rescue block, nlr_klass, node_in_body
+ parent.parent.replaceChild parent, new_call
+ else
+ new_node = prepare(block, parent_type)
+ end
+
+ replace_block_with_closure_in_call parent, block, new_node
+ @typer.infer(new_node)
+ end
+
+#privates-----
+ def replace_block_with_closure_in_call(parent: CallSite, block: Block, new_node: Node): void
+ if block == parent.block
+ parent.block = nil
+ parent.parameters.add(new_node)
+ else
+ new_node.setParent(nil)
+ parent.replaceChild(block, new_node)
+ end
+ end
+
+
+ def find_enclosing_body block: Block
enclosing_node = block.findAncestor {|node| node.kind_of?(MethodDefinition) || node.kind_of?(Script)}
- enclosing_body = if enclosing_node.kind_of?(MethodDefinition)
+ if enclosing_node.kind_of?(MethodDefinition)
MethodDefinition(enclosing_node).body
else
Script(enclosing_node).body
end
+ end
- klass = build_class(block.position, parent_type)
- insert_into_body enclosing_body, klass
+ def has_non_local_return(block: Block): boolean
+ (!contains_methods(block)) && # parser annoying
+ contains_return(block)
+ end
- # TODO(ribrdb) binding
- parent_scope = @scoper.getScope(block)
- build_constructor(enclosing_body, klass, parent_scope)
+ def build_nlr_exception(block: Block): Node
+ nlr_klass = build_class block.position, @types.getBaseExceptionType.resolve
+ insert_into_body find_enclosing_body(block), nlr_klass
+
+ return_type=SimpleString.new 'String'
+ args = Arguments.new(block.position,
+ [RequiredArgument.new(SimpleString.new('return_value'), return_type)], # TODO string is not really right
+ Collections.emptyList,
+ nil,
+ Collections.emptyList,
+ nil)
+ body = FieldAssign.new(SimpleString.new('return_value'), LocalAccess.new(SimpleString.new('return_value')), nil)
+ constructor = ConstructorDefinition.new(SimpleString.new('initialize'), args, SimpleString.new('void'), [body], nil)
+ nlr_klass.body.add(constructor)
+
+
+ name = SimpleString.new(block.position, 'return_value')
+ args = Arguments.new(block.position, Collections.emptyList, Collections.emptyList, nil, Collections.emptyList, nil)
+ method = MethodDefinition.new(block.position, name, args, return_type, nil, nil)
+ method.body = NodeList.new
+ method.body.add Return.new(block.position, FieldAccess.new(SimpleString.new 'return_value'))
+
+ nlr_klass.body.add method
+ nlr_klass
+ end
+
+ def nlr_prepare(block: Block, parent_type: ResolvedType, nlr_klass: Node)
+ # class MyNonLocalReturn < Throwable
+ # def initialize(return_value:`method return type`); @return_value = return_value; end
+ # def return_value; @return_value; end
+ # end
+ # begin
+ # call { raise MyNonLocalReturn, `value` }
+ # rescue MyNonLocalReturn => e
+ # return e.return_value
+ # end
+ parent_scope = get_scope block
+ klass = build_closure_class block, parent_type, parent_scope
+
+
+ convert_returns_to_raises block, nlr_klass
+ build_method(klass, block, parent_type, parent_scope)
+
+ Node(new_closure_call_node(block, klass)) # casting WTF
+ end
+#/private
+
+ def prepare(block: Block, parent_type: ResolvedType): Node #call & rescue are incompat?? wat?
+ parent_scope = get_scope block
+ klass = build_closure_class block, parent_type, parent_scope
+
if contains_methods(block)
copy_methods(klass, block, parent_scope)
+ Node(new_closure_call_node(block, klass))
else
build_method(klass, block, parent_type, parent_scope)
+ Node(new_closure_call_node(block, klass)) # casting WTF
end
+ end
+
+ def build_closure_class block: Block, parent_type: ResolvedType, parent_scope: Scope
+ klass = build_class(block.position, parent_type)
+ enclosing_body = find_enclosing_body block
+ # TODO(ribrdb) binding
+ build_constructor(enclosing_body, klass, parent_scope)
- new_closure_call_node(block, klass)
+
+ insert_into_body enclosing_body, klass
+ klass
end
+ def get_scope block: Block
+ @scoper.getScope(block)
+ end
+
+ def wrap_with_rescue block: Node, nlr_klass: Node, call: Node
+ Rescue.new(block.position,
+ [call],
+ [
+ RescueClause.new(
+ block.position,
+ [makeTypeName(block.position, infer(nlr_klass).resolve)],
+ SimpleString.new('ret_error'),
+ [ Return.new(Call.new(block.position,
+ LocalAccess.new(SimpleString.new 'ret_error'),
+ SimpleString.new("return_value"),
+ Collections.emptyList,
+ nil
+ ))
+ ]
+ )
+ ],nil
+ )
+ end
+
+ def convert_returns_to_raises block: Block, nlr_klass: Node
+ return_nodes(block).each do |_n|
+ node = Return(_n)
+ parent = node.parent
+
+ _raise = Raise.new(node.position, [
+ Call.new(node.position,
+ makeTypeName(node.position, infer(nlr_klass).resolve),
+ SimpleString.new('new'),
+ [node.value], #Why need a cast here? shouldn't it look up in super?
+ nil
+ )
+ ])
+ parent.replaceChild node, _raise
+ end
+ end
+
+ def contains_return block: Node
+ !return_nodes(block).isEmpty
+ end
+
+ def return_nodes(block: Node): List
+ #block.findDescendants { |c| c.kind_of? Return }
+ # from findDescendants
+ finder = DescendentFinder.new(false, true) { |c| c.kind_of? Return }
+ finder.scan(block, nil)
+ finder.results
+ end
+
+
+
+# from commented out code in the parser
+
+ class DescendentFinder < NodeScanner
+ def initialize(children_only: boolean, only_one: boolean, filter: NodeFilter)
+ @results = ArrayList.new
+ @children = children_only
+ @only_one = only_one
+ @filter = filter
+ end
+
+ def enterDefault(node: Node, arg: Object): boolean
+ return false if @results.size == 1 && @only_one
+ if @filter.matchesNode(node)
+ @results.add(node)
+ return false if @only_one
+ end
+ return !@children
+ end
+
+ def results: List
+ @results
+ end
+
+ def result: Node
+ if @results.size == 0
+ nil
+ else
+ Node(@results.get(0))
+ end
+ end
+ end
+
+
+#-------------------------
+
+
+
+
def new_closure_call_node(block: Block, klass: Node): Call
closure_type = infer(klass)
target = makeTypeName(block.position, closure_type.resolve)
diff --git a/src/org/mirah/typer/simple/simple_scope.mirah b/src/org/mirah/typer/simple/simple_scope.mirah
index 5927417..0f95218 100644
--- a/src/org/mirah/typer/simple/simple_scope.mirah
+++ b/src/org/mirah/typer/simple/simple_scope.mirah
@@ -63,7 +63,7 @@ class SimpleScope; implements Scope
def imports
@imports
end
- def search_packages
+ def search_packages: List
@search_packages
end
def package:String
diff --git a/src/org/mirah/typer/typer.mirah b/src/org/mirah/typer/typer.mirah
index c8df16d..2eefe28 100644
--- a/src/org/mirah/typer/typer.mirah
+++ b/src/org/mirah/typer/typer.mirah
@@ -993,20 +993,11 @@ class Typer < SimpleNodeVisitor
new_scope = @scopes.addScope(block)
infer(block.arguments) if block.arguments
closures = @closures
- parent = CallSite(block.parent)
+ #parent = CallSite(block.parent)
typer = self
BlockFuture.new(block) do |x, resolvedType|
unless resolvedType.isError
- # TODO: This will fail if the block's class changes.
- new_node = closures.prepare(block, resolvedType)
- if block == parent.block
- parent.block = nil
- parent.parameters.add(new_node)
- else
- new_node.setParent(nil)
- parent.replaceChild(block, new_node)
- end
- typer.infer(new_node)
+ closures.insert_closure(block, resolvedType)
end
end
end
diff --git a/test/jvm/blocks_test.rb b/test/jvm/blocks_test.rb
index 6009f56..61f9c02 100644
--- a/test/jvm/blocks_test.rb
+++ b/test/jvm/blocks_test.rb
@@ -353,4 +353,23 @@ class BlocksTest < Test::Unit::TestCase
cls.main(nil)
end
end
+
+ def test_closures_support_non_local_return
+ cls, = compile(<<-EOF)
+ class NonLocalMe
+ def foo(a: Runnable)
+ a.run
+ puts "doesn't get here"
+ end
+ end
+ def nlr: String
+ NonLocalMe.new.foo { return "NLR!"}
+ "nor here either"
+ end
+ puts nlr
+ EOF
+ assert_output "NLR!\n" do
+ cls.main(nil)
+ end
+ end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment