adding the ability to have classes with functions pre-bound to the instance, using the fat arrow, and the constructor-function-definition pattern, avoiding prototypes.

This commit is contained in:
Jeremy Ashkenas
2010-06-15 01:28:30 -04:00
parent de768aefc3
commit 8d853a6d58
3 changed files with 49 additions and 27 deletions

View File

@@ -897,9 +897,14 @@
ClassNode.prototype.compileNode = function(o) { ClassNode.prototype.compileNode = function(o) {
var _b, _c, _d, _e, access, applied, construct, extension, func, prop, props, pvar, returns, val; var _b, _c, _d, _e, access, applied, construct, extension, func, prop, props, pvar, returns, val;
extension = this.parent && new ExtendsNode(this.variable, this.parent); extension = this.parent && new ExtendsNode(this.variable, this.parent);
constructor = null;
props = new Expressions(); props = new Expressions();
o.top = true; o.top = true;
if (this.parent) {
applied = new ValueNode(this.parent, [new AccessorNode(literal('apply'))]);
constructor = new CodeNode([], new Expressions([new CallNode(applied, [literal('this'), literal('arguments')])]));
} else {
constructor = new CodeNode();
}
_c = this.properties; _c = this.properties;
for (_b = 0, _d = _c.length; _b < _d; _b++) { for (_b = 0, _d = _c.length; _b < _d; _b++) {
prop = _c[_b]; prop = _c[_b];
@@ -911,7 +916,14 @@
func.body.push(new ReturnNode(literal('this'))); func.body.push(new ReturnNode(literal('this')));
this.variable = new ValueNode(this.variable); this.variable = new ValueNode(this.variable);
this.variable.namespaced = include(func.name, '.'); this.variable.namespaced = include(func.name, '.');
constructor = new AssignNode(this.variable, func); constructor = func;
continue;
}
if (func instanceof CodeNode && func.bound) {
if (constructor.body.empty()) {
constructor.body.push(new ReturnNode(literal('this')));
}
constructor.body.unshift(new AssignNode(new ValueNode(literal('this'), [new AccessorNode(pvar)]), func));
} else { } else {
if (pvar) { if (pvar) {
access = prop.context === 'this' ? pvar.base.properties[0] : new AccessorNode(pvar, 'prototype'); access = prop.context === 'this' ? pvar.base.properties[0] : new AccessorNode(pvar, 'prototype');
@@ -921,15 +933,7 @@
props.push(prop); props.push(prop);
} }
} }
if (!(constructor)) { construct = this.idt() + (new AssignNode(this.variable, constructor)).compile(o) + ';\n';
if (this.parent) {
applied = new ValueNode(this.parent, [new AccessorNode(literal('apply'))]);
constructor = new AssignNode(this.variable, new CodeNode([], new Expressions([new CallNode(applied, [literal('this'), literal('arguments')])])));
} else {
constructor = new AssignNode(this.variable, new CodeNode());
}
}
construct = this.idt() + constructor.compile(o) + ';\n';
props = props.empty() ? '' : props.compile(o) + '\n'; props = props.empty() ? '' : props.compile(o) + '\n';
extension = extension ? this.idt() + extension.compile(o) + ';\n' : ''; extension = extension ? this.idt() + extension.compile(o) + ';\n' : '';
returns = this.returns ? new ReturnNode(this.variable).compile(o) : ''; returns = this.returns ? new ReturnNode(this.variable).compile(o) : '';

View File

@@ -635,9 +635,9 @@ exports.ArrayNode: class ArrayNode extends BaseNode
# The CoffeeScript class definition. # The CoffeeScript class definition.
exports.ClassNode: class ClassNode extends BaseNode exports.ClassNode: class ClassNode extends BaseNode
class: 'ClassNode' class: 'ClassNode'
children: ['variable', 'parent', 'properties'] children: ['variable', 'parent', 'properties']
isStatement: -> yes isStatement: -> yes
# Initialize a **ClassNode** with its name, an optional superclass, and a # Initialize a **ClassNode** with its name, an optional superclass, and a
# list of prototype property assignments. # list of prototype property assignments.
@@ -656,18 +656,29 @@ exports.ClassNode: class ClassNode extends BaseNode
# constructor, property assignments, and inheritance getting built out below. # constructor, property assignments, and inheritance getting built out below.
compileNode: (o) -> compileNode: (o) ->
extension: @parent and new ExtendsNode(@variable, @parent) extension: @parent and new ExtendsNode(@variable, @parent)
constructor: null
props: new Expressions() props: new Expressions()
o.top: true o.top: true
if @parent
applied: new ValueNode(@parent, [new AccessorNode(literal('apply'))])
constructor: new CodeNode([], new Expressions([
new CallNode(applied, [literal('this'), literal('arguments')])
]))
else
constructor: new CodeNode()
for prop in @properties for prop in @properties
[pvar, func]: [prop.variable, prop.value] [pvar, func]: [prop.variable, prop.value]
if pvar and pvar.base.value is 'constructor' and func instanceof CodeNode if pvar and pvar.base.value is 'constructor' and func instanceof CodeNode
func.name: @variable.compile(o) func.name: @variable.compile(o)
func.body.push(new ReturnNode(literal('this'))) func.body.push new ReturnNode literal 'this'
@variable: new ValueNode @variable @variable: new ValueNode @variable
@variable.namespaced: include func.name, '.' @variable.namespaced: include func.name, '.'
constructor: new AssignNode(@variable, func) constructor: func
continue
if func instanceof CodeNode and func.bound
constructor.body.push new ReturnNode literal 'this' if constructor.body.empty()
constructor.body.unshift new AssignNode(new ValueNode(literal('this'), [new AccessorNode(pvar)]), func)
else else
if pvar if pvar
access: if prop.context is 'this' then pvar.base.properties[0] else new AccessorNode(pvar, 'prototype') access: if prop.context is 'this' then pvar.base.properties[0] else new AccessorNode(pvar, 'prototype')
@@ -675,16 +686,7 @@ exports.ClassNode: class ClassNode extends BaseNode
prop: new AssignNode(val, func) prop: new AssignNode(val, func)
props.push prop props.push prop
unless constructor construct: @idt() + (new AssignNode(@variable, constructor)).compile(o) + ';\n'
if @parent
applied: new ValueNode(@parent, [new AccessorNode(literal('apply'))])
constructor: new AssignNode(@variable, new CodeNode([], new Expressions([
new CallNode(applied, [literal('this'), literal('arguments')])
])))
else
constructor: new AssignNode(@variable, new CodeNode())
construct: @idt() + constructor.compile(o) + ';\n'
props: if props.empty() then '' else props.compile(o) + '\n' props: if props.empty() then '' else props.compile(o) + '\n'
extension: if extension then @idt() + extension.compile(o) + ';\n' else '' extension: if extension then @idt() + extension.compile(o) + ';\n' else ''
returns: if @returns then new ReturnNode(@variable).compile(o) else '' returns: if @returns then new ReturnNode(@variable).compile(o) else ''

View File

@@ -127,4 +127,20 @@ class Class
instance: new Class() instance: new Class()
ok instance.class is 'class' ok instance.class is 'class'
ok instance.name() is 'class' ok instance.name() is 'class'
# Classes with methods that are pre-bound to the instance.
class Dog
constructor: (name) ->
@name: name
bark: =>
"$@name woofs!"
spark: new Dog('Spark')
fido: new Dog('Fido')
fido.bark: spark.bark
ok fido.bark() is 'Spark woofs!'