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:
parent
45b60c9a52
commit
1df8abf1cb
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue