switching 'a in b' to 'a of b', and adding an array presence check for 'a in b'.

This commit is contained in:
Jeremy Ashkenas 2010-06-21 23:51:12 -04:00
parent 38a9b7166b
commit 0fcfb80be4
9 changed files with 145 additions and 35 deletions

View File

@ -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)));
})
]

View File

@ -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()))) {
return [this, this];
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;
}
reference = literal(o.scope.freeVariable());
compiled = new AssignNode(reference, this);
return [compiled, reference];
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

View File

@ -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)) {

View File

@ -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
]
}

View File

@ -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]
reference: literal o.scope.freeVariable()
compiled: new AssignNode reference, this
[compiled, reference]
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,14 +1054,42 @@ 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.
exports.TryNode: class TryNode extends BaseNode
class: 'TryNode'
class: 'TryNode'
children: ['attempt', 'recovery', 'ensure']
isStatement: -> yes
isStatement: -> yes
constructor: (attempt, error, recovery, ensure) ->
@attempt: attempt
@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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