mod builder;
use anyhow::{anyhow, Result};
use std::cell::Cell;
use crate::{
ast::{LexToken, LexTokenKind},
internal::escape_string,
Key, Token, TryIntoWith,
};
pub use builder::{ParserBuilder, ParserOptions};
#[derive(Debug, Clone)]
pub struct Parser(pub(crate) ParserOptions);
impl Parser {
#[inline]
pub fn new() -> Self {
ParserBuilder::new().build()
}
#[inline]
pub fn new_with_options(options: ParserOptions) -> Self {
Self(options)
}
pub fn parse_str(&self, input: impl AsRef<str>) -> Result<Vec<Token>> {
input.as_ref().try_into_with(&self.0)
}
}
impl Default for Parser {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[inline]
fn lexer(input: &str) -> Result<Vec<LexToken<'_>>> {
use LexTokenKind::*;
let mut tokens = vec![];
let mut i = 0;
let char_vec: Vec<_> = input.chars().collect();
while i < char_vec.len() {
let char = char_vec[i];
if matches!(char, '*' | '+' | '?') {
tokens.push(LexToken {
kind: Modifier,
index: i,
value: &input[i..i + 1],
});
i += 1;
continue;
}
if char == '\\' {
tokens.push(LexToken {
kind: EscapedChar,
index: i,
value: &input[i + 1..i + 2],
});
i += 2;
continue;
}
if char == '{' {
tokens.push(LexToken {
kind: Open,
index: i,
value: &input[i..i + 1],
});
i += 1;
continue;
}
if char == '}' {
tokens.push(LexToken {
kind: Close,
index: i,
value: &input[i..i + 1],
});
i += 1;
continue;
}
if char == ':' {
let mut j = i + 1;
while j < input.len() {
if matches!(char_vec[j],'0'..='9' | 'A'..='Z' | 'a'..='z' |'_') {
j += 1;
continue;
}
break;
}
let name = &input[i + 1..j];
if name.is_empty() {
return Err(anyhow!("Missing parameter name at {i}"));
}
tokens.push(LexToken {
kind: Name,
index: i,
value: name,
});
i = j;
continue;
}
if char == '(' {
let mut count = 1;
let mut pattern = "";
let mut j = i + 1;
if char_vec[j] == '?' {
return Err(anyhow!("Pattern cannot start with \"?\" at {j}"));
}
while j < input.len() {
let char = char_vec[j];
if char == '\\' {
j += 2;
continue;
}
if char == ')' {
count -= 1;
if count == 0 {
j += 1;
break;
}
} else if char == '(' {
count += 1;
let it = char_vec.get(j + 1);
if it.is_none() || matches!(it, Some(&x) if x != '?') {
return Err(anyhow!("Capturing groups are not allowed at {j}"));
}
}
pattern = &input[i + 1..j + 1];
j += 1;
}
if count > 0 {
return Err(anyhow!("Unbalanced pattern at {i}"));
}
if pattern.is_empty() {
return Err(anyhow!("Missing pattern at {i}"));
}
tokens.push(LexToken {
kind: Pattern,
index: i,
value: pattern,
});
i = j;
continue;
}
tokens.push(LexToken {
kind: Char,
index: i,
value: &input[i..i + 1],
});
i += 1;
}
tokens.push(LexToken {
kind: End,
index: i,
value: "",
});
Ok(tokens)
}
#[inline]
pub(crate) fn parse_str_with_options(
input: impl AsRef<str>,
options: &ParserOptions,
) -> Result<Vec<Token>> {
let ParserOptions {
delimiter,
prefixes,
} = options;
use LexTokenKind::*;
let input = input.as_ref();
let tokens = lexer(input)?;
let mut result = vec![];
let default_pattern = format!("[^{}]+?", escape_string(delimiter));
let mut key: usize = 0;
let i: Cell<usize> = Cell::new(0);
let mut path = String::new();
let try_consume = |ty: LexTokenKind| {
if i.get() < tokens.len() && tokens[i.get()].kind == ty {
let value = tokens[i.get()].value;
i.set(i.get() + 1);
return Some(value);
}
None
};
let must_consume = |ty: LexTokenKind| {
let value = try_consume(ty);
if value.is_some() {
return Ok(value);
}
let LexToken { kind, index, .. } = &tokens[i.get()];
Err(anyhow!("Unexpected {kind} at {index}, expected {ty}"))
};
let consume_text = || {
let mut result = String::new();
while let Some(t) = try_consume(Char).or_else(|| try_consume(EscapedChar)) {
result += t;
}
result
};
while i.get() < tokens.len() {
let char = try_consume(Char);
let name = try_consume(Name);
let pattern = try_consume(Pattern);
if name.or(pattern).is_some() {
let mut prefix = char.unwrap_or_default();
if !prefixes.contains(prefix) {
path += prefix;
prefix = ""
}
if !path.is_empty() {
result.push(Token::Static(path));
path = String::new();
}
result.push(Token::Key(Key {
name: name.map_or_else(
|| {
let k = key;
key += 1;
k.to_string()
},
|x| x.to_owned(),
),
prefix: prefix.to_owned(),
suffix: String::new(),
pattern: pattern.map_or_else(|| default_pattern.clone(), |x| x.to_owned()),
modifier: try_consume(Modifier).unwrap_or_default().to_owned(),
}));
continue;
}
let value = char.or_else(|| try_consume(EscapedChar));
if let Some(value) = value {
path += value;
continue;
}
if !path.is_empty() {
result.push(Token::Static(path));
path = String::new();
}
let open = try_consume(Open);
if open.is_some() {
let prefix = consume_text();
let name = try_consume(Name);
let pattern = try_consume(Pattern);
let suffix = consume_text();
must_consume(Close)?;
result.push(Token::Key(Key {
name: name.map_or_else(
|| {
if pattern.is_some() {
let k = key;
key += 1;
k.to_string()
} else {
Default::default()
}
},
|x| x.to_owned(),
),
pattern: if name.is_some() && pattern.is_none() {
default_pattern.clone()
} else {
pattern.unwrap_or_default().to_owned()
},
prefix,
suffix,
modifier: try_consume(Modifier).unwrap_or_default().to_owned(),
}));
continue;
}
must_consume(End)?;
}
Ok(result)
}