1extern crate texcraft_stdext;
6extern crate texlang;
7
8use std::collections::HashMap;
9
10use texlang::command;
11use texlang::prelude as txl;
12use texlang::token;
13use texlang::traits::*;
14use texlang::types;
15use texlang::types::CatCode;
16use texlang::vm;
17use texlang::vm::implement_has_component;
18use texlang_common::HasFileSystem;
19use texlang_common::HasLogging;
20use texlang_common::HasTerminalIn;
21
22pub mod alias;
23pub mod alloc;
24pub mod chardef;
25pub mod codes;
26pub mod conditional;
27pub mod def;
28pub mod endlinechar;
29pub mod errormode;
30pub mod expansion;
31pub mod input;
32pub mod job;
33pub mod math;
34pub mod mathchardef;
35pub mod prefix;
36pub mod registers;
37pub mod repl;
38pub mod script;
39pub mod sleep;
40pub mod texcraft;
41pub mod the;
42pub mod time;
43pub mod tracingmacros;
44
45#[derive(Default)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub struct StdLibState {
49 pub alloc: alloc::Component,
50 pub codes_cat_code: codes::Component<CatCode>,
51 pub codes_math_code: codes::Component<types::MathCode>,
52 pub conditional: conditional::Component,
53 pub end_line_char: endlinechar::Component,
54 pub error_mode: errormode::Component,
55 pub input: input::Component<16>,
56 pub job: job::Component,
57 pub prefix: prefix::Component,
58 pub registers_i32: registers::Component<i32, 32768>,
59 pub registers_scaled: registers::Component<core::Scaled, 32768>,
60 pub registers_glue: registers::Component<core::Glue, 32768>,
61 pub registers_token_list: registers::Component<Vec<token::Token>, 256>,
62 pub repl: repl::Component,
63 pub script: script::Component,
64 #[cfg(test)]
65 pub testing: texlang_testing::TestingComponent,
66 pub time: time::Component,
67 pub tracing_macros: tracingmacros::Component,
68}
69
70impl TexlangState for StdLibState {
71 #[inline]
72 fn cat_code(&self, c: char) -> texlang::types::CatCode {
73 codes::cat_code(self, c)
74 }
75
76 #[inline]
77 fn end_line_char(&self) -> Option<char> {
78 endlinechar::end_line_char(self)
79 }
80
81 #[inline]
82 fn post_macro_expansion_hook(
83 token: texlang::token::Token,
84 input: &vm::ExpansionInput<Self>,
85 tex_macro: &texlang::texmacro::Macro,
86 arguments: &[&[texlang::token::Token]],
87 reversed_expansion: &[texlang::token::Token],
88 ) {
89 tracingmacros::hook(token, input, tex_macro, arguments, reversed_expansion)
90 }
91
92 #[inline]
93 fn expansion_override_hook(
94 token: texlang::token::Token,
95 input: &mut vm::ExpansionInput<Self>,
96 tag: Option<texlang::command::Tag>,
97 ) -> txl::Result<Option<texlang::token::Token>> {
98 expansion::noexpand_hook(token, input, tag)
99 }
100
101 #[inline]
102 fn variable_assignment_scope_hook(
103 state: &mut Self,
104 ) -> texcraft_stdext::collections::groupingmap::Scope {
105 prefix::variable_assignment_scope_hook(state)
106 }
107
108 fn recoverable_error_hook(
109 &self,
110 recoverable_error: texlang::error::TracedTexError,
111 ) -> Result<(), Box<dyn texlang::error::TexError>> {
112 errormode::recoverable_error_hook(self, recoverable_error)
113 }
114}
115
116impl the::TheCompatible for StdLibState {}
117
118implement_has_component![StdLibState{
119 alloc: alloc::Component,
120 codes_cat_code: codes::Component<CatCode>,
121 codes_math_code: codes::Component<types::MathCode>,
122 conditional: conditional::Component,
123 end_line_char: endlinechar::Component,
124 error_mode: errormode::Component,
125 input: input::Component<16>,
126 job: job::Component,
127 prefix: prefix::Component,
128 registers_i32: registers::Component<i32, 32768>,
129 registers_scaled: registers::Component<core::Scaled, 32768>,
130 registers_glue: registers::Component<core::Glue, 32768>,
131 registers_token_list: registers::Component<Vec<token::Token>, 256>,
132 repl: repl::Component,
133 script: script::Component,
134 time: time::Component,
135 tracing_macros: tracingmacros::Component,
136}];
137
138impl texlang_common::HasLogging for StdLibState {}
139impl texlang_common::HasFileSystem for StdLibState {}
140impl texlang_common::HasTerminalIn for StdLibState {
141 fn terminal_in(&self) -> std::rc::Rc<std::cell::RefCell<dyn texlang_common::TerminalIn>> {
142 self.error_mode.terminal_in()
143 }
144}
145
146pub fn built_in_commands<S>() -> HashMap<&'static str, command::BuiltIn<S>>
147where
148 S: TexlangState
149 + HasFileSystem
150 + HasTerminalIn
151 + HasLogging
152 + HasComponent<alloc::Component>
153 + HasComponent<codes::Component<CatCode>>
154 + HasComponent<codes::Component<types::MathCode>>
155 + HasComponent<conditional::Component>
156 + the::TheCompatible
157 + HasComponent<endlinechar::Component>
158 + HasComponent<errormode::Component>
159 + HasComponent<input::Component<16>>
160 + HasComponent<job::Component>
161 + HasComponent<prefix::Component>
162 + HasComponent<registers::Component<i32, 32768>>
163 + HasComponent<registers::Component<core::Scaled, 32768>>
164 + HasComponent<registers::Component<core::Glue, 32768>>
165 + HasComponent<registers::Component<Vec<token::Token>, 256>>
166 + HasComponent<repl::Component>
167 + HasComponent<script::Component>
168 + HasComponent<time::Component>
169 + HasComponent<tracingmacros::Component>,
170{
171 HashMap::from([
172 ("advance", math::get_advance()),
173 ("batchmode", errormode::get_batchmode()),
175 ("catcode", codes::get_catcode()),
177 ("closein", input::get_closein()),
178 ("chardef", chardef::get_chardef()),
179 ("count", registers::get_count()),
180 ("countdef", registers::get_countdef()),
181 ("day", time::get_day()),
183 ("def", def::get_def()),
184 ("dimen", registers::get_dimen()),
185 ("divide", math::get_divide()),
186 ("dumpFormat", job::get_dumpformat()),
187 ("dumpValidate", job::get_dumpvalidate()),
188 ("else", conditional::get_else()),
190 ("endinput", input::get_endinput()),
191 ("endlinechar", endlinechar::get_endlinechar()),
192 ("errorstopmode", errormode::get_errorstopmode()),
193 ("expandafter", expansion::get_expandafter_optimized()),
194 ("fi", conditional::get_fi()),
196 ("gdef", def::get_gdef()),
198 ("global", prefix::get_global()),
199 ("globaldefs", prefix::get_globaldefs()),
200 ("ifcase", conditional::get_ifcase()),
202 ("ifeof", input::get_ifeof()),
203 ("iffalse", conditional::get_iffalse()),
204 ("ifnum", conditional::get_ifnum()),
205 ("ifodd", conditional::get_ifodd()),
206 ("iftrue", conditional::get_iftrue()),
207 ("input", input::get_input()),
208 ("jobname", job::get_jobname()),
210 ("let", alias::get_let()),
212 ("long", prefix::get_long()),
213 ("mathchardef", mathchardef::get_mathchardef()),
215 ("mathcode", codes::get_mathcode()),
216 ("month", time::get_month()),
217 ("multiply", math::get_multiply()),
218 ("newInt", alloc::get_newint()),
220 (
221 "newInt_getter_provider_\u{0}",
222 alloc::get_newint_getter_provider(),
223 ),
224 ("newIntArray", alloc::get_newintarray()),
225 (
226 "newIntArray_getter_provider_\u{0}",
227 alloc::get_newintarray_getter_provider(),
228 ),
229 ("noexpand", expansion::get_noexpand()),
230 ("nonstopmode", errormode::get_nonstopmode()),
231 ("or", conditional::get_or()),
233 ("openin", input::get_openin()),
234 ("outer", prefix::get_outer()),
235 ("read", input::get_read()),
237 ("relax", expansion::get_relax()),
238 ("scrollmode", errormode::get_scrollmode()),
240 ("skip", registers::get_skip()),
241 ("sleep", sleep::get_sleep()),
242 ("the", the::get_the()),
244 ("time", time::get_time()),
245 ("toks", registers::get_toks()),
246 ("toksdef", registers::get_toksdef()),
247 ("tracingmacros", tracingmacros::get_tracingmacros()),
248 ("year", time::get_year()),
250 ])
251}
252
253impl HasDefaultBuiltInCommands for StdLibState {
254 fn default_built_in_commands() -> HashMap<&'static str, command::BuiltIn<Self>> {
255 built_in_commands()
256 }
257}
258
259pub struct ErrorCase {
261 pub description: &'static str,
262 pub source_code: &'static str,
263}
264
265impl ErrorCase {
266 pub fn all_error_cases() -> Vec<ErrorCase> {
271 let mut cases = vec![];
272 for (description, source_code) in vec![
273 (r"\toks starts with a letter token", r"\toks 0 = a"),
274 (
275 r"\toks starts with a non-variable command",
276 r"\toks 0 = \def",
277 ),
278 (
279 r"\toks starts is a variable command of the wrong type",
280 r"\toks 0 = \count 0",
281 ),
282 (
283 r"end of input while scanning token list",
284 r"\toks 0 = { no closing brace",
285 ),
286 (r"assign number from \toks", r"\count 0 = \toks 0"),
287 (r"end of input right after \toks", r"\toks 0"),
288 (r"\count is out of bounds (negative)", r"\count -200"),
289 (r"\count is out of bounds (positive)", r"\count 2000000"),
290 ("file does not exist", r"\input doesNotExist"),
291 ("end of input after \\global", r"\global"),
292 ("can't be prefixed by \\global", r"\global \sleep"),
293 ("can't be prefixed by \\global (character)", r"\global a"),
294 ("can't be prefixed by \\long", r"\long \let \a = \def"),
295 ("can't be prefixed by \\outer", r"\outer \let \a = \def"),
296 ("bad rhs in assignment", r"\year = X"),
297 ("invalid variable (undefined)", r"\advance \undefined by 4"),
298 (
299 "invalid variable (not a variable command)",
300 r"\advance \def by 4",
301 ),
302 ("invalid variable (character token)", r"\advance a by 4"),
303 ("invalid variable (eof)", r"\advance"),
304 ("invalid relation", r"\ifnum 3 z 4"),
305 ("malformed by keyword", r"\advance \year bg"),
306 ("undefined control sequence", r"\elephant"),
307 ("invalid character", "\u{7F}"),
308 ("empty control sequence", r"\"),
309 ("invalid end of group", r"}"),
310 ("invalid start of number", r"\count X"),
311 ("invalid start of number (eof)", r"\count"),
312 ("invalid start of number (not a variable)", r"\count \def"),
313 (
314 "case negative number to positive (from constant)",
315 r"\count -1",
316 ),
317 (
318 "cast negative number to positive (from variable)",
319 r"\count 0 = 1 \count - \count 0",
320 ),
321 (
322 "read positive number from negative variable value",
323 r"\count 0 = -1 \count \count 0",
324 ),
325 ("invalid character", r"\count `\def"),
326 ("invalid character (eof)", r"\count `"),
327 ("invalid octal digit", r"\count '9"),
328 ("invalid octal digit (eof)", r"\count '"),
329 ("invalid hexadecimal digit", "\\count \"Z"),
330 ("invalid hexadecimal digit (eof)", "\\count \""),
331 (
332 "decimal number too big (radix)",
333 r"\count 1000000000000000000000",
334 ),
335 (
336 "decimal number too big (sum)",
337 r"\count 18446744073709551617",
338 ),
339 ("octal number too big", r"\count '7777777777777777777777"),
340 (
341 "hexadecimal number too big",
342 "\\count \"AAAAAAAAAAAAAAAAAAAAAA",
343 ),
344 ("number with letter catcode", r"\catcode `1 = 11 \count 1"),
345 (
346 "non-arithmetic argument to math command",
347 r"\advance \catcode 1 by 3",
348 ),
349 ("category code out of bounds", r"\catcode 0 = 17"),
360 ("invalid command target", r"\let a = \year"),
361 ("invalid command target (eof)", r"\let"),
362 ] {
363 cases.push(ErrorCase {
364 description,
365 source_code,
366 })
367 }
368 cases
369 }
370}
371
372#[cfg(test)]
373impl HasComponent<texlang_testing::TestingComponent> for StdLibState {
374 fn component(&self) -> &texlang_testing::TestingComponent {
375 &self.testing
376 }
377
378 fn component_mut(&mut self) -> &mut texlang_testing::TestingComponent {
379 &mut self.testing
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use texlang_testing::*;
387
388 fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<StdLibState>> {
389 StdLibState::default_built_in_commands()
390 }
391
392 type State = StdLibState;
393
394 test_suite![
395 expansion_equality_tests(
396 (
397 relation_before_spaces,
398 r"\countdef\A1 \A=2 \def\cmp#1{\ifnum#1 <3}\cmp{\A}Pass\else Fail \fi",
399 r"Pass"
400 ),
401 (
402 overwrite_else,
403 r"\def\else{}\ifodd 2 \else should be skipped \fi",
404 r""
405 ),
406 (
407 math_and_active_char,
408 r"-\catcode`\A=13 \countdef A5 \countdef ~6 ~=7 A=8 \advance~byA \the~",
409 r"- 15",
410 ),
411 (
425 texbook_exercise_20_7,
426 r"\catcode`\[=1 \catcode`\]=2 \catcode`\!=6 \def\!!1#2![{!#]#!!2}\! x{[y]][z}",
427 r"\catcode`\[=1 \catcode`\]=2 \catcode`\!=6 {#]![y][z}",
428 ),
429 (
430 variable_assignment_space_before_equal,
431 r"\def\assign#1{#1 = 20\relax}\assign\year\the\year",
432 "20",
433 ),
434 ),
435 serde_tests((serde_sanity, r"\def\HW{Hello World} ", r"\HW"),),
436 ];
437
438 #[test]
439 fn all_error_cases() {
440 let options = vec![TestOption::BuiltInCommands(
441 StdLibState::default_built_in_commands,
442 )];
443 for case in ErrorCase::all_error_cases() {
444 println!("CASE {}", case.description);
445 run_fatal_error_test::<StdLibState>(case.source_code, &options, false)
446 }
447 }
448}