Last active
December 20, 2015 01:29
-
-
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
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
| 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