use super::common::ListToken; use super::error::{ParseError, ParseErrorContext}; use super::structure::Parser; use utf8_chars::BufReadCharsExt; pub struct InputToken { pub token: ListToken, pub context: (usize, usize), } impl InputToken { pub fn new(token: ListToken, context: (usize, usize)) -> Self { Self { token, context } } pub fn expect_any_start(self) -> Result { if let ListToken::Start { name } = self.token { Ok(name.to_ascii_lowercase()) } else { Err(ParseError::ExpectedStartOfList("").add_context(self.context)) } } pub fn expect_end(self) -> Result<(), ParseErrorContext> { if let ListToken::End = self.token { Ok(()) } else { Err(ParseError::ExpectedEndOfList.add_context(self.context)) } } } pub trait ReadDsn: Sized { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result; } // custom impl feeding the read values back into the tokenizer impl ReadDsn for Parser { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { Ok(Self { string_quote: tokenizer .read_optional(&["string_quote"])? .inspect(|v| tokenizer.quote_char = Some(*v)), space_in_quoted_tokens: tokenizer .read_optional(&["space_in_quoted_tokens"])? .inspect(|v| tokenizer.space_in_quoted = *v), host_cad: tokenizer.read_optional(&["host_cad"])?, host_version: tokenizer.read_optional(&["host_version"])?, }) } } impl ReadDsn for String { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { let inptoken = tokenizer.consume_token()?; if let ListToken::Leaf { value } = inptoken.token { Ok(value) } else { Err(ParseError::ExpectedLeaf.add_context(inptoken.context)) } } } impl ReadDsn for char { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { let err = tokenizer.add_context(ParseError::Expected("a single character")); let string = String::read_dsn(tokenizer)?; 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), } } } impl ReadDsn for bool { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { match String::read_dsn(tokenizer)?.as_str() { "on" => Ok(true), "off" => Ok(false), _ => Err(tokenizer.add_context(ParseError::Expected("boolean"))), } } } /// `impl_ReadDsn_via_FromStr!((TYPE, TYPE-NAME); ...)` macro_rules! impl_ReadDsn_via_FromStr { ($(($t:ty, $name:expr));* $(;)?) => { $( impl ReadDsn for $t { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { String::read_dsn(tokenizer)? .parse() .map_err(|_| tokenizer.add_context(ParseError::Expected($name))) } } )* } } impl_ReadDsn_via_FromStr!( (i32, "i32"); (u32, "u32"); (usize, "usize"); (f32, "f32"); (f64, "f64"); ); pub struct ListTokenizer { reader: R, peeked_char: Option, cached_token: Option, space_in_quoted: bool, quote_char: Option, line: usize, column: usize, } impl ListTokenizer { pub fn new(reader: R) -> Self { Self { reader, peeked_char: None, cached_token: None, space_in_quoted: false, quote_char: None, line: 1, column: 0, } } pub fn context(&self) -> (usize, usize) { (self.line, self.column) } fn add_context(&self, error: ParseError) -> ParseErrorContext { ParseErrorContext { error, context: (self.line, self.column), } } fn map_context(&self, result: Result) -> Result { result.map_err(|err| self.add_context(err)) } fn next_char(&mut self) -> Result { let return_chr = self.peek_char()?; 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 } else { let chr = self .reader .read_char() .transpose() .ok_or(self.add_context(ParseError::Eof))? .map_err(|err| self.add_context(err.into()))?; self.peeked_char = Some(chr); chr }) } fn skip_whitespace(&mut self) -> Result<(), ParseErrorContext> { loop { let chr = self.peek_char()?; if chr == ' ' || chr == '\r' || chr == '\n' { self.reset_char(); } else { return Ok(()); } } } fn read_string(&mut self) -> Result { fn read_quoted( this: &mut ListTokenizer, quote_chr: char, ) -> Result { let mut string = String::new(); this.reset_char(); loop { let ctx = this.context(); let chr = this.next_char()?; if chr == ' ' && !this.space_in_quoted { return Err(ParseError::UnexpectedSpaceInQuotedStr.add_context(ctx)); } else if chr == quote_chr { break; } else { string.push(chr); } } Ok(string) } if let Some(quote_chr) = self.quote_char { if quote_chr == self.peek_char()? { return read_quoted(self, quote_chr); } } self.read_unquoted() } fn read_unquoted(&mut self) -> Result { let mut string = String::new(); loop { let chr = self.peek_char()?; if chr == ' ' || chr == '(' || chr == ')' || chr == '\r' || chr == '\n' { break; } string.push(chr); self.reset_char(); } if string.is_empty() { Err(self.add_context(ParseError::Expected("string (unquoted)"))) } else { Ok(string) } } // the following two methods effectively allow 1 token of lookahead // returns next token, either a cached one returned earlier or a newly read one pub fn consume_token(&mut self) -> Result { // move out of cache if not empty, otherwise consume input // always leaves cache empty Ok(if let Some(token) = self.cached_token.take() { token } else { self.read_token()? }) } // puts a token back into cache, to be consumed by something else pub fn return_token(&mut self, token: InputToken) { assert!(self.cached_token.is_none()); self.cached_token = Some(token); } fn read_token(&mut self) -> Result { self.skip_whitespace()?; let context = self.context(); let chr = self.peek_char()?; Ok(InputToken::new( if chr == '(' { self.reset_char(); self.skip_whitespace()?; ListToken::Start { name: self.read_string()?, } } else if chr == ')' { self.reset_char(); ListToken::End } else { ListToken::Leaf { value: self.read_string()?, } }, context, )) } pub fn read_value>(&mut self) -> Result { T::read_dsn(self) } pub fn read_named>( &mut self, valid_names: &[&'static str], ) -> Result { assert!(!valid_names.is_empty()); self.read_optional(valid_names)? .ok_or_else(|| self.add_context(ParseError::ExpectedStartOfList(valid_names[0]))) } pub fn read_optional>( &mut self, valid_names: &[&'static str], ) -> Result, ParseErrorContext> { let input = self.consume_token()?; Ok(if input.token.is_start_of(valid_names) { let value = self.read_value::()?; self.consume_token()?.expect_end()?; Some(value) } else { self.return_token(input); None }) } pub fn read_array>(&mut self) -> Result, ParseErrorContext> { let mut array = Vec::::new(); loop { let input = self.consume_token()?; if let ListToken::Leaf { .. } = input.token { self.return_token(input); array.push(self.read_value::()?); } else { self.return_token(input); break; } } Ok(array) } pub fn read_named_array>( &mut self, valid_names: &[&'static str], ) -> Result, ParseErrorContext> { let mut array = Vec::new(); while let Some(value) = self.read_optional::(valid_names)? { array.push(value); } Ok(array) } }