lib/opal/nodes/iter.rb



require 'opal/nodes/scope'

module Opal
  module Nodes
    class IterNode < ScopeNode
      handle :iter

      children :args_sexp, :body_sexp

      def compile
        opt_args  = extract_opt_args
        block_arg = extract_block_arg

        # find any splat args
        if args.last.is_a?(Sexp) and args.last.type == :splat
          splat = args.last[1][1]
          args.pop
          len = args.length
        end

        params = args_to_params(args[1..-1])
        params << splat if splat

        to_vars = identity = body_code = nil

        in_scope do
          identity = scope.identify!
          add_temp "self = #{identity}.$$s || this"

          compile_args(args[1..-1], opt_args, params)

          if splat
            scope.add_arg splat
            push "#{splat} = $slice.call(arguments, #{len - 1});"
          end

          if block_arg
            scope.block_name = block_arg
            scope.add_temp block_arg
            scope_name = scope.identify!

            line "#{block_arg} = #{scope_name}.$$p || nil, #{scope_name}.$$p = null;"
          end

          body_code = stmt(body)
          to_vars = scope.to_vars
        end

        line body_code

        unshift to_vars

        unshift "(#{identity} = function(#{params.join ', '}){"
        push "}, #{identity}.$$s = self, #{identity})"
      end

      def compile_args(args, opt_args, params)
        args.each_with_index do |arg, idx|
          if arg.type == :lasgn
            arg = variable(arg[1])

            if opt_args and current_opt = opt_args.find { |s| s[1] == arg.to_sym }
              push "if (#{arg} == null) #{arg} = ", expr(current_opt[2]), ";"
            else
              push "if (#{arg} == null) #{arg} = nil;"
            end
          elsif arg.type == :array
            vars = {}
            arg[1..-1].each_with_index do |_arg, _idx|
              _arg = variable(_arg[1])
              unless vars.has_key?(_arg) || params.include?(_arg)
                vars[_arg] = "#{params[idx]}[#{_idx}]"
              end
            end
            push "var #{ vars.map{|k, v| "#{k} = #{v}"}.join(', ') };"
          else
            raise "Bad block arg type"
          end
        end
      end

      # opt args are last (if present) and are a s(:block)
      def extract_opt_args
        if args.last.is_a?(Sexp) and args.last.type == :block
          opt_args = args.pop
          opt_args.shift
          opt_args
        end
      end

      # does this iter define a block_pass
      def extract_block_arg
        if args.last.is_a?(Sexp) and args.last.type == :block_pass
          block_arg = args.pop
          block_arg = block_arg[1][1].to_sym
        end
      end

      def args
        if Fixnum === args_sexp or args_sexp.nil?
          s(:array)
        elsif args_sexp.type == :lasgn
          s(:array, args_sexp)
        else
          args_sexp[1]
        end
      end

      def body
        compiler.returns(body_sexp || s(:nil))
      end

      # Maps block args into array of jsid. Adds $ suffix to invalid js
      # identifiers.
      #
      # s(:args, parts...) => ["a", "b", "break$"]
      def args_to_params(sexp)
        result = []
        sexp.each do |arg|
          if arg[0] == :lasgn
            ref = variable(arg[1])
            next if ref == :_ && result.include?(ref)
            self.add_arg ref
            result << ref
          elsif arg[0] == :array
            result << scope.next_temp
          else
            raise "Bad js_block_arg: #{arg[0]}"
          end
        end

        result
      end
    end
  end
end