class Opal::Nodes::IfNode

Constants

SWITCH_BRANCH_TEST_MATCH

Matches: ‘when 456` (from `case foo; when 123; when 456; end`) Captures: [s(:int, 456), “$ret_or_1”]

SWITCH_BRANCH_TEST_MATCH_CONTINUED

Matches: ‘when 456` Captures: [

s(:int, 789),
"$ret_or_1",
…here we delegate to either SWITCH_BRANCH_TEST_MATCH or SWITCH_BRANCH_TEST_MATCH_CONTINUED

]

SWITCH_TEST_MATCH

Matches: ‘case some_value_or_expression; when 123` Captures: [s(:int, 123), “$ret_or_1”, s(:send, nil, :some_value_or_expression))]

SWITCH_TEST_MATCH_CONTINUED

Matches: case some_value_or_expression; when 123, 456; end Captures: [

s(:int, 123),
"$ret_or_1",
s(:send, nil, :some_value_or_expression)),
…here we delegate to either SWITCH_BRANCH_TEST_MATCH or SWITCH_BRANCH_TEST_MATCH_CONTINUED

]

Public Instance Methods

compile() click to toggle source
# File lib/opal/nodes/if.rb, line 13
def compile
  if should_compile_as_simple_expression?
    if true_body == s(:true)
      compile_with_binary_or
    elsif false_body == s(:false)
      compile_with_binary_and
    else
      compile_with_ternary
    end
  elsif could_become_switch?
    compile_with_switch
  else
    compile_with_if
  end
end
compile_switch_case(test) click to toggle source
# File lib/opal/nodes/if.rb, line 365
def compile_switch_case(test)
  line "case ", expr(test), ":"
  if @switch_additional_rules
    @switch_additional_rules.each do |rule|
      line "case ", expr(rule), ":"
    end
  end
  indent do
    line stmt(true_body)
    line "break;" if !true_body || !returning?(true_body)
  end
  if false_body
    if false_body.meta[:switch_default]
      compile_switch_default
    elsif false_body.meta[:switch_child]
      push stmt(false_body)
    end
  else
    push stmt(s(:nil))
  end
end
compile_switch_default() click to toggle source
# File lib/opal/nodes/if.rb, line 387
def compile_switch_default
  line "default:"
  indent do
    line stmt(false_body)
  end
end
compile_with_binary_and() click to toggle source
# File lib/opal/nodes/if.rb, line 130
def compile_with_binary_and
  if sexp.meta[:do_js_truthy_on_true_body]
    truthy = js_truthy(true_body || s(:nil))
  else
    truthy = expr(true_body || s(:nil))
  end

  push '('
  push js_truthy(test), ' && '
  push '(', truthy, ')'
  push ')'
end
compile_with_binary_or() click to toggle source
# File lib/opal/nodes/if.rb, line 143
def compile_with_binary_or
  if sexp.meta[:do_js_truthy_on_false_body]
    falsy = js_truthy(false_body || s(:nil))
  else
    falsy = expr(false_body || s(:nil))
  end

  push '('
  push js_truthy(test), ' || '
  push '(', falsy, ')'
  push ')'
end
compile_with_if() click to toggle source
# File lib/opal/nodes/if.rb, line 29
def compile_with_if
  push_closure if expects_expression?

  truthy = self.truthy
  falsy = self.falsy

  if falsy && !truthy
    # Let's optimize a little bit `unless` calls.
    push 'if (!', js_truthy(test), ') {'
    falsy, truthy = truthy, falsy
  else
    push 'if (', js_truthy(test), ') {'
  end

  # skip if-body if no truthy sexp
  indent { line stmt(truthy) } if truthy

  if falsy
    if falsy.type == :if
      line '} else ', stmt(falsy)
    else
      line '} else {'
      indent do
        line stmt(falsy)
      end

      line '}'
    end
  else
    line '}'

    # This resolution isn't finite. Let's ensure this block
    # always return something if we expect a return
    line 'return nil;' if expects_expression?
  end

  pop_closure if expects_expression?

  if expects_expression?
    return_kw = 'return ' if returning_if?

    if scope.await_encountered
      wrap "#{return_kw}(await (async function() {", '})())'
    else
      wrap "#{return_kw}(function() {", '})()'
    end
  end
end
compile_with_switch() click to toggle source
# File lib/opal/nodes/if.rb, line 346
def compile_with_switch
  if sexp.meta[:switch_child]
    @switch_variable = sexp.meta[:switch_variable]
    @switch_additional_rules = sexp.meta[:switch_additional_rules]
    compile_switch_case(sexp.meta[:switch_test])
  else
    line "switch (", expr(@switch_first_test), ".valueOf()) {"
    indent do
      compile_switch_case(@switch_test)
    end
    line "}"
  end
end
compile_with_ternary() click to toggle source
# File lib/opal/nodes/if.rb, line 112
def compile_with_ternary
  truthy = true_body
  falsy = false_body

  push '('

  push js_truthy(test), ' ? '

  push '(', expr(truthy || s(:nil)), ') : '
  if !falsy || falsy.type == :if
    push expr(falsy || s(:nil))
  else
    push '(', expr(falsy || s(:nil)), ')'
  end

  push ')'
end
could_become_switch?() click to toggle source
# File lib/opal/nodes/if.rb, line 254
def could_become_switch?
  return false if expects_expression?

  return true if sexp.meta[:switch_child]

  test_match = SWITCH_TEST_MATCH.match(test) || SWITCH_TEST_MATCH_CONTINUED.match(test)
  return false unless test_match
  @switch_test, @switch_variable, @switch_first_test, additional_rules = *test_match

  additional_rules = handle_additional_switch_rules(additional_rules)
  return false unless additional_rules # It's ok for them to be empty, but false denotes a mismatch
  @switch_additional_rules = additional_rules

  return false unless valid_switch_body?(true_body)

  could_become_switch_branch?(false_body)
end
could_become_switch_branch?(body) click to toggle source
# File lib/opal/nodes/if.rb, line 286
def could_become_switch_branch?(body)
  if !body
    return true
  elsif body.type != :if
    if valid_switch_body?(body)
      body.meta[:switch_default] = true
      return true
    end
    return false
  end

  test, true_body, false_body = *body

  test_match = SWITCH_BRANCH_TEST_MATCH.match(test) || SWITCH_BRANCH_TEST_MATCH_CONTINUED.match(test)
  unless test_match
    if valid_switch_body?(body, true)
      body.meta[:switch_default] = true
      return true
    end
  end
  switch_test, switch_variable, additional_rules = *test_match

  switch_additional_rules = handle_additional_switch_rules(additional_rules)
  return false unless switch_additional_rules # It's ok for them to be empty, but false denotes a mismatch

  return false unless switch_variable == @switch_variable

  return false unless valid_switch_body?(true_body)
  return false unless could_become_switch_branch?(false_body)

  body.meta.merge!(switch_child: true,
                   switch_test: switch_test,
                   switch_variable: @switch_variable,
                   switch_additional_rules: switch_additional_rules
  )

  true
end
expects_expression?() click to toggle source
# File lib/opal/nodes/if.rb, line 98
def expects_expression?
  expr? || recv?
end
falsy() click to toggle source
# File lib/opal/nodes/if.rb, line 86
def falsy
  returnify(false_body)
end
handle_additional_switch_rules(additional_rules) click to toggle source
# File lib/opal/nodes/if.rb, line 272
def handle_additional_switch_rules(additional_rules)
  switch_additional_rules = []
  while additional_rules
    match = SWITCH_BRANCH_TEST_MATCH.match(additional_rules) || SWITCH_BRANCH_TEST_MATCH_CONTINUED.match(additional_rules)
    return false unless match

    switch_test, switch_variable, additional_rules = *match
    return false unless switch_variable == @switch_variable

    switch_additional_rules << switch_test
  end
  switch_additional_rules
end
returnify(body) click to toggle source
# File lib/opal/nodes/if.rb, line 90
def returnify(body)
  if expects_expression? && body
    compiler.returns(body)
  else
    body
  end
end
returning?(body) click to toggle source
# File lib/opal/nodes/if.rb, line 360
def returning?(body)
  %i[return js_return next].include?(body.type) ||
    (body.type == :begin && %i[return js_return next].include?(body.children.last.type))
end
returning_if?() click to toggle source
# File lib/opal/nodes/if.rb, line 78
def returning_if?
  @sexp.meta[:returning]
end
should_compile_as_simple_expression?() click to toggle source

There was a particular case in the past, that when we expected an expression from if, we always had to closure it. This produced an ugly code that was hard to minify. This addition tries to make a few cases compiled with a ternary operator instead and possibly a binary operator even?

# File lib/opal/nodes/if.rb, line 108
def should_compile_as_simple_expression?
  expects_expression? && simple?(true_body) && simple?(false_body)
end
simple?(body) click to toggle source

Let’s ensure there are no control flow statements inside.

# File lib/opal/nodes/if.rb, line 157
def simple?(body)
  case body
  when AST::Node
    case body.type
    when :return, :js_return, :break, :next, :redo, :retry
      false
    when :xstr
      XStringNode.single_line?(
        XStringNode.strip_empty_children(body.children)
      )
    else
      body.children.all? { |i| simple?(i) }
    end
  else
    true
  end
end
truthy() click to toggle source
# File lib/opal/nodes/if.rb, line 82
def truthy
  returnify(true_body)
end
valid_switch_body?(body, check_variable = false) click to toggle source
# File lib/opal/nodes/if.rb, line 325
def valid_switch_body?(body, check_variable = false)
  case body
  when AST::Node
    case body.type
    when :break, :redo, :retry
      false
    when :iter, :while
      # Don't traverse the iters or whiles!
      true
    else
      body.children.all? { |i| valid_switch_body?(i, check_variable) }
    end
  when @switch_variable
    # Perhaps we ended abruptly and we lack a $ret_or variable... but sometimes
    # we can ignore this.
    !check_variable
  else
    true
  end
end