From be17b8215c4c4e223163aa669db1f66b18ddbf01 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 14 Nov 2010 14:21:55 -0500 Subject: [PATCH] constructor: prefix is back for classes. punto. --- lib/nodes.js | 36 ++++++++------- lib/rewriter.js | 5 ++- src/nodes.coffee | 80 +++++++++++++++++---------------- src/optparse.coffee | 2 +- src/rewriter.coffee | 13 +++--- src/scope.coffee | 2 +- test/test_arguments.coffee | 2 +- test/test_classes.coffee | 24 +++++----- test/test_comprehensions.coffee | 2 +- test/test_existence.coffee | 2 +- 10 files changed, 89 insertions(+), 79 deletions(-) diff --git a/lib/nodes.js b/lib/nodes.js index 05367e6e..a41acd6f 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -856,12 +856,27 @@ base = assign.variable.base; delete assign.context; func = assign.value; - if (!assign.variable["this"]) { - assign.variable = new Value(new Literal(name), [new Accessor(base, 'proto')]); - } - if (func instanceof Code && func.bound) { - this.boundFuncs.push(base); - func.bound = false; + if (base.value === 'constructor') { + if (this.ctor) { + throw new Error('cannot define more than one constructor in a class'); + } + if (func.bound) { + throw new Error('cannot define a constructor as a bound function'); + } + if (func instanceof Code) { + this.ctor = func; + } else { + this.ctor = new Assign(new Value(new Literal(name)), func); + } + assign = null; + } else { + if (!assign.variable["this"]) { + assign.variable = new Value(new Literal(name), [new Accessor(base, 'proto')]); + } + if (func instanceof Code && func.bound) { + this.boundFuncs.push(base); + func.bound = false; + } } } _results.push(assign); @@ -877,15 +892,6 @@ node = _ref[i]; if (node instanceof Value && node.isObject(true)) { exps[i] = compact(this.addProperties(node, name)); - } else if (node instanceof Code) { - 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'); - } - this.ctor = node; - exps[i] = null; } } return child.expressions = exps = compact(flatten(exps)); diff --git a/lib/rewriter.js b/lib/rewriter.js index 52e82aea..b909e17d 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -106,9 +106,10 @@ }); }; Rewriter.prototype.addImplicitBraces = function() { - var action, condition, stack, start; + var action, condition, stack, start, startIndent; stack = []; start = null; + startIndent = 0; condition = function(token, i) { var one, tag, three, two, _ref, _ref2; _ref = this.tokens, one = _ref[i + 1], two = _ref[i + 2], three = _ref[i + 3]; @@ -116,7 +117,7 @@ return false; } tag = token[0]; - return (tag === 'TERMINATOR' || tag === 'OUTDENT') && !((two != null ? two[0] : void 0) === ':' || (one != null ? one[0] : void 0) === '@' && (three != null ? three[0] : void 0) === ':') || tag === ',' && one && ((_ref2 = one[0]) !== 'IDENTIFIER' && _ref2 !== 'NUMBER' && _ref2 !== 'STRING' && _ref2 !== '@' && _ref2 !== 'TERMINATOR' && _ref2 !== 'OUTDENT' && _ref2 !== '('); + return ((tag === 'TERMINATOR' || tag === 'OUTDENT') && !((two != null ? two[0] : void 0) === ':' || (one != null ? one[0] : void 0) === '@' && (three != null ? three[0] : void 0) === ':')) || (tag === ',' && one && ((_ref2 = one[0]) !== 'IDENTIFIER' && _ref2 !== 'NUMBER' && _ref2 !== 'STRING' && _ref2 !== '@' && _ref2 !== 'TERMINATOR' && _ref2 !== 'OUTDENT' && _ref2 !== '(')); }; action = function(token, i) { return this.tokens.splice(i, 0, ['}', '}', token[2]]); diff --git a/src/nodes.coffee b/src/nodes.coffee index c8800697..31d2c6fc 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -150,7 +150,7 @@ exports.Base = class Base # indented block of code -- the implementation of a function, a clause in an # `if`, `switch`, or `try`, and so on... exports.Expressions = class Expressions extends Base - (nodes) -> + constructor: (nodes) -> @expressions = compact flatten nodes or [] children: ['expressions'] @@ -251,7 +251,7 @@ exports.Expressions = class Expressions extends Base # JavaScript without translation, such as: strings, numbers, # `true`, `false`, `null`... exports.Literal = class Literal extends Base - (@value) -> + constructor: (@value) -> makeReturn: -> if @isPureStatement() then this else new Return this @@ -280,7 +280,7 @@ exports.Literal = class Literal extends Base # A `return` is a *pureStatement* -- wrapping it in a closure wouldn't # make sense. exports.Return = class Return extends Base - (@expression) -> + constructor: (@expression) -> children: ['expression'] @@ -301,7 +301,7 @@ exports.Return = class Return extends Base # A value, variable or literal or parenthesized, indexed or dotted into, # or vanilla. exports.Value = class Value extends Base - (base, props, tag) -> + constructor: (base, props, tag) -> return base if not props and base instanceof Value @base = base @properties = props or [] @@ -393,7 +393,7 @@ exports.Value = class Value extends Base # CoffeeScript passes through block comments as JavaScript block comments # at the same position. exports.Comment = class Comment extends Base - (@comment) -> + constructor: (@comment) -> isPureStatement: YES isStatement: YES @@ -409,7 +409,7 @@ exports.Comment = class Comment extends Base # Node for a function invocation. Takes care of converting `super()` calls into # calls against the prototype's function of the same name. exports.Call = class Call extends Base - (variable, @args = [], @soak) -> + constructor: (variable, @args = [], @soak) -> @isNew = false @isSuper = variable is 'super' @variable = if @isSuper then null else variable @@ -511,7 +511,7 @@ exports.Call = class Call extends Base # After `goog.inherits` from the # [Closure Library](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.html). exports.Extends = class Extends extends Base - (@child, @parent) -> + constructor: (@child, @parent) -> children: ['child', 'parent'] @@ -525,7 +525,7 @@ exports.Extends = class Extends extends Base # A `.` accessor into a property of a value, or the `::` shorthand for # an accessor into the object's prototype. exports.Accessor = class Accessor extends Base - (@name, tag) -> + constructor: (@name, tag) -> @proto = if tag is 'proto' then '.prototype' else '' @soak = tag is 'soak' @@ -541,7 +541,7 @@ exports.Accessor = class Accessor extends Base # A `[ ... ]` indexed accessor into an array or object. exports.Index = class Index extends Base - (@index) -> + constructor: (@index) -> children: ['index'] @@ -555,7 +555,7 @@ exports.Index = class Index extends Base # An object literal, nothing fancy. exports.Obj = class Obj extends Base - (props, @generated = false) -> + constructor: (props, @generated = false) -> @objects = @properties = props or [] children: ['properties'] @@ -618,7 +618,7 @@ exports.Obj = class Obj extends Base # An array literal. exports.Arr = class Arr extends Base - (objs) -> + constructor: (objs) -> @objects = objs or [] children: ['objects'] @@ -643,7 +643,7 @@ exports.Arr = class Arr extends Base # Initialize a **Class** with its name, an optional superclass, and a # list of prototype property assignments. exports.Class = class Class extends Base - (@variable, @parent, @body = new Expressions) -> + constructor: (@variable, @parent, @body = new Expressions) -> @boundFuncs = [] children: ['variable', 'parent', 'body'] @@ -684,11 +684,22 @@ exports.Class = class Class extends Base base = assign.variable.base delete assign.context func = assign.value - unless assign.variable.this - assign.variable = new Value(new Literal(name), [new Accessor(base, 'proto')]) - if func instanceof Code and func.bound - @boundFuncs.push base - func.bound = no + if base.value is 'constructor' + if @ctor + throw new Error 'cannot define more than one constructor in a class' + if func.bound + throw new Error 'cannot define a constructor as a bound function' + if func instanceof Code + @ctor = func + else + @ctor = new Assign(new Value(new Literal name), func) + assign = null + else + unless assign.variable.this + assign.variable = new Value(new Literal(name), [new Accessor(base, 'proto')]) + if func instanceof Code and func.bound + @boundFuncs.push base + func.bound = no assign # Walk the body of the class, looking for prototype properties to be converted. @@ -698,13 +709,6 @@ exports.Class = class Class extends Base for node, i in exps = child.expressions if node instanceof Value and node.isObject(true) exps[i] = compact @addProperties node, name - else if node instanceof Code - 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 - exps[i] = null child.expressions = exps = compact flatten exps # Make sure that a constructor is defined for the class, and properly @@ -743,7 +747,7 @@ exports.Class = class Class extends Base # The **Assign** is used to assign a local variable to value, or to set the # property of an object -- including within object literals. exports.Assign = class Assign extends Base - (@variable, @value, @context) -> + constructor: (@variable, @value, @context) -> # Matchers for detecting class/method names METHOD_DEF: /^(?:(\S+)\.prototype\.|\S+?)?\b([$A-Za-z_][$\w]*)$/ @@ -860,7 +864,7 @@ exports.Assign = class Assign extends Base # When for the purposes of walking the contents of a function body, the Code # has no *children* -- they're within the inner scope. exports.Code = class Code extends Base - (params, body, tag) -> + constructor: (params, body, tag) -> @params = params or [] @body = body or new Expressions @bound = tag is 'boundfunc' @@ -926,7 +930,7 @@ exports.Code = class Code extends Base # these parameters can also attach themselves to the context of the function, # as well as be a splat, gathering up a group of parameters into an array. exports.Param = class Param extends Base - (@name, @value, @splat) -> + constructor: (@name, @value, @splat) -> children: ['name', 'value'] @@ -953,7 +957,7 @@ exports.Splat = class Splat extends Base isAssignable: YES - (name) -> + constructor: (name) -> @name = if name.compile then name else new Literal name assigns: (name) -> @@ -988,7 +992,7 @@ exports.Splat = class Splat extends Base # it, all other loops can be manufactured. Useful in cases where you need more # flexibility or more speed than a comprehension can provide. exports.While = class While extends Base - (condition, options) -> + constructor: (condition, options) -> @condition = if options?.invert then condition.invert() else condition @guard = options?.guard @@ -1038,7 +1042,7 @@ exports.While = class While extends Base # Simple Arithmetic and logical operations. Performs some conversion from # CoffeeScript operations into their JavaScript equivalents. exports.Op = class Op extends Base - (op, first, second, flip) -> + constructor: (op, first, second, flip) -> return new In first, second if op is 'in' if op is 'new' return first.newInstance() if first instanceof Call @@ -1129,7 +1133,7 @@ exports.Op = class Op extends Base #### In exports.In = class In extends Base - (@object, @array) -> + constructor: (@object, @array) -> children: ['object', 'array'] @@ -1164,7 +1168,7 @@ exports.In = class In extends Base # A classic *try/catch/finally* block. exports.Try = class Try extends Base - (@attempt, @error, @recovery, @ensure) -> + constructor: (@attempt, @error, @recovery, @ensure) -> children: ['attempt', 'recovery', 'ensure'] @@ -1194,7 +1198,7 @@ exports.Try = class Try extends Base # Simple node to throw an exception. exports.Throw = class Throw extends Base - (@expression) -> + constructor: (@expression) -> children: ['expression'] @@ -1212,7 +1216,7 @@ exports.Throw = class Throw extends Base # similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth # table. exports.Existence = class Existence extends Base - (@expression) -> + constructor: (@expression) -> children: ['expression'] @@ -1238,7 +1242,7 @@ exports.Existence = class Existence extends Base # # Parentheses are a good way to force any statement to become an expression. exports.Parens = class Parens extends Base - (@expression) -> + constructor: (@expression) -> children: ['expression'] @@ -1265,7 +1269,7 @@ exports.Parens = class Parens extends Base # the current index of the loop as a second parameter. Unlike Ruby blocks, # you can map and filter in a single pass. exports.For = class For extends Base - (body, head) -> + constructor: (body, head) -> if head.index instanceof Value throw SyntaxError 'index cannot be a pattern matching expression' extend this, head @@ -1398,7 +1402,7 @@ exports.For = class For extends Base # A JavaScript *switch* statement. Converts into a returnable expression on-demand. exports.Switch = class Switch extends Base - (@subject, @cases, @otherwise) -> + constructor: (@subject, @cases, @otherwise) -> children: ['subject', 'cases', 'otherwise'] @@ -1433,7 +1437,7 @@ exports.Switch = class Switch extends Base # Single-expression **Ifs** are compiled into conditional operators if possible, # because ternaries are already proper expressions, and don't need conversion. exports.If = class If extends Base - (condition, @body, options = {}) -> + constructor: (condition, @body, options = {}) -> @condition = if options.invert then condition.invert() else condition @elseBody = null @isChain = false diff --git a/src/optparse.coffee b/src/optparse.coffee index 3b013d99..3f486783 100644 --- a/src/optparse.coffee +++ b/src/optparse.coffee @@ -13,7 +13,7 @@ exports.OptionParser = class OptionParser # [short-flag, long-flag, description] # # Along with an an optional banner for the usage help. - (rules, @banner) -> + constructor: (rules, @banner) -> @rules = buildRules rules # Parse the list of arguments, populating an `options` object with all of the diff --git a/src/rewriter.coffee b/src/rewriter.coffee index af7fe250..f3ca1f65 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -93,16 +93,17 @@ class exports.Rewriter # Object literals may be written with implicit braces, for simple cases. # Insert the missing braces here, so that the parser doesn't have to. addImplicitBraces: -> - stack = [] - start = null + stack = [] + start = null + startIndent = 0 condition = (token, i) -> {(i+1): one, (i+2): two, (i+3): three} = @tokens return false if 'HERECOMMENT' is one?[0] [tag] = token - tag in ['TERMINATOR', 'OUTDENT'] and - not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':') or - tag is ',' and one and - one[0] not in ['IDENTIFIER', 'NUMBER', 'STRING', '@', 'TERMINATOR', 'OUTDENT', '('] + (tag in ['TERMINATOR', 'OUTDENT'] and + not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':')) or + (tag is ',' and one and + one[0] not in ['IDENTIFIER', 'NUMBER', 'STRING', '@', 'TERMINATOR', 'OUTDENT', '(']) action = (token, i) -> @tokens.splice i, 0, ['}', '}', token[2]] @scanTokens (token, i, tokens) -> if (tag = token[0]) in EXPRESSION_START diff --git a/src/scope.coffee b/src/scope.coffee index 8d4c46cb..344ef52f 100644 --- a/src/scope.coffee +++ b/src/scope.coffee @@ -17,7 +17,7 @@ exports.Scope = class Scope # as well as a reference to the **Expressions** node is belongs to, which is # where it should declare its variables, and a reference to the function that # it wraps. - (@parent, @expressions, @method) -> + constructor:(@parent, @expressions, @method) -> @variables = [{name: 'arguments', type: 'arguments'}] @positions = {} if @parent diff --git a/test/test_arguments.coffee b/test/test_arguments.coffee index 92686b06..198332de 100644 --- a/test/test_arguments.coffee +++ b/test/test_arguments.coffee @@ -35,7 +35,7 @@ eq context.arg, 3 eq context.arg.join(' '), '1 2 3' class Klass - (@one, @two) -> + constructor: (@one, @two) -> obj = new Klass 1, 2 diff --git a/test/test_classes.coffee b/test/test_classes.coffee index ad5d1d7b..6acd8bae 100644 --- a/test/test_classes.coffee +++ b/test/test_classes.coffee @@ -18,7 +18,7 @@ thirdCtor = -> @array = [1, 2, 3] class ThirdChild extends SecondChild - -> thirdCtor.call this + constructor: -> thirdCtor.call this # Gratuitous comment for testing. func: (string) -> @@ -40,15 +40,15 @@ ok (new ThirdChild).array.join(' ') is '1 2 3' class TopClass - (arg) -> + constructor: (arg) -> @prop = 'top-' + arg class SuperClass extends TopClass - (arg) -> + constructor: (arg) -> super 'super-' + arg class SubClass extends SuperClass - -> + constructor: -> super 'sub' ok (new SubClass).prop is 'top-super-sub' @@ -57,7 +57,7 @@ ok (new SubClass).prop is 'top-super-sub' class OneClass @new: 'new' function: 'function' - (name) -> @name = name + constructor: (name) -> @name = name class TwoClass extends OneClass delete TwoClass.new @@ -131,10 +131,10 @@ ok obj.amI() # super() calls in constructors of classes that are defined as object properties. class Hive - (name) -> @name = name + constructor: (name) -> @name = name class Hive.Bee extends Hive - (name) -> super + constructor: (name) -> super maya = new Hive.Bee 'Maya' ok maya.name is 'Maya' @@ -154,7 +154,7 @@ ok instance.name() is 'class' # ... or statically, to the class. class Dog - (name) -> + constructor: (name) -> @name = name bark: => @@ -188,7 +188,7 @@ eq (func() for func in m.generate()).join(' '), '10 10 10' # Testing a contructor called with varargs. class Connection - (one, two, three) -> + constructor: (one, two, three) -> [@one, @two, @three] = [one, two, three] out: -> @@ -257,12 +257,10 @@ classMaker = -> @value = inner class One - ctor = classMaker() - -> ctor.call this + constructor: classMaker() class Two - ctor = classMaker() - -> ctor.call this + constructor: classMaker() ok (new One).value is 1 ok (new Two).value is 2 diff --git a/test/test_comprehensions.coffee b/test/test_comprehensions.coffee index d24632ed..44f6cb7a 100644 --- a/test/test_comprehensions.coffee +++ b/test/test_comprehensions.coffee @@ -146,7 +146,7 @@ ok expr(2, 4, 8).join(' ') is '4 16 64' # Fast object comprehensions over all properties, including prototypal ones. class Cat - -> @name = 'Whiskers' + constructor: -> @name = 'Whiskers' breed: 'tabby' hair: 'cream' diff --git a/test/test_existence.coffee b/test/test_existence.coffee index 6ba016a3..8c8e2bb7 100644 --- a/test/test_existence.coffee +++ b/test/test_existence.coffee @@ -81,7 +81,7 @@ eq ident(non?.existent().method()), undefined, 'soaks inner values' # Soaks constructor invocations. a = 0 class Foo - -> a += 1 + constructor: -> a += 1 bar: "bat" ok (new Foo())?.bar is 'bat'