lib/opal/nodes/iter.rb
# frozen_string_literal: true require 'opal/nodes/node_with_args' require 'opal/rewriters/break_finder' module Opal module Nodes class IterNode < NodeWithArgs handle :iter children :args, :body attr_accessor :block_arg, :shadow_args def compile inline_params = nil extract_block_arg extract_shadow_args extract_underscore_args split_args to_vars = identity = body_code = nil in_scope do inline_params = process(inline_args_sexp) identity = scope.identify! add_temp "self = #{identity}.$$s || this" compile_block_arg compile_shadow_args compile_inline_args compile_post_args compile_norm_args if compiler.arity_check? compile_arity_check end body_code = stmt(returned_body) to_vars = scope.to_vars end line body_code unshift to_vars unshift "(#{identity} = function(", inline_params, "){" push "}, #{identity}.$$s = self," push " #{identity}.$$brk = $brk," if contains_break? push " #{identity}.$$arity = #{arity}," if compiler.arity_check? push " #{identity}.$$parameters = #{parameters_code}," end # MRI expands a passed argument if the block: # 1. takes a single argument that is an array # 2. has more that one argument # With a few exceptions: # 1. mlhs arg: if a block takes |(a, b)| argument # 2. trailing ',' in the arg list (|a, |) # This flag on the method indicates that a block has a top level mlhs argument # which means that we have to expand passed array explicitly in runtime. if has_top_level_mlhs_arg? push " #{identity}.$$has_top_level_mlhs_arg = true," end if has_trailing_comma_in_args? push " #{identity}.$$has_trailing_comma_in_args = true," end push " #{identity})" end def norm_args @norm_args ||= args.children.select { |arg| arg.type == :arg } end def compile_norm_args norm_args.each do |arg| arg_name, _ = *arg push "if (#{arg_name} == null) #{arg_name} = nil;" end end def compile_block_arg if block_arg scope.block_name = block_arg scope.add_temp block_arg scope_name = scope.identify! line "#{block_arg} = #{scope_name}.$$p || nil;" line "if (#{block_arg}) #{scope_name}.$$p = null;" end end def extract_block_arg *regular_args, last_arg = args.children if last_arg && last_arg.type == :blockarg @block_arg = last_arg.children[0] @sexp = @sexp.updated(nil, [ s(:args, *regular_args), body ]) end end def compile_shadow_args shadow_args.each do |shadow_arg| arg_name = shadow_arg.children[0] scope.locals << arg_name scope.add_arg(arg_name) end end def extract_shadow_args @shadow_args = [] valid_args = [] return unless args args.children.each_with_index do |arg, idx| if arg.type == :shadowarg @shadow_args << arg else valid_args << arg end end @sexp = @sexp.updated(nil, [ args.updated(nil, valid_args), body ]) end def extract_underscore_args valid_args = [] caught_blank_argument = false args.children.each do |arg| arg_name = arg.children.first if arg_name == :_ unless caught_blank_argument caught_blank_argument = true valid_args << arg end else valid_args << arg end end @sexp = @sexp.updated(nil, [ args.updated(nil, valid_args), body ]) end def returned_body compiler.returns(body || s(:nil)) end def mlhs_args scope.mlhs_mapping.keys end def has_top_level_mlhs_arg? args.children.any? { |arg| arg.type == :mlhs } end def has_trailing_comma_in_args? if args.loc && args.loc.expression args_source = args.loc.expression.source args_source.match(/,\s*\|/) end end # Returns code used in debug mode to check arity of method call def compile_arity_check if arity_checks.size > 0 parent_scope = scope while !(parent_scope.top? || parent_scope.def? || parent_scope.class_scope?) parent_scope = parent_scope.parent end context = if parent_scope.top? "'<main>'" elsif parent_scope.def? "'#{parent_scope.mid}'" elsif parent_scope.class? "'<class:#{parent_scope.name}>'" elsif parent_scope.module? "'<module:#{parent_scope.name}>'" end identity = scope.identity line "if (#{identity}.$$is_lambda || #{identity}.$$define_meth) {" line " var $arity = arguments.length;" line " if (#{arity_checks.join(' || ')}) { Opal.block_ac($arity, #{arity}, #{context}); }" line "}" end end def contains_break? finder = Opal::Rewriters::BreakFinder.new finder.process(@sexp) finder.found_break? end end end end