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 core::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 core::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::CatCode(_)
295 | variable::Variable::TokenList(_)
296 | variable::Variable::MathCode(_)
297 | variable::Variable::Font(_) => {
298 unreachable!("only arithmetic commands are considered");
299 }
300 }
301 }
302 Some(cmd) => input.error(
303 parse::Error::new("a variable", Some(token), "")
304 .with_got_override("got a non-variable command")
305 .with_annotation_override(format!["control sequence referencing {cmd}"]),
306 ),
307 }
308 }
309 _ => input.error(
310 parse::Error::new("a variable", Some(token), "")
311 .with_got_override("got a character token"),
312 ),
313 }
314}
315
316#[derive(Debug)]
317struct ArithmeticVariableEndOfInput;
318
319impl error::EndOfInputError for ArithmeticVariableEndOfInput {
320 fn doing(&self) -> String {
321 "parsing an arithmetic variable".into()
322 }
323}
324
325struct OptionalBy;
327
328impl Parsable for OptionalBy {
329 fn parse_impl<S: TexlangState>(input: &mut vm::ExpandedStream<S>) -> txl::Result<Self> {
330 texlang::parse::parse_keyword(input, "by")?;
331 Ok(OptionalBy)
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use std::collections::HashMap;
338
339 use super::*;
340 use crate::codes;
341 use crate::prefix;
342 use crate::registers;
343 use crate::the;
344 use texlang::types::CatCode;
345 use texlang::vm::implement_has_component;
346 use texlang_testing::*;
347
348 #[derive(Default)]
349 struct State {
350 catcode: codes::Component<CatCode>,
351 prefix: prefix::Component,
352 registers: registers::Component<i32, 256>,
353 registers_dimen: registers::Component<core::Scaled, 256>,
354 registers_skip: registers::Component<core::Glue, 256>,
355 testing: TestingComponent,
356 }
357
358 impl TexlangState for State {
359 fn variable_assignment_scope_hook(
360 state: &mut Self,
361 ) -> texcraft_stdext::collections::groupingmap::Scope {
362 prefix::variable_assignment_scope_hook(state)
363 }
364 fn recoverable_error_hook(
365 &self,
366 recoverable_error: error::TracedTexError,
367 ) -> Result<(), Box<dyn error::TexError>> {
368 texlang_testing::TestingComponent::recoverable_error_hook(self, recoverable_error)
369 }
370 }
371 impl the::TheCompatible for State {}
372
373 implement_has_component![State{
374 catcode: codes::Component<CatCode>,
375 prefix: prefix::Component,
376 registers: registers::Component<i32, 256>,
377 registers_dimen: registers::Component<core::Scaled, 256>,
378 registers_skip: registers::Component<core::Glue, 256>,
379 testing: TestingComponent,
380 }];
381
382 fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
383 HashMap::from([
384 ("advance", get_advance()),
385 ("advanceChecked", get_advance_checked()),
386 ("multiply", get_multiply()),
387 ("multiplyWrapped", get_multiply_wrapped()),
388 ("divide", get_divide()),
389 ("catcode", codes::get_catcode()),
391 ("count", registers::get_count()),
392 ("dimen", registers::get_dimen()),
393 ("skip", registers::get_skip()),
394 ("global", prefix::get_global()),
395 ("the", the::get_the()),
396 ])
397 }
398
399 macro_rules! arithmetic_tests {
400 ( $register: expr, $( ($name: ident, $op: expr, $lhs: expr, $rhs: expr, $expected: expr) ),* $(,)? ) => {
401 test_suite![
402 expansion_equality_tests(
403 $(
404 (
405 $name,
406 format![r"{} 1 {} {} {} 1 {} \the{} 1", $register, $lhs, $op, $register, $rhs, $register],
407 $expected
408 ),
409 )*
410 ),
411 ];
412 };
413 }
414
415 arithmetic_tests![
416 r"\count",
417 (advance_base_case, r"\advance", "1", "2", "3"),
418 (advance_base_case_with_by, r"\advance", "1", "by 2", "3"),
419 (advance_negative_summand, r"\advance", "10", "-2", "8"),
420 (
421 advance_overflow_case,
422 r"\advance",
423 "2147483647",
424 "1",
425 "-2147483648"
426 ),
427 (multiply_base_case, r"\multiply", "5", "4", "20"),
428 (multiply_base_case_with_by, r"\multiply", "5", "by 4", "20"),
429 (multiply_pos_neg, r"\multiply", "-5", "4", "-20"),
430 (multiply_neg_pos, r"\multiply", "5", "-4", "-20"),
431 (multiply_neg_neg, r"\multiply", "-5", "-4", "20"),
432 (
433 multiply_wrapping_overflow,
434 r"\multiplyWrapped",
435 "100000",
436 "100000",
437 "1410065408"
438 ),
439 (
440 multiply_wrapping_base_case,
441 r"\multiplyWrapped",
442 "5",
443 "4",
444 "20"
445 ),
446 (divide_base_case, r"\divide", "9", "4", "2"),
447 (divide_with_by, r"\divide", "9", "by 4", "2"),
448 (divide_pos_neg, r"\divide", "-9", "4", "-2"),
449 (divide_neg_pos, r"\divide", "9", "-4", "-2"),
450 (divide_neg_neg, r"\divide", "-9", "-4", "2"),
451 (divide_exact, r"\divide", "100", "10", "10"),
452 (advance_checked_base_case, r"\advanceChecked", "1", "2", "3"),
453 (
454 advance_checked_negative_summand,
455 r"\advanceChecked",
456 "10",
457 "-2",
458 "8"
459 )
460 ];
461
462 arithmetic_tests![
463 r"\dimen",
464 (advance_dimen_1, r"\advance", "1pt", "2pt", "3.0pt"),
465 (advance_dimen_2, r"\advance", "0.025pt", "0.5pt", "0.525pt"),
466 (mul_dimen_1, r"\multiply", "10pt", "2", "20.0pt"),
467 (div_dimen_1, r"\divide", "10pt", "2", "5.0pt"),
468 ];
469
470 arithmetic_tests![
471 r"\skip",
472 (
473 advance_glue_1,
474 r"\advance",
475 "1pt plus 2pt minus 3pt",
476 "60pt plus 50pt minus 40pt",
477 "61.0pt plus 52.0pt minus 43.0pt"
478 ),
479 (advance_glue_2, r"\advance", "0.025pt", "0.5pt", "0.025pt"),
482 (
483 advance_glue_3,
484 r"\advance",
485 "1pt plus 2fill minus 3fil",
486 "60pt plus 50pt minus 40filll",
487 "61.0pt plus 2.0fill minus 40.0filll"
488 ),
489 (
490 mul_glue_1,
491 r"\multiply",
492 "1pt plus 2pt minus 1.25pt",
493 "2",
494 "2.0pt plus 4.0pt minus 2.5pt"
495 ),
496 (
497 div_glue_1,
498 r"\divide",
499 "10pt plus 20pt minus 3pt",
500 "2",
501 "5.0pt plus 10.0pt minus 1.5pt"
502 ),
503 ];
504 test_suite![
505 expansion_equality_tests(
506 (
507 advance_x_by_x,
508 r"\count 1 200 \advance \count 1 by \count 1 a\the\count 1",
509 r"a400"
510 ),
511 (
512 global_advance,
513 r"\count 1 5{\global\advance\count 1 8}\the\count 1",
514 "13"
515 ),
516 (
517 local_advance,
518 r"\count 1 5{\advance\count 1 8}\the\count 1",
519 "5"
520 ),
521 ),
522 recoverable_failure_tests(
523 (
524 advance_incorrect_keyword_1,
525 r"\count 1 1\advance\count 1 fy 2 \the \count 1",
526 "fy 2 1",
527 ),
528 (
529 advance_incorrect_keyword_2,
530 r"\count 1 1\advance\count 1 be 2 \the \count 1",
531 "be 2 1",
532 ),
533 (
534 advance_catcode_not_supported,
535 r"\advance\catcode 100 by 2",
536 "100 by 2",
537 ),
538 (
539 advance_checked_overflow,
540 r"\count 1 2147483647 \advanceChecked\count 1 by 1",
541 "",
542 ),
543 (
544 multiply_overflow,
545 r"\count 1 100000 \multiply\count 1 by 100000 \the \count 1",
546 "100000"
547 ),
548 (
549 divide_by_zero,
550 r"\count 1 20 \divide\count 1 by 0 \the\count 1",
551 "20"
552 ),
553 ),
554 ];
555}