use crate::{
ParseError,
errors::err,
parser::tokenizer::{Pos, Token},
};
pub trait StrExt {
fn char_at(&self, ind: usize) -> Option<char>;
fn find_after(&self, pat: char, ind: usize) -> Option<usize>;
fn find_str_after(&self, pat: &str, ind: usize) -> Option<usize>;
fn find_ws_after(&self, ind: usize) -> Option<usize>;
}
impl StrExt for str {
fn char_at(&self, ind: usize) -> Option<char> {
self.as_bytes().get(ind).map(|b| *b as char)
}
fn find_after(&self, pat: char, ind: usize) -> Option<usize> {
self[ind..].find(pat).map(|i| ind + i)
}
fn find_ws_after(&self, ind: usize) -> Option<usize> {
self[ind..].find([' ', '\t', '\r', '\n']).map(|i| ind + i)
}
fn find_str_after(&self, pat: &str, ind: usize) -> Option<usize> {
self[ind..].find(pat).map(|i| ind + i)
}
}
pub fn is_hex(char: char) -> bool {
matches!(char, '0'..='9' | 'a'..='f' | 'A'..='F')
}
pub fn count_prefix(source: &str, search: &str) -> usize {
let mut count = 0;
let mut ind = 0;
while source.get(ind..ind + search.len()) == Some(search) {
count += 1;
ind += search.len();
}
count
}
pub fn remove_n_suffix<'a>(source: &'a str, sep: &str, count: usize) -> &'a str {
let mut ind = source.len();
for _ in 0..count {
match source[..ind].rfind(sep) {
Some(i) => ind = i,
None => return "",
}
}
&source[..ind]
}
pub fn while_matching(source: &str, ind: usize, pred: fn(char) -> bool) -> usize {
match source.get(ind..).unwrap_or("").find(|c| !pred(c)) {
Some(i) => ind + i,
_ => source.len(),
}
}
pub fn all_matching(source: &str, pred: fn(char) -> bool) -> bool {
source.bytes().all(|c| pred(c as char))
}
pub fn consume_ident<'a>(
tokens: &'a [Token], ind: &mut usize, file: &str,
) -> Result<&'a str, ParseError> {
match tokens.get(*ind) {
Some(Token::Ident(ident, _)) => (Ok(*ident), *ind += 1).0,
Some(Token::Eof(_)) | None => end_of_input(file),
Some(token) => unexpected_token(token, token.pos(), file),
}
}
pub fn try_consume_ident(
ident: &str, tokens: &[Token], ind: &mut usize, _file: &str,
) -> Result<bool, ParseError> {
match tokens.get(*ind) {
Some(Token::Ident(id, _)) if *id == ident => (Ok(true), *ind += 1).0,
_ => Ok(false),
}
}
pub fn consume_str<'a>(
tokens: &'a [Token], ind: &mut usize, file: &str,
) -> Result<&'a str, ParseError> {
match tokens.get(*ind) {
Some(Token::Str(str, _)) => (Ok(&str[..]), *ind += 1).0,
Some(Token::Eof(_)) | None => end_of_input(file),
Some(token) => unexpected_token(token, token.pos(), file),
}
}
pub fn consume_symbol(
token: char, tokens: &[Token], ind: &mut usize, file: &str,
) -> Result<(), ParseError> {
match tokens.get(*ind) {
Some(Token::Symbol(sym, _)) if *sym == token => (Ok(()), *ind += 1).0,
Some(Token::Eof(_)) | None => end_of_input(file),
Some(token) => unexpected_token(token, token.pos(), file),
}
}
pub fn try_consume_symbol(
token: char, tokens: &[Token], ind: &mut usize, _file: &str,
) -> Result<bool, ParseError> {
match tokens.get(*ind) {
Some(Token::Symbol(sym, _)) if *sym == token => (Ok(true), *ind += 1).0,
_ => Ok(false),
}
}
pub fn consume_uint(tokens: &[Token], ind: &mut usize, file: &str) -> Result<u64, ParseError> {
match tokens.get(*ind) {
Some(Token::Uint(nb, _)) => (Ok(*nb), *ind += 1).0,
Some(Token::Int(nb, _)) if (*nb >= 0) => (Ok(*nb as u64), *ind += 1).0,
Some(Token::Eof(_)) | None => end_of_input(file),
Some(token) => unexpected_token(token, token.pos(), file),
}
}
macro_rules! parse_struct_like {
($args:expr, $file:expr, $ind:ident => $eacher:block) => {
let (tokens, start_char, end_char) = $args;
consume_symbol(start_char, tokens, $ind, $file)?;
if try_consume_symbol(end_char, tokens, $ind, $file)? {
} else {
loop {
$eacher;
let has_comma = try_consume_symbol(',', tokens, $ind, $file)?;
if try_consume_symbol(end_char, tokens, $ind, $file)? {
break;
}
if !has_comma {
return unexpected_token(&tokens[*$ind], tokens[*$ind].pos(), $file);
}
}
}
};
}
pub(crate) use parse_struct_like;
pub fn unexpected_token<T>(token: impl ToString, pos: Pos, file: &str) -> Result<T, ParseError> {
err!(format!("unexpected token \"{}\"", token.to_string()), pos, file)
}
pub fn end_of_input<T>(file: &str) -> Result<T, ParseError> {
err!("end of input".to_string(), file)
}