Skip to content

Commit

Permalink
pythongh-113317: Rework Argument Clinic cpp.py error handling
Browse files Browse the repository at this point in the history
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.

Yak-shaving in preparation for putting cpp.py into libclinic.
  • Loading branch information
erlend-aasland committed Dec 27, 2023
1 parent 0651936 commit 19c720e
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 36 deletions.
4 changes: 2 additions & 2 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@


def _make_clinic(*, filename='clinic_tests'):
clang = clinic.CLanguage(None)
clang = clinic.CLanguage(filename)
c = clinic.Clinic(clang, filename=filename, limited_capi=False)
c.block_parser = clinic.BlockParser('', clang)
return c
Expand Down Expand Up @@ -3920,7 +3920,7 @@ def test_Function_and_Parameter_reprs(self):
self.assertEqual(repr(parameter), "<clinic.Parameter 'bar'>")

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=''>")

monitor.line_number = 42
Expand Down
23 changes: 1 addition & 22 deletions Tools/clinic/clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

# Local imports.
import libclinic
from libclinic import ClinicError


# TODO:
Expand Down Expand Up @@ -94,27 +95,6 @@ def __repr__(self) -> 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
def warn_or_fail(
*args: object,
Expand Down Expand Up @@ -669,7 +649,6 @@ class CLanguage(Language):
def __init__(self, filename: str) -> None:
super().__init__(filename)
self.cpp = cpp.Monitor(filename)
self.cpp.fail = fail # type: ignore[method-assign]

def parse_line(self, line: str) -> None:
self.cpp.writeline(line)
Expand Down
21 changes: 9 additions & 12 deletions Tools/clinic/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import sys
from typing import NoReturn

from libclinic.error import ParseError


TokenAndCondition = tuple[str, str]
TokenStack = list[TokenAndCondition]
Expand Down Expand Up @@ -32,7 +34,7 @@ class Monitor:
Anyway this implementation seems to work well enough for the CPython sources.
"""
filename: str | None = None
filename: str
_: dc.KW_ONLY
verbose: bool = False

Expand All @@ -59,22 +61,16 @@ def condition(self) -> str:
"""
return " && ".join(condition for token, condition in self.stack)

def fail(self, *a: object) -> NoReturn:
if self.filename:
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 fail(self, msg: str) -> NoReturn:
raise ParseError(msg, filename=self.filename, lineno=self.line_number)

def writeline(self, line: str) -> None:
self.line_number += 1
line = line.strip()

def pop_stack() -> TokenAndCondition:
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()

if self.continuation:
Expand Down Expand Up @@ -145,7 +141,7 @@ def pop_stack() -> TokenAndCondition:

if token in {'if', 'ifdef', 'ifndef', 'elif'}:
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 not is_a_simple_defined(condition):
condition = "(" + condition + ")"
Expand All @@ -155,7 +151,8 @@ def pop_stack() -> TokenAndCondition:
else:
fields = condition.split()
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]
condition = 'defined(' + symbol + ')'
if token == 'ifndef':
Expand Down
6 changes: 6 additions & 0 deletions Tools/clinic/libclinic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from typing import Final

from .error import (
ClinicError,
)
from .formatting import (
SIG_END_MARKER,
c_repr,
Expand All @@ -15,6 +18,9 @@


__all__ = [
# Error handling
"ClinicError",

# Formatting helpers
"SIG_END_MARKER",
"c_repr",
Expand Down
26 changes: 26 additions & 0 deletions Tools/clinic/libclinic/error.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 19c720e

Please sign in to comment.