1use crate::prefix;
4use texcraft_stdext::algorithms::substringsearch::Matcher;
5use texcraft_stdext::collections::groupingmap;
6use texcraft_stdext::collections::nevec::Nevec;
7use texcraft_stdext::nevec;
8use texlang::parse::Command;
9use texlang::traits::*;
10use texlang::*;
11
12pub const DEF_DOC: &str = "Define a custom macro";
13
14pub fn get_def<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
16 command::BuiltIn::new_execution(def_primitive_fn).with_tag(def_tag())
17}
18
19pub fn get_gdef<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
21 command::BuiltIn::new_execution(gdef_primitive_fn).with_tag(def_tag())
22}
23
24static DEF_TAG: command::StaticTag = command::StaticTag::new();
25
26pub fn def_tag() -> command::Tag {
27 DEF_TAG.get()
28}
29
30fn def_primitive_fn<S: HasComponent<prefix::Component>>(
31 def_token: token::Token,
32 input: &mut vm::ExecutionInput<S>,
33) -> Result<(), Box<error::Error>> {
34 parse_and_set_macro(def_token, input, false)
35}
36
37fn gdef_primitive_fn<S: HasComponent<prefix::Component>>(
38 def_token: token::Token,
39 input: &mut vm::ExecutionInput<S>,
40) -> Result<(), Box<error::Error>> {
41 parse_and_set_macro(def_token, input, true)
42}
43
44fn parse_and_set_macro<S: HasComponent<prefix::Component>>(
45 _: token::Token,
46 input: &mut vm::ExecutionInput<S>,
47 set_globally_override: bool,
48) -> Result<(), Box<error::Error>> {
49 let mut scope = input.state_mut().component_mut().read_and_reset_global();
50 if set_globally_override {
51 scope = groupingmap::Scope::Global;
52 }
53 let Command::ControlSequence(name) = Command::parse(input)?;
54 let (prefix, raw_parameters, replacement_end_token) =
55 parse_prefix_and_parameters(input.unexpanded())?;
56 let parameters: Vec<texmacro::Parameter> = raw_parameters
57 .into_iter()
58 .map(|a| match a {
59 RawParameter::Undelimited => texmacro::Parameter::Undelimited,
60 RawParameter::Delimited(vec) => texmacro::Parameter::Delimited(Matcher::new(vec)),
61 })
62 .collect();
63 let mut replacement =
64 parse_replacement_text(input.unexpanded(), replacement_end_token, parameters.len())?;
65 for r in replacement.iter_mut() {
66 if let texmacro::Replacement::Tokens(tokens) = r {
67 tokens.reverse();
68 }
69 }
70 let user_defined_macro = texmacro::Macro::new(prefix, parameters, replacement);
71 input
72 .commands_map_mut()
73 .insert_macro(name, user_defined_macro, scope);
74 Ok(())
75}
76
77enum RawParameter {
78 Undelimited,
79 Delimited(Nevec<token::Token>),
80}
81
82impl RawParameter {
83 fn push(&mut self, t: token::Token) {
84 match self {
85 RawParameter::Undelimited => {
86 *self = RawParameter::Delimited(nevec![t]);
87 }
88 RawParameter::Delimited(vec) => {
89 vec.push(t);
90 }
91 }
92 }
93}
94
95fn char_to_parameter_index(c: char) -> Option<usize> {
96 match c {
97 '1' => Some(0),
98 '2' => Some(1),
99 '3' => Some(2),
100 '4' => Some(3),
101 '5' => Some(4),
102 '6' => Some(5),
103 '7' => Some(6),
104 '8' => Some(7),
105 '9' => Some(8),
106 _ => None,
107 }
108}
109
110fn parse_prefix_and_parameters<S: TexlangState>(
111 input: &mut vm::UnexpandedStream<S>,
112) -> command::Result<(Vec<token::Token>, Vec<RawParameter>, Option<token::Token>)> {
113 let mut prefix = Vec::new();
114 let mut parameters = Vec::new();
115 let mut replacement_end_token = None;
116
117 while let Some(token) = input.next()? {
118 match token.value() {
119 token::Value::BeginGroup(_) => {
120 return Ok((prefix, parameters, replacement_end_token));
121 }
122 token::Value::EndGroup(_) => {
123 return Err(error::SimpleTokenError::new(
124 input.vm(),
125 token,
126 "unexpected end group token while parsing the parameter of a macro definition",
127 )
128 .into());
129 }
130 token::Value::Parameter(_) => {
131 let parameter_token = match input.next()? {
132 None => {
133 return Err(error::SimpleEndOfInputError::new(input.vm(),
134 "unexpected end of input while reading the token after a parameter token").into());
135 }
137 Some(token) => token,
138 };
139 match parameter_token.value() {
140 token::Value::BeginGroup(_) => {
141 replacement_end_token = Some(parameter_token);
143 match parameters.last_mut() {
144 None => {
145 prefix.push(parameter_token);
146 }
147 Some(spec) => {
148 spec.push(parameter_token);
149 }
150 }
151 return Ok((prefix, parameters, replacement_end_token));
152 }
153 token::Value::ControlSequence(..) => {
154 return Err(error::SimpleTokenError::new(
155 input.vm(),
156 parameter_token,
157 "unexpected control sequence after a parameter token",
158 )
159 .into());
160 }
162 _ => {
163 let c = parameter_token.char().unwrap();
164 let parameter_index = match char_to_parameter_index(c) {
165 None => {
166 return Err(error::SimpleTokenError::new(
167 input.vm(),
168 parameter_token,
169 "unexpected character after a parameter token",
170 )
171 .into());
172 }
174 Some(n) => n,
175 };
176 if parameter_index != parameters.len() {
177 return Err(error::SimpleTokenError::new(
178 input.vm(),
179 parameter_token,
180 format!["unexpected parameter number {}", parameter_index + 1],
181 )
182 .into());
183 }
186 parameters.push(RawParameter::Undelimited);
187 }
188 }
189 }
190 _ => match parameters.last_mut() {
191 None => {
192 prefix.push(token);
193 }
194 Some(parameter) => {
195 parameter.push(token);
196 }
197 },
198 }
199 }
200 Err(error::SimpleEndOfInputError::new(
201 input.vm(),
202 "unexpected end of input while reading the parameter text of a macro",
203 )
204 .into())
205 }
207
208fn parse_replacement_text<S: TexlangState>(
209 input: &mut vm::UnexpandedStream<S>,
210 opt_final_token: Option<token::Token>,
211 num_parameters: usize,
212) -> command::Result<Vec<texmacro::Replacement>> {
213 let mut result = vec![];
215 let mut scope_depth = 0;
216 let push = |result: &mut Vec<texmacro::Replacement>, token| match result.last_mut() {
217 Some(texmacro::Replacement::Tokens(tokens)) => {
218 tokens.push(token);
219 }
220 _ => {
221 result.push(texmacro::Replacement::Tokens(vec![token]));
222 }
223 };
224
225 while let Some(token) = input.next()? {
226 match token.value() {
227 token::Value::BeginGroup(_) => {
228 scope_depth += 1;
229 }
230 token::Value::EndGroup(_) => {
231 if scope_depth == 0 {
232 if let Some(final_token) = opt_final_token {
233 push(&mut result, final_token);
234 }
235 return Ok(result);
236 }
237 scope_depth -= 1;
238 }
239 token::Value::Parameter(_) => {
240 let parameter_token = match input.next()? {
241 None => {
242 return Err(error::SimpleEndOfInputError::new(
243 input.vm(),
244 "unexpected end of input while reading a parameter number",
245 )
246 .into())
247 }
248 Some(token) => token,
249 };
250 let c = match parameter_token.value() {
251 token::Value::ControlSequence(..) => {
252 return Err(error::SimpleTokenError::new(
253 input.vm(),
254 parameter_token,
255 "unexpected character while reading a parameter number",
256 )
257 .into());
258 }
260 token::Value::Parameter(_) => {
261 push(&mut result, parameter_token);
262 continue;
263 }
264 _ => parameter_token.char().unwrap(),
265 };
266
267 let parameter_index = match char_to_parameter_index(c) {
268 None => {
269 return Err(error::SimpleTokenError::new(
270 input.vm(),
271 parameter_token,
272 "unexpected character while reading a parameter number",
273 )
274 .into());
275 }
277 Some(n) => n,
278 };
279 if parameter_index >= num_parameters {
280 return Err(error::SimpleTokenError::new(
281 input.vm(),
282 parameter_token,
283 "unexpected character while reading a parameter number",
284 )
285 .into());
286
287 }
302 result.push(texmacro::Replacement::Parameter(parameter_index));
303 continue;
304 }
305 _ => {}
306 }
307
308 push(&mut result, token);
309 }
310
311 Err(error::SimpleEndOfInputError::new(
312 input.vm(),
313 "unexpected end of input while reading a parameter number",
314 )
315 .into())
316}
317
318#[cfg(test)]
319mod test {
320 use std::collections::HashMap;
321
322 use super::*;
323 use crate::testing::*;
324
325 fn initial_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
326 HashMap::from([
327 ("def", get_def()),
328 ("gdef", get_gdef()),
329 ("global", prefix::get_global()),
330 ("assertGlobalIsFalse", prefix::get_assert_global_is_false()),
331 ])
332 }
333
334 test_suite![
335 expansion_equality_tests(
336 (def_parsed_successfully, "\\def\\A{abc}", ""),
337 (output_is_correct, "\\def\\A{abc}\\A", "abc"),
338 (output_twice, "\\def\\A{abc}\\A\\A", "abcabc"),
339 (parse_one_parameter, "\\def\\A#1{a-#1-b}", ""),
340 (one_undelimited_parameter, "\\def\\A#1{a-#1-b}\\A1", "a-1-b"),
341 (
342 one_undelimited_parameter_multiple_times,
343 "\\def\\A#1{#1 #1 #1}\\A1",
344 "1 1 1"
345 ),
346 (
347 one_undelimited_parameter_multiple_tokens,
348 "\\def\\A#1{a-#1-b}\\A{123}",
349 "a-123-b"
350 ),
351 (
352 two_undelimited_parameters,
353 "\\def\\A#1#2{#2-#1}\\A56",
354 "6-5"
355 ),
356 (
357 two_undelimited_parameters_multiple_token_inputs,
358 "\\def\\A#1#2{#2-#1}\\A{abc}{xyz}",
359 "xyz-abc"
360 ),
361 (
362 consume_prefix_correctly,
363 "\\def\\A fgh{567}\\A fghi",
364 "567i"
365 ),
366 (
367 one_undelimited_parameter_with_prefix,
368 "\\def\\A abc#1{y#1z}\\A abcdefg",
369 "ydzefg"
370 ),
371 (
372 one_undelimited_parameter_with_prefix_multiple_tokens,
373 "\\def\\A abc#1{y#1z}\\A abcdefg",
374 "ydzefg"
375 ),
376 (
377 one_delimited_parameter,
378 "\\def\\A #1xxx{y#1z}\\A abcxxx",
379 "yabcz"
380 ),
381 (
382 one_delimited_parameter_empty,
383 "\\def\\A #1xxx{y#1z}\\A xxx",
384 "yz"
385 ),
386 (
387 one_delimited_parameter_with_scope,
388 "\\def\\A #1xxx{#1}\\A abc{123xxx}xxx",
389 "abc{123xxx}"
390 ),
391 (
392 one_delimited_parameter_with_prefix,
393 "\\def\\A a#1c{x#1y}\\A abcdef",
394 "xbydef"
395 ),
396 (
397 two_delimited_parameters_with_prefix,
398 r"\def\A a#1c#2e{x#2y#1z}\A abcdef",
399 "xdybzf"
400 ),
401 (
402 one_delimited_parameter_grouped_value,
403 r"\def\A #1c{x#1y}\A {Hello}c",
404 "xHelloy"
405 ),
406 (
407 parameter_brace_special_case,
408 r"\def\A #{Mint says }\A{hello}",
409 "Mint says {hello}"
410 ),
411 (
412 grouping,
413 r"\def\A{Hello}\A{\def\A{World}\A}\A",
414 r"HelloWorldHello"
415 ),
416 (
417 grouping_global,
418 r"\def\A{Hello}\A{\global\def\A{World}\A}\A",
419 r"HelloWorldWorld"
420 ),
421 (
422 gdef,
423 r"\def\A{Hello}\A{\gdef\A{World}\A}\A",
424 r"HelloWorldWorld"
425 ),
426 (
427 gdef_global,
428 r"\def\A{Hello}\A{\global\gdef\A{World}\A}\A",
429 r"HelloWorldWorld"
430 ),
431 (
432 def_takes_global,
433 r"\global\def\A{Hello}\assertGlobalIsFalse",
434 r""
435 ),
436 (
437 gdef_takes_global,
438 r"\global\gdef\A{Hello}\assertGlobalIsFalse",
439 r""
440 ),
441 (
442 texbook_exercise_20_1,
443 r"\def\mustnt{I must not talk in class.}%
444 \def\five{\mustnt\mustnt\mustnt\mustnt\mustnt}%
445 \def\twenty{\five\five\five\five}%
446 \def\punishment{\twenty\twenty\twenty\twenty\twenty}%
447 \punishment",
448 "I must not talk in class.".repeat(100)
449 ),
450 (
451 texbook_exercise_20_2,
452 r"\def\a{\b}%
453 \def\b{A\def\a{B\def\a{C\def\a{\b}}}}%
454 \def\puzzle{\a\a\a\a\a}%
455 \puzzle",
456 "ABCAB"
457 ),
458 (
459 texbook_exercise_20_3_part_1,
460 "\\def\\row#1{(#1_1,\\ldots,#1_n)}\\row{\\bf x}",
461 "(\\bf x_1,\\ldots,\\bf x_n)"
462 ),
463 (
464 texbook_exercise_20_3_part_2,
465 "\\def\\row#1{(#1_1,\\ldots,#1_n)}\\row{{\\bf x}}",
466 "({\\bf x}_1,\\ldots,{\\bf x}_n)"
467 ),
468 (
469 texbook_exercise_20_4_part_1,
470 r#"\def\mustnt#1#2{I must not #1 in #2.}%
471 \def\five#1#2{\mustnt{#1}{#2}\mustnt{#1}{#2}\mustnt{#1}{#2}\mustnt{#1}{#2}\mustnt{#1}{#2}}%
472 \def\twenty#1#2{\five{#1}{#2}\five{#1}{#2}\five{#1}{#2}\five{#1}{#2}}%
473 \def\punishment#1#2{\twenty{#1}{#2}\twenty{#1}{#2}\twenty{#1}{#2}\twenty{#1}{#2}\twenty{#1}{#2}}%
474 \punishment{run}{the halls}"#,
475 "I must not run in the halls.".repeat(100)
476 ),
477 (
478 texbook_exercise_20_4_part_2,
479 r#"\def\mustnt{I must not \doit\ in \thatplace.}%
480 \def\five{\mustnt\mustnt\mustnt\mustnt\mustnt}%
481 \def\twenty{\five\five\five\five}%
482 \def\punishment#1#2{\def\doit{#1}\def\thatplace{#2}\twenty\twenty\twenty\twenty\twenty}%
483 \punishment{run}{the halls}"#,
484 r"I must not run\ in the halls.".repeat(100)
485 ),
486 (
487 texbook_exercise_20_5,
488 r"\def\a#1{\def\b##1{##1#1}}\a!\b{Hello}",
489 "Hello!"
490 ),
491 (
492 texbook_exercise_20_5_temp,
493 r"\def\b#1{#1!}\b{Hello}",
494 "Hello!"
495 ),
496 (
497 texbook_exercise_20_5_example_below,
498 "\\def\\a#1#{\\hbox to #1}\\a3pt{x}",
499 "\\hbox to 3pt{x}"
500 ),
501 (
502 texbook_exercise_20_6,
503 r"\def\b#1{And #1, World!}\def\a#{\b}\a{Hello}",
504 "And Hello, World!"
505 ),
506 ),
507 serde_tests((
508 serde_basic,
509 r"\def\helloWorld{Hello World} ",
510 r"\helloWorld"
511 ),),
512 failure_tests(
513 (end_of_input_scanning_target, "\\def"),
514 (end_of_input_scanning_argument_text, "\\def\\A"),
515 (end_of_input_scanning_replacement, "\\def\\A{"),
516 (end_of_input_scanning_nested_replacement, "\\def\\A{{}"),
517 (end_of_input_reading_parameter_number, "\\def\\A#"),
518 (end_of_input_scanning_argument, "\\def\\A#1{} \\A"),
519 (
520 end_of_input_reading_value_for_parameter,
521 "\\def\\A#1{} \\A{this {is parameter 1 but it never ends}"
522 ),
523 (end_of_input_reading_prefix, "\\def\\A abc{} \\A ab"),
524 (
525 end_of_input_reading_delimiter,
526 "\\def\\A #1abc{} \\A {first parameter}ab"
527 ),
528 (unexpected_token_target, "\\def a"),
529 (unexpected_token_argument, "\\def\\A }"),
530 (unexpected_token_parameter_number, "\\def\\A #a}"),
531 (unexpected_parameter_number_in_argument, "\\def\\A #2{}"),
532 (unexpected_parameter_token_in_replacement, "\\def\\A #1{#a}"),
533 (unexpected_parameter_number_in_replacement, "\\def\\A {#2}"),
534 (
535 unexpected_parameter_number_in_replacement_2,
536 "\\def\\A #1{#2}"
537 ),
538 (unexpected_token_in_prefix, "\\def\\A abc{d} \\A abd"),
539 ),
540 ];
541
542 }