41
41
42
42
__all__ = [
43
43
"Expression" ,
44
- "ParseError " ,
44
+ "ExpressionMatcher " ,
45
45
]
46
46
47
47
48
+ FILE_NAME : Final = "<pytest match expression>"
49
+
50
+
48
51
class TokenType (enum .Enum ):
49
52
LPAREN = "left parenthesis"
50
53
RPAREN = "right parenthesis"
@@ -66,25 +69,11 @@ class Token:
66
69
pos : int
67
70
68
71
69
- class ParseError (Exception ):
70
- """The :class:`Expression` contains invalid syntax.
71
-
72
- :param column: The column in the line where the error occurred (1-based).
73
- :param message: A description of the error.
74
- """
75
-
76
- def __init__ (self , column : int , message : str ) -> None :
77
- self .column = column
78
- self .message = message
79
-
80
- def __str__ (self ) -> str :
81
- return f"at column { self .column } : { self .message } "
82
-
83
-
84
72
class Scanner :
85
- __slots__ = ("current" , "tokens" )
73
+ __slots__ = ("current" , "input" , " tokens" )
86
74
87
75
def __init__ (self , input : str ) -> None :
76
+ self .input = input
88
77
self .tokens = self .lex (input )
89
78
self .current = next (self .tokens )
90
79
@@ -108,15 +97,15 @@ def lex(self, input: str) -> Iterator[Token]:
108
97
elif (quote_char := input [pos ]) in ("'" , '"' ):
109
98
end_quote_pos = input .find (quote_char , pos + 1 )
110
99
if end_quote_pos == - 1 :
111
- raise ParseError (
112
- pos + 1 ,
100
+ raise SyntaxError (
113
101
f'closing quote "{ quote_char } " is missing' ,
102
+ (FILE_NAME , 1 , pos + 1 , input ),
114
103
)
115
104
value = input [pos : end_quote_pos + 1 ]
116
105
if (backslash_pos := input .find ("\\ " )) != - 1 :
117
- raise ParseError (
118
- backslash_pos + 1 ,
106
+ raise SyntaxError (
119
107
r'escaping with "\" not supported in marker expression' ,
108
+ (FILE_NAME , 1 , backslash_pos + 1 , input ),
120
109
)
121
110
yield Token (TokenType .STRING , value , pos )
122
111
pos += len (value )
@@ -134,9 +123,9 @@ def lex(self, input: str) -> Iterator[Token]:
134
123
yield Token (TokenType .IDENT , value , pos )
135
124
pos += len (value )
136
125
else :
137
- raise ParseError (
138
- pos + 1 ,
126
+ raise SyntaxError (
139
127
f'unexpected character "{ input [pos ]} "' ,
128
+ (FILE_NAME , 1 , pos + 1 , input ),
140
129
)
141
130
yield Token (TokenType .EOF , "" , pos )
142
131
@@ -159,12 +148,12 @@ def accept(self, type: TokenType, *, reject: bool = False) -> Token | None:
159
148
return None
160
149
161
150
def reject (self , expected : Sequence [TokenType ]) -> NoReturn :
162
- raise ParseError (
163
- self .current .pos + 1 ,
151
+ raise SyntaxError (
164
152
"expected {}; got {}" .format (
165
153
" OR " .join (type .value for type in expected ),
166
154
self .current .type .value ,
167
155
),
156
+ (FILE_NAME , 1 , self .current .pos + 1 , self .input ),
168
157
)
169
158
170
159
@@ -225,14 +214,14 @@ def not_expr(s: Scanner) -> ast.expr:
225
214
def single_kwarg (s : Scanner ) -> ast .keyword :
226
215
keyword_name = s .accept (TokenType .IDENT , reject = True )
227
216
if not keyword_name .value .isidentifier ():
228
- raise ParseError (
229
- keyword_name .pos + 1 ,
217
+ raise SyntaxError (
230
218
f"not a valid python identifier { keyword_name .value } " ,
219
+ (FILE_NAME , 1 , keyword_name .pos + 1 , s .input ),
231
220
)
232
221
if keyword .iskeyword (keyword_name .value ):
233
- raise ParseError (
234
- keyword_name .pos + 1 ,
222
+ raise SyntaxError (
235
223
f"unexpected reserved python keyword `{ keyword_name .value } `" ,
224
+ (FILE_NAME , 1 , keyword_name .pos + 1 , s .input ),
236
225
)
237
226
s .accept (TokenType .EQUAL , reject = True )
238
227
@@ -247,9 +236,9 @@ def single_kwarg(s: Scanner) -> ast.keyword:
247
236
elif value_token .value in BUILTIN_MATCHERS :
248
237
value = BUILTIN_MATCHERS [value_token .value ]
249
238
else :
250
- raise ParseError (
251
- value_token .pos + 1 ,
239
+ raise SyntaxError (
252
240
f'unexpected character/s "{ value_token .value } "' ,
241
+ (FILE_NAME , 1 , value_token .pos + 1 , s .input ),
253
242
)
254
243
255
244
ret = ast .keyword (keyword_name .value , ast .Constant (value ))
@@ -338,7 +327,7 @@ def compile(cls, input: str) -> Expression:
338
327
339
328
:param input: The input expression - one line.
340
329
341
- :raises ParseError : If the expression is malformed.
330
+ :raises SyntaxError : If the expression is malformed.
342
331
"""
343
332
astexpr = expression (Scanner (input ))
344
333
code = compile (
0 commit comments