diff --git a/crates/simplexpr/src/parser/lexer.rs b/crates/simplexpr/src/parser/lexer.rs index 34b0ed6..ce9919c 100644 --- a/crates/simplexpr/src/parser/lexer.rs +++ b/crates/simplexpr/src/parser/lexer.rs @@ -166,7 +166,7 @@ impl<'s> Lexer<'s> { let tok_str = &self.source[self.pos..self.pos + len]; let old_pos = self.pos; - self.advance_by(len); + self.advance_by(len)?; match LEXER_FNS[i](tok_str.to_string()) { Token::Skip | Token::Comment => {} token => { @@ -177,11 +177,17 @@ impl<'s> Lexer<'s> { } } - fn advance_by(&mut self, n: usize) { + /// Advance position by the given number of characters, respecting char boundaries. Returns `None` when n exceeds the source length + #[must_use] + fn advance_by(&mut self, n: usize) -> Option<()> { + if self.pos + n > self.source.len() { + return None; + } self.pos += n; while self.pos < self.source.len() && !self.source.is_char_boundary(self.pos) { self.pos += 1; } + Some(()) } fn advance_until_one_of<'a>(&mut self, pat: &[&'a str]) -> Option<&'a str> { @@ -190,10 +196,10 @@ impl<'s> Lexer<'s> { if remaining.is_empty() { return None; } else if let Some(matched) = pat.iter().find(|&&p| remaining.starts_with(p)) { - self.advance_by(matched.len()); + self.advance_by(matched.len())?; return Some(matched); } else { - self.advance_by(1); + self.advance_by(1)?; } } } @@ -203,7 +209,7 @@ impl<'s> Lexer<'s> { pattern.push("\\"); match self.advance_until_one_of(pattern.as_slice()) { Some("\\") => { - self.advance_by(1); + self.advance_by(1)?; self.advance_until_unescaped_one_of(pat) } result => result, @@ -213,7 +219,7 @@ impl<'s> Lexer<'s> { pub fn string_lit(&mut self) -> Option>>, LexicalError>> { let quote = self.remaining().chars().next()?.to_string(); let str_lit_start = self.pos; - self.advance_by(quote.len()); + self.advance_by(quote.len())?; let mut elements = Vec::new(); let mut in_string_lit = true; @@ -308,6 +314,7 @@ mod test { snapshot_string! { basic => v!(r#"bar "foo""#), digit => v!(r#"12"#), + quote_backslash_eof => v!(r#""\"#), number_in_ident => v!(r#"foo_1_bar"#), interpolation_1 => v!(r#" "foo ${2 * 2} bar" "#), interpolation_nested => v!(r#" "foo ${(2 * 2) + "${5 + 5}"} bar" "#), diff --git a/crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__quote_backslash_eof.snap b/crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__quote_backslash_eof.snap new file mode 100644 index 0000000..cd7f646 --- /dev/null +++ b/crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__quote_backslash_eof.snap @@ -0,0 +1,6 @@ +--- +source: crates/simplexpr/src/parser/lexer.rs +assertion_line: 313 +expression: "v!(r#\"\"\\\"#)" +--- +