diff --git a/lib/nodes.js b/lib/nodes.js index 674f2c49..04c87f2a 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -26,16 +26,16 @@ starts = _a.starts; ends = _a.ends; exports.BaseNode = (function() { - BaseNode = function() {}; + BaseNode = function() { + this.tags = {}; + return this; + }; BaseNode.prototype.compile = function(o) { var closure, top; this.options = merge(o || {}); this.tab = o.indent; - if (!(this instanceof ValueNode || this instanceof CallNode)) { - del(this.options, 'operation'); - if (!(this instanceof AccessorNode || this instanceof IndexNode)) { - del(this.options, 'chainRoot'); - } + if (!(this instanceof AccessorNode || this instanceof IndexNode)) { + del(this.options, 'chainRoot'); } top = this.topSensitive() ? this.options.top : del(this.options, 'top'); closure = this.isStatement(o) && !this.isPureStatement() && !top && !this.options.asStatement && !(this instanceof CommentNode) && !this.containsPureStatement(); @@ -170,6 +170,7 @@ })(); exports.Expressions = (function() { Expressions = function(nodes) { + Expressions.__superClass__.constructor.call(this); this.expressions = compact(flatten(nodes || [])); return this; }; @@ -259,6 +260,7 @@ exports.LiteralNode = (function() { LiteralNode = function(_b) { this.value = _b; + LiteralNode.__superClass__.constructor.call(this); return this; }; __extends(LiteralNode, BaseNode); @@ -284,6 +286,7 @@ exports.ReturnNode = (function() { ReturnNode = function(_b) { this.expression = _b; + ReturnNode.__superClass__.constructor.call(this); return this; }; __extends(ReturnNode, BaseNode); @@ -318,6 +321,7 @@ ValueNode = function(_b, _c) { this.properties = _c; this.base = _b; + ValueNode.__superClass__.constructor.call(this); this.properties || (this.properties = []); return this; }; @@ -380,7 +384,7 @@ ValueNode.prototype.compileNode = function(o) { var _b, _c, _d, baseline, complete, i, only, op, props; only = del(o, 'onlyFirst'); - op = del(o, 'operation'); + op = this.tags.operation; props = only ? this.properties.slice(0, this.properties.length - 1) : this.properties; o.chainRoot || (o.chainRoot = this); if (this.parenthetical && !props.length) { @@ -422,6 +426,7 @@ exports.CommentNode = (function() { CommentNode = function(_b) { this.lines = _b; + CommentNode.__superClass__.constructor.call(this); return this; }; __extends(CommentNode, BaseNode); @@ -442,6 +447,7 @@ exports.CallNode = (function() { CallNode = function(variable, _b) { this.args = _b; + CallNode.__superClass__.constructor.call(this); this.isNew = false; this.isSuper = variable === 'super'; this.variable = this.isSuper ? null : variable; @@ -527,6 +533,7 @@ ExtendsNode = function(_b, _c) { this.parent = _c; this.child = _b; + ExtendsNode.__superClass__.constructor.call(this); return this; }; __extends(ExtendsNode, BaseNode); @@ -542,6 +549,7 @@ exports.AccessorNode = (function() { AccessorNode = function(_b, tag) { this.name = _b; + AccessorNode.__superClass__.constructor.call(this); this.prototype = tag === 'prototype' ? '.prototype' : ''; this.soakNode = tag === 'soak'; return this; @@ -561,6 +569,7 @@ exports.IndexNode = (function() { IndexNode = function(_b) { this.index = _b; + IndexNode.__superClass__.constructor.call(this); return this; }; __extends(IndexNode, BaseNode); @@ -579,6 +588,7 @@ RangeNode = function(_b, _c, exclusive) { this.to = _c; this.from = _b; + RangeNode.__superClass__.constructor.call(this); this.exclusive = !!exclusive; this.equals = this.exclusive ? '' : '='; return this; @@ -675,6 +685,7 @@ exports.SliceNode = (function() { SliceNode = function(_b) { this.range = _b; + SliceNode.__superClass__.constructor.call(this); return this; }; __extends(SliceNode, BaseNode); @@ -691,6 +702,7 @@ })(); exports.ObjectNode = (function() { ObjectNode = function(props) { + ObjectNode.__superClass__.constructor.call(this); this.objects = (this.properties = props || []); return this; }; @@ -745,6 +757,7 @@ exports.ArrayNode = (function() { ArrayNode = function(_b) { this.objects = _b; + ArrayNode.__superClass__.constructor.call(this); this.objects || (this.objects = []); this.compileSplatLiteral = function(o) { return SplatNode.compileSplattedArray.call(this, this.objects, o); @@ -782,6 +795,7 @@ this.properties = _d; this.parent = _c; this.variable = _b; + ClassNode.__superClass__.constructor.call(this); this.properties || (this.properties = []); this.returns = false; return this; @@ -865,6 +879,7 @@ this.context = _d; this.value = _c; this.variable = _b; + AssignNode.__superClass__.constructor.call(this); return this; }; __extends(AssignNode, BaseNode); @@ -982,6 +997,7 @@ CodeNode = function(_b, _c, tag) { this.body = _c; this.params = _b; + CodeNode.__superClass__.constructor.call(this); this.params || (this.params = []); this.body || (this.body = new Expressions()); this.bound = tag === 'boundfunc'; @@ -1082,6 +1098,7 @@ this.splat = _d; this.attach = _c; this.name = _b; + ParamNode.__superClass__.constructor.call(this); this.value = literal(this.name); return this; }; @@ -1098,6 +1115,7 @@ })(); exports.SplatNode = (function() { SplatNode = function(name) { + SplatNode.__superClass__.constructor.call(this); if (!(name.compile)) { name = literal(name); } @@ -1168,6 +1186,7 @@ }).call(this); exports.WhileNode = (function() { WhileNode = function(condition, opts) { + WhileNode.__superClass__.constructor.call(this); if (opts && opts.invert) { if (condition instanceof OpNode) { condition = new ParentheticalNode(condition); @@ -1230,11 +1249,16 @@ this.second = _d; this.first = _c; this.operator = _b; + OpNode.__superClass__.constructor.call(this); this.operator = this.CONVERSIONS[this.operator] || this.operator; this.flip = !!flip; if (this.first instanceof ValueNode && this.first.base instanceof ObjectNode) { this.first = new ParentheticalNode(this.first); } + this.first.tags.operation = true; + if (this.second) { + this.second.tags.operation = true; + } return this; }; __extends(OpNode, BaseNode); @@ -1271,7 +1295,6 @@ return OpNode.__superClass__.toString.call(this, idt, this["class"] + ' ' + this.operator); }; OpNode.prototype.compileNode = function(o) { - o.operation = true; if (this.isChainable() && this.first.unwrap() instanceof OpNode && this.first.unwrap().isChainable()) { return this.compileChain(o); } @@ -1348,6 +1371,7 @@ InNode = function(_b, _c) { this.array = _c; this.object = _b; + InNode.__superClass__.constructor.call(this); return this; }; __extends(InNode, BaseNode); @@ -1398,6 +1422,7 @@ this.recovery = _d; this.error = _c; this.attempt = _b; + TryNode.__superClass__.constructor.call(this); return this; }; __extends(TryNode, BaseNode); @@ -1430,6 +1455,7 @@ exports.ThrowNode = (function() { ThrowNode = function(_b) { this.expression = _b; + ThrowNode.__superClass__.constructor.call(this); return this; }; __extends(ThrowNode, BaseNode); @@ -1449,6 +1475,7 @@ exports.ExistenceNode = (function() { ExistenceNode = function(_b) { this.expression = _b; + ExistenceNode.__superClass__.constructor.call(this); return this; }; __extends(ExistenceNode, BaseNode); @@ -1473,6 +1500,7 @@ exports.ParentheticalNode = (function() { ParentheticalNode = function(_b) { this.expression = _b; + ParentheticalNode.__superClass__.constructor.call(this); return this; }; __extends(ParentheticalNode, BaseNode); @@ -1505,6 +1533,7 @@ this.index = _d; this.name = _c; this.body = _b; + ForNode.__superClass__.constructor.call(this); this.index || (this.index = null); this.source = source.source; this.guard = source.guard; @@ -1770,11 +1799,15 @@ return "" + (ifPart) + (elsePart); }; IfNode.prototype.compileTernary = function(o) { - var elsePart, ifPart; - o.operation = true; + var code, elsePart, ifPart; + this.bodyNode().tags.operation = (this.condition.tags.operation = true); + if (this.elseBody) { + this.elseBodyNode().tags.operation = true; + } ifPart = this.condition.compile(o) + ' ? ' + this.bodyNode().compile(o); elsePart = this.elseBody ? this.elseBodyNode().compile(o) : 'null'; - return "" + (ifPart) + " : " + (elsePart); + code = ("" + (ifPart) + " : " + (elsePart)); + return this.tags.operation ? ("(" + (code) + ")") : code; }; return IfNode; })(); diff --git a/src/nodes.coffee b/src/nodes.coffee index db6913fc..4a9d736e 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -29,6 +29,9 @@ else # scope, and indentation level. exports.BaseNode = class BaseNode + constructor: -> + @tags = {} + # Common logic for determining whether to wrap this node in a closure before # compiling it, or to compile directly. We need to wrap if this node is a # *statement*, and it's not a *pureStatement*, and we're not at @@ -42,9 +45,7 @@ exports.BaseNode = class BaseNode compile: (o) -> @options = merge o or {} @tab = o.indent - unless this instanceof ValueNode or this instanceof CallNode - del @options, 'operation' - 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' closure = @isStatement(o) and not @isPureStatement() and not top and not @options.asStatement and not (this instanceof CommentNode) and @@ -157,6 +158,7 @@ exports.Expressions = class Expressions extends BaseNode isStatement: -> yes constructor: (nodes) -> + super() @expressions = compact flatten nodes or [] # Tack an expression on to the end of this expression list. @@ -239,6 +241,7 @@ exports.LiteralNode = class LiteralNode extends BaseNode class: 'LiteralNode' constructor: (@value) -> + super() makeReturn: -> if @isStatement() then this else super() @@ -269,6 +272,7 @@ exports.ReturnNode = class ReturnNode extends BaseNode children: ['expression'] constructor: (@expression) -> + super() makeReturn: -> this @@ -293,6 +297,7 @@ exports.ValueNode = class ValueNode extends BaseNode # A **ValueNode** has a base and a list of property accesses. constructor: (@base, @properties) -> + super() @properties or= [] # Add a property access to the list. @@ -351,7 +356,7 @@ exports.ValueNode = class ValueNode extends BaseNode # evaluate a anything twice when building the soak chain. compileNode: (o) -> only = del o, 'onlyFirst' - op = del o, 'operation' + op = @tags.operation props = if only then @properties[0...@properties.length - 1] else @properties o.chainRoot or= this @base.parenthetical = yes if @parenthetical and not props.length @@ -388,6 +393,7 @@ exports.CommentNode = class CommentNode extends BaseNode isStatement: -> yes constructor: (@lines) -> + super() makeReturn: -> this @@ -406,6 +412,7 @@ exports.CallNode = class CallNode extends BaseNode children: ['variable', 'args'] constructor: (variable, @args) -> + super() @isNew = false @isSuper = variable is 'super' @variable = if @isSuper then null else variable @@ -484,6 +491,7 @@ exports.ExtendsNode = class ExtendsNode extends BaseNode children: ['child', 'parent'] constructor: (@child, @parent) -> + super() # Hooks one constructor into another's prototype chain. compileNode: (o) -> @@ -500,6 +508,7 @@ exports.AccessorNode = class AccessorNode extends BaseNode children: ['name'] constructor: (@name, tag) -> + super() @prototype = if tag is 'prototype' then '.prototype' else '' @soakNode = tag is 'soak' @@ -518,6 +527,7 @@ exports.IndexNode = class IndexNode extends BaseNode children: ['index'] constructor: (@index) -> + super() compileNode: (o) -> o.chainRoot.wrapped or= @soakNode @@ -536,6 +546,7 @@ exports.RangeNode = class RangeNode extends BaseNode children: ['from', 'to'] constructor: (@from, @to, exclusive) -> + super() @exclusive = !!exclusive @equals = if @exclusive then '' else '=' @@ -607,6 +618,7 @@ exports.SliceNode = class SliceNode extends BaseNode children: ['range'] constructor: (@range) -> + super() compileNode: (o) -> from = @range.from.compile(o) @@ -625,6 +637,7 @@ exports.ObjectNode = class ObjectNode extends BaseNode topSensitive: -> true constructor: (props) -> + super() @objects = @properties = props or [] compileNode: (o) -> @@ -652,6 +665,7 @@ exports.ArrayNode = class ArrayNode extends BaseNode children: ['objects'] constructor: (@objects) -> + super() @objects or= [] @compileSplatLiteral = (o) -> SplatNode.compileSplattedArray.call(this, @objects, o) @@ -687,6 +701,7 @@ exports.ClassNode = class ClassNode extends BaseNode # Initialize a **ClassNode** with its name, an optional superclass, and a # list of prototype property assignments. constructor: (@variable, @parent, @properties) -> + super() @properties or= [] @returns = false @@ -758,6 +773,7 @@ exports.AssignNode = class AssignNode extends BaseNode children: ['variable', 'value'] constructor: (@variable, @value, @context) -> + super() topSensitive: -> true @@ -857,6 +873,7 @@ exports.CodeNode = class CodeNode extends BaseNode children: ['params', 'body'] constructor: (@params, @body, tag) -> + super() @params or= [] @body or= new Expressions @bound = tag is 'boundfunc' @@ -927,6 +944,7 @@ exports.ParamNode = class ParamNode extends BaseNode children: ['name'] constructor: (@name, @attach, @splat) -> + super() @value = literal @name compileNode: (o) -> @@ -945,6 +963,7 @@ exports.SplatNode = class SplatNode extends BaseNode children: ['name'] constructor: (name) -> + super() name = literal(name) unless name.compile @name = name @@ -1009,6 +1028,7 @@ exports.WhileNode = class WhileNode extends BaseNode isStatement: -> yes constructor: (condition, opts) -> + super() if opts and opts.invert condition = new ParentheticalNode condition if condition instanceof OpNode condition = new OpNode('!', condition) @@ -1078,10 +1098,13 @@ exports.OpNode = class OpNode extends BaseNode children: ['first', 'second'] constructor: (@operator, @first, @second, flip) -> + super() @operator = @CONVERSIONS[@operator] or @operator @flip = !!flip if @first instanceof ValueNode and @first.base instanceof ObjectNode @first = new ParentheticalNode @first + @first.tags.operation = yes + @second.tags.operation = yes if @second isUnary: -> not @second @@ -1102,7 +1125,6 @@ exports.OpNode = class OpNode extends BaseNode super(idt, @class + ' ' + @operator) compileNode: (o) -> - o.operation = true 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() @@ -1153,6 +1175,7 @@ exports.InNode = class InNode extends BaseNode children: ['object', 'array'] constructor: (@object, @array) -> + super() isArray: -> @array instanceof ValueNode and @array.isArray() @@ -1182,6 +1205,7 @@ exports.TryNode = class TryNode extends BaseNode isStatement: -> yes constructor: (@attempt, @error, @recovery, @ensure) -> + super() makeReturn: -> @attempt = @attempt.makeReturn() if @attempt @@ -1209,6 +1233,7 @@ exports.ThrowNode = class ThrowNode extends BaseNode isStatement: -> yes constructor: (@expression) -> + super() # A **ThrowNode** is already a return, of sorts... makeReturn: -> @@ -1228,6 +1253,7 @@ exports.ExistenceNode = class ExistenceNode extends BaseNode children: ['expression'] constructor: (@expression) -> + super() compileNode: (o) -> test = ExistenceNode.compileTest(o, @expression)[0] @@ -1253,6 +1279,7 @@ exports.ParentheticalNode = class ParentheticalNode extends BaseNode children: ['expression'] constructor: (@expression) -> + super() isStatement: (o) -> @expression.isStatement(o) @@ -1287,6 +1314,7 @@ exports.ForNode = class ForNode extends BaseNode isStatement: -> yes constructor: (@body, source, @name, @index) -> + super() @index or= null @source = source.source @guard = source.guard @@ -1379,7 +1407,7 @@ exports.IfNode = class IfNode extends BaseNode topSensitive: -> true constructor: (@condition, @body, @tags) -> - @tags or= {} + @tags or= {} if @tags.invert if @condition instanceof OpNode and @condition.isInvertable() @condition.invert() @@ -1472,10 +1500,12 @@ exports.IfNode = class IfNode extends BaseNode # Compile the IfNode as a ternary operator. compileTernary: (o) -> - o.operation = true + @bodyNode().tags.operation = @condition.tags.operation = yes + @elseBodyNode().tags.operation = yes if @elseBody ifPart = @condition.compile(o) + ' ? ' + @bodyNode().compile(o) elsePart = if @elseBody then @elseBodyNode().compile(o) else 'null' - "#{ifPart} : #{elsePart}" + code = "#{ifPart} : #{elsePart}" + if @tags.operation then "(#{code})" else code # Faux-Nodes # ---------- diff --git a/test/test_if.coffee b/test/test_if.coffee index 7e478958..a79c073c 100644 --- a/test/test_if.coffee +++ b/test/test_if.coffee @@ -90,3 +90,9 @@ try ok yes catch e ok no + + +# If-to-ternary as part of a larger operation requires parens. +x = 1 +result = x + if false then 10 else 1 +ok result is 2