use arrayvec::ArrayVec;
use logos::Logos;
use super::{hash_name, Block, Error, Template};
use crate::Partials;
#[derive(Debug, PartialEq, Clone, Copy, Default)]
pub struct ParseError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Logos)]
#[logos(
skip r"[^{]+",
skip r"\{",
extras = Braces,
error = ParseError,
)]
pub enum Tag {
#[token("{{")]
Escaped,
#[token("{{&")]
#[token("{{{", |lex| lex.extras = Braces::Three)]
Unescaped,
#[token("{{#")]
Section,
#[token("{{^")]
Inverse,
#[token("{{/")]
Closing,
#[token("{{!")]
Comment,
#[token("{{>")]
Partial,
#[token("{{?")]
NotNone,
Tail,
}
impl From<ParseError> for Error {
fn from(_: ParseError) -> Error {
Error::UnclosedTag
}
}
#[derive(Logos)]
#[logos(
skip r"[. ]+",
extras = Braces,
)]
#[derive(Debug)]
enum Closing {
#[token("}}", |lex| {
// 如果开始的是 {{ 而结尾的 是 }}} ,则报错
// Force fail the match if we expected 3 braces
lex.extras != Braces::Three
})]
#[token("}}}")]
Match,
#[regex(r"[^. \}]+")]
Ident,
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Braces {
Two = 2,
Three = 3,
}
impl Default for Braces {
#[inline]
fn default() -> Self {
Braces::Two
}
}
impl<'tpl> Template<'tpl> {
pub(crate) fn parse(
&mut self,
source: &'tpl str,
partials: &mut impl Partials<'tpl>,
) -> Result<usize, Error> {
let mut last = 0;
let mut lex = Tag::lexer(source);
let mut stack = ArrayVec::<usize, 16>::new();
while let Some(tag) = lex.next() {
let tag = tag?;
let mut html = &lex.source()[last..lex.span().start];
self.capacity_hint += html.len();
let mut closing = lex.morph();
let tail_idx = self.blocks.len();
let _tok = closing.next();
if !matches!(Some(Closing::Ident), _tok) {
return Err(Error::UnclosedTag);
}
let mut name = closing.slice();
match tag {
Tag::Escaped | Tag::Unescaped => {
loop {
match closing.next() {
Some(Ok(Closing::Ident)) => {
self.blocks.push(Block::new(html, name, Tag::Section));
name = closing.slice();
html = "";
}
Some(Ok(Closing::Match)) => {
self.blocks.push(Block::new(html, name, tag));
break;
}
_ => return Err(Error::UnclosedTag),
}
}
let d = self.blocks.len() - tail_idx - 1;
for i in 0..d {
self.blocks[tail_idx + i].children = (d - i) as u32; }
}
Tag::Section | Tag::Inverse => loop {
match closing.next() {
Some(Ok(Closing::Ident)) => {
stack.try_push(self.blocks.len())?;
self.blocks.push(Block::new(html, name, Tag::Section));
name = closing.slice();
html = "";
}
Some(Ok(Closing::Match)) => {
stack.try_push(self.blocks.len())?;
self.blocks.push(Block::new(html, name, tag));
break;
}
_ => return Err(Error::UnclosedTag),
}
},
Tag::NotNone => loop {
match closing.next() {
Some(Ok(Closing::Ident)) => {
stack.try_push(self.blocks.len())?;
self.blocks.push(Block::new(html, name, Tag::NotNone));
name = closing.slice();
html = "";
}
Some(Ok(Closing::Match)) => {
stack.try_push(self.blocks.len())?;
self.blocks.push(Block::new(html, name, tag));
break;
}
_ => return Err(Error::UnclosedTag),
}
}
Tag::Closing => {
self.blocks.push(Block::nameless(html, Tag::Closing));
let mut pop_section = |name| {
let hash = hash_name(name);
let head_idx = stack
.pop()
.ok_or_else(|| Error::UnopenedSection(name.into()))?;
let head = &mut self.blocks[head_idx];
head.children = (tail_idx - head_idx) as u32;
if head.hash != hash {
return Err(Error::UnclosedSection(head.name.into()));
}
Ok(())
};
let mut tmp = vec![name];
loop {
match closing.next() {
Some(Ok(Closing::Ident)) => {
tmp.push(closing.slice());
}
Some(Ok(Closing::Match)) => break,
_ => return Err(Error::UnclosedTag),
}
}
let t_len = tmp.len();
for i in 0..t_len {
pop_section(tmp[t_len - i - 1])?;
}
}
Tag::Partial => {
match closing.next() {
Some(Ok(Closing::Match)) => {}
_ => return Err(Error::UnclosedTag),
}
self.blocks.push(Block::nameless(html, tag));
let partial = partials.get_partial(name)?;
self.blocks.extend_from_slice(&partial.blocks);
self.capacity_hint += partial.capacity_hint;
}
_ => {
loop {
match closing.next() {
Some(Ok(Closing::Ident)) => continue,
Some(Ok(Closing::Match)) => break,
_ => return Err(Error::UnclosedTag),
}
}
self.blocks.push(Block::nameless(html, tag));
}
};
last = closing.span().start + closing.extras as usize;
lex = closing.morph();
lex.extras = Braces::Two;
}
Ok(last)
}
}
#[cfg(test)]
mod tests {
use crate::Template;
#[test]
fn test() {
let s = "
{{?t1}}
{{#t1}}
{{name}}abcd
{{/t1}}
{{/t1}}
";
let _tpl = Template::new(s).unwrap();
println!("--------------")
}
}