Skip to main content

lunar_lib/formatter/
format_args.rs

1use crate::formatter::{
2    ErrorKind, FormatError, FormatTable, Render,
3    block::{Block, BlockBuilder, BlockMode},
4    condition::ConditionalToken,
5    lexer::Lex,
6    tag::Tag,
7};
8
9/// Ready-to-render arguments for [`crate::formatter::format()`]
10#[derive(Debug, Clone)]
11pub struct Arguments<'a> {
12    args: Vec<FormatArg<'a>>,
13}
14
15impl<'a> Arguments<'a> {
16    pub(super) fn from_lex(
17        lexes: impl IntoIterator<Item = Lex<'a>>,
18    ) -> Result<Arguments<'a>, FormatError> {
19        let mut args = Vec::new();
20        let mut escaped = false;
21
22        let mut stack: Vec<ParserEntry<'_>> = Vec::new();
23
24        for lex in lexes.into_iter() {
25            if escaped {
26                push_arg(&mut stack, &mut args, FormatArg::Text(lex.to_str()));
27                escaped = false;
28            } else {
29                match lex {
30                Lex::BlockStart => stack.push(ParserEntry::Block(BlockBuilder::default())),
31                Lex::BlockEnd => end_block(&mut stack, &mut args)?,
32                Lex::Variable => {
33                    end_tag(&mut stack, &mut args);
34                    stack.push(ParserEntry::Tag(Tag::default()));
35                }
36                Lex::Conditional | Lex::Prefix | Lex::Suffix | Lex::Fallback => block_mode_switch(
37                    &mut stack,
38                    &mut args,
39                    lex.to_block_mode()
40                        .expect("The lex was already matched on block modes. This should not fail"),
41                )?,
42                Lex::Or | Lex::And | Lex::Not => push_conditional_token(
43                    &mut stack,
44                    &mut args,
45                    lex.to_condition_token().expect(
46                        "The lex was already matched on conditional tokens. This should not fail",
47                    ),
48                ),
49                Lex::Space => {
50                    end_tag(&mut stack, &mut args);
51                    push_arg(&mut stack, &mut args, FormatArg::Text(lex.to_str()));
52                }
53                Lex::Escape => escaped = true,
54                Lex::Text(str) => push_arg(&mut stack, &mut args, FormatArg::Text(str)),
55            }
56            }
57        }
58
59        end_tag(&mut stack, &mut args);
60
61        if !stack.is_empty() {
62            return Err(FormatError::new(ErrorKind::BadClosure(
63                "Completed parsing all lexes, but the stack was not empty. This means a tag or block was left unclosed",
64            )));
65        }
66
67        Ok(Arguments { args })
68    }
69}
70
71impl<'a> Render for Arguments<'a> {
72    fn render(&self, format_table: &FormatTable) -> String {
73        self.args.render(format_table)
74    }
75}
76
77#[derive(Debug, Clone)]
78pub(super) enum FormatArg<'a> {
79    Text(&'a str),
80    Tag(Tag<'a>),
81    Block(Block<'a>),
82}
83
84impl<'a> Render for FormatArg<'a> {
85    fn render(&self, format_table: &super::FormatTable) -> String {
86        match self {
87            FormatArg::Text(str) => str.to_string(),
88            FormatArg::Tag(tag) => tag.render(format_table),
89            FormatArg::Block(block) => block.render(format_table),
90        }
91    }
92}
93
94impl<'a> Render for [FormatArg<'a>] {
95    fn render(&self, format_table: &FormatTable) -> String {
96        self.iter().map(|arg| arg.render(format_table)).collect()
97    }
98}
99
100enum ParserEntry<'a> {
101    Tag(Tag<'a>),
102    Block(BlockBuilder<'a>),
103}
104
105#[inline(always)]
106fn push_arg<'a>(
107    stack: &mut Vec<ParserEntry<'a>>,
108    args: &mut Vec<FormatArg<'a>>,
109    arg: FormatArg<'a>,
110) {
111    match stack.last_mut() {
112        Some(ParserEntry::Block(block_builder)) => {
113            block_builder.push_arg(arg);
114        }
115        Some(ParserEntry::Tag(tag)) => tag.args.push(arg),
116        None => args.push(arg),
117    }
118}
119
120fn push_conditional_token<'a>(
121    stack: &mut Vec<ParserEntry<'a>>,
122    args: &mut Vec<FormatArg<'a>>,
123    token: ConditionalToken<'a>,
124) {
125    if let Some(ParserEntry::Block(block)) = stack.last_mut() {
126        block.push_conditional_token(token);
127    } else {
128        push_arg(stack, args, FormatArg::Text(token.to_str()));
129    }
130}
131
132fn end_tag<'a>(stack: &mut Vec<ParserEntry<'a>>, args: &mut Vec<FormatArg<'a>>) {
133    if let Some(ParserEntry::Tag(_)) = stack.last() {
134        let tag = match stack.pop().unwrap() {
135            ParserEntry::Tag(tag) => tag,
136            _ => unreachable!(),
137        };
138
139        push_arg(stack, args, FormatArg::Tag(tag));
140    }
141}
142
143fn end_tag_if_in_block<'a>(stack: &mut Vec<ParserEntry<'a>>, args: &mut Vec<FormatArg<'a>>) {
144    if let Some(ParserEntry::Tag(_)) = stack.last()
145        && let Some(ParserEntry::Block(_)) = stack.get(stack.len() - 2)
146    {
147        let tag = match stack.pop().unwrap() {
148            ParserEntry::Tag(tag) => tag,
149            _ => unreachable!(),
150        };
151        push_arg(stack, args, FormatArg::Tag(tag));
152    }
153}
154
155fn end_block<'a>(
156    stack: &mut Vec<ParserEntry<'a>>,
157    args: &mut Vec<FormatArg<'a>>,
158) -> Result<(), FormatError> {
159    end_tag(stack, args);
160
161    let block = match stack.pop() {
162        Some(ParserEntry::Block(block)) => block,
163        Some(_) => {
164            panic!("'}}' was found unescaped but it didn't close a block");
165        }
166        None => {
167            return Err(FormatError::new(ErrorKind::BadClosure(
168                "'}' was found unescaped with nothing to close",
169            )));
170        }
171    };
172
173    push_arg(stack, args, FormatArg::Block(block.build()));
174
175    Ok(())
176}
177
178fn block_mode_switch<'a>(
179    stack: &mut Vec<ParserEntry<'a>>,
180    args: &mut Vec<FormatArg<'a>>,
181    mode: BlockMode,
182) -> Result<(), FormatError> {
183    end_tag_if_in_block(stack, args);
184
185    if let Some(ParserEntry::Block(block)) = stack.last_mut() {
186        block.set_mode(mode)
187    } else {
188        args.push(FormatArg::Text(mode.to_str()));
189        Ok(())
190    }
191}