and now with if/else statements, CoffeeScript-in-CoffeeScript is language-complete -- now for the shakedown cruise

This commit is contained in:
Jeremy Ashkenas
2010-02-10 21:40:10 -05:00
parent 13d3b3a3ce
commit 38e1991f82
5 changed files with 194 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
(function(){
var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IndexNode, LiteralNode, Node, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThisNode, ThrowNode, TryNode, ValueNode, WhileNode, any, compact, del, dup, flatten, inherit, merge, statement;
var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, Node, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThisNode, ThrowNode, TryNode, ValueNode, WhileNode, any, compact, del, dup, flatten, inherit, merge, statement;
var __hasProp = Object.prototype.hasOwnProperty;
process.mixin(require('./scope'));
// The abstract base class for all CoffeeScript nodes.
@@ -1290,4 +1290,113 @@
}
}));
statement(ForNode);
// If/else statements. Switch/whens get compiled into these. Acts as an
// expression by pushing down requested returns to the expression bodies.
// Single-expression IfNodes are compiled into ternary operators if possible,
// because ternaries are first-class returnable assignable expressions.
IfNode = (exports.IfNode = inherit(Node, {
constructor: function constructor(condition, body, else_body, tags) {
this.condition = condition;
this.body = body && body.unwrap();
this.else_body = else_body && else_body.unwrap();
this.children = [this.condition, this.body, this.else_body];
this.tags = tags || {
};
if (this.condition instanceof Array) {
this.multiple = true;
}
if (this.tags.invert) {
this.condition = new OpNode('!', new ParentheticalNode(this.condition));
}
return this;
},
push: function push(else_body) {
var eb;
eb = else_body.unwrap();
this.else_body ? this.else_body.push(eb) : (this.else_body = eb);
return this;
},
force_statement: function force_statement() {
this.tags.statement = true;
return this;
},
// Rewrite a chain of IfNodes with their switch condition for equality.
rewrite_condition: function rewrite_condition(expression) {
var __a, __b, __c, cond;
this.condition = (function() {
if (this.multiple) {
__a = []; __b = this.condition;
for (__c = 0; __c < __b.length; __c++) {
cond = __b[__c];
__a.push(new OpNode('is', expression, cond));
}
return __a;
} else {
return new OpNode('is', expression, this.condition);
}
}).call(this);
if (this.is_chain()) {
this.else_body.rewrite_condition(expression);
}
return this;
},
// Rewrite a chain of IfNodes to add a default case as the final else.
add_else: function add_else(exprs) {
this.is_chain() ? this.else_body.add_else(exprs) : (this.else_body = exprs && exprs.unwrap());
return this;
},
// If the else_body is an IfNode itself, then we've got an if-else chain.
is_chain: function is_chain() {
return this.chain = this.chain || this.else_body && this.else_body instanceof IfNode;
},
// The IfNode only compiles into a statement if either of the bodies needs
// to be a statement.
is_statement: function is_statement() {
return this.statement = this.statement || !!(this.comment || this.tags.statement || this.body.is_statement() || (this.else_body && this.else_body.is_statement()));
},
compile_condition: function compile_condition(o) {
var __a, __b, __c, cond;
return ((function() {
__a = []; __b = flatten(this.condition);
for (__c = 0; __c < __b.length; __c++) {
cond = __b[__c];
__a.push(cond.compile(o));
}
return __a;
}).call(this)).join(' || ');
},
compile_node: function compile_node(o) {
return this.is_statement() ? this.compile_statement(o) : this.compile_ternary(o);
},
// Compile the IfNode as a regular if-else statement. Flattened chains
// force sub-else bodies into statement form.
compile_statement: function compile_statement(o) {
var body, child, com_dent, cond_o, else_part, if_dent, if_part, prefix;
child = del(o, 'chain_child');
cond_o = dup(o);
del(cond_o, 'returns');
o.indent = this.idt(1);
o.top = true;
if_dent = child ? '' : this.idt();
com_dent = child ? this.idt() : '';
prefix = this.comment ? this.comment.compile(cond_o) + '\n' + com_dent : '';
body = Expressions.wrap([body]).compile(o);
if_part = prefix + if_dent + 'if (' + compile_condition(cond_o) + ') {\n' + body + '\n' + this.idt() + '}';
if (!(this.else_body)) {
return if_part;
}
else_part = this.is_chain() ? ' else ' + this.else_body.compile(merge(o, {
indent: this.idt(),
chain_child: true
})) : ' else {\n' + Expressions.wrap(this.else_body).compile(o) + '\n' + this.idt() + '}';
return if_part + else_part;
},
// Compile the IfNode into a ternary operator.
compile_ternary: function compile_ternary(o) {
var else_part, if_part;
if_part = this.condition.compile(o) + ' ? ' + this.body.compile(o);
else_part = this.else_body ? this.else_body.compile(o) : 'null';
return if_part + ' : ' + else_part;
}
}));
})();