summaryrefslogtreecommitdiffstats
path: root/Tools/cases_generator/generate_cases.py
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2023-03-15 15:37:36 (GMT)
committerGitHub <noreply@github.com>2023-03-15 15:37:36 (GMT)
commit70185de1abfe428049a5c43d58fcb656b46db96c (patch)
tree83aa4823b38c17f0df2610b73a2503edd82b3939 /Tools/cases_generator/generate_cases.py
parent61b9ff35cbda0cc59816951a17de073968fc25c6 (diff)
downloadcpython-70185de1abfe428049a5c43d58fcb656b46db96c.zip
cpython-70185de1abfe428049a5c43d58fcb656b46db96c.tar.gz
cpython-70185de1abfe428049a5c43d58fcb656b46db96c.tar.bz2
gh-102654: Insert #line directives in generated_cases.c.h (#102669)
This behavior is optional, because in some extreme cases it may just make debugging harder. The tool defaults it to off, but it is on in Makefile.pre.in. Also note that this makes diffs to generated_cases.c.h noisier, since whenever you insert or delete a line in bytecodes.c, all subsequent #line directives will change.
Diffstat (limited to 'Tools/cases_generator/generate_cases.py')
-rw-r--r--Tools/cases_generator/generate_cases.py89
1 files changed, 73 insertions, 16 deletions
diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py
index 25cf75e..b16c927 100644
--- a/Tools/cases_generator/generate_cases.py
+++ b/Tools/cases_generator/generate_cases.py
@@ -13,6 +13,7 @@ import re
import sys
import typing
+import lexer as lx
import parser
from parser import StackEffect
@@ -44,6 +45,9 @@ arg_parser.add_argument(
"-m", "--metadata", type=str, help="Generated metadata", default=DEFAULT_METADATA_OUTPUT
)
arg_parser.add_argument(
+ "-l", "--emit-line-directives", help="Emit #line directives", action="store_true"
+)
+arg_parser.add_argument(
"input", nargs=argparse.REMAINDER, help="Instruction definition file(s)"
)
@@ -105,13 +109,34 @@ class Formatter:
stream: typing.TextIO
prefix: str
-
- def __init__(self, stream: typing.TextIO, indent: int) -> None:
+ emit_line_directives: bool = False
+ lineno: int # Next line number, 1-based
+ filename: str # Slightly improved stream.filename
+ nominal_lineno: int
+ nominal_filename: str
+
+ def __init__(
+ self, stream: typing.TextIO, indent: int, emit_line_directives: bool = False
+ ) -> None:
self.stream = stream
self.prefix = " " * indent
+ self.emit_line_directives = emit_line_directives
+ self.lineno = 1
+ # Make filename more user-friendly and less platform-specific
+ filename = self.stream.name.replace("\\", "/")
+ if filename.startswith("./"):
+ filename = filename[2:]
+ if filename.endswith(".new"):
+ filename = filename[:-4]
+ self.filename = filename
+ self.nominal_lineno = 1
+ self.nominal_filename = filename
def write_raw(self, s: str) -> None:
self.stream.write(s)
+ newlines = s.count("\n")
+ self.lineno += newlines
+ self.nominal_lineno += newlines
def emit(self, arg: str) -> None:
if arg:
@@ -119,6 +144,17 @@ class Formatter:
else:
self.write_raw("\n")
+ def set_lineno(self, lineno: int, filename: str) -> None:
+ if self.emit_line_directives:
+ if lineno != self.nominal_lineno or filename != self.nominal_filename:
+ self.emit(f'#line {lineno} "{filename}"')
+ self.nominal_lineno = lineno
+ self.nominal_filename = filename
+
+ def reset_lineno(self) -> None:
+ if self.lineno != self.nominal_lineno or self.filename != self.nominal_filename:
+ self.set_lineno(self.lineno + 1, self.filename)
+
@contextlib.contextmanager
def indent(self):
self.prefix += " "
@@ -199,6 +235,7 @@ class Instruction:
block: parser.Block
block_text: list[str] # Block.text, less curlies, less PREDICT() calls
predictions: list[str] # Prediction targets (instruction names)
+ block_line: int # First line of block in original code
# Computed by constructor
always_exits: bool
@@ -223,7 +260,7 @@ class Instruction:
self.kind = inst.kind
self.name = inst.name
self.block = inst.block
- self.block_text, self.check_eval_breaker, self.predictions = \
+ self.block_text, self.check_eval_breaker, self.predictions, self.block_line = \
extract_block_text(self.block)
self.always_exits = always_exits(self.block_text)
self.cache_effects = [
@@ -388,7 +425,13 @@ class Instruction:
assert dedent <= 0
extra = " " * -dedent
names_to_skip = self.unmoved_names | frozenset({UNUSED, "null"})
+ offset = 0
+ context = self.block.context
+ assert context != None
+ filename = context.owner.filename
for line in self.block_text:
+ out.set_lineno(self.block_line + offset, filename)
+ offset += 1
if m := re.match(r"(\s*)ERROR_IF\((.+), (\w+)\);\s*(?://.*)?$", line):
space, cond, label = m.groups()
space = extra + space
@@ -416,6 +459,7 @@ class Instruction:
out.write_raw(f"{space}if ({cond}) goto {label};\n")
elif m := re.match(r"(\s*)DECREF_INPUTS\(\);\s*(?://.*)?$", line):
if not self.register:
+ out.reset_lineno()
space = extra + m.group(1)
for ieff in self.input_effects:
if ieff.name in names_to_skip:
@@ -431,6 +475,7 @@ class Instruction:
out.write_raw(f"{space}Py_{decref}({ieff.name});\n")
else:
out.write_raw(extra + line)
+ out.reset_lineno()
InstructionOrCacheEffect = Instruction | parser.CacheEffect
@@ -501,6 +546,7 @@ class Analyzer:
output_filename: str
metadata_filename: str
errors: int = 0
+ emit_line_directives: bool = False
def __init__(self, input_filenames: list[str], output_filename: str, metadata_filename: str):
"""Read the input file."""
@@ -561,6 +607,10 @@ class Analyzer:
with open(filename) as file:
src = file.read()
+ # Make filename more user-friendly and less platform-specific
+ filename = filename.replace("\\", "/")
+ if filename.startswith("./"):
+ filename = filename[2:]
psr = parser.Parser(src, filename=filename)
# Skip until begin marker
@@ -972,13 +1022,14 @@ class Analyzer:
format_enums = [INSTR_FMT_PREFIX + format for format in sorted(all_formats)]
with open(self.metadata_filename, "w") as f:
+ # Create formatter
+ self.out = Formatter(f, 0)
+
# Write provenance header
- f.write(f"// This file is generated by {THIS}\n")
- f.write(self.from_source_files())
- f.write(f"// Do not edit!\n")
+ self.out.write_raw(f"// This file is generated by {THIS}\n")
+ self.out.write_raw(self.from_source_files())
+ self.out.write_raw(f"// Do not edit!\n")
- # Create formatter; the rest of the code uses this
- self.out = Formatter(f, 0)
self.write_stack_effect_functions()
@@ -1053,13 +1104,13 @@ class Analyzer:
def write_instructions(self) -> None:
"""Write instructions to output file."""
with open(self.output_filename, "w") as f:
- # Write provenance header
- f.write(f"// This file is generated by {THIS}\n")
- f.write(self.from_source_files())
- f.write(f"// Do not edit!\n")
+ # Create formatter
+ self.out = Formatter(f, 8, self.emit_line_directives)
- # Create formatter; the rest of the code uses this
- self.out = Formatter(f, 8)
+ # Write provenance header
+ self.out.write_raw(f"// This file is generated by {THIS}\n")
+ self.out.write_raw(self.from_source_files())
+ self.out.write_raw(f"// Do not edit!\n")
# Write and count instructions of all kinds
n_instrs = 0
@@ -1179,13 +1230,16 @@ class Analyzer:
self.out.emit(f"DISPATCH();")
-def extract_block_text(block: parser.Block) -> tuple[list[str], bool, list[str]]:
+def extract_block_text(block: parser.Block) -> tuple[list[str], bool, list[str], int]:
# Get lines of text with proper dedent
blocklines = block.text.splitlines(True)
+ first_token: lx.Token = block.tokens[0] # IndexError means the context is broken
+ block_line = first_token.begin[0]
# Remove blank lines from both ends
while blocklines and not blocklines[0].strip():
blocklines.pop(0)
+ block_line += 1
while blocklines and not blocklines[-1].strip():
blocklines.pop()
@@ -1194,6 +1248,7 @@ def extract_block_text(block: parser.Block) -> tuple[list[str], bool, list[str]]
assert blocklines and blocklines[-1].strip() == "}"
blocklines.pop()
blocklines.pop(0)
+ block_line += 1
# Remove trailing blank lines
while blocklines and not blocklines[-1].strip():
@@ -1213,7 +1268,7 @@ def extract_block_text(block: parser.Block) -> tuple[list[str], bool, list[str]]
predictions.insert(0, m.group(1))
blocklines.pop()
- return blocklines, check_eval_breaker, predictions
+ return blocklines, check_eval_breaker, predictions, block_line
def always_exits(lines: list[str]) -> bool:
@@ -1250,6 +1305,8 @@ def main():
if len(args.input) == 0:
args.input.append(DEFAULT_INPUT)
a = Analyzer(args.input, args.output, args.metadata) # Raises OSError if input unreadable
+ if args.emit_line_directives:
+ a.emit_line_directives = True
a.parse() # Raises SyntaxError on failure
a.analyze() # Prints messages and sets a.errors on failure
if a.errors: