gh-113317: Rework Argument Clinic cpp.py error handling (#113525)

Rework error handling in the C preprocessor helper. Instead of monkey-
patching the cpp.Monitor.fail() method from within clinic.py, rewrite
cpp.py to use a subclass of the ClinicError exception. As a side-effect,
ClinicError is moved into Tools/clinic/libclinic/errors.py.

Yak-shaving in preparation for putting cpp.py into libclinic.
This commit is contained in:
Erlend E. Aasland 2023-12-27 22:43:19 +01:00 committed by GitHub
parent 6c98fce33a
commit 87295b4068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 44 additions and 36 deletions

View File

@ -22,7 +22,7 @@ with test_tools.imports_under_tool('clinic'):
def _make_clinic(*, filename='clinic_tests'): def _make_clinic(*, filename='clinic_tests'):
clang = clinic.CLanguage(None) clang = clinic.CLanguage(filename)
c = clinic.Clinic(clang, filename=filename, limited_capi=False) c = clinic.Clinic(clang, filename=filename, limited_capi=False)
c.block_parser = clinic.BlockParser('', clang) c.block_parser = clinic.BlockParser('', clang)
return c return c
@ -3920,7 +3920,7 @@ class ClinicReprTests(unittest.TestCase):
self.assertEqual(repr(parameter), "<clinic.Parameter 'bar'>") self.assertEqual(repr(parameter), "<clinic.Parameter 'bar'>")
def test_Monitor_repr(self): def test_Monitor_repr(self):
monitor = clinic.cpp.Monitor() monitor = clinic.cpp.Monitor("test.c")
self.assertRegex(repr(monitor), r"<clinic.Monitor \d+ line=0 condition=''>") self.assertRegex(repr(monitor), r"<clinic.Monitor \d+ line=0 condition=''>")
monitor.line_number = 42 monitor.line_number = 42

View File

@ -53,6 +53,7 @@ from typing import (
# Local imports. # Local imports.
import libclinic import libclinic
from libclinic import ClinicError
# TODO: # TODO:
@ -94,27 +95,6 @@ NULL = Null()
TemplateDict = dict[str, str] TemplateDict = dict[str, str]
@dc.dataclass
class ClinicError(Exception):
message: str
_: dc.KW_ONLY
lineno: int | None = None
filename: str | None = None
def __post_init__(self) -> None:
super().__init__(self.message)
def report(self, *, warn_only: bool = False) -> str:
msg = "Warning" if warn_only else "Error"
if self.filename is not None:
msg += f" in file {self.filename!r}"
if self.lineno is not None:
msg += f" on line {self.lineno}"
msg += ":\n"
msg += f"{self.message}\n"
return msg
@overload @overload
def warn_or_fail( def warn_or_fail(
*args: object, *args: object,
@ -669,7 +649,6 @@ class CLanguage(Language):
def __init__(self, filename: str) -> None: def __init__(self, filename: str) -> None:
super().__init__(filename) super().__init__(filename)
self.cpp = cpp.Monitor(filename) self.cpp = cpp.Monitor(filename)
self.cpp.fail = fail # type: ignore[method-assign]
def parse_line(self, line: str) -> None: def parse_line(self, line: str) -> None:
self.cpp.writeline(line) self.cpp.writeline(line)

View File

@ -3,6 +3,8 @@ import re
import sys import sys
from typing import NoReturn from typing import NoReturn
from libclinic.errors import ParseError
TokenAndCondition = tuple[str, str] TokenAndCondition = tuple[str, str]
TokenStack = list[TokenAndCondition] TokenStack = list[TokenAndCondition]
@ -32,7 +34,7 @@ class Monitor:
Anyway this implementation seems to work well enough for the CPython sources. Anyway this implementation seems to work well enough for the CPython sources.
""" """
filename: str | None = None filename: str
_: dc.KW_ONLY _: dc.KW_ONLY
verbose: bool = False verbose: bool = False
@ -59,14 +61,8 @@ class Monitor:
""" """
return " && ".join(condition for token, condition in self.stack) return " && ".join(condition for token, condition in self.stack)
def fail(self, *a: object) -> NoReturn: def fail(self, msg: str) -> NoReturn:
if self.filename: raise ParseError(msg, filename=self.filename, lineno=self.line_number)
filename = " " + self.filename
else:
filename = ''
print("Error at" + filename, "line", self.line_number, ":")
print(" ", ' '.join(str(x) for x in a))
sys.exit(-1)
def writeline(self, line: str) -> None: def writeline(self, line: str) -> None:
self.line_number += 1 self.line_number += 1
@ -74,7 +70,7 @@ class Monitor:
def pop_stack() -> TokenAndCondition: def pop_stack() -> TokenAndCondition:
if not self.stack: if not self.stack:
self.fail("#" + token + " without matching #if / #ifdef / #ifndef!") self.fail(f"#{token} without matching #if / #ifdef / #ifndef!")
return self.stack.pop() return self.stack.pop()
if self.continuation: if self.continuation:
@ -145,7 +141,7 @@ class Monitor:
if token in {'if', 'ifdef', 'ifndef', 'elif'}: if token in {'if', 'ifdef', 'ifndef', 'elif'}:
if not condition: if not condition:
self.fail("Invalid format for #" + token + " line: no argument!") self.fail(f"Invalid format for #{token} line: no argument!")
if token in {'if', 'elif'}: if token in {'if', 'elif'}:
if not is_a_simple_defined(condition): if not is_a_simple_defined(condition):
condition = "(" + condition + ")" condition = "(" + condition + ")"
@ -155,7 +151,8 @@ class Monitor:
else: else:
fields = condition.split() fields = condition.split()
if len(fields) != 1: if len(fields) != 1:
self.fail("Invalid format for #" + token + " line: should be exactly one argument!") self.fail(f"Invalid format for #{token} line: "
"should be exactly one argument!")
symbol = fields[0] symbol = fields[0]
condition = 'defined(' + symbol + ')' condition = 'defined(' + symbol + ')'
if token == 'ifndef': if token == 'ifndef':

View File

@ -1,5 +1,8 @@
from typing import Final from typing import Final
from .errors import (
ClinicError,
)
from .formatting import ( from .formatting import (
SIG_END_MARKER, SIG_END_MARKER,
c_repr, c_repr,
@ -15,6 +18,9 @@ from .formatting import (
__all__ = [ __all__ = [
# Error handling
"ClinicError",
# Formatting helpers # Formatting helpers
"SIG_END_MARKER", "SIG_END_MARKER",
"c_repr", "c_repr",

View File

@ -0,0 +1,26 @@
import dataclasses as dc
@dc.dataclass
class ClinicError(Exception):
message: str
_: dc.KW_ONLY
lineno: int | None = None
filename: str | None = None
def __post_init__(self) -> None:
super().__init__(self.message)
def report(self, *, warn_only: bool = False) -> str:
msg = "Warning" if warn_only else "Error"
if self.filename is not None:
msg += f" in file {self.filename!r}"
if self.lineno is not None:
msg += f" on line {self.lineno}"
msg += ":\n"
msg += f"{self.message}\n"
return msg
class ParseError(ClinicError):
pass