@@ -5,7 +5,7 @@ use rustpython_compiler_core::{
5
5
OneIndexed , SourceLocation ,
6
6
bytecode:: {
7
7
CodeFlags , CodeObject , CodeUnit , ConstantData , InstrDisplayContext , Instruction , Label ,
8
- OpArg ,
8
+ OpArg , PyCodeLocationInfoKind ,
9
9
} ,
10
10
} ;
11
11
@@ -72,6 +72,7 @@ pub struct InstructionInfo {
72
72
pub target : BlockIdx ,
73
73
// pub range: TextRange,
74
74
pub location : SourceLocation ,
75
+ // TODO: end_location for debug ranges
75
76
}
76
77
77
78
// spell-checker:ignore petgraph
@@ -199,6 +200,9 @@ impl CodeInfo {
199
200
locations. clear ( )
200
201
}
201
202
203
+ // Generate linetable from locations
204
+ let linetable = generate_linetable ( & locations, first_line_number. get ( ) as i32 ) ;
205
+
202
206
Ok ( CodeObject {
203
207
flags,
204
208
posonlyarg_count,
@@ -218,6 +222,8 @@ impl CodeInfo {
218
222
cellvars : cellvar_cache. into_iter ( ) . collect ( ) ,
219
223
freevars : freevar_cache. into_iter ( ) . collect ( ) ,
220
224
cell2arg,
225
+ linetable,
226
+ exceptiontable : Box :: new ( [ ] ) , // TODO: Generate actual exception table
221
227
} )
222
228
}
223
229
@@ -388,3 +394,134 @@ fn iter_blocks(blocks: &[Block]) -> impl Iterator<Item = (BlockIdx, &Block)> + '
388
394
Some ( ( idx, b) )
389
395
} )
390
396
}
397
+
398
+ /// Generate CPython 3.11+ format linetable from source locations
399
+ fn generate_linetable ( locations : & [ SourceLocation ] , first_line : i32 ) -> Box < [ u8 ] > {
400
+ if locations. is_empty ( ) {
401
+ return Box :: new ( [ ] ) ;
402
+ }
403
+
404
+ let mut linetable = Vec :: new ( ) ;
405
+ // Initialize prev_line to first_line
406
+ // The first entry's delta is relative to co_firstlineno
407
+ let mut prev_line = first_line;
408
+ let mut i = 0 ;
409
+
410
+ while i < locations. len ( ) {
411
+ let loc = & locations[ i] ;
412
+
413
+ // Count consecutive instructions with the same location
414
+ let mut length = 1 ;
415
+ while i + length < locations. len ( ) && locations[ i + length] == locations[ i] {
416
+ length += 1 ;
417
+ }
418
+
419
+ // Process in chunks of up to 8 instructions
420
+ while length > 0 {
421
+ let entry_length = length. min ( 8 ) ;
422
+
423
+ // Get line and column information
424
+ // SourceLocation always has row and column (both are OneIndexed)
425
+ let line = loc. row . get ( ) as i32 ;
426
+ let col = ( loc. column . get ( ) as i32 ) - 1 ; // Convert 1-based to 0-based
427
+
428
+ let line_delta = line - prev_line;
429
+
430
+ // Choose the appropriate encoding based on line delta and column info
431
+ // Note: SourceLocation always has valid column, so we never get NO_COLUMNS case
432
+ if line_delta == 0 {
433
+ let end_col = col; // Use same column for end (no range info available)
434
+
435
+ if col < 80 && end_col - col < 16 && end_col >= col {
436
+ // Short form (codes 0-9) for common cases
437
+ let code = ( col / 8 ) . min ( 9 ) as u8 ; // Short0 to Short9
438
+ linetable. push ( 0x80 | ( code << 3 ) | ( ( entry_length - 1 ) as u8 ) ) ;
439
+ let col_byte = ( ( ( col % 8 ) as u8 ) << 4 ) | ( ( end_col - col) as u8 & 0xf ) ;
440
+ linetable. push ( col_byte) ;
441
+ } else if col < 128 && end_col < 128 {
442
+ // One-line form (code 10) for same line
443
+ linetable. push (
444
+ 0x80 | ( ( PyCodeLocationInfoKind :: OneLine0 as u8 ) << 3 )
445
+ | ( ( entry_length - 1 ) as u8 ) ,
446
+ ) ;
447
+ linetable. push ( col as u8 ) ;
448
+ linetable. push ( end_col as u8 ) ;
449
+ } else {
450
+ // Long form for columns >= 128
451
+ linetable. push (
452
+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 )
453
+ | ( ( entry_length - 1 ) as u8 ) ,
454
+ ) ;
455
+ write_signed_varint ( & mut linetable, 0 ) ; // line_delta = 0
456
+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
457
+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
458
+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
459
+ }
460
+ } else if line_delta > 0 && line_delta < 3
461
+ /* && column.is_some() */
462
+ {
463
+ // One-line form (codes 11-12) for line deltas 1-2
464
+ let end_col = col; // Use same column for end
465
+
466
+ if col < 128 && end_col < 128 {
467
+ let code = ( PyCodeLocationInfoKind :: OneLine0 as u8 ) + ( line_delta as u8 ) ; // 11 for delta=1, 12 for delta=2
468
+ linetable. push ( 0x80 | ( code << 3 ) | ( ( entry_length - 1 ) as u8 ) ) ;
469
+ linetable. push ( col as u8 ) ;
470
+ linetable. push ( end_col as u8 ) ;
471
+ } else {
472
+ // Long form for columns >= 128 or negative line delta
473
+ linetable. push (
474
+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 )
475
+ | ( ( entry_length - 1 ) as u8 ) ,
476
+ ) ;
477
+ write_signed_varint ( & mut linetable, line_delta) ;
478
+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
479
+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
480
+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
481
+ }
482
+ } else {
483
+ // Long form (code 14) for all other cases
484
+ // This handles: line_delta < 0, line_delta >= 3, or columns >= 128
485
+ let end_col = col; // Use same column for end
486
+ linetable. push (
487
+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 ) | ( ( entry_length - 1 ) as u8 ) ,
488
+ ) ;
489
+ write_signed_varint ( & mut linetable, line_delta) ;
490
+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
491
+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
492
+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
493
+ }
494
+
495
+ prev_line = line;
496
+ length -= entry_length;
497
+ i += entry_length;
498
+ }
499
+ }
500
+
501
+ linetable. into_boxed_slice ( )
502
+ }
503
+
504
+ /// Write a variable-length unsigned integer (6-bit chunks)
505
+ /// Returns the number of bytes written
506
+ fn write_varint ( buf : & mut Vec < u8 > , mut val : u32 ) -> usize {
507
+ let start_len = buf. len ( ) ;
508
+ while val >= 64 {
509
+ buf. push ( 0x40 | ( val & 0x3f ) as u8 ) ;
510
+ val >>= 6 ;
511
+ }
512
+ buf. push ( val as u8 ) ;
513
+ buf. len ( ) - start_len
514
+ }
515
+
516
+ /// Write a variable-length signed integer
517
+ /// Returns the number of bytes written
518
+ fn write_signed_varint ( buf : & mut Vec < u8 > , val : i32 ) -> usize {
519
+ let uval = if val < 0 {
520
+ // (unsigned int)(-val) has an undefined behavior for INT_MIN
521
+ // So we use (0 - val as u32) to handle it correctly
522
+ ( ( 0u32 . wrapping_sub ( val as u32 ) ) << 1 ) | 1
523
+ } else {
524
+ ( val as u32 ) << 1
525
+ } ;
526
+ write_varint ( buf, uval)
527
+ }
0 commit comments