Skip to content

Commit d180159

Browse files
committed
Add file name and line number info to exceptions
1 parent ec0254b commit d180159

5 files changed

Lines changed: 121 additions & 51 deletions

File tree

rezparser/common.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,22 @@
1212

1313

1414
class RezParserError(Exception):
15-
__slots__ = ()
15+
__slots__ = ("message", "filename", "lineno")
16+
17+
def __init__(self, message, *, filename=None, lineno=None):
18+
full_message = str(message)
19+
20+
if filename is not None:
21+
full_message += f", in file {filename!r}"
22+
23+
if lineno is not None:
24+
full_message += f", on line {lineno}"
25+
26+
super().__init__(full_message)
27+
28+
self.message = message
29+
self.filename = filename
30+
self.lineno = lineno
1631

1732

1833
class Token(ply.lex.LexToken):

rezparser/lexer.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ def lexpos(self, lexpos):
4141
else:
4242
self.lexer.lexpos = lexpos
4343

44+
@property
45+
def filename(self):
46+
if self.lexer is None:
47+
return self._filename
48+
else:
49+
return self.lexer.filename
50+
51+
@filename.setter
52+
def filename(self, filename):
53+
if self.lexer is None:
54+
self._filename = filename
55+
else:
56+
self.lexer.filename = filename
57+
4458
def __init__(self, lexer=None):
4559
super().__init__()
4660

@@ -66,7 +80,12 @@ def token(self):
6680
return self.lexer.token()
6781

6882
def clone(self):
69-
cloned = NoOpLexer(self.lexer.clone())
83+
cloned = NoOpLexer(None if self.lexer is None else self.lexer.clone())
84+
for attr in ("_lineno", "_lexpos", "_filename"):
85+
try:
86+
setattr(cloned, attr, getattr(self, attr))
87+
except AttributeError:
88+
pass
7089
cloned.input(self.tokens)
7190
return cloned
7291

@@ -233,7 +252,12 @@ class RezLexer(object):
233252
tokens += tuple("FUN_" + fun[2:].upper() for fun in rez_functions)
234253

235254
def t_error(self, t):
236-
raise LexError(t)
255+
try:
256+
newline_pos = t.value.index("\n", 1)
257+
except ValueError:
258+
newline_pos = len(t.value)
259+
260+
raise LexError(repr(t.value[:newline_pos]), filename=self.filename, lineno=self.lineno)
237261

238262
_pp = r"(?m:^)[ \t]*\#[ \t]*"
239263
_id = r"[A-Za-z_][A-Za-z0-9_]*"
@@ -360,7 +384,7 @@ def t_IDENTIFIER(self, t):
360384
@ply.lex.TOKEN(r"\$\$"+_id)
361385
def t_REZ_FUNCTION(self, t):
362386
if t.value.lower() not in RezLexer.rez_functions:
363-
raise LexError("Unknown Rez function: " + t.value)
387+
raise LexError("Unknown Rez function: " + t.value, filename=self.filename, lineno=self.lineno)
364388

365389
t.type = "FUN_" + t.value[2:].upper()
366390

@@ -443,11 +467,23 @@ def lexpos(self):
443467
def lexpos(self, lexpos):
444468
self.lexer.lexpos = lexpos
445469

446-
def __init__(self, *, _lexer=None, **kwargs):
470+
@property
471+
def filename(self):
472+
try:
473+
return self.lexer.filename
474+
except AttributeError:
475+
return None
476+
477+
@filename.setter
478+
def filename(self, filename):
479+
self.lexer.filename = filename
480+
481+
def __init__(self, *, filename="<input>", _lexer=None, **kwargs):
447482
super().__init__()
448483

449484
if _lexer is None:
450485
self.lexer = ply.lex.lex(module=self, lextab="_table_lexer", **kwargs)
486+
self.filename = filename
451487
else:
452488
self.lexer = _lexer
453489

@@ -462,4 +498,6 @@ def token(self):
462498
return self.lexer.token()
463499

464500
def clone(self):
465-
return RezLexer(_lexer=self.lexer.clone())
501+
new_lexer = RezLexer(_lexer=self.lexer.clone())
502+
new_lexer.filename = self.filename
503+
return new_lexer

rezparser/parser.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ class RezParser(object):
9898

9999
start = "Start symbol must be set manually"
100100

101-
def p_error(self, p):
102-
raise ParseError(p)
101+
def p_error(self, t):
102+
raise ParseError(t, filename=t.lexer.filename, lineno=t.lineno)
103103

