mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-02-18 03:21:20 -05:00
783: corrected chained comparison precedence
This commit is contained in:
@@ -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];
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
lib/nodes.js
40
lib/nodes.js
@@ -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;
|
||||||
})();
|
})();
|
||||||
|
|||||||
138
lib/parser.js
138
lib/parser.js
File diff suppressed because one or more lines are too long
@@ -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']
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user