use itertools::{Itertools, PeekingNext};
use std::iter::Peekable;
use std::str::FromStr;
use std::fmt;
use std::num::ParseIntError;
#[derive(Clone, Debug)]
pub enum LefDefParseError {
InvalidCharacter,
UnexpectedEndOfFile,
UnexpectedToken(String, String),
UnknownToken(String),
InvalidLiteral(String),
IllegalBusBitChars(char, char),
NotImplemented(&'static str),
UndefinedProperty(String),
ParseIntError(ParseIntError),
Other(&'static str)
}
impl fmt::Display for LefDefParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LefDefParseError::InvalidCharacter => write!(f, "Invalid character."),
LefDefParseError::UnexpectedEndOfFile => write!(f, "Unexpected end of file."),
LefDefParseError::UnexpectedToken(actual, exp) =>
write!(f,"Unexpected token. '{}' instead of '{}'", actual, exp),
LefDefParseError::UnknownToken(t) => write!(f, "Unknown token: '{}'.", t),
LefDefParseError::InvalidLiteral(n) => write!(f, "Invalid literal: '{}'.", n),
LefDefParseError::IllegalBusBitChars(a, b) => write!(f, "Illegal bus bit chars: '{} {}'.", a, b),
LefDefParseError::NotImplemented(n) => write!(f, "Not implemented: '{}'.", n),
LefDefParseError::UndefinedProperty(p) => write!(f, "Undefined property: '{}'.", p),
LefDefParseError::Other(msg) => write!(f, "'{}'.", msg),
LefDefParseError::ParseIntError(e) => write!(f, "Illegal integer: '{}'", e)
}
}
}
impl From<ParseIntError> for LefDefParseError {
fn from(e: ParseIntError) -> Self {
Self::ParseIntError(e)
}
}
pub(crate) fn read_token<'a, I>(iter: &mut I, buffer: &'a mut String) -> Option<&'a str>
where I: Iterator<Item=char> + PeekingNext {
buffer.clear();
let iter = iter.by_ref();
loop {
let _n = iter.peeking_take_while(|c| c.is_whitespace()).count();
if let Some(c) = iter.peeking_next(|_| true) {
debug_assert!(!c.is_whitespace());
match c {
'#' => {
iter.peeking_take_while(|&c| c != '\n' && c != '\r').count();
}
'"' | '\'' => {
let quote_char = c;
let mut prev = None;
while let Some(c) = iter.next() {
if prev != Some('\\') && c == quote_char {
break;
}
buffer.push(c);
prev = Some(c);
}
return Some(buffer.as_str());
}
_ => {
let mut prev = Some(c);
buffer.push(c);
while let Some(c) = iter.next() {
if prev != Some('\\') && c.is_whitespace() {
break;
}
buffer.push(c);
prev = Some(c);
}
return Some(buffer.as_str());
}
}
} else {
return None;
}
}
}
#[test]
fn test_read_token() {
let data = r#"
# Comment 1
# Comment 2
token1
# Comment 3
token2 token3
"quoted token"
token4
"#;
let mut iter = data.chars()
.inspect(|c| print!("{}", c))
.peekable();
let mut buffer = String::new();
let result = read_token(&mut iter, &mut buffer);
assert!(result.is_some());
assert_eq!(buffer, "token1");
let result = read_token(&mut iter, &mut buffer);
assert!(result.is_some());
assert_eq!(buffer, "token2");
let result = read_token(&mut iter, &mut buffer);
assert!(result.is_some());
assert_eq!(buffer, "token3");
let result = read_token(&mut iter, &mut buffer);
assert!(result.is_some());
assert_eq!(buffer, "quoted token");
let result = read_token(&mut iter, &mut buffer);
assert!(result.is_some());
assert_eq!(buffer, "token4");
let result = read_token(&mut iter, &mut buffer);
assert!(result.is_none());
}
pub struct Tokenized<I>
where I: Iterator<Item=char> + PeekingNext {
iter: I,
has_current: bool,
current_token: Option<String>,
}
impl<I> Tokenized<I>
where I: Iterator<Item=char> + PeekingNext {
pub fn next_str(&mut self) -> Option<&str> {
self.advance();
self.current_token_str()
}
pub fn next_string(&mut self) -> Option<String> {
self.advance();
self.current_token()
}
pub fn take(&mut self) -> Result<String, LefDefParseError> {
let s = self.current_token();
self.advance();
if let Some(s) = s {
Ok(s)
} else {
Err(LefDefParseError::UnexpectedEndOfFile)
}
}
pub fn take_and_parse<F: FromStr>(&mut self) -> Result<F, LefDefParseError> {
let result = if let Some(s) = self.current_token_str() {
if let Ok(parsed) = s.parse::<F>() {
Ok(parsed)
} else {
Err(LefDefParseError::InvalidLiteral(s.to_string()))
}
} else {
Err(LefDefParseError::UnexpectedEndOfFile)
};
self.advance();
result
}
pub fn advance(&mut self) {
let mut buffer = self.current_token.take()
.unwrap_or_else(|| String::new());
let next_token = read_token(&mut self.iter, &mut buffer);
let has_next = next_token.is_some();
self.current_token = Some(buffer);
self.has_current = has_next;
}
pub fn current_token_str(&self) -> Option<&str> {
if self.has_current {
self.current_token.as_ref().map(|s| s.as_str())
} else {
None
}
}
pub fn current_token(&self) -> Option<String> {
self.current_token_str().map(|s| s.to_string())
}
pub fn expect(&mut self, s: &str) -> Result<(), LefDefParseError> {
if self.current_token.is_none() {
Err(LefDefParseError::UnexpectedEndOfFile)?;
}
if self.current_token_str() == Some(s) {
self.advance();
Ok(())
} else {
Err(LefDefParseError::UnexpectedToken(
s.to_string(), self.current_token().unwrap().to_string(),
))
}
}
pub fn test(&mut self, s: &str) -> Result<bool, LefDefParseError> {
let result = self.peeking_test(s)?;
if result {
self.advance();
}
Ok(result)
}
pub fn peeking_test(&mut self, s: &str) -> Result<bool, LefDefParseError> {
if self.current_token.is_none() {
Err(LefDefParseError::UnexpectedEndOfFile)?;
}
if self.current_token_str() == Some(s) {
Ok(true)
} else {
Ok(false)
}
}
pub fn skip_until(&mut self, s: &str) -> Result<(), LefDefParseError> {
while !self.test(s)? {
self.advance()
}
Ok(())
}
}
pub fn tokenize<I>(iter: I) -> Tokenized<Peekable<I>>
where I: Iterator<Item=char> {
Tokenized {
iter: iter.peekable(),
has_current: false,
current_token: None,
}
}
#[test]
fn test_tokenized() {
let data = r#"
# Comment 1
# Comment 2
token1
# Comment 3
token2 token3
"quoted token"
token4
"#;
let mut tokens = tokenize(data.chars());
assert_eq!(tokens.next_str(), Some("token1"));
assert_eq!(tokens.next_str(), Some("token2"));
}