diff --git a/lib/nodes.js b/lib/nodes.js index ddda62a3..08e3a6d0 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -431,17 +431,21 @@ }; // Compile a vanilla function call. CallNode.prototype.compile_node = function compile_node(o) { - var _a, _b, _c, _d, arg, args; - if (this.args[this.args.length - 1] instanceof SplatNode) { - return this.compile_splat(o); + var _a, _b, _c, _d, _e, _f, _g, arg, args; + _a = this.args; + for (_b = 0, _c = _a.length; _b < _c; _b++) { + arg = _a[_b]; + if (arg instanceof SplatNode) { + return this.compile_splat(o); + } } args = (function() { - _a = []; _b = this.args; - for (_c = 0, _d = _b.length; _c < _d; _c++) { - arg = _b[_c]; - _a.push(arg.compile(o)); + _d = []; _e = this.args; + for (_f = 0, _g = _e.length; _f < _g; _f++) { + arg = _e[_f]; + _d.push(arg.compile(o)); } - return _a; + return _d; }).call(this).join(', '); if (this.variable === 'super') { return this.compile_super(args, o); @@ -459,7 +463,7 @@ // If you call a function with a splat, it's converted into a JavaScript // `.apply()` call to allow an array of arguments to be passed. CallNode.prototype.compile_splat = function compile_splat(o) { - var _a, _b, _c, arg, args, code, i, meth, obj, temp; + var meth, obj, temp; meth = this.variable.compile(o); obj = this.variable.source || 'this'; if (obj.match(/\(/)) { @@ -467,19 +471,34 @@ obj = temp; meth = "(" + temp + " = " + (this.variable.source) + ")" + (this.variable.last); } - args = (function() { - _a = []; _b = this.args; - for (i = 0, _c = _b.length; i < _c; i++) { - arg = _b[i]; - _a.push((function() { - code = arg.compile(o); - code = arg instanceof SplatNode ? code : "[" + code + "]"; - return i === 0 ? code : ".concat(" + code + ")"; - }).call(this)); + return '' + this.prefix + (meth) + ".apply(" + obj + ", " + (this.compile_splat_arguments(o)) + ")"; + }; + // Converts arbitrary number of arguments, mixed with splats, to + // a proper array to pass to an `.apply()` call + CallNode.prototype.compile_splat_arguments = function compile_splat_arguments(o) { + var _a, _b, _c, arg, args, code, i, prev; + args = []; + i = 0; + _a = this.args; + for (_b = 0, _c = _a.length; _b < _c; _b++) { + arg = _a[_b]; + code = arg.compile(o); + if (!(arg instanceof SplatNode)) { + prev = args[i - 1]; + if (i === 1 && prev[0] === '[' && prev[prev.length - 1] === ']') { + args[i - 1] = '' + (prev.slice(0, prev.length - 1)) + ", " + code + "]"; + continue; + } else if (i > 1 && prev[8] === '[' && prev[prev.length - 2] === ']') { + args[i - 1] = '' + (prev.slice(0, prev.length - 2)) + ", " + code + "])"; + continue; + } else { + code = "[" + code + "]"; + } } - return _a; - }).call(this); - return '' + this.prefix + (meth) + ".apply(" + obj + ", " + (args.join('')) + ")"; + args.push(i === 0 ? code : ".concat(" + code + ")"); + i += 1; + } + return args.join(''); }; return CallNode; }).call(this); @@ -899,7 +918,7 @@ // arrow, generates a wrapper that saves the current value of `this` through // a closure. CodeNode.prototype.compile_node = function compile_node(o) { - var _a, _b, _c, _d, _e, _f, _g, code, func, inner, name_part, param, params, shared_scope, splat, top; + var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, code, func, i, inner, name_part, param, params, shared_scope, splat, top; shared_scope = del(o, 'shared_scope'); top = del(o, 'top'); o.scope = shared_scope || new Scope(o.scope, this.body, this); @@ -908,22 +927,35 @@ o.indent = this.idt(this.bound ? 2 : 1); del(o, 'no_wrap'); del(o, 'globals'); - if (this.params[this.params.length - 1] instanceof SplatNode) { - splat = this.params.pop(); - splat.index = this.params.length; - this.body.unshift(splat); + i = 0; + splat = undefined; + params = []; + _a = this.params; + for (_b = 0, _c = _a.length; _b < _c; _b++) { + param = _a[_b]; + if (param instanceof SplatNode && !(typeof splat !== "undefined" && splat !== null)) { + splat = param; + splat.index = i; + this.body.unshift(splat); + splat.trailings = []; + } else if ((typeof splat !== "undefined" && splat !== null)) { + splat.trailings.push(param); + } else { + params.push(param); + } + i += 1; } params = (function() { - _a = []; _b = this.params; - for (_c = 0, _d = _b.length; _c < _d; _c++) { - param = _b[_c]; - _a.push(param.compile(o)); + _d = []; _e = params; + for (_f = 0, _g = _e.length; _f < _g; _f++) { + param = _e[_f]; + _d.push(param.compile(o)); } - return _a; + return _d; }).call(this); - _e = params; - for (_f = 0, _g = _e.length; _f < _g; _f++) { - param = _e[_f]; + _h = params; + for (_i = 0, _j = _h.length; _i < _j; _i++) { + param = _h[_i]; (o.scope.parameter(param)); } code = this.body.expressions.length ? "\n" + (this.body.compile_with_declarations(o)) + "\n" : ''; @@ -992,10 +1024,17 @@ // Compiling a parameter splat means recovering the parameters that succeed // the splat in the parameter list, by slicing the arguments object. SplatNode.prototype.compile_param = function compile_param(o) { - var name; + var _a, _b, _c, i, name, trailing; name = this.name.compile(o); o.scope.find(name); - return '' + name + " = Array.prototype.slice.call(arguments, " + this.index + ")"; + i = 0; + _a = this.trailings; + for (_b = 0, _c = _a.length; _b < _c; _b++) { + trailing = _a[_b]; + o.scope.assign(trailing.compile(o), "arguments[arguments.length - " + this.trailings.length + " + " + i + "]"); + i += 1; + } + return '' + name + " = Array.prototype.slice.call(arguments, " + this.index + ", arguments.length - " + (this.trailings.length) + ")"; }; // A compiling a splat as a destructuring assignment means slicing arguments // from the right-hand-side's corresponding array. diff --git a/src/nodes.coffee b/src/nodes.coffee index f530efc5..b8772122 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -338,7 +338,8 @@ exports.CallNode: class CallNode extends BaseNode # Compile a vanilla function call. compile_node: (o) -> - return @compile_splat(o) if @args[@args.length - 1] instanceof SplatNode + for arg in @args + return @compile_splat(o) if arg instanceof SplatNode args: (arg.compile(o) for arg in @args).join(', ') return @compile_super(args, o) if @variable is 'super' "$@prefix${@variable.compile(o)}($args)" @@ -362,11 +363,28 @@ exports.CallNode: class CallNode extends BaseNode temp: o.scope.free_variable() obj: temp meth: "($temp = ${ @variable.source })${ @variable.last }" - args: for arg, i in @args + "$@prefix${meth}.apply($obj, ${ @compile_splat_arguments(o) })" + + # Converts arbitrary number of arguments, mixed with splats, to + # a proper array to pass to an `.apply()` call + compile_splat_arguments: (o) -> + args: [] + i: 0 + for arg in @args code: arg.compile o - code: if arg instanceof SplatNode then code else "[$code]" - if i is 0 then code else ".concat($code)" - "$@prefix${meth}.apply($obj, ${ args.join('') })" + if not (arg instanceof SplatNode) + prev: args[i - 1] + if i is 1 and prev[0] is '[' and prev[prev.length - 1] is ']' + args[i - 1] = "${prev[0...prev.length - 1]}, $code]" + continue + else if i > 1 and prev[8] is '[' and prev[prev.length - 2] is ']' + args[i - 1] = "${prev[0...prev.length - 2]}, $code])" + continue + else + code: "[$code]" + args.push(if i is 0 then code else ".concat($code)") + i += 1 + args.join('') #### ExtendsNode @@ -699,11 +717,21 @@ exports.CodeNode: class CodeNode extends BaseNode o.indent: @idt(if @bound then 2 else 1) del o, 'no_wrap' del o, 'globals' - if @params[@params.length - 1] instanceof SplatNode - splat: @params.pop() - splat.index: @params.length - @body.unshift(splat) - params: (param.compile(o) for param in @params) + i: 0 + splat: undefined + params: [] + for param in @params + if param instanceof SplatNode and not splat? + splat: param + splat.index: i + @body.unshift(splat) + splat.trailings: [] + else if splat? + splat.trailings.push(param) + else + params.push(param) + i += 1 + params: (param.compile(o) for param in params) (o.scope.parameter(param)) for param in params code: if @body.expressions.length then "\n${ @body.compile_with_declarations(o) }\n" else '' name_part: if @name then ' ' + @name else '' @@ -750,8 +778,12 @@ exports.SplatNode: class SplatNode extends BaseNode compile_param: (o) -> name: @name.compile(o) o.scope.find name - "$name = Array.prototype.slice.call(arguments, $@index)" - + i: 0 + for trailing in @trailings + o.scope.assign(trailing.compile(o), "arguments[arguments.length - $@trailings.length + $i]") + i += 1 + "$name = Array.prototype.slice.call(arguments, $@index, arguments.length - ${@trailings.length})" + # A compiling a splat as a destructuring assignment means slicing arguments # from the right-hand-side's corresponding array. compile_value: (o, name, index) -> diff --git a/test/test_splats.coffee b/test/test_splats.coffee index 87b0afd7..b8d1c4d3 100644 --- a/test/test_splats.coffee +++ b/test/test_splats.coffee @@ -6,13 +6,14 @@ result: func 1, 2, 3, 4, 5 ok result is "3 4 5" -gold: silver: bronze: the_field: null +gold: silver: bronze: the_field: last: null -medalists: (first, second, third, rest...) -> +medalists: (first, second, third, rest..., unlucky) -> gold: first silver: second bronze: third - the_field: rest + the_field: rest.concat([last]) + last: unlucky contenders: [ "Michael Phelps" @@ -32,8 +33,17 @@ medalists "Mighty Mouse", contenders... ok gold is "Mighty Mouse" ok silver is "Michael Phelps" ok bronze is "Liu Xiang" +ok last is "Usain Bolt" ok the_field.length is 8 +contenders.reverse() +medalists contenders[0...2]..., "Mighty Mouse", contenders[2...contenders.length]... + +ok gold is "Usain Bolt" +ok silver is "Asafa Powell" +ok bronze is "Mighty Mouse" +ok last is "Michael Phelps" +ok the_field.length is 8 obj: { name: 'bob'