Self-compiler: object literals.

This commit is contained in:
Jeremy Ashkenas
2010-02-09 20:53:25 -05:00
parent 91a7102f11
commit 001c915c21
7 changed files with 403 additions and 175 deletions

View File

@@ -1,5 +1,5 @@
(function(){
var AccessorNode, CallNode, CommentNode, Expressions, ExtendsNode, IndexNode, LiteralNode, Node, RangeNode, ReturnNode, SliceNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, any, compact, del, dup, flatten, inherit, merge, statement;
var AccessorNode, AssignNode, CallNode, CommentNode, Expressions, ExtendsNode, IndexNode, LiteralNode, Node, ObjectNode, RangeNode, ReturnNode, SliceNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, 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.
@@ -341,8 +341,7 @@
// A collection of nodes, each one representing an expression.
Expressions = (exports.Expressions = inherit(Node, {
constructor: function constructor(nodes) {
this.expressions = flatten(nodes);
this.children = this.expressions;
this.children = (this.expressions = flatten(nodes));
return this;
},
// Tack an expression on to the end of this expression list.
@@ -451,8 +450,7 @@
// JavaScript without translation, eg.: strings, numbers, true, false, null...
LiteralNode = (exports.LiteralNode = inherit(Node, {
constructor: function constructor(value) {
this.value = value;
this.children = [value];
this.children = [(this.value = value)];
return this;
},
// Break and continue must be treated as statements -- they lose their meaning
@@ -471,8 +469,7 @@
// Return an expression, or wrap it in a closure and return it.
ReturnNode = (exports.ReturnNode = inherit(Node, {
constructor: function constructor(expression) {
this.expression = expression;
this.children = [expression];
this.children = [(this.expression = expression)];
return this;
},
compile_node: function compile_node(o) {
@@ -489,9 +486,7 @@
ValueNode = (exports.ValueNode = inherit(Node, {
SOAK: " == undefined ? undefined : ",
constructor: function constructor(base, properties) {
this.base = base;
this.properties = flatten(properties || []);
this.children = flatten(this.base, this.properties);
this.children = flatten((this.base = base), (this.properties = (properties || [])));
return this;
},
push: function push(prop) {
@@ -572,9 +567,7 @@
// calls against the prototype's function of the same name.
CallNode = (exports.CallNode = inherit(Node, {
constructor: function constructor(variable, args) {
this.variable = variable;
this.args = args || [];
this.children = flatten([this.variable, this.args]);
this.children = flatten([(this.variable = variable), (this.args = (args || []))]);
this.prefix = '';
return this;
},
@@ -648,9 +641,7 @@
// After goog.inherits from the Closure Library.
ExtendsNode = (exports.ExtendsNode = inherit(Node, {
constructor: function constructor(child, parent) {
this.child = child;
this.parent = parent;
this.children = [child, parent];
this.children = [(this.child = child), (this.parent = parent)];
return this;
},
// Hooking one constructor into another's prototype chain.
@@ -667,8 +658,7 @@
// an accessor into the object's prototype.
AccessorNode = (exports.AccessorNode = inherit(Node, {
constructor: function constructor(name, tag) {
this.name = name;
this.children = [this.name];
this.children = [(this.name = name)];
this.prototype = tag === 'prototype';
this.soak = tag === 'soak';
return this;
@@ -701,9 +691,7 @@
// or to specify a range for list comprehensions.
RangeNode = (exports.RangeNode = inherit(Node, {
constructor: function constructor(from, to, exclusive) {
this.from = from;
this.to = to;
this.children = [from, to];
this.children = [(this.from = from), (this.to = to)];
this.exclusive = !!exclusive;
return this;
},
@@ -754,4 +742,143 @@
return ".slice(" + from + ', ' + to + plus_part + ')';
}
}));
// An object literal.
ObjectNode = (exports.ObjectNode = inherit(Node, {
constructor: function constructor(props) {
this.objects = (this.properties = props || []);
return this;
},
// All the mucking about with commas is to make sure that CommentNodes and
// AssignNodes get interleaved correctly, with no trailing commas or
// commas affixed to comments. TODO: Extract this and add it to ArrayNode.
compile_node: function compile_node(o) {
var __a, __b, __c, __d, __e, i, indent, join, last_noncom, non_comments, prop, props;
o.indent = this.idt(1);
non_comments = (function() {
__a = []; __b = this.properties;
for (__c = 0; __c < __b.length; __c++) {
prop = __b[__c];
if (!(prop instanceof CommentNode)) {
__a.push(prop);
}
}
return __a;
}).call(this);
last_noncom = non_comments[non_comments.length - 1];
props = (function() {
__d = []; __e = this.properties;
for (i = 0; i < __e.length; i++) {
prop = __e[i];
__d.push((function() {
join = ",\n";
if (prop === last_noncom || prop instanceof CommentNode) {
join = "\n";
}
if (i === non_comments.length - 1) {
join = '';
}
indent = prop instanceof CommentNode ? '' : this.idt(1);
return indent + prop.compile(o) + join;
}).call(this));
}
return __d;
}).call(this);
return '{\n' + props.join('') + '\n' + this.idt() + '}';
}
}));
// Setting the value of a local variable, or the value of an object property.
AssignNode = (exports.AssignNode = inherit(Node, {
// Keep the identifier regex in sync with the Lexer.
IDENTIFIER: /^([a-zA-Z$_](\w|\$)*)/,
PROTO_ASSIGN: /^(\S+)\.prototype/,
LEADING_DOT: /^\.(prototype\.)?/,
constructor: function constructor(variable, value, context) {
this.children = [(this.variable = variable), (this.value = value)];
this.context = context;
return this;
},
top_sensitive: function top_sensitive() {
return true;
},
is_value: function is_value() {
return this.variable instanceof ValueNode;
},
is_statement: function is_statement() {
return this.is_value() && (this.variable.is_array() || this.variable.is_object());
},
compile_node: function compile_node(o) {
var last, match, name, proto, stmt, top, val;
top = del(o, 'top');
if (this.is_statement()) {
return this.compile_pattern_match(o);
}
if (this.is_value() && this.variable.is_splice()) {
return this.compile_splice(o);
}
stmt = del(o, 'as_statement');
name = this.variable.compile(o);
last = this.is_value() ? this.variable.last.replace(this.LEADING_DOT, '') : name;
match = name.match(this.PROTO_ASSIGN);
proto = match && match[1];
if (this.value instanceof CodeNode) {
if (last.match(this.IDENTIFIER)) {
this.value.name = last;
}
if (proto) {
this.value.proto = proto;
}
}
if (this.context === 'object') {
return name + ': ' + this.value.compile(o);
}
if (!(this.is_value() && this.variable.has_properties())) {
o.scope.find(name);
}
val = name + ' = ' + this.value.compile(o);
if (stmt) {
return this.idt() + val + ';';
}
if (!top || o.returns) {
val = '(' + val + ')';
}
if (o.returns) {
val = this.idt() + 'return ' + val;
}
return val;
},
// Implementation of recursive pattern matching, when assigning array or
// object literals to a value. Peeks at their properties to assign inner names.
// See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
compile_pattern_match: function compile_pattern_match(o) {
var __a, __b, access_class, assigns, i, obj, val, val_var;
val_var = o.scope.free_variable();
assigns = [this.idt() + val_var + ' = ' + this.value.compile(o) + ';'];
o.top = true;
o.as_statement = true;
__a = this.variable.base.objects;
for (i = 0; i < __a.length; i++) {
obj = __a[i];
if (this.variable.is_object()) {
__b = [obj.value, obj.variable.base];
obj = __b[0];
i = __b[1];
}
access_class = this.variable.is_array() ? IndexNode : AccessorNode;
obj instanceof SplatNode ? (val = new LiteralNode(obj.compile_value(o, val_var, this.variable.base.objects.indexOf(obj)))) : (val = new ValueNode(val_var, [new access_class(new LiteralNode(i))]));
assigns.push(new AssignNode(obj, val).compile(o));
}
return assigns.join("\n");
},
compile_splice: function compile_splice(o) {
var from, name, plus, range, to;
name = this.variable.compile(merge(o, {
only_first: true
}));
range = this.variable.properties.last.range;
plus = range.exclusive ? '' : ' + 1';
from = range.from.compile(o);
to = range.to.compile(o) + ' - ' + from + plus;
return name + '.splice.apply(' + name + ', [' + from + ', ' + to + '].concat(' + this.value.compile(o) + '))';
}
}));
})();