Skip to content

Commit 198bc76

Browse files
authored
gh-151464: exclude '<>' token from tokenize output (#154854)
* gh-151464: exclude '<>' token from tokenize output Was: ``` $ echo '1 <> 2' | python -m tokenize 1,0-1,1: NUMBER '1' 1,2-1,4: OP '<>' 1,5-1,6: NUMBER '2' 1,6-1,7: NEWLINE '\n' 2,0-2,0: ENDMARKER '' ``` Now (regardless on ``__future__.barry_as_FLUFL`` import): ``` $ echo '1 <> 2' | ./python -m tokenize 1,0-1,1: NUMBER '1' 1,2-1,3: OP '<' 1,3-1,4: OP '>' 1,5-1,6: NUMBER '2' 1,6-1,7: NEWLINE '\n' 2,0-2,0: ENDMARKER '' ``` in accordance with the Grammar: https://docs.python.org/3.14/reference/lexical_analysis.html#operators-and-delimiters Also adds a custom error message for ``<>`` ("not equal" in Pascal and Python 2). * +1 * address review: lowercase and move invalid rule * address review: news * address review: move test_guido_as_bdfl_ineq_tokens() * address review: revert _PyTokenizer_From* changes * + revert unrelated change * address review: remove whatsnew entry
1 parent f156510 commit 198bc76

12 files changed

Lines changed: 314 additions & 187 deletions

File tree

‎Grammar/python.gram‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ compare_op_bitwise_or_pair[CmpopExprPair*]:
789789
| (tok='!=' { _PyPegen_check_barry_as_flufl(p, tok) ? NULL : tok }) a=bitwise_or {
790790
_PyPegen_cmpop_expr_pair(p, NotEq, a) }
791791
| '<=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, LtE, a) }
792+
| invalid_noteq
792793
| '<' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Lt, a) }
793794
| '>=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, GtE, a) }
794795
| '>' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Gt, a) }
@@ -1615,3 +1616,10 @@ invalid_bitwise_or:
16151616
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(b, c, "invalid syntax. Maybe you meant 'or' or '|' instead of '||'?")
16161617
: NULL
16171618
}
1619+
1620+
invalid_noteq:
1621+
| a='<' b='>' {
1622+
_PyPegen_tokens_are_adjacent(a, b)
1623+
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '!=' instead of '<>'?")
1624+
: NULL
1625+
}

‎Include/internal/pycore_token.h‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Lib/test/test_syntax.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3644,6 +3644,31 @@ def test_ifexp_body_stmt_else_stmt(self):
36443644
]:
36453645
self._check_error(f"x = {lhs_stmt} if 1 else {rhs_stmt}", msg)
36463646

3647+
def test_diamond_operator(self):
3648+
self._check_error(
3649+
"1<>2",
3650+
r"Maybe you meant '!=' instead of '<>'\?",
3651+
lineno=1,
3652+
end_lineno=1,
3653+
offset=2,
3654+
end_offset=4,
3655+
)
3656+
3657+
def test_diamond_operator_barry_as_flufl(self):
3658+
# Under barry_as_FLUFL, '<>' is the valid "not equal" operator
3659+
compile(
3660+
"from __future__ import barry_as_FLUFL\n1<>2",
3661+
"<test>", "exec",
3662+
)
3663+
self._check_error(
3664+
"from __future__ import barry_as_FLUFL\na != b",
3665+
"with Barry as BDFL, use '<>' instead of '!='",
3666+
lineno=2,
3667+
end_lineno=2,
3668+
offset=3,
3669+
end_offset=5,
3670+
)
3671+
36473672
def test_double_ampersand(self):
36483673
self._check_error(
36493674
"a && b",

‎Lib/test/test_tokenize.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,6 +1234,20 @@ def test_multiline_non_ascii_fstring_with_expr(self):
12341234
FSTRING_END \'"\' (2, 2) (2, 3)
12351235
""")
12361236

1237+
def test_ineq_tokens(self):
1238+
self.check_tokenize("1 != 2", """\
1239+
NUMBER '1' (1, 0) (1, 1)
1240+
OP '!=' (1, 2) (1, 4)
1241+
NUMBER '2' (1, 5) (1, 6)
1242+
""")
1243+
self.check_tokenize("1 <> 2", """\
1244+
NUMBER '1' (1, 0) (1, 1)
1245+
OP '<' (1, 2) (1, 3)
1246+
OP '>' (1, 3) (1, 4)
1247+
NUMBER '2' (1, 5) (1, 6)
1248+
""")
1249+
1250+
12371251
class GenerateTokensTest(TokenizeTest):
12381252
def check_tokenize(self, s, expected):
12391253
# Format the tokens in s in a table format.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Exclude invalid token ``<>`` from :mod:`tokenize` output. :exc:`SyntaxError`
2+
for ``<>`` now suggests ``!=``.

‎Parser/action_helpers.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "pegen.h"
88
#include "string_parser.h" // _PyPegen_decode_string()
9+
#include "lexer/state.h" // tok_state
910

1011

1112
void *
@@ -2133,6 +2134,7 @@ _PyPegen_checked_from_import(Parser *p, asdl_seq *dots, expr_ty module_name,
21332134
alias_ty alias = asdl_seq_GET(names, i);
21342135
if (PyUnicode_CompareWithASCIIString(alias->name, "barry_as_FLUFL") == 0) {
21352136
p->flags |= PyPARSE_BARRY_AS_BDFL;
2137+
p->tok->barry_as_bdfl = 1;
21362138
}
21372139
}
21382140
}

‎Parser/lexer/lexer.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state *current, struct token
409409
/* Check for two-character token */
410410
{
411411
int c2 = tok_nextc(tok);
412-
int current_token = _PyToken_TwoChars(c, c2);
412+
int current_token = _PyToken_TwoChars(c, c2, tok->barry_as_bdfl);
413413
if (current_token != OP) {
414414
int c3 = tok_nextc(tok);
415415
int current_token3 = _PyToken_ThreeChars(c, c2, c3);

‎Parser/lexer/state.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ struct tok_state {
108108
#ifdef Py_DEBUG
109109
int debug;
110110
#endif
111+
int barry_as_bdfl;
111112
};
112113

113114
static inline ftstring_state *

0 commit comments

Comments
 (0)