lunar-lib 0.1.0

Common utilities for lunar applications
Documentation
use crate::formatter::{
    ErrorKind, FormatError, FormatTable, Render,
    block::{Block, BlockBuilder, BlockMode},
    condition::ConditionalToken,
    lexer::Lex,
    tag::Tag,
};

/// Ready-to-render arguments for [`crate::formatter::format()`]
#[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(())
    }
}