#[cfg(feature = "debug")]
use log::trace;
mod error;
mod functions;
mod literals;
#[cfg(test)]
mod test;
pub use error::{SyntaxError, SyntaxErrorKind};
use functions::parse_function_arguments;
#[derive(Debug, PartialEq, Eq)]
pub enum TemplateNode<'a> {
Variable(&'a str),
Function(&'a str, Vec<TemplateNode<'a>>),
String(&'a str),
Number(&'a str),
Float(&'a str),
RawText(&'a str),
}
pub fn parser<'a>(
input: &'a str,
start: &'a str,
close: &'a str,
) -> Result<Vec<TemplateNode<'a>>, crate::Error> {
#[cfg(feature = "debug")]
trace!("Start Parser: {input} with delimiters: {start} - {close}");
let mut res = Vec::with_capacity(20);
let chars = input.as_bytes();
let mut position = 0usize;
while !is_eof(chars, position) {
if advance_delimiter(chars, start, &mut position) {
let var = parse_template_expression(input, chars, &mut position)?;
if !advance_delimiter(chars, close, &mut position) {
return Err(SyntaxError::found_eof(input, position, close));
}
res.push(var);
continue;
}
res.push(raw_text(input, chars, start, &mut position));
}
Ok(res)
}
fn parse_template_expression<'a>(
input: &'a str,
chars: &[u8],
position: &mut usize,
) -> Result<TemplateNode<'a>, crate::Error> {
skip_whitespace(chars, position);
let (start, name_end) = identifier(chars, position);
skip_whitespace(chars, position);
if !is_eof(chars, *position) && chars[*position] == b'(' {
advance(chars, position);
skip_whitespace(chars, position);
let args = parse_function_arguments(input, chars, position)?;
skip_whitespace(chars, position);
if !advance_delimiter(chars, ")", position) {
return Err(SyntaxErrorKind::UnterminatedArgument.into_error(input, *position));
}
skip_whitespace(chars, position);
Ok(TemplateNode::Function(&input[start..name_end], args))
} else {
Ok(TemplateNode::Variable(&input[start..name_end]))
}
}
fn identifier(chars: &[u8], position: &mut usize) -> (usize, usize) {
let start = *position;
while !is_eof(chars, *position)
&& (chars[*position].is_ascii_alphanumeric()
|| chars[*position] == b'_'
|| chars[*position] == b'.')
{
advance(chars, position);
}
(start, *position)
}
fn raw_text<'a>(
input: &'a str,
chars: &[u8],
open_delim: &str,
position: &mut usize,
) -> TemplateNode<'a> {
let start = *position;
while !is_eof(chars, *position) {
if check_delimiter(chars, open_delim, *position) {
break;
}
advance(chars, position);
}
TemplateNode::RawText(&input[start..*position])
}
fn advance(chars: &[u8], position: &mut usize) {
if !is_eof(chars, *position) {
*position += 1;
}
}
fn check_delimiter(chars: &[u8], delim: &str, position: usize) -> bool {
position + delim.len() <= chars.len()
&& &chars[position..position + delim.len()] == delim.as_bytes()
}
fn advance_delimiter(chars: &[u8], delim: &str, position: &mut usize) -> bool {
if check_delimiter(chars, delim, *position) {
if *position + delim.len() <= chars.len() {
*position += delim.len();
}
return true;
}
false
}
fn is_eof(chars: &[u8], position: usize) -> bool {
position >= chars.len()
}
fn skip_whitespace(chars: &[u8], position: &mut usize) {
while !is_eof(chars, *position) && chars[*position].is_ascii_whitespace() {
advance(chars, position);
}
}