Refactor closure compilation

* Break up `Closure` and merge `Closure.wrap` into `Base.compileClosure`
* Construct class closure directly in `Class.compileNode`
* Reuse `isLiteralArguments` in `Range.compileArray`
* Move all helpers to bottom of file
* Add test for #3063
This commit is contained in:
Marc Häfner 2013-10-31 23:25:11 +01:00
parent 45b60c9a52
commit 1df8abf1cb
3 changed files with 99 additions and 119 deletions

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.3
(function() {
var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, CodeFragment, Comment, Existence, Extends, For, HEXNUM, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, last, locationDataToString, merge, multident, parseNum, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1,
var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Extends, For, HEXNUM, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isLiteralArguments, isLiteralThis, last, locationDataToString, merge, multident, parseNum, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
@ -87,12 +87,24 @@
};
Base.prototype.compileClosure = function(o) {
var jumpNode;
var args, argumentsNode, func, jumpNode, meth;
if (jumpNode = this.jumps()) {
jumpNode.error('cannot use a pure statement in an expression');
}
o.sharedScope = true;
return Closure.wrap(this).compileNode(o);
func = new Code([], Block.wrap([this]));
args = [];
if ((argumentsNode = this.contains(isLiteralArguments)) || this.contains(isLiteralThis)) {
args = [new Literal('this')];
if (argumentsNode) {
meth = 'apply';
args.push(new Literal('arguments'));
} else {
meth = 'call';
}
func = new Value(func, [new Access(new Literal(meth))]);
}
return (new Call(func, args)).compileNode(o);
};
Base.prototype.cache = function(o, level, reused) {
@ -1177,9 +1189,7 @@
}
post = "{ " + result + ".push(" + i + "); }\n" + idt + "return " + result + ";\n" + o.indent;
hasArgs = function(node) {
return node != null ? node.contains(function(n) {
return n instanceof Literal && n.value === 'arguments' && !n.asKey;
}) : void 0;
return node != null ? node.contains(isLiteralArguments) : void 0;
};
if (hasArgs(this.from) || hasArgs(this.to)) {
args = ', arguments';
@ -1506,12 +1516,20 @@
};
Class.prototype.compileNode = function(o) {
var call, klass, lname, name, params, _ref2;
var args, argumentsNode, func, jumpNode, klass, lname, name, superClass, _ref2;
if (jumpNode = this.body.jumps()) {
jumpNode.error('Class bodies cannot contain pure statements');
}
if (argumentsNode = this.body.contains(isLiteralArguments)) {
argumentsNode.error("Class bodies shouldn't reference arguments");
}
name = this.determineName() || '_Class';
if (name.reserved) {
name = "_" + name;
}
lname = new Literal(name);
func = new Code([], Block.wrap([this.body]));
args = [];
this.hoistDirectivePrologue();
this.setContext(name);
this.walkBody(name, o);
@ -1522,16 +1540,14 @@
this.body.expressions.unshift(this.ctor);
}
this.body.expressions.push(lname);
call = Closure.wrap(this.body);
if (this.parent && call.args) {
this.superClass = new Literal(o.scope.freeVariable('super', false));
this.body.expressions.unshift(new Extends(lname, this.superClass));
call.args.push(this.parent);
params = call.variable.params || call.variable.base.params;
params.push(new Param(this.superClass));
if (this.parent) {
superClass = new Literal(o.scope.freeVariable('super', false));
this.body.expressions.unshift(new Extends(lname, superClass));
func.params.push(new Param(superClass));
args.push(this.parent);
}
(_ref2 = this.body.expressions).unshift.apply(_ref2, this.directives);
klass = new Parens(call, true);
klass = new Parens(new Call(func, args));
if (this.variable) {
klass = new Assign(this.variable, klass);
}
@ -2969,52 +2985,6 @@
})(Base);
Closure = {
wrap: function(expressions, statement, noReturn) {
var args, argumentsNode, call, func, meth;
if (expressions.jumps()) {
return expressions;
}
func = new Code([], Block.wrap([expressions]));
args = [];
argumentsNode = expressions.contains(this.isLiteralArguments);
if (argumentsNode && expressions.classBody) {
argumentsNode.error("Class bodies shouldn't reference arguments");
}
if (argumentsNode || expressions.contains(this.isLiteralThis)) {
meth = new Literal(argumentsNode ? 'apply' : 'call');
args = [new Literal('this')];
if (argumentsNode) {
args.push(new Literal('arguments'));
}
func = new Value(func, [new Access(meth)]);
}
func.noReturn = noReturn;
call = new Call(func, args);
if (statement) {
return Block.wrap([call]);
} else {
return call;
}
},
isLiteralArguments: function(node) {
return node instanceof Literal && node.value === 'arguments' && !node.asKey;
},
isLiteralThis: function(node) {
return (node instanceof Literal && node.value === 'this' && !node.asKey) || (node instanceof Code && node.bound) || (node instanceof Call && node.isSuper);
}
};
unfoldSoak = function(o, parent, name) {
var ifn;
if (!(ifn = parent[name].unfoldSoak(o))) {
return;
}
parent[name] = ifn.body;
ifn.body = new Value(parent);
return ifn;
};
UTILITIES = {
"extends": function() {
return "function(child, parent) { for (var key in parent) { if (" + (utility('hasProp')) + ".call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }";
@ -3085,4 +3055,22 @@
}
};
isLiteralArguments = function(node) {
return node instanceof Literal && node.value === 'arguments' && !node.asKey;
};
isLiteralThis = function(node) {
return (node instanceof Literal && node.value === 'this' && !node.asKey) || (node instanceof Code && node.bound) || (node instanceof Call && node.isSuper);
};
unfoldSoak = function(o, parent, name) {
var ifn;
if (!(ifn = parent[name].unfoldSoak(o))) {
return;
}
parent[name] = ifn.body;
ifn.body = new Value(parent);
return ifn;
};
}).call(this);

