class Opal::Nodes::IfNode
def compile
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
def compile_switch_case(test)
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
def compile_switch_default
def compile_switch_default line "default:" indent do line stmt(false_body) end end
def compile_with_binary_and
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
def compile_with_binary_or
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
def compile_with_if
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
def compile_with_switch
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
def compile_with_ternary
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
def could_become_switch?
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
def could_become_switch_branch?(body)
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
def expects_expression?
def expects_expression? expr? || recv? end
def falsy
def falsy returnify(false_body) end
def handle_additional_switch_rules(additional_rules)
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
def returnify(body)
def returnify(body) if expects_expression? && body compiler.returns(body) else body end end
def returning?(body)
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
def returning_if?
def returning_if? @sexp.meta[:returning] end
def should_compile_as_simple_expression?
a ternary operator instead and possibly a binary operator
This addition tries to make a few cases compiled with
it. This produced an ugly code that was hard to minify.
expected an expression from if, we always had to closure
There was a particular case in the past, that when we
def should_compile_as_simple_expression? expects_expression? && simple?(true_body) && simple?(false_body) end
def simple?(body)
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
def truthy
def truthy returnify(true_body) end
def valid_switch_body?(body, check_variable = false)
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