diff --git a/lib/command.js b/lib/command.js index 215bbb7b..eafa5805 100644 --- a/lib/command.js +++ b/lib/command.js @@ -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); diff --git a/lib/lexer.js b/lib/lexer.js index 68c8bccf..84a5a301 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -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())) { diff --git a/lib/nodes.js b/lib/nodes.js index 55dc28ff..79146abf 100644 --- a/lib/nodes.js +++ b/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; diff --git a/lib/optparse.js b/lib/optparse.js index 1a96d35e..79cf8610 100755 --- a/lib/optparse.js +++ b/lib/optparse.js @@ -68,7 +68,7 @@ if (tuple.length < 3) { tuple.unshift(null); } - return buildRule.apply(this, tuple); + return buildRule.apply(buildRule, tuple); })()); } return _result; diff --git a/lib/rewriter.js b/lib/rewriter.js index 0f3e9b32..f80c0746 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -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 { diff --git a/src/nodes.coffee b/src/nodes.coffee index d5b8d20e..5869cf56 100644 --- a/src/nodes.coffee +++ b/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 diff --git a/test/test_compound_assignment.coffee b/test/test_compound_assignment.coffee index 5556e872..cc2fe583 100644 --- a/test/test_compound_assignment.coffee +++ b/test/test_compound_assignment.coffee @@ -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 \ No newline at end of file +val ?= 'eulav' +eq val, 'value' diff --git a/test/test_existence.coffee b/test/test_existence.coffee index 21ad73a3..6a859068 100644 --- a/test/test_existence.coffee +++ b/test/test_existence.coffee @@ -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 diff --git a/test/test_splats.coffee b/test/test_splats.coffee index c447d269..f882058d 100644 --- a/test/test_splats.coffee +++ b/test/test_splats.coffee @@ -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'