mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
#733: streamlined soak compilations and improved reference cachings
This commit is contained in:
parent
54f162e523
commit
341f511bbd
9 changed files with 459 additions and 409 deletions
|
@ -206,7 +206,7 @@
|
|||
var o;
|
||||
optionParser = new optparse.OptionParser(SWITCHES, BANNER);
|
||||
o = (opts = optionParser.parse(process.argv.slice(2, process.argv.length)));
|
||||
o.compile || (o.compile = (!!o.output));
|
||||
o.compile || (o.compile = !!o.output);
|
||||
o.run = !(o.compile || o.print || o.lint);
|
||||
o.print = !!(o.print || (o.eval || o.stdio && o.compile));
|
||||
return (sources = o.arguments);
|
||||
|
|
|
@ -294,7 +294,7 @@
|
|||
}
|
||||
this.i += value.length;
|
||||
prev = last(this.tokens);
|
||||
spaced = prev == null ? undefined : prev.spaced;
|
||||
spaced = ((prev != null) ? prev.spaced : null);
|
||||
tag = value;
|
||||
if (value === '=') {
|
||||
if (include(JS_FORBIDDEN, val = this.value())) {
|
||||
|
|
424
lib/nodes.js
424
lib/nodes.js
|
@ -26,7 +26,7 @@
|
|||
};
|
||||
BaseNode.prototype.compile = function(o) {
|
||||
var closure, code, top;
|
||||
this.options = merge(o || {});
|
||||
this.options = o ? merge(o) : {};
|
||||
this.tab = o.indent;
|
||||
if (!(this instanceof AccessorNode || this instanceof IndexNode)) {
|
||||
del(this.options, 'chainRoot');
|
||||
|
@ -48,21 +48,22 @@
|
|||
return ClosureNode.wrap(this).compile(o);
|
||||
};
|
||||
BaseNode.prototype.compileReference = function(o, options) {
|
||||
var compiled, pair, reference;
|
||||
options || (options = {});
|
||||
var _len, _ref2, compiled, i, node, pair, reference;
|
||||
pair = (function() {
|
||||
if (!this.isComplex()) {
|
||||
if (!(this.isComplex())) {
|
||||
return [this, this];
|
||||
} else if (this instanceof ValueNode && options.assignment) {
|
||||
return this.cacheIndexes(o);
|
||||
} else {
|
||||
reference = literal(o.scope.freeVariable('ref'));
|
||||
compiled = new AssignNode(reference, this);
|
||||
return [compiled, reference];
|
||||
}
|
||||
}).call(this);
|
||||
if (options.precompile) {
|
||||
return [pair[0].compile(o), pair[1].compile(o)];
|
||||
if (((options != null) ? options.precompile : null)) {
|
||||
_ref2 = pair;
|
||||
for (i = 0, _len = _ref2.length; i < _len; i++) {
|
||||
node = _ref2[i];
|
||||
(pair[i] = node.compile(o));
|
||||
}
|
||||
}
|
||||
return pair;
|
||||
};
|
||||
|
@ -96,7 +97,7 @@
|
|||
};
|
||||
BaseNode.prototype.containsPureStatement = function() {
|
||||
return this.isPureStatement() || this.contains(function(n) {
|
||||
return n.isPureStatement && n.isPureStatement();
|
||||
return (typeof n.isPureStatement !== "function" ? undefined : n.isPureStatement());
|
||||
});
|
||||
};
|
||||
BaseNode.prototype.traverse = function(block) {
|
||||
|
@ -146,8 +147,10 @@
|
|||
};
|
||||
BaseNode.prototype.traverseChildren = function(crossScope, func) {
|
||||
return this.eachChild(function(child) {
|
||||
func.apply(this, arguments);
|
||||
return child instanceof BaseNode ? child.traverseChildren(crossScope, func) : null;
|
||||
if (func(child) === false) {
|
||||
return false;
|
||||
}
|
||||
return child instanceof BaseNode && (crossScope || !(child instanceof CodeNode)) ? child.traverseChildren(crossScope, func) : null;
|
||||
});
|
||||
};
|
||||
BaseNode.prototype["class"] = 'BaseNode';
|
||||
|
@ -319,10 +322,10 @@
|
|||
return !!this.properties.length;
|
||||
};
|
||||
ValueNode.prototype.isArray = function() {
|
||||
return this.base instanceof ArrayNode && !this.hasProperties();
|
||||
return this.base instanceof ArrayNode && !this.properties.length;
|
||||
};
|
||||
ValueNode.prototype.isObject = function() {
|
||||
return this.base instanceof ObjectNode && !this.hasProperties();
|
||||
return this.base instanceof ObjectNode && !this.properties.length;
|
||||
};
|
||||
ValueNode.prototype.isSplice = function() {
|
||||
return last(this.properties) instanceof SliceNode;
|
||||
|
@ -331,7 +334,7 @@
|
|||
return this.base.isComplex() || this.hasProperties();
|
||||
};
|
||||
ValueNode.prototype.makeReturn = function() {
|
||||
return this.hasProperties() ? ValueNode.__super__.makeReturn.call(this) : this.base.makeReturn();
|
||||
return this.properties.length ? ValueNode.__super__.makeReturn.call(this) : this.base.makeReturn();
|
||||
};
|
||||
ValueNode.prototype.unwrap = function() {
|
||||
return this.properties.length ? this : this.base;
|
||||
|
@ -342,76 +345,95 @@
|
|||
ValueNode.prototype.isNumber = function() {
|
||||
return this.base instanceof LiteralNode && NUMBER.test(this.base.value);
|
||||
};
|
||||
ValueNode.prototype.cacheIndexes = function(o) {
|
||||
var _len, _ref2, _ref3, copy, first, i, index, indexVar, prop;
|
||||
copy = new ValueNode(this.base, this.properties.slice(0));
|
||||
if (this.base.isComplex()) {
|
||||
_ref2 = this.base.compileReference(o), this.base = _ref2[0], copy.base = _ref2[1];
|
||||
ValueNode.prototype.cacheReference = function(o) {
|
||||
var base, bref, name, nref;
|
||||
name = last(this.properties);
|
||||
if (!this.base.isComplex() && this.properties.length < 2 && !((name != null) ? name.isComplex() : null)) {
|
||||
return [this, this];
|
||||
}
|
||||
_ref2 = copy.properties;
|
||||
for (i = 0, _len = _ref2.length; i < _len; i++) {
|
||||
prop = _ref2[i];
|
||||
if (prop instanceof IndexNode && prop.index.isComplex()) {
|
||||
_ref3 = prop.index.compileReference(o), index = _ref3[0], indexVar = _ref3[1];
|
||||
this.properties[i] = (first = new IndexNode(index));
|
||||
copy.properties[i] = new IndexNode(indexVar);
|
||||
if (prop.soakNode) {
|
||||
first.soakNode = true;
|
||||
}
|
||||
}
|
||||
base = new ValueNode(this.base, this.properties.slice(0, -1));
|
||||
if (base.isComplex()) {
|
||||
bref = literal(o.scope.freeVariable('base'));
|
||||
base = new ValueNode(new ParentheticalNode(new AssignNode(bref, base)));
|
||||
}
|
||||
return [this, copy];
|
||||
if (!(name)) {
|
||||
return [base, bref];
|
||||
}
|
||||
if (name.isComplex()) {
|
||||
nref = literal(o.scope.freeVariable('name'));
|
||||
name = new IndexNode(new AssignNode(nref, name.index));
|
||||
nref = new IndexNode(nref);
|
||||
}
|
||||
return [base.push(name), new ValueNode(bref || base.base, [nref || name])];
|
||||
};
|
||||
ValueNode.prototype.compile = function(o) {
|
||||
return !o.top || this.properties.length ? ValueNode.__super__.compile.call(this, o) : this.base.compile(o);
|
||||
};
|
||||
ValueNode.prototype.compileNode = function(o) {
|
||||
var _i, _len, _ref2, baseline, complete, copy, hasSoak, i, me, only, op, part, prop, props, temp;
|
||||
only = del(o, 'onlyFirst');
|
||||
op = this.tags.operation;
|
||||
props = only ? this.properties.slice(0, -1) : this.properties;
|
||||
var _i, _len, _ref2, code, ex, prop, props;
|
||||
if (ex = this.unfoldSoak(o)) {
|
||||
return ex.compile(o);
|
||||
}
|
||||
props = this.properties;
|
||||
o.chainRoot || (o.chainRoot = this);
|
||||
_ref2 = props;
|
||||
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
|
||||
prop = _ref2[_i];
|
||||
if (prop.soakNode) {
|
||||
hasSoak = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasSoak && this.isComplex()) {
|
||||
_ref2 = this.cacheIndexes(o), me = _ref2[0], copy = _ref2[1];
|
||||
}
|
||||
if (this.parenthetical && !props.length) {
|
||||
this.base.parenthetical = true;
|
||||
}
|
||||
baseline = this.base.compile(o);
|
||||
if (this.hasProperties() && (this.base instanceof ObjectNode || this.isNumber())) {
|
||||
baseline = ("(" + (baseline) + ")");
|
||||
code = this.base.compile(o);
|
||||
if (props[0] instanceof AccessorNode && this.isNumber() || o.top && this.base instanceof ObjectNode) {
|
||||
code = ("(" + (code) + ")");
|
||||
}
|
||||
complete = (this.last = baseline);
|
||||
_ref2 = props;
|
||||
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
|
||||
prop = _ref2[_i];
|
||||
(code += prop.compile(o));
|
||||
}
|
||||
return code;
|
||||
};
|
||||
ValueNode.prototype.unfoldSoak = function(o) {
|
||||
var _len, _ref2, fst, i, ifn, prop, ref, snd;
|
||||
if (this.base.soakNode) {
|
||||
Array.prototype.push.apply(this.base.body.properties, this.properties);
|
||||
return this.base;
|
||||
}
|
||||
_ref2 = this.properties;
|
||||
for (i = 0, _len = _ref2.length; i < _len; i++) {
|
||||
prop = _ref2[i];
|
||||
this.source = baseline;
|
||||
if (prop.soakNode) {
|
||||
if (i === 0 && this.base.isComplex()) {
|
||||
temp = o.scope.freeVariable('ref');
|
||||
complete = ("(" + (baseline = temp) + " = (" + (complete) + "))");
|
||||
prop.soakNode = false;
|
||||
fst = new ValueNode(this.base, this.properties.slice(0, i));
|
||||
snd = new ValueNode(this.base, this.properties.slice(i));
|
||||
if (fst.isComplex()) {
|
||||
ref = literal(o.scope.freeVariable('ref'));
|
||||
fst = new ParentheticalNode(new AssignNode(ref, fst));
|
||||
snd.base = ref;
|
||||
}
|
||||
complete = i === 0 && !o.scope.check(complete) ? ("(typeof " + (complete) + " === \"undefined\" || " + (baseline) + " === null)") : ("" + (complete) + " == null");
|
||||
complete += ' ? undefined : ' + (baseline += prop.compile(o));
|
||||
} else {
|
||||
part = prop.compile(o);
|
||||
baseline += (hasSoak && prop.isComplex() ? copy.properties[i].compile(o) : part);
|
||||
complete += part;
|
||||
this.last = part;
|
||||
ifn = new IfNode(new ExistenceNode(fst), snd, {
|
||||
operation: true
|
||||
});
|
||||
ifn.soakNode = true;
|
||||
return ifn;
|
||||
}
|
||||
}
|
||||
return op && this.wrapped ? ("(" + (complete) + ")") : complete;
|
||||
return null;
|
||||
};
|
||||
ValueNode.unfoldSoak = function(o, parent, name) {
|
||||
var ifnode, node;
|
||||
node = parent[name];
|
||||
if (node instanceof IfNode && node.soakNode) {
|
||||
ifnode = node;
|
||||
} else if (node instanceof ValueNode) {
|
||||
ifnode = node.unfoldSoak(o);
|
||||
}
|
||||
if (!(ifnode)) {
|
||||
return null;
|
||||
}
|
||||
parent[name] = ifnode.body;
|
||||
ifnode.body = new ValueNode(parent);
|
||||
return ifnode;
|
||||
};
|
||||
return ValueNode;
|
||||
})();
|
||||
}).call(this);
|
||||
exports.CommentNode = (function() {
|
||||
CommentNode = function(_arg) {
|
||||
this.comment = _arg;
|
||||
|
@ -436,15 +458,14 @@
|
|||
this.isSuper = variable === 'super';
|
||||
this.variable = this.isSuper ? null : variable;
|
||||
this.args || (this.args = []);
|
||||
this.first = (this.last = '');
|
||||
this.compileSplatArguments = function(o) {
|
||||
return SplatNode.compileSplattedArray.call(this, this.args, o);
|
||||
};
|
||||
return this;
|
||||
};
|
||||
__extends(CallNode, BaseNode);
|
||||
CallNode.prototype["class"] = 'CallNode';
|
||||
CallNode.prototype.children = ['variable', 'args'];
|
||||
CallNode.prototype.compileSplatArguments = function(o) {
|
||||
return SplatNode.compileSplattedArray(this.args, o);
|
||||
};
|
||||
CallNode.prototype.newInstance = function() {
|
||||
this.isNew = true;
|
||||
return this;
|
||||
|
@ -453,97 +474,128 @@
|
|||
return this.isNew ? 'new ' : '';
|
||||
};
|
||||
CallNode.prototype.superReference = function(o) {
|
||||
var meth, methname;
|
||||
if (!(o.scope.method)) {
|
||||
throw new Error("cannot call super outside of a function");
|
||||
var method, name;
|
||||
method = o.scope.method;
|
||||
if (!(method)) {
|
||||
throw Error("cannot call super outside of a function");
|
||||
}
|
||||
methname = o.scope.method.name;
|
||||
return (meth = (function() {
|
||||
if (o.scope.method.proto) {
|
||||
return "" + (o.scope.method.proto) + ".__super__." + (methname);
|
||||
} else if (methname) {
|
||||
return "" + (methname) + ".__super__.constructor";
|
||||
} else {
|
||||
throw new Error("cannot call super on an anonymous function.");
|
||||
name = method.name;
|
||||
if (!(name)) {
|
||||
throw Error("cannot call super on an anonymous function.");
|
||||
}
|
||||
return method.klass ? ("" + (method.klass) + ".__super__." + (name)) : ("" + (name) + ".__super__.constructor");
|
||||
};
|
||||
CallNode.prototype.unfoldSoak = function(o) {
|
||||
var _i, _len, _ref2, call, list, node;
|
||||
call = this;
|
||||
list = [];
|
||||
while (true) {
|
||||
if (call.variable instanceof CallNode) {
|
||||
list.push(call);
|
||||
call = call.variable;
|
||||
continue;
|
||||
}
|
||||
})());
|
||||
if (!(call.variable instanceof ValueNode)) {
|
||||
break;
|
||||
}
|
||||
list.push(call);
|
||||
if (!((call = call.variable.base) instanceof CallNode)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ref2 = list.reverse();
|
||||
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
|
||||
call = _ref2[_i];
|
||||
if (node) {
|
||||
if (call.variable instanceof CallNode) {
|
||||
call.variable = node;
|
||||
} else {
|
||||
call.variable.base = node;
|
||||
}
|
||||
}
|
||||
node = ValueNode.unfoldSoak(o, call, 'variable');
|
||||
}
|
||||
return node;
|
||||
};
|
||||
CallNode.prototype.compileNode = function(o) {
|
||||
var _i, _len, _ref2, _result, arg, args, code, first, meth, methodAccessor, op;
|
||||
if (!(o.chainRoot)) {
|
||||
o.chainRoot = this;
|
||||
var _i, _len, _ref2, _result, arg, args, left, node, rite, val;
|
||||
if (node = this.unfoldSoak(o)) {
|
||||
return node.compile(o);
|
||||
}
|
||||
op = this.tags.operation;
|
||||
o.chainRoot || (o.chainRoot = this);
|
||||
if (this.exist) {
|
||||
if (this.variable instanceof ValueNode && last(this.variable.properties) instanceof AccessorNode) {
|
||||
methodAccessor = this.variable.properties.pop();
|
||||
_ref2 = this.variable.compileReference(o), first = _ref2[0], meth = _ref2[1];
|
||||
this.first = new ValueNode(first, [methodAccessor]).compile(o);
|
||||
this.meth = new ValueNode(meth, [methodAccessor]).compile(o);
|
||||
if (val = this.variable) {
|
||||
if (!(val instanceof ValueNode)) {
|
||||
val = new ValueNode(val);
|
||||
}
|
||||
_ref2 = val.cacheReference(o), left = _ref2[0], rite = _ref2[1];
|
||||
rite = new CallNode(rite, this.args);
|
||||
} else {
|
||||
_ref2 = this.variable.compileReference(o, {
|
||||
precompile: true
|
||||
}), this.first = _ref2[0], this.meth = _ref2[1];
|
||||
left = literal(this.superReference(o));
|
||||
rite = new CallNode(new ValueNode(left), this.args);
|
||||
rite.isNew = this.isNew;
|
||||
}
|
||||
this.first = ("(typeof " + (this.first) + " === \"function\" ? ");
|
||||
this.last = " : undefined)";
|
||||
} else if (this.variable) {
|
||||
this.meth = this.variable.compile(o);
|
||||
left = ("typeof " + (left.compile(o)) + " !== \"function\"");
|
||||
rite = rite.compile(o);
|
||||
return ("(" + (left) + " ? undefined : " + (rite) + ")");
|
||||
}
|
||||
_ref2 = this.args;
|
||||
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
|
||||
arg = _ref2[_i];
|
||||
if (arg instanceof SplatNode) {
|
||||
code = this.compileSplat(o);
|
||||
return this.compileSplat(o);
|
||||
}
|
||||
}
|
||||
if (!code) {
|
||||
args = (function() {
|
||||
_result = []; _ref2 = this.args;
|
||||
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
|
||||
arg = _ref2[_i];
|
||||
_result.push((function() {
|
||||
arg.parenthetical = true;
|
||||
return arg.compile(o);
|
||||
})());
|
||||
}
|
||||
return _result;
|
||||
}).call(this);
|
||||
code = this.isSuper ? this.compileSuper(args.join(', '), o) : ("" + (this.first) + (this.prefix()) + (this.meth) + "(" + (args.join(', ')) + ")" + (this.last));
|
||||
}
|
||||
return op && this.variable && this.variable.wrapped ? ("(" + (code) + ")") : code;
|
||||
args = (function() {
|
||||
_result = []; _ref2 = this.args;
|
||||
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
|
||||
arg = _ref2[_i];
|
||||
_result.push((arg.parenthetical = true) && arg.compile(o));
|
||||
}
|
||||
return _result;
|
||||
}).call(this).join(', ');
|
||||
return this.isSuper ? this.compileSuper(args, o) : ("" + (this.prefix()) + (this.variable.compile(o)) + "(" + (args) + ")");
|
||||
};
|
||||
CallNode.prototype.compileSuper = function(args, o) {
|
||||
return "" + (this.superReference(o)) + ".call(this" + (args.length ? ', ' : '') + (args) + ")";
|
||||
};
|
||||
CallNode.prototype.compileSplat = function(o) {
|
||||
var _i, _len, _ref2, a, b, c, mentionsArgs, meth, obj, temp;
|
||||
meth = this.meth || this.superReference(o);
|
||||
obj = this.variable && this.variable.source || 'this';
|
||||
if (!(IDENTIFIER.test(obj) || NUMBER.test(obj))) {
|
||||
temp = o.scope.freeVariable('ref');
|
||||
obj = temp;
|
||||
meth = ("(" + (temp) + " = " + (this.variable.source) + ")" + (this.variable.last));
|
||||
var _i, _len, _ref2, a, arg, argvar, b, base, c, call, fun, idt, name, ref, splatargs;
|
||||
splatargs = this.compileSplatArguments(o);
|
||||
if (this.isSuper) {
|
||||
return ("" + (this.superReference(o)) + ".apply(this, " + (splatargs) + ")");
|
||||
}
|
||||
if (this.isNew) {
|
||||
mentionsArgs = false;
|
||||
_ref2 = this.args;
|
||||
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
|
||||
(function() {
|
||||
var arg = _ref2[_i];
|
||||
return arg.contains(function(n) {
|
||||
return mentionsArgs || (mentionsArgs = (n instanceof LiteralNode && (n.value === 'arguments')));
|
||||
});
|
||||
})();
|
||||
if (!(this.isNew)) {
|
||||
if (!((base = this.variable) instanceof ValueNode)) {
|
||||
base = new ValueNode(base);
|
||||
}
|
||||
utility('extends');
|
||||
a = o.scope.freeVariable('ctor');
|
||||
b = o.scope.freeVariable('ref');
|
||||
c = o.scope.freeVariable('result');
|
||||
return "" + (this.first) + "(function() {\n" + (this.idt(1)) + "var ctor = function(){};\n" + (this.idt(1)) + "__extends(ctor, " + (a) + " = " + (meth) + ");\n" + (this.idt(1)) + "return typeof (" + (c) + " = " + (a) + ".apply(" + (b) + " = new ctor, " + (this.compileSplatArguments(o)) + ")) === \"object\" ? " + (c) + " : " + (b) + ";\n" + (this.tab) + "})." + (mentionsArgs ? 'apply(this, arguments)' : 'call(this)') + (this.last);
|
||||
} else {
|
||||
return "" + (this.first) + (meth) + ".apply(" + (obj) + ", " + (this.compileSplatArguments(o)) + ")" + (this.last);
|
||||
if ((name = base.properties.pop()) && base.isComplex()) {
|
||||
ref = o.scope.freeVariable('this');
|
||||
fun = ("(" + (ref) + " = " + (base.compile(o)) + ")" + (name.compile(o)));
|
||||
} else {
|
||||
fun = (ref = base.compile(o));
|
||||
if (name) {
|
||||
fun += name.compile(o);
|
||||
}
|
||||
}
|
||||
return ("" + (fun) + ".apply(" + (ref) + ", " + (splatargs) + ")");
|
||||
}
|
||||
call = 'call(this)';
|
||||
argvar = function(n) {
|
||||
return n instanceof LiteralNode && n.value === 'arguments';
|
||||
};
|
||||
_ref2 = this.args;
|
||||
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
|
||||
arg = _ref2[_i];
|
||||
if (arg.contains(argvar)) {
|
||||
call = 'apply(this, arguments)';
|
||||
break;
|
||||
}
|
||||
}
|
||||
a = o.scope.freeVariable('ctor');
|
||||
b = o.scope.freeVariable('ref');
|
||||
c = o.scope.freeVariable('result');
|
||||
return "(function() {\n" + (idt = this.idt(1)) + "var ctor = function() {};\n" + (idt) + (utility('extends')) + "(ctor, " + (a) + " = " + (this.variable.compile(o)) + ");\n" + (idt) + "return typeof (" + (c) + " = " + (a) + ".apply(" + (b) + " = new ctor, " + (splatargs) + ")) === \"object\" ? " + (c) + " : " + (b) + ";\n" + (this.tab) + "})." + (call);
|
||||
};
|
||||
return CallNode;
|
||||
})();
|
||||
|
@ -576,9 +628,9 @@
|
|||
AccessorNode.prototype["class"] = 'AccessorNode';
|
||||
AccessorNode.prototype.children = ['name'];
|
||||
AccessorNode.prototype.compileNode = function(o) {
|
||||
var name, namePart;
|
||||
var _base, name, namePart;
|
||||
name = this.name.compile(o);
|
||||
o.chainRoot.wrapped || (o.chainRoot.wrapped = this.soakNode);
|
||||
(_base = o.chainRoot).wrapped || (_base.wrapped = this.soakNode);
|
||||
namePart = name.match(IS_STRING) ? ("[" + (name) + "]") : ("." + (name));
|
||||
return this.prototype + namePart;
|
||||
};
|
||||
|
@ -595,8 +647,8 @@
|
|||
IndexNode.prototype["class"] = 'IndexNode';
|
||||
IndexNode.prototype.children = ['index'];
|
||||
IndexNode.prototype.compileNode = function(o) {
|
||||
var idx, prefix;
|
||||
o.chainRoot.wrapped || (o.chainRoot.wrapped = this.soakNode);
|
||||
var _base, idx, prefix;
|
||||
(_base = o.chainRoot).wrapped || (_base.wrapped = this.soakNode);
|
||||
idx = this.index.compile(o);
|
||||
prefix = this.proto ? '.prototype' : '';
|
||||
return "" + (prefix) + "[" + (idx) + "]";
|
||||
|
@ -774,14 +826,14 @@
|
|||
this.objects = _arg;
|
||||
ArrayNode.__super__.constructor.call(this);
|
||||
this.objects || (this.objects = []);
|
||||
this.compileSplatLiteral = function(o) {
|
||||
return SplatNode.compileSplattedArray.call(this, this.objects, o);
|
||||
};
|
||||
return this;
|
||||
};
|
||||
__extends(ArrayNode, BaseNode);
|
||||
ArrayNode.prototype["class"] = 'ArrayNode';
|
||||
ArrayNode.prototype.children = ['objects'];
|
||||
ArrayNode.prototype.compileSplatLiteral = function(o) {
|
||||
return SplatNode.compileSplattedArray(this.objects, o);
|
||||
};
|
||||
ArrayNode.prototype.compileNode = function(o) {
|
||||
var _len, _ref2, code, i, obj, objects;
|
||||
o.indent = this.idt(1);
|
||||
|
@ -898,8 +950,7 @@
|
|||
return this;
|
||||
};
|
||||
__extends(AssignNode, BaseNode);
|
||||
AssignNode.prototype.PROTO_ASSIGN = /^(\S+)\.prototype/;
|
||||
AssignNode.prototype.LEADING_DOT = /^\.(?:prototype\.)?/;
|
||||
AssignNode.prototype.METHOD_DEF = /^(?:(\S+)\.prototype\.)?([$A-Za-z_][$\w]*)$/;
|
||||
AssignNode.prototype["class"] = 'AssignNode';
|
||||
AssignNode.prototype.children = ['variable', 'value'];
|
||||
AssignNode.prototype.topSensitive = YES;
|
||||
|
@ -907,7 +958,7 @@
|
|||
return this.variable instanceof ValueNode;
|
||||
};
|
||||
AssignNode.prototype.compileNode = function(o) {
|
||||
var end, isValue, match, name, proto, stmt, top, val;
|
||||
var isValue, match, name, node, stmt, top, val;
|
||||
if (isValue = this.isValue()) {
|
||||
if (this.variable.isArray() || this.variable.isObject()) {
|
||||
return this.compilePatternMatch(o);
|
||||
|
@ -915,20 +966,16 @@
|
|||
if (this.variable.isSplice()) {
|
||||
return this.compileSplice(o);
|
||||
}
|
||||
if (node = ValueNode.unfoldSoak(o, this, 'variable')) {
|
||||
return node.compile(o);
|
||||
}
|
||||
}
|
||||
top = del(o, 'top');
|
||||
stmt = del(o, 'asStatement');
|
||||
name = this.variable.compile(o);
|
||||
end = isValue ? this.variable.last.replace(this.LEADING_DOT, '') : name;
|
||||
match = name.match(this.PROTO_ASSIGN);
|
||||
proto = match && match[1];
|
||||
if (this.value instanceof CodeNode) {
|
||||
if (IDENTIFIER.test(end)) {
|
||||
this.value.name = end;
|
||||
}
|
||||
if (proto) {
|
||||
this.value.proto = proto;
|
||||
}
|
||||
if (this.value instanceof CodeNode && (match = this.METHOD_DEF.exec(name))) {
|
||||
this.value.name = match[2];
|
||||
this.value.klass = match[1];
|
||||
}
|
||||
val = this.value.compile(o);
|
||||
if (this.context === 'object') {
|
||||
|
@ -1005,17 +1052,15 @@
|
|||
return top || this.parenthetical ? code : ("(" + (code) + ")");
|
||||
};
|
||||
AssignNode.prototype.compileSplice = function(o) {
|
||||
var from, l, name, plus, range, to, val;
|
||||
name = this.variable.compile(merge(o, {
|
||||
onlyFirst: true
|
||||
}));
|
||||
l = this.variable.properties.length;
|
||||
range = this.variable.properties[l - 1].range;
|
||||
var from, name, plus, range, ref, to, val;
|
||||
range = this.variable.properties.pop().range;
|
||||
name = this.variable.compile(o);
|
||||
plus = range.exclusive ? '' : ' + 1';
|
||||
from = range.from ? range.from.compile(o) : '0';
|
||||
to = range.to ? range.to.compile(o) + ' - ' + from + plus : ("" + (name) + ".length");
|
||||
ref = o.scope.freeVariable('ref');
|
||||
val = this.value.compile(o);
|
||||
return "[].splice.apply(" + (name) + ", [" + (from) + ", " + (to) + "].concat(" + (val) + "))";
|
||||
return "([].splice.apply(" + (name) + ", [" + (from) + ", " + (to) + "].concat(" + (ref) + " = " + (val) + ")), " + (ref) + ")";
|
||||
};
|
||||
return AssignNode;
|
||||
})();
|
||||
|
@ -1025,7 +1070,7 @@
|
|||
this.params = _arg;
|
||||
CodeNode.__super__.constructor.call(this);
|
||||
this.params || (this.params = []);
|
||||
this.body || (this.body = (new Expressions));
|
||||
this.body || (this.body = new Expressions);
|
||||
this.bound = tag === 'boundfunc';
|
||||
if (this.bound) {
|
||||
this.context = 'this';
|
||||
|
@ -1143,8 +1188,7 @@
|
|||
SplatNode.prototype["class"] = 'SplatNode';
|
||||
SplatNode.prototype.children = ['name'];
|
||||
SplatNode.prototype.compileNode = function(o) {
|
||||
var _ref2;
|
||||
return (typeof (_ref2 = this.index) !== "undefined" && _ref2 !== null) ? this.compileParam(o) : this.name.compile(o);
|
||||
return (this.index != null) ? this.compileParam(o) : this.name.compile(o);
|
||||
};
|
||||
SplatNode.prototype.compileParam = function(o) {
|
||||
var _len, _ref2, assign, end, idx, len, name, pos, trailing, variadic;
|
||||
|
@ -1205,14 +1249,14 @@
|
|||
exports.WhileNode = (function() {
|
||||
WhileNode = function(condition, opts) {
|
||||
WhileNode.__super__.constructor.call(this);
|
||||
if (opts == null ? undefined : opts.invert) {
|
||||
if (((opts != null) ? opts.invert : null)) {
|
||||
if (condition instanceof OpNode) {
|
||||
condition = new ParentheticalNode(condition);
|
||||
}
|
||||
condition = new OpNode('!', condition);
|
||||
}
|
||||
this.condition = condition;
|
||||
this.guard = opts == null ? undefined : opts.guard;
|
||||
this.guard = ((opts != null) ? opts.guard : null);
|
||||
return this;
|
||||
};
|
||||
__extends(WhileNode, BaseNode);
|
||||
|
@ -1315,6 +1359,10 @@
|
|||
return OpNode.__super__.toString.call(this, idt, this["class"] + ' ' + this.operator);
|
||||
};
|
||||
OpNode.prototype.compileNode = function(o) {
|
||||
var node;
|
||||
if (node = ValueNode.unfoldSoak(o, this, 'first')) {
|
||||
return node.compile(o);
|
||||
}
|
||||
if (this.isChainable() && this.first.unwrap() instanceof OpNode && this.first.unwrap().isChainable()) {
|
||||
return this.compileChain(o);
|
||||
}
|
||||
|
@ -1343,7 +1391,10 @@
|
|||
return "(" + (first) + ") && (" + (shared) + " " + (this.operator) + " " + (second) + ")";
|
||||
};
|
||||
OpNode.prototype.compileAssignment = function(o) {
|
||||
var _ref2, first, firstVar, second;
|
||||
var _ref2, first, firstVar, left, rite, second;
|
||||
_ref2 = this.first.cacheReference(o), left = _ref2[0], rite = _ref2[1];
|
||||
rite = new AssignNode(rite, this.second);
|
||||
return new OpNode(this.operator.slice(0, -1), left, rite).compile(o);
|
||||
_ref2 = this.first.compileReference(o, {
|
||||
precompile: true,
|
||||
assignment: true
|
||||
|
@ -1361,9 +1412,15 @@
|
|||
return "" + (first) + " " + (this.operator.substr(0, 2)) + " (" + (firstVar) + " = " + (second) + ")";
|
||||
};
|
||||
OpNode.prototype.compileExistence = function(o) {
|
||||
var _ref2, ref, test;
|
||||
_ref2 = ExistenceNode.compileTest(o, this.first), test = _ref2[0], ref = _ref2[1];
|
||||
return "" + (test) + " ? " + (ref) + " : " + (this.second.compile(o));
|
||||
var fst, ref;
|
||||
if (this.first.isComplex()) {
|
||||
ref = o.scope.freeVariable('ref');
|
||||
fst = new ParentheticalNode(new AssignNode(literal(ref), this.first));
|
||||
} else {
|
||||
fst = this.first;
|
||||
ref = fst.compile(o);
|
||||
}
|
||||
return new ExistenceNode(fst).compile(o) + (" ? " + (ref) + " : " + (this.second.compile(o)));
|
||||
};
|
||||
OpNode.prototype.compileUnary = function(o) {
|
||||
var parts, space;
|
||||
|
@ -1479,20 +1536,13 @@
|
|||
ExistenceNode.prototype["class"] = 'ExistenceNode';
|
||||
ExistenceNode.prototype.children = ['expression'];
|
||||
ExistenceNode.prototype.compileNode = function(o) {
|
||||
var test;
|
||||
test = ExistenceNode.compileTest(o, this.expression)[0];
|
||||
return this.parenthetical ? test.slice(1, -1) : test;
|
||||
};
|
||||
ExistenceNode.compileTest = function(o, variable) {
|
||||
var _ref2, first, second;
|
||||
_ref2 = variable.compileReference(o, {
|
||||
precompile: true
|
||||
}), first = _ref2[0], second = _ref2[1];
|
||||
first = first === second && o.scope.check(first) ? ("(" + (first) + " != null)") : ("(typeof " + (first) + " !== \"undefined\" && " + (second) + " !== null)");
|
||||
return [first, second];
|
||||
var code;
|
||||
code = this.expression.compile(o);
|
||||
code = IDENTIFIER.test(code) && !o.scope.check(code) ? ("typeof " + (code) + " !== \"undefined\" && " + (code) + " !== null") : ("" + (code) + " != null");
|
||||
return this.parenthetical ? code : ("(" + (code) + ")");
|
||||
};
|
||||
return ExistenceNode;
|
||||
}).call(this);
|
||||
})();
|
||||
exports.ParentheticalNode = (function() {
|
||||
ParentheticalNode = function(_arg) {
|
||||
this.expression = _arg;
|
||||
|
@ -1742,10 +1792,12 @@
|
|||
IfNode.prototype.children = ['condition', 'body', 'elseBody', 'assigner'];
|
||||
IfNode.prototype.topSensitive = YES;
|
||||
IfNode.prototype.bodyNode = function() {
|
||||
return this.body == null ? undefined : this.body.unwrap();
|
||||
var _ref2;
|
||||
return (((_ref2 = this.body) != null) ? _ref2.unwrap() : null);
|
||||
};
|
||||
IfNode.prototype.elseBodyNode = function() {
|
||||
return this.elseBody == null ? undefined : this.elseBody.unwrap();
|
||||
var _ref2;
|
||||
return (((_ref2 = this.elseBody) != null) ? _ref2.unwrap() : null);
|
||||
};
|
||||
IfNode.prototype.addElse = function(elseBody, statement) {
|
||||
if (this.isChain) {
|
||||
|
@ -1757,7 +1809,7 @@
|
|||
return this;
|
||||
};
|
||||
IfNode.prototype.isStatement = function(o) {
|
||||
return this.statement || (this.statement = (!!((o && o.top) || this.bodyNode().isStatement(o) || (this.elseBody && this.elseBodyNode().isStatement(o)))));
|
||||
return this.statement || (this.statement = !!((o && o.top) || this.bodyNode().isStatement(o) || (this.elseBody && this.elseBodyNode().isStatement(o))));
|
||||
};
|
||||
IfNode.prototype.compileCondition = function(o) {
|
||||
var _i, _len, _ref2, _result, cond, conditions;
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
if (tuple.length < 3) {
|
||||
tuple.unshift(null);
|
||||
}
|
||||
return buildRule.apply(this, tuple);
|
||||
return buildRule.apply(buildRule, tuple);
|
||||
})());
|
||||
}
|
||||
return _result;
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
};
|
||||
Rewriter.prototype.adjustComments = function() {
|
||||
return this.scanTokens(function(token, i) {
|
||||
var _ref, after, before, post, prev;
|
||||
var _ref, _this, after, before, post, prev;
|
||||
if (token[0] !== 'HERECOMMENT') {
|
||||
return 1;
|
||||
}
|
||||
|
@ -68,7 +68,7 @@
|
|||
}
|
||||
} else if (prev && !('TERMINATOR' === (_ref = prev[0]) || 'INDENT' === _ref || 'OUTDENT' === _ref)) {
|
||||
if (post && post[0] === 'TERMINATOR' && after && after[0] === 'OUTDENT') {
|
||||
(_ref = this.tokens).splice.apply(_ref, [i + 2, 0].concat(this.tokens.splice(i, 2)));
|
||||
(_this = this.tokens).splice.apply(_this, [i + 2, 0].concat(this.tokens.splice(i, 2)));
|
||||
if (this.tokens[i + 2][0] !== 'TERMINATOR') {
|
||||
this.tokens.splice(i + 2, 0, ['TERMINATOR', "\n", prev[2]]);
|
||||
}
|
||||
|
@ -219,13 +219,13 @@
|
|||
};
|
||||
Rewriter.prototype.addImplicitIndentation = function() {
|
||||
return this.scanTokens(function(token, i) {
|
||||
var _ref, action, condition, indent, outdent, starter;
|
||||
var _ref, _this, action, condition, indent, outdent, starter;
|
||||
if (token[0] === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') {
|
||||
(_ref = this.tokens).splice.apply(_ref, [i, 0].concat(this.indentation(token)));
|
||||
(_this = this.tokens).splice.apply(_this, [i, 0].concat(this.indentation(token)));
|
||||
return 2;
|
||||
}
|
||||
if (token[0] === 'CATCH' && (this.tag(i + 2) === 'TERMINATOR' || this.tag(i + 2) === 'FINALLY')) {
|
||||
(_ref = this.tokens).splice.apply(_ref, [i + 2, 0].concat(this.indentation(token)));
|
||||
(_this = this.tokens).splice.apply(_this, [i + 2, 0].concat(this.indentation(token)));
|
||||
return 4;
|
||||
}
|
||||
if (include(SINGLE_LINERS, token[0]) && this.tag(i + 1) !== 'INDENT' && !(token[0] === 'ELSE' && this.tag(i + 1) === 'IF')) {
|
||||
|
@ -345,7 +345,7 @@
|
|||
}
|
||||
debt[mtag] += 1;
|
||||
val = [oppos, mtag === 'INDENT' ? match[1] : oppos];
|
||||
if ((this.tokens[(_ref2 = i + 2)] == null ? undefined : this.tokens[_ref2][0]) === mtag) {
|
||||
if ((((_ref2 = this.tokens[i + 2]) != null) ? _ref2[0] === mtag : null)) {
|
||||
this.tokens.splice(i + 3, 0, val);
|
||||
stack.push(match);
|
||||
} else {
|
||||
|
|
319
src/nodes.coffee
319
src/nodes.coffee
|
@ -40,7 +40,7 @@ exports.BaseNode = class BaseNode
|
|||
# depending on whether it's being used as part of a larger expression, or is a
|
||||
# top-level statement within the function body.
|
||||
compile: (o) ->
|
||||
@options = merge o or {}
|
||||
@options = if o then merge o else {}
|
||||
@tab = o.indent
|
||||
del @options, 'chainRoot' unless this instanceof AccessorNode or this instanceof IndexNode
|
||||
top = if @topSensitive() then @options.top else del @options, 'top'
|
||||
|
@ -63,16 +63,13 @@ exports.BaseNode = class BaseNode
|
|||
# in multiple places, ensure that the expression is only ever evaluated once,
|
||||
# by assigning it to a temporary variable.
|
||||
compileReference: (o, options) ->
|
||||
options or= {}
|
||||
pair = if not @isComplex()
|
||||
pair = unless @isComplex()
|
||||
[this, this]
|
||||
else if this instanceof ValueNode and options.assignment
|
||||
this.cacheIndexes(o)
|
||||
else
|
||||
reference = literal o.scope.freeVariable 'ref'
|
||||
compiled = new AssignNode reference, this
|
||||
[compiled, reference]
|
||||
return [pair[0].compile(o), pair[1].compile(o)] if options.precompile
|
||||
(pair[i] = node.compile o) for node, i in pair if options?.precompile
|
||||
pair
|
||||
|
||||
# Convenience method to grab the current indentation level, plus tabbing in.
|
||||
|
@ -107,7 +104,7 @@ exports.BaseNode = class BaseNode
|
|||
# Convenience for the most common use of contains. Does the node contain
|
||||
# a pure statement?
|
||||
containsPureStatement: ->
|
||||
@isPureStatement() or @contains (n) -> n.isPureStatement and n.isPureStatement()
|
||||
@isPureStatement() or @contains (n) -> n.isPureStatement?()
|
||||
|
||||
# Perform an in-order traversal of the AST. Crosses scope boundaries.
|
||||
traverse: (block) -> @traverseChildren true, block
|
||||
|
@ -133,8 +130,10 @@ exports.BaseNode = class BaseNode
|
|||
|
||||
traverseChildren: (crossScope, func) ->
|
||||
@eachChild (child) ->
|
||||
func.apply(this, arguments)
|
||||
child.traverseChildren(crossScope, func) if child instanceof BaseNode
|
||||
return false if func(child) is false
|
||||
if child instanceof BaseNode and
|
||||
(crossScope or child not instanceof CodeNode)
|
||||
child.traverseChildren crossScope, func
|
||||
|
||||
# Default implementations of the common node properties and methods. Nodes
|
||||
# will override these with custom logic, if needed.
|
||||
|
@ -311,10 +310,10 @@ exports.ValueNode = class ValueNode extends BaseNode
|
|||
# Some boolean checks for the benefit of other nodes.
|
||||
|
||||
isArray: ->
|
||||
@base instanceof ArrayNode and not @hasProperties()
|
||||
@base instanceof ArrayNode and not @properties.length
|
||||
|
||||
isObject: ->
|
||||
@base instanceof ObjectNode and not @hasProperties()
|
||||
@base instanceof ObjectNode and not @properties.length
|
||||
|
||||
isSplice: ->
|
||||
last(@properties) instanceof SliceNode
|
||||
|
@ -323,7 +322,8 @@ exports.ValueNode = class ValueNode extends BaseNode
|
|||
@base.isComplex() or @hasProperties()
|
||||
|
||||
makeReturn: ->
|
||||
if @hasProperties() then super() else @base.makeReturn()
|
||||
if @properties.length then super() else @base.makeReturn()
|
||||
|
||||
|
||||
# The value can be unwrapped as its inner node, if there are no attached
|
||||
# properties.
|
||||
|
@ -337,20 +337,24 @@ exports.ValueNode = class ValueNode extends BaseNode
|
|||
isNumber: ->
|
||||
@base instanceof LiteralNode and NUMBER.test @base.value
|
||||
|
||||
# If the value node has indexes containing function calls, and the value node
|
||||
# needs to be used twice, in compound assignment ... then we need to cache
|
||||
# the value of the indexes.
|
||||
cacheIndexes: (o) ->
|
||||
copy = new ValueNode @base, @properties[0..]
|
||||
if @base.isComplex()
|
||||
[@base, copy.base] = @base.compileReference o
|
||||
for prop, i in copy.properties
|
||||
if prop instanceof IndexNode and prop.index.isComplex()
|
||||
[index, indexVar] = prop.index.compileReference o
|
||||
this.properties[i] = first = new IndexNode index
|
||||
copy.properties[i] = new IndexNode indexVar
|
||||
first.soakNode = yes if prop.soakNode
|
||||
[this, copy]
|
||||
# A reference has base part (`this` value) and name part.
|
||||
# We cache them separately for compiling complex expressions.
|
||||
# `a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c`
|
||||
cacheReference: (o) ->
|
||||
name = last @properties
|
||||
if not @base.isComplex() and @properties.length < 2 and
|
||||
not name?.isComplex()
|
||||
return [this, this] # `a` `a.b`
|
||||
base = new ValueNode @base, @properties.slice 0, -1
|
||||
if base.isComplex() # `a().b`
|
||||
bref = literal o.scope.freeVariable 'base'
|
||||
base = new ValueNode new ParentheticalNode new AssignNode bref, base
|
||||
return [base, bref] unless name # `a()`
|
||||
if name.isComplex() # `a[b()]`
|
||||
nref = literal o.scope.freeVariable 'name'
|
||||
name = new IndexNode new AssignNode nref, name.index
|
||||
nref = new IndexNode nref
|
||||
[base.push(name), new ValueNode(bref or base.base, [nref or name])]
|
||||
|
||||
# Override compile to unwrap the value when possible.
|
||||
compile: (o) ->
|
||||
|
@ -361,41 +365,46 @@ exports.ValueNode = class ValueNode extends BaseNode
|
|||
# operators `?.` interspersed. Then we have to take care not to accidentally
|
||||
# evaluate a anything twice when building the soak chain.
|
||||
compileNode: (o) ->
|
||||
only = del o, 'onlyFirst'
|
||||
op = @tags.operation
|
||||
props = if only then @properties[0...-1] else @properties
|
||||
return ex.compile o if ex = @unfoldSoak o
|
||||
props = @properties
|
||||
o.chainRoot or= this
|
||||
for prop in props when prop.soakNode
|
||||
hasSoak = yes
|
||||
break
|
||||
if hasSoak and @isComplex()
|
||||
[me, copy] = @cacheIndexes o
|
||||
@base.parenthetical = yes if @parenthetical and not props.length
|
||||
baseline = @base.compile o
|
||||
baseline = "(#{baseline})" if @hasProperties() and (@base instanceof ObjectNode or @isNumber())
|
||||
complete = @last = baseline
|
||||
code = @base.compile o
|
||||
if props[0] instanceof AccessorNode and @isNumber() or
|
||||
o.top and @base instanceof ObjectNode
|
||||
code = "(#{code})"
|
||||
(code += prop.compile o) for prop in props
|
||||
return code
|
||||
|
||||
for prop, i in props
|
||||
@source = baseline
|
||||
if prop.soakNode
|
||||
if i is 0 and @base.isComplex()
|
||||
temp = o.scope.freeVariable 'ref'
|
||||
complete = "(#{ baseline = temp } = (#{complete}))"
|
||||
complete = if i is 0 and not o.scope.check complete
|
||||
"(typeof #{complete} === \"undefined\" || #{baseline} === null)"
|
||||
else
|
||||
"#{complete} == null"
|
||||
complete += ' ? undefined : ' + baseline += prop.compile o
|
||||
else
|
||||
part = prop.compile(o)
|
||||
baseline += if hasSoak and prop.isComplex()
|
||||
copy.properties[i].compile o
|
||||
else
|
||||
part
|
||||
complete += part
|
||||
@last = part
|
||||
# Unfold a soak into an `IfNode`: `a?.b` -> `a.b if a?`
|
||||
unfoldSoak: (o) ->
|
||||
if @base.soakNode
|
||||
Array::push.apply @base.body.properties, @properties
|
||||
return @base
|
||||
for prop, i in @properties when prop.soakNode
|
||||
prop.soakNode = off
|
||||
fst = new ValueNode @base, @properties.slice 0, i
|
||||
snd = new ValueNode @base, @properties.slice i
|
||||
if fst.isComplex()
|
||||
ref = literal o.scope.freeVariable 'ref'
|
||||
fst = new ParentheticalNode new AssignNode ref, fst
|
||||
snd.base = ref
|
||||
ifn = new IfNode new ExistenceNode(fst), snd, operation: yes
|
||||
ifn.soakNode = on
|
||||
return ifn
|
||||
null
|
||||
|
||||
if op and @wrapped then "(#{complete})" else complete
|
||||
# Unfold a node's child if soak, then tuck the node under created `IfNode`
|
||||
@unfoldSoak: (o, parent, name) ->
|
||||
node = parent[name]
|
||||
if node instanceof IfNode and node.soakNode
|
||||
ifnode = node
|
||||
else if node instanceof ValueNode
|
||||
ifnode = node.unfoldSoak o
|
||||
return unless ifnode
|
||||
parent[name] = ifnode.body
|
||||
ifnode.body = new ValueNode parent
|
||||
ifnode
|
||||
|
||||
#### CommentNode
|
||||
|
||||
|
@ -429,9 +438,9 @@ exports.CallNode = class CallNode extends BaseNode
|
|||
@isSuper = variable is 'super'
|
||||
@variable = if @isSuper then null else variable
|
||||
@args or= []
|
||||
@first = @last = ''
|
||||
@compileSplatArguments = (o) ->
|
||||
SplatNode.compileSplattedArray.call(this, @args, o)
|
||||
|
||||
compileSplatArguments: (o) ->
|
||||
SplatNode.compileSplattedArray @args, o
|
||||
|
||||
# Tag this invocation as creating a new instance.
|
||||
newInstance: ->
|
||||
|
@ -443,42 +452,58 @@ exports.CallNode = class CallNode extends BaseNode
|
|||
|
||||
# Grab the reference to the superclass' implementation of the current method.
|
||||
superReference: (o) ->
|
||||
throw new Error "cannot call super outside of a function" unless o.scope.method
|
||||
methname = o.scope.method.name
|
||||
meth = if o.scope.method.proto
|
||||
"#{o.scope.method.proto}.__super__.#{methname}"
|
||||
else if methname
|
||||
"#{methname}.__super__.constructor"
|
||||
else throw new Error "cannot call super on an anonymous function."
|
||||
{method} = o.scope
|
||||
throw Error "cannot call super outside of a function" unless method
|
||||
{name} = method
|
||||
throw Error "cannot call super on an anonymous function." unless name
|
||||
if method.klass
|
||||
"#{method.klass}.__super__.#{name}"
|
||||
else
|
||||
"#{name}.__super__.constructor"
|
||||
|
||||
unfoldSoak: (o) ->
|
||||
call = this
|
||||
list = []
|
||||
loop
|
||||
if call.variable instanceof CallNode
|
||||
list.push call
|
||||
call = call.variable
|
||||
continue
|
||||
break unless call.variable instanceof ValueNode
|
||||
list.push call
|
||||
break unless (call = call.variable.base) instanceof CallNode
|
||||
for call in list.reverse()
|
||||
if node
|
||||
if call.variable instanceof CallNode
|
||||
call.variable = node
|
||||
else
|
||||
call.variable.base = node
|
||||
node = ValueNode.unfoldSoak o, call, 'variable'
|
||||
node
|
||||
|
||||
# Compile a vanilla function call.
|
||||
compileNode: (o) ->
|
||||
o.chainRoot = this unless o.chainRoot
|
||||
op = @tags.operation
|
||||
return node.compile o if node = @unfoldSoak o
|
||||
o.chainRoot or= this
|
||||
if @exist
|
||||
if @variable instanceof ValueNode and
|
||||
last(@variable.properties) instanceof AccessorNode
|
||||
methodAccessor = @variable.properties.pop()
|
||||
[first, meth] = @variable.compileReference o
|
||||
@first = new ValueNode(first, [methodAccessor]).compile o
|
||||
@meth = new ValueNode(meth, [methodAccessor]).compile o
|
||||
if val = @variable
|
||||
val = new ValueNode val unless val instanceof ValueNode
|
||||
[left, rite] = val.cacheReference o
|
||||
rite = new CallNode rite, @args
|
||||
else
|
||||
[@first, @meth] = @variable.compileReference o, precompile: yes
|
||||
@first = "(typeof #{@first} === \"function\" ? "
|
||||
@last = " : undefined)"
|
||||
else if @variable
|
||||
@meth = @variable.compile o
|
||||
left = literal @superReference o
|
||||
rite = new CallNode new ValueNode(left), @args
|
||||
rite.isNew = @isNew
|
||||
left = "typeof #{ left.compile o } !== \"function\""
|
||||
rite = rite.compile o
|
||||
return "(#{left} ? undefined : #{rite})"
|
||||
for arg in @args when arg instanceof SplatNode
|
||||
code = @compileSplat(o)
|
||||
if not code
|
||||
args = for arg in @args
|
||||
arg.parenthetical = true
|
||||
arg.compile o
|
||||
code = if @isSuper
|
||||
@compileSuper(args.join(', '), o)
|
||||
else
|
||||
"#{@first}#{@prefix()}#{@meth}(#{ args.join(', ') })#{@last}"
|
||||
if op and @variable and @variable.wrapped then "(#{code})" else code
|
||||
return @compileSplat o
|
||||
args = ((arg.parenthetical = on) and arg.compile o for arg in @args).join ', '
|
||||
if @isSuper
|
||||
@compileSuper args, o
|
||||
else
|
||||
"#{@prefix()}#{@variable.compile o}(#{args})"
|
||||
|
||||
# `super()` is converted into a call against the superclass's implementation
|
||||
# of the current function.
|
||||
|
@ -490,29 +515,32 @@ exports.CallNode = class CallNode extends BaseNode
|
|||
# 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) ->
|
||||
meth = @meth or @superReference(o)
|
||||
obj = @variable and @variable.source or 'this'
|
||||
unless IDENTIFIER.test(obj) or NUMBER.test(obj)
|
||||
temp = o.scope.freeVariable 'ref'
|
||||
obj = temp
|
||||
meth = "(#{temp} = #{ @variable.source })#{ @variable.last }"
|
||||
if @isNew
|
||||
mentionsArgs = no
|
||||
for arg in @args
|
||||
arg.contains (n) -> mentionsArgs or= n instanceof LiteralNode and (n.value is 'arguments')
|
||||
utility 'extends'
|
||||
a = o.scope.freeVariable 'ctor'
|
||||
b = o.scope.freeVariable 'ref'
|
||||
c = o.scope.freeVariable 'result'
|
||||
"""
|
||||
#{@first}(function() {
|
||||
#{@idt(1)}var ctor = function(){};
|
||||
#{@idt(1)}__extends(ctor, #{a} = #{meth});
|
||||
#{@idt(1)}return typeof (#{c} = #{a}.apply(#{b} = new ctor, #{ @compileSplatArguments(o) })) === "object" ? #{c} : #{b};
|
||||
#{@tab}}).#{ if mentionsArgs then 'apply(this, arguments)' else 'call(this)'}#{@last}
|
||||
"""
|
||||
else
|
||||
"#{@first}#{meth}.apply(#{obj}, #{ @compileSplatArguments(o) })#{@last}"
|
||||
splatargs = @compileSplatArguments o
|
||||
return "#{ @superReference o }.apply(this, #{splatargs})" if @isSuper
|
||||
unless @isNew
|
||||
base = new ValueNode base unless (base = @variable) instanceof ValueNode
|
||||
if (name = base.properties.pop()) and base.isComplex()
|
||||
ref = o.scope.freeVariable 'this'
|
||||
fun = "(#{ref} = #{ base.compile o })#{ name.compile o }"
|
||||
else
|
||||
fun = ref = base.compile o
|
||||
fun += name.compile o if name
|
||||
return "#{fun}.apply(#{ref}, #{splatargs})"
|
||||
call = 'call(this)'
|
||||
argvar = (n) -> n instanceof LiteralNode and n.value is 'arguments'
|
||||
for arg in @args when arg.contains argvar
|
||||
call = 'apply(this, arguments)'
|
||||
break
|
||||
a = o.scope.freeVariable 'ctor'
|
||||
b = o.scope.freeVariable 'ref'
|
||||
c = o.scope.freeVariable 'result'
|
||||
"""
|
||||
(function() {
|
||||
#{idt = @idt 1}var ctor = function() {};
|
||||
#{idt}#{utility 'extends'}(ctor, #{a} = #{ @variable.compile o });
|
||||
#{idt}return typeof (#{c} = #{a}.apply(#{b} = new ctor, #{splatargs})) === "object" ? #{c} : #{b};
|
||||
#{@tab}}).#{call}
|
||||
"""
|
||||
|
||||
#### ExtendsNode
|
||||
|
||||
|
@ -706,8 +734,9 @@ exports.ArrayNode = class ArrayNode extends BaseNode
|
|||
constructor: (@objects) ->
|
||||
super()
|
||||
@objects or= []
|
||||
@compileSplatLiteral = (o) ->
|
||||
SplatNode.compileSplattedArray.call(this, @objects, o)
|
||||
|
||||
compileSplatLiteral: (o) ->
|
||||
SplatNode.compileSplattedArray @objects, o
|
||||
|
||||
compileNode: (o) ->
|
||||
o.indent = @idt 1
|
||||
|
@ -807,9 +836,8 @@ exports.ClassNode = class ClassNode extends BaseNode
|
|||
# property of an object -- including within object literals.
|
||||
exports.AssignNode = class AssignNode extends BaseNode
|
||||
|
||||
# Matchers for detecting prototype assignments.
|
||||
PROTO_ASSIGN: /^(\S+)\.prototype/
|
||||
LEADING_DOT: /^\.(?:prototype\.)?/
|
||||
# Matchers for detecting class/method names
|
||||
METHOD_DEF: /^(?:(\S+)\.prototype\.)?([$A-Za-z_][$\w]*)$/
|
||||
|
||||
class: 'AssignNode'
|
||||
children: ['variable', 'value']
|
||||
|
@ -830,15 +858,13 @@ exports.AssignNode = class AssignNode extends BaseNode
|
|||
if isValue = @isValue()
|
||||
return @compilePatternMatch(o) if @variable.isArray() or @variable.isObject()
|
||||
return @compileSplice(o) if @variable.isSplice()
|
||||
return node.compile o if node = ValueNode.unfoldSoak o, this, 'variable'
|
||||
top = del o, 'top'
|
||||
stmt = del o, 'asStatement'
|
||||
name = @variable.compile(o)
|
||||
end = if isValue then @variable.last.replace(@LEADING_DOT, '') else name
|
||||
match = name.match(@PROTO_ASSIGN)
|
||||
proto = match and match[1]
|
||||
if @value instanceof CodeNode
|
||||
@value.name = end if IDENTIFIER.test end
|
||||
@value.proto = proto if proto
|
||||
if @value instanceof CodeNode and match = @METHOD_DEF.exec name
|
||||
@value.name = match[2]
|
||||
@value.klass = match[1]
|
||||
val = @value.compile o
|
||||
return "#{name}: #{val}" if @context is 'object'
|
||||
o.scope.find name unless isValue and (@variable.hasProperties() or @variable.namespaced)
|
||||
|
@ -898,14 +924,14 @@ exports.AssignNode = class AssignNode extends BaseNode
|
|||
# Compile the assignment from an array splice literal, using JavaScript's
|
||||
# `Array#splice` method.
|
||||
compileSplice: (o) ->
|
||||
name = @variable.compile merge o, onlyFirst: true
|
||||
l = @variable.properties.length
|
||||
range = @variable.properties[l - 1].range
|
||||
{range} = @variable.properties.pop()
|
||||
name = @variable.compile o
|
||||
plus = if range.exclusive then '' else ' + 1'
|
||||
from = if range.from then range.from.compile(o) else '0'
|
||||
to = if range.to then range.to.compile(o) + ' - ' + from + plus else "#{name}.length"
|
||||
ref = o.scope.freeVariable 'ref'
|
||||
val = @value.compile(o)
|
||||
"[].splice.apply(#{name}, [#{from}, #{to}].concat(#{val}))"
|
||||
"([].splice.apply(#{name}, [#{from}, #{to}].concat(#{ref} = #{val})), #{ref})"
|
||||
|
||||
#### CodeNode
|
||||
|
||||
|
@ -1172,6 +1198,7 @@ exports.OpNode = class OpNode extends BaseNode
|
|||
super(idt, @class + ' ' + @operator)
|
||||
|
||||
compileNode: (o) ->
|
||||
return node.compile o if node = ValueNode.unfoldSoak o, this, 'first'
|
||||
return @compileChain(o) if @isChainable() and @first.unwrap() instanceof OpNode and @first.unwrap().isChainable()
|
||||
return @compileAssignment(o) if indexOf(@ASSIGNMENT, @operator) >= 0
|
||||
return @compileUnary(o) if @isUnary()
|
||||
|
@ -1195,6 +1222,10 @@ exports.OpNode = class OpNode extends BaseNode
|
|||
# operands are only evaluated once, even though we have to reference them
|
||||
# more than once.
|
||||
compileAssignment: (o) ->
|
||||
[left, rite] = @first.cacheReference o
|
||||
rite = new AssignNode rite, @second
|
||||
return new OpNode(@operator.slice(0, -1), left, rite).compile o
|
||||
|
||||
[first, firstVar] = @first.compileReference o, precompile: yes, assignment: yes
|
||||
second = @second.compile o
|
||||
second = "(#{second})" if @second instanceof OpNode
|
||||
|
@ -1202,11 +1233,14 @@ exports.OpNode = class OpNode extends BaseNode
|
|||
return "#{first} = #{ ExistenceNode.compileTest(o, literal(firstVar))[0] } ? #{firstVar} : #{second}" if @operator is '?='
|
||||
"#{first} #{ @operator.substr(0, 2) } (#{firstVar} = #{second})"
|
||||
|
||||
# If this is an existence operator, we delegate to `ExistenceNode.compileTest`
|
||||
# to give us the safe references for the variables.
|
||||
compileExistence: (o) ->
|
||||
[test, ref] = ExistenceNode.compileTest(o, @first)
|
||||
"#{test} ? #{ref} : #{ @second.compile(o) }"
|
||||
if @first.isComplex()
|
||||
ref = o.scope.freeVariable 'ref'
|
||||
fst = new ParentheticalNode new AssignNode literal(ref), @first
|
||||
else
|
||||
fst = @first
|
||||
ref = fst.compile o
|
||||
new ExistenceNode(fst).compile(o) + " ? #{ref} : #{ @second.compile o }"
|
||||
|
||||
# Compile a unary **OpNode**.
|
||||
compileUnary: (o) ->
|
||||
|
@ -1302,19 +1336,12 @@ exports.ExistenceNode = class ExistenceNode extends BaseNode
|
|||
super()
|
||||
|
||||
compileNode: (o) ->
|
||||
test = ExistenceNode.compileTest(o, @expression)[0]
|
||||
if @parenthetical then test.slice 1, -1 else test
|
||||
|
||||
# The meat of the **ExistenceNode** is in this static `compileTest` method
|
||||
# because other nodes like to check the existence of their variables as well.
|
||||
# Be careful not to double-evaluate anything.
|
||||
@compileTest: (o, variable) ->
|
||||
[first, second] = variable.compileReference o, precompile: yes
|
||||
first = if first is second and o.scope.check first
|
||||
"(#{first} != null)"
|
||||
code = @expression.compile o
|
||||
code = if IDENTIFIER.test(code) and not o.scope.check code
|
||||
"typeof #{code} !== \"undefined\" && #{code} !== null"
|
||||
else
|
||||
"(typeof #{first} !== \"undefined\" && #{second} !== null)"
|
||||
[first, second]
|
||||
"#{code} != null"
|
||||
if @parenthetical then code else "(#{code})"
|
||||
|
||||
#### ParentheticalNode
|
||||
|
||||
|
@ -1413,8 +1440,8 @@ exports.ForNode = class ForNode extends BaseNode
|
|||
sourcePart = source.compileVariables(o)
|
||||
forPart = source.compile merge o, index: ivar, step: @step
|
||||
else
|
||||
svar = scope.freeVariable 'ref'
|
||||
sourcePart = "#{svar} = #{ @source.compile(o) };"
|
||||
svar = scope.freeVariable 'ref'
|
||||
sourcePart = "#{svar} = #{ @source.compile(o) };"
|
||||
if @pattern
|
||||
namePart = new AssignNode(@name, literal("#{svar}[#{ivar}]")).compile(merge o, {indent: @idt(1), top: true, keepLevel: yes}) + '\n'
|
||||
else
|
||||
|
|
|
@ -1,45 +1,26 @@
|
|||
num = 10
|
||||
num -= 5
|
||||
|
||||
ok num is 5
|
||||
|
||||
num = -3
|
||||
|
||||
ok num is -3
|
||||
|
||||
num = +3
|
||||
|
||||
ok num is 3
|
||||
eq num, 5
|
||||
|
||||
num *= 10
|
||||
|
||||
ok num is 30
|
||||
eq num, 50
|
||||
|
||||
num /= 10
|
||||
eq num, 5
|
||||
|
||||
ok num is 3
|
||||
|
||||
num %= 3
|
||||
eq num, 2
|
||||
|
||||
val = false
|
||||
val ||= 'value'
|
||||
val ||= 'eulav'
|
||||
eq val, 'value'
|
||||
|
||||
ok val is 'value'
|
||||
|
||||
val &&= 'rehto'
|
||||
val &&= 'other'
|
||||
|
||||
ok val is 'other'
|
||||
|
||||
eq val, 'other'
|
||||
|
||||
val = null
|
||||
val ?= 'value'
|
||||
|
||||
ok val is 'value'
|
||||
|
||||
|
||||
val = 6
|
||||
val = -(10)
|
||||
|
||||
ok val is -10
|
||||
|
||||
val -= (10)
|
||||
ok val is -20
|
||||
val ?= 'eulav'
|
||||
eq val, 'value'
|
||||
|
|
|
@ -37,45 +37,33 @@ ok(if getNextNode()? then true else false)
|
|||
|
||||
|
||||
# Existence chains, soaking up undefined properties:
|
||||
obj = {
|
||||
obj =
|
||||
prop: "hello"
|
||||
}
|
||||
|
||||
ok obj?.prop is "hello"
|
||||
|
||||
ok obj?['prop'] is "hello"
|
||||
|
||||
ok obj.prop?.length is 5
|
||||
|
||||
ok obj?['prop']?['length'] is 5
|
||||
|
||||
ok obj?.prop?.non?.existent?.property is undefined
|
||||
|
||||
ok obj?['non']?['existent'].property is undefined
|
||||
eq obj?.prop, "hello"
|
||||
eq obj?['prop'], "hello"
|
||||
eq obj.prop?.length, 5
|
||||
eq obj?.prop?['length'], 5
|
||||
eq obj?.prop?.non?.existent?.property, null
|
||||
|
||||
|
||||
# Soaks and caches method calls as well.
|
||||
arr = ["--", "----"]
|
||||
|
||||
ok arr.pop()?.length is 4
|
||||
ok arr.pop()?.length is 2
|
||||
ok arr.pop()?.length is undefined
|
||||
ok arr[0]?.length is undefined
|
||||
ok arr.pop()?.length?.non?.existent()?.property is undefined
|
||||
eq arr.pop()?.length, 4
|
||||
eq arr.pop()?.length, 2
|
||||
eq arr.pop()?.length, null
|
||||
eq arr.pop()?.length?.non?.existent()?.property, null
|
||||
|
||||
|
||||
# Soaks method calls safely.
|
||||
value = undefined
|
||||
result = value?.toString().toLowerCase()
|
||||
|
||||
ok result is undefined
|
||||
value = null
|
||||
eq value?.toString().toLowerCase(), null
|
||||
|
||||
value = 10
|
||||
result = value?.toString().toLowerCase()
|
||||
eq value?.toString().toLowerCase(), '10'
|
||||
|
||||
ok result is '10'
|
||||
|
||||
ok(process.exit.nothing?.property() or 101)
|
||||
eq process.exit.nothing?.property() or 101, 101
|
||||
|
||||
counter = 0
|
||||
func = ->
|
||||
|
@ -89,9 +77,8 @@ ok obj[func()]()[func()]()[func()]()?.value is 25
|
|||
ok counter is 3
|
||||
|
||||
|
||||
# Soaks inner values.
|
||||
ident = (obj) -> obj
|
||||
ok ident(non?.existent().method()) is undefined
|
||||
eq ident(non?.existent().method()), null, 'soaks inner values'
|
||||
|
||||
|
||||
# Soaks constructor invocations.
|
||||
|
@ -104,14 +91,10 @@ ok (new Foo())?.bar is 'bat'
|
|||
ok a is 1
|
||||
|
||||
|
||||
# Safely existence test on soaks.
|
||||
result = not value?.property?
|
||||
ok result
|
||||
ok not value?.property?, 'safely checks existence on soaks'
|
||||
|
||||
|
||||
# Safely calls values off of non-existent variables.
|
||||
result = nothing?.value
|
||||
ok result is undefined
|
||||
eq nothing?.value, null, 'safely calls values off of non-existent variables'
|
||||
|
||||
|
||||
# Assign to the result of an exsitential operation with a minus.
|
||||
|
@ -146,6 +129,14 @@ ok maybe_close(plus1, 41)?() is 42
|
|||
ok (maybe_close plus1, 41)?() is 42
|
||||
ok (maybe_close 'string', 41)?() is undefined
|
||||
|
||||
ok 2?(3) is undefined
|
||||
eq 2?(3), undefined
|
||||
|
||||
ok calendar?[Date()] is undefined
|
||||
#726
|
||||
eq calendar?[Date()], null
|
||||
|
||||
#733
|
||||
a = b: {c: null}
|
||||
eq a.b?.c?(), undefined
|
||||
a.b?.c or= (it) -> it
|
||||
eq a.b?.c?(1), 1
|
||||
eq a.b?.c?([2, 3]...), 2
|
||||
|
|
|
@ -49,7 +49,7 @@ medalists contenders..., 'Tim', 'Moe', 'Jim'
|
|||
ok last is 'Jim'
|
||||
|
||||
|
||||
obj = {
|
||||
obj =
|
||||
name: 'moe'
|
||||
accessor: (args...) ->
|
||||
[@name].concat(args).join(' ')
|
||||
|
@ -58,7 +58,6 @@ obj = {
|
|||
@accessor(args...)
|
||||
index: 0
|
||||
0: {method: -> this is obj[0]}
|
||||
}
|
||||
|
||||
ok obj.getNames() is 'moe jane ted'
|
||||
ok obj[obj.index++].method([]...), 'should cache base value'
|
||||
|
|
Loading…
Add table
Reference in a new issue