constructor: prefix is back for classes. punto.

This commit is contained in:
Jeremy Ashkenas
2010-11-14 14:21:55 -05:00
parent 15bdcf79e6
commit be17b8215c
10 changed files with 89 additions and 79 deletions

View File

@@ -856,12 +856,27 @@
base = assign.variable.base; base = assign.variable.base;
delete assign.context; delete assign.context;
func = assign.value; func = assign.value;
if (!assign.variable["this"]) { if (base.value === 'constructor') {
assign.variable = new Value(new Literal(name), [new Accessor(base, 'proto')]); if (this.ctor) {
} throw new Error('cannot define more than one constructor in a class');
if (func instanceof Code && func.bound) { }
this.boundFuncs.push(base); if (func.bound) {
func.bound = false; 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); _results.push(assign);
@@ -877,15 +892,6 @@
node = _ref[i]; node = _ref[i];
if (node instanceof Value && node.isObject(true)) { if (node instanceof Value && node.isObject(true)) {
exps[i] = compact(this.addProperties(node, name)); 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)); return child.expressions = exps = compact(flatten(exps));

View File

@@ -106,9 +106,10 @@
}); });
}; };
Rewriter.prototype.addImplicitBraces = function() { Rewriter.prototype.addImplicitBraces = function() {
var action, condition, stack, start; var action, condition, stack, start, startIndent;
stack = []; stack = [];
start = null; start = null;
startIndent = 0;
condition = function(token, i) { condition = function(token, i) {
var one, tag, three, two, _ref, _ref2; var one, tag, three, two, _ref, _ref2;
_ref = this.tokens, one = _ref[i + 1], two = _ref[i + 2], three = _ref[i + 3]; _ref = this.tokens, one = _ref[i + 1], two = _ref[i + 2], three = _ref[i + 3];
@@ -116,7 +117,7 @@
return false; return false;
} }
tag = token[0]; 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) { action = function(token, i) {
return this.tokens.splice(i, 0, ['}', '}', token[2]]); return this.tokens.splice(i, 0, ['}', '}', token[2]]);

View File

@@ -150,7 +150,7 @@ exports.Base = class Base
# indented block of code -- the implementation of a function, a clause in an # indented block of code -- the implementation of a function, a clause in an
# `if`, `switch`, or `try`, and so on... # `if`, `switch`, or `try`, and so on...
exports.Expressions = class Expressions extends Base exports.Expressions = class Expressions extends Base
(nodes) -> constructor: (nodes) ->
@expressions = compact flatten nodes or [] @expressions = compact flatten nodes or []
children: ['expressions'] children: ['expressions']
@@ -251,7 +251,7 @@ exports.Expressions = class Expressions extends Base
# JavaScript without translation, such as: strings, numbers, # JavaScript without translation, such as: strings, numbers,
# `true`, `false`, `null`... # `true`, `false`, `null`...
exports.Literal = class Literal extends Base exports.Literal = class Literal extends Base
(@value) -> constructor: (@value) ->
makeReturn: -> makeReturn: ->
if @isPureStatement() then this else new Return this 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 # A `return` is a *pureStatement* -- wrapping it in a closure wouldn't
# make sense. # make sense.
exports.Return = class Return extends Base exports.Return = class Return extends Base
(@expression) -> constructor: (@expression) ->
children: ['expression'] children: ['expression']
@@ -301,7 +301,7 @@ exports.Return = class Return extends Base
# A value, variable or literal or parenthesized, indexed or dotted into, # A value, variable or literal or parenthesized, indexed or dotted into,
# or vanilla. # or vanilla.
exports.Value = class Value extends Base exports.Value = class Value extends Base
(base, props, tag) -> constructor: (base, props, tag) ->
return base if not props and base instanceof Value return base if not props and base instanceof Value
@base = base @base = base
@properties = props or [] @properties = props or []
@@ -393,7 +393,7 @@ exports.Value = class Value extends Base
# CoffeeScript passes through block comments as JavaScript block comments # CoffeeScript passes through block comments as JavaScript block comments
# at the same position. # at the same position.
exports.Comment = class Comment extends Base exports.Comment = class Comment extends Base
(@comment) -> constructor: (@comment) ->
isPureStatement: YES isPureStatement: YES
isStatement: 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 # Node for a function invocation. Takes care of converting `super()` calls into
# calls against the prototype's function of the same name. # calls against the prototype's function of the same name.
exports.Call = class Call extends Base exports.Call = class Call extends Base
(variable, @args = [], @soak) -> constructor: (variable, @args = [], @soak) ->
@isNew = false @isNew = false
@isSuper = variable is 'super' @isSuper = variable is 'super'
@variable = if @isSuper then null else variable @variable = if @isSuper then null else variable
@@ -511,7 +511,7 @@ exports.Call = class Call extends Base
# After `goog.inherits` from the # After `goog.inherits` from the
# [Closure Library](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.html). # [Closure Library](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.html).
exports.Extends = class Extends extends Base exports.Extends = class Extends extends Base
(@child, @parent) -> constructor: (@child, @parent) ->
children: ['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 # A `.` accessor into a property of a value, or the `::` shorthand for
# an accessor into the object's prototype. # an accessor into the object's prototype.
exports.Accessor = class Accessor extends Base exports.Accessor = class Accessor extends Base
(@name, tag) -> constructor: (@name, tag) ->
@proto = if tag is 'proto' then '.prototype' else '' @proto = if tag is 'proto' then '.prototype' else ''
@soak = tag is 'soak' @soak = tag is 'soak'
@@ -541,7 +541,7 @@ exports.Accessor = class Accessor extends Base
# A `[ ... ]` indexed accessor into an array or object. # A `[ ... ]` indexed accessor into an array or object.
exports.Index = class Index extends Base exports.Index = class Index extends Base
(@index) -> constructor: (@index) ->
children: ['index'] children: ['index']
@@ -555,7 +555,7 @@ exports.Index = class Index extends Base
# An object literal, nothing fancy. # An object literal, nothing fancy.
exports.Obj = class Obj extends Base exports.Obj = class Obj extends Base
(props, @generated = false) -> constructor: (props, @generated = false) ->
@objects = @properties = props or [] @objects = @properties = props or []
children: ['properties'] children: ['properties']
@@ -618,7 +618,7 @@ exports.Obj = class Obj extends Base
# An array literal. # An array literal.
exports.Arr = class Arr extends Base exports.Arr = class Arr extends Base
(objs) -> constructor: (objs) ->
@objects = objs or [] @objects = objs or []
children: ['objects'] children: ['objects']
@@ -643,7 +643,7 @@ exports.Arr = class Arr extends Base
# Initialize a **Class** with its name, an optional superclass, and a # Initialize a **Class** with its name, an optional superclass, and a
# list of prototype property assignments. # list of prototype property assignments.
exports.Class = class Class extends Base exports.Class = class Class extends Base
(@variable, @parent, @body = new Expressions) -> constructor: (@variable, @parent, @body = new Expressions) ->
@boundFuncs = [] @boundFuncs = []
children: ['variable', 'parent', 'body'] children: ['variable', 'parent', 'body']
@@ -684,11 +684,22 @@ exports.Class = class Class extends Base
base = assign.variable.base base = assign.variable.base
delete assign.context delete assign.context
func = assign.value func = assign.value
unless assign.variable.this if base.value is 'constructor'
assign.variable = new Value(new Literal(name), [new Accessor(base, 'proto')]) if @ctor
if func instanceof Code and func.bound throw new Error 'cannot define more than one constructor in a class'
@boundFuncs.push base if func.bound
func.bound = no 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 assign
# Walk the body of the class, looking for prototype properties to be converted. # 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 for node, i in exps = child.expressions
if node instanceof Value and node.isObject(true) if node instanceof Value and node.isObject(true)
exps[i] = compact @addProperties node, name 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 child.expressions = exps = compact flatten exps
# Make sure that a constructor is defined for the class, and properly # 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 # The **Assign** is used to assign a local variable to value, or to set the
# property of an object -- including within object literals. # property of an object -- including within object literals.
exports.Assign = class Assign extends Base exports.Assign = class Assign extends Base
(@variable, @value, @context) -> constructor: (@variable, @value, @context) ->
# Matchers for detecting class/method names # Matchers for detecting class/method names
METHOD_DEF: /^(?:(\S+)\.prototype\.|\S+?)?\b([$A-Za-z_][$\w]*)$/ 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 # When for the purposes of walking the contents of a function body, the Code
# has no *children* -- they're within the inner scope. # has no *children* -- they're within the inner scope.
exports.Code = class Code extends Base exports.Code = class Code extends Base
(params, body, tag) -> constructor: (params, body, tag) ->
@params = params or [] @params = params or []
@body = body or new Expressions @body = body or new Expressions
@bound = tag is 'boundfunc' @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, # 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. # as well as be a splat, gathering up a group of parameters into an array.
exports.Param = class Param extends Base exports.Param = class Param extends Base
(@name, @value, @splat) -> constructor: (@name, @value, @splat) ->
children: ['name', 'value'] children: ['name', 'value']
@@ -953,7 +957,7 @@ exports.Splat = class Splat extends Base
isAssignable: YES isAssignable: YES
(name) -> constructor: (name) ->
@name = if name.compile then name else new Literal name @name = if name.compile then name else new Literal name
assigns: (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 # it, all other loops can be manufactured. Useful in cases where you need more
# flexibility or more speed than a comprehension can provide. # flexibility or more speed than a comprehension can provide.
exports.While = class While extends Base exports.While = class While extends Base
(condition, options) -> constructor: (condition, options) ->
@condition = if options?.invert then condition.invert() else condition @condition = if options?.invert then condition.invert() else condition
@guard = options?.guard @guard = options?.guard
@@ -1038,7 +1042,7 @@ exports.While = class While extends Base
# Simple Arithmetic and logical operations. Performs some conversion from # Simple Arithmetic and logical operations. Performs some conversion from
# CoffeeScript operations into their JavaScript equivalents. # CoffeeScript operations into their JavaScript equivalents.
exports.Op = class Op extends Base exports.Op = class Op extends Base
(op, first, second, flip) -> constructor: (op, first, second, flip) ->
return new In first, second if op is 'in' return new In first, second if op is 'in'
if op is 'new' if op is 'new'
return first.newInstance() if first instanceof Call return first.newInstance() if first instanceof Call
@@ -1129,7 +1133,7 @@ exports.Op = class Op extends Base
#### In #### In
exports.In = class In extends Base exports.In = class In extends Base
(@object, @array) -> constructor: (@object, @array) ->
children: ['object', 'array'] children: ['object', 'array']
@@ -1164,7 +1168,7 @@ exports.In = class In extends Base
# A classic *try/catch/finally* block. # A classic *try/catch/finally* block.
exports.Try = class Try extends Base exports.Try = class Try extends Base
(@attempt, @error, @recovery, @ensure) -> constructor: (@attempt, @error, @recovery, @ensure) ->
children: ['attempt', 'recovery', 'ensure'] children: ['attempt', 'recovery', 'ensure']
@@ -1194,7 +1198,7 @@ exports.Try = class Try extends Base
# Simple node to throw an exception. # Simple node to throw an exception.
exports.Throw = class Throw extends Base exports.Throw = class Throw extends Base
(@expression) -> constructor: (@expression) ->
children: ['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 # similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth
# table. # table.
exports.Existence = class Existence extends Base exports.Existence = class Existence extends Base
(@expression) -> constructor: (@expression) ->
children: ['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. # Parentheses are a good way to force any statement to become an expression.
exports.Parens = class Parens extends Base exports.Parens = class Parens extends Base
(@expression) -> constructor: (@expression) ->
children: ['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, # the current index of the loop as a second parameter. Unlike Ruby blocks,
# you can map and filter in a single pass. # you can map and filter in a single pass.
exports.For = class For extends Base exports.For = class For extends Base
(body, head) -> constructor: (body, head) ->
if head.index instanceof Value if head.index instanceof Value
throw SyntaxError 'index cannot be a pattern matching expression' throw SyntaxError 'index cannot be a pattern matching expression'
extend this, head extend this, head
@@ -1398,7 +1402,7 @@ exports.For = class For extends Base
# A JavaScript *switch* statement. Converts into a returnable expression on-demand. # A JavaScript *switch* statement. Converts into a returnable expression on-demand.
exports.Switch = class Switch extends Base exports.Switch = class Switch extends Base
(@subject, @cases, @otherwise) -> constructor: (@subject, @cases, @otherwise) ->
children: ['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, # Single-expression **Ifs** are compiled into conditional operators if possible,
# because ternaries are already proper expressions, and don't need conversion. # because ternaries are already proper expressions, and don't need conversion.
exports.If = class If extends Base exports.If = class If extends Base
(condition, @body, options = {}) -> constructor: (condition, @body, options = {}) ->
@condition = if options.invert then condition.invert() else condition @condition = if options.invert then condition.invert() else condition
@elseBody = null @elseBody = null
@isChain = false @isChain = false

View File

@@ -13,7 +13,7 @@ exports.OptionParser = class OptionParser
# [short-flag, long-flag, description] # [short-flag, long-flag, description]
# #
# Along with an an optional banner for the usage help. # Along with an an optional banner for the usage help.
(rules, @banner) -> constructor: (rules, @banner) ->
@rules = buildRules rules @rules = buildRules rules
# Parse the list of arguments, populating an `options` object with all of the # Parse the list of arguments, populating an `options` object with all of the

View File

@@ -93,16 +93,17 @@ class exports.Rewriter
# Object literals may be written with implicit braces, for simple cases. # Object literals may be written with implicit braces, for simple cases.
# Insert the missing braces here, so that the parser doesn't have to. # Insert the missing braces here, so that the parser doesn't have to.
addImplicitBraces: -> addImplicitBraces: ->
stack = [] stack = []
start = null start = null
startIndent = 0
condition = (token, i) -> condition = (token, i) ->
{(i+1): one, (i+2): two, (i+3): three} = @tokens {(i+1): one, (i+2): two, (i+3): three} = @tokens
return false if 'HERECOMMENT' is one?[0] return false if 'HERECOMMENT' is one?[0]
[tag] = token [tag] = token
tag in ['TERMINATOR', 'OUTDENT'] and (tag in ['TERMINATOR', 'OUTDENT'] and
not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':') or not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':')) or
tag is ',' and one and (tag is ',' and one and
one[0] not in ['IDENTIFIER', 'NUMBER', 'STRING', '@', 'TERMINATOR', 'OUTDENT', '('] one[0] not in ['IDENTIFIER', 'NUMBER', 'STRING', '@', 'TERMINATOR', 'OUTDENT', '('])
action = (token, i) -> @tokens.splice i, 0, ['}', '}', token[2]] action = (token, i) -> @tokens.splice i, 0, ['}', '}', token[2]]
@scanTokens (token, i, tokens) -> @scanTokens (token, i, tokens) ->
if (tag = token[0]) in EXPRESSION_START if (tag = token[0]) in EXPRESSION_START

View File

@@ -17,7 +17,7 @@ exports.Scope = class Scope
# as well as a reference to the **Expressions** node is belongs to, which is # 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 # where it should declare its variables, and a reference to the function that
# it wraps. # it wraps.
(@parent, @expressions, @method) -> constructor:(@parent, @expressions, @method) ->
@variables = [{name: 'arguments', type: 'arguments'}] @variables = [{name: 'arguments', type: 'arguments'}]
@positions = {} @positions = {}
if @parent if @parent

View File

@@ -35,7 +35,7 @@ eq context.arg, 3
eq context.arg.join(' '), '1 2 3' eq context.arg.join(' '), '1 2 3'
class Klass class Klass
(@one, @two) -> constructor: (@one, @two) ->
obj = new Klass 1, 2 obj = new Klass 1, 2

View File

@@ -18,7 +18,7 @@ thirdCtor = ->
@array = [1, 2, 3] @array = [1, 2, 3]
class ThirdChild extends SecondChild class ThirdChild extends SecondChild
-> thirdCtor.call this constructor: -> thirdCtor.call this
# Gratuitous comment for testing. # Gratuitous comment for testing.
func: (string) -> func: (string) ->
@@ -40,15 +40,15 @@ ok (new ThirdChild).array.join(' ') is '1 2 3'
class TopClass class TopClass
(arg) -> constructor: (arg) ->
@prop = 'top-' + arg @prop = 'top-' + arg
class SuperClass extends TopClass class SuperClass extends TopClass
(arg) -> constructor: (arg) ->
super 'super-' + arg super 'super-' + arg
class SubClass extends SuperClass class SubClass extends SuperClass
-> constructor: ->
super 'sub' super 'sub'
ok (new SubClass).prop is 'top-super-sub' ok (new SubClass).prop is 'top-super-sub'
@@ -57,7 +57,7 @@ ok (new SubClass).prop is 'top-super-sub'
class OneClass class OneClass
@new: 'new' @new: 'new'
function: 'function' function: 'function'
(name) -> @name = name constructor: (name) -> @name = name
class TwoClass extends OneClass class TwoClass extends OneClass
delete TwoClass.new delete TwoClass.new
@@ -131,10 +131,10 @@ ok obj.amI()
# super() calls in constructors of classes that are defined as object properties. # super() calls in constructors of classes that are defined as object properties.
class Hive class Hive
(name) -> @name = name constructor: (name) -> @name = name
class Hive.Bee extends Hive class Hive.Bee extends Hive
(name) -> super constructor: (name) -> super
maya = new Hive.Bee 'Maya' maya = new Hive.Bee 'Maya'
ok maya.name is 'Maya' ok maya.name is 'Maya'
@@ -154,7 +154,7 @@ ok instance.name() is 'class'
# ... or statically, to the class. # ... or statically, to the class.
class Dog class Dog
(name) -> constructor: (name) ->
@name = name @name = name
bark: => bark: =>
@@ -188,7 +188,7 @@ eq (func() for func in m.generate()).join(' '), '10 10 10'
# Testing a contructor called with varargs. # Testing a contructor called with varargs.
class Connection class Connection
(one, two, three) -> constructor: (one, two, three) ->
[@one, @two, @three] = [one, two, three] [@one, @two, @three] = [one, two, three]
out: -> out: ->
@@ -257,12 +257,10 @@ classMaker = ->
@value = inner @value = inner
class One class One
ctor = classMaker() constructor: classMaker()
-> ctor.call this
class Two class Two
ctor = classMaker() constructor: classMaker()
-> ctor.call this
ok (new One).value is 1 ok (new One).value is 1
ok (new Two).value is 2 ok (new Two).value is 2

View File

@@ -146,7 +146,7 @@ ok expr(2, 4, 8).join(' ') is '4 16 64'
# Fast object comprehensions over all properties, including prototypal ones. # Fast object comprehensions over all properties, including prototypal ones.
class Cat class Cat
-> @name = 'Whiskers' constructor: -> @name = 'Whiskers'
breed: 'tabby' breed: 'tabby'
hair: 'cream' hair: 'cream'

View File

@@ -81,7 +81,7 @@ eq ident(non?.existent().method()), undefined, 'soaks inner values'
# Soaks constructor invocations. # Soaks constructor invocations.
a = 0 a = 0
class Foo class Foo
-> a += 1 constructor: -> a += 1
bar: "bat" bar: "bat"
ok (new Foo())?.bar is 'bat' ok (new Foo())?.bar is 'bat'