use super::string_lit::{is_string_delim, raw_string_end};
use std::borrow::Cow;
#[derive(Debug, Clone, PartialEq)]
pub(super) enum Token {
Ident(String),
Quoted(String),
ArrayLiteral(String),
SubExpr(String),
Pipe,
Space(String),
Other(String),
}
pub(super) fn tokenize_block(inner: &str) -> Vec<Token> {
let mut tokens = Vec::new();
let bytes = inner.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i].is_ascii_whitespace() {
let start = i;
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
tokens.push(Token::Space(inner[start..i].to_string()));
continue;
}
if is_string_delim(bytes[i]) {
let start = i;
i = raw_string_end(bytes, i);
tokens.push(Token::Quoted(inner[start..i].to_string()));
continue;
}
if bytes[i] == b'[' {
let start = i;
let mut depth = 1;
i += 1;
while i < bytes.len() && depth > 0 {
if bytes[i] == b'[' {
depth += 1;
} else if bytes[i] == b']' {
depth -= 1;
} else if is_string_delim(bytes[i]) {
i = raw_string_end(bytes, i);
continue;
}
i += 1;
}
tokens.push(Token::ArrayLiteral(inner[start..i].to_string()));
continue;
}
if bytes[i] == b'(' {
if !matches!(tokens.last(), Some(Token::Ident(_)))
&& let Some(end) = balanced_paren_end(bytes, i)
{
tokens.push(Token::SubExpr(inner[i..end].to_string()));
i = end;
continue;
}
tokens.push(Token::Other("(".to_string()));
i += 1;
continue;
}
if bytes[i] == b'|' {
tokens.push(Token::Pipe);
i += 1;
continue;
}
if bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_' {
let start = i;
while i < bytes.len()
&& (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_' || bytes[i] == b'.')
{
i += 1;
}
tokens.push(Token::Ident(inner[start..i].to_string()));
continue;
}
let Some(ch) = inner[i..].chars().next() else {
break;
};
tokens.push(Token::Other(ch.to_string()));
i += ch.len_utf8();
}
tokens
}
pub(super) fn balanced_paren_end(bytes: &[u8], start: usize) -> Option<usize> {
let mut depth = 0usize;
let mut i = start;
while i < bytes.len() {
match bytes[i] {
b'(' => {
depth += 1;
i += 1;
}
b')' => {
depth = depth.checked_sub(1)?;
i += 1;
if depth == 0 {
return Some(i);
}
}
b if is_string_delim(b) => i = raw_string_end(bytes, i),
_ => i += 1,
}
}
None
}
pub(super) const MAX_EXPR_NESTING: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ParenImbalance {
Unclosed(usize),
UnmatchedClose,
UnterminatedString,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct ParenScan {
pub imbalance: Option<ParenImbalance>,
pub max_depth: usize,
}
pub(super) fn scan_parens(inner: &str) -> ParenScan {
let bytes = inner.as_bytes();
let mut depth = 0usize;
let mut max_depth = 0usize;
let mut i = 0;
let imbalance = loop {
if i >= bytes.len() {
break (depth > 0).then_some(ParenImbalance::Unclosed(depth));
}
match bytes[i] {
b'(' => {
depth += 1;
max_depth = max_depth.max(depth);
i += 1;
}
b')' => match depth.checked_sub(1) {
Some(next) => {
depth = next;
i += 1;
}
None => break Some(ParenImbalance::UnmatchedClose),
},
delim if is_string_delim(delim) => {
let end = raw_string_end(bytes, i);
if end < i + 2 || bytes[end - 1] != delim {
break Some(ParenImbalance::UnterminatedString);
}
i = end;
}
_ => i += 1,
}
};
ParenScan {
imbalance,
max_depth,
}
}
pub(super) fn significant_tokens(tokens: &[Token]) -> Vec<&Token> {
tokens
.iter()
.filter(|t| !matches!(t, Token::Space(_)))
.collect()
}
pub(super) fn token_to_str(token: &Token) -> Cow<'_, str> {
match token {
Token::Ident(s)
| Token::Quoted(s)
| Token::ArrayLiteral(s)
| Token::SubExpr(s)
| Token::Space(s)
| Token::Other(s) => Cow::Borrowed(s.as_str()),
Token::Pipe => Cow::Borrowed("|"),
}
}