From 30bcf28fdec43dec96b014ac79a49b6c57f3f10e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 25 Jul 2023 02:05:47 -0700 Subject: [PATCH] Allow escaping newlines (#1551) --- src/lexer.rs | 26 ++++++++++++++ tests/lib.rs | 1 + tests/newline_escape.rs | 77 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 tests/newline_escape.rs diff --git a/src/lexer.rs b/src/lexer.rs index 55de456..aaa3bbd 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -481,6 +481,7 @@ impl<'src> Lexer<'src> { ',' => self.lex_single(Comma), '/' => self.lex_single(Slash), ':' => self.lex_colon(), + '\\' => self.lex_escape(), '=' => self.lex_choices('=', &[('=', EqualsEquals), ('~', EqualsTilde)], Equals), '@' => self.lex_single(At), '[' => self.lex_delimiter(BracketL), @@ -709,6 +710,31 @@ impl<'src> Lexer<'src> { Ok(()) } + /// Lex an token starting with '\' escape + fn lex_escape(&mut self) -> CompileResult<'src, ()> { + self.presume('\\')?; + + // Treat newline escaped with \ as whitespace + if self.accepted('\n')? { + while self.next_is_whitespace() { + self.advance()?; + } + self.token(Whitespace); + } else if self.accepted('\r')? { + if !self.accepted('\n')? { + return Err(self.error(UnpairedCarriageReturn)); + } + while self.next_is_whitespace() { + self.advance()?; + } + self.token(Whitespace); + } else if let Some(character) = self.next { + return Err(self.error(CompileErrorKind::InvalidEscapeSequence { character })); + } + + Ok(()) + } + /// Lex a carriage return and line feed fn lex_eol(&mut self) -> CompileResult<'src, ()> { if self.accepted('\r')? { diff --git a/tests/lib.rs b/tests/lib.rs index 23f5a72..6f119d0 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -63,6 +63,7 @@ mod json; mod line_prefixes; mod misc; mod multibyte_char; +mod newline_escape; mod no_cd; mod no_exit_message; mod os_attributes; diff --git a/tests/newline_escape.rs b/tests/newline_escape.rs new file mode 100644 index 0000000..2b278fb --- /dev/null +++ b/tests/newline_escape.rs @@ -0,0 +1,77 @@ +use super::*; + +#[test] +fn newline_escape_deps() { + Test::new() + .justfile( + " + default: a \\ + b \\ + c + a: + echo a + b: + echo b + c: + echo c + ", + ) + .stdout("a\nb\nc\n") + .stderr("echo a\necho b\necho c\n") + .run(); +} + +#[test] +fn newline_escape_deps_no_indent() { + Test::new() + .justfile( + " + default: a\\ + b\\ + c + a: + echo a + b: + echo b + c: + echo c + ", + ) + .stdout("a\nb\nc\n") + .stderr("echo a\necho b\necho c\n") + .run(); +} + +#[test] +fn newline_escape_deps_linefeed() { + Test::new() + .justfile( + " + default: a\\\r + b + a: + echo a + b: + echo b + ", + ) + .stdout("a\nb\n") + .stderr("echo a\necho b\n") + .run(); +} + +#[test] +fn newline_escape_deps_invalid_esc() { + Test::new() + .justfile( + " + default: a\\ b + ", + ) + .stdout("") + .stderr( + "error: `\\ ` is not a valid escape sequence\n |\n1 | default: a\\ b\n | ^\n", + ) + .status(EXIT_FAILURE) + .run(); +}