104104
def p_empty(self, p):
105105
"""empty : """
@@ -132,7 +132,11 @@ def p_intlit(self, p):
132132
"""
133133

134134
if p[1].startswith("'"):
135-
value = int.from_bytes(_unescape_string(p[1][1:-1]), "big")
135+
try:
136+
unescaped = _unescape_string(p[1][1:-1])
137+
except ValueError as e:
138+
raise ParseError(str(e), filename=p[-1].lexer.filename, lineno=p[-1].lineno)
139+
value = int.from_bytes(unescaped, "big")
136140
elif p[1].startswith("$"):
137141
value = int(p[1][1:], 16)
138142
elif p[1].startswith("0X") or p[1].startswith("0x"):
@@ -514,7 +518,11 @@ def p_single_string(self, p):
514518
if p[1].startswith("$"):
515519
p[0] = ast.StringLiteral(value=bytes.fromhex(p[1][2:-1]))
516520
else:
517-
p[0] = ast.StringLiteral(value=_unescape_string(p[1][1:-1]))
521+
try:
522+
unescaped = _unescape_string(p[1][1:-1])
523+
except ValueError as e:
524+
raise ParseError(str(e), filename=p[-1].lexer.filename, lineno=p[-1].lineno)
525+
p[0] = ast.StringLiteral(value=unescaped)
518526
else:
519527
p[0] = p[1]
520528

@@ -972,13 +980,13 @@ def p_array_field(self, p):
972980
for mod in p[1]:
973981
mod = mod.lower()
974982
if mod in seen:
975-
raise ParseError(f"Duplicate attribute {mod!r}")
983+
raise ParseError(f"Duplicate attribute {mod!r}", filename=p[-1].lexer.filename, lineno=p[-1].lineno)
976984
seen.add(mod)
977985

978986
if mod == "wide":
979987
wide = True
980988
else:
981-
raise ParseError(f"Unsupported modifier {mod!r} for type array")
989+
raise ParseError(f"Unsupported modifier {mod!r} for type array", filename=p[-1].lexer.filename, lineno=p[-1].lineno)
982990

983991
if len(p) > 9:
984992
p[0] = ast.ArrayField(wide=wide, label=None, count=p[4], fields=p[7])
@@ -1038,29 +1046,29 @@ def p_field(self, p):
10381046
for mod in modifiers:
10391047
mod = mod.lower()
10401048
if mod in seen:
1041-
raise ParseError(f"Duplicate attribute {mod!r}")
1049+
raise ParseError(f"Duplicate attribute {mod!r}", filename=p[-1].lexer.filename, lineno=p[-1].lineno)
10421050
seen.add(mod)
10431051

10441052
if mod == "key":
10451053
is_key = True
10461054
elif mod == "unsigned":
10471055
if typename not in ("bitstring", "byte", "integer", "longint"):
1048-
raise ParseError(f"Unsupported modifier {mod!r} for type {typename!r}")
1056+
raise ParseError(f"Unsupported modifier {mod!r} for type {typename!r}", filename=p[-1].lexer.filename, lineno=p[-1].lineno)
10491057

10501058
signed = False
10511059
elif mod in ("binary", "octal", "decimal", "hex", "literal"):
10521060
if mod == "hex" and typename == "string":
10531061
# Special case: hex is allowed on string
10541062
pass
10551063
elif typename not in ("bitstring", "byte", "integer", "longint"):
1056-
raise ParseError(f"Unsupported modifier {mod!r} for type {typename!r}")
1064+
raise ParseError(f"Unsupported modifier {mod!r} for type {typename!r}", filename=p[-1].lexer.filename, lineno=p[-1].lineno)
10571065

10581066
if base is None:
10591067
base = mod
10601068
else:
1061-
raise ParseError(f"Duplicate base modifier {mod!r} (base was previously set to {base!r}")
1069+
raise ParseError(f"Duplicate base modifier {mod!r} (base was previously set to {base!r}", filename=p[-1].lexer.filename, lineno=p[-1].lineno)
10621070
else:
1063-
raise ParseError(f"Invalid modifier: {mod!r}")
1071+
raise ParseError(f"Invalid modifier: {mod!r}", filename=p[-1].lexer.filename, lineno=p[-1].lineno)
10641072

10651073
if typename == "int":
10661074
typename = "integer"
@@ -1087,7 +1095,7 @@ def p_field(self, p):
10871095
elif typename == "rect":
10881096
fieldtype = ast.RectFieldType()
10891097
else:
1090-
raise ParseError(f"Unknown field type {typename!r}")
1098+
raise ParseError(f"Unknown field type {typename!r}", filename=p[-1].lexer.filename, lineno=p[-1].lineno)
10911099

10921100
if value_or_symconsts is None:
10931101
value = None

0 commit comments

Comments
 (0)