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)
This commit is contained in:
Alain Emilia Anna Zscheile 2024-11-29 18:49:26 +01:00
parent 1d0c2cf953
commit 9ddc83e72a
1 changed files with 34 additions and 23 deletions

View File

@ -95,11 +95,16 @@ impl<R: std::io::BufRead> ReadDsn<R> for String {
impl<R: std::io::BufRead> ReadDsn<R> for char { impl<R: std::io::BufRead> ReadDsn<R> for char {
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> { fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
let err = tokenizer.add_context(ParseError::Expected("a single character"));
let string = tokenizer.consume_token()?.expect_leaf()?; let string = tokenizer.consume_token()?.expect_leaf()?;
if string.chars().count() == 1 { let mut it = string.chars();
Ok(string.chars().next().unwrap()) let first = match it.next() {
} else { None => return Err(err),
Err(tokenizer.add_context(ParseError::Expected("a single character"))) Some(x) => x,
};
match it.next() {
None => Ok(first),
Some(_) => Err(err),
} }
} }
} }
@ -204,16 +209,22 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
fn next_char(&mut self) -> Result<char, ParseErrorContext> { fn next_char(&mut self) -> Result<char, ParseErrorContext> {
let return_chr = self.peek_char()?; let return_chr = self.peek_char()?;
self.peeked_char = None; self.reset_char();
Ok(return_chr)
}
/// discard last peeked character, move cursor forward
fn reset_char(&mut self) -> Option<char> {
let ret = self.peeked_char.take();
if let Some(return_chr) = ret {
if return_chr == '\n' { if return_chr == '\n' {
self.line += 1; self.line += 1;
self.column = 0; self.column = 0;
} else { } else {
self.column += 1; self.column += 1;
} }
}
Ok(return_chr) ret
} }
fn peek_char(&mut self) -> Result<char, ParseErrorContext> { fn peek_char(&mut self) -> Result<char, ParseErrorContext> {
@ -235,7 +246,7 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
loop { loop {
let chr = self.peek_char()?; let chr = self.peek_char()?;
if chr == ' ' || chr == '\r' || chr == '\n' { if chr == ' ' || chr == '\r' || chr == '\n' {
self.next_char().unwrap(); self.reset_char();
} else { } else {
return Ok(()); return Ok(());
} }
@ -246,7 +257,7 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
if let Some(quote_chr) = self.quote_char { if let Some(quote_chr) = self.quote_char {
if quote_chr == self.peek_char()? { if quote_chr == self.peek_char()? {
let mut string = String::new(); let mut string = String::new();
assert_eq!(self.next_char().unwrap(), quote_chr); self.reset_char();
loop { loop {
let ctx = self.context(); let ctx = self.context();
@ -274,7 +285,8 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
if chr == ' ' || chr == '(' || chr == ')' || chr == '\r' || chr == '\n' { if chr == ' ' || chr == '(' || chr == ')' || chr == '\r' || chr == '\n' {
break; break;
} }
string.push(self.next_char().unwrap()); string.push(chr);
self.reset_char();
} }
if string.is_empty() { if string.is_empty() {
@ -290,12 +302,11 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
pub fn consume_token(&mut self) -> Result<InputToken, ParseErrorContext> { pub fn consume_token(&mut self) -> Result<InputToken, ParseErrorContext> {
// move out of cache if not empty, otherwise consume input // move out of cache if not empty, otherwise consume input
// always leaves cache empty // always leaves cache empty
if let Some(token) = self.cached_token.take() { Ok(if let Some(token) = self.cached_token.take() {
Ok(token) token
} else { } else {
let token = self.read_token()?; self.read_token()?
Ok(token) })
}
} }
// puts a token back into cache, to be consumed by something else // puts a token back into cache, to be consumed by something else
@ -310,13 +321,13 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
let chr = self.peek_char()?; let chr = self.peek_char()?;
Ok(InputToken::new( Ok(InputToken::new(
if chr == '(' { if chr == '(' {
self.next_char().unwrap(); self.reset_char();
self.skip_whitespace()?; self.skip_whitespace()?;
ListToken::Start { ListToken::Start {
name: self.read_string()?, name: self.read_string()?,
} }
} else if chr == ')' { } else if chr == ')' {
self.next_char().unwrap(); self.reset_char();
ListToken::End ListToken::End
} else { } else {
ListToken::Leaf { ListToken::Leaf {