View File

@ -79,7 +79,17 @@ exports.Base = class Base
if jumpNode = @jumps()
jumpNode.error 'cannot use a pure statement in an expression'
o.sharedScope = yes
Closure.wrap(this).compileNode o
func = new Code [], Block.wrap [this]
args = []
if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
args = [new Literal 'this']
if argumentsNode
meth = 'apply'
args.push new Literal 'arguments'
else
meth = 'call'
func = new Value func, [new Access new Literal meth]
(new Call func, args).compileNode o
# If the code generation wishes to use the result of a complex expression
# in multiple places, ensure that the expression is only ever evaluated once,
@ -848,7 +858,7 @@ exports.Range = class Range extends Base
cond = "#{@fromVar} <= #{@toVar}"
body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
hasArgs = (node) -> node?.contains (n) -> n instanceof Literal and n.value is 'arguments' and not n.asKey
hasArgs = (node) -> node?.contains isLiteralArguments
args = ', arguments' if hasArgs(@from) or hasArgs(@to)
[@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
@ -1078,9 +1088,16 @@ exports.Class = class Class extends Base
# equivalent syntax tree and compile that, in pieces. You can see the
# constructor, property assignments, and inheritance getting built out below.
compileNode: (o) ->
if jumpNode = @body.jumps()
jumpNode.error 'Class bodies cannot contain pure statements'
if argumentsNode = @body.contains isLiteralArguments
argumentsNode.error "Class bodies shouldn't reference arguments"
name = @determineName() or '_Class'
name = "_#{name}" if name.reserved
name = "_#{name}" if name.reserved
lname = new Literal name
func = new Code [], Block.wrap [@body]
args = []
@hoistDirectivePrologue()
@setContext name
@ -1091,18 +1108,15 @@ exports.Class = class Class extends Base
@body.expressions.unshift @ctor unless @ctor instanceof Code
@body.expressions.push lname
call = Closure.wrap @body
if @parent and call.args
@superClass = new Literal o.scope.freeVariable 'super', no
@body.expressions.unshift new Extends lname, @superClass
call.args.push @parent
params = call.variable.params or call.variable.base.params
params.push new Param @superClass
if @parent
superClass = new Literal o.scope.freeVariable 'super', no
@body.expressions.unshift new Extends lname, superClass
func.params.push new Param superClass
args.push @parent
@body.expressions.unshift @directives...
klass = new Parens call, yes
klass = new Parens new Call func, args
klass = new Assign @variable, klass if @variable
klass.compileToFragments o
@ -2080,50 +2094,6 @@ exports.If = class If extends Base
unfoldSoak: ->
@soak and this
# Faux-Nodes
# ----------
# Faux-nodes are never created by the grammar, but are used during code
# generation to generate other combinations of nodes.
#### Closure
# A faux-node used to wrap an expressions body in a closure.
Closure =
# Wrap the expressions body, unless it contains a pure statement,
# in which case, no dice. If the body mentions `this` or `arguments`,
# then make sure that the closure wrapper preserves the original values.
wrap: (expressions, statement, noReturn) ->
return expressions if expressions.jumps()
func = new Code [], Block.wrap [expressions]
args = []
argumentsNode = expressions.contains @isLiteralArguments
if argumentsNode and expressions.classBody
argumentsNode.error "Class bodies shouldn't reference arguments"
if argumentsNode or expressions.contains @isLiteralThis
meth = new Literal if argumentsNode then 'apply' else 'call'
args = [new Literal 'this']
args.push new Literal 'arguments' if argumentsNode
func = new Value func, [new Access meth]
func.noReturn = noReturn
call = new Call func, args
if statement then Block.wrap [call] else call
isLiteralArguments: (node) ->
node instanceof Literal and node.value is 'arguments' and not node.asKey
isLiteralThis: (node) ->
(node instanceof Literal and node.value is 'this' and not node.asKey) or
(node instanceof Code and node.bound) or
(node instanceof Call and node.isSuper)
# Unfold a node's child if soak, then tuck the node under created `If`
unfoldSoak = (o, parent, name) ->
return unless ifn = parent[name].unfoldSoak o
parent[name] = ifn.body
ifn.body = new Value parent
ifn
# Constants
# ---------
@ -2190,8 +2160,8 @@ METHOD_DEF = ///
IS_STRING = /^['"]/
IS_REGEX = /^\//
# Utility Functions
# -----------------
# Helper Functions
# ----------------
# Helper for ensuring that utility functions are assigned at the top level.
utility = (name) ->
@ -2212,3 +2182,18 @@ parseNum = (x) ->
parseInt x, 16
else
parseFloat x
isLiteralArguments = (node) ->
node instanceof Literal and node.value is 'arguments' and not node.asKey
isLiteralThis = (node) ->
(node instanceof Literal and node.value is 'this' and not node.asKey) or
(node instanceof Code and node.bound) or
(node instanceof Call and node.isSuper)
# Unfold a node's child if soak, then tuck the node under created `If`
unfoldSoak = (o, parent, name) ->
return unless ifn = parent[name].unfoldSoak o
parent[name] = ifn.body
ifn.body = new Value parent
ifn

View File

@ -800,3 +800,10 @@ test "#2796: ditto, ditto, ditto", ->
new Base
eq answer, 'right!'
test "#3063: Class bodies cannot contain pure statements", ->
throws -> CoffeeScript.compile """
class extends S
return if S.f
@f: => this
"""