LEF/DEF input and output module for LibrEDA.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

346 lines
9.8 KiB

/*
* Copyright (c) 2021-2021 Thomas Kramer.
*
* This file is part of LibrEDA
* (see https://codeberg.org/libreda/libreda-lefdef).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//! Functions for parsing ASCII-based formats from iterators over bytes.
use itertools::{Itertools, PeekingNext};
use std::iter::Peekable;
use std::str::FromStr;
use std::fmt;
use std::num::ParseIntError;
/// Error while parsing LEF or DEF.
/// TODO: Separate lexer errors from LEF/DEF specific errors.
#[derive(Clone, Debug)]
pub enum LefDefError {
InvalidCharacter,
UnexpectedEndOfFile,
/// Expected and actual token.
UnexpectedToken(String, String),
UnknownToken(String),
InvalidLiteral(String),
IllegalBusBitChars(char, char),
NotImplemented(&'static str),
/// Using a property name that has not been defined in PROPERTYDEFINITIONS.
UndefinedProperty(String),
ParseIntError(ParseIntError),
Other(&'static str)
}
impl fmt::Display for LefDefError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LefDefError::InvalidCharacter => write!(f, "Invalid character."),
LefDefError::UnexpectedEndOfFile => write!(f,"Unexpected end of file."),
LefDefError::UnexpectedToken(actual, exp) =>
write!(f,"Unexpected token. '{}' instead of '{}'", actual, exp),
LefDefError::UnknownToken(t) => write!(f,"Unknown token: '{}'.", t),
LefDefError::InvalidLiteral(n) => write!(f, "Invalid literal: '{}'.", n),
LefDefError::IllegalBusBitChars(a, b) => write!(f,"Illegal bus bit chars: '{} {}'.", a, b),
LefDefError::NotImplemented(n) => write!(f,"Not implemented: '{}'.", n),
LefDefError::UndefinedProperty(p) => write!(f,"Undefined property: '{}'.", p),
LefDefError::Other(msg) => write!(f,"'{}'.", msg),
LefDefError::ParseIntError(e) => write!(f, "Illegal integer: '{}'", e)
}
}
}
impl From<ParseIntError> for LefDefError {
fn from(e: ParseIntError) -> Self {
Self::ParseIntError(e)
}
}
// /// Check if a char is whitespace.
// fn is_whitespace(c: char) -> bool {
// match c {
// ' ' => true,
// '\t' => true,
// '\r' | '\n' => true,
// _ => false
// }
// }
/// Read a token into the buffer. Tokens are separated by white space. Comments are ignored.
/// Quoted tokens can contain white space.
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 {
// Skip whitespace.
let _n = iter.peeking_take_while(|c| c.is_whitespace()).count();
// Look ahead.
if let Some(c) = iter.peeking_next(|_| true) {
debug_assert!(!c.is_whitespace());
match c {
'#' => {
// Skip comments.
iter.peeking_take_while(|&c| c != '\n' && c != '\r').count();
}
'"' | '\'' => {
// Quoted string.
let quote_char = c;
let mut prev = None;
while let Some(c) = iter.next() {
if prev != Some('\\') && c == quote_char {
// Abort on quote char.
break;
}
buffer.push(c);
prev = Some(c);
}
return Some(buffer.as_str());
}
_ => {
// Normal token.
let mut prev = Some(c);
buffer.push(c);
while let Some(c) = iter.next() {
if prev != Some('\\') && c.is_whitespace() {
// Abort on unmasked whitespace.
break;
}
buffer.push(c);
prev = Some(c);
}
return Some(buffer.as_str());
}
}
} else {
return None;
}
}
}
/// Read simple tokens and skip comments.
#[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());
}
/// Provide sequential access to tokens that are created on the fly by
/// splitting characters at whitespace.
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, LefDefError> {
let s = self.current_token();
self.advance();
if let Some(s) = s {
Ok(s)
} else {
Err(LefDefError::UnexpectedEndOfFile)
}
}
pub fn take_and_parse<F: FromStr>(&mut self) -> Result<F, LefDefError> {
let result = if let Some(s) = self.current_token_str() {
if let Ok(parsed) = s.parse::<F>() {
Ok(parsed)
} else {
Err(LefDefError::InvalidLiteral(s.to_string()))
}
} else {
Err(LefDefError::UnexpectedEndOfFile)
};
self.advance();
result
}
/// Advance to the next token.
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())
}
/// Test if the current token equals to the expected token.
/// Returns `Ok(())` if the token matches and advances the iterator.
/// Returns the actual token otherwise.
pub fn expect(&mut self, s: &str) -> Result<(), LefDefError> {
if self.current_token.is_none() {
Err(LefDefError::UnexpectedEndOfFile)?;
}
if self.current_token_str() == Some(s) {
self.advance();
Ok(())
} else {
Err(LefDefError::UnexpectedToken(
s.to_string(), self.current_token().unwrap().to_string(),
))
}
}
/// Test if the current token matches with the string.
/// The token is consumed only if it matches.
pub fn test(&mut self, s: &str) -> Result<bool, LefDefError> {
let result = self.peeking_test(s)?;
if result {
self.advance();
}
Ok(result)
}
/// Test if the current token matches with the string.
/// The token is not consumed.
pub fn peeking_test(&mut self, s: &str) -> Result<bool, LefDefError> {
if self.current_token.is_none() {
Err(LefDefError::UnexpectedEndOfFile)?;
}
if self.current_token_str() == Some(s) {
Ok(true)
} else {
Ok(false)
}
}
/// Consume all tokens until and including `s`.
pub fn skip_until(&mut self, s: &str) -> Result<(), LefDefError> {
while !self.test(s)? {
self.advance()
}
Ok(())
}
}
/// Split a stream of characters into tokens separated by whitespace.
/// Comments are ignored.
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"));
}