module Ruby2JS class Converter # (send nil :puts # (int 1)) # (attr nil :puts) # (sendw nil :puts # (int 1)) # Note: attr, sendw, and await are only generated by filters. Attr forces # interpretation as an attribute vs a function call with zero parameters. # Sendw forces parameters to be placed on separate lines. handle :send, :sendw, :await, :attr, :call do |receiver, method, *args| ast = @ast # strip '!' and '?' decorations method = method.to_s[0..-2] if method =~ /\w[!?]$/ # three ways to define anonymous functions if method == :new and receiver and receiver.children == [nil, :Proc] return parse args.first, @state elsif not receiver and [:lambda, :proc].include? method if method == :lambda return parse s(args.first.type, *args.first.children[0..-2], s(:autoreturn, args.first.children[-1])), @state else return parse args.first, @state end end # call anonymous function if [:call, :[]].include? method and receiver and receiver.type == :block t2,m2,*args2 = receiver.children.first.children if not t2 and [:lambda, :proc].include? m2 and args2.length == 0 (es2015 || @state == :statement ? group(receiver) : parse(receiver)) put '('; parse_all(*args, join: ', '); put ')' return end end # async/await support if es2017 and receiver == nil and args.length == 1 if method == :async if args.first.type == :def # async def f(x) {...} return parse args.first.updated :async elsif args.first.type == :defs # async def o.m(x) {...} return parse args.first.updated :asyncs elsif args.first.type == :block block = args.first if block.children[0].children.last == :lambda # async lambda {|x| ... } # async -> (x) { ... } return parse block.updated(:async, [nil, block.children[1], s(:autoreturn, block.children[2])]) elsif block.children[0].children.last == :proc # async proc {|x| ... } return parse block.updated(:async, [nil, *block.children[1..-1]]) elsif block.children[0].children[1] == :new and block.children[0].children[0] == s(:const, nil, :Proc) then # async Proc.new {|x| ... } return parse block.updated(:async, [nil, *block.children[1..-1]]) end end elsif method == :await if args.first.type == :send # await f(x) return parse args.first.updated(:await) elsif args.first.type == :block # await f(x) { ... } block = args.first return parse block.updated nil, [block.children[0].updated(:await), *block.children[1..-1]] end end end op_index = operator_index method if op_index != -1 target = args.first end # resolve anonymous receivers against rbstack receiver ||= @rbstack.map {|rb| rb[method]}.compact.last if receiver group_receiver = receiver.type == :send && op_index < operator_index( receiver.children[1] ) if receiver group_receiver ||= GROUP_OPERATORS.include? receiver.type group_receiver = false if receiver.children[1] == :[] if receiver.type == :int and !OPERATORS.flatten.include?(method) group_receiver = true end end if target group_target = target.type == :send && op_index < operator_index( target.children[1] ) group_target ||= GROUP_OPERATORS.include? target.type end if method == :! parse s(:not, receiver) elsif method == :[] (group_receiver ? group(receiver) : parse(receiver)) put '['; parse_all(*args, join: ', '); put ']' elsif method == :[]= parse receiver; put '['; parse_all(*args[0..-2], join: ', '); put '] = ' parse args[-1] elsif method == :** and not es2016 put 'Math.pow(' parse receiver put ', ' parse args.first put ')' elsif [:-@, :+@, :~, '~'].include? method put method.to_s[0]; parse receiver elsif method == :=~ parse args.first; put '.test('; parse receiver; put ')' elsif method == :!~ put '!'; parse args.first; put '.test('; parse receiver; put ')' elsif method == :<< and args.length == 1 and @state == :statement parse receiver; put '.push('; parse args.first; put ')' elsif method == :<=> raise NotImplementedError, "use of <=>" elsif OPERATORS.flatten.include?(method) and not LOGICAL.include?(method) (group_receiver ? group(receiver) : parse(receiver)) put " #{ method } " (group_target ? group(target) : parse(target)) elsif method =~ /=$/ multi_assign_declarations if @state == :statement parse receiver put "#{ '.' if receiver }#{ method.to_s.sub(/=$/, ' =') } " parse args.first, (@state == :method ? :method : :expression) elsif method == :new if receiver # map Ruby's "Regexp" to JavaScript's "Regexp" if receiver == s(:const, nil, :Regexp) receiver = s(:const, nil, :RegExp) end # allow a RegExp to be constructed from another RegExp if receiver == s(:const, nil, :RegExp) if args.first.type == :regexp opts = '' if args.first.children.last.children.length > 0 opts = args.first.children.last.children.join end if args.length > 1 opts += args.last.children.last end return parse s(:regexp, *args.first.children[0...-1], s(:regopt, *opts.split('').map(&:to_sym))) elsif args.first.type == :str if args.length == 2 and args[1].type == :str opts = args[1].children[0] else opts = '' end return parse s(:regexp, args.first, s(:regopt, *opts.each_char.map {|c| c})) end end put "new "; parse receiver if ast.is_method? put '('; parse_all(*args, join: ', '); put ')' end elsif args.length == 1 and args.first.type == :send # accommodation for JavaScript like new syntax w/argument list parse s(:send, s(:const, nil, args.first.children[1]), :new, *args.first.children[2..-1]), @state elsif args.length == 1 and args.first.type == :const # accommodation for JavaScript like new syntax w/o argument list parse s(:attr, args.first, :new), @state elsif args.length == 2 and [:send, :const].include? args.first.type and args.last.type == :def and args.last.children.first == nil then # accommodation for JavaScript like new syntax with block parse s(:send, s(:const, nil, args.first.children[1]), :new, *args.first.children[2..-1], args.last), @state else raise NotImplementedError, "use of JavaScript keyword new" end elsif method == :raise and receiver == nil if args.length == 1 put 'throw '; parse args.first else put 'throw new '; parse args.first; put '('; parse args[1]; put ')' end elsif method == :typeof and receiver == nil put 'typeof '; parse args.first else put 'await ' if @ast.type == :await if not ast.is_method? if receiver (group_receiver ? group(receiver) : parse(receiver)) put ".#{ method }" else parse ast.updated(:lvasgn, [method]), @state end elsif args.any? {|arg| arg.type == :splat} and not es2015 parse s(:send, s(:attr, receiver, method), :apply, (receiver || s(:nil)), s(:array, *args)) else (group_receiver ? group(receiver) : parse(receiver)) put "#{ '.' if receiver && method}#{ method }" if args.length <= 1 put "("; parse_all(*args, join: ', '); put ')' else compact { puts "("; parse_all(*args, join: ",#@ws"); sput ')' } end end end end handle :csend do |receiver, method, *args| node = @ast # collect up chain of conditional sends stack = [] while node.children.first.type == :csend stack << node node = node.children.first end # conditionally evaluate most nested expression expr = node.updated(:send) result = s(:and, node.children.first, expr) # build up chain of conditional evaluations until stack.empty? node = stack.pop expr = node.updated(:send, [expr, *node.children[1..-1]]) result = s(:and, result, expr) end parse result end handle :splat do |expr| put '...' parse expr end end end