mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-02-18 03:21:20 -05:00
Fixes Issue #603 -- a longstanding precedence issue involving prefix vs postfix if and unless, involving using the entire single-line if statment as an expression.
This commit is contained in:
@@ -516,20 +516,20 @@
|
|||||||
})
|
})
|
||||||
],
|
],
|
||||||
If: [
|
If: [
|
||||||
o("IfBlock"), o("Statement IF Expression", function() {
|
o("IfBlock"), o("Statement POST_IF Expression", function() {
|
||||||
return new IfNode($3, Expressions.wrap([$1]), {
|
return new IfNode($3, Expressions.wrap([$1]), {
|
||||||
statement: true
|
statement: true
|
||||||
});
|
});
|
||||||
}), o("Expression IF Expression", function() {
|
}), o("Expression POST_IF Expression", function() {
|
||||||
return new IfNode($3, Expressions.wrap([$1]), {
|
return new IfNode($3, Expressions.wrap([$1]), {
|
||||||
statement: true
|
statement: true
|
||||||
});
|
});
|
||||||
}), o("Statement UNLESS Expression", function() {
|
}), o("Statement POST_UNLESS Expression", function() {
|
||||||
return new IfNode($3, Expressions.wrap([$1]), {
|
return new IfNode($3, Expressions.wrap([$1]), {
|
||||||
statement: true,
|
statement: true,
|
||||||
invert: true
|
invert: true
|
||||||
});
|
});
|
||||||
}), o("Expression UNLESS Expression", function() {
|
}), o("Expression POST_UNLESS Expression", function() {
|
||||||
return new IfNode($3, Expressions.wrap([$1]), {
|
return new IfNode($3, Expressions.wrap([$1]), {
|
||||||
statement: true,
|
statement: true,
|
||||||
invert: true
|
invert: true
|
||||||
@@ -644,7 +644,7 @@
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["left", '==', '!='], ["left", '&', '|', '^'], ["left", '&&', '||', 'OP?'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'NEW', 'SUPER', 'CLASS'], ["left", 'EXTENDS'], ["right", '=', ':', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE']];
|
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["left", '==', '!='], ["left", '&', '|', '^'], ["left", '&&', '||', 'OP?'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'IF', 'UNLESS', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'NEW', 'SUPER', 'CLASS'], ["left", 'EXTENDS'], ["right", '=', ':', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'POST_IF', 'POST_UNLESS']];
|
||||||
tokens = [];
|
tokens = [];
|
||||||
_a = grammar;
|
_a = grammar;
|
||||||
for (name in _a) {
|
for (name in _a) {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -21,6 +21,7 @@
|
|||||||
this.closeOpenIndexes();
|
this.closeOpenIndexes();
|
||||||
this.addImplicitIndentation();
|
this.addImplicitIndentation();
|
||||||
this.addImplicitBraces();
|
this.addImplicitBraces();
|
||||||
|
this.tagPostfixConditionals();
|
||||||
this.addImplicitParentheses();
|
this.addImplicitParentheses();
|
||||||
this.ensureBalance(BALANCED_PAIRS);
|
this.ensureBalance(BALANCED_PAIRS);
|
||||||
this.rewriteClosingParens();
|
this.rewriteClosingParens();
|
||||||
@@ -231,6 +232,26 @@
|
|||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Rewriter.prototype.tagPostfixConditionals = function() {
|
||||||
|
return this.scanTokens(function(token, i) {
|
||||||
|
var _c, action, condition, original;
|
||||||
|
if (('IF' === (_c = token[0]) || 'UNLESS' === _c)) {
|
||||||
|
original = token;
|
||||||
|
condition = function(token, i) {
|
||||||
|
var _c;
|
||||||
|
return ('TERMINATOR' === (_c = token[0]) || 'INDENT' === _c);
|
||||||
|
};
|
||||||
|
action = function(token, i) {
|
||||||
|
if (token[0] !== 'INDENT') {
|
||||||
|
return (original[0] = 'POST_' + original[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.detectEnd(i + 1, condition, action);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
};
|
||||||
Rewriter.prototype.ensureBalance = function(pairs) {
|
Rewriter.prototype.ensureBalance = function(pairs) {
|
||||||
var _c, _d, key, levels, line, open, openLine, unclosed, value;
|
var _c, _d, key, levels, line, open, openLine, unclosed, value;
|
||||||
levels = {};
|
levels = {};
|
||||||
@@ -354,9 +375,9 @@
|
|||||||
})();
|
})();
|
||||||
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END);
|
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END);
|
||||||
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@'];
|
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@'];
|
||||||
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'THIS', 'NULL', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', '@', '->', '=>', '[', '(', '{'];
|
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'UNLESS', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'THIS', 'NULL', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', '@', '->', '=>', '[', '(', '{'];
|
||||||
IMPLICIT_BLOCK = ['->', '=>', '{', '[', ','];
|
IMPLICIT_BLOCK = ['->', '=>', '{', '[', ','];
|
||||||
IMPLICIT_END = ['IF', 'UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'TERMINATOR', 'INDENT'];
|
IMPLICIT_END = ['POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'TERMINATOR', 'INDENT'];
|
||||||
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
|
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
|
||||||
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
|
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -507,10 +507,10 @@ grammar =
|
|||||||
# *if* and *unless*.
|
# *if* and *unless*.
|
||||||
If: [
|
If: [
|
||||||
o "IfBlock"
|
o "IfBlock"
|
||||||
o "Statement IF Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true
|
o "Statement POST_IF Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true
|
||||||
o "Expression IF Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true
|
o "Expression POST_IF Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true
|
||||||
o "Statement UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true, invert: true
|
o "Statement POST_UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true, invert: true
|
||||||
o "Expression UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true, invert: true
|
o "Expression POST_UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true, invert: true
|
||||||
]
|
]
|
||||||
|
|
||||||
# Arithmetic and logical operators, working on one or more operands.
|
# Arithmetic and logical operators, working on one or more operands.
|
||||||
@@ -609,10 +609,10 @@ operators = [
|
|||||||
["right", 'INDENT']
|
["right", 'INDENT']
|
||||||
["left", 'OUTDENT']
|
["left", 'OUTDENT']
|
||||||
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW']
|
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW']
|
||||||
["right", 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'NEW', 'SUPER', 'CLASS']
|
["right", 'IF', 'UNLESS', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'NEW', 'SUPER', 'CLASS']
|
||||||
["left", 'EXTENDS']
|
["left", 'EXTENDS']
|
||||||
["right", '=', ':', 'RETURN']
|
["right", '=', ':', 'RETURN']
|
||||||
["right", '->', '=>', 'UNLESS', 'IF', 'ELSE']
|
["right", '->', '=>', 'UNLESS', 'POST_IF', 'POST_UNLESS']
|
||||||
]
|
]
|
||||||
|
|
||||||
# Wrapping Up
|
# Wrapping Up
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ exports.Rewriter = class Rewriter
|
|||||||
@closeOpenIndexes()
|
@closeOpenIndexes()
|
||||||
@addImplicitIndentation()
|
@addImplicitIndentation()
|
||||||
@addImplicitBraces()
|
@addImplicitBraces()
|
||||||
|
@tagPostfixConditionals()
|
||||||
@addImplicitParentheses()
|
@addImplicitParentheses()
|
||||||
@ensureBalance BALANCED_PAIRS
|
@ensureBalance BALANCED_PAIRS
|
||||||
@rewriteClosingParens()
|
@rewriteClosingParens()
|
||||||
@@ -196,6 +197,20 @@ exports.Rewriter = class Rewriter
|
|||||||
return 2
|
return 2
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
# Tag postfix conditionals as such, so that we can parse them with a
|
||||||
|
# different precedence.
|
||||||
|
tagPostfixConditionals: ->
|
||||||
|
@scanTokens (token, i) ->
|
||||||
|
if token[0] in ['IF', 'UNLESS']
|
||||||
|
original = token
|
||||||
|
condition = (token, i) ->
|
||||||
|
token[0] in ['TERMINATOR', 'INDENT']
|
||||||
|
action = (token, i) ->
|
||||||
|
original[0] = 'POST_' + original[0] if token[0] isnt 'INDENT'
|
||||||
|
@detectEnd i + 1, condition, action
|
||||||
|
return 1
|
||||||
|
return 1
|
||||||
|
|
||||||
# Ensure that all listed pairs of tokens are correctly balanced throughout
|
# Ensure that all listed pairs of tokens are correctly balanced throughout
|
||||||
# the course of the token stream.
|
# the course of the token stream.
|
||||||
ensureBalance: (pairs) ->
|
ensureBalance: (pairs) ->
|
||||||
@@ -301,7 +316,7 @@ IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@
|
|||||||
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
|
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
|
||||||
IMPLICIT_CALL = [
|
IMPLICIT_CALL = [
|
||||||
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS',
|
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS',
|
||||||
'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'THIS', 'NULL',
|
'IF', 'UNLESS', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'THIS', 'NULL',
|
||||||
'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF',
|
'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF',
|
||||||
'!', '!!', '@', '->', '=>', '[', '(', '{'
|
'!', '!!', '@', '->', '=>', '[', '(', '{'
|
||||||
]
|
]
|
||||||
@@ -310,7 +325,7 @@ IMPLICIT_CALL = [
|
|||||||
IMPLICIT_BLOCK = ['->', '=>', '{', '[', ',']
|
IMPLICIT_BLOCK = ['->', '=>', '{', '[', ',']
|
||||||
|
|
||||||
# Tokens that always mark the end of an implicit call for single-liners.
|
# Tokens that always mark the end of an implicit call for single-liners.
|
||||||
IMPLICIT_END = ['IF', 'UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'TERMINATOR', 'INDENT']
|
IMPLICIT_END = ['POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'TERMINATOR', 'INDENT']
|
||||||
|
|
||||||
# Single-line flavors of block expressions that have unclosed endings.
|
# Single-line flavors of block expressions that have unclosed endings.
|
||||||
# The grammar can't disambiguate them, so we insert the implicit indentation.
|
# The grammar can't disambiguate them, so we insert the implicit indentation.
|
||||||
|
|||||||
@@ -64,10 +64,20 @@ ok result is undefined
|
|||||||
|
|
||||||
# Return an if with no else.
|
# Return an if with no else.
|
||||||
func = ->
|
func = ->
|
||||||
return (if false then callback())
|
return if false then callback()
|
||||||
|
|
||||||
ok func() is null
|
ok func() is null
|
||||||
|
|
||||||
|
func = ->
|
||||||
|
return unless false then 100 else -100
|
||||||
|
|
||||||
|
ok func() is 100
|
||||||
|
|
||||||
|
ident = (x) -> x
|
||||||
|
result = ident if false then 300 else 100
|
||||||
|
|
||||||
|
ok result is 100
|
||||||
|
|
||||||
|
|
||||||
# If-to-ternary with instanceof requires parentheses (no comment).
|
# If-to-ternary with instanceof requires parentheses (no comment).
|
||||||
if {} instanceof Object
|
if {} instanceof Object
|
||||||
|
|||||||
Reference in New Issue
Block a user