# frozen_string_literal: true require 'opal/nodes/base' module Opal module Nodes class DefinedNode < Base handle :defined? children :value def compile case value.type when :self, :nil, :false, :true push value.type.to_s.inspect when :lvasgn, :ivasgn, :gvasgn, :cvasgn, :casgn, :op_asgn, :or_asgn, :and_asgn push "'assignment'" when :lvar push "'local-variable'" when :begin if value.children.size == 1 && value.children[0].type == :masgn push "'assignment'" else push "'expression'" end when :send compile_defined_send(value) wrap '(', " ? 'method' : nil)" when :ivar compile_defined_ivar(value) wrap '(', " ? 'instance-variable' : nil)" when :zsuper, :super compile_defined_super when :yield compile_defined_yield wrap '(', " ? 'yield' : nil)" when :xstr compile_defined_xstr(value) when :const compile_defined_const(value) wrap '(', " ? 'constant' : nil)" when :cvar compile_defined_cvar(value) wrap '(', " ? 'class variable' : nil)" when :gvar compile_defined_gvar(value) wrap '(', " ? 'global-variable' : nil)" when :back_ref compile_defined_back_ref wrap '(', " ? 'global-variable' : nil)" when :nth_ref compile_defined_nth_ref wrap '(', " ? 'global-variable' : nil)" when :array compile_defined_array(value) wrap '(', " ? 'expression' : nil)" else push "'expression'" end end def compile_defined(node) type = node.type if respond_to? "compile_defined_#{type}" __send__("compile_defined_#{type}", node) else node_tmp = scope.new_temp push "(#{node_tmp} = ", expr(node), ')' node_tmp end end def wrap_with_try_catch(code) returning_tmp = scope.new_temp push "(#{returning_tmp} = (function() { try {" push " return #{code};" push '} catch ($err) {' push ' if (Opal.rescue($err, [Opal.Exception])) {' push ' try {' push ' return false;' push ' } finally { Opal.pop_exception($err); }' push ' } else { throw $err; }' push '}})())' returning_tmp end def compile_send_recv_doesnt_raise(recv_code) wrap_with_try_catch(recv_code) end def compile_defined_send(node) recv, method_name, *args = *node mid = mid_to_jsid(method_name.to_s) if recv recv_code = compile_defined(recv) push ' && ' if recv.type == :send recv_code = compile_send_recv_doesnt_raise(recv_code) push ' && ' end recv_tmp = scope.new_temp push "(#{recv_tmp} = ", recv_code, ", #{recv_tmp}) && " else recv_tmp = scope.self end recv_value_tmp = scope.new_temp push "(#{recv_value_tmp} = #{recv_tmp}) && " meth_tmp = scope.new_temp push "(((#{meth_tmp} = #{recv_value_tmp}#{mid}) && !#{meth_tmp}.$$stub)" push " || #{recv_value_tmp}['$respond_to_missing?']('#{method_name}'))" args.each do |arg| case arg.type when :block_pass # ignoring else push ' && ' compile_defined(arg) end end wrap '(', ')' "#{meth_tmp}()" end def compile_defined_ivar(node) name = node.children[0].to_s[1..-1] # FIXME: this check should be positive for ivars initialized as nil too. # Since currently all known ivars are inialized to nil in the constructor # we can't tell if it was the user that put nil and made the ivar #defined? # or not. tmp = scope.new_temp push "(#{tmp} = #{scope.self}['#{name}'], #{tmp} != null && #{tmp} !== nil)" tmp end def compile_defined_super push expr s(:defined_super) end def compile_defined_yield scope.uses_block! block_name = scope.block_name || scope.find_parent_def.block_name push "(#{block_name} != null && #{block_name} !== nil)" block_name end def compile_defined_xstr(node) push '(typeof(', expr(node), ') !== "undefined")' end def compile_defined_const(node) const_scope, const_name = *node const_tmp = scope.new_temp if const_scope.nil? push "(#{const_tmp} = #{scope.relative_access}('#{const_name}', 'skip_raise'))" elsif const_scope == s(:cbase) push "(#{const_tmp} = #{top_scope.absolute_const}('::', '#{const_name}', 'skip_raise'))" else const_scope_tmp = compile_defined(const_scope) push " && (#{const_tmp} = #{top_scope.absolute_const}(#{const_scope_tmp}, '#{const_name}', 'skip_raise'))" end const_tmp end def compile_defined_cvar(node) cvar_name, _ = *node cvar_tmp = scope.new_temp push "(#{cvar_tmp} = #{class_variable_owner}.$$cvars['#{cvar_name}'], #{cvar_tmp} != null)" cvar_tmp end def compile_defined_gvar(node) helper :gvars name = node.children[0].to_s[1..-1] gvar_temp = scope.new_temp if %w[~ !].include? name push "(#{gvar_temp} = ", expr(node), ' || true)' else push "(#{gvar_temp} = $gvars[#{name.inspect}], #{gvar_temp} != null)" end gvar_temp end def compile_defined_back_ref helper :gvars back_ref_temp = scope.new_temp push "(#{back_ref_temp} = $gvars['~'], #{back_ref_temp} != null && #{back_ref_temp} !== nil)" back_ref_temp end def compile_defined_nth_ref helper :gvars nth_ref_tmp = scope.new_temp push "(#{nth_ref_tmp} = $gvars['~'], #{nth_ref_tmp} != null && #{nth_ref_tmp} != nil)" nth_ref_tmp end def compile_defined_array(node) node.children.each_with_index do |child, idx| push ' && ' unless idx == 0 compile_defined(child) end end end end end