# frozen_string_literal: true require 'opal/rewriters/base' module Opal module Rewriters class BinaryOperatorAssignment < Base def self.reset_tmp_counter! @@counter = 0 end def self.new_temp @@counter ||= 0 @@counter += 1 :"$binary_op_recvr_tmp_#{@@counter}" end GET_SET = ->(get_type, set_type) { ->(lhs, operation, rhs) { get_node = lhs.updated(get_type) # lhs set_node = s(:send, get_node, operation, rhs) # lhs + rhs lhs.updated(set_type, [*lhs, set_node]) # lhs = lhs + rhs } } # Takes `lhs += rhs` # Produces `lhs = lhs + rhs` LocalVariableHandler = GET_SET[:lvar, :lvasgn] # Takes `@lhs += rhs` # Produces `@lhs = @lhs + rhs` InstanceVariableHandler = GET_SET[:ivar, :ivasgn] # Takes `LHS += rhs` # Produces `LHS = LHS + rhs` ConstantHandler = GET_SET[:const, :casgn] # Takes `$lhs += rhs` # Produces `$lhs = $lhs + rhs` GlobalVariableHandler = GET_SET[:gvar, :gvasgn] # Takes `@@lhs += rhs` # Produces `@@lhs = @@lhs + rhs` ClassVariableHandler = GET_SET[:cvar, :cvasgn] # Takes `recvr.meth += rhs` # Produces `recvr.meth = recvr.meth + rhs` # (lhs is a recvr.meth, op is :+) class SendHandler < self def self.call(lhs, operation, rhs) recvr, reader_method, *args = *lhs # If recvr is a complex expression it must be cached. # MRI calls recvr in `recvr.meth ||= rhs` only once. if recvr && recvr.type == :send recvr_tmp = new_temp cache_recvr = s(:lvasgn, recvr_tmp, recvr) # $tmp = recvr recvr = s(:js_tmp, recvr_tmp) end writer_method = :"#{reader_method}=" call_reader = lhs.updated(:send, [recvr, reader_method, *args]) # $tmp.meth call_op = s(:send, call_reader, operation, rhs) # $tmp.meth + rhs call_writer = lhs.updated(:send, [recvr, writer_method, *args, call_op]) # $tmp.meth = $tmp.meth + rhs if cache_recvr s(:begin, cache_recvr, call_writer) else call_writer end end end # Takes `recvr.meth += rhs` # Produces `recvr.nil? ? nil : recvr.meth += rhs` # NOTE: Later output of this handler gets post-processed by this rewriter again # using SendHandler to `recvr.nil? ? nil : (recvr.meth = recvr.meth + rhs)` class ConditionalSendHandler < self def self.call(lhs, operation, rhs) recvr, meth, *args = *lhs recvr_tmp = new_temp cache_recvr = s(:lvasgn, recvr_tmp, recvr) # $tmp = recvr recvr = s(:js_tmp, recvr_tmp) recvr_is_nil = s(:send, recvr, :nil?) # recvr.nil? plain_send = lhs.updated(:send, [recvr, meth, *args]) # recvr.meth plain_op_asgn = s(:op_asgn, plain_send, operation, rhs) # recvr.meth += rhs s(:begin, cache_recvr, s(:if, recvr_is_nil, # if recvr.nil? s(:nil), # nil # else plain_op_asgn # recvr.meth ||= rhs ), ) # end end end HANDLERS = { lvasgn: LocalVariableHandler, ivasgn: InstanceVariableHandler, casgn: ConstantHandler, gvasgn: GlobalVariableHandler, cvasgn: ClassVariableHandler, send: SendHandler, csend: ConditionalSendHandler }.freeze # lhs += rhs def on_op_asgn(node) lhs, op, rhs = *node result = HANDLERS .fetch(lhs.type) { error "cannot handle LHS type: #{lhs.type}" } .call(lhs, op, rhs) process(result) end ASSIGNMENT_STRING_NODE = s(:str, 'assignment') # Rewrites any or_asgn and and_asgn node like # `defined?(a ||= 1)` # and # `defined?(a &&= 1)` # to a static "assignment" string node def on_defined?(node) inner, _ = *node if inner.type == :op_asgn ASSIGNMENT_STRING_NODE else super(node) end end end end end