From 9ddc83e72abf15da98091c01669ed0918b551bcd Mon Sep 17 00:00:00 2001 From: Alain Emilia Anna Zscheile Date: Fri, 29 Nov 2024 18:49:26 +0100 Subject: [PATCH] feat(specctra/read): separate function to discard peeked char This is useful to get rid of the pattern "self.next_char().unwrap();", which makes it harder to assess panic safety of parsing code (which should never panic via its public interface) --- src/specctra/read.rs | 57 ++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/specctra/read.rs b/src/specctra/read.rs index 585066f..620bc0e 100644 --- a/src/specctra/read.rs +++ b/src/specctra/read.rs @@ -95,11 +95,16 @@ impl ReadDsn for String { impl ReadDsn for char { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + let err = tokenizer.add_context(ParseError::Expected("a single character")); let string = tokenizer.consume_token()?.expect_leaf()?; - if string.chars().count() == 1 { - Ok(string.chars().next().unwrap()) - } else { - Err(tokenizer.add_context(ParseError::Expected("a single character"))) + let mut it = string.chars(); + let first = match it.next() { + None => return Err(err), + Some(x) => x, + }; + match it.next() { + None => Ok(first), + Some(_) => Err(err), } } } @@ -204,18 +209,24 @@ impl ListTokenizer { fn next_char(&mut self) -> Result { let return_chr = self.peek_char()?; - self.peeked_char = None; - - if return_chr == '\n' { - self.line += 1; - self.column = 0; - } else { - self.column += 1; - } - + self.reset_char(); Ok(return_chr) } + /// discard last peeked character, move cursor forward + fn reset_char(&mut self) -> Option { + let ret = self.peeked_char.take(); + if let Some(return_chr) = ret { + if return_chr == '\n' { + self.line += 1; + self.column = 0; + } else { + self.column += 1; + } + } + ret + } + fn peek_char(&mut self) -> Result { Ok(if let Some(chr) = self.peeked_char { chr @@ -235,7 +246,7 @@ impl ListTokenizer { loop { let chr = self.peek_char()?; if chr == ' ' || chr == '\r' || chr == '\n' { - self.next_char().unwrap(); + self.reset_char(); } else { return Ok(()); } @@ -246,7 +257,7 @@ impl ListTokenizer { if let Some(quote_chr) = self.quote_char { if quote_chr == self.peek_char()? { let mut string = String::new(); - assert_eq!(self.next_char().unwrap(), quote_chr); + self.reset_char(); loop { let ctx = self.context(); @@ -274,7 +285,8 @@ impl ListTokenizer { if chr == ' ' || chr == '(' || chr == ')' || chr == '\r' || chr == '\n' { break; } - string.push(self.next_char().unwrap()); + string.push(chr); + self.reset_char(); } if string.is_empty() { @@ -290,12 +302,11 @@ impl ListTokenizer { pub fn consume_token(&mut self) -> Result { // move out of cache if not empty, otherwise consume input // always leaves cache empty - if let Some(token) = self.cached_token.take() { - Ok(token) + Ok(if let Some(token) = self.cached_token.take() { + token } else { - let token = self.read_token()?; - Ok(token) - } + self.read_token()? + }) } // puts a token back into cache, to be consumed by something else @@ -310,13 +321,13 @@ impl ListTokenizer { let chr = self.peek_char()?; Ok(InputToken::new( if chr == '(' { - self.next_char().unwrap(); + self.reset_char(); self.skip_whitespace()?; ListToken::Start { name: self.read_string()?, } } else if chr == ')' { - self.next_char().unwrap(); + self.reset_char(); ListToken::End } else { ListToken::Leaf {