use crate::formatter::{
ErrorKind, FormatError, FormatTable, Render,
block::{Block, BlockBuilder, BlockMode},
condition::ConditionalToken,
lexer::Lex,
tag::Tag,
};
#[derive(Debug, Clone)]
pub struct Arguments<'a> {
args: Vec<FormatArg<'a>>,
}
impl<'a> Arguments<'a> {
pub(super) fn from_lex(
lexes: impl IntoIterator<Item = Lex<'a>>,
) -> Result<Arguments<'a>, FormatError> {
let mut args = Vec::new();
let mut escaped = false;
let mut stack: Vec<ParserEntry<'_>> = Vec::new();
for lex in lexes.into_iter() {
if escaped {
push_arg(&mut stack, &mut args, FormatArg::Text(lex.to_str()));
escaped = false;
} else {
match lex {
Lex::BlockStart => stack.push(ParserEntry::Block(BlockBuilder::default())),
Lex::BlockEnd => end_block(&mut stack, &mut args)?,
Lex::Variable => {
end_tag(&mut stack, &mut args);
stack.push(ParserEntry::Tag(Tag::default()));
}
Lex::Conditional | Lex::Prefix | Lex::Suffix | Lex::Fallback => block_mode_switch(
&mut stack,
&mut args,
lex.to_block_mode()
.expect("The lex was already matched on block modes. This should not fail"),
)?,
Lex::Or | Lex::And | Lex::Not => push_conditional_token(
&mut stack,
&mut args,
lex.to_condition_token().expect(
"The lex was already matched on conditional tokens. This should not fail",
),
),
Lex::Space => {
end_tag(&mut stack, &mut args);
push_arg(&mut stack, &mut args, FormatArg::Text(lex.to_str()));
}
Lex::Escape => escaped = true,
Lex::Text(str) => push_arg(&mut stack, &mut args, FormatArg::Text(str)),
}
}
}
end_tag(&mut stack, &mut args);
if !stack.is_empty() {
return Err(FormatError::new(ErrorKind::BadClosure(
"Completed parsing all lexes, but the stack was not empty. This means a tag or block was left unclosed",
)));
}
Ok(Arguments { args })
}
}
impl<'a> Render for Arguments<'a> {
fn render(&self, format_table: &FormatTable) -> String {
self.args.render(format_table)
}
}
#[derive(Debug, Clone)]
pub(super) enum FormatArg<'a> {
Text(&'a str),
Tag(Tag<'a>),
Block(Block<'a>),
}
impl<'a> Render for FormatArg<'a> {
fn render(&self, format_table: &super::FormatTable) -> String {
match self {
FormatArg::Text(str) => str.to_string(),
FormatArg::Tag(tag) => tag.render(format_table),
FormatArg::Block(block) => block.render(format_table),
}
}
}
impl<'a> Render for [FormatArg<'a>] {
fn render(&self, format_table: &FormatTable) -> String {
self.iter().map(|arg| arg.render(format_table)).collect()
}
}
enum ParserEntry<'a> {
Tag(Tag<'a>),
Block(BlockBuilder<'a>),
}
#[inline(always)]
fn push_arg<'a>(
stack: &mut Vec<ParserEntry<'a>>,
args: &mut Vec<FormatArg<'a>>,
arg: FormatArg<'a>,
) {
match stack.last_mut() {
Some(ParserEntry::Block(block_builder)) => {
block_builder.push_arg(arg);
}
Some(ParserEntry::Tag(tag)) => tag.args.push(arg),
None => args.push(arg),
}
}
fn push_conditional_token<'a>(
stack: &mut Vec<ParserEntry<'a>>,
args: &mut Vec<FormatArg<'a>>,
token: ConditionalToken<'a>,
) {
if let Some(ParserEntry::Block(block)) = stack.last_mut() {
block.push_conditional_token(token);
} else {
push_arg(stack, args, FormatArg::Text(token.to_str()));
}
}
fn end_tag<'a>(stack: &mut Vec<ParserEntry<'a>>, args: &mut Vec<FormatArg<'a>>) {
if let Some(ParserEntry::Tag(_)) = stack.last() {
let tag = match stack.pop().unwrap() {
ParserEntry::Tag(tag) => tag,
_ => unreachable!(),
};
push_arg(stack, args, FormatArg::Tag(tag));
}
}
fn end_tag_if_in_block<'a>(stack: &mut Vec<ParserEntry<'a>>, args: &mut Vec<FormatArg<'a>>) {
if let Some(ParserEntry::Tag(_)) = stack.last()
&& let Some(ParserEntry::Block(_)) = stack.get(stack.len() - 2)
{
let tag = match stack.pop().unwrap() {
ParserEntry::Tag(tag) => tag,
_ => unreachable!(),
};
push_arg(stack, args, FormatArg::Tag(tag));
}
}
fn end_block<'a>(
stack: &mut Vec<ParserEntry<'a>>,
args: &mut Vec<FormatArg<'a>>,
) -> Result<(), FormatError> {
end_tag(stack, args);
let block = match stack.pop() {
Some(ParserEntry::Block(block)) => block,
Some(_) => {
panic!("'}}' was found unescaped but it didn't close a block");
}
None => {
return Err(FormatError::new(ErrorKind::BadClosure(
"'}' was found unescaped with nothing to close",
)));
}
};
push_arg(stack, args, FormatArg::Block(block.build()));
Ok(())
}
fn block_mode_switch<'a>(
stack: &mut Vec<ParserEntry<'a>>,
args: &mut Vec<FormatArg<'a>>,
mode: BlockMode,
) -> Result<(), FormatError> {
end_tag_if_in_block(stack, args);
if let Some(ParserEntry::Block(block)) = stack.last_mut() {
block.set_mode(mode)
} else {
args.push(FormatArg::Text(mode.to_str()));
Ok(())
}
}