1use texlang::traits::*;
9use texlang::*;
10
11#[derive(Default)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct Component {
14 tokens: Vec<token::Token>,
15 #[cfg_attr(feature = "serde", serde(skip))]
16 writer: Option<token::Writer<Box<dyn std::io::Write>>>,
17 num_trailing_newlines: usize,
18 allow_undefined_command: bool,
19}
20
21impl Component {
22 fn push_token(&mut self, token: token::Token, interner: &token::CsNameInterner) {
23 self.tokens.push(token);
24 if let Some(writer) = &mut self.writer {
25 writer.write(interner, token).unwrap()
26 }
27 }
28}
29
30pub fn set_allow_undefined_command<S: HasComponent<Component>>(state: &mut S, value: bool) {
32 state.component_mut().allow_undefined_command = value;
33}
34
35pub fn get_newline<S: HasComponent<Component>>() -> command::BuiltIn<S> {
39 command::BuiltIn::new_execution(newline_primitive_fn)
40}
41
42fn newline_primitive_fn<S: HasComponent<Component>>(
43 t: token::Token,
44 input: &mut vm::ExecutionInput<S>,
45) -> command::Result<()> {
46 let (state, interner) = input.state_mut_and_cs_name_interner();
47 let mut c = state.component_mut();
48 let newline_token = token::Token::new_space('\n', t.trace_key());
49 c.push_token(newline_token, interner);
50 c.num_trailing_newlines += 1;
51 Ok(())
52}
53
54pub fn get_par<S: HasComponent<Component>>() -> command::BuiltIn<S> {
59 command::BuiltIn::new_execution(par_primitive_fn)
60}
61
62fn par_primitive_fn<S: HasComponent<Component>>(
63 t: token::Token,
64 input: &mut vm::ExecutionInput<S>,
65) -> command::Result<()> {
66 let (state, interner) = input.state_mut_and_cs_name_interner();
67 let mut c = state.component_mut();
68 let par_token = token::Token::new_space('\n', t.trace_key());
69 match c.num_trailing_newlines {
70 0 => {
71 c.push_token(par_token, interner);
72 c.push_token(par_token, interner);
73 c.num_trailing_newlines += 2;
74 }
75 1 => {
76 c.push_token(par_token, interner);
77 c.num_trailing_newlines += 1;
78 }
79 _ => {}
80 }
81 Ok(())
82}
83
84pub fn run<S: HasComponent<Component>>(
86 vm: &mut vm::VM<S>,
87) -> Result<Vec<token::Token>, Box<error::Error>> {
88 vm.run::<Handlers>()?;
89 let mut result = Vec::new();
90 std::mem::swap(&mut result, &mut vm.state.component_mut().tokens);
91 Ok(result)
92}
93
94pub fn run_and_write<S: HasComponent<Component>>(
96 vm: &mut vm::VM<S>,
97 io_writer: Box<dyn std::io::Write>,
98) -> Result<(), Box<error::Error>> {
99 vm.state.component_mut().writer = Some(token::Writer::new(io_writer));
100 vm.run::<Handlers>()
101}
102
103struct Handlers;
104
105impl<S: HasComponent<Component>> vm::Handlers<S> for Handlers {
106 fn character_handler(
107 mut token: token::Token,
108 input: &mut vm::ExecutionInput<S>,
109 ) -> command::Result<()> {
110 let (state, interner) = input.state_mut_and_cs_name_interner();
111 let mut c = state.component_mut();
112 if let Some('\n') = token.char() {
113 token = token::Token::new_space(' ', token.trace_key());
114 }
115 c.push_token(token, interner);
116 c.num_trailing_newlines = 0;
117 Ok(())
118 }
119
120 fn undefined_command_handler(
121 token: token::Token,
122 input: &mut vm::ExecutionInput<S>,
123 ) -> command::Result<()> {
124 if input.state().component().allow_undefined_command {
125 Handlers::character_handler(token, input)
126 } else {
127 Err(error::UndefinedCommandError::new(input.vm(), token).into())
128 }
129 }
130
131 fn unexpanded_expansion_command(
132 token: token::Token,
133 input: &mut vm::ExecutionInput<S>,
134 ) -> command::Result<()> {
135 Handlers::character_handler(token, input)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use std::collections::HashMap;
142
143 use super::*;
144 use crate::def;
145 use crate::testing::*;
146 use texlang::token;
147
148 fn initial_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
149 HashMap::from([
150 ("par", get_par()),
151 ("def", def::get_def()),
152 ("newline", get_newline()),
153 ])
154 }
155
156 macro_rules! script_tests {
157 ( $( ($name: ident, $input: expr, $want: expr) ),* $(,)? ) => {
158 $(
159 #[test]
160 fn $name() {
161 let options = vec![TestOption::InitialCommands(initial_commands)];
162 let input = $input;
163 let options = ResolvedOptions::new(&options);
164 let mut vm = initialize_vm(&options);
165 let got_tokens = execute_source_code(&mut vm, input, &options);
166 let got = token::write_tokens(&got_tokens.unwrap(), vm.cs_name_interner());
167 let want = $want.to_string();
168
169 if got != want {
170 println!("Output is different:");
171 println!("------[got]-------");
172 println!("{}", got);
173 println!("------[want]------");
174 println!("{}", want);
175 println!("-----------------");
176 panic!("write_tokens test failed");
177 }
178 }
179 )*
180 };
181 }
182
183 script_tests![
184 (char_newline_1, "H\nW", "H W"),
185 (newline_1, "H\\newline W", "H\nW"),
186 (newline_2, "H\\newline \\newline W", "H\n\nW"),
187 (newline_3, "H\\newline \\newline \\newline W", "H\n\n\nW"),
188 (par_1, "H\n\n\nW", "H\n\nW"),
189 (par_2, "H\n\n\n\n\nW", "H\n\nW"),
190 ];
191}