141 lines
4.5 KiB
Python
141 lines
4.5 KiB
Python
from lark.exceptions import UnexpectedToken
|
|
|
|
|
|
class _ValidationError(Exception):
|
|
def __init__(self, *args, **kwargs):
|
|
if self.__class__ is _ValidationError:
|
|
raise TypeError("Do not raise _ValidationError directly.")
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
class ErrorCollector:
|
|
def __init__(self):
|
|
self.errors = []
|
|
|
|
def add(self, error):
|
|
self.errors.append(error)
|
|
|
|
def raise_if_any(self):
|
|
if self.errors:
|
|
raise CollectedValidationErrors(self.errors)
|
|
|
|
|
|
class CollectedValidationErrors(_ValidationError):
|
|
def __init__(self, errors):
|
|
self.errors = errors
|
|
|
|
def asdict(self, with_message=True):
|
|
return [e.asdict(with_message=with_message) for e in self.errors]
|
|
|
|
def __str__(self):
|
|
return f"{len(self.errors)} validation error(s) collected:\n" + "\n\n".join(
|
|
str(e) for e in self.errors
|
|
)
|
|
|
|
|
|
class SyntaxError(_ValidationError):
|
|
def __init__(self, filecontent, exception):
|
|
self.filecontent = filecontent
|
|
self.exception = exception
|
|
|
|
def asdict(self, with_message=True):
|
|
return {
|
|
"type": (
|
|
"unexpected_token"
|
|
if isinstance(self.exception, UnexpectedToken)
|
|
else "unexpected_character"
|
|
),
|
|
"lineno": self.exception.line,
|
|
"column": self.exception.column,
|
|
"found_type": self.exception.token.type.lower(),
|
|
"found_value": self.exception.token.value,
|
|
"expected": sorted(x for x in self.exception.accepts if "__ANON" not in x),
|
|
"line": self.filecontent.split("\n")[self.exception.line - 1],
|
|
**({"message": str(self)} if with_message else {}),
|
|
}
|
|
|
|
def __str__(self):
|
|
d = self.asdict(with_message=False)
|
|
if len(d["expected"]) == 1:
|
|
exp = d["expected"][0]
|
|
else:
|
|
exp = f"one of {' '.join(d['expected'])}"
|
|
|
|
sth = "character" if d["type"] == "unexpected_character" else ""
|
|
|
|
return f"On line {d['lineno']} column {d['column']}:\nUnexpected {sth}{d['found_type']} ('{d['found_value']}')\nExpecting {exp}\n{d['lineno']:05d} | {d['line']}\n {' ' * (self.exception.column - 1)}^"
|
|
|
|
|
|
class DuplicateNameError(_ValidationError):
|
|
def __init__(self, filecontent, name, linenumbers):
|
|
self.name = name
|
|
self.filecontent = filecontent
|
|
self.linenumbers = linenumbers
|
|
|
|
def asdict(self, with_message=True):
|
|
return {
|
|
"type": "duplicate_name",
|
|
"name": self.name,
|
|
"lineno": self.linenumbers[0],
|
|
"line": self.filecontent.split("\n")[self.linenumbers[0] - 1],
|
|
**({"message": str(self)} if with_message else {}),
|
|
}
|
|
|
|
def __str__(self):
|
|
d = self.asdict(with_message=False)
|
|
|
|
def build():
|
|
yield f"On line {d['lineno']}:\nDuplicate instance name #{d['name']}"
|
|
yield f"{d['lineno']:05d} | {d['line']}"
|
|
yield " " * 8 + "^" * len(d["line"].rstrip())
|
|
|
|
return "\n".join(build())
|
|
|
|
|
|
class HeaderFieldError(_ValidationError):
|
|
def __init__(self, field, found_len, expected_len):
|
|
self.field = field
|
|
self.found_len = found_len
|
|
self.expected_len = expected_len
|
|
|
|
def asdict(self, with_message=True):
|
|
return {
|
|
"type": "invalid_header_field",
|
|
"field": self.field,
|
|
"expected_field_count": self.expected_len,
|
|
"actual_field_count": self.found_len,
|
|
**({"message": str(self)} if with_message else {}),
|
|
}
|
|
|
|
def __str__(self):
|
|
return (
|
|
f"Invalid number of parameters for HEADER field '{self.field}'. "
|
|
f"Expected {self.expected_len}, found {self.found_len}."
|
|
)
|
|
|
|
|
|
class InvalidNameError(_ValidationError):
|
|
def __init__(self, filecontent, name, linenumbers):
|
|
self.name = name
|
|
self.filecontent = filecontent
|
|
self.linenumbers = linenumbers
|
|
|
|
def asdict(self, with_message=True):
|
|
return {
|
|
"type": "invalid_name",
|
|
"name": self.name,
|
|
"lineno": self.linenumbers[0],
|
|
"line": self.filecontent.split("\n")[self.linenumbers[0] - 1],
|
|
**({"message": str(self)} if with_message else {}),
|
|
}
|
|
|
|
def __str__(self):
|
|
d = self.asdict(with_message=False)
|
|
|
|
def build():
|
|
yield f"On line {d['lineno']}:\nInvalid instance name #{d['name']}"
|
|
yield f"{d['lineno']:05d} | {d['line']}"
|
|
yield " " * 8 + "^" * len(d["line"].rstrip())
|
|
|
|
return "\n".join(build())
|