@@ -8,10 +8,8 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
88use std:: env;
99use std:: ffi:: { OsStr , OsString } ;
1010use std:: io:: { self , StdoutLock , Write } ;
11- use std:: iter:: Peekable ;
12- use std:: ops:: ControlFlow ;
13- use std:: slice:: Iter ;
1411use uucore:: error:: { UResult , USimpleError } ;
12+ use uucore:: format:: { parse_escape_only, EscapedChar , FormatChar } ;
1513use uucore:: { format_usage, help_about, help_section, help_usage} ;
1614
1715const ABOUT : & str = help_about ! ( "echo.md" ) ;
@@ -25,236 +23,6 @@ mod options {
2523 pub const DISABLE_BACKSLASH_ESCAPE : & str = "disable_backslash_escape" ;
2624}
2725
28- enum BackslashNumberType {
29- OctalStartingWithNonZero ( u8 ) ,
30- OctalStartingWithZero ,
31- Hexadecimal ,
32- }
33-
34- impl BackslashNumberType {
35- fn base ( & self ) -> Base {
36- match self {
37- BackslashNumberType :: OctalStartingWithZero
38- | BackslashNumberType :: OctalStartingWithNonZero ( _) => Base :: Octal ,
39- BackslashNumberType :: Hexadecimal => Base :: Hexadecimal ,
40- }
41- }
42- }
43-
44- enum Base {
45- Octal ,
46- Hexadecimal ,
47- }
48-
49- impl Base {
50- fn ascii_to_number ( & self , digit : u8 ) -> Option < u8 > {
51- fn octal_ascii_digit_to_number ( digit : u8 ) -> Option < u8 > {
52- let number = match digit {
53- b'0' => 0 ,
54- b'1' => 1 ,
55- b'2' => 2 ,
56- b'3' => 3 ,
57- b'4' => 4 ,
58- b'5' => 5 ,
59- b'6' => 6 ,
60- b'7' => 7 ,
61- _ => {
62- return None ;
63- }
64- } ;
65-
66- Some ( number)
67- }
68-
69- fn hexadecimal_ascii_digit_to_number ( digit : u8 ) -> Option < u8 > {
70- let number = match digit {
71- b'0' => 0 ,
72- b'1' => 1 ,
73- b'2' => 2 ,
74- b'3' => 3 ,
75- b'4' => 4 ,
76- b'5' => 5 ,
77- b'6' => 6 ,
78- b'7' => 7 ,
79- b'8' => 8 ,
80- b'9' => 9 ,
81- b'A' | b'a' => 10 ,
82- b'B' | b'b' => 11 ,
83- b'C' | b'c' => 12 ,
84- b'D' | b'd' => 13 ,
85- b'E' | b'e' => 14 ,
86- b'F' | b'f' => 15 ,
87- _ => {
88- return None ;
89- }
90- } ;
91-
92- Some ( number)
93- }
94-
95- match self {
96- Self :: Octal => octal_ascii_digit_to_number ( digit) ,
97- Self :: Hexadecimal => hexadecimal_ascii_digit_to_number ( digit) ,
98- }
99- }
100-
101- fn maximum_number_of_digits ( & self ) -> u8 {
102- match self {
103- Self :: Octal => 3 ,
104- Self :: Hexadecimal => 2 ,
105- }
106- }
107-
108- fn radix ( & self ) -> u8 {
109- match self {
110- Self :: Octal => 8 ,
111- Self :: Hexadecimal => 16 ,
112- }
113- }
114- }
115-
116- /// Parse the numeric part of `\xHHH`, `\0NNN`, and `\NNN` escape sequences
117- fn parse_backslash_number (
118- input : & mut Peekable < Iter < u8 > > ,
119- backslash_number_type : BackslashNumberType ,
120- ) -> Option < u8 > {
121- let first_digit_ascii = match backslash_number_type {
122- BackslashNumberType :: OctalStartingWithZero | BackslashNumberType :: Hexadecimal => {
123- match input. peek ( ) {
124- Some ( & & digit_ascii) => digit_ascii,
125- None => {
126- // One of the following cases: argument ends with "\0" or "\x"
127- // If "\0" (octal): caller will print not ASCII '0', 0x30, but ASCII '\0' (NUL), 0x00
128- // If "\x" (hexadecimal): caller will print literal "\x"
129- return None ;
130- }
131- }
132- }
133- // Never returns early when backslash number starts with "\1" through "\7", because caller provides the
134- // first digit
135- BackslashNumberType :: OctalStartingWithNonZero ( digit_ascii) => digit_ascii,
136- } ;
137-
138- let base = backslash_number_type. base ( ) ;
139-
140- let first_digit_number = match base. ascii_to_number ( first_digit_ascii) {
141- Some ( digit_number) => {
142- // Move past byte, since it was successfully parsed
143- let _ = input. next ( ) ;
144-
145- digit_number
146- }
147- None => {
148- // The first digit was not a valid octal or hexadecimal digit
149- // This should never be the case when the backslash number starts with "\1" through "\7"
150- // (caller unwraps to verify this)
151- return None ;
152- }
153- } ;
154-
155- let radix = base. radix ( ) ;
156-
157- let mut sum = first_digit_number;
158-
159- for _ in 1 ..( base. maximum_number_of_digits ( ) ) {
160- match input
161- . peek ( )
162- . and_then ( |& & digit_ascii| base. ascii_to_number ( digit_ascii) )
163- {
164- Some ( digit_number) => {
165- // Move past byte, since it was successfully parsed
166- let _ = input. next ( ) ;
167-
168- // All arithmetic on `sum` needs to be wrapping, because octal input can
169- // take 3 digits, which is 9 bits, and therefore more than what fits in a
170- // `u8`.
171- //
172- // GNU Core Utilities: "if nnn is a nine-bit value, the ninth bit is ignored"
173- // https://www.gnu.org/software/coreutils/manual/html_node/echo-invocation.html
174- sum = sum. wrapping_mul ( radix) . wrapping_add ( digit_number) ;
175- }
176- None => {
177- break ;
178- }
179- }
180- }
181-
182- Some ( sum)
183- }
184-
185- fn print_escaped ( input : & [ u8 ] , output : & mut StdoutLock ) -> io:: Result < ControlFlow < ( ) > > {
186- let mut iter = input. iter ( ) . peekable ( ) ;
187-
188- while let Some ( & current_byte) = iter. next ( ) {
189- if current_byte != b'\\' {
190- output. write_all ( & [ current_byte] ) ?;
191-
192- continue ;
193- }
194-
195- // This is for the \NNN syntax for octal sequences
196- // Note that '0' is intentionally omitted, because the \0NNN syntax is handled below
197- if let Some ( & & first_digit @ b'1' ..=b'7' ) = iter. peek ( ) {
198- // Unwrap because anything starting with "\1" through "\7" can be successfully parsed
199- let parsed_octal_number = parse_backslash_number (
200- & mut iter,
201- BackslashNumberType :: OctalStartingWithNonZero ( first_digit) ,
202- )
203- . unwrap ( ) ;
204-
205- output. write_all ( & [ parsed_octal_number] ) ?;
206-
207- continue ;
208- }
209-
210- if let Some ( next) = iter. next ( ) {
211- let unescaped: & [ u8 ] = match * next {
212- b'\\' => br"\" ,
213- b'a' => b"\x07 " ,
214- b'b' => b"\x08 " ,
215- b'c' => return Ok ( ControlFlow :: Break ( ( ) ) ) ,
216- b'e' => b"\x1B " ,
217- b'f' => b"\x0C " ,
218- b'n' => b"\n " ,
219- b'r' => b"\r " ,
220- b't' => b"\t " ,
221- b'v' => b"\x0B " ,
222- b'x' => {
223- if let Some ( parsed_hexadecimal_number) =
224- parse_backslash_number ( & mut iter, BackslashNumberType :: Hexadecimal )
225- {
226- & [ parsed_hexadecimal_number]
227- } else {
228- // "\x" with any non-hexadecimal digit after means "\x" is treated literally
229- br"\x"
230- }
231- }
232- b'0' => {
233- if let Some ( parsed_octal_number) = parse_backslash_number (
234- & mut iter,
235- BackslashNumberType :: OctalStartingWithZero ,
236- ) {
237- & [ parsed_octal_number]
238- } else {
239- // "\0" with any non-octal digit after it means "\0" is treated as ASCII '\0' (NUL), 0x00
240- b"\0 "
241- }
242- }
243- other_byte => {
244- // Backslash and the following byte are treated literally
245- & [ b'\\' , other_byte]
246- }
247- } ;
248-
249- output. write_all ( unescaped) ?;
250- } else {
251- output. write_all ( br"\" ) ?;
252- }
253- }
254-
255- Ok ( ControlFlow :: Continue ( ( ) ) )
256- }
257-
25826// A workaround because clap interprets the first '--' as a marker that a value
25927// follows. In order to use '--' as a value, we have to inject an additional '--'
26028fn handle_double_hyphens ( args : impl uucore:: Args ) -> impl uucore:: Args {
@@ -367,8 +135,11 @@ fn execute(
367135 }
368136
369137 if escaped {
370- if print_escaped ( bytes, stdout_lock) ?. is_break ( ) {
371- return Ok ( ( ) ) ;
138+ for item in parse_escape_only ( bytes) {
139+ match item {
140+ EscapedChar :: End => return Ok ( ( ) ) ,
141+ c => c. write ( & mut * stdout_lock) ?,
142+ } ;
372143 }
373144 } else {
374145 stdout_lock. write_all ( bytes) ?;
0 commit comments