15
15
16
16
from enum import Enum , auto
17
17
18
+ import lexer as lx
18
19
import parser
19
20
from parser import StackEffect
20
21
from parser import TypeSrcLiteral , TypeSrcConst , TypeSrcLocals , TypeSrcStackInput
87
88
arg_parser .add_argument (
88
89
"-m" , "--metadata" , type = str , help = "Generated metadata" , default = DEFAULT_METADATA_OUTPUT
89
90
)
91
+ arg_parser .add_argument (
92
+ "-l" , "--emit-line-directives" , help = "Emit #line directives" , action = "store_true"
93
+ )
90
94
arg_parser .add_argument (
91
95
"input" , nargs = argparse .REMAINDER , help = "Instruction definition file(s)"
92
96
)
@@ -158,21 +162,53 @@ class Formatter:
158
162
stream : typing .TextIO
159
163
prefix : str
160
164
postfix : str
161
-
162
- def __init__ (self , stream : typing .TextIO , indent : int ) -> None :
165
+ emit_line_directives : bool = False
166
+ lineno : int # Next line number, 1-based
167
+ filename : str # Slightly improved stream.filename
168
+ nominal_lineno : int
169
+ nominal_filename : str
170
+
171
+ def __init__ (
172
+ self , stream : typing .TextIO , indent : int , emit_line_directives : bool = False
173
+ ) -> None :
163
174
self .stream = stream
164
175
self .prefix = " " * indent
165
176
self .postfix = ""
177
+ self .emit_line_directives = emit_line_directives
178
+ self .lineno = 1
179
+ # Make filename more user-friendly and less platform-specific
180
+ filename = self .stream .name .replace ("\\ " , "/" )
181
+ if filename .startswith ("./" ):
182
+ filename = filename [2 :]
183
+ if filename .endswith (".new" ):
184
+ filename = filename [:- 4 ]
185
+ self .filename = filename
186
+ self .nominal_lineno = 1
187
+ self .nominal_filename = filename
166
188
167
189
def write_raw (self , s : str ) -> None :
168
190
self .stream .write (s )
191
+ newlines = s .count ("\n " )
192
+ self .lineno += newlines
193
+ self .nominal_lineno += newlines
169
194
170
195
def emit (self , arg : str ) -> None :
171
196
if arg :
172
197
self .write_raw (f"{ self .prefix } { arg } { self .postfix } \n " )
173
198
else :
174
199
self .write_raw ("\n " )
175
200
201
+ def set_lineno (self , lineno : int , filename : str ) -> None :
202
+ if self .emit_line_directives :
203
+ if lineno != self .nominal_lineno or filename != self .nominal_filename :
204
+ self .emit (f'#line { lineno } "{ filename } "' )
205
+ self .nominal_lineno = lineno
206
+ self .nominal_filename = filename
207
+
208
+ def reset_lineno (self ) -> None :
209
+ if self .lineno != self .nominal_lineno or self .filename != self .nominal_filename :
210
+ self .set_lineno (self .lineno + 1 , self .filename )
211
+
176
212
@contextlib .contextmanager
177
213
def indent (self ):
178
214
self .prefix += " "
@@ -253,6 +289,7 @@ class Instruction:
253
289
block : parser .Block
254
290
block_text : list [str ] # Block.text, less curlies, less PREDICT() calls
255
291
predictions : list [str ] # Prediction targets (instruction names)
292
+ block_line : int # First line of block in original code
256
293
257
294
# Computed by constructor
258
295
always_exits : bool
@@ -278,7 +315,7 @@ def __init__(self, inst: parser.InstDef):
278
315
self .kind = inst .kind
279
316
self .name = inst .name
280
317
self .block = inst .block
281
- self .block_text , self .check_eval_breaker , self .predictions = \
318
+ self .block_text , self .check_eval_breaker , self .predictions , self . block_line = \
282
319
extract_block_text (self .block )
283
320
self .always_exits = always_exits (self .block_text )
284
321
self .cache_effects = [
@@ -587,7 +624,13 @@ def write_body(self, out: Formatter, dedent: int, cache_adjust: int = 0) -> None
587
624
assert dedent <= 0
588
625
extra = " " * - dedent
589
626
names_to_skip = self .unmoved_names | frozenset ({UNUSED , "null" })
627
+ offset = 0
628
+ context = self .block .context
629
+ assert context != None
630
+ filename = context .owner .filename
590
631
for line in self .block_text :
632
+ out .set_lineno (self .block_line + offset , filename )
633
+ offset += 1
591
634
if m := re .match (r"(\s*)U_INST\((.+)\);\s*$" , line ):
592
635
space , label = m .groups ()
593
636
out .emit (f"UOP_{ label } ();" )
@@ -618,6 +661,7 @@ def write_body(self, out: Formatter, dedent: int, cache_adjust: int = 0) -> None
618
661
out .write_raw (f"{ space } if ({ cond } ) goto { label } ;{ out .postfix } \n " )
619
662
elif m := re .match (r"(\s*)DECREF_INPUTS\(\);\s*(?://.*)?$" , line ):
620
663
if not self .register :
664
+ out .reset_lineno ()
621
665
space = extra + m .group (1 )
622
666
for ieff in self .input_effects :
623
667
if ieff .name in names_to_skip :
@@ -633,6 +677,7 @@ def write_body(self, out: Formatter, dedent: int, cache_adjust: int = 0) -> None
633
677
out .write_raw (f"{ space } Py_{ decref } ({ ieff .name } );\n " )
634
678
else :
635
679
out .write_raw (extra + line .rstrip ("\n " ) + out .postfix + "\n " )
680
+ out .reset_lineno ()
636
681
637
682
638
683
InstructionOrCacheEffect = Instruction | parser .CacheEffect
@@ -707,6 +752,7 @@ class Analyzer:
707
752
output_filename : str
708
753
metadata_filename : str
709
754
errors : int = 0
755
+ emit_line_directives : bool = False
710
756
711
757
def __init__ (self , input_filenames : list [str ], output_filename : str , metadata_filename : str ):
712
758
"""Read the input file."""
@@ -772,6 +818,10 @@ def parse_file(self, filename: str, instrs_idx: dict[str, int]) -> None:
772
818
with open (filename ) as file :
773
819
src = file .read ()
774
820
821
+ # Make filename more user-friendly and less platform-specific
822
+ filename = filename .replace ("\\ " , "/" )
823
+ if filename .startswith ("./" ):
824
+ filename = filename [2 :]
775
825
psr = parser .Parser (src , filename = filename )
776
826
777
827
# Skip until begin marker
@@ -1220,13 +1270,14 @@ def write_metadata(self) -> None:
1220
1270
format_enums = [INSTR_FMT_PREFIX + format for format in sorted (all_formats )]
1221
1271
1222
1272
with open (self .metadata_filename , "w" ) as f :
1273
+ # Create formatter
1274
+ self .out = Formatter (f , 0 )
1275
+
1223
1276
# Write provenance header
1224
- f . write (f"// This file is generated by { THIS } \n " )
1225
- f . write (self .from_source_files ())
1226
- f . write (f"// Do not edit!\n " )
1277
+ self . out . write_raw (f"// This file is generated by { THIS } \n " )
1278
+ self . out . write_raw (self .from_source_files ())
1279
+ self . out . write_raw (f"// Do not edit!\n " )
1227
1280
1228
- # Create formatter; the rest of the code uses this
1229
- self .out = Formatter (f , 0 )
1230
1281
1231
1282
self .write_stack_effect_functions ()
1232
1283
@@ -1302,13 +1353,13 @@ def write_metadata_for_macro(self, mac: MacroInstruction) -> None:
1302
1353
def write_instructions (self ) -> None :
1303
1354
"""Write instructions to output file."""
1304
1355
with open (self .output_filename , "w" ) as f :
1305
- # Write provenance header
1306
- f .write (f"// This file is generated by { THIS } \n " )
1307
- f .write (self .from_source_files ())
1308
- f .write (f"// Do not edit!\n " )
1356
+ # Create formatter
1357
+ self .out = Formatter (f , 8 , self .emit_line_directives )
1309
1358
1310
- # Create formatter; the rest of the code uses this
1311
- self .out = Formatter (f , 8 )
1359
+ # Write provenance header
1360
+ self .out .write_raw (f"// This file is generated by { THIS } \n " )
1361
+ self .out .write_raw (self .from_source_files ())
1362
+ self .out .write_raw (f"// Do not edit!\n " )
1312
1363
1313
1364
# Write and count instructions of all kinds
1314
1365
n_instrs = 0
@@ -1478,13 +1529,16 @@ def wrap_super_or_macro(self, up: SuperOrMacroInstruction):
1478
1529
self .out .emit (f"DISPATCH();" )
1479
1530
1480
1531
1481
- def extract_block_text (block : parser .Block ) -> tuple [list [str ], bool , list [str ]]:
1532
+ def extract_block_text (block : parser .Block ) -> tuple [list [str ], bool , list [str ], int ]:
1482
1533
# Get lines of text with proper dedent
1483
1534
blocklines = block .text .splitlines (True )
1535
+ first_token : lx .Token = block .tokens [0 ] # IndexError means the context is broken
1536
+ block_line = first_token .begin [0 ]
1484
1537
1485
1538
# Remove blank lines from both ends
1486
1539
while blocklines and not blocklines [0 ].strip ():
1487
1540
blocklines .pop (0 )
1541
+ block_line += 1
1488
1542
while blocklines and not blocklines [- 1 ].strip ():
1489
1543
blocklines .pop ()
1490
1544
@@ -1493,6 +1547,7 @@ def extract_block_text(block: parser.Block) -> tuple[list[str], bool, list[str]]
1493
1547
assert blocklines and blocklines [- 1 ].strip () == "}"
1494
1548
blocklines .pop ()
1495
1549
blocklines .pop (0 )
1550
+ block_line += 1
1496
1551
1497
1552
# Remove trailing blank lines
1498
1553
while blocklines and not blocklines [- 1 ].strip ():
@@ -1512,7 +1567,7 @@ def extract_block_text(block: parser.Block) -> tuple[list[str], bool, list[str]]
1512
1567
predictions .insert (0 , m .group (1 ))
1513
1568
blocklines .pop ()
1514
1569
1515
- return blocklines , check_eval_breaker , predictions
1570
+ return blocklines , check_eval_breaker , predictions , block_line
1516
1571
1517
1572
1518
1573
def always_exits (lines : list [str ]) -> bool :
@@ -1548,6 +1603,8 @@ def main():
1548
1603
if len (args .input ) == 0 :
1549
1604
args .input .append (DEFAULT_INPUT )
1550
1605
a = Analyzer (args .input , args .output , args .metadata ) # Raises OSError if input unreadable
1606
+ if args .emit_line_directives :
1607
+ a .emit_line_directives = True
1551
1608
a .parse () # Raises SyntaxError on failure
1552
1609
a .analyze () # Prints messages and sets a.errors on failure
1553
1610
if a .errors :
0 commit comments