783: corrected chained comparison precedence

This commit is contained in:
satyr
2010-10-23 23:51:22 +09:00
parent 6058910b49
commit 1335aee54b
7 changed files with 120 additions and 119 deletions

View File

@@ -596,7 +596,7 @@
}) })
] ]
}; };
operators = [["left", 'CALL_START', 'CALL_END'], ["nonassoc", '++', '--'], ["left", '?'], ["right", 'UNARY'], ["left", 'MATH'], ["left", '+', '-'], ["left", 'SHIFT'], ["left", 'COMPARE'], ["left", 'RELATION'], ["left", '==', '!='], ["left", 'LOGIC'], ["left", '.'], ["nonassoc", 'INDENT', 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'FORIN', 'FOROF', 'FROM', 'TO', 'BY', 'THROW'], ["right", 'IF', 'UNLESS', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'EXTENDS'], ["right", '=', ':', 'COMPOUND_ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'POST_IF', 'POST_UNLESS']]; operators = [["left", 'CALL_START', 'CALL_END'], ["nonassoc", '++', '--'], ["left", '?'], ["right", 'UNARY'], ["left", 'MATH'], ["left", '+', '-'], ["left", 'SHIFT'], ["left", 'RELATION'], ["left", '==', '!=', 'COMPARE'], ["left", 'LOGIC'], ["left", '.'], ["nonassoc", 'INDENT', 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'FORIN', 'FOROF', 'FROM', 'TO', 'BY', 'THROW'], ["right", 'IF', 'UNLESS', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'EXTENDS'], ["right", '=', ':', 'COMPOUND_ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'POST_IF', 'POST_UNLESS']];
tokens = []; tokens = [];
for (name in grammar) { for (name in grammar) {
alternatives = grammar[name]; alternatives = grammar[name];

View File

@@ -416,7 +416,7 @@
if (!herecomment) { if (!herecomment) {
while (match = HEREDOC_INDENT.exec(doc)) { while (match = HEREDOC_INDENT.exec(doc)) {
attempt = match[1]; attempt = match[1];
if (indent === null || 0 < (_ref2 = attempt.length) && _ref2 < indent.length) { if (indent === null || (0 < (_ref2 = attempt.length) && _ref2 < indent.length)) {
indent = attempt; indent = attempt;
} }
} }

View File

@@ -1232,9 +1232,6 @@
'!==': '===', '!==': '===',
'===': '!==' '===': '!=='
}; };
Op.prototype.CHAINABLE = ['<', '>', '>=', '<=', '===', '!=='];
Op.prototype.PREFIX_OPERATORS = ['new', 'typeof', 'delete'];
Op.prototype.MUTATORS = ['++', '--', 'delete'];
Op.prototype.children = ['first', 'second']; Op.prototype.children = ['first', 'second'];
Op.prototype.isUnary = function() { Op.prototype.isUnary = function() {
return !this.second; return !this.second;
@@ -1244,7 +1241,7 @@
}; };
Op.prototype.isChainable = function() { Op.prototype.isChainable = function() {
var _ref2; var _ref2;
return _ref2 = this.operator, __indexOf.call(this.CHAINABLE, _ref2) >= 0; return (_ref2 = this.operator) === '<' || _ref2 === '>' || _ref2 === '>=' || _ref2 === '<=' || _ref2 === '===' || _ref2 === '!==';
}; };
Op.prototype.invert = function() { Op.prototype.invert = function() {
var op; var op;
@@ -1253,18 +1250,15 @@
return this; return this;
} else return this.second ? new Parens(this).invert() : Op.__super__.invert.call(this); } else return this.second ? new Parens(this).invert() : Op.__super__.invert.call(this);
}; };
Op.prototype.toString = function(idt) {
return Op.__super__.toString.call(this, idt, this.constructor.name + ' ' + this.operator);
};
Op.prototype.unfoldSoak = function(o) { Op.prototype.unfoldSoak = function(o) {
var _ref2; var _ref2;
return (_ref2 = this.operator, __indexOf.call(this.MUTATORS, _ref2) >= 0) && If.unfoldSoak(o, this, 'first'); return ((_ref2 = this.operator) === '++' || _ref2 === '--' || _ref2 === 'delete') && If.unfoldSoak(o, this, 'first');
}; };
Op.prototype.compileNode = function(o) { Op.prototype.compileNode = function(o) {
if (this.isUnary()) { if (this.isUnary()) {
return this.compileUnary(o); return this.compileUnary(o);
} }
if (this.isChainable() && this.first.unwrap().isChainable()) { if (this.isChainable() && this.first.isChainable()) {
return this.compileChain(o); return this.compileChain(o);
} }
if (this.operator === '?') { if (this.operator === '?') {
@@ -1274,9 +1268,14 @@
return "" + (this.first.compile(o, LEVEL.OP)) + " " + this.operator + " " + (this.second.compile(o, LEVEL.OP)); return "" + (this.first.compile(o, LEVEL.OP)) + " " + this.operator + " " + (this.second.compile(o, LEVEL.OP));
}; };
Op.prototype.compileChain = function(o) { Op.prototype.compileChain = function(o) {
var _ref2, shared; var _ref2, code, fst, shared;
_ref2 = this.first.unwrap().second.cache(o), this.first.second = _ref2[0], shared = _ref2[1]; _ref2 = this.first.second.cache(o), this.first.second = _ref2[0], shared = _ref2[1];
return "" + (this.first.compile(o, LEVEL.OP)) + " && " + (shared.compile(o)) + " " + this.operator + " " + (this.second.compile(o, LEVEL.OP)); fst = this.first.compile(o, LEVEL.OP);
if (fst.charAt(0) === '(') {
fst = fst.slice(1, -1);
}
code = "" + fst + " && " + (shared.compile(o)) + " " + this.operator + " " + (this.second.compile(o, LEVEL.OP));
return o.level < LEVEL.OP ? code : "(" + code + ")";
}; };
Op.prototype.compileExistence = function(o) { Op.prototype.compileExistence = function(o) {
var fst, ref; var fst, ref;
@@ -1290,10 +1289,19 @@
return new Existence(fst).compile(o) + (" ? " + ref + " : " + (this.second.compile(o, LEVEL.LIST))); return new Existence(fst).compile(o) + (" ? " + ref + " : " + (this.second.compile(o, LEVEL.LIST)));
}; };
Op.prototype.compileUnary = function(o) { Op.prototype.compileUnary = function(o) {
var _ref2, _ref3, parts, space; var op, parts;
space = (_ref2 = this.operator, __indexOf.call(this.PREFIX_OPERATORS, _ref2) >= 0) || this.first instanceof Op && this.first.operator === this.operator && ((_ref3 = this.operator) === '+' || _ref3 === '-') ? ' ' : ''; parts = [op = this.operator];
parts = [this.operator, space, this.first.compile(o, LEVEL.OP)]; if ((op === 'new' || op === 'typeof' || op === 'delete') || (op === '+' || op === '-') && this.first instanceof Op && this.first.operator === op) {
return (this.flip ? parts.reverse() : parts).join(''); parts.push(' ');
}
parts.push(this.first.compile(o, LEVEL.OP));
if (this.flip) {
parts.reverse();
}
return parts.join('');
};
Op.prototype.toString = function(idt) {
return Op.__super__.toString.call(this, idt, this.constructor.name + ' ' + this.operator);
}; };
return Op; return Op;
})(); })();

