1use 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#[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#[derive(Debug, Clone)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub enum Replacement {
33 Tokens(Vec<Token>),
35
36 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
50pub 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 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 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 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 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 }
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 }
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
394pub 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}