mirror of https://codeberg.org/topola/topola.git
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:
parent
1d0c2cf953
commit
9ddc83e72a
|
|
@ -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,18 +209,24 @@ 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();
|
||||||
|
|
||||||
if return_chr == '\n' {
|
|
||||||
self.line += 1;
|
|
||||||
self.column = 0;
|
|
||||||
} else {
|
|
||||||
self.column += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(return_chr)
|
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' {
|
||||||
|
self.line += 1;
|
||||||
|
self.column = 0;
|
||||||
|
} else {
|
||||||
|
self.column += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
fn peek_char(&mut self) -> Result<char, ParseErrorContext> {
|
fn peek_char(&mut self) -> Result<char, ParseErrorContext> {
|
||||||
Ok(if let Some(chr) = self.peeked_char {
|
Ok(if let Some(chr) = self.peeked_char {
|
||||||
chr
|
chr
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue