switching 'a in b' to 'a of b', and adding an array presence check for 'a in b'.
This commit is contained in:
parent
38a9b7166b
commit
0fcfb80be4
|
@ -739,8 +739,12 @@
|
|||
}), o("Expression INSTANCEOF Expression", function() {
|
||||
return new OpNode('instanceof', $1, $3);
|
||||
}), o("Expression IN Expression", function() {
|
||||
return new InNode($1, $3);
|
||||
}), o("Expression OF Expression", function() {
|
||||
return new OpNode('in', $1, $3);
|
||||
}), o("Expression ! IN Expression", function() {
|
||||
return new OpNode('!', new InNode($1, $4));
|
||||
}), o("Expression ! OF Expression", function() {
|
||||
return new OpNode('!', new ParentheticalNode(new OpNode('in', $1, $4)));
|
||||
})
|
||||
]
|
||||
|
|
80
lib/nodes.js
80
lib/nodes.js
|
@ -1,5 +1,5 @@
|
|||
(function(){
|
||||
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, flatten, helpers, include, indexOf, literal, merge, starts, utility;
|
||||
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, flatten, helpers, include, indexOf, literal, merge, starts, utility;
|
||||
var __extends = function(child, parent) {
|
||||
var ctor = function(){ };
|
||||
ctor.prototype = parent.prototype;
|
||||
|
@ -79,14 +79,21 @@
|
|||
// 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,
|
||||
// by assigning it to a temporary variable.
|
||||
BaseNode.prototype.compileReference = function(o, onlyIfNecessary) {
|
||||
var compiled, reference;
|
||||
if (onlyIfNecessary && !(this instanceof CallNode || this instanceof ValueNode && (!(this.base instanceof LiteralNode) || this.hasProperties()))) {
|
||||
BaseNode.prototype.compileReference = function(o, options) {
|
||||
var compiled, pair, reference;
|
||||
pair = (function() {
|
||||
if (!(this instanceof CallNode || this instanceof ValueNode && (!(this.base instanceof LiteralNode) || this.hasProperties()))) {
|
||||
return [this, this];
|
||||
}
|
||||
} else {
|
||||
reference = literal(o.scope.freeVariable());
|
||||
compiled = new AssignNode(reference, this);
|
||||
return [compiled, reference];
|
||||
}
|
||||
}).call(this);
|
||||
if (!(options && options.precompile)) {
|
||||
return pair;
|
||||
}
|
||||
return [pair[0].compile(o), pair[1].compile(o)];
|
||||
};
|
||||
// Convenience method to grab the current indentation level, plus tabbing in.
|
||||
BaseNode.prototype.idt = function(tabs) {
|
||||
|
@ -704,10 +711,10 @@
|
|||
// But only if they need to be cached to avoid double evaluation.
|
||||
RangeNode.prototype.compileVariables = function(o) {
|
||||
var _b, _c, parts;
|
||||
_b = this.from.compileReference(o, true);
|
||||
_b = this.from.compileReference(o);
|
||||
this.from = _b[0];
|
||||
this.fromVar = _b[1];
|
||||
_c = this.to.compileReference(o, true);
|
||||
_c = this.to.compileReference(o);
|
||||
this.to = _c[0];
|
||||
this.toVar = _c[1];
|
||||
parts = [];
|
||||
|
@ -1425,6 +1432,63 @@
|
|||
};
|
||||
return OpNode;
|
||||
})();
|
||||
//### InNode
|
||||
exports.InNode = (function() {
|
||||
InNode = function(object, array) {
|
||||
this.object = object;
|
||||
this.array = array;
|
||||
return this;
|
||||
};
|
||||
__extends(InNode, BaseNode);
|
||||
InNode.prototype['class'] = 'InNode';
|
||||
InNode.prototype.children = ['object', 'array'];
|
||||
InNode.prototype.isArray = function() {
|
||||
return this.array instanceof ValueNode && this.array.isArray();
|
||||
};
|
||||
InNode.prototype.compileNode = function(o) {
|
||||
var _b;
|
||||
_b = this.object.compileReference(o, {
|
||||
precompile: true
|
||||
});
|
||||
this.obj1 = _b[0];
|
||||
this.obj2 = _b[1];
|
||||
if (this.isArray()) {
|
||||
return this.compileOrTest(o);
|
||||
} else {
|
||||
return this.compileLoopTest(o);
|
||||
}
|
||||
};
|
||||
InNode.prototype.compileOrTest = function(o) {
|
||||
var _b, _c, _d, i, item, tests;
|
||||
tests = (function() {
|
||||
_b = []; _c = this.array.base.objects;
|
||||
for (i = 0, _d = _c.length; i < _d; i++) {
|
||||
item = _c[i];
|
||||
_b.push(("" + (item.compile(o)) + " === " + (i ? this.obj2 : this.obj1)));
|
||||
}
|
||||
return _b;
|
||||
}).call(this);
|
||||
return "(" + (tests.join(' || ')) + ")";
|
||||
};
|
||||
InNode.prototype.compileLoopTest = function(o) {
|
||||
var _b, _c, body, i, l;
|
||||
_b = this.array.compileReference(o, {
|
||||
precompile: true
|
||||
});
|
||||
this.arr1 = _b[0];
|
||||
this.arr2 = _b[1];
|
||||
_c = [o.scope.freeVariable(), o.scope.freeVariable()];
|
||||
i = _c[0];
|
||||
l = _c[1];
|
||||
body = ("!!(function(){ for (var " + i + "=0, " + l + "=" + (this.arr1) + ".length; " + i + "<" + l + "; " + i + "++) if (" + (this.arr2) + "[" + i + "] === " + this.obj2 + ") return true; })()");
|
||||
if (this.obj1 !== this.obj2) {
|
||||
return "" + this.obj1 + ";\n" + this.tab + body;
|
||||
} else {
|
||||
return body;
|
||||
}
|
||||
};
|
||||
return InNode;
|
||||
})();
|
||||
//### TryNode
|
||||
// A classic *try/catch/finally* block.
|
||||
exports.TryNode = (function() {
|
||||
|
@ -1506,7 +1570,7 @@
|
|||
// Be careful not to double-evaluate anything.
|
||||
ExistenceNode.compileTest = function(o, variable) {
|
||||
var _b, first, second;
|
||||
_b = variable.compileReference(o, true);
|
||||
_b = variable.compileReference(o);
|
||||
first = _b[0];
|
||||
second = _b[1];
|
||||
return "(typeof " + (first.compile(o)) + " !== \"undefined\" && " + (second.compile(o)) + " !== null)";
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -180,7 +180,7 @@
|
|||
stack[stack.length - 2] += stack.pop();
|
||||
}
|
||||
open = stack[stack.length - 1] > 0;
|
||||
if (prev && prev.spaced && include(IMPLICIT_FUNC, prev[0]) && include(IMPLICIT_CALL, tag) && !(tag === '!' && post[0] === 'IN')) {
|
||||
if (prev && prev.spaced && include(IMPLICIT_FUNC, prev[0]) && include(IMPLICIT_CALL, tag) && !(tag === '!' && (post[0] === 'IN' || post[0] === 'OF'))) {
|
||||
this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
|
||||
stack[stack.length - 1] += 1;
|
||||
if (include(EXPRESSION_START, tag)) {
|
||||
|
|
|
@ -565,8 +565,10 @@ grammar: {
|
|||
o "Expression ?= Expression", -> new OpNode '?=', $1, $3
|
||||
|
||||
o "Expression INSTANCEOF Expression", -> new OpNode 'instanceof', $1, $3
|
||||
o "Expression IN Expression", -> new OpNode 'in', $1, $3
|
||||
o "Expression ! IN Expression", -> new OpNode '!', new ParentheticalNode new OpNode 'in', $1, $4
|
||||
o "Expression IN Expression", -> new InNode $1, $3
|
||||
o "Expression OF Expression", -> new OpNode 'in', $1, $3
|
||||
o "Expression ! IN Expression", -> new OpNode '!', new InNode $1, $4
|
||||
o "Expression ! OF Expression", -> new OpNode '!', new ParentheticalNode new OpNode 'in', $1, $4
|
||||
]
|
||||
|
||||
}
|
||||
|
|
|
@ -61,14 +61,16 @@ exports.BaseNode: class BaseNode
|
|||
# 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,
|
||||
# by assigning it to a temporary variable.
|
||||
compileReference: (o, onlyIfNecessary) ->
|
||||
if onlyIfNecessary and not
|
||||
(this instanceof CallNode or
|
||||
this instanceof ValueNode and (not (@base instanceof LiteralNode) or @hasProperties()))
|
||||
return [this, this]
|
||||
compileReference: (o, options) ->
|
||||
pair: if not (this instanceof CallNode or this instanceof ValueNode and
|
||||
(not (@base instanceof LiteralNode) or @hasProperties()))
|
||||
[this, this]
|
||||
else
|
||||
reference: literal o.scope.freeVariable()
|
||||
compiled: new AssignNode reference, this
|
||||
[compiled, reference]
|
||||
return pair unless options and options.precompile
|
||||
[pair[0].compile(o), pair[1].compile(o)]
|
||||
|
||||
# Convenience method to grab the current indentation level, plus tabbing in.
|
||||
idt: (tabs) ->
|
||||
|
@ -518,8 +520,8 @@ exports.RangeNode: class RangeNode extends BaseNode
|
|||
# Compiles the range's source variables -- where it starts and where it ends.
|
||||
# But only if they need to be cached to avoid double evaluation.
|
||||
compileVariables: (o) ->
|
||||
[@from, @fromVar]: @from.compileReference o, true
|
||||
[@to, @toVar]: @to.compileReference o, true
|
||||
[@from, @fromVar]: @from.compileReference o
|
||||
[@to, @toVar]: @to.compileReference o
|
||||
parts: []
|
||||
parts.push @from.compile o if @from isnt @fromVar
|
||||
parts.push @to.compile o if @to isnt @toVar
|
||||
|
@ -1052,6 +1054,34 @@ exports.OpNode: class OpNode extends BaseNode
|
|||
parts: parts.reverse() if @flip
|
||||
parts.join('')
|
||||
|
||||
#### InNode
|
||||
exports.InNode: class InNode extends BaseNode
|
||||
|
||||
class: 'InNode'
|
||||
children: ['object', 'array']
|
||||
|
||||
constructor: (object, array) ->
|
||||
@object: object
|
||||
@array: array
|
||||
|
||||
isArray: ->
|
||||
@array instanceof ValueNode and @array.isArray()
|
||||
|
||||
compileNode: (o) ->
|
||||
[@obj1, @obj2]: @object.compileReference o, {precompile: yes}
|
||||
if @isArray() then @compileOrTest(o) else @compileLoopTest(o)
|
||||
|
||||
compileOrTest: (o) ->
|
||||
tests: for item, i in @array.base.objects
|
||||
"${item.compile(o)} === ${if i then @obj2 else @obj1}"
|
||||
"(${tests.join(' || ')})"
|
||||
|
||||
compileLoopTest: (o) ->
|
||||
[@arr1, @arr2]: @array.compileReference o, {precompile: yes}
|
||||
[i, l]: [o.scope.freeVariable(), o.scope.freeVariable()]
|
||||
body: "!!(function(){ for (var $i=0, $l=${@arr1}.length; $i<$l; $i++) if (${@arr2}[$i] === $@obj2) return true; })()"
|
||||
if @obj1 isnt @obj2 then "$@obj1;\n$@tab$body" else body
|
||||
|
||||
#### TryNode
|
||||
|
||||
# A classic *try/catch/finally* block.
|
||||
|
@ -1122,7 +1152,7 @@ exports.ExistenceNode: class ExistenceNode extends BaseNode
|
|||
# because other nodes like to check the existence of their variables as well.
|
||||
# Be careful not to double-evaluate anything.
|
||||
@compileTest: (o, variable) ->
|
||||
[first, second]: variable.compileReference o, true
|
||||
[first, second]: variable.compileReference o
|
||||
"(typeof ${first.compile(o)} !== \"undefined\" && ${second.compile(o)} !== null)"
|
||||
|
||||
#### ParentheticalNode
|
||||
|
|
|
@ -127,7 +127,7 @@ exports.Rewriter: class Rewriter
|
|||
stack[stack.length - 2]: + stack.pop() if tag is 'OUTDENT'
|
||||
open: stack[stack.length - 1] > 0
|
||||
if prev and prev.spaced and include(IMPLICIT_FUNC, prev[0]) and include(IMPLICIT_CALL, tag) and
|
||||
not (tag is '!' and post[0] is 'IN')
|
||||
not (tag is '!' and (post[0] is 'IN' or post[0] is 'OF'))
|
||||
@tokens.splice i, 0, ['CALL_START', '(', token[2]]
|
||||
stack[stack.length - 1]: + 1
|
||||
stack.push 0 if include(EXPRESSION_START, tag)
|
||||
|
|
|
@ -56,7 +56,7 @@ ok evens.join(', ') is '4, 6, 8'
|
|||
|
||||
|
||||
# The in operator still works, standalone.
|
||||
ok 2 in evens
|
||||
ok 2 of evens
|
||||
|
||||
|
||||
# Ensure that the closure wrapper preserves local variables.
|
||||
|
|
|
@ -38,5 +38,11 @@ ok val is 1
|
|||
|
||||
# Allow "if x not in y"
|
||||
obj: {a: true}
|
||||
ok 'b' not in obj
|
||||
ok not ('a' not in obj)
|
||||
ok 'a' of obj
|
||||
ok 'b' not of obj
|
||||
|
||||
# And for "a in b" with array presence.
|
||||
ok 100 in [100, 200, 300]
|
||||
array: [100, 200, 300]
|
||||
ok 100 in array
|
||||
ok 1 not in array
|
Loading…
Reference in New Issue