File diff suppressed because one or more lines are too long

View File

@@ -555,9 +555,8 @@ operators = [
["left", 'MATH'] ["left", 'MATH']
["left", '+', '-'] ["left", '+', '-']
["left", 'SHIFT'] ["left", 'SHIFT']
["left", 'COMPARE']
["left", 'RELATION'] ["left", 'RELATION']
["left", '==', '!='] ["left", '==', '!=', 'COMPARE']
["left", 'LOGIC'] ["left", 'LOGIC']
["left", '.'] ["left", '.']
["nonassoc", 'INDENT', 'OUTDENT'] ["nonassoc", 'INDENT', 'OUTDENT']

View File

@@ -1033,16 +1033,6 @@ exports.Op = class Op extends Base
'!==': '===' '!==': '==='
'===': '!==' '===': '!=='
# The list of operators for which we perform
# [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin).
CHAINABLE: ['<', '>', '>=', '<=', '===', '!==']
# Operators must come before their operands with a space.
PREFIX_OPERATORS: ['new', 'typeof', 'delete']
# Operators that modify a reference.
MUTATORS: ['++', '--', 'delete']
children: ['first', 'second'] children: ['first', 'second']
constructor: (op, first, second, flip) -> constructor: (op, first, second, flip) ->
@@ -1056,14 +1046,13 @@ exports.Op = class Op extends Base
@second = second @second = second
@flip = !!flip @flip = !!flip
isUnary: -> isUnary: -> not @second
not @second
isComplex: -> isComplex: -> @operator isnt '!' or @first.isComplex()
@operator isnt '!' or @first.isComplex()
isChainable: -> # Am I capable of
@operator in @CHAINABLE # [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin)?
isChainable: -> @operator in ['<', '>', '>=', '<=', '===', '!==']
invert: -> invert: ->
if op = @INVERSIONS[@operator] if op = @INVERSIONS[@operator]
@@ -1074,16 +1063,12 @@ exports.Op = class Op extends Base
else else
super() super()
toString: (idt) ->
super idt, @constructor.name + ' ' + @operator
unfoldSoak: (o) -> unfoldSoak: (o) ->
@operator in @MUTATORS and If.unfoldSoak o, this, 'first' @operator in ['++', '--', 'delete'] and If.unfoldSoak o, this, 'first'
compileNode: (o) -> compileNode: (o) ->
if @isUnary() return @compileUnary o if @isUnary()
return @compileUnary o return @compileChain o if @isChainable() and @first.isChainable()
return @compileChain o if @isChainable() and @first.unwrap().isChainable()
return @compileExistence o if @operator is '?' return @compileExistence o if @operator is '?'
@first.tags.front = @tags.front @first.tags.front = @tags.front
"#{ @first.compile o, LEVEL.OP } #{@operator} #{ @second.compile o, LEVEL.OP }" "#{ @first.compile o, LEVEL.OP } #{@operator} #{ @second.compile o, LEVEL.OP }"
@@ -1094,8 +1079,11 @@ exports.Op = class Op extends Base
# bin/coffee -e 'console.log 50 < 65 > 10' # bin/coffee -e 'console.log 50 < 65 > 10'
# true # true
compileChain: (o) -> compileChain: (o) ->
[@first.second, shared] = @first.unwrap().second.cache o [@first.second, shared] = @first.second.cache o
"#{ @first.compile o, LEVEL.OP } && #{ shared.compile o } #{@operator} #{ @second.compile o, LEVEL.OP }" fst = @first .compile o, LEVEL.OP
fst = fst.slice 1, -1 if fst.charAt(0) is '('
code = "#{fst} && #{ shared.compile o } #{@operator} #{ @second.compile o, LEVEL.OP }"
if o.level < LEVEL.OP then code else "(#{code})"
compileExistence: (o) -> compileExistence: (o) ->
if @first.isComplex() if @first.isComplex()
@@ -1108,10 +1096,14 @@ exports.Op = class Op extends Base
# Compile a unary **Op**. # Compile a unary **Op**.
compileUnary: (o) -> compileUnary: (o) ->
space = if @operator in @PREFIX_OPERATORS or @first instanceof Op and parts = [op = @operator]
@first.operator is @operator and @operator in ['+', '-'] then ' ' else '' parts.push ' ' if op in ['new', 'typeof', 'delete'] or
parts = [@operator, space, @first.compile(o, LEVEL.OP)] op in ['+', '-'] and @first instanceof Op and @first.operator is op
(if @flip then parts.reverse() else parts).join '' parts.push @first.compile o, LEVEL.OP
parts.reverse() if @flip
parts.join ''
toString: (idt) -> super idt, @constructor.name + ' ' + @operator
#### In #### In
exports.In = class In extends Base exports.In = class In extends Base

View File

@@ -9,6 +9,10 @@ ok 10 < 20 > 10
ok 50 > 10 > 5 is parseInt('5', 10) ok 50 > 10 > 5 is parseInt('5', 10)
eq 1, 1 | 2 < 3 < 4
ok 1 == 1 <= 1, '`x == y <= z` should become `x === y && y <= z`'
i = 0 i = 0
ok 1 > i++ < 1, 'chained operations should evaluate each value only once' ok 1 > i++ < 1, 'chained operations should evaluate each value only once'