module Opal::Nodes::Closure::NodeSupport
def closure_is?(type)
def closure_is?(type) @closure.is?(type) end
def compile_catcher
def compile_catcher catchers = @closure.catchers return if catchers.empty? helper :thrower catchers_without_eval_return = catchers.grep_v(:eval_return) push "} catch($e) {" indent do catchers.each do |type| case type when :eval_return line "if ($e === Opal.t_eval_return) return $e.$v;" else line "if ($e === $t_#{type}) return $e.$v;" end end line "throw $e;" end line "}" unless catchers_without_eval_return.empty? push " finally {", *catchers_without_eval_return.map { |type| "$t_#{type}.is_orphan = true;" }, "}" end unshift "return " if closure_is? SEND unless catchers_without_eval_return.empty? unshift "var ", catchers_without_eval_return.map { |type| "$t_#{type} = $thrower('#{type}')" }.join(", "), "; " end unshift "try { " unless closure_is? JS_FUNCTION if scope.await_encountered wrap "(await (async function(){", "})())" else wrap "(function(){", "})()" end end end
def generate_thrower(type, closure, value)
def generate_thrower(type, closure, value) id = closure.register_catcher(type) closure.register_thrower(type, id) push id, '.$throw(', expr_or_nil(value), ', ', scope.identify!, '.$$is_lambda)' id end
def generate_thrower_without_catcher(type, closure, value)
def generate_thrower_without_catcher(type, closure, value) helper :thrower if closure.throwers.key? type id = closure.throwers[type] else id = compiler.unique_temp('t_') parent_scope = closure.node.scope&.parent || top_scope parent_scope.add_scope_temp("#{id} = $thrower('#{type}')") closure.register_thrower(type, id) end push id, '.$throw(', expr_or_nil(value), ', ', scope.identify!, '.$$is_lambda)' id end
def in_closure(type = JS_FUNCTION)
def in_closure(type = JS_FUNCTION) closure = push_closure(type) out = yield closure pop_closure out end
def pop_closure
def pop_closure compile_catcher @compiler.closure_stack.pop last = @compiler.closure_stack.last @closure = last if last&.node == self end
def push_closure(type = JS_FUNCTION)
def push_closure(type = JS_FUNCTION) closure = Closure.new(self, type, select_closure) @compiler.closure_stack << closure @closure = closure end
def select_closure(type = ANY, break_after: NONE)
def select_closure(type = ANY, break_after: NONE) @compiler.closure_stack.reverse.find do |i| break if (i.type & break_after) != 0 (i.type & type) != 0 end end
def thrower(type, value = nil)
def thrower(type, value = nil) case type when :return thrower_closure = select_closure(DEF, break_after: MODULE | TOP) last_closure = select_closure(JS_FUNCTION) if !thrower_closure iter_closure = select_closure(ITER, break_after: DEF | MODULE | TOP) if iter_closure generate_thrower_without_catcher(:return, iter_closure, value) elsif compiler.eval? push 'Opal.t_eval_return.$throw(', expr_or_nil(value), ', false)' else error 'Invalid return' end elsif thrower_closure == last_closure push 'return ', expr_or_nil(value) else id = generate_thrower(:return, thrower_closure, value) # Additionally, register our thrower on the surrounding iter, if present iter_closure = select_closure(ITER, break_after: DEF | MODULE | TOP) iter_closure.register_thrower(:return, id) if iter_closure end when :eval_return thrower_closure = select_closure(DEF | LAMBDA, break_after: MODULE | TOP) if thrower_closure thrower_closure.register_catcher(:eval_return) end when :next, :redo thrower_closure = select_closure(ITER | LOOP_INSIDE, break_after: DEF | MODULE | TOP) last_closure = select_closure(JS_FUNCTION | JS_LOOP_INSIDE) if !thrower_closure error 'Invalid next' elsif thrower_closure == last_closure if thrower_closure.is? LOOP_INSIDE push 'continue' elsif thrower_closure.is? ITER | LAMBDA push 'return ', expr_or_nil(value) end else generate_thrower(:next, thrower_closure, value) end when :break thrower_closure = select_closure(SEND | LAMBDA | LOOP, break_after: DEF | MODULE | TOP) last_closure = select_closure(JS_FUNCTION | JS_LOOP) if !thrower_closure iter_closure = select_closure(ITER, break_after: DEF | MODULE | TOP) if iter_closure generate_thrower_without_catcher(:break, iter_closure, value) else error 'Invalid break' end elsif thrower_closure == last_closure if thrower_closure.is? JS_FUNCTION | LAMBDA push 'return ', expr_or_nil(value) elsif thrower_closure.is? LOOP push 'break' end else generate_thrower(:break, thrower_closure, value) end when :retry thrower_closure = select_closure(RESCUE_RETRIER, break_after: DEF | MODULE | TOP) last_closure = select_closure(JS_LOOP_INSIDE) if !thrower_closure error 'Invalid retry' elsif thrower_closure == last_closure push 'continue' else generate_thrower(:retry, thrower_closure, value) end end end