lib/opal/nodes/masgn.rb



require 'opal/nodes/base'

module Opal
  module Nodes
    class MassAssignNode < Base
      SIMPLE_ASSIGNMENT = [:lasgn, :iasgn, :lvar, :gasgn, :cdecl]

      handle :masgn
      children :lhs, :rhs

      def compile
        array = scope.new_temp

        if rhs.type == :array
          push "#{array} = ", expr(rhs)
          compile_masgn(lhs.children, array, rhs.size - 1)
          push ", #{array}" # a mass assignment evaluates to the RHS
        elsif rhs.type == :to_ary
          retval = scope.new_temp
          push "#{retval} = ", expr(rhs[1])
          push ", #{array} = Opal.to_ary(#{retval})"
          compile_masgn(lhs.children, array)
          push ", #{retval}"
          scope.queue_temp(retval)
        elsif rhs.type == :splat
          push "#{array} = Opal.to_a(", expr(rhs[1]), ")"
          compile_masgn(lhs.children, array)
          push ", #{array}"
        else
          raise "unsupported mlhs type"
        end

        scope.queue_temp(array)
      end

      # 'len' is how many rhs items are we sure we have
      def compile_masgn(lhs_items, array, len = nil)
        pre_splat  = lhs_items.take_while { |child| child.type != :splat }
        post_splat = lhs_items.drop(pre_splat.size)

        pre_splat.each_with_index do |child, idx|
          compile_assignment(child, array, idx, len)
        end

        unless post_splat.empty?
          splat = post_splat.shift

          if post_splat.empty? # trailing splat
            if part = splat[1]
              part = part.dup << s(:js_tmp, "$slice.call(#{array}, #{pre_splat.size})")
              push ', '
              push expr(part)
            end
          else
            tmp = scope.new_temp # end index for items consumed by splat
            push ", #{tmp} = #{array}.length - #{post_splat.size}"
            push ", #{tmp} = (#{tmp} < #{pre_splat.size}) ? #{pre_splat.size} : #{tmp}"

            if part = splat[1]
              part = part.dup << s(:js_tmp, "$slice.call(#{array}, #{pre_splat.size}, #{tmp})")
              push ', '
              push expr(part)
            end

            post_splat.each_with_index do |child, idx|
              if idx == 0
                compile_assignment(child, array, tmp)
              else
                compile_assignment(child, array, "#{tmp} + #{idx}")
              end
            end

            scope.queue_temp(tmp)
          end
        end
      end

      def compile_assignment(child, array, idx, len = nil)
        if !len || idx >= len
          assign = s(:js_tmp, "(#{array}[#{idx}] == null ? nil : #{array}[#{idx}])")
        else
          assign = s(:js_tmp, "#{array}[#{idx}]")
        end

        part = child.dup
        if SIMPLE_ASSIGNMENT.include?(child.type)
          part << assign
        elsif child.type == :call
          part[2] = "#{part[2]}=".to_sym
          part.last << assign
        elsif child.type == :attrasgn
          part.last << assign
        elsif child.type == :array
          # nested destructuring
          tmp = scope.new_temp
          push ", (#{tmp} = Opal.to_ary(#{assign[1]})"
          compile_masgn(child.children, tmp)
          push ')'
          scope.queue_temp(tmp)
          return
        else
          raise "Bad child node in masgn LHS: #{child}. LHS: #{lhs}"
        end

        push ', '
        push expr(part)
      end
    end
  end
end