texlang/
texmacro.rs

1//! Implementation of TeX user defined macros.
2
3use crate::error;
4use crate::parse;
5use crate::prelude as txl;
6use crate::token;
7use crate::token::Token;
8use crate::token::Value;
9use crate::traits::*;
10use crate::vm;
11use texcraft_stdext::algorithms::substringsearch::Matcher;
12use texcraft_stdext::color::Colorize;
13
14/// A TeX Macro.
15#[derive(Debug, Clone)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct Macro {
18    prefix: Vec<Token>,
19    parameters: Vec<Parameter>,
20    replacements: Vec<Replacement>,
21}
22
23impl Macro {
24    pub fn replacements(&self) -> &[Replacement] {
25        &self.replacements
26    }
27}
28
29/// A token list or parameter in a replacement text.
30#[derive(Debug, Clone)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub enum Replacement {
33    /// A list of tokens.
34    Tokens(Vec<Token>),
35
36    /// A parameter.
37    ///
38    /// In order to be valid, the parameters index must be less than the number
39    /// of parameters in the macro.
40    Parameter(usize),
41}
42
43#[derive(Debug, Clone)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45pub enum Parameter {
46    Undelimited,
47    Delimited(Matcher<Value>),
48}
49
50// Input type for macro hooks
51pub struct HookInput<'a, S> {
52    pub vm: &'a vm::VM<S>,
53    pub token: Token,
54    pub tex_macro: &'a Macro,
55    pub arguments: &'a [&'a [Token]],
56    pub reverse_expansion: &'a [Token],
57}
58
59pub fn no_op_hook<S>(_: HookInput<S>) {}
60
61impl Macro {
62    pub fn call<S: TexlangState>(
63        &self,
64        token: Token,
65        input: &mut vm::ExpansionInput<S>,
66    ) -> txl::Result<()> {
67        let prefix_matched = remove_tokens_from_stream(&self.prefix, input.unexpanded())?;
68        if !prefix_matched {
69            return Ok(());
70        }
71        let mut argument_indices: Vec<(usize, usize)> = Default::default();
72        let mut argument_tokens = input.checkout_token_buffer();
73        for (i, parameter) in self.parameters.iter().enumerate() {
74            let start_index = argument_tokens.len();
75            let trim_outer_braces = parameter.parse_argument(input, i, &mut argument_tokens)?;
76            let element = match trim_outer_braces {
77                true => (start_index + 1, argument_tokens.len() - 1),
78                false => (start_index, argument_tokens.len()),
79            };
80            argument_indices.push(element);
81        }
82
83        let mut arguments: Vec<&[Token]> = Default::default();
84        for (i, j) in &argument_indices {
85            let slice = argument_tokens.get(*i..*j).unwrap();
86            arguments.push(slice);
87        }
88
89        let result = input.expansions_mut();
90        let num_tokens = Macro::perform_replacement(&self.replacements, &arguments, result);
91
92        // To keep the borrow checker happy we need to downgrade result to a shared reference.
93        let result = input.expansions();
94        S::post_macro_expansion_hook(
95            token,
96            input,
97            self,
98            &arguments,
99            &result[result.len() - num_tokens..result.len()],
100        );
101
102        input.return_token_buffer(argument_tokens);
103        Ok(())
104    }
105
106    pub fn doc(&self, interner: &token::CsNameInterner) -> String {
107        let mut d = String::default();
108        d.push_str("User defined macro\n\n");
109        d.push_str(&format![
110            "{}\n{}",
111            "Parameters definition".italic(),
112            pretty_print_prefix_and_parameters(&self.prefix, &self.parameters, interner),
113        ]);
114        d.push_str(&format![
115            "\n\n{} `{}`\n",
116            "Replacement definition:".italic(),
117            pretty_print_replacement_text(&self.replacements),
118        ]);
119        d
120    }
121
122    /// Create a new macro.
123    pub fn new(
124        prefix: Vec<Token>,
125        parameters: Vec<Parameter>,
126        replacement_text: Vec<Replacement>,
127    ) -> Macro {
128        Macro {
129            prefix,
130            parameters,
131            replacements: replacement_text,
132        }
133    }
134
135    fn perform_replacement(
136        replacements: &[Replacement],
137        arguments: &[&[Token]],
138        result: &mut Vec<Token>,
139    ) -> usize {
140        let mut output_size = 0;
141        for replacement in replacements.iter() {
142            output_size += match replacement {
143                Replacement::Tokens(tokens) => tokens.len(),
144                Replacement::Parameter(i) => arguments.get(*i).unwrap().len(),
145            };
146        }
147        result.reserve(output_size);
148        for replacement in replacements.iter().rev() {
149            match replacement {
150                Replacement::Tokens(tokens) => {
151                    result.extend(tokens);
152                }
153                Replacement::Parameter(i) => {
154                    result.extend(arguments.get(*i).unwrap().iter().rev().copied());
155                }
156            }
157        }
158        output_size
159    }
160}
161
162impl Parameter {
163    pub fn parse_argument<S: TexlangState>(
164        &self,
165        input: &mut vm::ExpansionInput<S>,
166        index: usize,
167        result: &mut Vec<Token>,
168    ) -> txl::Result<bool> {
169        match self {
170            Parameter::Undelimited => {
171                Parameter::parse_undelimited_argument(input, index + 1, result)?;
172                Ok(false)
173            }
174            Parameter::Delimited(matcher_factory) => Parameter::parse_delimited_argument(
175                input.unexpanded(),
176                matcher_factory,
177                index + 1,
178                result,
179            ),
180        }
181    }
182
183    fn parse_delimited_argument<S: TexlangState>(
184        stream: &mut vm::UnexpandedStream<S>,
185        matcher_factory: &Matcher<Value>,
186        param_num: usize,
187        result: &mut Vec<Token>,
188    ) -> txl::Result<bool> {
189        let mut matcher = matcher_factory.start();
190        let mut scope_depth = 0;
191
192        // This handles the case of a macro whose argument ends with the special #{ tokens. In this special case the parsing
193        // will end with a scope depth of 1, because the last token parsed will be the { and all braces before that will
194        // be balanced.
195        let closing_scope_depth = match matcher_factory.substring().last() {
196            token::Value::BeginGroup(_) => 1,
197            _ => 0,
198        };
199        let start_index = result.len();
200        loop {
201            let token = stream.next_or_err(DelimitedArgumentEndOfInputError { param_num })?;
202            match token.value() {
203                token::Value::BeginGroup(_) => {
204                    scope_depth += 1;
205                }
206                token::Value::EndGroup(_) => {
207                    scope_depth -= 1;
208                }
209                _ => (),
210            };
211            let matches_delimiter = matcher.next(&token.value());
212            result.push(token);
213            if scope_depth == closing_scope_depth && matches_delimiter {
214                // Remove the suffix.
215                for _ in 0..matcher_factory.substring().len() {
216                    result.pop();
217                }
218                return Ok(Parameter::should_trim_outer_braces_if_present(
219                    &result[start_index..],
220                ));
221            }
222        }
223        /*
224        TODO
225        .add_note(format![
226            "this argument is delimited and must be suffixed by the tokens `{}`",
227            matcher_factory.substring()
228        ]);
229        if let Some(first_token) = result.first() {
230            e = e.add_token_context(first_token, "the argument started here:");
231            e = e.add_note(format![
232                "{} tokens were read for the argument so far",
233                result.len()
234            ]);
235        } else {
236            e = e.add_note("no tokens were read for the argument so far");
237        }
238        Err(e
239            .add_token_context(macro_token, "the macro invocation started here:")
240            .cast())
241         */
242    }
243
244    fn should_trim_outer_braces_if_present(list: &[Token]) -> bool {
245        if list.len() <= 1 {
246            return false;
247        }
248        match list[0].value() {
249            token::Value::BeginGroup(_) => (),
250            _ => {
251                return false;
252            }
253        }
254        match list[list.len() - 1].value() {
255            token::Value::EndGroup(_) => (),
256            _ => {
257                return false;
258            }
259        }
260        true
261    }
262
263    fn parse_undelimited_argument<S: TexlangState>(
264        input: &mut vm::ExpansionInput<S>,
265        param_num: usize,
266        result: &mut Vec<Token>,
267    ) -> txl::Result<()> {
268        parse::SpacesUnexpanded::parse(input)?;
269        let input = input.unexpanded();
270        let token = input.next_or_err(UnDelimitedArgumentEndOfInputError { param_num })?;
271        match token.value() {
272            token::Value::BeginGroup(_) => token,
273            _ => {
274                result.push(token);
275                return Ok(());
276            }
277        };
278        parse::finish_parsing_balanced_tokens(input, result)?;
279        Ok(())
280        /* TODO
281            .add_note(format![
282            "this argument started with a `{opening_brace}` and must be finished with a matching closing brace"
283        ])
284            .add_token_context(&opening_brace, "the argument started here:")
285            .add_token_context(macro_token, "the macro invocation started here:")
286            .cast()),
287             */
288    }
289}
290
291#[derive(Debug)]
292struct DelimitedArgumentEndOfInputError {
293    param_num: usize,
294}
295
296impl error::EndOfInputError for DelimitedArgumentEndOfInputError {
297    fn doing(&self) -> String {
298        "parsing a delimited argument for a macro".into()
299    }
300    fn notes(&self) -> Vec<error::display::Note> {
301        vec![format!("this is argument number {} for this macro", self.param_num).into()]
302    }
303}
304
305#[derive(Debug)]
306struct UnDelimitedArgumentEndOfInputError {
307    param_num: usize,
308}
309
310impl error::EndOfInputError for UnDelimitedArgumentEndOfInputError {
311    fn doing(&self) -> String {
312        "parsing an undelimited argument for a macro".into()
313    }
314    fn notes(&self) -> Vec<error::display::Note> {
315        vec![format!("this is argument number {} for this macro", self.param_num).into()]
316    }
317}
318
319fn colored_parameter_number(n: usize) -> String {
320    let color = match n {
321        1 => |s: String| s.bright_yellow(),
322        _ => |s: String| s.bright_blue(),
323    };
324    format![
325        "{}{}",
326        color("#".to_string()).bold(),
327        color(n.to_string()).bold()
328    ]
329}
330
331pub fn pretty_print_prefix_and_parameters(
332    prefix: &[Token],
333    parameters: &[Parameter],
334    interner: &token::CsNameInterner,
335) -> String {
336    let mut d = String::default();
337    if prefix.is_empty() {
338        d.push_str(" . No prefix\n");
339    } else {
340        d.push_str(&format![
341            " . Prefix: `{}`\n",
342            token::write_tokens(prefix, interner)
343        ]);
344    }
345
346    d.push_str(&format![" . Parameters ({}):\n", parameters.len()]);
347    for (i, parameter) in parameters.iter().enumerate() {
348        let parameter_number = i + 1;
349        match parameter {
350            Parameter::Undelimited => {
351                d.push_str(&format![
352                    "    {}: undelimited\n",
353                    colored_parameter_number(parameter_number),
354                ]);
355            }
356            Parameter::Delimited(factory) => {
357                d.push_str(&format![
358                    "    {}: delimited by `{}`\n",
359                    colored_parameter_number(parameter_number),
360                    token::write_token_values(factory.substring(), interner)
361                ]);
362            }
363        }
364    }
365
366    d.push_str(" . Full argument specification: `");
367    d.push_str(&token::write_tokens(prefix, interner));
368    for (i, parameter) in parameters.iter().enumerate() {
369        let parameter_number = i + 1;
370        d.push_str(&colored_parameter_number(parameter_number));
371        if let Parameter::Delimited(factory) = parameter {
372            d.push_str(token::write_token_values(factory.substring(), interner).as_str());
373        }
374    }
375    d.push('`');
376    d
377}
378
379pub fn pretty_print_replacement_text(replacements: &[Replacement]) -> String {
380    let mut b = String::default();
381    for replacement in replacements.iter() {
382        match replacement {
383            Replacement::Parameter(i) => {
384                b.push_str(colored_parameter_number(*i + 1).as_str());
385            }
386            Replacement::Tokens(_) => {
387                b.push_str("TODO");
388            }
389        }
390    }
391    b
392}
393
394/// Removes the provided vector of tokens from the front of the stream.
395pub fn remove_tokens_from_stream<S: TexlangState>(
396    tokens: &[Token],
397    stream: &mut vm::UnexpandedStream<S>,
398) -> txl::Result<bool> {
399    for prefix_token in tokens.iter() {
400        let stream_token = stream.next_or_err(PrefixEndOfInputError {})?;
401        if stream_token.value() != prefix_token.value() {
402            stream.error(error::SimpleTokenError::new(
403                stream_token,
404                "unexpected token while matching the prefix for a user-defined macro",
405            ))?;
406            return Ok(false);
407        }
408    }
409    Ok(true)
410}
411
412#[derive(Debug)]
413struct PrefixEndOfInputError;
414
415impl error::EndOfInputError for PrefixEndOfInputError {
416    fn doing(&self) -> String {
417        "matching the prefix of a user-defined macro".into()
418    }
419}