@@ -32,6 +32,64 @@ mod splice;
3232const USAGE : & str = help_usage ! ( "cat.md" ) ;
3333const ABOUT : & str = help_about ! ( "cat.md" ) ;
3434
35+ struct LineNumber {
36+ buf : Vec < u8 > ,
37+ }
38+
39+ // Logic to store a string for the line number. Manually incrementing the value
40+ // represented in a buffer like this is significantly faster than storing
41+ // a `usize` and using the standard Rust formatting macros to format a `usize`
42+ // to a string each time it's needed.
43+ // String is initialized to " 1\t" and incremented each time `increment` is
44+ // called. When the value overflows the range storable in the buffer, a b'1' is
45+ // prepended and the counting continues.
46+ impl LineNumber {
47+ fn new ( ) -> Self {
48+ LineNumber {
49+ // Initialize buf to b" 1\t"
50+ buf : Vec :: from ( b" 1\t " ) ,
51+ }
52+ }
53+
54+ fn increment ( & mut self ) {
55+ // skip(1) to avoid the \t in the last byte.
56+ for ascii_digit in self . buf . iter_mut ( ) . rev ( ) . skip ( 1 ) {
57+ // Working from the least-significant digit, increment the number in the buffer.
58+ // If we hit anything other than a b'9' we can break since the next digit is
59+ // unaffected.
60+ // Also note that if we hit a b' ', we can think of that as a 0 and increment to b'1'.
61+ // If/else here is faster than match (as measured with some benchmarking Apr-2025),
62+ // probably since we can prioritize most likely digits first.
63+ if ( b'0' ..=b'8' ) . contains ( ascii_digit) {
64+ * ascii_digit += 1 ;
65+ break ;
66+ } else if b'9' == * ascii_digit {
67+ * ascii_digit = b'0' ;
68+ } else {
69+ assert_eq ! ( * ascii_digit, b' ' ) ;
70+ * ascii_digit = b'1' ;
71+ break ;
72+ }
73+ }
74+ if self . buf [ 0 ] == b'0' {
75+ // This implies we've overflowed. In this case the buffer will be
76+ // [b'0', b'0', ..., b'0', b'\t'].
77+ // For debugging, the following logic would assert that to be the case.
78+ // assert_eq!(*self.buf.last().unwrap(), b'\t');
79+ // for ascii_digit in self.buf.iter_mut().rev().skip(1) {
80+ // assert_eq!(*ascii_digit, b'0');
81+ // }
82+
83+ // All we need to do is prepend a b'1' and we're good.
84+ self . buf . insert ( 0 , b'1' ) ;
85+ }
86+ }
87+
88+ fn write ( & self , writer : & mut impl Write ) -> std:: io:: Result < ( ) > {
89+ writer. write_all ( & self . buf )
90+ }
91+ }
92+
3593#[ derive( Error , Debug ) ]
3694enum CatError {
3795 /// Wrapper around `io::Error`
@@ -105,7 +163,7 @@ impl OutputOptions {
105163/// when we can't write fast.
106164struct OutputState {
107165 /// The current line number
108- line_number : usize ,
166+ line_number : LineNumber ,
109167
110168 /// Whether the output cursor is at the beginning of a new line
111169 at_line_start : bool ,
@@ -389,7 +447,7 @@ fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> {
389447 let out_info = FileInformation :: from_file ( & std:: io:: stdout ( ) ) . ok ( ) ;
390448
391449 let mut state = OutputState {
392- line_number : 1 ,
450+ line_number : LineNumber :: new ( ) ,
393451 at_line_start : true ,
394452 skipped_carriage_return : false ,
395453 one_blank_kept : false ,
@@ -528,8 +586,8 @@ fn write_lines<R: FdReadable>(
528586 }
529587 state. one_blank_kept = false ;
530588 if state. at_line_start && options. number != NumberingMode :: None {
531- write ! ( writer , "{0:6} \t " , state. line_number) ?;
532- state. line_number += 1 ;
589+ state. line_number . write ( & mut writer ) ?;
590+ state. line_number . increment ( ) ;
533591 }
534592
535593 // print to end of line or end of buffer
@@ -588,8 +646,8 @@ fn write_new_line<W: Write>(
588646 if !state. at_line_start || !options. squeeze_blank || !state. one_blank_kept {
589647 state. one_blank_kept = true ;
590648 if state. at_line_start && options. number == NumberingMode :: All {
591- write ! ( writer , "{0:6} \t " , state. line_number) ?;
592- state. line_number += 1 ;
649+ state. line_number . write ( writer ) ?;
650+ state. line_number . increment ( ) ;
593651 }
594652 write_end_of_line ( writer, options. end_of_line ( ) . as_bytes ( ) , is_interactive) ?;
595653 }
@@ -741,4 +799,25 @@ mod tests {
741799 assert_eq ! ( writer. buffer( ) , [ b'^' , byte + 64 ] ) ;
742800 }
743801 }
802+
803+ #[ test]
804+ fn test_incrementing_string ( ) {
805+ let mut incrementing_string = super :: LineNumber :: new ( ) ;
806+ assert_eq ! ( b" 1\t " , incrementing_string. buf. as_slice( ) ) ;
807+ incrementing_string. increment ( ) ;
808+ assert_eq ! ( b" 2\t " , incrementing_string. buf. as_slice( ) ) ;
809+ // Run through to 100
810+ for _ in 3 ..=100 {
811+ incrementing_string. increment ( ) ;
812+ }
813+ assert_eq ! ( b" 100\t " , incrementing_string. buf. as_slice( ) ) ;
814+ // Run through until we overflow the original size.
815+ for _ in 101 ..=1000000 {
816+ incrementing_string. increment ( ) ;
817+ }
818+ // Confirm that the buffer expands when we overflow the original size.
819+ assert_eq ! ( b"1000000\t " , incrementing_string. buf. as_slice( ) ) ;
820+ incrementing_string. increment ( ) ;
821+ assert_eq ! ( b"1000001\t " , incrementing_string. buf. as_slice( ) ) ;
822+ }
744823}
0 commit comments