#733: streamlined soak compilations and improved reference cachings

This commit is contained in:
satyr
2010-10-02 07:17:35 +09:00
parent 54f162e523
commit 341f511bbd
9 changed files with 459 additions and 409 deletions

View File

@@ -206,7 +206,7 @@
var o; var o;
optionParser = new optparse.OptionParser(SWITCHES, BANNER); optionParser = new optparse.OptionParser(SWITCHES, BANNER);
o = (opts = optionParser.parse(process.argv.slice(2, process.argv.length))); 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.run = !(o.compile || o.print || o.lint);
o.print = !!(o.print || (o.eval || o.stdio && o.compile)); o.print = !!(o.print || (o.eval || o.stdio && o.compile));
return (sources = o.arguments); return (sources = o.arguments);

View File

@@ -294,7 +294,7 @@
} }
this.i += value.length; this.i += value.length;
prev = last(this.tokens); prev = last(this.tokens);
spaced = prev == null ? undefined : prev.spaced; spaced = ((prev != null) ? prev.spaced : null);
tag = value; tag = value;
if (value === '=') { if (value === '=') {
if (include(JS_FORBIDDEN, val = this.value())) { if (include(JS_FORBIDDEN, val = this.value())) {

View File

@@ -26,7 +26,7 @@
}; };
BaseNode.prototype.compile = function(o) { BaseNode.prototype.compile = function(o) {
var closure, code, top; var closure, code, top;
this.options = merge(o || {}); this.options = o ? merge(o) : {};
this.tab = o.indent; this.tab = o.indent;
if (!(this instanceof AccessorNode || this instanceof IndexNode)) { if (!(this instanceof AccessorNode || this instanceof IndexNode)) {
del(this.options, 'chainRoot'); del(this.options, 'chainRoot');
@@ -48,21 +48,22 @@
return ClosureNode.wrap(this).compile(o); return ClosureNode.wrap(this).compile(o);
}; };
BaseNode.prototype.compileReference = function(o, options) { BaseNode.prototype.compileReference = function(o, options) {
var compiled, pair, reference; var _len, _ref2, compiled, i, node, pair, reference;
options || (options = {});
pair = (function() { pair = (function() {
if (!this.isComplex()) { if (!(this.isComplex())) {
return [this, this]; return [this, this];
} else if (this instanceof ValueNode && options.assignment) {
return this.cacheIndexes(o);
} else { } else {
reference = literal(o.scope.freeVariable('ref')); reference = literal(o.scope.freeVariable('ref'));
compiled = new AssignNode(reference, this); compiled = new AssignNode(reference, this);
return [compiled, reference]; return [compiled, reference];
} }
}).call(this); }).call(this);
if (options.precompile) { if (((options != null) ? options.precompile : null)) {
return [pair[0].compile(o), pair[1].compile(o)]; _ref2 = pair;
for (i = 0, _len = _ref2.length; i < _len; i++) {
node = _ref2[i];
(pair[i] = node.compile(o));
}
} }
return pair; return pair;
}; };
@@ -96,7 +97,7 @@
}; };
BaseNode.prototype.containsPureStatement = function() { BaseNode.prototype.containsPureStatement = function() {
return this.isPureStatement() || this.contains(function(n) { 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) { BaseNode.prototype.traverse = function(block) {
@@ -146,8 +147,10 @@
}; };
BaseNode.prototype.traverseChildren = function(crossScope, func) { BaseNode.prototype.traverseChildren = function(crossScope, func) {
return this.eachChild(function(child) { return this.eachChild(function(child) {
func.apply(this, arguments); if (func(child) === false) {
return child instanceof BaseNode ? child.traverseChildren(crossScope, func) : null; return false;
}
return child instanceof BaseNode && (crossScope || !(child instanceof CodeNode)) ? child.traverseChildren(crossScope, func) : null;
}); });
}; };
BaseNode.prototype["class"] = 'BaseNode'; BaseNode.prototype["class"] = 'BaseNode';
@@ -319,10 +322,10 @@
return !!this.properties.length; return !!this.properties.length;
}; };
ValueNode.prototype.isArray = function() { ValueNode.prototype.isArray = function() {
return this.base instanceof ArrayNode && !this.hasProperties(); return this.base instanceof ArrayNode && !this.properties.length;
}; };
ValueNode.prototype.isObject = function() { ValueNode.prototype.isObject = function() {
return this.base instanceof ObjectNode && !this.hasProperties(); return this.base instanceof ObjectNode && !this.properties.length;
}; };
ValueNode.prototype.isSplice = function() { ValueNode.prototype.isSplice = function() {
return last(this.properties) instanceof SliceNode; return last(this.properties) instanceof SliceNode;
@@ -331,7 +334,7 @@
return this.base.isComplex() || this.hasProperties(); return this.base.isComplex() || this.hasProperties();
}; };
ValueNode.prototype.makeReturn = function() { 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() { ValueNode.prototype.unwrap = function() {
return this.properties.length ? this : this.base; return this.properties.length ? this : this.base;
@@ -342,76 +345,95 @@
ValueNode.prototype.isNumber = function() { ValueNode.prototype.isNumber = function() {
return this.base instanceof LiteralNode && NUMBER.test(this.base.value); return this.base instanceof LiteralNode && NUMBER.test(this.base.value);
}; };
ValueNode.prototype.cacheIndexes = function(o) { ValueNode.prototype.cacheReference = function(o) {
var _len, _ref2, _ref3, copy, first, i, index, indexVar, prop; var base, bref, name, nref;
copy = new ValueNode(this.base, this.properties.slice(0)); name = last(this.properties);
if (this.base.isComplex()) { if (!this.base.isComplex() && this.properties.length < 2 && !((name != null) ? name.isComplex() : null)) {
_ref2 = this.base.compileReference(o), this.base = _ref2[0], copy.base = _ref2[1]; return [this, this];
} }
_ref2 = copy.properties; base = new ValueNode(this.base, this.properties.slice(0, -1));
for (i = 0, _len = _ref2.length; i < _len; i++) { if (base.isComplex()) {
prop = _ref2[i]; bref = literal(o.scope.freeVariable('base'));
if (prop instanceof IndexNode && prop.index.isComplex()) { base = new ValueNode(new ParentheticalNode(new AssignNode(bref, base)));
_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;
}
}
} }
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) { ValueNode.prototype.compile = function(o) {
return !o.top || this.properties.length ? ValueNode.__super__.compile.call(this, o) : this.base.compile(o); return !o.top || this.properties.length ? ValueNode.__super__.compile.call(this, o) : this.base.compile(o);
}; };
ValueNode.prototype.compileNode = function(o) { ValueNode.prototype.compileNode = function(o) {
var _i, _len, _ref2, baseline, complete, copy, hasSoak, i, me, only, op, part, prop, props, temp; var _i, _len, _ref2, code, ex, prop, props;
only = del(o, 'onlyFirst'); if (ex = this.unfoldSoak(o)) {
op = this.tags.operation; return ex.compile(o);
props = only ? this.properties.slice(0, -1) : this.properties; }
props = this.properties;
o.chainRoot || (o.chainRoot = this); 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) { if (this.parenthetical && !props.length) {
this.base.parenthetical = true; this.base.parenthetical = true;
} }
baseline = this.base.compile(o); code = this.base.compile(o);
if (this.hasProperties() && (this.base instanceof ObjectNode || this.isNumber())) { if (props[0] instanceof AccessorNode && this.isNumber() || o.top && this.base instanceof ObjectNode) {
baseline = ("(" + (baseline) + ")"); code = ("(" + (code) + ")");
} }
complete = (this.last = baseline);
_ref2 = props; _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++) { for (i = 0, _len = _ref2.length; i < _len; i++) {
prop = _ref2[i]; prop = _ref2[i];
this.source = baseline;
if (prop.soakNode) { if (prop.soakNode) {
if (i === 0 && this.base.isComplex()) { prop.soakNode = false;
temp = o.scope.freeVariable('ref'); fst = new ValueNode(this.base, this.properties.slice(0, i));
complete = ("(" + (baseline = temp) + " = (" + (complete) + "))"); 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"); ifn = new IfNode(new ExistenceNode(fst), snd, {
complete += ' ? undefined : ' + (baseline += prop.compile(o)); operation: true
} else { });
part = prop.compile(o); ifn.soakNode = true;
baseline += (hasSoak && prop.isComplex() ? copy.properties[i].compile(o) : part); return ifn;
complete += part;
this.last = part;
} }
} }
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; return ValueNode;
})(); }).call(this);
exports.CommentNode = (function() { exports.CommentNode = (function() {
CommentNode = function(_arg) { CommentNode = function(_arg) {
this.comment = _arg; this.comment = _arg;
@@ -436,15 +458,14 @@
this.isSuper = variable === 'super'; this.isSuper = variable === 'super';
this.variable = this.isSuper ? null : variable; this.variable = this.isSuper ? null : variable;
this.args || (this.args = []); this.args || (this.args = []);
this.first = (this.last = '');
this.compileSplatArguments = function(o) {
return SplatNode.compileSplattedArray.call(this, this.args, o);
};
return this; return this;
}; };
__extends(CallNode, BaseNode); __extends(CallNode, BaseNode);
CallNode.prototype["class"] = 'CallNode'; CallNode.prototype["class"] = 'CallNode';
CallNode.prototype.children = ['variable', 'args']; CallNode.prototype.children = ['variable', 'args'];
CallNode.prototype.compileSplatArguments = function(o) {
return SplatNode.compileSplattedArray(this.args, o);
};
CallNode.prototype.newInstance = function() { CallNode.prototype.newInstance = function() {
this.isNew = true; this.isNew = true;
return this; return this;
@@ -453,97 +474,128 @@
return this.isNew ? 'new ' : ''; return this.isNew ? 'new ' : '';
}; };
CallNode.prototype.superReference = function(o) { CallNode.prototype.superReference = function(o) {
var meth, methname; var method, name;
if (!(o.scope.method)) { method = o.scope.method;
throw new Error("cannot call super outside of a function"); if (!(method)) {
throw Error("cannot call super outside of a function");
} }
methname = o.scope.method.name; name = method.name;
return (meth = (function() { if (!(name)) {
if (o.scope.method.proto) { throw Error("cannot call super on an anonymous function.");
return "" + (o.scope.method.proto) + ".__super__." + (methname); }
} else if (methname) { return method.klass ? ("" + (method.klass) + ".__super__." + (name)) : ("" + (name) + ".__super__.constructor");
return "" + (methname) + ".__super__.constructor"; };
} else { CallNode.prototype.unfoldSoak = function(o) {
throw new Error("cannot call super on an anonymous function."); 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) { CallNode.prototype.compileNode = function(o) {
var _i, _len, _ref2, _result, arg, args, code, first, meth, methodAccessor, op; var _i, _len, _ref2, _result, arg, args, left, node, rite, val;
if (!(o.chainRoot)) { if (node = this.unfoldSoak(o)) {
o.chainRoot = this; return node.compile(o);
} }
op = this.tags.operation; o.chainRoot || (o.chainRoot = this);
if (this.exist) { if (this.exist) {
if (this.variable instanceof ValueNode && last(this.variable.properties) instanceof AccessorNode) { if (val = this.variable) {
methodAccessor = this.variable.properties.pop(); if (!(val instanceof ValueNode)) {
_ref2 = this.variable.compileReference(o), first = _ref2[0], meth = _ref2[1]; val = new ValueNode(val);
this.first = new ValueNode(first, [methodAccessor]).compile(o); }
this.meth = new ValueNode(meth, [methodAccessor]).compile(o); _ref2 = val.cacheReference(o), left = _ref2[0], rite = _ref2[1];
rite = new CallNode(rite, this.args);
} else { } else {
_ref2 = this.variable.compileReference(o, { left = literal(this.superReference(o));
precompile: true rite = new CallNode(new ValueNode(left), this.args);
}), this.first = _ref2[0], this.meth = _ref2[1]; rite.isNew = this.isNew;
} }
this.first = ("(typeof " + (this.first) + " === \"function\" ? "); left = ("typeof " + (left.compile(o)) + " !== \"function\"");
this.last = " : undefined)"; rite = rite.compile(o);
} else if (this.variable) { return ("(" + (left) + " ? undefined : " + (rite) + ")");
this.meth = this.variable.compile(o);
} }
_ref2 = this.args; _ref2 = this.args;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) { for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
arg = _ref2[_i]; arg = _ref2[_i];
if (arg instanceof SplatNode) { if (arg instanceof SplatNode) {
code = this.compileSplat(o); return this.compileSplat(o);
} }
} }
if (!code) { args = (function() {
args = (function() { _result = []; _ref2 = this.args;
_result = []; _ref2 = this.args; for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
for (_i = 0, _len = _ref2.length; _i < _len; _i++) { arg = _ref2[_i];
arg = _ref2[_i]; _result.push((arg.parenthetical = true) && arg.compile(o));
_result.push((function() { }
arg.parenthetical = true; return _result;
return arg.compile(o); }).call(this).join(', ');
})()); return this.isSuper ? this.compileSuper(args, o) : ("" + (this.prefix()) + (this.variable.compile(o)) + "(" + (args) + ")");
}
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;
}; };
CallNode.prototype.compileSuper = function(args, o) { CallNode.prototype.compileSuper = function(args, o) {
return "" + (this.superReference(o)) + ".call(this" + (args.length ? ', ' : '') + (args) + ")"; return "" + (this.superReference(o)) + ".call(this" + (args.length ? ', ' : '') + (args) + ")";
}; };
CallNode.prototype.compileSplat = function(o) { CallNode.prototype.compileSplat = function(o) {
var _i, _len, _ref2, a, b, c, mentionsArgs, meth, obj, temp; var _i, _len, _ref2, a, arg, argvar, b, base, c, call, fun, idt, name, ref, splatargs;
meth = this.meth || this.superReference(o); splatargs = this.compileSplatArguments(o);
obj = this.variable && this.variable.source || 'this'; if (this.isSuper) {
if (!(IDENTIFIER.test(obj) || NUMBER.test(obj))) { return ("" + (this.superReference(o)) + ".apply(this, " + (splatargs) + ")");
temp = o.scope.freeVariable('ref');
obj = temp;
meth = ("(" + (temp) + " = " + (this.variable.source) + ")" + (this.variable.last));
} }
if (this.isNew) { if (!(this.isNew)) {
mentionsArgs = false; if (!((base = this.variable) instanceof ValueNode)) {
_ref2 = this.args; base = new ValueNode(base);
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')));
});
})();
} }
utility('extends'); if ((name = base.properties.pop()) && base.isComplex()) {
a = o.scope.freeVariable('ctor'); ref = o.scope.freeVariable('this');
b = o.scope.freeVariable('ref'); fun = ("(" + (ref) + " = " + (base.compile(o)) + ")" + (name.compile(o)));
c = o.scope.freeVariable('result'); } else {
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); fun = (ref = base.compile(o));
} else { if (name) {
return "" + (this.first) + (meth) + ".apply(" + (obj) + ", " + (this.compileSplatArguments(o)) + ")" + (this.last); 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; return CallNode;
})(); })();
@@ -576,9 +628,9 @@
AccessorNode.prototype["class"] = 'AccessorNode'; AccessorNode.prototype["class"] = 'AccessorNode';
AccessorNode.prototype.children = ['name']; AccessorNode.prototype.children = ['name'];
AccessorNode.prototype.compileNode = function(o) { AccessorNode.prototype.compileNode = function(o) {
var name, namePart; var _base, name, namePart;
name = this.name.compile(o); 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)); namePart = name.match(IS_STRING) ? ("[" + (name) + "]") : ("." + (name));
return this.prototype + namePart; return this.prototype + namePart;
}; };
@@ -595,8 +647,8 @@
IndexNode.prototype["class"] = 'IndexNode'; IndexNode.prototype["class"] = 'IndexNode';
IndexNode.prototype.children = ['index']; IndexNode.prototype.children = ['index'];
IndexNode.prototype.compileNode = function(o) { IndexNode.prototype.compileNode = function(o) {
var idx, prefix; var _base, idx, prefix;
o.chainRoot.wrapped || (o.chainRoot.wrapped = this.soakNode); (_base = o.chainRoot).wrapped || (_base.wrapped = this.soakNode);
idx = this.index.compile(o); idx = this.index.compile(o);
prefix = this.proto ? '.prototype' : ''; prefix = this.proto ? '.prototype' : '';
return "" + (prefix) + "[" + (idx) + "]"; return "" + (prefix) + "[" + (idx) + "]";
@@ -774,14 +826,14 @@
this.objects = _arg; this.objects = _arg;
ArrayNode.__super__.constructor.call(this); ArrayNode.__super__.constructor.call(this);
this.objects || (this.objects = []); this.objects || (this.objects = []);
this.compileSplatLiteral = function(o) {
return SplatNode.compileSplattedArray.call(this, this.objects, o);
};
return this; return this;
}; };
__extends(ArrayNode, BaseNode); __extends(ArrayNode, BaseNode);
ArrayNode.prototype["class"] = 'ArrayNode'; ArrayNode.prototype["class"] = 'ArrayNode';
ArrayNode.prototype.children = ['objects']; ArrayNode.prototype.children = ['objects'];
ArrayNode.prototype.compileSplatLiteral = function(o) {
return SplatNode.compileSplattedArray(this.objects, o);
};
ArrayNode.prototype.compileNode = function(o) { ArrayNode.prototype.compileNode = function(o) {
var _len, _ref2, code, i, obj, objects; var _len, _ref2, code, i, obj, objects;
o.indent = this.idt(1); o.indent = this.idt(1);
@@ -898,8 +950,7 @@
return this; return this;
}; };
__extends(AssignNode, BaseNode); __extends(AssignNode, BaseNode);
AssignNode.prototype.PROTO_ASSIGN = /^(\S+)\.prototype/; AssignNode.prototype.METHOD_DEF = /^(?:(\S+)\.prototype\.)?([$A-Za-z_][$\w]*)$/;
AssignNode.prototype.LEADING_DOT = /^\.(?:prototype\.)?/;
AssignNode.prototype["class"] = 'AssignNode'; AssignNode.prototype["class"] = 'AssignNode';
AssignNode.prototype.children = ['variable', 'value']; AssignNode.prototype.children = ['variable', 'value'];
AssignNode.prototype.topSensitive = YES; AssignNode.prototype.topSensitive = YES;
@@ -907,7 +958,7 @@
return this.variable instanceof ValueNode; return this.variable instanceof ValueNode;
}; };
AssignNode.prototype.compileNode = function(o) { 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 (isValue = this.isValue()) {
if (this.variable.isArray() || this.variable.isObject()) { if (this.variable.isArray() || this.variable.isObject()) {
return this.compilePatternMatch(o); return this.compilePatternMatch(o);
@@ -915,20 +966,16 @@
if (this.variable.isSplice()) { if (this.variable.isSplice()) {
return this.compileSplice(o); return this.compileSplice(o);
} }
if (node = ValueNode.unfoldSoak(o, this, 'variable')) {
return node.compile(o);
}
} }
top = del(o, 'top'); top = del(o, 'top');
stmt = del(o, 'asStatement'); stmt = del(o, 'asStatement');
name = this.variable.compile(o); name = this.variable.compile(o);
end = isValue ? this.variable.last.replace(this.LEADING_DOT, '') : name; if (this.value instanceof CodeNode && (match = this.METHOD_DEF.exec(name))) {
match = name.match(this.PROTO_ASSIGN); this.value.name = match[2];
proto = match && match[1]; this.value.klass = match[1];
if (this.value instanceof CodeNode) {
if (IDENTIFIER.test(end)) {
this.value.name = end;
}
if (proto) {
this.value.proto = proto;
}
} }
val = this.value.compile(o); val = this.value.compile(o);
if (this.context === 'object') { if (this.context === 'object') {
@@ -1005,17 +1052,15 @@
return top || this.parenthetical ? code : ("(" + (code) + ")"); return top || this.parenthetical ? code : ("(" + (code) + ")");
}; };
AssignNode.prototype.compileSplice = function(o) { AssignNode.prototype.compileSplice = function(o) {
var from, l, name, plus, range, to, val; var from, name, plus, range, ref, to, val;
name = this.variable.compile(merge(o, { range = this.variable.properties.pop().range;
onlyFirst: true name = this.variable.compile(o);
}));
l = this.variable.properties.length;
range = this.variable.properties[l - 1].range;
plus = range.exclusive ? '' : ' + 1'; plus = range.exclusive ? '' : ' + 1';
from = range.from ? range.from.compile(o) : '0'; from = range.from ? range.from.compile(o) : '0';
to = range.to ? range.to.compile(o) + ' - ' + from + plus : ("" + (name) + ".length"); to = range.to ? range.to.compile(o) + ' - ' + from + plus : ("" + (name) + ".length");
ref = o.scope.freeVariable('ref');
val = this.value.compile(o); 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; return AssignNode;
})(); })();
@@ -1025,7 +1070,7 @@
this.params = _arg; this.params = _arg;
CodeNode.__super__.constructor.call(this); CodeNode.__super__.constructor.call(this);
this.params || (this.params = []); this.params || (this.params = []);
this.body || (this.body = (new Expressions)); this.body || (this.body = new Expressions);
this.bound = tag === 'boundfunc'; this.bound = tag === 'boundfunc';
if (this.bound) { if (this.bound) {
this.context = 'this'; this.context = 'this';
@@ -1143,8 +1188,7 @@
SplatNode.prototype["class"] = 'SplatNode'; SplatNode.prototype["class"] = 'SplatNode';
SplatNode.prototype.children = ['name']; SplatNode.prototype.children = ['name'];
SplatNode.prototype.compileNode = function(o) { SplatNode.prototype.compileNode = function(o) {
var _ref2; return (this.index != null) ? this.compileParam(o) : this.name.compile(o);
return (typeof (_ref2 = this.index) !== "undefined" && _ref2 !== null) ? this.compileParam(o) : this.name.compile(o);
}; };
SplatNode.prototype.compileParam = function(o) { SplatNode.prototype.compileParam = function(o) {
var _len, _ref2, assign, end, idx, len, name, pos, trailing, variadic; var _len, _ref2, assign, end, idx, len, name, pos, trailing, variadic;
@@ -1205,14 +1249,14 @@
exports.WhileNode = (function() { exports.WhileNode = (function() {
WhileNode = function(condition, opts) { WhileNode = function(condition, opts) {
WhileNode.__super__.constructor.call(this); WhileNode.__super__.constructor.call(this);
if (opts == null ? undefined : opts.invert) { if (((opts != null) ? opts.invert : null)) {
if (condition instanceof OpNode) { if (condition instanceof OpNode) {
condition = new ParentheticalNode(condition); condition = new ParentheticalNode(condition);
} }
condition = new OpNode('!', condition); condition = new OpNode('!', condition);
} }
this.condition = condition; this.condition = condition;
this.guard = opts == null ? undefined : opts.guard; this.guard = ((opts != null) ? opts.guard : null);
return this; return this;
}; };
__extends(WhileNode, BaseNode); __extends(WhileNode, BaseNode);
@@ -1315,6 +1359,10 @@
return OpNode.__super__.toString.call(this, idt, this["class"] + ' ' + this.operator); return OpNode.__super__.toString.call(this, idt, this["class"] + ' ' + this.operator);
}; };
OpNode.prototype.compileNode = function(o) { 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()) { if (this.isChainable() && this.first.unwrap() instanceof OpNode && this.first.unwrap().isChainable()) {
return this.compileChain(o); return this.compileChain(o);
} }
@@ -1343,7 +1391,10 @@
return "(" + (first) + ") && (" + (shared) + " " + (this.operator) + " " + (second) + ")"; return "(" + (first) + ") && (" + (shared) + " " + (this.operator) + " " + (second) + ")";
}; };
OpNode.prototype.compileAssignment = function(o) { 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, { _ref2 = this.first.compileReference(o, {
precompile: true, precompile: true,
assignment: true assignment: true
@@ -1361,9 +1412,15 @@
return "" + (first) + " " + (this.operator.substr(0, 2)) + " (" + (firstVar) + " = " + (second) + ")"; return "" + (first) + " " + (this.operator.substr(0, 2)) + " (" + (firstVar) + " = " + (second) + ")";
}; };
OpNode.prototype.compileExistence = function(o) { OpNode.prototype.compileExistence = function(o) {
var _ref2, ref, test; var fst, ref;
_ref2 = ExistenceNode.compileTest(o, this.first), test = _ref2[0], ref = _ref2[1]; if (this.first.isComplex()) {
return "" + (test) + " ? " + (ref) + " : " + (this.second.compile(o)); 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) { OpNode.prototype.compileUnary = function(o) {
var parts, space; var parts, space;
@@ -1479,20 +1536,13 @@
ExistenceNode.prototype["class"] = 'ExistenceNode'; ExistenceNode.prototype["class"] = 'ExistenceNode';
ExistenceNode.prototype.children = ['expression']; ExistenceNode.prototype.children = ['expression'];
ExistenceNode.prototype.compileNode = function(o) { ExistenceNode.prototype.compileNode = function(o) {
var test; var code;
test = ExistenceNode.compileTest(o, this.expression)[0]; code = this.expression.compile(o);
return this.parenthetical ? test.slice(1, -1) : test; code = IDENTIFIER.test(code) && !o.scope.check(code) ? ("typeof " + (code) + " !== \"undefined\" && " + (code) + " !== null") : ("" + (code) + " != null");
}; return this.parenthetical ? code : ("(" + (code) + ")");
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];
}; };
return ExistenceNode; return ExistenceNode;
}).call(this); })();
exports.ParentheticalNode = (function() { exports.ParentheticalNode = (function() {
ParentheticalNode = function(_arg) { ParentheticalNode = function(_arg) {
this.expression = _arg; this.expression = _arg;
@@ -1742,10 +1792,12 @@
IfNode.prototype.children = ['condition', 'body', 'elseBody', 'assigner']; IfNode.prototype.children = ['condition', 'body', 'elseBody', 'assigner'];
IfNode.prototype.topSensitive = YES; IfNode.prototype.topSensitive = YES;
IfNode.prototype.bodyNode = function() { 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() { 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) { IfNode.prototype.addElse = function(elseBody, statement) {
if (this.isChain) { if (this.isChain) {
@@ -1757,7 +1809,7 @@
return this; return this;
}; };
IfNode.prototype.isStatement = function(o) { 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) { IfNode.prototype.compileCondition = function(o) {
var _i, _len, _ref2, _result, cond, conditions; var _i, _len, _ref2, _result, cond, conditions;

View File

@@ -68,7 +68,7 @@
if (tuple.length < 3) { if (tuple.length < 3) {
tuple.unshift(null); tuple.unshift(null);
} }
return buildRule.apply(this, tuple); return buildRule.apply(buildRule, tuple);
})()); })());
} }
return _result; return _result;

View File

@@ -54,7 +54,7 @@
}; };
Rewriter.prototype.adjustComments = function() { Rewriter.prototype.adjustComments = function() {
return this.scanTokens(function(token, i) { return this.scanTokens(function(token, i) {
var _ref, after, before, post, prev; var _ref, _this, after, before, post, prev;
if (token[0] !== 'HERECOMMENT') { if (token[0] !== 'HERECOMMENT') {
return 1; return 1;
} }
@@ -68,7 +68,7 @@
} }
} else if (prev && !('TERMINATOR' === (_ref = prev[0]) || 'INDENT' === _ref || 'OUTDENT' === _ref)) { } else if (prev && !('TERMINATOR' === (_ref = prev[0]) || 'INDENT' === _ref || 'OUTDENT' === _ref)) {
if (post && post[0] === 'TERMINATOR' && after && after[0] === 'OUTDENT') { 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') { if (this.tokens[i + 2][0] !== 'TERMINATOR') {
this.tokens.splice(i + 2, 0, ['TERMINATOR', "\n", prev[2]]); this.tokens.splice(i + 2, 0, ['TERMINATOR', "\n", prev[2]]);
} }
@@ -219,13 +219,13 @@
}; };
Rewriter.prototype.addImplicitIndentation = function() { Rewriter.prototype.addImplicitIndentation = function() {
return this.scanTokens(function(token, i) { 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') { 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; return 2;
} }
if (token[0] === 'CATCH' && (this.tag(i + 2) === 'TERMINATOR' || this.tag(i + 2) === 'FINALLY')) { 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; return 4;
} }
if (include(SINGLE_LINERS, token[0]) && this.tag(i + 1) !== 'INDENT' && !(token[0] === 'ELSE' && this.tag(i + 1) === 'IF')) { 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; debt[mtag] += 1;
val = [oppos, mtag === 'INDENT' ? match[1] : oppos]; 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); this.tokens.splice(i + 3, 0, val);
stack.push(match); stack.push(match);
} else { } else {

View File

@@ -40,7 +40,7 @@ exports.BaseNode = class BaseNode
# depending on whether it's being used as part of a larger expression, or is a # depending on whether it's being used as part of a larger expression, or is a
# top-level statement within the function body. # top-level statement within the function body.
compile: (o) -> compile: (o) ->
@options = merge o or {} @options = if o then merge o else {}
@tab = o.indent @tab = o.indent
del @options, 'chainRoot' unless this instanceof AccessorNode or this instanceof IndexNode del @options, 'chainRoot' unless this instanceof AccessorNode or this instanceof IndexNode
top = if @topSensitive() then @options.top else del @options, 'top' 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, # in multiple places, ensure that the expression is only ever evaluated once,
# by assigning it to a temporary variable. # by assigning it to a temporary variable.
compileReference: (o, options) -> compileReference: (o, options) ->
options or= {} pair = unless @isComplex()
pair = if not @isComplex()
[this, this] [this, this]
else if this instanceof ValueNode and options.assignment
this.cacheIndexes(o)
else else
reference = literal o.scope.freeVariable 'ref' reference = literal o.scope.freeVariable 'ref'
compiled = new AssignNode reference, this compiled = new AssignNode reference, this
[compiled, reference] [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 pair
# Convenience method to grab the current indentation level, plus tabbing in. # 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 # Convenience for the most common use of contains. Does the node contain
# a pure statement? # a pure statement?
containsPureStatement: -> 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. # Perform an in-order traversal of the AST. Crosses scope boundaries.
traverse: (block) -> @traverseChildren true, block traverse: (block) -> @traverseChildren true, block
@@ -133,8 +130,10 @@ exports.BaseNode = class BaseNode
traverseChildren: (crossScope, func) -> traverseChildren: (crossScope, func) ->
@eachChild (child) -> @eachChild (child) ->
func.apply(this, arguments) return false if func(child) is false
child.traverseChildren(crossScope, func) if child instanceof BaseNode 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 # Default implementations of the common node properties and methods. Nodes
# will override these with custom logic, if needed. # 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. # Some boolean checks for the benefit of other nodes.
isArray: -> isArray: ->
@base instanceof ArrayNode and not @hasProperties() @base instanceof ArrayNode and not @properties.length
isObject: -> isObject: ->
@base instanceof ObjectNode and not @hasProperties() @base instanceof ObjectNode and not @properties.length
isSplice: -> isSplice: ->
last(@properties) instanceof SliceNode last(@properties) instanceof SliceNode
@@ -323,7 +322,8 @@ exports.ValueNode = class ValueNode extends BaseNode
@base.isComplex() or @hasProperties() @base.isComplex() or @hasProperties()
makeReturn: -> 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 # The value can be unwrapped as its inner node, if there are no attached
# properties. # properties.
@@ -337,20 +337,24 @@ exports.ValueNode = class ValueNode extends BaseNode
isNumber: -> isNumber: ->
@base instanceof LiteralNode and NUMBER.test @base.value @base instanceof LiteralNode and NUMBER.test @base.value
# If the value node has indexes containing function calls, and the value node # A reference has base part (`this` value) and name part.
# needs to be used twice, in compound assignment ... then we need to cache # We cache them separately for compiling complex expressions.
# the value of the indexes. # `a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c`
cacheIndexes: (o) -> cacheReference: (o) ->
copy = new ValueNode @base, @properties[0..] name = last @properties
if @base.isComplex() if not @base.isComplex() and @properties.length < 2 and
[@base, copy.base] = @base.compileReference o not name?.isComplex()
for prop, i in copy.properties return [this, this] # `a` `a.b`
if prop instanceof IndexNode and prop.index.isComplex() base = new ValueNode @base, @properties.slice 0, -1
[index, indexVar] = prop.index.compileReference o if base.isComplex() # `a().b`
this.properties[i] = first = new IndexNode index bref = literal o.scope.freeVariable 'base'
copy.properties[i] = new IndexNode indexVar base = new ValueNode new ParentheticalNode new AssignNode bref, base
first.soakNode = yes if prop.soakNode return [base, bref] unless name # `a()`
[this, copy] 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. # Override compile to unwrap the value when possible.
compile: (o) -> compile: (o) ->
@@ -361,41 +365,46 @@ exports.ValueNode = class ValueNode extends BaseNode
# operators `?.` interspersed. Then we have to take care not to accidentally # operators `?.` interspersed. Then we have to take care not to accidentally
# evaluate a anything twice when building the soak chain. # evaluate a anything twice when building the soak chain.
compileNode: (o) -> compileNode: (o) ->
only = del o, 'onlyFirst' return ex.compile o if ex = @unfoldSoak o
op = @tags.operation props = @properties
props = if only then @properties[0...-1] else @properties
o.chainRoot or= this 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 @base.parenthetical = yes if @parenthetical and not props.length
baseline = @base.compile o code = @base.compile o
baseline = "(#{baseline})" if @hasProperties() and (@base instanceof ObjectNode or @isNumber()) if props[0] instanceof AccessorNode and @isNumber() or
complete = @last = baseline o.top and @base instanceof ObjectNode
code = "(#{code})"
(code += prop.compile o) for prop in props
return code
for prop, i in props # Unfold a soak into an `IfNode`: `a?.b` -> `a.b if a?`
@source = baseline unfoldSoak: (o) ->
if prop.soakNode if @base.soakNode
if i is 0 and @base.isComplex() Array::push.apply @base.body.properties, @properties
temp = o.scope.freeVariable 'ref' return @base
complete = "(#{ baseline = temp } = (#{complete}))" for prop, i in @properties when prop.soakNode
complete = if i is 0 and not o.scope.check complete prop.soakNode = off
"(typeof #{complete} === \"undefined\" || #{baseline} === null)" fst = new ValueNode @base, @properties.slice 0, i
else snd = new ValueNode @base, @properties.slice i
"#{complete} == null" if fst.isComplex()
complete += ' ? undefined : ' + baseline += prop.compile o ref = literal o.scope.freeVariable 'ref'
else fst = new ParentheticalNode new AssignNode ref, fst
part = prop.compile(o) snd.base = ref
baseline += if hasSoak and prop.isComplex() ifn = new IfNode new ExistenceNode(fst), snd, operation: yes
copy.properties[i].compile o ifn.soakNode = on
else return ifn
part null
complete += part
@last = part
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 #### CommentNode
@@ -429,9 +438,9 @@ exports.CallNode = class CallNode extends BaseNode
@isSuper = variable is 'super' @isSuper = variable is 'super'
@variable = if @isSuper then null else variable @variable = if @isSuper then null else variable
@args or= [] @args or= []
@first = @last = ''
@compileSplatArguments = (o) -> compileSplatArguments: (o) ->
SplatNode.compileSplattedArray.call(this, @args, o) SplatNode.compileSplattedArray @args, o
# Tag this invocation as creating a new instance. # Tag this invocation as creating a new instance.
newInstance: -> newInstance: ->
@@ -443,42 +452,58 @@ exports.CallNode = class CallNode extends BaseNode
# Grab the reference to the superclass' implementation of the current method. # Grab the reference to the superclass' implementation of the current method.
superReference: (o) -> superReference: (o) ->
throw new Error "cannot call super outside of a function" unless o.scope.method {method} = o.scope
methname = o.scope.method.name throw Error "cannot call super outside of a function" unless method
meth = if o.scope.method.proto {name} = method
"#{o.scope.method.proto}.__super__.#{methname}" throw Error "cannot call super on an anonymous function." unless name
else if methname if method.klass
"#{methname}.__super__.constructor" "#{method.klass}.__super__.#{name}"
else throw new Error "cannot call super on an anonymous function." 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. # Compile a vanilla function call.
compileNode: (o) -> compileNode: (o) ->
o.chainRoot = this unless o.chainRoot return node.compile o if node = @unfoldSoak o
op = @tags.operation o.chainRoot or= this
if @exist if @exist
if @variable instanceof ValueNode and if val = @variable
last(@variable.properties) instanceof AccessorNode val = new ValueNode val unless val instanceof ValueNode
methodAccessor = @variable.properties.pop() [left, rite] = val.cacheReference o
[first, meth] = @variable.compileReference o rite = new CallNode rite, @args
@first = new ValueNode(first, [methodAccessor]).compile o
@meth = new ValueNode(meth, [methodAccessor]).compile o
else else
[@first, @meth] = @variable.compileReference o, precompile: yes left = literal @superReference o
@first = "(typeof #{@first} === \"function\" ? " rite = new CallNode new ValueNode(left), @args
@last = " : undefined)" rite.isNew = @isNew
else if @variable left = "typeof #{ left.compile o } !== \"function\""
@meth = @variable.compile o rite = rite.compile o
return "(#{left} ? undefined : #{rite})"
for arg in @args when arg instanceof SplatNode for arg in @args when arg instanceof SplatNode
code = @compileSplat(o) return @compileSplat o
if not code args = ((arg.parenthetical = on) and arg.compile o for arg in @args).join ', '
args = for arg in @args if @isSuper
arg.parenthetical = true @compileSuper args, o
arg.compile o else
code = if @isSuper "#{@prefix()}#{@variable.compile o}(#{args})"
@compileSuper(args.join(', '), o)
else
"#{@first}#{@prefix()}#{@meth}(#{ args.join(', ') })#{@last}"
if op and @variable and @variable.wrapped then "(#{code})" else code
# `super()` is converted into a call against the superclass's implementation # `super()` is converted into a call against the superclass's implementation
# of the current function. # 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 # 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. # inner constructor in order to be able to pass the varargs.
compileSplat: (o) -> compileSplat: (o) ->
meth = @meth or @superReference(o) splatargs = @compileSplatArguments o
obj = @variable and @variable.source or 'this' return "#{ @superReference o }.apply(this, #{splatargs})" if @isSuper
unless IDENTIFIER.test(obj) or NUMBER.test(obj) unless @isNew
temp = o.scope.freeVariable 'ref' base = new ValueNode base unless (base = @variable) instanceof ValueNode
obj = temp if (name = base.properties.pop()) and base.isComplex()
meth = "(#{temp} = #{ @variable.source })#{ @variable.last }" ref = o.scope.freeVariable 'this'
if @isNew fun = "(#{ref} = #{ base.compile o })#{ name.compile o }"
mentionsArgs = no else
for arg in @args fun = ref = base.compile o
arg.contains (n) -> mentionsArgs or= n instanceof LiteralNode and (n.value is 'arguments') fun += name.compile o if name
utility 'extends' return "#{fun}.apply(#{ref}, #{splatargs})"
a = o.scope.freeVariable 'ctor' call = 'call(this)'
b = o.scope.freeVariable 'ref' argvar = (n) -> n instanceof LiteralNode and n.value is 'arguments'
c = o.scope.freeVariable 'result' for arg in @args when arg.contains argvar
""" call = 'apply(this, arguments)'
#{@first}(function() { break
#{@idt(1)}var ctor = function(){}; a = o.scope.freeVariable 'ctor'
#{@idt(1)}__extends(ctor, #{a} = #{meth}); b = o.scope.freeVariable 'ref'
#{@idt(1)}return typeof (#{c} = #{a}.apply(#{b} = new ctor, #{ @compileSplatArguments(o) })) === "object" ? #{c} : #{b}; c = o.scope.freeVariable 'result'
#{@tab}}).#{ if mentionsArgs then 'apply(this, arguments)' else 'call(this)'}#{@last} """
""" (function() {
else #{idt = @idt 1}var ctor = function() {};
"#{@first}#{meth}.apply(#{obj}, #{ @compileSplatArguments(o) })#{@last}" #{idt}#{utility 'extends'}(ctor, #{a} = #{ @variable.compile o });
#{idt}return typeof (#{c} = #{a}.apply(#{b} = new ctor, #{splatargs})) === "object" ? #{c} : #{b};
#{@tab}}).#{call}
"""
#### ExtendsNode #### ExtendsNode
@@ -706,8 +734,9 @@ exports.ArrayNode = class ArrayNode extends BaseNode
constructor: (@objects) -> constructor: (@objects) ->
super() super()
@objects or= [] @objects or= []
@compileSplatLiteral = (o) ->
SplatNode.compileSplattedArray.call(this, @objects, o) compileSplatLiteral: (o) ->
SplatNode.compileSplattedArray @objects, o
compileNode: (o) -> compileNode: (o) ->
o.indent = @idt 1 o.indent = @idt 1
@@ -807,9 +836,8 @@ exports.ClassNode = class ClassNode extends BaseNode
# property of an object -- including within object literals. # property of an object -- including within object literals.
exports.AssignNode = class AssignNode extends BaseNode exports.AssignNode = class AssignNode extends BaseNode
# Matchers for detecting prototype assignments. # Matchers for detecting class/method names
PROTO_ASSIGN: /^(\S+)\.prototype/ METHOD_DEF: /^(?:(\S+)\.prototype\.)?([$A-Za-z_][$\w]*)$/
LEADING_DOT: /^\.(?:prototype\.)?/
class: 'AssignNode' class: 'AssignNode'
children: ['variable', 'value'] children: ['variable', 'value']
@@ -830,15 +858,13 @@ exports.AssignNode = class AssignNode extends BaseNode
if isValue = @isValue() if isValue = @isValue()
return @compilePatternMatch(o) if @variable.isArray() or @variable.isObject() return @compilePatternMatch(o) if @variable.isArray() or @variable.isObject()
return @compileSplice(o) if @variable.isSplice() return @compileSplice(o) if @variable.isSplice()
return node.compile o if node = ValueNode.unfoldSoak o, this, 'variable'
top = del o, 'top' top = del o, 'top'
stmt = del o, 'asStatement' stmt = del o, 'asStatement'
name = @variable.compile(o) name = @variable.compile(o)
end = if isValue then @variable.last.replace(@LEADING_DOT, '') else name if @value instanceof CodeNode and match = @METHOD_DEF.exec name
match = name.match(@PROTO_ASSIGN) @value.name = match[2]
proto = match and match[1] @value.klass = match[1]
if @value instanceof CodeNode
@value.name = end if IDENTIFIER.test end
@value.proto = proto if proto
val = @value.compile o val = @value.compile o
return "#{name}: #{val}" if @context is 'object' return "#{name}: #{val}" if @context is 'object'
o.scope.find name unless isValue and (@variable.hasProperties() or @variable.namespaced) 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 # Compile the assignment from an array splice literal, using JavaScript's
# `Array#splice` method. # `Array#splice` method.
compileSplice: (o) -> compileSplice: (o) ->
name = @variable.compile merge o, onlyFirst: true {range} = @variable.properties.pop()
l = @variable.properties.length name = @variable.compile o
range = @variable.properties[l - 1].range
plus = if range.exclusive then '' else ' + 1' plus = if range.exclusive then '' else ' + 1'
from = if range.from then range.from.compile(o) else '0' 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" to = if range.to then range.to.compile(o) + ' - ' + from + plus else "#{name}.length"
ref = o.scope.freeVariable 'ref'
val = @value.compile(o) val = @value.compile(o)
"[].splice.apply(#{name}, [#{from}, #{to}].concat(#{val}))" "([].splice.apply(#{name}, [#{from}, #{to}].concat(#{ref} = #{val})), #{ref})"
#### CodeNode #### CodeNode
@@ -1172,6 +1198,7 @@ exports.OpNode = class OpNode extends BaseNode
super(idt, @class + ' ' + @operator) super(idt, @class + ' ' + @operator)
compileNode: (o) -> 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 @compileChain(o) if @isChainable() and @first.unwrap() instanceof OpNode and @first.unwrap().isChainable()
return @compileAssignment(o) if indexOf(@ASSIGNMENT, @operator) >= 0 return @compileAssignment(o) if indexOf(@ASSIGNMENT, @operator) >= 0
return @compileUnary(o) if @isUnary() 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 # operands are only evaluated once, even though we have to reference them
# more than once. # more than once.
compileAssignment: (o) -> 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 [first, firstVar] = @first.compileReference o, precompile: yes, assignment: yes
second = @second.compile o second = @second.compile o
second = "(#{second})" if @second instanceof OpNode 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 '?=' return "#{first} = #{ ExistenceNode.compileTest(o, literal(firstVar))[0] } ? #{firstVar} : #{second}" if @operator is '?='
"#{first} #{ @operator.substr(0, 2) } (#{firstVar} = #{second})" "#{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) -> compileExistence: (o) ->
[test, ref] = ExistenceNode.compileTest(o, @first) if @first.isComplex()
"#{test} ? #{ref} : #{ @second.compile(o) }" 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**. # Compile a unary **OpNode**.
compileUnary: (o) -> compileUnary: (o) ->
@@ -1302,19 +1336,12 @@ exports.ExistenceNode = class ExistenceNode extends BaseNode
super() super()
compileNode: (o) -> compileNode: (o) ->
test = ExistenceNode.compileTest(o, @expression)[0] code = @expression.compile o
if @parenthetical then test.slice 1, -1 else test code = if IDENTIFIER.test(code) and not o.scope.check code
"typeof #{code} !== \"undefined\" && #{code} !== null"
# 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)"
else else
"(typeof #{first} !== \"undefined\" && #{second} !== null)" "#{code} != null"
[first, second] if @parenthetical then code else "(#{code})"
#### ParentheticalNode #### ParentheticalNode
@@ -1413,8 +1440,8 @@ exports.ForNode = class ForNode extends BaseNode
sourcePart = source.compileVariables(o) sourcePart = source.compileVariables(o)
forPart = source.compile merge o, index: ivar, step: @step forPart = source.compile merge o, index: ivar, step: @step
else else
svar = scope.freeVariable 'ref' svar = scope.freeVariable 'ref'
sourcePart = "#{svar} = #{ @source.compile(o) };" sourcePart = "#{svar} = #{ @source.compile(o) };"
if @pattern if @pattern
namePart = new AssignNode(@name, literal("#{svar}[#{ivar}]")).compile(merge o, {indent: @idt(1), top: true, keepLevel: yes}) + '\n' namePart = new AssignNode(@name, literal("#{svar}[#{ivar}]")).compile(merge o, {indent: @idt(1), top: true, keepLevel: yes}) + '\n'
else else

View File

@@ -1,45 +1,26 @@
num = 10 num = 10
num -= 5 num -= 5
eq num, 5
ok num is 5
num = -3
ok num is -3
num = +3
ok num is 3
num *= 10 num *= 10
eq num, 50
ok num is 30
num /= 10 num /= 10
eq num, 5
ok num is 3 num %= 3
eq num, 2
val = false val = false
val ||= 'value' val ||= 'value'
val ||= 'eulav'
eq val, 'value'
ok val is 'value' val &&= 'rehto'
val &&= 'other' val &&= 'other'
eq val, 'other'
ok val is 'other'
val = null val = null
val ?= 'value' val ?= 'value'
val ?= 'eulav'
ok val is 'value' eq val, 'value'
val = 6
val = -(10)
ok val is -10
val -= (10)
ok val is -20

View File

@@ -37,45 +37,33 @@ ok(if getNextNode()? then true else false)
# Existence chains, soaking up undefined properties: # Existence chains, soaking up undefined properties:
obj = { obj =
prop: "hello" prop: "hello"
}
ok obj?.prop is "hello" eq obj?.prop, "hello"
eq obj?['prop'], "hello"
ok obj?['prop'] is "hello" eq obj.prop?.length, 5
eq obj?.prop?['length'], 5
ok obj.prop?.length is 5 eq obj?.prop?.non?.existent?.property, null
ok obj?['prop']?['length'] is 5
ok obj?.prop?.non?.existent?.property is undefined
ok obj?['non']?['existent'].property is undefined
# Soaks and caches method calls as well. # Soaks and caches method calls as well.
arr = ["--", "----"] arr = ["--", "----"]
ok arr.pop()?.length is 4 eq arr.pop()?.length, 4
ok arr.pop()?.length is 2 eq arr.pop()?.length, 2
ok arr.pop()?.length is undefined eq arr.pop()?.length, null
ok arr[0]?.length is undefined eq arr.pop()?.length?.non?.existent()?.property, null
ok arr.pop()?.length?.non?.existent()?.property is undefined
# Soaks method calls safely. # Soaks method calls safely.
value = undefined value = null
result = value?.toString().toLowerCase() eq value?.toString().toLowerCase(), null
ok result is undefined
value = 10 value = 10
result = value?.toString().toLowerCase() eq value?.toString().toLowerCase(), '10'
ok result is '10' eq process.exit.nothing?.property() or 101, 101
ok(process.exit.nothing?.property() or 101)
counter = 0 counter = 0
func = -> func = ->
@@ -89,9 +77,8 @@ ok obj[func()]()[func()]()[func()]()?.value is 25
ok counter is 3 ok counter is 3
# Soaks inner values.
ident = (obj) -> obj ident = (obj) -> obj
ok ident(non?.existent().method()) is undefined eq ident(non?.existent().method()), null, 'soaks inner values'
# Soaks constructor invocations. # Soaks constructor invocations.
@@ -104,14 +91,10 @@ ok (new Foo())?.bar is 'bat'
ok a is 1 ok a is 1
# Safely existence test on soaks. ok not value?.property?, 'safely checks existence on soaks'
result = not value?.property?
ok result
# Safely calls values off of non-existent variables. eq nothing?.value, null, 'safely calls values off of non-existent variables'
result = nothing?.value
ok result is undefined
# Assign to the result of an exsitential operation with a minus. # 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 plus1, 41)?() is 42
ok (maybe_close 'string', 41)?() is undefined 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

View File

@@ -49,7 +49,7 @@ medalists contenders..., 'Tim', 'Moe', 'Jim'
ok last is 'Jim' ok last is 'Jim'
obj = { obj =
name: 'moe' name: 'moe'
accessor: (args...) -> accessor: (args...) ->
[@name].concat(args).join(' ') [@name].concat(args).join(' ')
@@ -58,7 +58,6 @@ obj = {
@accessor(args...) @accessor(args...)
index: 0 index: 0
0: {method: -> this is obj[0]} 0: {method: -> this is obj[0]}
}
ok obj.getNames() is 'moe jane ted' ok obj.getNames() is 'moe jane ted'
ok obj[obj.index++].method([]...), 'should cache base value' ok obj[obj.index++].method([]...), 'should cache base value'