summaryrefslogtreecommitdiffstats
path: root/Tools/cases_generator/generate_cases.py
diff options
context:
space:
mode:
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: