From 4bae87a58cd8d22efa2b610d3ac4cb7578cd3d3b Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 28 Nov 2023 15:39:59 -0800 Subject: [PATCH] allow $\n in most contexts when parsing The Ninja rules here aren't super clear, like I think this might be legal build $ foo$ :$ bar baz$ =$ blah and at least that kind of thing appears to be possible output of ninja_syntax.py. Fixes #89. --- src/parse.rs | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/parse.rs b/src/parse.rs index f128e76..382f6f0 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -93,7 +93,7 @@ impl<'text> Parser<'text> { ' ' | '\t' => return self.scanner.parse_error("unexpected whitespace"), _ => { let ident = self.read_ident()?; - self.scanner.skip_spaces(); + self.skip_spaces(); match ident { "rule" => return Ok(Some(Statement::Rule(self.read_rule()?))), "build" => return Ok(Some(Statement::Build(self.read_build(loader)?))), @@ -125,19 +125,21 @@ impl<'text> Parser<'text> { } } + /// Read the `= ...` part of a variable definition. fn read_vardef(&mut self) -> ParseResult> { - self.scanner.skip_spaces(); + self.skip_spaces(); self.scanner.expect('=')?; - self.scanner.skip_spaces(); + self.skip_spaces(); self.read_eval() } + /// Read a collection of ` foo = bar` variables, with leading indent. fn read_scoped_vars(&mut self) -> ParseResult> { let mut vars = VarList::default(); while self.scanner.peek() == ' ' { self.scanner.skip_spaces(); let name = self.read_ident()?; - self.scanner.skip_spaces(); + self.skip_spaces(); let val = self.read_vardef()?; vars.insert(name, val.into_owned()); } @@ -184,10 +186,10 @@ impl<'text> Parser<'text> { loader: &mut L, v: &mut Vec, ) -> ParseResult<()> { - self.scanner.skip_spaces(); + self.skip_spaces(); while let Some(path) = self.read_path(loader)? { v.push(path); - self.scanner.skip_spaces(); + self.skip_spaces(); } Ok(()) } @@ -204,7 +206,7 @@ impl<'text> Parser<'text> { } self.scanner.expect(':')?; - self.scanner.skip_spaces(); + self.skip_spaces(); let rule = self.read_ident()?; let mut ins = Vec::new(); @@ -246,10 +248,7 @@ impl<'text> Parser<'text> { fn read_default(&mut self, loader: &mut L) -> ParseResult> { let mut defaults = Vec::new(); - while let Some(path) = self.read_path(loader)? { - defaults.push(path); - self.scanner.skip_spaces(); - } + self.read_paths_to(loader, &mut defaults)?; if defaults.is_empty() { return self.scanner.parse_error("expected path"); } @@ -407,6 +406,25 @@ impl<'text> Parser<'text> { } }) } + + fn skip_spaces(&mut self) { + loop { + match self.scanner.read() { + ' ' => {} + '$' => { + if self.scanner.peek() != '\n' { + self.scanner.back(); + return; + } + self.scanner.next(); + } + _ => { + self.scanner.back(); + return; + } + } + } + } } #[cfg(test)] @@ -466,4 +484,15 @@ mod tests { }) )); } + + #[test] + fn parse_trailing_newline() { + let mut buf = "build$\n foo$\n : $\n touch $\n\n".as_bytes().to_vec(); + let mut parser = Parser::new(&mut buf); + let stmt = parser.read(&mut StringLoader {}).unwrap().unwrap(); + assert!(matches!( + stmt, + Statement::Build(Build { rule: "touch", .. }) + )); + } }