1use texlang::parse::OptionalEquals;
4use texlang::prelude as txl;
5use texlang::traits::*;
6use texlang::variable::SupportedType;
7use texlang::*;
8
9pub const COUNT_DOC: &str = "Get or set an integer register";
10pub const COUNTDEF_DOC: &str = "Bind an integer register to a control sequence";
11
12pub struct DefaultMarker;
14
15pub struct Component<T, const N: usize, Marker = DefaultMarker>(
23 Box<[T; N]>,
27 std::marker::PhantomData<Marker>,
28);
29
30impl<T, const N: usize, Marker> Component<T, N, Marker> {
31 pub fn values(&self) -> &[T; N] {
32 &self.0
33 }
34}
35
36static COUNTDEF_TAG: command::StaticTag = command::StaticTag::new();
37
38pub fn countdef_tag() -> command::Tag {
39 COUNTDEF_TAG.get()
40}
41
42#[cfg(feature = "serde")]
43impl<T: serde::Serialize, const N: usize> serde::Serialize for Component<T, N> {
44 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45 where
46 S: serde::Serializer,
47 {
48 let v: Vec<&T> = self.0.iter().collect();
49 v.serialize(serializer)
50 }
51}
52
53#[cfg(feature = "serde")]
54impl<'de, T: std::fmt::Debug + serde::Deserialize<'de>, const N: usize> serde::Deserialize<'de>
55 for Component<T, N>
56{
57 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
58 where
59 D: serde::Deserializer<'de>,
60 {
61 let v = Vec::<T>::deserialize(deserializer)?;
62 let a: Box<[T; N]> = v.try_into().unwrap();
63 Ok(Component(a, Default::default()))
64 }
65}
66
67impl<T: std::fmt::Debug + Default, const N: usize, Marker> Default for Component<T, N, Marker> {
68 fn default() -> Self {
69 let mut v = vec![];
70 for _ in 0..N {
71 v.push(T::default())
72 }
73 Self(Box::new(v.try_into().unwrap()), Default::default())
74 }
75}
76
77pub fn get_count<S: HasComponent<Component<i32, N>>, const N: usize>() -> command::BuiltIn<S> {
79 new_registers_command()
80}
81
82pub fn get_dimen<S: HasComponent<Component<common::Scaled, N>>, const N: usize>(
84) -> command::BuiltIn<S> {
85 new_registers_command()
86}
87
88pub fn get_skip<S: HasComponent<Component<common::Glue, N>>, const N: usize>() -> command::BuiltIn<S>
90{
91 new_registers_command()
92}
93
94pub fn get_toks<S: HasComponent<Component<Vec<token::Token>, N>>, const N: usize>(
96) -> command::BuiltIn<S> {
97 new_registers_command()
98}
99
100pub fn new_registers_command<
102 T: SupportedType,
103 Marker: 'static,
104 S: HasComponent<Component<T, N, Marker>>,
105 const N: usize,
106>() -> command::BuiltIn<S> {
107 variable::Command::new_array(ref_fn, mut_fn, variable::IndexResolver::Dynamic(count_fn)).into()
108}
109
110fn count_fn<T, Marker: 'static, S: HasComponent<Component<T, N, Marker>>, const N: usize>(
111 _: token::Token,
112 input: &mut vm::ExpandedStream<S>,
113) -> txl::Result<variable::Index> {
114 let index = parse::Uint::<N>::parse(input)?;
115 Ok(index.0.into())
116}
117
118pub fn get_countdef<S: HasComponent<Component<i32, N>>, const N: usize>() -> command::BuiltIn<S> {
120 command::BuiltIn::new_execution(countdef_fn).with_tag(countdef_tag())
121}
122
123pub fn get_toksdef<S: HasComponent<Component<Vec<token::Token>, N>>, const N: usize>(
125) -> command::BuiltIn<S> {
126 command::BuiltIn::new_execution(countdef_fn).with_tag(countdef_tag())
127}
128
129fn countdef_fn<T: variable::SupportedType, S: HasComponent<Component<T, N>>, const N: usize>(
130 _: token::Token,
131 input: &mut vm::ExecutionInput<S>,
132) -> txl::Result<()> {
133 let scope = TexlangState::variable_assignment_scope_hook(input.state_mut());
134 let (cmd_ref_or, _, index) =
135 <(Option<token::CommandRef>, OptionalEquals, parse::Uint<N>)>::parse(input)?;
136 if let Some(cmd_ref) = cmd_ref_or {
137 input.commands_map_mut().insert_variable_command(
138 cmd_ref,
139 variable::Command::new_array(
140 ref_fn,
141 mut_fn,
142 variable::IndexResolver::Static(index.0.into()),
143 ),
144 scope,
145 );
146 }
147 Ok(())
148}
149
150fn ref_fn<T, Marker: 'static, S: HasComponent<Component<T, N, Marker>>, const N: usize>(
151 state: &S,
152 index: variable::Index,
153) -> &T {
154 state.component().0.get(index.0).unwrap()
155}
156
157fn mut_fn<T, Marker: 'static, S: HasComponent<Component<T, N, Marker>>, const N: usize>(
158 state: &mut S,
159 index: variable::Index,
160) -> &mut T {
161 state.component_mut().0.get_mut(index.0).unwrap()
162}
163
164#[cfg(test)]
165mod tests {
166 use std::collections::HashMap;
167
168 use super::*;
169 use crate::{prefix, the};
170 use texlang::vm::implement_has_component;
171 use texlang_testing::*;
172
173 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174 #[derive(Default)]
175 struct State {
176 registers_i32: Component<i32, 256>,
177 registers_dimen: Component<common::Scaled, 256>,
178 registers_token_list: Component<Vec<token::Token>, 256>,
179 prefix: prefix::Component,
180 testing: TestingComponent,
181 }
182
183 impl TexlangState for State {
184 fn em_width(&self) -> common::Scaled {
185 self.registers_dimen.0[254]
186 }
187 fn ex_height(&self) -> common::Scaled {
188 self.registers_dimen.0[255]
189 }
190 fn recoverable_error_hook(
191 &self,
192 recoverable_error: error::TracedTexError,
193 ) -> Result<(), Box<dyn error::TexError>> {
194 TestingComponent::recoverable_error_hook(self, recoverable_error)
195 }
196 fn variable_assignment_scope_hook(
197 state: &mut Self,
198 ) -> texcraft_stdext::collections::groupingmap::Scope {
199 prefix::variable_assignment_scope_hook(state)
200 }
201 }
202 impl the::TheCompatible for State {}
203
204 implement_has_component![State{
205 registers_i32: Component<i32, 256>,
206 registers_dimen: Component<common::Scaled, 256>,
207 registers_token_list: Component<Vec<token::Token>, 256>,
208 prefix: prefix::Component,
209 testing: TestingComponent,
210 }];
211
212 fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
213 HashMap::from([
214 ("the", the::get_the()),
215 ("count", get_count()),
216 ("countdef", get_countdef()),
217 ("dimen", get_dimen()),
218 ("global", prefix::get_global()),
219 ("toks", get_toks()),
220 ("toksdef", get_toksdef()),
221 ])
222 }
223
224 test_suite![
225 expansion_equality_tests(
226 (write_and_read_register, r"\count 23 4 \the\count 23", r"4"),
227 (
228 write_and_read_register_eq,
229 r"\count 23 = 4 \the\count 23",
230 r"4"
231 ),
232 (
233 negative_negative,
234 r"\count 1=5000 \count 0=-1 \the \count -\count 0",
235 r"5000"
236 ),
237 (countdef_base_case, r"\countdef\A 23\A 4 \the\A", r"4"),
238 (countdef_base_case_eq, r"\countdef\A = 23\A 4 \the\A", r"4"),
239 (
240 countdef_with_count,
241 r"\countdef\A 23\A 4\count 1 0 \the\A",
242 r"4"
243 ),
244 (
245 countdef_local,
246 r"\count 1=1 \count 2=2 \countdef\A 1{\countdef\A 2}\the\A",
247 r"1"
248 ),
249 (
250 countdef_global,
251 r"\count 1=1 \count 2=2 \countdef\A 1{\global\countdef\A 2}\the\A",
252 r"2"
253 ),
254 (
255 countdef_with_same_count,
256 r"\countdef\A 23\A 4\count 23 5 \the\A",
257 r"5"
258 ),
259 (
260 toks_basic,
261 r"\toks 1 = {Hola, Mundo}\the \toks 1",
262 r"Hola, Mundo"
263 ),
264 (
265 toksdef_basic,
266 r"\toksdef\content 1 \toks 1 = {Hola, Mundo}\the \content",
267 r"Hola, Mundo"
268 ),
269 (
270 toks_copy,
271 r"\toks 1 = {Hola, Mundo}\toks 2 = \toks 1 \the \toks 2",
272 r"Hola, Mundo"
273 ),
274 (
275 dimen_to_int_1,
276 r"\dimen 1 = 40sp \count 1 = \dimen 1 \the \count 1",
277 r"40",
278 ),
279 (
280 dimen_to_int_2,
281 r"\dimen 1 = 40in \count 1 = \dimen 1 \the \count 1",
282 r"189451468",
283 ),
284 (
285 int_to_dimen_1,
286 r"\count 1 = 40 \dimen 1 = \count 1 pt \the \dimen 1",
287 r"40.0pt",
288 ),
289 (
290 int_to_dimen_2,
291 r"\count 1 = -40 \dimen 1 = \count 1 pt \the \dimen 1",
292 r"-40.0pt",
293 ),
294 (
295 int_to_dimen_3,
296 r"\count 3 = 40 \count 5 = 2 \dimen 7 = \count 3 \count 5 \the \dimen 7",
298 r"0.00122pt",
299 ),
300 (
301 int_to_dimen_4,
302 r"\count 1 = 40 \dimen 2 = 5sp \dimen 1 = \count 1 \dimen 2 \the \dimen 1",
303 r"0.00305pt",
304 ),
305 (
306 int_to_dimen_5,
307 r"\count 1 = 0 \dimen 2 = 5sp \dimen 1 = \count 1 \dimen 2 \the \dimen 1",
308 r"0.0pt",
309 ),
310 (
311 dimen_to_dimen_1,
312 r"\dimen 1 = 10pt \dimen 2 = 5 \dimen 1 \the \dimen 2",
313 r"50.0pt",
314 ),
315 (
316 dimen_to_dimen_2,
317 r"\dimen 2 = 10pt \dimen 1 = - 1.5 \dimen 2 \the \dimen 1",
318 r"-15.0pt",
319 ),
320 (
321 dimen_to_dimen_3,
322 r"\dimen 2 = 5pt \dimen 1 = - 1.5 \dimen 2 \the \dimen 1",
323 r"-7.5pt",
324 ),
325 (
326 dimen_to_dimen_4,
327 r"\dimen 2 = -10pt \dimen 1 = 1.5 \dimen 2 \the \dimen 1",
328 r"-15.0pt",
329 ),
330 (
331 dimen_to_dimen_5,
332 r"\dimen 2 = -5pt \dimen 1 = 1.5 \dimen 2 \the \dimen 1",
333 r"-7.5pt",
334 ),
335 (
336 dimen_to_dimen_6,
337 r"\dimen 2 = -10pt \dimen 1 = - 1.5 \dimen 2 \the \dimen 1",
338 r"15.0pt",
339 ),
340 (
341 dimen_to_dimen_7,
342 r"\dimen 2 = -5pt \dimen 1 = - 1.5 \dimen 2 \the \dimen 1",
343 r"7.5pt",
344 ),
345 (
346 int_dimen_to_dimen_1,
347 r"\count 1 = 2 \dimen 1 = 3pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
348 r"6.0pt",
349 ),
350 (
351 int_dimen_to_dimen_2,
352 r"\count 1 = -2 \dimen 1 = 3pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
353 r"-6.0pt",
354 ),
355 (
356 int_dimen_to_dimen_3,
357 r"\count 1 = 2 \dimen 1 = -3pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
358 r"-6.0pt",
359 ),
360 (
361 int_dimen_to_dimen_4,
362 r"\count 1 = 2 \dimen 1 = 3pt \dimen 2 = -\count 1 \dimen 1 \the \dimen 2",
363 r"-6.0pt",
364 ),
365 (
366 int_dimen_to_dimen_5,
367 r"\count 1 = 2 \dimen 1 = 0pt \dimen 2 = -\count 1 \dimen 1 \the \dimen 2",
368 r"0.0pt",
369 ),
370 (
371 em_dimen,
372 r"\dimen 254 = 2.25pt \dimen 0 = 4.5em \the \dimen 0",
373 r"10.125pt",
374 ),
375 (
376 ex_dimen,
377 r"\dimen 255 = 2.25pt \dimen 0 = 4.5ex \the \dimen 0",
378 r"10.125pt",
379 ),
380 ),
381 serde_tests(
382 (serde_basic, r"\count 100 200 ", r"\the \count 100"),
383 (serde_countdef, r"\countdef \A 100 \A = 200 ", r"\the \A"),
384 (
385 serde_group,
386 r"\count 1 2 {\count 1 3 ",
387 r"\the \count 1 } \the \count 1"
388 ),
389 ),
390 recoverable_failure_tests(
391 (
392 write_register_index_too_big,
393 r"\count 260 = 4 \the\count 0",
394 "4"
395 ),
396 (
397 write_register_negative_index,
398 r"\count -1 = 4 \the\count 0",
399 "4"
400 ),
401 (
402 countdef_register_index_too_big,
403 r"\countdef\A 260 \A= 4 \the\count 0",
404 "4"
405 ),
406 (countdef_missing_cs, r"\countdef 260 End", "End"),
407 (
408 dimen_to_dimen_too_big_1,
409 r"\dimen 1 = 10000pt \dimen 2 = 2 \dimen 1 \the \dimen 2",
410 r"16383.99998pt",
411 ),
412 (
413 dimen_to_dimen_too_big_2,
414 r"\dimen 1 = -10000pt \dimen 2 = 2 \dimen 1 \the \dimen 2",
415 r"-16383.99998pt",
416 ),
417 (
418 dimen_to_dimen_too_big_3,
419 r"\dimen 1 = 10000pt \dimen 2 = -2 \dimen 1 \the \dimen 2",
420 r"-16383.99998pt",
421 ),
422 (
423 dimen_to_dimen_too_big_4,
424 r"\dimen 1 = -10000pt \dimen 2 = -2 \dimen 1 \the \dimen 2",
425 r"16383.99998pt",
426 ),
427 (
428 int_to_dimen_too_big_1,
429 r"\count 1 = 400 \dimen 1 = \count 1 in \the \dimen 1",
430 r"16383.99998pt",
431 ),
432 (
433 int_to_dimen_too_big_2,
434 r"\count 1 = -400 \dimen 1 = \count 1 in \the \dimen 1",
435 r"-16383.99998pt",
436 ),
437 (
438 int_to_dimen_too_big_3,
439 r"\count 1 = 400 \dimen 1 = - \count 1 in \the \dimen 1",
440 r"-16383.99998pt",
441 ),
442 (
443 int_to_dimen_too_big_4,
444 r"\count 1 = -400 \dimen 1 = - \count 1 in \the \dimen 1",
445 r"16383.99998pt",
446 ),
447 (
448 int_dimen_to_dimen_too_big_1,
449 r"\count 1 = 2 \dimen 1 = 10000pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
450 r"16383.99998pt",
451 ),
452 (
453 int_dimen_to_dimen_too_big_2,
454 r"\count 1 = 2 \dimen 1 = 10000pt \dimen 2 = -\count 1 \dimen 1 \the \dimen 2",
455 r"-16383.99998pt",
456 ),
457 (
458 int_dimen_to_dimen_too_big_3,
459 r"\count 1 = -2 \dimen 1 = 10000pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
460 r"-16383.99998pt",
461 ),
462 (
463 int_dimen_to_dimen_too_big_4,
464 r"\count 1 = 2 \dimen 1 = -10000pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
465 r"-16383.99998pt",
466 ),
467 ),
468 ];
469}