From 9a5546c8e9dbeb5e3cd1917ea9c5e5a97e1b9baa Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 13 Nov 2010 17:05:54 -0500 Subject: [PATCH] further refactors to Class --- lib/nodes.js | 68 ++++++++++++++++++++++++++++-------------------- src/nodes.coffee | 65 +++++++++++++++++++++++++-------------------- 2 files changed, 77 insertions(+), 56 deletions(-) diff --git a/lib/nodes.js b/lib/nodes.js index 1e5d6b8b..d9eb08a9 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -846,6 +846,19 @@ } }); }; + Class.prototype.addBoundFunctions = function(o) { + var bname, bvar, _i, _len, _ref, _results; + if (this.boundFuncs.length) { + _ref = this.boundFuncs; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + bvar = _ref[_i]; + bname = bvar.compile(o); + _results.push(this.ctor.body.unshift(new Literal("this." + bname + " = " + (utility('bind')) + "(this." + bname + ", this);"))); + } + return _results; + } + }; Class.prototype.addProperties = function(node, name) { var assign, base, func, props, _results; props = node.base.properties.slice(0); @@ -867,25 +880,20 @@ } return _results; }; - Class.prototype.compileNode = function(o) { - var bname, bvar, ctor, decl, exps, klass, lname, name, _fn, _i, _len, _len2, _ref, _ref2, _ref3; - ctor = null; - decl = this.determineName(); - name = decl || this.name || '_Class'; - lname = new Literal(name); - this.setContext(name); + Class.prototype.walkBody = function(name) { + var exps, _fn, _len, _ref; _ref = exps = this.body.expressions; _fn = function(node, i) { if (node instanceof Value && node.isObject(true)) { return exps[i] = compact(this.addProperties(node, name)); } else if (node instanceof Code) { - if (ctor) { + if (this.ctor) { throw new Error('cannot define more than one constructor in a class'); } if (node.bound) { throw new Error('cannot define a constructor as a bound function'); } - ctor = node; + this.ctor = node; return exps[i] = null; } else { return node.traverseChildren(false, __bind(function(n2) { @@ -907,31 +915,35 @@ node = _ref[i]; _fn.call(this, node, i); } - this.body.expressions = exps = compact(flatten(exps)); - if (!ctor) { - ctor = new Code; + return this.body.expressions = exps = compact(flatten(exps)); + }; + Class.prototype.ensureConstructor = function(name) { + if (!this.ctor) { + this.ctor = new Code; if (this.parent) { - ctor.body.push(new Call('super', [new Splat(new Literal('arguments'))])); + this.ctor.body.push(new Call('super', [new Splat(new Literal('arguments'))])); } } - ctor.ctor = ctor.name = name; - ctor.klass = null; - ctor.noReturn = true; + this.ctor.ctor = this.ctor.name = name; + this.ctor.klass = null; + return this.ctor.noReturn = true; + }; + Class.prototype.compileNode = function(o) { + var decl, klass, lname, name, _ref; + decl = this.determineName(); + name = decl || this.name || '_Class'; + lname = new Literal(name); + this.setContext(name); + this.walkBody(name); + this.ensureConstructor(name); if (this.parent) { - exps.unshift(new Extends(lname, this.parent)); - } - exps.unshift(ctor); - exps.push(lname); - if (this.boundFuncs.length) { - _ref2 = this.boundFuncs; - for (_i = 0, _len2 = _ref2.length; _i < _len2; _i++) { - bvar = _ref2[_i]; - bname = bvar.compile(o); - ctor.body.unshift(new Literal("this." + bname + " = " + (utility('bind')) + "(this." + bname + ", this);")); - } + this.body.expressions.unshift(new Extends(lname, this.parent)); } + this.body.expressions.unshift(this.ctor); + this.body.expressions.push(lname); + this.addBoundFunctions(o); klass = new Parens(new Call(new Code([], this.body)), true); - if (decl && ((_ref3 = this.variable) != null ? _ref3.isComplex() : void 0)) { + if (decl && ((_ref = this.variable) != null ? _ref.isComplex() : void 0)) { klass = new Assign(new Value(lname), klass); } if (this.variable) { diff --git a/src/nodes.coffee b/src/nodes.coffee index 25c704cd..6146c0e5 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -686,6 +686,14 @@ exports.Class = class Class extends Base node.klass = name node.context = name if node.bound + # Ensure that all functions bound to the instance are proxied in the + # constructor. + addBoundFunctions: (o) -> + if @boundFuncs.length + for bvar in @boundFuncs + bname = bvar.compile o + @ctor.body.unshift new Literal "this.#{bname} = #{utility 'bind'}(this.#{bname}, this);" + # Merge the properties from a top-level object as prototypal properties # on the class. addProperties: (node, name) -> @@ -702,26 +710,17 @@ exports.Class = class Class extends Base func.bound = no assign - # Instead of generating the JavaScript string directly, we build up the - # equivalent syntax tree and compile that, in pieces. You can see the - # constructor, property assignments, and inheritance getting built out below. - compileNode: (o) -> - ctor = null - - decl = @determineName() - name = decl or @name or '_Class' - lname = new Literal name - @setContext name - + # Walk the body of the class, looking for prototype properties to be converted. + walkBody: (name) -> for node, i in exps = @body.expressions if node instanceof Value and node.isObject(true) exps[i] = compact @addProperties node, name else if node instanceof Code - if ctor + if @ctor throw new Error 'cannot define more than one constructor in a class' if node.bound throw new Error 'cannot define a constructor as a bound function' - ctor = node + @ctor = node exps[i] = null else node.traverseChildren false, (n2) => @@ -730,23 +729,33 @@ exports.Class = class Class extends Base if expr2 instanceof Value and expr2.isObject(true) n2.expressions[j] = compact @addProperties expr2, name n2.expressions = flatten n2.expressions - @body.expressions = exps = compact flatten exps - unless ctor - ctor = new Code - if @parent - ctor.body.push new Call 'super', [new Splat new Literal 'arguments'] - ctor.ctor = ctor.name = name - ctor.klass = null - ctor.noReturn = yes - exps.unshift new Extends lname, @parent if @parent - exps.unshift ctor - exps.push lname - if @boundFuncs.length - for bvar in @boundFuncs - bname = bvar.compile o - ctor.body.unshift new Literal "this.#{bname} = #{utility 'bind'}(this.#{bname}, this);" + # Make sure that a constructor is defined for the class, and properly + # configured. + ensureConstructor: (name) -> + if not @ctor + @ctor = new Code + @ctor.body.push new Call 'super', [new Splat new Literal 'arguments'] if @parent + @ctor.ctor = @ctor.name = name + @ctor.klass = null + @ctor.noReturn = yes + + # Instead of generating the JavaScript string directly, we build up the + # equivalent syntax tree and compile that, in pieces. You can see the + # constructor, property assignments, and inheritance getting built out below. + compileNode: (o) -> + decl = @determineName() + name = decl or @name or '_Class' + lname = new Literal name + + @setContext name + @walkBody name + @ensureConstructor name + @body.expressions.unshift new Extends lname, @parent if @parent + @body.expressions.unshift @ctor + @body.expressions.push lname + @addBoundFunctions o klass = new Parens new Call(new Code [], @body), true klass = new Assign new Value(lname), klass if decl and @variable?.isComplex()