@@ -32,6 +32,63 @@ 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 ! [ b' ' , b' ' , b' ' , b' ' , b' ' , b'1' , b'\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 is faster than match since we can prioritize most likely digits first.
62+ if ( b'0' ..=b'8' ) . contains ( ascii_digit) {
63+ * ascii_digit += 1 ;
64+ break ;
65+ } else if b'9' == * ascii_digit {
66+ * ascii_digit = b'0' ;
67+ } else {
68+ assert_eq ! ( * ascii_digit, b' ' ) ;
69+ * ascii_digit = b'1' ;
70+ break ;
71+ }
72+ }
73+ if self . buf [ 0 ] == b'0' {
74+ // This implies we've overflowed. In this case the buffer will be
75+ // [b'0', b'0', ..., b'0', b'\t'].
76+ // For debugging, the following logic would assert that to be the case.
77+ // assert_eq!(*self.buf.last().unwrap(), b'\t');
78+ // for ascii_digit in self.buf.iter_mut().rev().skip(1) {
79+ // assert_eq!(*ascii_digit, b'0');
80+ // }
81+
82+ // All we need to do is prepend a b'1' and we're good.
83+ self . buf . insert ( 0 , b'1' ) ;
84+ }
85+ }
86+
87+ fn write ( & self , writer : & mut impl Write ) -> std:: io:: Result < ( ) > {
88+ writer. write_all ( & self . buf )
89+ }
90+ }
91+
3592#[ derive( Error , Debug ) ]
3693enum CatError {
3794 /// Wrapper around `io::Error`
@@ -105,7 +162,7 @@ impl OutputOptions {
105162/// when we can't write fast.
106163struct OutputState {
107164 /// The current line number
108- line_number : usize ,
165+ line_number : LineNumber ,
109166
110167 /// Whether the output cursor is at the beginning of a new line
111168 at_line_start : bool ,
@@ -389,7 +446,7 @@ fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> {
389446 let out_info = FileInformation :: from_file ( & std:: io:: stdout ( ) ) . ok ( ) ;
390447
391448 let mut state = OutputState {
392- line_number : 1 ,
449+ line_number : LineNumber :: new ( ) ,
393450 at_line_start : true ,
394451 skipped_carriage_return : false ,
395452 one_blank_kept : false ,
@@ -528,8 +585,8 @@ fn write_lines<R: FdReadable>(
528585 }
529586 state. one_blank_kept = false ;
530587 if state. at_line_start && options. number != NumberingMode :: None {
531- write ! ( writer , "{0:6} \t " , state. line_number) ?;
532- state. line_number += 1 ;
588+ state. line_number . write ( & mut writer ) ?;
589+ state. line_number . increment ( ) ;
533590 }
534591
535592 // print to end of line or end of buffer
@@ -588,8 +645,8 @@ fn write_new_line<W: Write>(
588645 if !state. at_line_start || !options. squeeze_blank || !state. one_blank_kept {
589646 state. one_blank_kept = true ;
590647 if state. at_line_start && options. number == NumberingMode :: All {
591- write ! ( writer , "{0:6} \t " , state. line_number) ?;
592- state. line_number += 1 ;
648+ state. line_number . write ( writer ) ?;
649+ state. line_number . increment ( ) ;
593650 }
594651 write_end_of_line ( writer, options. end_of_line ( ) . as_bytes ( ) , is_interactive) ?;
595652 }
@@ -741,4 +798,25 @@ mod tests {
741798 assert_eq ! ( writer. buffer( ) , [ b'^' , byte + 64 ] ) ;
742799 }
743800 }
801+
802+ #[ test]
803+ fn test_incrementing_string ( ) {
804+ let mut incrementing_string = super :: LineNumber :: new ( ) ;
805+ assert_eq ! ( b" 1\t " , incrementing_string. buf. as_slice( ) ) ;
806+ incrementing_string. increment ( ) ;
807+ assert_eq ! ( b" 2\t " , incrementing_string. buf. as_slice( ) ) ;
808+ // Run through to 100
809+ for _ in 3 ..=100 {
810+ incrementing_string. increment ( ) ;
811+ }
812+ assert_eq ! ( b" 100\t " , incrementing_string. buf. as_slice( ) ) ;
813+ // Run through until we overflow the original size.
814+ for _ in 101 ..=1000000 {
815+ incrementing_string. increment ( ) ;
816+ }
817+ // Confirm that the buffer expands when we overflow the original size.
818+ assert_eq ! ( b"1000000\t " , incrementing_string. buf. as_slice( ) ) ;
819+ incrementing_string. increment ( ) ;
820+ assert_eq ! ( b"1000001\t " , incrementing_string. buf. as_slice( ) ) ;
821+ }
744822}
0 commit comments