safer splats with __slice where necessary.

This commit is contained in:
Jeremy Ashkenas 2010-11-01 23:41:05 -04:00
parent 6163215bbe
commit 6aaa2eb4d0
3 changed files with 109 additions and 106 deletions

View File

@ -6,7 +6,7 @@
child.prototype = new ctor;
if (typeof parent.extended === "function") parent.extended(child);
child.__super__ = parent.prototype;
};
}, __slice = Array.prototype.slice;
Scope = require('./scope').Scope;
_ref = require('./helpers'), compact = _ref.compact, flatten = _ref.flatten, extend = _ref.extend, merge = _ref.merge, del = _ref.del, starts = _ref.starts, ends = _ref.ends, last = _ref.last;
exports.extend = extend;
@ -537,22 +537,18 @@
return ifn;
};
Call.prototype.compileNode = function(o) {
var _i, _j, _len, _len2, _ref2, _ref3, _ref4, _result, arg, args;
var _i, _len, _ref2, _ref3, _result, arg, args, code;
if ((_ref2 = this.variable) != null) {
_ref2.front = this.front;
}
_ref3 = this.args;
for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
arg = _ref3[_i];
if (arg instanceof Splat) {
return this.compileSplat(o);
}
if (code = Splat.compileSplattedArray(o, this.args, true)) {
return this.compileSplat(o, code);
}
args = ((function() {
_ref4 = this.args;
_ref3 = this.args;
_result = [];
for (_j = 0, _len2 = _ref4.length; _j < _len2; _j++) {
arg = _ref4[_j];
for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
arg = _ref3[_i];
_result.push(arg.compile(o, LEVEL_LIST));
}
return _result;
@ -562,11 +558,10 @@
Call.prototype.compileSuper = function(args, o) {
return "" + (this.superReference(o)) + ".call(this" + (args.length ? ', ' : '') + args + ")";
};
Call.prototype.compileSplat = function(o) {
var base, fun, idt, name, ref, splatargs;
splatargs = Splat.compileSplattedArray(this.args, o);
Call.prototype.compileSplat = function(o, splatArgs) {
var base, fun, idt, name, ref;
if (this.isSuper) {
return "" + (this.superReference(o)) + ".apply(this, " + splatargs + ")";
return "" + (this.superReference(o)) + ".apply(this, " + splatArgs + ")";
}
if (!this.isNew) {
base = new Value(this.variable);
@ -579,10 +574,10 @@
fun += name.compile(o);
}
}
return "" + fun + ".apply(" + ref + ", " + splatargs + ")";
return "" + fun + ".apply(" + ref + ", " + splatArgs + ")";
}
idt = this.idt(1);
return "(function(func, args, ctor) {\n" + idt + "ctor.prototype = func.prototype;\n" + idt + "var child = new ctor, result = func.apply(child, args);\n" + idt + "return typeof result === \"object\" ? result : child;\n" + this.tab + "})(" + (this.variable.compile(o, LEVEL_LIST)) + ", " + splatargs + ", function() {})";
return "(function(func, args, ctor) {\n" + idt + "ctor.prototype = func.prototype;\n" + idt + "var child = new ctor, result = func.apply(child, args);\n" + idt + "return typeof result === \"object\" ? result : child;\n" + this.tab + "})(" + (this.variable.compile(o, LEVEL_LIST)) + ", " + splatArgs + ", function() {})";
};
return Call;
})();
@ -746,19 +741,15 @@
__extends(Arr, Base);
Arr.prototype.children = ['objects'];
Arr.prototype.compileNode = function(o) {
var _i, _len, _len2, _ref2, _ref3, code, i, obj, objects;
var _len, _ref2, code, i, obj, objects;
o.indent = this.idt(1);
_ref2 = this.objects;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
obj = _ref2[_i];
if (obj instanceof Splat) {
return Splat.compileSplattedArray(this.objects, o);
}
if (code = Splat.compileSplattedArray(o, this.objects)) {
return code;
}
objects = [];
_ref3 = this.objects;
for (i = 0, _len2 = _ref3.length; i < _len2; i++) {
obj = _ref3[i];
_ref2 = this.objects;
for (i = 0, _len = _ref2.length; i < _len; i++) {
obj = _ref2[i];
code = obj.compile(o, LEVEL_LIST);
objects.push((obj instanceof Comment ? "\n" + code + "\n" + o.indent : i === this.objects.length - 1 ? code : code + ', '));
}
@ -1073,7 +1064,7 @@
exprs.unshift(splats);
}
if (exprs.length) {
(_this = this.body.expressions).splice.apply(_this, [0, 0].concat(exprs));
(_this = this.body.expressions).splice.apply(_this, [0, 0].concat(__slice.call(exprs)));
}
if (!(wasEmpty || this.noReturn)) {
this.body.makeReturn();
@ -1158,28 +1149,41 @@
Splat.prototype.compile = function(o) {
return this.index != null ? this.compileParam(o) : this.name.compile(o);
};
Splat.compileSplattedArray = function(list, o) {
var _len, arg, args, code, end, i, prev;
args = [];
end = -1;
for (i = 0, _len = list.length; i < _len; i++) {
arg = list[i];
code = arg.compile(o, LEVEL_LIST);
prev = args[end];
if (!(arg instanceof Splat)) {
if (prev && starts(prev, '[') && ends(prev, ']')) {
args[end] = "" + (prev.slice(0, -1)) + ", " + code + "]";
continue;
}
if (prev && starts(prev, '.concat([') && ends(prev, '])')) {
args[end] = "" + (prev.slice(0, -2)) + ", " + code + "])";
continue;
}
code = "[" + code + "]";
}
args[++end] = i === 0 ? code : ".concat(" + code + ")";
Splat.compileSplattedArray = function(o, list, apply) {
var _i, _len, _len2, _ref2, _result, args, base, code, i, index, node;
index = -1;
while ((node = list[++index]) && !(node instanceof Splat)) {
continue;
}
return args.join('');
if (index >= list.length) {
return '';
}
if (list.length === 1) {
code = list[0].compile(o, LEVEL_LIST);
if (apply) {
return code;
}
return "" + (utility('slice')) + ".call(" + code + ")";
}
args = list.slice(index);
for (i = 0, _len = args.length; i < _len; i++) {
node = args[i];
code = node.compile(o, LEVEL_LIST);
args[i] = node instanceof Splat ? "" + (utility('slice')) + ".call(" + code + ")" : "[" + code + "]";
}
if (index === 0) {
return args[0] + (".concat(" + (args.slice(1).join(', ')) + ")");
}
base = ((function() {
_ref2 = list.slice(0, index);
_result = [];
for (_i = 0, _len2 = _ref2.length; _i < _len2; _i++) {
node = _ref2[_i];
_result.push(node.compile(o, LEVEL_LIST));
}
return _result;
})());
return "[" + (base.join(', ')) + "].concat(" + (args.join(', ')) + ")";
};
return Splat;
}).call(this);

View File

@ -5,7 +5,7 @@
if (this[i] === item) return i;
}
return -1;
};
}, __slice = Array.prototype.slice;
exports.Rewriter = (function() {
function Rewriter() {
return this;
@ -72,7 +72,7 @@
}
} else if (prev && ((_ref = prev[0]) !== 'TERMINATOR' && _ref !== 'INDENT' && _ref !== 'OUTDENT')) {
if ((post != null ? post[0] : void 0) === 'TERMINATOR' && (after != null ? after[0] : void 0) === 'OUTDENT') {
tokens.splice.apply(tokens, [i + 2, 0].concat(tokens.splice(i, 2)));
tokens.splice.apply(tokens, [i + 2, 0].concat(__slice.call(tokens.splice(i, 2))));
if (tokens[i + 2][0] !== 'TERMINATOR') {
tokens.splice(i + 2, 0, ['TERMINATOR', '\n', prev[2]]);
}
@ -233,11 +233,11 @@
return 0;
}
if (tag === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') {
tokens.splice.apply(tokens, [i, 0].concat(this.indentation(token)));
tokens.splice.apply(tokens, [i, 0].concat(__slice.call(this.indentation(token))));
return 2;
}
if (tag === 'CATCH' && ((_ref = this.tag(i + 2)) === 'OUTDENT' || _ref === 'TERMINATOR' || _ref === 'FINALLY')) {
tokens.splice.apply(tokens, [i + 2, 0].concat(this.indentation(token)));
tokens.splice.apply(tokens, [i + 2, 0].concat(__slice.call(this.indentation(token))));
return 4;
}
if (__indexOf.call(SINGLE_LINERS, tag) >= 0 && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) {

View File

@ -78,7 +78,7 @@ exports.Base = class Base
# Construct a node that returns the current node's result.
# Note that this is overridden for smarter behavior for
# many statement nodes (eg If, For)...
makeReturn: ->
makeReturn: ->
new Return this
# Does this node, or any of its children, contain a node of a certain kind?
@ -130,7 +130,7 @@ exports.Base = class Base
return false if (arg = func child, arg) is false
child.traverseChildren crossScope, func, arg
invert: ->
invert: ->
new Op '!', this
unwrapAll: ->
@ -174,7 +174,7 @@ exports.Expressions = class Expressions extends Base
this
# Remove and return the last expression of this expression list.
pop: ->
pop: ->
@expressions.pop()
# Add an expression at the beginning of this expression list.
@ -184,11 +184,11 @@ exports.Expressions = class Expressions extends Base
# If this Expressions consists of just a single node, unwrap it by pulling
# it back out.
unwrap: ->
unwrap: ->
if @expressions.length is 1 then @expressions[0] else this
# Is this an empty block of code?
isEmpty: ->
isEmpty: ->
not @expressions.length
# An Expressions node does not return its entire body, rather it
@ -256,26 +256,26 @@ exports.Literal = class Literal extends Base
constructor: (@value) ->
makeReturn: ->
makeReturn: ->
if @isStatement() then this else super()
# Break and continue must be treated as pure statements -- they lose their
# meaning when wrapped in a closure.
isPureStatement: ->
isPureStatement: ->
@value in ['break', 'continue', 'debugger']
isAssignable: ->
isAssignable: ->
IDENTIFIER.test @value
isComplex: NO
assigns: (name) ->
assigns: (name) ->
name is @value
compile: ->
compile: ->
if @value.reserved then "\"#{@value}\"" else @value
toString: ->
toString: ->
' "' + @value + '"'
#### Return
@ -321,7 +321,7 @@ exports.Value = class Value extends Base
@properties.push prop
this
hasProperties: ->
hasProperties: ->
!!@properties.length
# Some boolean checks for the benefit of other nodes.
@ -338,12 +338,12 @@ exports.Value = class Value extends Base
isStatement : (o) -> not @properties.length and @base.isStatement o
assigns : (name) -> not @properties.length and @base.assigns name
makeReturn: ->
makeReturn: ->
if @properties.length then super() else @base.makeReturn()
# The value can be unwrapped as its inner node, if there are no attached
# properties.
unwrap: ->
unwrap: ->
if @properties.length then this else @base
# A reference has base part (`this` value) and name part.
@ -405,7 +405,7 @@ exports.Comment = class Comment extends Base
makeReturn: THIS
compileNode: (o) ->
compileNode: (o) ->
@tab + '/*' + multident(@comment, @tab) + '*/'
#### Call
@ -472,8 +472,8 @@ exports.Call = class Call extends Base
# Compile a vanilla function call.
compileNode: (o) ->
@variable?.front = @front
for arg in @args when arg instanceof Splat
return @compileSplat o
if code = Splat.compileSplattedArray o, @args, true
return @compileSplat o, code
args = (arg.compile o, LEVEL_LIST for arg in @args).join ', '
if @isSuper
@compileSuper args, o
@ -489,9 +489,8 @@ exports.Call = class Call extends Base
# `.apply()` call to allow an array of arguments to be passed.
# If it's a constructor, then things get real tricky. We have to inject an
# inner constructor in order to be able to pass the varargs.
compileSplat: (o) ->
splatargs = Splat.compileSplattedArray @args, o
return "#{ @superReference o }.apply(this, #{splatargs})" if @isSuper
compileSplat: (o, splatArgs) ->
return "#{ @superReference o }.apply(this, #{splatArgs})" if @isSuper
unless @isNew
base = new Value @variable
if (name = base.properties.pop()) and base.isComplex()
@ -500,14 +499,14 @@ exports.Call = class Call extends Base
else
fun = ref = base.compile o, LEVEL_ACCESS
fun += name.compile o if name
return "#{fun}.apply(#{ref}, #{splatargs})"
return "#{fun}.apply(#{ref}, #{splatArgs})"
idt = @idt 1
"""
(function(func, args, ctor) {
#{idt}ctor.prototype = func.prototype;
#{idt}var child = new ctor, result = func.apply(child, args);
#{idt}return typeof result === "object" ? result : child;
#{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatargs}, function() {})
#{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function() {})
"""
#### Extends
@ -555,7 +554,7 @@ exports.Index = class Index extends Base
compile: (o) ->
(if @proto then '.prototype' else '') + "[#{ @index.compile o, LEVEL_PAREN }]"
isComplex: ->
isComplex: ->
@index.isComplex()
#### Obj
@ -631,8 +630,7 @@ exports.Arr = class Arr extends Base
compileNode: (o) ->
o.indent = @idt 1
for obj in @objects when obj instanceof Splat
return Splat.compileSplattedArray @objects, o
return code if code = Splat.compileSplattedArray o, @objects
objects = []
for obj, i in @objects
code = obj.compile o, LEVEL_LIST
@ -750,7 +748,7 @@ exports.Assign = class Assign extends Base
assigns: (name) ->
@[if @context is 'object' then 'value' else 'variable'].assigns name
unfoldSoak: (o) ->
unfoldSoak: (o) ->
If.unfoldSoak o, this, 'variable'
# Compile an assignment, delegating to `compilePatternMatch` or
@ -931,7 +929,7 @@ exports.Param = class Param extends Base
constructor: (@name, @value, @splat) ->
compile: (o) ->
compile: (o) ->
@name.compile o, LEVEL_LIST
asReference: (o) ->
@ -941,7 +939,7 @@ exports.Param = class Param extends Base
node = new Splat node if @splat
@reference = node
isComplex: ->
isComplex: ->
@name.isComplex()
#### Splat
@ -957,30 +955,31 @@ exports.Splat = class Splat extends Base
constructor: (name) ->
@name = if name.compile then name else new Literal name
assigns: (name) ->
assigns: (name) ->
@name.assigns name
compile: (o) ->
compile: (o) ->
if @index? then @compileParam o else @name.compile o
# Utility function that converts arbitrary number of elements, mixed with
# splats, to a proper array
@compileSplattedArray: (list, o) ->
args = []
end = -1
for arg, i in list
code = arg.compile o, LEVEL_LIST
prev = args[end]
if arg not instanceof Splat
if prev and starts(prev, '[') and ends(prev, ']')
args[end] = "#{prev.slice 0, -1}, #{code}]"
continue
if prev and starts(prev, '.concat([') and ends(prev, '])')
args[end] = "#{prev.slice 0, -2}, #{code}])"
continue
code = "[#{code}]"
args[++end] = if i is 0 then code else ".concat(#{code})"
args.join ''
# splats, to a proper array.
@compileSplattedArray: (o, list, apply) ->
index = -1
continue while (node = list[++index]) and node not instanceof Splat
return '' if index >= list.length
if list.length is 1
code = list[0].compile o, LEVEL_LIST
return code if apply
return "#{ utility 'slice' }.call(#{code})"
args = list.slice index
for node, i in args
code = node.compile o, LEVEL_LIST
args[i] = if node instanceof Splat
then "#{ utility 'slice' }.call(#{code})"
else "[#{code}]"
return args[0] + ".concat(#{ args.slice(1).join ', ' })" if index is 0
base = (node.compile o, LEVEL_LIST for node in list.slice 0, index)
"[#{ base.join ', ' }].concat(#{ args.join ', ' })"
#### While
@ -1056,12 +1055,12 @@ exports.Op = class Op extends Base
@second = second
@flip = !!flip
isUnary: ->
isUnary: ->
not @second
# Am I capable of
# [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin)?
isChainable: ->
isChainable: ->
@operator in ['<', '>', '>=', '<=', '===', '!==']
invert: ->
@ -1113,7 +1112,7 @@ exports.Op = class Op extends Base
parts.reverse() if @flip
parts.join ''
toString: (idt) ->
toString: (idt) ->
super idt, @constructor.name + ' ' + @operator
#### In
@ -1197,7 +1196,7 @@ exports.Throw = class Throw extends Base
# A **Throw** is already a return, of sorts...
makeReturn: THIS
compileNode: (o) ->
compileNode: (o) ->
@tab + "throw #{ @expression.compile o };"
#### Existence
@ -1447,7 +1446,7 @@ exports.If = class If extends Base
code = "#{cond} ? #{body} : #{alt}"
if o.level >= LEVEL_COND then "(#{code})" else code
unfoldSoak: ->
unfoldSoak: ->
@soak and this
# Unfold a node's child if soak, then tuck the node under created `If`
@ -1568,5 +1567,5 @@ utility = (name) ->
Scope.root.assign ref, UTILITIES[name]
ref
multident = (code, tab) ->
multident = (code, tab) ->
code.replace /\n/g, '$&' + tab