Skip to content

Commit 73f2d90

Browse files
committed
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 parent 8b048eb commit 73f2d90

16 files changed

Lines changed: 472 additions & 339 deletions

File tree

Grammar/python.gram

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,13 +807,21 @@ noteq_bitwise_or[CmpopExprPair*]:
807807
| (tok='!=' { _PyPegen_check_barry_as_flufl(p, tok) ? NULL : tok}) a=bitwise_or {_PyPegen_cmpop_expr_pair(p, NotEq, a) }
808808
lte_bitwise_or[CmpopExprPair*]: '<=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, LtE, a) }
809809
lt_bitwise_or[CmpopExprPair*]: '<' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Lt, a) }
810+
| invalid_noteq
810811
gte_bitwise_or[CmpopExprPair*]: '>=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, GtE, a) }
811812
gt_bitwise_or[CmpopExprPair*]: '>' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Gt, a) }
812813
notin_bitwise_or[CmpopExprPair*]: 'not' 'in' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, NotIn, a) }
813814
in_bitwise_or[CmpopExprPair*]: 'in' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, In, a) }
814815
isnot_bitwise_or[CmpopExprPair*]: 'is' 'not' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, IsNot, a) }
815816
is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Is, a) }
816817

818+
invalid_noteq:
819+
| a='<' b='>' {
820+
_PyPegen_tokens_are_adjacent(a, b)
821+
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '!=' instead of '<>'?")
822+
: NULL
823+
}
824+
817825
# Bitwise operators
818826
# -----------------
819827

Include/internal/pycore_token.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ extern "C" {
101101
// Export these 4 symbols for 'test_peg_generator'
102102
PyAPI_DATA(const char * const) _PyParser_TokenNames[]; /* Token names */
103103
PyAPI_FUNC(int) _PyToken_OneChar(int);
104-
PyAPI_FUNC(int) _PyToken_TwoChars(int, int);
104+
PyAPI_FUNC(int) _PyToken_TwoChars(int, int, int);
105105
PyAPI_FUNC(int) _PyToken_ThreeChars(int, int, int);
106106

107107
#ifdef __cplusplus

Lib/test/test_flufl.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,30 @@ def test_barry_as_bdfl_relative_import(self):
5858
self.assertEqual(cm.exception.lineno, 1)
5959
self.assertEqual(cm.exception.offset, len(code) - 4)
6060

61+
def test_guido_as_bdfl_ineq_tokens(self):
62+
code = """
63+
from io import BytesIO
64+
import tokenize
65+
from test.test_tokenize import stringify_tokens_from_source
6166
67+
s = "{0}"
68+
f = BytesIO(s.encode('utf-8'))
69+
globals()['result'] = stringify_tokens_from_source(tokenize.tokenize(f.readline), s)
70+
"""
71+
ns = {}
72+
exec(code.format('1 != 2'), ns)
73+
self.assertEqual(ns['result'],
74+
[" ENCODING 'utf-8' (0, 0) (0, 0)",
75+
" NUMBER '1' (1, 0) (1, 1)",
76+
" OP '!=' (1, 2) (1, 4)",
77+
" NUMBER '2' (1, 5) (1, 6)"])
78+
exec(code.format('1 <> 2'), ns)
79+
self.assertEqual(ns['result'],
80+
[" ENCODING 'utf-8' (0, 0) (0, 0)",
81+
" NUMBER '1' (1, 0) (1, 1)",
82+
" OP '<' (1, 2) (1, 3)",
83+
" OP '>' (1, 3) (1, 4)",
84+
" NUMBER '2' (1, 5) (1, 6)"])
6285

6386

6487
if __name__ == '__main__':

Lib/test/test_syntax.py

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

3611+
def test_diamond_operator(self):
3612+
self._check_error(
3613+
"1<>2",
3614+
r"Maybe you meant '!=' instead of '<>'\?",
3615+
lineno=1,
3616+
end_lineno=1,
3617+
offset=2,
3618+
end_offset=4,
3619+
)
3620+
3621+
def test_diamond_operator_barry_as_flufl(self):
3622+
# Under barry_as_FLUFL, '<>' is the valid "not equal" operator
3623+
compile(
3624+
"from __future__ import barry_as_FLUFL\n1<>2",
3625+
"<test>", "exec",
3626+
)
3627+
self._check_error(
3628+
"from __future__ import barry_as_FLUFL\na != b",
3629+
"with Barry as BDFL, use '<>' instead of '!='",
3630+
lineno=2,
3631+
end_lineno=2,
3632+
offset=3,
3633+
end_offset=5,
3634+
)
3635+
36113636
def test_double_ampersand(self):
36123637
self._check_error(
36133638
"a && b",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Exclude invalid token ``<>`` from :mod:`tokenize` output.

Parser/action_helpers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,6 +2076,7 @@ _PyPegen_checked_from_import(Parser *p, asdl_seq *dots, expr_ty module_name,
20762076
alias_ty alias = asdl_seq_GET(names, i);
20772077
if (PyUnicode_CompareWithASCIIString(alias->name, "barry_as_FLUFL") == 0) {
20782078
p->flags |= PyPARSE_BARRY_AS_BDFL;
2079+
p->tok->BARRY_AS_BDFL = 1;
20792080
}
20802081
}
20812082
}

Parser/lexer/lexer.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
562562
/* Check for two-character token */
563563
{
564564
int c2 = tok_nextc(tok);
565-
int current_token = _PyToken_TwoChars(c, c2);
565+
int current_token = _PyToken_TwoChars(c, c2, tok->BARRY_AS_BDFL);
566566
if (current_token != OP) {
567567
int c3 = tok_nextc(tok);
568568
int current_token3 = _PyToken_ThreeChars(c, c2, c3);

Parser/lexer/state.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ _PyTokenizer_tok_new(void)
6363
#ifdef Py_DEBUG
6464
tok->debug = _Py_GetConfig()->parser_debug;
6565
#endif
66+
tok->BARRY_AS_BDFL = 0;
6667
return tok;
6768
}
6869

Parser/lexer/state.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ struct tok_state {
137137
#ifdef Py_DEBUG
138138
int debug;
139139
#endif
140+
int BARRY_AS_BDFL;
140141
};
141142

142143
int _PyLexer_type_comment_token_setup(struct tok_state *tok, struct token *token, int type, int col_offset,

0 commit comments

Comments
 (0)