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:
Jeremy Ashkenas
2010-08-11 21:28:22 -04:00
parent ac752a46bc
commit ba02ebc3dc
6 changed files with 65 additions and 19 deletions

View File

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

View File

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

View File

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

View File

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

View File

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