mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
Chained comparison AST (#5225)
* chained comparison AST * Explicit return Co-Authored-By: Geoffrey Booth <GeoffreyBooth@users.noreply.github.com>
This commit is contained in:
committed by
Geoffrey Booth
parent
0174ee082c
commit
c6fdde8834
@@ -6459,13 +6459,17 @@
|
||||
return (ref1 = this.operator) === '<' || ref1 === '>' || ref1 === '>=' || ref1 === '<=' || ref1 === '===' || ref1 === '!==';
|
||||
}
|
||||
|
||||
isChain() {
|
||||
return this.isChainable() && this.first.isChainable();
|
||||
}
|
||||
|
||||
invert() {
|
||||
var allInvertable, curr, fst, op, ref1;
|
||||
if (this.isInOperator()) {
|
||||
this.invertOperator = '!';
|
||||
return this;
|
||||
}
|
||||
if (this.isChainable() && this.first.isChainable()) {
|
||||
if (this.isChain()) {
|
||||
allInvertable = true;
|
||||
curr = this;
|
||||
while (curr && curr.operator) {
|
||||
@@ -6538,7 +6542,7 @@
|
||||
if (this.operator === 'do') {
|
||||
return Op.prototype.generateDo(this.first).compileNode(o);
|
||||
}
|
||||
isChain = this.isChainable() && this.first.isChainable();
|
||||
isChain = this.isChain();
|
||||
if (!isChain) {
|
||||
// In chains, there's no need to wrap bare obj literals in parens,
|
||||
// as the chained expression is wrapped.
|
||||
@@ -6697,6 +6701,9 @@
|
||||
if (this.isYield()) {
|
||||
return 'YieldExpression';
|
||||
}
|
||||
if (this.isChain()) {
|
||||
return 'ChainedComparison';
|
||||
}
|
||||
switch (this.operator) {
|
||||
case '||':
|
||||
case '&&':
|
||||
@@ -6714,10 +6721,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
operatorAst() {
|
||||
return `${this.invertOperator ? `${this.invertOperator} ` : ''}${this.originalOperator}`;
|
||||
}
|
||||
|
||||
chainAstProperties(o) {
|
||||
var currentOp, operand, operands, operators;
|
||||
operators = [this.operatorAst()];
|
||||
operands = [this.second];
|
||||
currentOp = this.first;
|
||||
while (true) {
|
||||
operators.unshift(currentOp.operatorAst());
|
||||
operands.unshift(currentOp.second);
|
||||
currentOp = currentOp.first;
|
||||
if (!currentOp.isChainable()) {
|
||||
operands.unshift(currentOp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
operators,
|
||||
operands: (function() {
|
||||
var j, len1, results;
|
||||
results = [];
|
||||
for (j = 0, len1 = operands.length; j < len1; j++) {
|
||||
operand = operands[j];
|
||||
results.push(operand.ast(o, LEVEL_OP));
|
||||
}
|
||||
return results;
|
||||
})()
|
||||
};
|
||||
}
|
||||
|
||||
astProperties(o) {
|
||||
var argument, firstAst, ref1, secondAst;
|
||||
var argument, firstAst, operatorAst, ref1, secondAst;
|
||||
if (this.isChain()) {
|
||||
return this.chainAstProperties(o);
|
||||
}
|
||||
firstAst = this.first.ast(o, LEVEL_OP);
|
||||
secondAst = (ref1 = this.second) != null ? ref1.ast(o, LEVEL_OP) : void 0;
|
||||
operatorAst = this.operatorAst();
|
||||
switch (false) {
|
||||
case !this.isUnary():
|
||||
argument = this.isYield() && this.first.unwrap().value === '' ? null : firstAst;
|
||||
@@ -6732,14 +6775,14 @@
|
||||
}
|
||||
return {
|
||||
argument,
|
||||
operator: this.originalOperator,
|
||||
operator: operatorAst,
|
||||
prefix: !this.flip
|
||||
};
|
||||
default:
|
||||
return {
|
||||
left: firstAst,
|
||||
right: secondAst,
|
||||
operator: `${this.invertOperator ? `${this.invertOperator} ` : ''}${this.originalOperator}`
|
||||
operator: operatorAst
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4342,11 +4342,14 @@ exports.Op = class Op extends Base
|
||||
isChainable: ->
|
||||
@operator in ['<', '>', '>=', '<=', '===', '!==']
|
||||
|
||||
isChain: ->
|
||||
@isChainable() and @first.isChainable()
|
||||
|
||||
invert: ->
|
||||
if @isInOperator()
|
||||
@invertOperator = '!'
|
||||
return @
|
||||
if @isChainable() and @first.isChainable()
|
||||
if @isChain()
|
||||
allInvertable = yes
|
||||
curr = this
|
||||
while curr and curr.operator
|
||||
@@ -4402,7 +4405,7 @@ exports.Op = class Op extends Base
|
||||
@invertOperator = null
|
||||
return @invert().compileNode(o)
|
||||
return Op::generateDo(@first).compileNode o if @operator is 'do'
|
||||
isChain = @isChainable() and @first.isChainable()
|
||||
isChain = @isChain()
|
||||
# In chains, there's no need to wrap bare obj literals in parens,
|
||||
# as the chained expression is wrapped.
|
||||
@first.front = @front unless isChain
|
||||
@@ -4502,6 +4505,7 @@ exports.Op = class Op extends Base
|
||||
astType: ->
|
||||
return 'AwaitExpression' if @isAwait()
|
||||
return 'YieldExpression' if @isYield()
|
||||
return 'ChainedComparison' if @isChain()
|
||||
switch @operator
|
||||
when '||', '&&', '?' then 'LogicalExpression'
|
||||
when '++', '--' then 'UpdateExpression'
|
||||
@@ -4509,9 +4513,31 @@ exports.Op = class Op extends Base
|
||||
if @isUnary() then 'UnaryExpression'
|
||||
else 'BinaryExpression'
|
||||
|
||||
operatorAst: ->
|
||||
"#{if @invertOperator then "#{@invertOperator} " else ''}#{@originalOperator}"
|
||||
|
||||
chainAstProperties: (o) ->
|
||||
operators = [@operatorAst()]
|
||||
operands = [@second]
|
||||
currentOp = @first
|
||||
loop
|
||||
operators.unshift currentOp.operatorAst()
|
||||
operands.unshift currentOp.second
|
||||
currentOp = currentOp.first
|
||||
unless currentOp.isChainable()
|
||||
operands.unshift currentOp
|
||||
break
|
||||
return {
|
||||
operators
|
||||
operands: (operand.ast(o, LEVEL_OP) for operand in operands)
|
||||
}
|
||||
|
||||
astProperties: (o) ->
|
||||
return @chainAstProperties(o) if @isChain()
|
||||
|
||||
firstAst = @first.ast o, LEVEL_OP
|
||||
secondAst = @second?.ast o, LEVEL_OP
|
||||
operatorAst = @operatorAst()
|
||||
switch
|
||||
when @isUnary()
|
||||
argument =
|
||||
@@ -4526,14 +4552,14 @@ exports.Op = class Op extends Base
|
||||
} if @isYield()
|
||||
return {
|
||||
argument
|
||||
operator: @originalOperator
|
||||
operator: operatorAst
|
||||
prefix: !@flip
|
||||
}
|
||||
else
|
||||
return
|
||||
left: firstAst
|
||||
right: secondAst
|
||||
operator: "#{if @invertOperator then "#{@invertOperator} " else ''}#{@originalOperator}"
|
||||
operator: operatorAst
|
||||
|
||||
#### In
|
||||
exports.In = class In extends Base
|
||||
|
||||
@@ -3829,3 +3829,48 @@ test "AST as expected for comments", ->
|
||||
type: 'CommentBlock'
|
||||
value: '\n # multiline\n # indented here comment\n '
|
||||
]
|
||||
|
||||
test "AST as expected for chained comparisons", ->
|
||||
testExpression '''
|
||||
a < b < c
|
||||
''',
|
||||
type: 'ChainedComparison'
|
||||
operands: [
|
||||
ID 'a'
|
||||
ID 'b'
|
||||
ID 'c'
|
||||
]
|
||||
operators: [
|
||||
'<'
|
||||
'<'
|
||||
]
|
||||
|
||||
testExpression '''
|
||||
a isnt b is c isnt d
|
||||
''',
|
||||
type: 'ChainedComparison'
|
||||
operands: [
|
||||
ID 'a'
|
||||
ID 'b'
|
||||
ID 'c'
|
||||
ID 'd'
|
||||
]
|
||||
operators: [
|
||||
'isnt'
|
||||
'is'
|
||||
'isnt'
|
||||
]
|
||||
|
||||
testExpression '''
|
||||
a >= b < c
|
||||
''',
|
||||
type: 'ChainedComparison'
|
||||
operands: [
|
||||
ID 'a'
|
||||
ID 'b'
|
||||
ID 'c'
|
||||
]
|
||||
operators: [
|
||||
'>='
|
||||
'<'
|
||||
]
|
||||
|
||||
@@ -7854,3 +7854,53 @@ test "AST as expected for comments", ->
|
||||
line: 5
|
||||
column: 5
|
||||
]
|
||||
|
||||
test "AST location data as expected for chained comparisons", ->
|
||||
testAstLocationData '''
|
||||
a >= b < c
|
||||
''',
|
||||
type: 'ChainedComparison'
|
||||
operands: [
|
||||
start: 0
|
||||
end: 1
|
||||
range: [0, 1]
|
||||
loc:
|
||||
start:
|
||||
line: 1
|
||||
column: 0
|
||||
end:
|
||||
line: 1
|
||||
column: 1
|
||||
,
|
||||
start: 5
|
||||
end: 6
|
||||
range: [5, 6]
|
||||
loc:
|
||||
start:
|
||||
line: 1
|
||||
column: 5
|
||||
end:
|
||||
line: 1
|
||||
column: 6
|
||||
,
|
||||
start: 9
|
||||
end: 10
|
||||
range: [9, 10]
|
||||
loc:
|
||||
start:
|
||||
line: 1
|
||||
column: 9
|
||||
end:
|
||||
line: 1
|
||||
column: 10
|
||||
]
|
||||
start: 0
|
||||
end: 10
|
||||
range: [0, 10]
|
||||
loc:
|
||||
start:
|
||||
line: 1
|
||||
column: 0
|
||||
end:
|
||||
line: 1
|
||||
column: 10
|
||||
|
||||
Reference in New Issue
Block a user