nodes: refactored to reduce parens

This commit is contained in:
satyr 2010-10-20 19:53:41 +09:00
parent b0a4b7ab85
commit e2a6f292a2
3 changed files with 147 additions and 164 deletions

View File

@ -413,7 +413,7 @@
if (!herecomment) { if (!herecomment) {
while (match = HEREDOC_INDENT.exec(doc)) { while (match = HEREDOC_INDENT.exec(doc)) {
attempt = match[1]; attempt = match[1];
if (indent === null || (0 < (_ref2 = attempt.length)) && (_ref2 < indent.length)) { if (indent === null || 0 < (_ref2 = attempt.length) && _ref2 < indent.length) {
indent = attempt; indent = attempt;
} }
} }
@ -528,7 +528,7 @@
i += expr.length; i += expr.length;
pi = i + 1; pi = i + 1;
} }
if ((i > pi) && (pi < str.length)) { if (i > pi && pi < str.length) {
tokens.push(['TO_BE_STRING', str.slice(pi)]); tokens.push(['TO_BE_STRING', str.slice(pi)]);
} }
if (regex) { if (regex) {

View File

@ -41,7 +41,7 @@
Base.prototype.compileClosure = function(o) { Base.prototype.compileClosure = function(o) {
o.sharedScope = o.scope; o.sharedScope = o.scope;
if (this.containsPureStatement()) { if (this.containsPureStatement()) {
throw new Error('cannot include a pure statement in an expression.'); throw SyntaxError('cannot include a pure statement in an expression.');
} }
return Closure.wrap(this).compile(o); return Closure.wrap(this).compile(o);
}; };
@ -109,11 +109,10 @@
return '\n' + idt + klass + children; return '\n' + idt + klass + children;
}; };
Base.prototype.eachChild = function(func) { Base.prototype.eachChild = function(func) {
var _i, _j, _len, _len2, _ref2, _ref3, _result, attr, child; var _i, _j, _len, _len2, _ref2, _ref3, attr, child;
if (!this.children) { if (!this.children) {
return; return;
} }
_result = [];
for (_i = 0, _len = (_ref2 = this.children).length; _i < _len; _i++) { for (_i = 0, _len = (_ref2 = this.children).length; _i < _len; _i++) {
attr = _ref2[_i]; attr = _ref2[_i];
if (this[attr]) { if (this[attr]) {
@ -125,7 +124,7 @@
} }
} }
} }
return _result; return this;
}; };
Base.prototype.collectChildren = function() { Base.prototype.collectChildren = function() {
var nodes; var nodes;
@ -292,14 +291,14 @@
return Return; return Return;
})(); })();
__extends(Return, Base); __extends(Return, Base);
Return.prototype.children = ['expression'];
Return.prototype.isStatement = YES; Return.prototype.isStatement = YES;
Return.prototype.isPureStatement = YES; Return.prototype.isPureStatement = YES;
Return.prototype.children = ['expression'];
Return.prototype.makeReturn = THIS; Return.prototype.makeReturn = THIS;
Return.prototype.compile = function(o) { Return.prototype.compile = function(o) {
var _ref2, expr; var _ref2, expr;
expr = (((_ref2 = this.expression) != null) ? _ref2.makeReturn() : undefined); expr = (((_ref2 = this.expression) != null) ? _ref2.makeReturn() : undefined);
if (expr && (!(expr instanceof Return))) { if (expr && !(expr instanceof Return)) {
return expr.compile(o); return expr.compile(o);
} }
return Return.__super__.compile.call(this, o); return Return.__super__.compile.call(this, o);
@ -319,11 +318,10 @@
})(); })();
exports.Value = (function() { exports.Value = (function() {
Value = (function() { Value = (function() {
function Value(_arg, _arg2, tag) { function Value(_arg, props, tag) {
this.properties = _arg2;
this.base = _arg; this.base = _arg;
Value.__super__.constructor.call(this); Value.__super__.constructor.call(this);
this.properties || (this.properties = []); this.properties = props || [];
if (tag) { if (tag) {
this.tags[tag] = true; this.tags[tag] = true;
} }
@ -485,11 +483,11 @@
var method, name; var method, name;
method = o.scope.method; method = o.scope.method;
if (!method) { if (!method) {
throw Error("cannot call super outside of a function."); throw SyntaxError('cannot call super outside of a function.');
} }
name = method.name; name = method.name;
if (!name) { if (!name) {
throw Error("cannot call super on an anonymous function."); throw SyntaxError('cannot call super on an anonymous function.');
} }
return method.klass ? ("" + (method.klass) + ".__super__." + name) : ("" + name + ".__super__.constructor"); return method.klass ? ("" + (method.klass) + ".__super__." + name) : ("" + name + ".__super__.constructor");
}; };
@ -606,9 +604,7 @@
__extends(Extends, Base); __extends(Extends, Base);
Extends.prototype.children = ['child', 'parent']; Extends.prototype.children = ['child', 'parent'];
Extends.prototype.compileNode = function(o) { Extends.prototype.compileNode = function(o) {
var ref; return new Call(new Value(new Literal(utility('extends'))), [this.child, this.parent]).compile(o);
ref = new Value(new Literal(utility('extends')));
return (new Call(ref, [this.child, this.parent])).compile(o);
}; };
return Extends; return Extends;
})(); })();
@ -626,10 +622,9 @@
__extends(Accessor, Base); __extends(Accessor, Base);
Accessor.prototype.children = ['name']; Accessor.prototype.children = ['name'];
Accessor.prototype.compileNode = function(o) { Accessor.prototype.compileNode = function(o) {
var name, namePart; var name;
name = this.name.compile(o); name = this.name.compile(o);
namePart = name.match(IS_STRING) ? ("[" + name + "]") : ("." + name); return this.prototype + (IS_STRING.test(name) ? ("[" + name + "]") : ("." + name));
return this.prototype + namePart;
}; };
Accessor.prototype.isComplex = NO; Accessor.prototype.isComplex = NO;
return Accessor; return Accessor;
@ -646,10 +641,7 @@
__extends(Index, Base); __extends(Index, Base);
Index.prototype.children = ['index']; Index.prototype.children = ['index'];
Index.prototype.compileNode = function(o) { Index.prototype.compileNode = function(o) {
var idx, prefix; return "" + (this.proto ? '.prototype' : '') + "[" + (this.index.compile(o)) + "]";
idx = this.index.compile(o);
prefix = this.proto ? '.prototype' : '';
return "" + prefix + "[" + idx + "]";
}; };
Index.prototype.isComplex = function() { Index.prototype.isComplex = function() {
return this.index.isComplex(); return this.index.isComplex();
@ -828,10 +820,9 @@
})(); })();
exports.ArrayLiteral = (function() { exports.ArrayLiteral = (function() {
ArrayLiteral = (function() { ArrayLiteral = (function() {
function ArrayLiteral(_arg) { function ArrayLiteral(objs) {
this.objects = _arg;
ArrayLiteral.__super__.constructor.call(this); ArrayLiteral.__super__.constructor.call(this);
this.objects || (this.objects = []); this.objects = objs || [];
return this; return this;
}; };
return ArrayLiteral; return ArrayLiteral;
@ -873,12 +864,11 @@
})(); })();
exports.Class = (function() { exports.Class = (function() {
Class = (function() { Class = (function() {
function Class(variable, _arg, _arg2) { function Class(variable, _arg, props) {
this.properties = _arg2;
this.parent = _arg; this.parent = _arg;
Class.__super__.constructor.call(this); Class.__super__.constructor.call(this);
this.variable = variable === '__temp__' ? new Literal(variable) : variable; this.variable = variable === '__temp__' ? new Literal(variable) : variable;
this.properties || (this.properties = []); this.properties = props || [];
this.returns = false; this.returns = false;
return this; return this;
}; };
@ -922,7 +912,7 @@
func = new Code([], new Expressions([apply])); func = new Code([], new Expressions([apply]));
} }
if (func.bound) { if (func.bound) {
throw new Error("cannot define a constructor as a bound function."); throw SyntaxError('cannot define a constructor as a bound function.');
} }
func.name = className; func.name = className;
func.body.push(new Return(new Literal('this'))); func.body.push(new Return(new Literal('this')));
@ -955,7 +945,7 @@
} }
props.push(prop); props.push(prop);
} }
constructor.className = className.match(/[\w\d\$_]+$/); constructor.className = className.match(/[$\w]+$/);
if (me) { if (me) {
constructor.body.unshift(new Literal("" + me + " = this")); constructor.body.unshift(new Literal("" + me + " = this"));
} }
@ -1067,7 +1057,7 @@
} }
} }
if (!(obj instanceof Value || obj instanceof Splat)) { if (!(obj instanceof Value || obj instanceof Splat)) {
throw new Error('pattern matching must use only identifiers on the left-hand side.'); throw SyntaxError('pattern matching must use only identifiers on the left-hand side.');
} }
accessClass = isObject && IDENTIFIER.test(idx.value) ? Accessor : Index; accessClass = isObject && IDENTIFIER.test(idx.value) ? Accessor : Index;
if (!splat && obj instanceof Splat) { if (!splat && obj instanceof Splat) {
@ -1177,7 +1167,7 @@
} }
for (_i = 0, _len2 = params.length; _i < _len2; _i++) { for (_i = 0, _len2 = params.length; _i < _len2; _i++) {
param = params[_i]; param = params[_i];
(o.scope.parameter(param)); o.scope.parameter(param);
} }
comm = this.comment ? this.comment.compile(o) + '\n' : ''; comm = this.comment ? this.comment.compile(o) + '\n' : '';
if (this.className) { if (this.className) {
@ -1311,6 +1301,7 @@
})(); })();
__extends(While, Base); __extends(While, Base);
While.prototype.children = ['condition', 'guard', 'body']; While.prototype.children = ['condition', 'guard', 'body'];
While.prototype.topSensitive = YES;
While.prototype.isStatement = YES; While.prototype.isStatement = YES;
While.prototype.addBody = function(body) { While.prototype.addBody = function(body) {
this.body = body; this.body = body;
@ -1320,7 +1311,6 @@
this.returns = true; this.returns = true;
return this; return this;
}; };
While.prototype.topSensitive = YES;
While.prototype.compileNode = function(o) { While.prototype.compileNode = function(o) {
var cond, post, pre, rvar, set, top; var cond, post, pre, rvar, set, top;
top = del(o, 'top') && !this.returns; top = del(o, 'top') && !this.returns;
@ -1398,9 +1388,9 @@
return (_ref2 = this.operator, __indexOf.call(this.CHAINABLE, _ref2) >= 0); return (_ref2 = this.operator, __indexOf.call(this.CHAINABLE, _ref2) >= 0);
}; };
Op.prototype.invert = function() { Op.prototype.invert = function() {
var _ref2; var op;
if (((_ref2 = this.operator) === '===' || _ref2 === '!==')) { if (op = this.INVERSIONS[this.operator]) {
this.operator = this.INVERSIONS[this.operator]; this.operator = op;
return this; return this;
} else return this.second ? new Parens(this).invert() : Op.__super__.invert.call(this); } else return this.second ? new Parens(this).invert() : Op.__super__.invert.call(this);
}; };
@ -1425,11 +1415,9 @@
return "" + (this.first.compile(o)) + " " + (this.operator) + " " + (this.second.compile(o)); return "" + (this.first.compile(o)) + " " + (this.operator) + " " + (this.second.compile(o));
}; };
Op.prototype.compileChain = function(o) { Op.prototype.compileChain = function(o) {
var _ref2, _ref3, first, second, shared; var _ref2, shared;
shared = this.first.unwrap().second; _ref2 = this.first.unwrap().second.compileReference(o), this.first.second = _ref2[0], shared = _ref2[1];
_ref2 = shared.compileReference(o), this.first.second = _ref2[0], shared = _ref2[1]; return "" + (this.first.compile(o)) + " && " + (shared.compile(o)) + " " + (this.operator) + " " + (this.second.compile(o));
_ref3 = [this.first.compile(o), this.second.compile(o), shared.compile(o)], first = _ref3[0], second = _ref3[1], shared = _ref3[2];
return "(" + first + ") && (" + shared + " " + (this.operator) + " " + second + ")";
}; };
Op.prototype.compileExistence = function(o) { Op.prototype.compileExistence = function(o) {
var fst, ref; var fst, ref;
@ -1580,13 +1568,13 @@
})(); })();
__extends(Parens, Base); __extends(Parens, Base);
Parens.prototype.children = ['expression']; Parens.prototype.children = ['expression'];
Parens.prototype.topSensitive = YES;
Parens.prototype.isStatement = function(o) { Parens.prototype.isStatement = function(o) {
return this.expression.isStatement(o); return this.expression.isStatement(o);
}; };
Parens.prototype.isComplex = function() { Parens.prototype.isComplex = function() {
return this.expression.isComplex(); return this.expression.isComplex();
}; };
Parens.prototype.topSensitive = YES;
Parens.prototype.makeReturn = function() { Parens.prototype.makeReturn = function() {
return this.expression.makeReturn(); return this.expression.makeReturn();
}; };
@ -1619,10 +1607,10 @@
if (this.object) { if (this.object) {
_ref2 = [this.index, this.name], this.name = _ref2[0], this.index = _ref2[1]; _ref2 = [this.index, this.name], this.name = _ref2[0], this.index = _ref2[1];
} }
this.pattern = this.name instanceof Value;
if (this.index instanceof Value) { if (this.index instanceof Value) {
throw new Error('index cannot be a pattern matching expression'); throw SyntaxError('index cannot be a pattern matching expression');
} }
this.pattern = this.name instanceof Value;
this.returns = false; this.returns = false;
return this; return this;
}; };
@ -1630,8 +1618,8 @@
})(); })();
__extends(For, Base); __extends(For, Base);
For.prototype.children = ['body', 'source', 'guard']; For.prototype.children = ['body', 'source', 'guard'];
For.prototype.isStatement = YES;
For.prototype.topSensitive = YES; For.prototype.topSensitive = YES;
For.prototype.isStatement = YES;
For.prototype.makeReturn = function() { For.prototype.makeReturn = function() {
this.returns = true; this.returns = true;
return this; return this;
@ -1771,8 +1759,6 @@
this.cases = _arg2; this.cases = _arg2;
this.subject = _arg; this.subject = _arg;
Switch.__super__.constructor.call(this); Switch.__super__.constructor.call(this);
this.tags.subjectless = !this.subject;
this.subject || (this.subject = new Literal('true'));
return this; return this;
}; };
return Switch; return Switch;
@ -1792,28 +1778,27 @@
return this; return this;
}; };
Switch.prototype.compileNode = function(o) { Switch.prototype.compileNode = function(o) {
var _i, _j, _len, _len2, _ref2, _ref3, block, code, condition, conditions, exprs, idt, pair; var _i, _j, _len, _len2, _ref2, _ref3, _ref4, _ref5, block, code, condition, conditions, idt1, idt2;
idt = (o.indent = this.idt(2)); idt1 = this.idt(1);
idt2 = (o.indent = this.idt(2));
o.top = true; o.top = true;
code = ("" + (this.tab) + "switch (" + (this.subject.compile(o)) + ") {"); code = ("" + (this.tab) + "switch (" + ((((_ref2 = this.subject) != null) ? _ref2.compile(o) : undefined) || true) + ") {");
for (_i = 0, _len = (_ref2 = this.cases).length; _i < _len; _i++) { for (_i = 0, _len = (_ref3 = this.cases).length; _i < _len; _i++) {
pair = _ref2[_i]; _ref4 = _ref3[_i], conditions = _ref4[0], block = _ref4[1];
conditions = pair[0], block = pair[1]; for (_j = 0, _len2 = (_ref5 = flatten([conditions])).length; _j < _len2; _j++) {
exprs = block.expressions; condition = _ref5[_j];
for (_j = 0, _len2 = (_ref3 = flatten([conditions])).length; _j < _len2; _j++) { if (!this.subject) {
condition = _ref3[_j]; condition = condition.invert().invert();
if (this.tags.subjectless) {
condition = new Op('!!', new Parens(condition));
} }
code += ("\n" + (this.idt(1)) + "case " + (condition.compile(o)) + ":"); code += ("\n" + idt1 + "case " + (condition.compile(o)) + ":");
} }
code += ("\n" + (block.compile(o))); code += ("\n" + (block.compile(o)));
if (!(last(exprs) instanceof Return)) { if (!(last(block.expressions) instanceof Return)) {
code += ("\n" + idt + "break;"); code += ("\n" + idt2 + "break;");
} }
} }
if (this.otherwise) { if (this.otherwise) {
code += ("\n" + (this.idt(1)) + "default:\n" + (this.otherwise.compile(o))); code += ("\n" + idt1 + "default:\n" + (this.otherwise.compile(o)));
} }
code += ("\n" + (this.tab) + "}"); code += ("\n" + (this.tab) + "}");
return code; return code;
@ -1965,7 +1950,7 @@
TAB = ' '; TAB = ' ';
TRAILING_WHITESPACE = /[ \t]+$/gm; TRAILING_WHITESPACE = /[ \t]+$/gm;
IDENTIFIER = /^[$A-Za-z_][$\w]*$/; IDENTIFIER = /^[$A-Za-z_][$\w]*$/;
NUMBER = /^0x[\da-f]+|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?$/i; NUMBER = /^0x[\da-f]+$|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?$/i;
SIMPLENUM = /^[+-]?\d+$/; SIMPLENUM = /^[+-]?\d+$/;
IS_STRING = /^['"]/; IS_STRING = /^['"]/;
utility = function(name) { utility = function(name) {

View File

@ -52,7 +52,7 @@ exports.Base = class Base
# object with their parent closure, to preserve the expected lexical scope. # object with their parent closure, to preserve the expected lexical scope.
compileClosure: (o) -> compileClosure: (o) ->
o.sharedScope = o.scope o.sharedScope = o.scope
throw new Error 'cannot include a pure statement in an expression.' if @containsPureStatement() throw SyntaxError 'cannot include a pure statement in an expression.' if @containsPureStatement()
Closure.wrap(this).compile o Closure.wrap(this).compile o
# If the code generation wishes to use the result of a complex expression # If the code generation wishes to use the result of a complex expression
@ -115,6 +115,7 @@ exports.Base = class Base
for attr in @children when this[attr] for attr in @children when this[attr]
for child in flatten [this[attr]] for child in flatten [this[attr]]
return if func(child) is false return if func(child) is false
this
collectChildren: -> collectChildren: ->
nodes = [] nodes = []
@ -151,7 +152,8 @@ exports.Base = class Base
# `if`, `switch`, or `try`, and so on... # `if`, `switch`, or `try`, and so on...
exports.Expressions = class Expressions extends Base exports.Expressions = class Expressions extends Base
children: ['expressions'] children: ['expressions']
isStatement: YES isStatement: YES
constructor: (nodes) -> constructor: (nodes) ->
@ -212,7 +214,7 @@ exports.Expressions = class Expressions extends Base
if o.scope.hasAssignments this if o.scope.hasAssignments this
code = "#{@tab}var #{ o.scope.compiledAssignments().replace /\n/g, '$&' + @tab };\n#{code}" code = "#{@tab}var #{ o.scope.compiledAssignments().replace /\n/g, '$&' + @tab };\n#{code}"
if not o.globals and o.scope.hasDeclarations this if not o.globals and o.scope.hasDeclarations this
code = "#{@tab}var #{o.scope.compiledDeclarations()};\n#{code}" code = "#{@tab}var #{ o.scope.compiledDeclarations() };\n#{code}"
code code
# Compiles a single expression within the expressions body. If we need to # Compiles a single expression within the expressions body. If we need to
@ -257,9 +259,9 @@ exports.Literal = class Literal extends Base
assigns: (name) -> name is @value assigns: (name) -> name is @value
compileNode: (o) -> compileNode: (o) ->
idt = if @isStatement(o) then @idt() else '' idt = if @isStatement o then @idt() else ''
end = if @isStatement(o) then ';' else '' end = if @isStatement o then ';' else ''
val = if @isReserved() then "\"#{@value}\"" else @value val = if @isReserved() then "\"#{@value}\"" else @value
idt + val + end idt + val + end
toString: -> ' "' + @value + '"' toString: -> ' "' + @value + '"'
@ -270,9 +272,10 @@ exports.Literal = class Literal extends Base
# make sense. # make sense.
exports.Return = class Return extends Base exports.Return = class Return extends Base
isStatement: YES children: ['expression']
isPureStatement: YES
children: ['expression'] isStatement : YES
isPureStatement: YES
constructor: (@expression) -> constructor: (@expression) ->
super() super()
@ -281,7 +284,7 @@ exports.Return = class Return extends Base
compile: (o) -> compile: (o) ->
expr = @expression?.makeReturn() expr = @expression?.makeReturn()
return expr.compile o if expr and (expr not instanceof Return) return expr.compile o if expr and expr not instanceof Return
super o super o
compileNode: (o) -> compileNode: (o) ->
@ -300,9 +303,9 @@ exports.Value = class Value extends Base
children: ['base', 'properties'] children: ['base', 'properties']
# A **Value** has a base and a list of property accesses. # A **Value** has a base and a list of property accesses.
constructor: (@base, @properties, tag) -> constructor: (@base, props, tag) ->
super() super()
@properties or= [] @properties = props or []
@tags[tag] = yes if tag @tags[tag] = yes if tag
# Add a property access to the list. # Add a property access to the list.
@ -333,7 +336,6 @@ exports.Value = class Value extends Base
makeReturn: -> makeReturn: ->
if @properties.length 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.
unwrap: -> unwrap: ->
@ -429,7 +431,7 @@ exports.Call = class Call extends Base
@isNew = false @isNew = false
@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= []
compileSplatArguments: (o) -> compileSplatArguments: (o) ->
Splat.compileSplattedArray @args, o Splat.compileSplattedArray @args, o
@ -445,9 +447,9 @@ exports.Call = class Call extends Base
# 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) ->
{method} = o.scope {method} = o.scope
throw Error "cannot call super outside of a function." unless method throw SyntaxError 'cannot call super outside of a function.' unless method
{name} = method {name} = method
throw Error "cannot call super on an anonymous function." unless name throw SyntaxError 'cannot call super on an anonymous function.' unless name
if method.klass if method.klass
"#{method.klass}.__super__.#{name}" "#{method.klass}.__super__.#{name}"
else else
@ -542,8 +544,7 @@ exports.Extends = class Extends extends Base
# Hooks one constructor into another's prototype chain. # Hooks one constructor into another's prototype chain.
compileNode: (o) -> compileNode: (o) ->
ref = new Value new Literal utility 'extends' new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compile o
(new Call ref, [@child, @parent]).compile o
#### Accessor #### Accessor
@ -556,12 +557,11 @@ exports.Accessor = class Accessor extends Base
constructor: (@name, tag) -> constructor: (@name, tag) ->
super() super()
@prototype = if tag is 'prototype' then '.prototype' else '' @prototype = if tag is 'prototype' then '.prototype' else ''
@soakNode = tag is 'soak' @soakNode = tag is 'soak'
compileNode: (o) -> compileNode: (o) ->
name = @name.compile o name = @name.compile o
namePart = if name.match(IS_STRING) then "[#{name}]" else ".#{name}" @prototype + if IS_STRING.test(name) then "[#{name}]" else ".#{name}"
@prototype + namePart
isComplex: NO isComplex: NO
@ -576,9 +576,7 @@ exports.Index = class Index extends Base
super() super()
compileNode: (o) -> compileNode: (o) ->
idx = @index.compile o "#{ if @proto then '.prototype' else '' }[#{ @index.compile o }]"
prefix = if @proto then '.prototype' else ''
"#{prefix}[#{idx}]"
isComplex: -> @index.isComplex() isComplex: -> @index.isComplex()
@ -716,9 +714,9 @@ exports.ArrayLiteral = class ArrayLiteral extends Base
children: ['objects'] children: ['objects']
constructor: (@objects) -> constructor: (objs) ->
super() super()
@objects or= [] @objects = objs or []
compileSplatLiteral: (o) -> compileSplatLiteral: (o) ->
Splat.compileSplattedArray @objects, o Splat.compileSplattedArray @objects, o
@ -752,15 +750,16 @@ exports.ArrayLiteral = class ArrayLiteral extends Base
# The CoffeeScript class definition. # The CoffeeScript class definition.
exports.Class = class Class extends Base exports.Class = class Class extends Base
children: ['variable', 'parent', 'properties'] children: ['variable', 'parent', 'properties']
isStatement: YES isStatement: YES
# Initialize a **Class** with its name, an optional superclass, and a # Initialize a **Class** with its name, an optional superclass, and a
# list of prototype property assignments. # list of prototype property assignments.
constructor: (variable, @parent, @properties) -> constructor: (variable, @parent, props) ->
super() super()
@variable = if variable is '__temp__' then new Literal variable else variable @variable = if variable is '__temp__' then new Literal variable else variable
@properties or= [] @properties = props or []
@returns = false @returns = false
makeReturn: -> makeReturn: ->
@ -782,9 +781,9 @@ exports.Class = class Class extends Base
if @parent if @parent
applied = new Value @parent, [new Accessor new Literal 'apply'] applied = new Value @parent, [new Accessor new Literal 'apply']
constructor = new Code([], new Expressions([ constructor = new Code [], new Expressions [
new Call applied, [new Literal('this'), new Literal('arguments')] new Call applied, [new Literal('this'), new Literal('arguments')]
])) ]
else else
constructor = new Code [], new Expressions [new Return new Literal 'this'] constructor = new Code [], new Expressions [new Return new Literal 'this']
@ -794,9 +793,10 @@ exports.Class = class Class extends Base
if func not instanceof Code if func not instanceof Code
[func, ref] = func.compileReference o [func, ref] = func.compileReference o
props.push func if func isnt ref props.push func if func isnt ref
apply = new Call(new Value(ref, [new Accessor new Literal 'apply']), [new Literal('this'), new Literal('arguments')]) apply = new Call new Value(ref, [new Accessor new Literal 'apply']),
[new Literal('this'), new Literal('arguments')]
func = new Code [], new Expressions([apply]) func = new Code [], new Expressions([apply])
throw new Error "cannot define a constructor as a bound function." if func.bound throw SyntaxError 'cannot define a constructor as a bound function.' if func.bound
func.name = className func.name = className
func.body.push new Return new Literal 'this' func.body.push new Return new Literal 'this'
variable = new Value variable variable = new Value variable
@ -809,18 +809,18 @@ exports.Class = class Class extends Base
func.context = className func.context = className
else else
func.bound = false func.bound = false
constScope or= new Scope(o.scope, constructor.body, constructor) constScope or= new Scope o.scope, constructor.body, constructor
me or= constScope.freeVariable 'this' me or= constScope.freeVariable 'this'
pname = pvar.compile(o) pname = pvar.compile o
constructor.body.push new Return new Literal 'this' if constructor.body.empty() constructor.body.push new Return new Literal 'this' if constructor.body.empty()
constructor.body.unshift new Literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#{me}, arguments); }" constructor.body.unshift new Literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#{me}, arguments); }"
if pvar if pvar
access = if prop.context is 'this' then pvar.base.properties[0] else new Accessor(pvar, 'prototype') access = if prop.context is 'this' then pvar.base.properties[0] else new Accessor(pvar, 'prototype')
val = new Value variable, [access] val = new Value variable, [access]
prop = new Assign(val, func) prop = new Assign val, func
props.push prop props.push prop
constructor.className = className.match /[\w\d\$_]+$/ constructor.className = className.match /[$\w]+$/
constructor.body.unshift new Literal "#{me} = this" if me constructor.body.unshift new Literal "#{me} = this" if me
construct = @idt() + new Assign(variable, constructor).compile(merge o, sharedScope: constScope) + ';' construct = @idt() + new Assign(variable, constructor).compile(merge o, sharedScope: constScope) + ';'
props = if !props.empty() then '\n' + props.compile(o) else '' props = if !props.empty() then '\n' + props.compile(o) else ''
@ -841,11 +841,11 @@ exports.Assign = class Assign extends Base
children: ['variable', 'value'] children: ['variable', 'value']
topSensitive: YES
constructor: (@variable, @value, @context) -> constructor: (@variable, @value, @context) ->
super() super()
topSensitive: YES
# Compile an assignment, delegating to `compilePatternMatch` or # Compile an assignment, delegating to `compilePatternMatch` or
# `compileSplice` if appropriate. Keep track of the name of the base object # `compileSplice` if appropriate. Keep track of the name of the base object
# we've been assigned to, for correct internal references. If the variable # we've been assigned to, for correct internal references. If the variable
@ -911,7 +911,7 @@ exports.Assign = class Assign extends Base
# A shorthand `{a, b, @c} = val` pattern-match. # A shorthand `{a, b, @c} = val` pattern-match.
idx = if obj.tags.this then obj.properties[0].name else obj idx = if obj.tags.this then obj.properties[0].name else obj
unless obj instanceof Value or obj instanceof Splat unless obj instanceof Value or obj instanceof Splat
throw new Error 'pattern matching must use only identifiers on the left-hand side.' throw SyntaxError 'pattern matching must use only identifiers on the left-hand side.'
accessClass = if isObject and IDENTIFIER.test(idx.value) then Accessor else Index accessClass = if isObject and IDENTIFIER.test(idx.value) then Accessor else Index
if not splat and obj instanceof Splat if not splat and obj instanceof Splat
val = new Literal obj.compileValue o, valVar, i, olength - i - 1 val = new Literal obj.compileValue o, valVar, i, olength - i - 1
@ -972,7 +972,7 @@ exports.Code = class Code extends Base
top = del o, 'top' top = del o, 'top'
o.scope = sharedScope or new Scope(o.scope, @body, this) o.scope = sharedScope or new Scope(o.scope, @body, this)
o.top = true o.top = true
o.indent = @idt(1) o.indent = @idt 1
empty = @body.expressions.length is 0 empty = @body.expressions.length is 0
delete o.bare delete o.bare
delete o.globals delete o.globals
@ -998,9 +998,9 @@ exports.Code = class Code extends Base
else else
params.push param params.push param
o.scope.startLevel() o.scope.startLevel()
params = (param.compile(o) for param in params) params = param.compile(o) for param in params
@body.makeReturn() unless empty or @noReturn @body.makeReturn() unless empty or @noReturn
(o.scope.parameter(param)) for param in params o.scope.parameter param for param in params
comm = if @comment then @comment.compile(o) + '\n' else '' comm = if @comment then @comment.compile(o) + '\n' else ''
o.indent = @idt 2 if @className o.indent = @idt 2 if @className
code = if @body.expressions.length then "\n#{ @body.compileWithDeclarations(o) }\n" else '' code = if @body.expressions.length then "\n#{ @body.compileWithDeclarations(o) }\n" else ''
@ -1052,12 +1052,12 @@ exports.Splat = class Splat extends Base
assigns: (name) -> @name.assigns name assigns: (name) -> @name.assigns name
compileNode: (o) -> compileNode: (o) ->
if @index? then @compileParam(o) else @name.compile(o) if @index? then @compileParam o else @name.compile o
# Compiling a parameter splat means recovering the parameters that succeed # Compiling a parameter splat means recovering the parameters that succeed
# the splat in the parameter list, by slicing the arguments object. # the splat in the parameter list, by slicing the arguments object.
compileParam: (o) -> compileParam: (o) ->
name = @name.compile(o) name = @name.compile o
o.scope.find name o.scope.find name
end = '' end = ''
if @trailings.length if @trailings.length
@ -1107,8 +1107,10 @@ exports.Splat = class Splat extends Base
# flexibility or more speed than a comprehension can provide. # flexibility or more speed than a comprehension can provide.
exports.While = class While extends Base exports.While = class While extends Base
children: ['condition', 'guard', 'body'] children: ['condition', 'guard', 'body']
isStatement: YES
topSensitive: YES
isStatement : YES
constructor: (condition, opts) -> constructor: (condition, opts) ->
super() super()
@ -1123,8 +1125,6 @@ exports.While = class While extends Base
@returns = true @returns = true
this this
topSensitive: YES
# The main difference from a JavaScript *while* is that the CoffeeScript # The main difference from a JavaScript *while* is that the CoffeeScript
# *while* can be used as a part of a larger expression -- while loops may # *while* can be used as a part of a larger expression -- while loops may
# return an array containing the computed result of each iteration. # return an array containing the computed result of each iteration.
@ -1140,9 +1140,9 @@ exports.While = class While extends Base
set = "#{@tab}#{rvar} = [];\n" set = "#{@tab}#{rvar} = [];\n"
@body = Push.wrap(rvar, @body) if @body @body = Push.wrap(rvar, @body) if @body
pre = "#{set}#{@tab}while (#{cond})" pre = "#{set}#{@tab}while (#{cond})"
@body = Expressions.wrap([new If(@guard, @body)]) if @guard @body = Expressions.wrap [new If @guard, @body] if @guard
if @returns if @returns
post = '\n' + new Return(new Literal rvar).compile(merge(o, indent: @idt())) post = '\n' + new Return(new Literal rvar).compile merge o, indent: @idt()
else else
post = '' post = ''
"#{pre} {\n#{ @body.compile(o) }\n#{@tab}}#{post}" "#{pre} {\n#{ @body.compile(o) }\n#{@tab}}#{post}"
@ -1182,21 +1182,22 @@ exports.Op = class Op extends Base
first = new Parens first if first instanceof Code and first.bound first = new Parens first if first instanceof Code and first.bound
super() super()
@operator = @CONVERSIONS[op] or op @operator = @CONVERSIONS[op] or op
(@first = first ).tags.operation = yes (@first = first ).tags.operation = yes
(@second = second).tags.operation = yes if second (@second = second).tags.operation = yes if second
@flip = !!flip @flip = !!flip
isUnary: -> isUnary: ->
not @second not @second
isComplex: -> @operator isnt '!' or @first.isComplex() isComplex: ->
@operator isnt '!' or @first.isComplex()
isChainable: -> isChainable: ->
@operator in @CHAINABLE @operator in @CHAINABLE
invert: -> invert: ->
if @operator in ['===', '!=='] if op = @INVERSIONS[@operator]
@operator = @INVERSIONS[@operator] @operator = op
this this
else if @second else if @second
new Parens(this).invert() new Parens(this).invert()
@ -1204,7 +1205,7 @@ exports.Op = class Op extends Base
super() super()
toString: (idt) -> toString: (idt) ->
super(idt, @constructor.name + ' ' + @operator) super idt, @constructor.name + ' ' + @operator
compileNode: (o) -> compileNode: (o) ->
if @isUnary() if @isUnary()
@ -1221,10 +1222,8 @@ exports.Op = class Op extends Base
# bin/coffee -e "puts 50 < 65 > 10" # bin/coffee -e "puts 50 < 65 > 10"
# true # true
compileChain: (o) -> compileChain: (o) ->
shared = @first.unwrap().second [@first.second, shared] = @first.unwrap().second.compileReference o
[@first.second, shared] = shared.compileReference o "#{ @first.compile o } && #{ shared.compile o } #{@operator} #{ @second.compile o }"
[first, second, shared] = [@first.compile(o), @second.compile(o), shared.compile(o)]
"(#{first}) && (#{shared} #{@operator} #{second})"
compileExistence: (o) -> compileExistence: (o) ->
if @first.isComplex() if @first.isComplex()
@ -1253,13 +1252,13 @@ exports.In = class In extends Base
@array instanceof Value and @array.isArray() @array instanceof Value and @array.isArray()
compileNode: (o) -> compileNode: (o) ->
if @isArray() then @compileOrTest(o) else @compileLoopTest(o) if @isArray() then @compileOrTest o else @compileLoopTest o
compileOrTest: (o) -> compileOrTest: (o) ->
[obj1, obj2] = @object.compileReference o, precompile: yes [obj1, obj2] = @object.compileReference o, precompile: yes
tests = for item, i in @array.base.objects tests = for item, i in @array.base.objects
"#{if i then obj2 else obj1} === #{item.compile(o)}" "#{ if i then obj2 else obj1 } === #{ item.compile o }"
"(#{tests.join(' || ')})" "(#{ tests.join ' || ' })"
compileLoopTest: (o) -> compileLoopTest: (o) ->
[obj1, obj2] = @object.compileReference merge(o, top: yes), precompile: yes [obj1, obj2] = @object.compileReference merge(o, top: yes), precompile: yes
@ -1272,14 +1271,15 @@ exports.In = class In extends Base
# A classic *try/catch/finally* block. # A classic *try/catch/finally* block.
exports.Try = class Try extends Base exports.Try = class Try extends Base
children: ['attempt', 'recovery', 'ensure'] children: ['attempt', 'recovery', 'ensure']
isStatement: YES isStatement: YES
constructor: (@attempt, @error, @recovery, @ensure) -> constructor: (@attempt, @error, @recovery, @ensure) ->
super() super()
makeReturn: -> makeReturn: ->
@attempt = @attempt.makeReturn() if @attempt @attempt = @attempt .makeReturn() if @attempt
@recovery = @recovery.makeReturn() if @recovery @recovery = @recovery.makeReturn() if @recovery
this this
@ -1289,11 +1289,11 @@ exports.Try = class Try extends Base
o.indent = @idt 1 o.indent = @idt 1
o.top = true o.top = true
attemptPart = @attempt.compile(o) attemptPart = @attempt.compile(o)
errorPart = if @error then " (#{ @error.compile(o) }) " else ' ' errorPart = if @error then " (#{ @error.compile o }) " else ' '
catchPart = if @recovery catchPart = if @recovery
" catch#{errorPart}{\n#{ @recovery.compile(o) }\n#{@tab}}" " catch#{errorPart}{\n#{ @recovery.compile o }\n#{@tab}}"
else unless @ensure or @recovery then ' catch (_e) {}' else '' else unless @ensure or @recovery then ' catch (_e) {}' else ''
finallyPart = (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o)) + "\n#{@tab}}" finallyPart = (@ensure or '') and ' finally {\n' + @ensure.compile(merge o) + "\n#{@tab}}"
"#{@tab}try {\n#{attemptPart}\n#{@tab}}#{catchPart}#{finallyPart}" "#{@tab}try {\n#{attemptPart}\n#{@tab}}#{catchPart}#{finallyPart}"
#### Throw #### Throw
@ -1301,7 +1301,8 @@ exports.Try = class Try extends Base
# Simple node to throw an exception. # Simple node to throw an exception.
exports.Throw = class Throw extends Base exports.Throw = class Throw extends Base
children: ['expression'] children: ['expression']
isStatement: YES isStatement: YES
constructor: (@expression) -> constructor: (@expression) ->
@ -1311,7 +1312,7 @@ exports.Throw = class Throw extends Base
makeReturn: THIS makeReturn: THIS
compileNode: (o) -> compileNode: (o) ->
"#{@tab}throw #{@expression.compile(o)};" "#{@tab}throw #{ @expression.compile o };"
#### Existence #### Existence
@ -1344,23 +1345,23 @@ exports.Parens = class Parens extends Base
children: ['expression'] children: ['expression']
topSensitive: YES
constructor: (@expression) -> constructor: (@expression) ->
super() super()
isStatement: (o) -> isStatement: (o) ->
@expression.isStatement(o) @expression.isStatement o
isComplex: -> isComplex: ->
@expression.isComplex() @expression.isComplex()
topSensitive: YES
makeReturn: -> makeReturn: ->
@expression.makeReturn() @expression.makeReturn()
compileNode: (o) -> compileNode: (o) ->
top = del o, 'top' top = del o, 'top'
@expression.parenthetical = true @expression.parenthetical = true
code = @expression.compile(o) code = @expression.compile o
return code if top and @expression.isPureStatement o return code if top and @expression.isPureStatement o
if @parenthetical or @isStatement o if @parenthetical or @isStatement o
return if top then @tab + code + ';' else code return if top then @tab + code + ';' else code
@ -1377,8 +1378,10 @@ exports.Parens = class Parens extends Base
# you can map and filter in a single pass. # you can map and filter in a single pass.
exports.For = class For extends Base exports.For = class For extends Base
children: ['body', 'source', 'guard'] children: ['body', 'source', 'guard']
isStatement: YES
topSensitive: YES
isStatement : YES
constructor: (@body, source, @name, @index) -> constructor: (@body, source, @name, @index) ->
super() super()
@ -1386,18 +1389,16 @@ exports.For = class For extends Base
@raw = !!source.raw @raw = !!source.raw
@object = !!source.object @object = !!source.object
[@name, @index] = [@index, @name] if @object [@name, @index] = [@index, @name] if @object
throw SyntaxError 'index cannot be a pattern matching expression' if @index instanceof Value
@pattern = @name instanceof Value @pattern = @name instanceof Value
throw new Error('index cannot be a pattern matching expression') if @index instanceof Value
@returns = false @returns = false
topSensitive: YES
makeReturn: -> makeReturn: ->
@returns = true @returns = true
this this
compileReturnValue: (val, o) -> compileReturnValue: (val, o) ->
return '\n' + new Return(new Literal val).compile(o) if @returns return '\n' + new Return(new Literal val).compile o if @returns
return '\n' + val if val return '\n' + val if val
'' ''
@ -1422,7 +1423,7 @@ exports.For = class For extends Base
varPart = '' varPart = ''
guardPart = '' guardPart = ''
unstepPart = '' unstepPart = ''
body = Expressions.wrap([@body]) body = Expressions.wrap [@body]
idt1 = @idt 1 idt1 = @idt 1
if range if range
forPart = source.compile merge o, {index: ivar, @step} forPart = source.compile merge o, {index: ivar, @step}
@ -1442,10 +1443,10 @@ exports.For = class For extends Base
stepPart = if @step then "#{ivar} += #{ @step.compile(o) }" else "#{ivar}++" stepPart = if @step then "#{ivar} += #{ @step.compile(o) }" else "#{ivar}++"
forPart = "#{ivar} = 0, #{lvar} = #{sourcePart}.length; #{ivar} < #{lvar}; #{stepPart}" forPart = "#{ivar} = 0, #{lvar} = #{sourcePart}.length; #{ivar} < #{lvar}; #{stepPart}"
resultPart = if rvar then "#{@tab}#{rvar} = [];\n" else '' resultPart = if rvar then "#{@tab}#{rvar} = [];\n" else ''
returnResult = @compileReturnValue(rvar, o) returnResult = @compileReturnValue rvar, o
body = Push.wrap(rvar, body) unless topLevel body = Push.wrap rvar, body unless topLevel
if @guard if @guard
body = Expressions.wrap([new If(@guard, body)]) body = Expressions.wrap [new If @guard, body]
if codeInBody if codeInBody
body.unshift new Literal "var #{name} = #{ivar}" if range body.unshift new Literal "var #{name} = #{ivar}" if range
body.unshift new Literal "var #{namePart}" if namePart body.unshift new Literal "var #{namePart}" if namePart
@ -1485,8 +1486,6 @@ exports.Switch = class Switch extends Base
constructor: (@subject, @cases, @otherwise) -> constructor: (@subject, @cases, @otherwise) ->
super() super()
@tags.subjectless = !@subject
@subject or= new Literal 'true'
makeReturn: -> makeReturn: ->
pair[1].makeReturn() for pair in @cases pair[1].makeReturn() for pair in @cases
@ -1494,19 +1493,18 @@ exports.Switch = class Switch extends Base
this this
compileNode: (o) -> compileNode: (o) ->
idt = o.indent = @idt 2 idt1 = @idt 1
idt2 = o.indent = @idt 2
o.top = yes o.top = yes
code = "#{ @tab }switch (#{ @subject.compile o }) {" code = "#{ @tab }switch (#{ @subject?.compile(o) or true }) {"
for pair in @cases for [conditions, block] in @cases
[conditions, block] = pair
exprs = block.expressions
for condition in flatten [conditions] for condition in flatten [conditions]
condition = new Op '!!', new Parens condition if @tags.subjectless condition = condition.invert().invert() unless @subject
code += "\n#{ @idt(1) }case #{ condition.compile o }:" code += "\n#{ idt1 }case #{ condition.compile o }:"
code += "\n#{ block.compile o }" code += "\n#{ block.compile o }"
code += "\n#{ idt }break;" unless last(exprs) instanceof Return code += "\n#{ idt2 }break;" unless last(block.expressions) instanceof Return
if @otherwise if @otherwise
code += "\n#{ @idt(1) }default:\n#{ @otherwise.compile o }" code += "\n#{ idt1 }default:\n#{ @otherwise.compile o }"
code += "\n#{ @tab }}" code += "\n#{ @tab }}"
code code
@ -1556,8 +1554,8 @@ exports.If = class If extends Base
makeReturn: -> makeReturn: ->
if @isStatement() if @isStatement()
@body and= @ensureExpressions(@body.makeReturn()) @body and= @ensureExpressions @body.makeReturn()
@elseBody and= @ensureExpressions(@elseBody.makeReturn()) @elseBody and= @ensureExpressions @elseBody.makeReturn()
this this
else else
new Return this new Return this
@ -1687,7 +1685,7 @@ TAB = ' '
TRAILING_WHITESPACE = /[ \t]+$/gm TRAILING_WHITESPACE = /[ \t]+$/gm
IDENTIFIER = /^[$A-Za-z_][$\w]*$/ IDENTIFIER = /^[$A-Za-z_][$\w]*$/
NUMBER = /^0x[\da-f]+|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?$/i NUMBER = /^0x[\da-f]+$|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?$/i
SIMPLENUM = /^[+-]?\d+$/ SIMPLENUM = /^[+-]?\d+$/
# Is a literal value a string? # Is a literal value a string?