diff --git a/src/commands/x/parser.rs b/src/commands/x/parser.rs index f88182ad..ecad49f6 100644 --- a/src/commands/x/parser.rs +++ b/src/commands/x/parser.rs @@ -10,9 +10,11 @@ use thiserror::Error; const EXPR_START: char = '{'; const EXPR_END: char = '}'; const PIPE: char = '|'; -const DEFAULT_ESCAPE: char = '\\'; + const SINGLE_QUOTE: char = '\''; const DOUBLE_QUOTE: char = '"'; + +const DEFAULT_ESCAPE: char = '\\'; const ESCAPED_LF: char = 'n'; const ESCAPED_CR: char = 'c'; const ESCAPED_TAB: char = 't'; @@ -178,7 +180,7 @@ impl Parser<'_> { self.consume(quote); return Ok(arg); } else if char == DEFAULT_ESCAPE { - arg.push(self.parse_escape_sequence(&[quote])?); + arg.push(self.parse_escape_sequence(|char| char == quote)); } else { arg.push(self.consume(char)); } @@ -193,15 +195,15 @@ impl Parser<'_> { while let Some(char) = self.peek() { match char { EXPR_START | PIPE | EXPR_END | ' ' => break, - DEFAULT_ESCAPE => arg.push(self.parse_escape_sequence(&[ - ' ', - DEFAULT_ESCAPE, - SINGLE_QUOTE, - DOUBLE_QUOTE, - EXPR_START, - PIPE, - EXPR_END, - ])?), + DEFAULT_ESCAPE => { + let is_escapable = |char| match char { + DEFAULT_ESCAPE | SINGLE_QUOTE | DOUBLE_QUOTE | EXPR_START | PIPE + | EXPR_END => true, + char if char.is_whitespace() => true, + _ => false, + }; + arg.push(self.parse_escape_sequence(is_escapable)) + } quote @ (SINGLE_QUOTE | DOUBLE_QUOTE) => { arg.push_str(&self.parse_quote_arg(quote)?) } @@ -218,7 +220,8 @@ impl Parser<'_> { while let Some(char) = self.peek() { match char { DEFAULT_ESCAPE => { - constant.push(self.parse_escape_sequence(&[EXPR_START, EXPR_END])?) + let is_escapable = |char| matches!(char, EXPR_START | EXPR_END); + constant.push(self.parse_escape_sequence(is_escapable)) } EXPR_START => break, EXPR_END => { @@ -231,24 +234,16 @@ impl Parser<'_> { Ok(constant) } - fn parse_escape_sequence(&mut self, escapable_chars: &[char]) -> Result { + fn parse_escape_sequence(&mut self, is_escapable: impl Fn(char) -> bool) -> char { self.consume(DEFAULT_ESCAPE); match self.peek() { - Some(char @ (ESCAPED_LF | ESCAPED_CR | ESCAPED_TAB | DEFAULT_ESCAPE)) => { - self.parse_escaped_char(char) - } - Some(char) if escapable_chars.contains(&char) => self.parse_escaped_char(char), - _ => Ok(DEFAULT_ESCAPE), - } - } - - fn parse_escaped_char(&mut self, char: char) -> Result { - match self.consume(char) { - ESCAPED_LF => Ok('\n'), - ESCAPED_CR => Ok('\r'), - ESCAPED_TAB => Ok('\t'), - other => Ok(other), + Some(DEFAULT_ESCAPE) => self.consume(DEFAULT_ESCAPE), + Some(ESCAPED_LF) => self.consume_as(ESCAPED_LF, '\n'), + Some(ESCAPED_CR) => self.consume_as(ESCAPED_CR, '\r'), + Some(ESCAPED_TAB) => self.consume_as(ESCAPED_TAB, '\t'), + Some(char) if is_escapable(char) => self.consume(char), + _ => DEFAULT_ESCAPE, // If this is not a valid escape sequence, keep the escape character } } @@ -274,8 +269,12 @@ impl Parser<'_> { } fn consume(&mut self, expected: char) -> char { + self.consume_as(expected, expected) + } + + fn consume_as(&mut self, expected: char, result: char) -> char { match self.next() { - Some(char) if char == expected => char, + Some(char) if char == expected => result, Some(char) => unreachable!("parser expected {:?} but got {:?}", expected, char), None => unreachable!("parser expected {:?} but got EOF", expected), }