# frozen_string_literal: true require 'opal/rewriters/base' module Opal module Rewriters class LogicalOperatorAssignment < Base def self.reset_tmp_counter! @@counter = 0 end def self.new_temp @@counter ||= 0 @@counter += 1 :"$logical_op_recvr_tmp_#{@@counter}" end GET_SET = ->(get_type, set_type) { ->(lhs, rhs, root_type) { get_node = lhs.updated(get_type) # lhs condition_node = s(root_type, get_node, rhs) # lhs || rhs if %i[const cvar].include?(get_type) && root_type == :or # defined?(lhs) defined_node = s(:defined?, get_node) # LHS = defined?(LHS) ? (LHS || rhs) : rhs condition_node = s(:if, defined_node, s(:begin, condition_node), rhs) end lhs.updated(set_type, [*lhs, condition_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 = defined?(LHS) ? (LHS || rhs) : rhs` # # 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 = defined?(@@lhs) ? (@@lhs || rhs) : rhs` # # 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) class SendHandler < self def self.call(lhs, rhs, root_type) 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_writer = lhs.updated(:send, [recvr, writer_method, *args, rhs]) # $tmp.meth = rhs get_or_set = s(root_type, call_reader, call_writer) if cache_recvr s(:begin, cache_recvr, get_or_set) else get_or_set 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, rhs, root_type) root_type = :"#{root_type}_asgn" 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_or_asgn = s(root_type, plain_send, rhs) # recvr.meth ||= rhs s(:begin, cache_recvr, s(:if, recvr_is_nil, # if recvr.nil? s(:nil), # nil # else plain_or_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_or_asgn(node) lhs, rhs = *node result = HANDLERS .fetch(lhs.type) { error "cannot handle LHS type: #{lhs.type}" } .call(lhs, rhs, :or) process(result) end # lhs &&= rhs def on_and_asgn(node) lhs, rhs = *node result = HANDLERS .fetch(lhs.type) { error "cannot handle LHS type: #{lhs.type}" } .call(lhs, rhs, :and) 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 %i[or_asgn and_asgn].include?(inner.type) ASSIGNMENT_STRING_NODE else super(node) end end end end end