1extern crate texcraft_stdext;
6extern crate texlang;
7
8use std::collections::HashMap;
9
10use texlang::command;
11use texlang::traits::*;
12use texlang::vm;
13use texlang::vm::implement_has_component;
14
15pub mod alias;
16pub mod alloc;
17pub mod catcode;
18pub mod conditional;
19pub mod def;
20pub mod expansion;
21pub mod io;
22pub mod job;
23pub mod math;
24pub mod prefix;
25pub mod registers;
26pub mod repl;
27pub mod script;
28pub mod sleep;
29pub mod testing;
30pub mod texcraft;
31pub mod the;
32pub mod time;
33pub mod tracingmacros;
34
35#[derive(Default)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct StdLibState {
39 alloc: alloc::Component,
40 catcode: catcode::Component,
41 conditional: conditional::Component,
42 pub job: job::Component,
43 prefix: prefix::Component,
44 registers: registers::Component<i32, 32768>,
45 repl: repl::Component,
46 script: script::Component,
47 time: time::Component,
48 tracing_macros: tracingmacros::Component,
49}
50
51impl TexlangState for StdLibState {
52 #[inline]
53 fn cat_code(&self, c: char) -> texlang::token::CatCode {
54 catcode::cat_code(self, c)
55 }
56
57 #[inline]
58 fn post_macro_expansion_hook(
59 token: texlang::token::Token,
60 input: &vm::ExpansionInput<Self>,
61 tex_macro: &texlang::texmacro::Macro,
62 arguments: &[&[texlang::token::Token]],
63 reversed_expansion: &[texlang::token::Token],
64 ) {
65 tracingmacros::hook(token, input, tex_macro, arguments, reversed_expansion)
66 }
67
68 #[inline]
69 fn expansion_override_hook(
70 token: texlang::token::Token,
71 input: &mut vm::ExpansionInput<Self>,
72 tag: Option<texlang::command::Tag>,
73 ) -> command::Result<Option<texlang::token::Token>> {
74 expansion::noexpand_hook(token, input, tag)
75 }
76
77 #[inline]
78 fn variable_assignment_scope_hook(
79 state: &mut Self,
80 ) -> texcraft_stdext::collections::groupingmap::Scope {
81 prefix::variable_assignment_scope_hook(state)
82 }
83}
84
85impl StdLibState {
86 pub fn all_initial_built_ins(
87 ) -> HashMap<&'static str, texlang::command::BuiltIn<StdLibState>> {
88 HashMap::from([
89 ("advance", math::get_advance()),
90 ("catcode", catcode::get_catcode()),
92 ("count", registers::get_count()),
93 ("countdef", registers::get_countdef()),
94 ("day", time::get_day()),
96 ("def", def::get_def()),
97 ("divide", math::get_divide()),
98 ("dumpFormat", job::get_dumpformat()),
99 ("dumpValidate", job::get_dumpvalidate()),
100 ("else", conditional::get_else()),
102 ("expandafter", expansion::get_expandafter_optimized()),
103 ("fi", conditional::get_fi()),
105 ("gdef", def::get_gdef()),
107 ("global", prefix::get_global()),
108 ("globaldefs", prefix::get_globaldefs()),
109 ("ifcase", conditional::get_if_case()),
111 ("iffalse", conditional::get_if_false()),
112 ("ifnum", conditional::get_if_num()),
113 ("ifodd", conditional::get_if_odd()),
114 ("iftrue", conditional::get_if_true()),
115 ("input", io::get_input()),
116 ("jobname", job::get_jobname()),
118 ("let", alias::get_let()),
120 ("long", prefix::get_long()),
121 ("month", time::get_month()),
123 ("multiply", math::get_multiply()),
124 ("newInt", alloc::get_newint()),
126 (
127 "newInt_getter_provider_\u{0}",
128 alloc::get_newint_getter_provider(),
129 ),
130 ("newIntArray", alloc::get_newintarray()),
131 (
132 "newIntArray_getter_provider_\u{0}",
133 alloc::get_newintarray_getter_provider(),
134 ),
135 ("noexpand", expansion::get_noexpand()),
136 ("or", conditional::get_or()),
138 ("outer", prefix::get_outer()),
139 ("relax", expansion::get_relax()),
141 ("sleep", sleep::get_sleep()),
143 ("the", the::get_the()),
145 ("time", time::get_time()),
146 ("tracingmacros", tracingmacros::get_tracingmacros()),
147 ("year", time::get_year()),
149 ])
150 }
151
152 pub fn new() -> Box<vm::VM<StdLibState>> {
153 vm::VM::<StdLibState>::new(StdLibState::all_initial_built_ins())
154 }
155}
156
157implement_has_component![
158 StdLibState,
159 (alloc::Component, alloc),
160 (catcode::Component, catcode),
161 (conditional::Component, conditional),
162 (job::Component, job),
163 (prefix::Component, prefix),
164 (registers::Component<i32, 32768>, registers),
165 (repl::Component, repl),
166 (script::Component, script),
167 (time::Component, time),
168 (tracingmacros::Component, tracing_macros),
169];
170
171pub struct ErrorCase {
172 pub description: &'static str,
173 pub source_code: &'static str,
174}
175
176impl ErrorCase {
177 pub fn all_error_cases() -> Vec<ErrorCase> {
179 let mut cases = vec![];
180 for (description, source_code) in vec![
181 ("end of input after \\global", r"\global"),
182 ("can't be prefixed by \\global", r"\global \sleep"),
183 ("can't be prefixed by \\global (character)", r"\global a"),
184 ("can't be prefixed by \\long", r"\long \let \a = \def"),
185 ("can't be prefixed by \\outer", r"\outer \let \a = \def"),
186 ("bad rhs in assignment", r"\year = X"),
187 ("invalid variable (undefined)", r"\advance \undefined by 4"),
188 (
189 "invalid variable (not a variable command)",
190 r"\advance \def by 4",
191 ),
192 ("invalid variable (character token)", r"\advance a by 4"),
193 ("invalid variable (eof)", r"\advance"),
194 ("invalid relation", r"\ifnum 3 z 4"),
195 ("malformed by keyword", r"\advance \year bg"),
196 ("undefined control sequence", r"\elephant"),
197 ("invalid character", "\u{7F}"),
198 ("empty control sequence", r"\"),
199 ("invalid end of group", r"}"),
200 ("invalid start of number", r"\count X"),
201 ("invalid start of number (eof)", r"\count"),
202 ("invalid start of number (not a variable)", r"\count \def"),
203 (
204 "case negative number to positive (from constant)",
205 r"\count -1",
206 ),
207 (
208 "cast negative number to positive (from variable)",
209 r"\count 0 = 1 \count - \count 0",
210 ),
211 (
212 "read positive number from negative variable value",
213 r"\count 0 = -1 \count \count 0",
214 ),
215 ("invalid character", r"\count `\def"),
216 ("invalid character (eof)", r"\count `"),
217 ("invalid octal digit", r"\count '9"),
218 ("invalid octal digit (eof)", r"\count '"),
219 ("invalid hexadecimal digit", "\\count \"Z"),
220 ("invalid hexadecimal digit (eof)", "\\count \""),
221 (
222 "decimal number too big (radix)",
223 r"\count 1000000000000000000000",
224 ),
225 (
226 "decimal number too big (sum)",
227 r"\count 18446744073709551617",
228 ),
229 ("octal number too big", r"\count '7777777777777777777777"),
230 (
231 "hexadecimal number too big",
232 "\\count \"AAAAAAAAAAAAAAAAAAAAAA",
233 ),
234 ("number with letter catcode", r"\catcode `1 = 11 \count 1"),
235 ("category code out of bounds", r"\catcode 0 = 17"),
251 ("invalid command target", r"\let a = \year"),
252 ("invalid command target (eof)", r"\let"),
253 ] {
254 cases.push(ErrorCase {
255 description,
256 source_code,
257 })
258 }
259 cases
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use testing::*;
267 use texlang::command;
268
269 type State = StdLibState;
270
271 fn initial_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
272 StdLibState::all_initial_built_ins()
273 }
274
275 test_suite![
276 expansion_equality_tests((
277 overwrite_else,
278 r"\def\else{}\ifodd 2 \else should be skipped \fi",
279 r""
280 )),
281 serde_tests((serde_sanity, r"\def\HW{Hello World} ", r"\HW"),),
282 ];
283
284 #[test]
285 fn all_error_cases() {
286 let options = vec![
287 TestOption::InitialCommands(StdLibState::all_initial_built_ins),
288 TestOption::AllowUndefinedCommands(false),
289 ];
290 for case in ErrorCase::all_error_cases() {
291 println!("CASE {}", case.description);
292 run_failure_test::<StdLibState>(case.source_code, &options)
293 }
294 }
295}