module Opal::Nodes::Closure::NodeSupport

Attributes

closure[RW]

Public Instance Methods

closure_is?(type) click to toggle source
# File lib/opal/nodes/closure.rb, line 202
def closure_is?(type)
  @closure.is?(type)
end
compile_catcher() click to toggle source

Generate a catcher if thrower has been used

# File lib/opal/nodes/closure.rb, line 207
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
generate_thrower(type, closure, value) click to toggle source
# File lib/opal/nodes/closure.rb, line 102
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
generate_thrower_without_catcher(type, closure, value) click to toggle source
# File lib/opal/nodes/closure.rb, line 109
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
in_closure(type = JS_FUNCTION) { |closure| ... } click to toggle source
# File lib/opal/nodes/closure.rb, line 88
def in_closure(type = JS_FUNCTION)
  closure = push_closure(type)
  out = yield closure
  pop_closure
  out
end
pop_closure() click to toggle source
# File lib/opal/nodes/closure.rb, line 81
def pop_closure
  compile_catcher
  @compiler.closure_stack.pop
  last = @compiler.closure_stack.last
  @closure = last if last&.node == self
end
push_closure(type = JS_FUNCTION) click to toggle source
# File lib/opal/nodes/closure.rb, line 73
def push_closure(type = JS_FUNCTION)
  closure = Closure.new(self, type, select_closure)
  @compiler.closure_stack << closure
  @closure = closure
end
select_closure(type = ANY, break_after: NONE) click to toggle source
# File lib/opal/nodes/closure.rb, line 95
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
thrower(type, value = nil) click to toggle source
# File lib/opal/nodes/closure.rb, line 124
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