blob: 9e117499a77b3a356e73749557029fad16d9524e [file]
/*++
Licensed under the Apache-2.0 license.
--*/
use std::str::{Chars, FromStr};
use crate::{token::Token, Bits};
pub type Span = std::ops::Range<usize>;
pub struct Lexer<'a> {
start_ptr: *const u8,
token_start_ptr: *const u8,
iter: std::str::Chars<'a>,
}
impl<'a> Lexer<'a> {
pub fn new(s: &'a str) -> Self {
Self {
start_ptr: s.as_bytes().as_ptr(),
token_start_ptr: s.as_bytes().as_ptr(),
iter: s.chars(),
}
}
pub fn span(&self) -> Span {
Span {
start: self.token_start_ptr as usize - self.start_ptr as usize,
end: self.iter.as_str().as_ptr() as usize - self.start_ptr as usize,
}
}
}
impl<'a> Iterator for Lexer<'a> {
type Item = Token<'a>;
fn next(&mut self) -> Option<Token<'a>> {
let mut iter = self.iter.clone();
loop {
let result = match iter.next() {
Some(' ' | '\t' | '\n' | '\r') => Some(Token::Skip),
Some('/') => {
match iter.next() {
Some('*') => {
// skip comments
loop {
match iter.next() {
Some('*') => match iter.next() {
Some('/') => break Some(Token::Skip),
Some(_) => continue,
None => break Some(Token::Error),
},
Some(_) => continue,
None => break Some(Token::Error),
}
}
}
Some('/') => loop {
match iter.next() {
Some('\n') => break Some(Token::Skip),
Some(_) => continue,
None => break None,
}
},
_ => Some(Token::Error),
}
}
Some('"') => loop {
match iter.next() {
Some('"') => {
break Some(Token::StringLiteral(str_between(&self.iter, &iter)))
}
Some('\\') => match iter.next() {
Some(_) => continue,
None => break Some(Token::Error),
},
Some(_) => continue,
None => break Some(Token::Error),
}
},
Some(ch) if ch.is_ascii_alphabetic() => {
next_while(&mut iter, |ch| ch.is_ascii_alphanumeric() || ch == '_');
match str_between(&self.iter, &iter) {
"field" => Some(Token::Field),
"reg" => Some(Token::Reg),
"regfile" => Some(Token::RegFile),
"addrmap" => Some(Token::AddrMap),
"signal" => Some(Token::Signal),
"enum" => Some(Token::Enum),
"mem" => Some(Token::Mem),
"constraint" => Some(Token::Constraint),
ident => Some(Token::Identifier(ident)),
}
}
Some(ch) if ch.is_ascii_digit() => {
if ch == '0' && iter.peek() == Some('x') {
iter.next();
let num_start = iter.clone();
next_while(&mut iter, |ch| ch.is_ascii_hexdigit() || ch == '_');
Some(parse_num(str_between(&num_start, &iter), 16))
} else {
next_while(&mut iter, |ch| ch.is_ascii_digit() || ch == '_');
let mut peek = iter.clone();
if let Some('\'') = peek.next() {
iter = peek;
next_while(&mut iter, |ch| {
ch == 'b' || ch == 'o' || ch == 'd' || ch == 'h'
});
next_while(&mut iter, |ch| ch.is_ascii_hexdigit() || ch == '_');
match Bits::from_str(str_between(&self.iter, &iter)) {
Ok(bits) => Some(Token::Bits(bits)),
Err(_) => Some(Token::Error),
}
} else {
Some(parse_num(str_between(&self.iter, &iter), 10))
}
}
}
Some('{') => Some(Token::BraceOpen),
Some('}') => Some(Token::BraceClose),
Some('[') => Some(Token::BracketOpen),
Some(']') => Some(Token::BracketClose),
Some('(') => Some(Token::ParenOpen),
Some(')') => Some(Token::ParenClose),
Some(';') => Some(Token::Semicolon),
Some(',') => Some(Token::Comma),
Some('.') => Some(Token::Period),
Some('=') => Some(Token::Equals),
Some('@') => Some(Token::At),
Some('#') => Some(Token::Hash),
Some(':') => Some(Token::Colon),
Some('`') => {
let keyword_start = iter.clone();
next_while(&mut iter, |ch| ch.is_ascii_alphabetic() || ch == '_');
match str_between(&keyword_start, &iter) {
"include" => Some(Token::PreprocInclude),
_ => Some(Token::Error),
}
}
Some('+') => match iter.next() {
Some('=') => Some(Token::PlusEqual),
_ => return Some(Token::Error),
},
Some('%') => match iter.next() {
Some('=') => Some(Token::PercentEqual),
_ => Some(Token::Error),
},
Some('-') => match iter.next() {
Some('>') => Some(Token::Pointer),
_ => Some(Token::Error),
},
None => None,
_ => Some(Token::Error),
};
match result {
Some(Token::Skip) => {
self.iter = iter.clone();
continue;
}
Some(token) => {
self.token_start_ptr = self.iter.as_str().as_ptr();
self.iter = iter;
return Some(token);
}
None => return None,
}
}
}
}
fn next_while(iter: &mut Chars, mut f: impl FnMut(char) -> bool) {
loop {
let mut peek = iter.clone();
if let Some(ch) = peek.next() {
if f(ch) {
*iter = peek;
continue;
} else {
break;
}
} else {
break;
}
}
}
fn parse_num(s: &str, radix: u32) -> Token {
let replaced;
let s = if s.contains('_') {
replaced = s.replace('_', "");
&replaced
} else {
s
};
if let Ok(val) = u64::from_str_radix(s, radix) {
Token::Number(val)
} else {
Token::Error
}
}
trait PeekableChar {
fn peek(&self) -> Option<char>;
}
impl PeekableChar for std::str::Chars<'_> {
fn peek(&self) -> Option<char> {
self.clone().next()
}
}
fn str_between<'a>(start: &Chars<'a>, end: &Chars<'a>) -> &'a str {
let first_ptr = start.as_str().as_ptr();
let second_ptr = end.as_str().as_ptr();
&start.as_str()[0..second_ptr as usize - first_ptr as usize]
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_foo() {
let tokens: Vec<Token> = Lexer::new("field 35\tiDentifier2_ 0x24\n\r 0xf00_bad 2'b01 5'o27 4'd9 16'h1caf /* ignore comment */ %= // line comment\n += \"string 1\" \"string\\\"2\" {}[]();:,.=@#reg field regfile addrmap signal enum mem constraint").take(35).collect();
assert_eq!(
tokens,
vec![
Token::Field,
Token::Number(35),
Token::Identifier("iDentifier2_"),
Token::Number(0x24),
Token::Number(0xf00bad),
Token::Bits(Bits::new(2, 1)),
Token::Bits(Bits::new(5, 0o27)),
Token::Bits(Bits::new(4, 9)),
Token::Bits(Bits::new(16, 0x1caf)),
Token::PercentEqual,
Token::PlusEqual,
Token::StringLiteral("\"string 1\""),
Token::StringLiteral("\"string\\\"2\""),
Token::BraceOpen,
Token::BraceClose,
Token::BracketOpen,
Token::BracketClose,
Token::ParenOpen,
Token::ParenClose,
Token::Semicolon,
Token::Colon,
Token::Comma,
Token::Period,
Token::Equals,
Token::At,
Token::Hash,
Token::Reg,
Token::Field,
Token::RegFile,
Token::AddrMap,
Token::Signal,
Token::Enum,
Token::Mem,
Token::Constraint,
]
);
}
}