1use std::collections::HashMap;
7use texcraft_stdext::collections::groupingmap;
8use texlang::parse::Command;
9use texlang::traits::*;
10use texlang::*;
11
12pub const NEWINT_DOC: &str = r"Allocate a new integer
13
14Usage: `\newInt <control sequence>`
15
16The `\newInt` command allocates a new integer
17that is referenced using the provided control sequence.
18Simple example:
19
20```
21\newInt \myvariable
22\myvariable = 4
23\advance \myvariable by 5
24\asserteq{\the \myvariable}{9}
25```
26
27You can think of `\newInt` as being a replacement for
28Plain TeX's `\newcount` macro (TeXBook p346).
29The benefit of `\newInt` is that different callers of the command
30do not share the underlying memory; the allocated memory is unique
31to the caller.
32Under the hood `\newInt` works by allocating new memory on the TeX engine's heap.
33";
34
35pub const NEWINTARRAY_DOC: &str = r"Allocate a new integer array
36
37Usage: `\newIntArray <control sequence> <array length>`
38
39The `\newIntArray` command allocates a new array of integers that
40is referenced using the provided control sequence.
41This new control sequence works pretty much like `\count`, but you can create
42 as many arrays as you like and don't need to worry about other
43 TeX code reusing the memory.
44Unlike `\count`, the size of the array is not fixed by
45the engine.
46The only constraint on the size is that you have enough RAM
47 on the machine to store it.
48Simple example:
49
50```
51\newIntArray \myarray 3
52\myarray 0 = 4
53\asserteq{\the \myarray 0}{4}
54\myarray 1 = 5
55\asserteq{\the \myarray 1}{5}
56\myarray 2 = 6
57\asserteq{\the \myarray 2}{6}
58```
59
60The new control sequence can *not* be aliased using \let.
61";
62
63#[derive(Default)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct Component {
67 singletons: Vec<i32>,
68 arrays: Vec<i32>,
69 array_refs: HashMap<token::CsName, (usize, usize)>,
70}
71
72pub fn get_newint<S: HasComponent<Component>>() -> command::BuiltIn<S> {
74 command::BuiltIn::new_execution(newint_primitive_fn)
75}
76
77fn newint_primitive_fn<S: HasComponent<Component>>(
78 _: token::Token,
79 input: &mut vm::ExecutionInput<S>,
80) -> command::Result<()> {
81 let Command::ControlSequence(name) = Command::parse(input)?;
82 let component = input.state_mut().component_mut();
83 let index = component.singletons.len();
84 component.singletons.push(Default::default());
85 input.commands_map_mut().insert_variable_command(
86 name,
87 variable::Command::new_array(
88 singleton_ref_fn,
89 singleton_mut_ref_fn,
90 variable::IndexResolver::Static(variable::Index(index)),
91 ),
92 groupingmap::Scope::Local,
93 );
94 Ok(())
95}
96
97fn singleton_ref_fn<S: HasComponent<Component>>(state: &S, index: variable::Index) -> &i32 {
98 &state.component().singletons[index.0]
99}
100
101fn singleton_mut_ref_fn<S: HasComponent<Component>>(
102 state: &mut S,
103 index: variable::Index,
104) -> &mut i32 {
105 &mut state.component_mut().singletons[index.0]
106}
107
108pub fn get_newint_getter_provider<S: HasComponent<Component>>() -> command::BuiltIn<S> {
113 variable::Command::new_getter_provider(singleton_ref_fn, singleton_mut_ref_fn).into()
114}
115
116pub fn get_newintarray<S: HasComponent<Component>>() -> command::BuiltIn<S> {
118 command::BuiltIn::new_execution(newintarray_primitive_fn)
119}
120
121fn newintarray_primitive_fn<S: HasComponent<Component>>(
122 _: token::Token,
123 input: &mut vm::ExecutionInput<S>,
124) -> command::Result<()> {
125 let Command::ControlSequence(name) = Command::parse(input)?;
126 let len = usize::parse(input)?;
127 let component = input.state_mut().component_mut();
128 let start = component.arrays.len();
129 component.arrays.resize(start + len, Default::default());
130 component.array_refs.insert(name, (start, len));
131 input.commands_map_mut().insert_variable_command(
132 name,
133 variable::Command::new_array(
134 array_element_ref_fn,
135 array_element_mut_ref_fn,
136 variable::IndexResolver::Dynamic(resolve),
137 ),
138 groupingmap::Scope::Local,
139 );
140 Ok(())
142}
143
144fn resolve<S: HasComponent<Component>>(
145 token: token::Token,
146 input: &mut vm::ExpandedStream<S>,
147) -> command::Result<variable::Index> {
148 let name = match token.value() {
149 token::Value::ControlSequence(name) => name,
150 _ => todo!(),
151 };
152 let (array_index, array_len) = *input.state().component().array_refs.get(&name).unwrap();
153 let inner_index = usize::parse(input)?;
154 if inner_index >= array_len {
155 return Err(error::SimpleTokenError::new(input.vm(),
156 token,
157 format![
158 "Array out of bounds: cannot access index {inner_index} of array with length {array_len}"
159 ],
160 )
161 .into());
162 }
163 Ok(variable::Index(array_index + inner_index))
164}
165
166fn array_element_ref_fn<S: HasComponent<Component>>(state: &S, index: variable::Index) -> &i32 {
167 &state.component().arrays[index.0]
168}
169
170fn array_element_mut_ref_fn<S: HasComponent<Component>>(
171 state: &mut S,
172 index: variable::Index,
173) -> &mut i32 {
174 &mut state.component_mut().arrays[index.0]
175}
176
177pub fn get_newintarray_getter_provider<S: HasComponent<Component>>() -> command::BuiltIn<S> {
182 variable::Command::new_array(
183 array_element_ref_fn,
184 array_element_mut_ref_fn,
185 variable::IndexResolver::Dynamic(resolve),
186 )
187 .into()
188}
189
190#[cfg(test)]
191mod test {
192 use super::*;
193 use crate::the::get_the;
194 use crate::{script, testing::*};
195 use texlang::vm::implement_has_component;
196
197 #[derive(Default)]
198 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199 struct State {
200 alloc: Component,
201 script: script::Component,
202 }
203
204 impl TexlangState for State {}
205
206 implement_has_component![State, (Component, alloc), (script::Component, script),];
207
208 fn initial_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
209 HashMap::from([
210 ("newInt", get_newint()),
211 ("newInt_getter_provider_\u{0}", get_newint_getter_provider()),
212 ("newIntArray", get_newintarray()),
213 (
214 "newIntArray_getter_provider_\u{0}",
215 get_newintarray_getter_provider(),
216 ),
217 ("the", get_the()),
218 ])
219 }
220
221 test_suite![
222 expansion_equality_tests(
223 (newint_base_case, r"\newInt\a \a=3 \the\a", "3"),
224 (
225 newintarray_base_case_0,
226 r"\newIntArray \a 3 \a 0 = 2 \the\a 0",
227 "2"
228 ),
229 (
230 newintarray_base_case_1,
231 r"\newIntArray \a 3 \a 1 = 2 \the\a 1",
232 "2"
233 ),
234 (
235 newintarray_base_case_2,
236 r"\newIntArray \a 3 \a 2 = 2 \the\a 2",
237 "2"
238 ),
239 ),
240 serde_tests(
241 (serde_singleton, r"\newInt\a \a=-1 ", r"\the\a"),
242 (serde_array, r"\newIntArray\a 20 \a 3=-1 ", r"\the\a 3"),
243 ),
244 failure_tests(
245 (newintarray_out_of_bounds, r"\newIntArray \a 3 \a 3 = 2"),
246 (newintarray_negative_index, r"\newIntArray \a 3 \a -3 = 2"),
247 (newintarray_negative_length, r"\newIntArray \a -3"),
248 ),
249 ];
250}