1use std::cell::RefCell;
8use std::ops::{Deref, DerefMut};
9use std::rc::Rc;
10use texlang::prelude as txl;
11use texlang::traits::*;
12use texlang::*;
13
14pub struct Component {
15 io_writer: Rc<RefCell<dyn std::io::Write>>,
16 writer: token::Writer,
17}
18
19impl Default for Component {
20 fn default() -> Self {
21 Self {
22 io_writer: Rc::new(RefCell::new(std::io::sink())),
23 writer: Default::default(),
24 }
25 }
26}
27
28#[cfg(feature = "serde")]
29impl serde::Serialize for Component {
30 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
31 where
32 S: serde::Serializer,
33 {
34 ().serialize(serializer)
35 }
36}
37
38#[cfg(feature = "serde")]
39impl<'de> serde::Deserialize<'de> for Component {
40 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
41 where
42 D: serde::Deserializer<'de>,
43 {
44 <()>::deserialize(deserializer)?;
45 Ok(Default::default())
46 }
47}
48
49impl Component {
50 fn write_token<S: HasComponent<Self>>(input: &mut vm::ExecutionInput<S>, token: token::Token) {
51 let vm::Parts {
52 state,
53 cs_name_interner,
54 ..
55 } = input.vm_parts();
56 let c = state.component_mut();
57 c.writer
58 .write(
59 c.io_writer.borrow_mut().deref_mut(),
60 cs_name_interner,
61 token.value(),
62 )
63 .unwrap()
64 }
65}
66
67pub fn get_newline<S: HasComponent<Component>>() -> command::BuiltIn<S> {
71 command::BuiltIn::new_execution(newline_primitive_fn)
72}
73
74fn newline_primitive_fn<S: HasComponent<Component>>(
75 _: token::Token,
76 input: &mut vm::ExecutionInput<S>,
77) -> txl::Result<()> {
78 input.state_mut().component_mut().writer.add_newline();
79 Ok(())
80}
81
82pub fn get_par<S: HasComponent<Component>>() -> command::BuiltIn<S> {
87 command::BuiltIn::new_execution(par_primitive_fn)
88}
89
90fn par_primitive_fn<S: HasComponent<Component>>(
91 _: token::Token,
92 input: &mut vm::ExecutionInput<S>,
93) -> txl::Result<()> {
94 input.state_mut().component_mut().writer.start_paragraph();
95 Ok(())
96}
97
98pub fn run_to_string<S: HasComponent<Component>>(
100 vm: &mut vm::VM<S>,
101) -> Result<String, Box<error::TracedTexError>> {
102 let buffer = Rc::new(RefCell::new(Vec::<u8>::new()));
103 vm.state.component_mut().io_writer = buffer.clone();
104 run(vm)?;
105 let result: String = std::str::from_utf8(buffer.borrow().deref()).unwrap().into();
106 Ok(result)
107}
108
109pub fn run<S: HasComponent<Component>>(
111 vm: &mut vm::VM<S>,
112) -> Result<(), Box<error::TracedTexError>> {
113 vm.run::<Handlers>()
114}
115
116pub fn set_io_writer<S: HasComponent<Component>, I: std::io::Write + 'static>(
118 vm: &mut vm::VM<S>,
119 writer: I,
120) {
121 vm.state.component_mut().io_writer = Rc::new(RefCell::new(writer))
122}
123
124struct Handlers;
125
126impl<S: HasComponent<Component>> vm::Handlers<S> for Handlers {
127 fn character_handler(
128 input: &mut vm::ExecutionInput<S>,
129 token: token::Token,
130 _: char,
131 ) -> txl::Result<()> {
132 Component::write_token(input, token);
134 Ok(())
135 }
136
137 fn unexpanded_expansion_command(
138 input: &mut vm::ExecutionInput<S>,
139 token: token::Token,
140 ) -> txl::Result<()> {
141 Component::write_token(input, token);
142 Ok(())
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use std::collections::HashMap;
150
151 #[derive(Default)]
152 struct State {
153 script: Component,
154 }
155
156 impl vm::TexlangState for State {}
157
158 implement_has_component![State { script: Component }];
159
160 fn run_script_test(input: &str, want: &str) {
161 let built_ins = HashMap::from([("par", get_par()), ("newline", get_newline())]);
162 let mut vm = vm::VM::<State>::new_with_built_in_commands(built_ins);
163 vm.push_source("testing.tex", input).unwrap();
164 let got = run_to_string(&mut vm).unwrap();
165 let want = want.to_string();
166
167 if got != want {
168 println!("Output is different:");
169 println!("------[got]-------");
170 println!("{}", got);
171 println!("------[want]------");
172 println!("{}", want);
173 println!("-----------------");
174 panic!("run_script test failed");
175 }
176 }
177
178 macro_rules! script_tests {
179 ( $( ($name: ident, $input: expr, $want: expr) ),* $(,)? ) => {
180 $(
181 #[test]
182 fn $name() {
183 run_script_test($input, $want);
184 }
185 )*
186 };
187 }
188
189 script_tests![
190 (char_newline_1, "H\nW", "H W"),
191 (newline_1, "H\\newline W", "H\nW"),
192 (newline_2, "H\\newline \\newline W", "H\n\nW"),
193 (newline_3, "H\\newline \\newline \\newline W", "H\n\n\nW"),
194 (par_1, "H\n\n\nW", "H\n\nW"),
195 (par_2, "H\n\n\n\n\nW", "H\n\nW"),
196 ];
197}