3131import keyword
3232import re
3333import types
34+ from typing import Final
3435from typing import Literal
3536from typing import NoReturn
3637from typing import overload
3940
4041__all__ = [
4142 "Expression" ,
42- "ParseError " ,
43+ "ExpressionMatcher " ,
4344]
4445
4546
47+ FILE_NAME : Final = "<pytest match expression>"
48+
49+
4650class TokenType (enum .Enum ):
4751 LPAREN = "left parenthesis"
4852 RPAREN = "right parenthesis"
@@ -64,25 +68,11 @@ class Token:
6468 pos : int
6569
6670
67- class ParseError (Exception ):
68- """The :class:`Expression` contains invalid syntax.
69-
70- :param column: The column in the line where the error occurred (1-based).
71- :param message: A description of the error.
72- """
73-
74- def __init__ (self , column : int , message : str ) -> None :
75- self .column = column
76- self .message = message
77-
78- def __str__ (self ) -> str :
79- return f"at column { self .column } : { self .message } "
80-
81-
8271class Scanner :
83- __slots__ = ("current" , "tokens" )
72+ __slots__ = ("current" , "input" , " tokens" )
8473
8574 def __init__ (self , input : str ) -> None :
75+ self .input = input
8676 self .tokens = self .lex (input )
8777 self .current = next (self .tokens )
8878
@@ -106,15 +96,15 @@ def lex(self, input: str) -> Iterator[Token]:
10696 elif (quote_char := input [pos ]) in ("'" , '"' ):
10797 end_quote_pos = input .find (quote_char , pos + 1 )
10898 if end_quote_pos == - 1 :
109- raise ParseError (
110- pos + 1 ,
99+ raise SyntaxError (
111100 f'closing quote "{ quote_char } " is missing' ,
101+ (FILE_NAME , 1 , pos + 1 , input ),
112102 )
113103 value = input [pos : end_quote_pos + 1 ]
114104 if (backslash_pos := input .find ("\\ " )) != - 1 :
115- raise ParseError (
116- backslash_pos + 1 ,
105+ raise SyntaxError (
117106 r'escaping with "\" not supported in marker expression' ,
107+ (FILE_NAME , 1 , backslash_pos + 1 , input ),
118108 )
119109 yield Token (TokenType .STRING , value , pos )
120110 pos += len (value )
@@ -132,9 +122,9 @@ def lex(self, input: str) -> Iterator[Token]:
132122 yield Token (TokenType .IDENT , value , pos )
133123 pos += len (value )
134124 else :
135- raise ParseError (
136- pos + 1 ,
125+ raise SyntaxError (
137126 f'unexpected character "{ input [pos ]} "' ,
127+ (FILE_NAME , 1 , pos + 1 , input ),
138128 )
139129 yield Token (TokenType .EOF , "" , pos )
140130
@@ -157,12 +147,12 @@ def accept(self, type: TokenType, *, reject: bool = False) -> Token | None:
157147 return None
158148
159149 def reject (self , expected : Sequence [TokenType ]) -> NoReturn :
160- raise ParseError (
161- self .current .pos + 1 ,
150+ raise SyntaxError (
162151 "expected {}; got {}" .format (
163152 " OR " .join (type .value for type in expected ),
164153 self .current .type .value ,
165154 ),
155+ (FILE_NAME , 1 , self .current .pos + 1 , self .input ),
166156 )
167157
168158
@@ -223,14 +213,14 @@ def not_expr(s: Scanner) -> ast.expr:
223213def single_kwarg (s : Scanner ) -> ast .keyword :
224214 keyword_name = s .accept (TokenType .IDENT , reject = True )
225215 if not keyword_name .value .isidentifier ():
226- raise ParseError (
227- keyword_name .pos + 1 ,
216+ raise SyntaxError (
228217 f"not a valid python identifier { keyword_name .value } " ,
218+ (FILE_NAME , 1 , keyword_name .pos + 1 , s .input ),
229219 )
230220 if keyword .iskeyword (keyword_name .value ):
231- raise ParseError (
232- keyword_name .pos + 1 ,
221+ raise SyntaxError (
233222 f"unexpected reserved python keyword `{ keyword_name .value } `" ,
223+ (FILE_NAME , 1 , keyword_name .pos + 1 , s .input ),
234224 )
235225 s .accept (TokenType .EQUAL , reject = True )
236226
@@ -245,9 +235,9 @@ def single_kwarg(s: Scanner) -> ast.keyword:
245235 elif value_token .value in BUILTIN_MATCHERS :
246236 value = BUILTIN_MATCHERS [value_token .value ]
247237 else :
248- raise ParseError (
249- value_token .pos + 1 ,
238+ raise SyntaxError (
250239 f'unexpected character/s "{ value_token .value } "' ,
240+ (FILE_NAME , 1 , value_token .pos + 1 , s .input ),
251241 )
252242
253243 ret = ast .keyword (keyword_name .value , ast .Constant (value ))
@@ -333,7 +323,7 @@ def compile(cls, input: str) -> Expression:
333323
334324 :param input: The input expression - one line.
335325
336- :raises ParseError : If the expression is malformed.
326+ :raises SyntaxError : If the expression is malformed.
337327 """
338328 astexpr = expression (Scanner (input ))
339329 code = compile (
0 commit comments