1use texcraft_stdext::collections::groupingmap;
4use texlang::prelude as txl;
5use texlang::traits::*;
6use texlang::variable::SupportedType;
7use texlang::variable::TypedVariable;
8use texlang::*;
9
10pub fn get_advance<S: TexlangState>() -> command::BuiltIn<S> {
12 get_command::<S, AdvanceOp>()
13}
14
15pub fn get_advance_checked<S: TexlangState>() -> command::BuiltIn<S> {
17 get_command::<S, AdvanceCheckedOp>()
18}
19
20pub fn get_multiply<S: TexlangState>() -> command::BuiltIn<S> {
22 get_command::<S, MultiplyOp>()
23}
24
25pub fn get_multiply_wrapped<S: TexlangState>() -> command::BuiltIn<S> {
27 get_command::<S, MultiplyWrappedOp>()
28}
29
30pub fn get_divide<S: TexlangState>() -> command::BuiltIn<S> {
32 get_command::<S, DivideOp>()
33}
34
35fn get_command<S: TexlangState, O: Op>() -> command::BuiltIn<S> {
36 command::BuiltIn::new_execution(math_primitive_fn::<S, O>)
37 .with_tag(variable_op_tag())
38 .with_doc(O::DOC)
39}
40
41static VARIABLE_OP_TAG: command::StaticTag = command::StaticTag::new();
42
43pub fn variable_op_tag() -> command::Tag {
44 VARIABLE_OP_TAG.get()
45}
46
47trait Number: Sized + Default + SupportedType + Copy + std::fmt::Display {
48 fn wrapping_add(lhs: Self, rhs: Self) -> Self;
49 fn checked_add(lhs: Self, rhs: Self) -> Option<Self>;
50 fn checked_mul(lhs: Self, rhs: i32) -> Option<Self>;
51 fn wrapping_mul(lhs: Self, rhs: i32) -> Self;
52 fn checked_div(lhs: Self, rhs: i32) -> Option<Self>;
53}
54
55impl Number for i32 {
56 fn wrapping_add(lhs: Self, rhs: Self) -> Self {
57 lhs.wrapping_add(rhs)
58 }
59 fn checked_add(lhs: Self, rhs: Self) -> Option<Self> {
60 lhs.checked_add(rhs)
61 }
62 fn checked_mul(lhs: Self, rhs: i32) -> Option<Self> {
63 lhs.checked_mul(rhs)
64 }
65 fn wrapping_mul(lhs: Self, rhs: i32) -> Self {
66 lhs.wrapping_mul(rhs)
67 }
68 fn checked_div(lhs: Self, rhs: i32) -> Option<Self> {
69 lhs.checked_div(rhs)
70 }
71}
72
73impl Number for common::Scaled {
74 fn wrapping_add(lhs: Self, rhs: Self) -> Self {
75 lhs.wrapping_add(rhs)
76 }
77 fn checked_add(lhs: Self, rhs: Self) -> Option<Self> {
78 lhs.checked_add(rhs)
79 }
80 fn checked_mul(lhs: Self, rhs: i32) -> Option<Self> {
81 lhs.checked_mul(rhs)
82 }
83 fn wrapping_mul(lhs: Self, rhs: i32) -> Self {
84 lhs.wrapping_mul(rhs)
85 }
86 fn checked_div(lhs: Self, rhs: i32) -> Option<Self> {
87 lhs.checked_div(rhs)
88 }
89}
90
91impl Number for common::Glue {
92 fn wrapping_add(lhs: Self, rhs: Self) -> Self {
93 lhs.wrapping_add(rhs)
94 }
95 fn checked_add(lhs: Self, rhs: Self) -> Option<Self> {
96 lhs.checked_add(rhs)
97 }
98 fn checked_mul(lhs: Self, rhs: i32) -> Option<Self> {
99 lhs.checked_mul(rhs)
100 }
101 fn wrapping_mul(lhs: Self, rhs: i32) -> Self {
102 lhs.wrapping_mul(rhs)
103 }
104 fn checked_div(lhs: Self, rhs: i32) -> Option<Self> {
105 lhs.checked_div(rhs)
106 }
107}
108
109trait Op {
110 const DOC: &'static str;
111 const RHS_SAME: bool;
112 type Error: error::TexError;
113 fn apply<N: Number>(lhs: N, rhs_i: i32, rhs_n: N) -> Result<N, Self::Error>;
114
115 fn apply_to_variable<S: TexlangState, N: Number>(
116 variable: TypedVariable<S, N>,
117 input: &mut vm::ExecutionInput<S>,
118 scope: groupingmap::Scope,
119 ) -> txl::Result<()> {
120 let lhs = *variable.get(input.state());
121 let (rhs_i, rhs_n) = if Self::RHS_SAME {
122 (0_i32, N::parse(input)?)
123 } else {
124 (i32::parse(input)?, Default::default())
125 };
126 let result = match Self::apply(lhs, rhs_i, rhs_n) {
127 Ok(result) => result,
128 Err(err) => {
129 return input.error(err);
130 }
131 };
132 variable.set(input, scope, result);
133 Ok(())
134 }
135}
136
137struct AdvanceOp;
138
139impl Op for AdvanceOp {
140 const DOC: &'static str = "Add a quantity to a variable";
141 const RHS_SAME: bool = true;
142 type Error = OverflowError;
143 fn apply<N: Number>(lhs: N, _: i32, rhs_n: N) -> Result<N, Self::Error> {
144 Ok(N::wrapping_add(lhs, rhs_n))
146 }
147}
148
149struct AdvanceCheckedOp;
150
151impl Op for AdvanceCheckedOp {
152 const DOC: &'static str = "Add a number to a variable (error on overflow)";
153 const RHS_SAME: bool = true;
154 type Error = OverflowError;
155 fn apply<N: Number>(lhs: N, _: i32, rhs_n: N) -> Result<N, Self::Error> {
156 match N::checked_add(lhs, rhs_n) {
157 Some(result) => Ok(result),
158 None => Err(OverflowError {
159 op_name: "addition",
160 lhs: format!["{lhs}"],
161 rhs: format!["{rhs_n}"],
162 wrapped_result: format!["{}", N::wrapping_add(lhs, rhs_n)],
163 }),
164 }
165 }
166}
167
168struct MultiplyOp;
169
170impl Op for MultiplyOp {
171 const DOC: &'static str = "Multiply a variable by an integer";
172 const RHS_SAME: bool = false;
173 type Error = OverflowError;
174 fn apply<N: Number>(lhs: N, rhs_i: i32, _: N) -> Result<N, Self::Error> {
175 match N::checked_mul(lhs, rhs_i) {
176 Some(result) => Ok(result),
177 None => Err(OverflowError {
178 op_name: "multiplication",
179 lhs: format!["{lhs}"],
180 rhs: format!["{rhs_i}"],
181 wrapped_result: format!["{}", N::wrapping_mul(lhs, rhs_i)],
182 }),
183 }
184 }
185}
186
187struct MultiplyWrappedOp;
188
189impl Op for MultiplyWrappedOp {
190 const DOC: &'static str = "Multiply a variable by an integer (wrap on overflow)";
191 const RHS_SAME: bool = false;
192 type Error = OverflowError;
193 fn apply<N: Number>(lhs: N, rhs_i: i32, _: N) -> Result<N, Self::Error> {
194 Ok(N::wrapping_mul(lhs, rhs_i))
195 }
196}
197
198struct DivideOp;
199
200impl Op for DivideOp {
201 const DOC: &'static str = "Divide a variable by an integer";
202 const RHS_SAME: bool = false;
203 type Error = DivisionByZeroError;
204 fn apply<N: Number>(lhs: N, rhs_i: i32, _: N) -> Result<N, Self::Error> {
205 match N::checked_div(lhs, rhs_i) {
206 Some(result) => Ok(result),
207 None => Err(DivisionByZeroError {
208 numerator: format!["{lhs}"],
209 }),
210 }
211 }
212}
213
214#[derive(Debug)]
215struct OverflowError {
216 op_name: &'static str,
217 lhs: String,
218 rhs: String,
219 wrapped_result: String,
220}
221
222impl error::TexError for OverflowError {
223 fn kind(&self) -> error::Kind {
224 error::Kind::FailedPrecondition
225 }
226
227 fn title(&self) -> String {
228 format!["overflow in checked {}", self.op_name]
229 }
230
231 fn notes(&self) -> Vec<error::display::Note> {
232 vec![
233 format!["left hand side evaluated to {}", self.lhs].into(),
234 format!["right hand side evaluated to {}", self.rhs].into(),
235 format!["wrapped result would be {}", self.wrapped_result].into(),
236 ]
237 }
238}
239
240#[derive(Debug)]
241struct DivisionByZeroError {
242 numerator: String,
243}
244
245impl error::TexError for DivisionByZeroError {
246 fn kind(&self) -> error::Kind {
247 error::Kind::FailedPrecondition
248 }
249
250 fn title(&self) -> String {
251 "division by zero".into()
252 }
253
254 fn notes(&self) -> Vec<error::display::Note> {
255 vec![format!["numerator evaluated to {}", self.numerator].into()]
256 }
257}
258
259fn math_primitive_fn<S: TexlangState, O: Op>(
261 _: token::Token,
262 input: &mut vm::ExecutionInput<S>,
263) -> txl::Result<()> {
264 let scope = TexlangState::variable_assignment_scope_hook(input.state_mut());
265 let token = input.next_or_err(ArithmeticVariableEndOfInput {})?;
266 match token.value() {
267 token::Value::CommandRef(command_ref) => {
268 match input.commands_map().get_command(&command_ref) {
269 None => input.error(
270 parse::Error::new("a variable", Some(token), "")
271 .with_got_override("got an undefined control sequence")
272 .with_annotation_override("undefined control sequence"),
273 ),
274 Some(command::Command::Variable(cmd)) => {
275 if !cmd.is_arithmetic() {
276 input.error(
277 parse::Error::new("an arithmetic variable", Some(token), "")
278 .with_got_override("got a non-arithmetic variable"),
279 )?;
280 return Ok(());
281 }
282 let variable = cmd.clone().resolve(token, input.as_mut())?;
283 OptionalBy::parse(input)?;
284 match variable {
285 variable::Variable::Int(variable) => {
286 O::apply_to_variable(variable, input, scope)
287 }
288 variable::Variable::Dimen(variable) => {
289 O::apply_to_variable(variable, input, scope)
290 }
291 variable::Variable::Glue(variable) => {
292 O::apply_to_variable(variable, input, scope)
293 }
294 variable::Variable::SmallInt(_)
295 | variable::Variable::CatCode(_)
296 | variable::Variable::TokenList(_)
297 | variable::Variable::MathCode(_)
298 | variable::Variable::Font(_) => {
299 unreachable!("only arithmetic commands are considered");
300 }
301 }
302 }
303 Some(cmd) => input.error(
304 parse::Error::new("a variable", Some(token), "")
305 .with_got_override("got a non-variable command")
306 .with_annotation_override(format!["control sequence referencing {cmd}"]),
307 ),
308 }
309 }
310 _ => input.error(
311 parse::Error::new("a variable", Some(token), "")
312 .with_got_override("got a character token"),
313 ),
314 }
315}
316
317#[derive(Debug)]
318struct ArithmeticVariableEndOfInput;
319
320impl error::EndOfInputError for ArithmeticVariableEndOfInput {
321 fn doing(&self) -> String {
322 "parsing an arithmetic variable".into()
323 }
324}
325
326struct OptionalBy;
328
329impl Parsable for OptionalBy {
330 fn parse_impl<S: TexlangState>(input: &mut vm::ExpandedStream<S>) -> txl::Result<Self> {
331 texlang::parse::parse_keyword(input, "by")?;
332 Ok(OptionalBy)
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use std::collections::HashMap;
339
340 use super::*;
341 use crate::codes;
342 use crate::prefix;
343 use crate::registers;
344 use crate::the;
345 use texlang::types::CatCode;
346 use texlang::vm::implement_has_component;
347 use texlang_testing::*;
348
349 #[derive(Default)]
350 struct State {
351 catcode: codes::Component<CatCode>,
352 prefix: prefix::Component,
353 registers: registers::Component<i32, 256>,
354 registers_dimen: registers::Component<common::Scaled, 256>,
355 registers_skip: registers::Component<common::Glue, 256>,
356 testing: TestingComponent,
357 }
358
359 impl TexlangState for State {
360 fn variable_assignment_scope_hook(
361 state: &mut Self,
362 ) -> texcraft_stdext::collections::groupingmap::Scope {
363 prefix::variable_assignment_scope_hook(state)
364 }
365 fn recoverable_error_hook(
366 &self,
367 recoverable_error: error::TracedTexError,
368 ) -> Result<(), Box<dyn error::TexError>> {
369 texlang_testing::TestingComponent::recoverable_error_hook(self, recoverable_error)
370 }
371 }
372 impl the::TheCompatible for State {}
373
374 implement_has_component![State{
375 catcode: codes::Component<CatCode>,
376 prefix: prefix::Component,
377 registers: registers::Component<i32, 256>,
378 registers_dimen: registers::Component<common::Scaled, 256>,
379 registers_skip: registers::Component<common::Glue, 256>,
380 testing: TestingComponent,
381 }];
382
383 fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
384 HashMap::from([
385 ("advance", get_advance()),
386 ("advanceChecked", get_advance_checked()),
387 ("multiply", get_multiply()),
388 ("multiplyWrapped", get_multiply_wrapped()),
389 ("divide", get_divide()),
390 ("catcode", codes::get_catcode()),
392 ("count", registers::get_count()),
393 ("dimen", registers::get_dimen()),
394 ("skip", registers::get_skip()),
395 ("global", prefix::get_global()),
396 ("the", the::get_the()),
397 ])
398 }
399
400 macro_rules! arithmetic_tests {
401 ( $register: expr, $( ($name: ident, $op: expr, $lhs: expr, $rhs: expr, $expected: expr) ),* $(,)? ) => {
402 test_suite![
403 expansion_equality_tests(
404 $(
405 (
406 $name,
407 format![r"{} 1 {} {} {} 1 {} \the{} 1", $register, $lhs, $op, $register, $rhs, $register],
408 $expected
409 ),
410 )*
411 ),
412 ];
413 };
414 }
415
416 arithmetic_tests![
417 r"\count",
418 (advance_base_case, r"\advance", "1", "2", "3"),
419 (advance_base_case_with_by, r"\advance", "1", "by 2", "3"),
420 (advance_negative_summand, r"\advance", "10", "-2", "8"),
421 (
422 advance_overflow_case,
423 r"\advance",
424 "2147483647",
425 "1",
426 "-2147483648"
427 ),
428 (multiply_base_case, r"\multiply", "5", "4", "20"),
429 (multiply_base_case_with_by, r"\multiply", "5", "by 4", "20"),
430 (multiply_pos_neg, r"\multiply", "-5", "4", "-20"),
431 (multiply_neg_pos, r"\multiply", "5", "-4", "-20"),
432 (multiply_neg_neg, r"\multiply", "-5", "-4", "20"),
433 (
434 multiply_wrapping_overflow,
435 r"\multiplyWrapped",
436 "100000",
437 "100000",
438 "1410065408"
439 ),
440 (
441 multiply_wrapping_base_case,
442 r"\multiplyWrapped",
443 "5",
444 "4",
445 "20"
446 ),
447 (divide_base_case, r"\divide", "9", "4", "2"),
448 (divide_with_by, r"\divide", "9", "by 4", "2"),
449 (divide_pos_neg, r"\divide", "-9", "4", "-2"),
450 (divide_neg_pos, r"\divide", "9", "-4", "-2"),
451 (divide_neg_neg, r"\divide", "-9", "-4", "2"),
452 (divide_exact, r"\divide", "100", "10", "10"),
453 (advance_checked_base_case, r"\advanceChecked", "1", "2", "3"),
454 (
455 advance_checked_negative_summand,
456 r"\advanceChecked",
457 "10",
458 "-2",
459 "8"
460 )
461 ];
462
463 arithmetic_tests![
464 r"\dimen",
465 (advance_dimen_1, r"\advance", "1pt", "2pt", "3.0pt"),
466 (advance_dimen_2, r"\advance", "0.025pt", "0.5pt", "0.525pt"),
467 (mul_dimen_1, r"\multiply", "10pt", "2", "20.0pt"),
468 (div_dimen_1, r"\divide", "10pt", "2", "5.0pt"),
469 ];
470
471 arithmetic_tests![
472 r"\skip",
473 (
474 advance_glue_1,
475 r"\advance",
476 "1pt plus 2pt minus 3pt",
477 "60pt plus 50pt minus 40pt",
478 "61.0pt plus 52.0pt minus 43.0pt"
479 ),
480 (advance_glue_2, r"\advance", "0.025pt", "0.5pt", "0.025pt"),
483 (
484 advance_glue_3,
485 r"\advance",
486 "1pt plus 2fill minus 3fil",
487 "60pt plus 50pt minus 40filll",
488 "61.0pt plus 2.0fill minus 40.0filll"
489 ),
490 (
491 mul_glue_1,
492 r"\multiply",
493 "1pt plus 2pt minus 1.25pt",
494 "2",
495 "2.0pt plus 4.0pt minus 2.5pt"
496 ),
497 (
498 div_glue_1,
499 r"\divide",
500 "10pt plus 20pt minus 3pt",
501 "2",
502 "5.0pt plus 10.0pt minus 1.5pt"
503 ),
504 ];
505 test_suite![
506 expansion_equality_tests(
507 (
508 advance_x_by_x,
509 r"\count 1 200 \advance \count 1 by \count 1 a\the\count 1",
510 r"a400"
511 ),
512 (
513 global_advance,
514 r"\count 1 5{\global\advance\count 1 8}\the\count 1",
515 "13"
516 ),
517 (
518 local_advance,
519 r"\count 1 5{\advance\count 1 8}\the\count 1",
520 "5"
521 ),
522 ),
523 recoverable_failure_tests(
524 (
525 advance_incorrect_keyword_1,
526 r"\count 1 1\advance\count 1 fy 2 \the \count 1",
527 "fy 2 1",
528 ),
529 (
530 advance_incorrect_keyword_2,
531 r"\count 1 1\advance\count 1 be 2 \the \count 1",
532 "be 2 1",
533 ),
534 (
535 advance_catcode_not_supported,
536 r"\advance\catcode 100 by 2",
537 "100 by 2",
538 ),
539 (
540 advance_checked_overflow,
541 r"\count 1 2147483647 \advanceChecked\count 1 by 1",
542 "",
543 ),
544 (
545 multiply_overflow,
546 r"\count 1 100000 \multiply\count 1 by 100000 \the \count 1",
547 "100000"
548 ),
549 (
550 divide_by_zero,
551 r"\count 1 20 \divide\count 1 by 0 \the\count 1",
552 "20"
553 ),
554 ),
555 ];
556}