1use ::rustex_lib::engine::{CompilationResult, RusTeXEngineExt};
2use flams_utils::binary::BinaryWriter;
3use parking_lot::Mutex;
4use std::io::Write;
5use std::path::Path;
6use tex_engine::prelude::Mouth;
7
8#[allow(clippy::module_inception)]
9mod rustex {
10 pub use rustex_lib;
11 pub use rustex_lib::engine::commands::{
12 register_primitives_postinit, register_primitives_preinit,
13 };
14 pub use rustex_lib::engine::files::RusTeXFileSystem;
15 pub use rustex_lib::engine::output::{OutputCont, RusTeXOutput};
16 pub use rustex_lib::engine::stomach::RusTeXStomach;
17 pub use rustex_lib::engine::{fonts::Fontsystem, state::RusTeXState};
18 pub use rustex_lib::engine::{Extension, RusTeXEngine, RusTeXEngineT, Types};
19 pub use tex_engine::commands::{Macro, MacroSignature, TeXCommand};
20 pub use tex_engine::engine::filesystem::FileSystem;
21 pub use tex_engine::engine::gullet::DefaultGullet;
22 pub use tex_engine::engine::mouth::DefaultMouth;
23 pub use tex_engine::engine::{DefaultEngine, EngineAux};
24 pub use tex_engine::pdflatex::{nodes::PDFExtension, PDFTeXEngine};
25 pub use tex_engine::prelude::{CSName, InternedCSName, Token, TokenList};
26 pub use tex_engine::tex::tokens::control_sequences::CSInterner;
27 pub use tex_engine::tex::tokens::StandardToken;
28 pub use tex_engine::{engine::utils::memory::MemoryManager, tex::tokens::CompactToken};
29 pub use tracing::{debug, error, instrument, trace, warn};
30 pub type RTSettings = rustex_lib::engine::Settings;
31 pub use rustex_lib::ImageOptions;
32}
33pub use rustex::OutputCont;
34#[allow(clippy::wildcard_imports)]
35use rustex::*;
36
37struct FileOutput(std::cell::RefCell<std::io::BufWriter<std::fs::File>>);
38impl FileOutput {
39 fn new(path: &Path) -> Self {
40 let f = std::fs::File::create(path).expect("This should not happen!");
41 let buf = std::io::BufWriter::new(f);
42 Self(std::cell::RefCell::new(buf))
43 }
44}
45
46impl OutputCont for FileOutput {
47 fn message(&self, text: String) {
48 let _ = writeln!(self.0.borrow_mut(), "{text}");
49 }
50 fn errmessage(&self, text: String) {
51 let _ = writeln!(self.0.borrow_mut(), "{text}");
52 }
53 fn file_open(&self, text: String) {
54 let _ = writeln!(self.0.borrow_mut(), "({text}");
55 }
56 fn file_close(&self, _text: String) {
57 let _ = self.0.borrow_mut().write_string(")\n");
58 }
59 fn write_18(&self, text: String) {
60 let _ = writeln!(self.0.borrow_mut(), "{text}");
61 }
62 fn write_17(&self, text: String) {
63 let _ = writeln!(self.0.borrow_mut(), "{text}");
64 }
65 fn write_16(&self, text: String) {
66 let _ = writeln!(self.0.borrow_mut(), "{text}");
67 }
68 fn write_neg1(&self, text: String) {
69 let _ = writeln!(self.0.borrow_mut(), "{text}");
70 }
71 fn write_other(&self, text: String) {
72 let _ = writeln!(self.0.borrow_mut(), "{text}");
73 }
74 #[inline]
75 fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
76 self
77 }
78}
79
80struct TracingOutput;
81impl OutputCont for TracingOutput {
82 fn message(&self, text: String) {
83 debug!(target:"rustex","{}", text);
84 }
85 fn errmessage(&self, text: String) {
86 debug!(target:"rustex","{}", text);
87 }
88 fn file_open(&self, text: String) {
89 trace!(target:"rustex","({}", text);
90 }
91 fn file_close(&self, _text: String) {
92 trace!(target:"rustex",")");
93 }
94 fn write_18(&self, text: String) {
95 trace!(target:"rustex","write18: {}", text);
96 }
97 fn write_17(&self, text: String) {
98 debug!(target:"rustex","{}", text);
99 }
100 fn write_16(&self, text: String) {
101 trace!(target:"rustex","write16: {}", text);
102 }
103 fn write_neg1(&self, text: String) {
104 trace!(target:"rustex","write-1: {}", text);
105 }
106 fn write_other(&self, text: String) {
107 trace!(target:"rustex","write: {}", text);
108 }
109
110 #[inline]
111 fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
112 self
113 }
114}
115
116#[derive(Clone)]
117pub(crate) struct EngineBase {
118 state: RusTeXState,
119 memory: MemoryManager<CompactToken>,
120 font_system: Fontsystem,
121}
122static ENGINE_BASE: Mutex<Option<Result<EngineBase, ()>>> = Mutex::new(None);
123
124impl EngineBase {
125 pub(crate) fn into_engine(self) -> RusTeXEngine {
126 self.into_engine_opts(
127 std::iter::once(("FLAMS_ADMIN_PWD".to_string(), "NOPE".to_string())),
128 TracingOutput,
129 )
130 }
131 fn into_engine_opts<O: OutputCont + 'static, I: IntoIterator<Item = (String, String)>>(
132 mut self,
133 envs: I,
134 out: O,
135 ) -> RusTeXEngine {
136 use tex_engine::engine::gullet::Gullet;
138 use tex_engine::engine::stomach::Stomach;
139 use tex_engine::engine::EngineExtension;
140 use tex_engine::prelude::ErrorHandler;
141 use tex_engine::prelude::Mouth;
142 let mut aux = EngineAux {
143 outputs: RusTeXOutput::Cont(Box::new(out)),
144 error_handler: ErrorThrower::new(),
145 start_time: chrono::Local::now(),
146 extension: Extension::new(&mut self.memory),
147 memory: self.memory,
148 jobname: String::new(),
149 };
150 let mut mouth = DefaultMouth::new(&mut aux, &mut self.state);
151 let gullet = DefaultGullet::new(&mut aux, &mut self.state, &mut mouth);
152 let mut stomach = RusTeXStomach::new(&mut aux, &mut self.state);
153 stomach.continuous = true;
154 DefaultEngine {
155 state: self.state,
156 aux,
157 fontsystem: self.font_system,
158 filesystem: RusTeXFileSystem::new_with_envs(tex_engine::utils::PWD.to_path_buf(), envs),
159 mouth,
160 gullet,
161 stomach,
162 }
163 }
164 fn get<R, F: FnOnce(&Self) -> R>(f: F) -> Result<R, ()> {
165 let mut lock = ENGINE_BASE.lock();
166 match &mut *lock {
167 Some(Ok(engine)) => Ok(f(engine)),
168 Some(_) => Err(()),
169 o => {
170 *o = Some(Self::initialize());
171 o.as_ref()
172 .unwrap_or_else(|| unreachable!())
173 .as_ref()
174 .map(f)
175 .map_err(|()| ())
176 }
177 }
178 }
179
180 pub(crate) fn from_engine(engine: DefaultEngine<Types>) -> Self {
181 Self {
182 state: engine.state,
183 memory: engine.aux.memory,
184 font_system: engine.fontsystem,
185 }
186 }
187
188 #[instrument(level = "info", target = "sTeX", name = "Initializing RusTeX engine")]
189 fn initialize() -> Result<Self, ()> {
190 use tex_engine::engine::TeXEngine;
191 std::panic::catch_unwind(|| {
192 let mut engine = DefaultEngine::<Types>::default();
193 engine.aux.outputs = RusTeXOutput::Cont(Box::new(TracingOutput));
194 register_primitives_preinit(&mut engine);
195 match engine.initialize_pdflatex() {
196 Ok(()) => {}
197 Err(e) => {
198 error!("Error initializing RusTeX engine: {}", e);
199 }
200 }
201 register_primitives_postinit(&mut engine);
202 match engine.init_file("rustex_defs.def") {
203 Ok(()) => {}
204 Err(e) => {
205 error!("Error initializing RusTeX engine: {}", e);
206 }
207 }
208 Self::from_engine(engine)
209 })
210 .map_err(|a| {
211 if let Some(s) = a.downcast_ref::<String>() {
212 tracing::error!("Error initializing RusTeX engine: {}", s);
213 } else if let Some(s) = a.downcast_ref::<&str>() {
214 tracing::error!("Error initializing RusTeX engine: {}", s);
215 } else {
216 tracing::error!("Error initializing RusTeX engine");
217 }
218 })
219 }
220}
221
222pub struct RusTeX(pub(crate) Mutex<EngineBase>);
223impl RusTeX {
224 pub fn get() -> Result<Self, ()> {
226 Ok(Self(
227 ENGINE_BASE
228 .lock()
229 .as_ref()
230 .unwrap_or_else(|| unreachable!())
231 .clone()?
232 .into(),
233 ))
234 }
235 pub fn initialize() {
236 let _ = EngineBase::get(|_| ());
237 }
238 pub fn run(&self, file: &Path, memorize: bool, out: Option<&Path>) -> Result<String, String> {
240 self.run_with_envs(
241 file,
242 memorize,
243 std::iter::once(("FLAMS_ADMIN_PWD".to_string(), "NOPE".to_string())),
244 out,
245 )
246 }
247
248 fn set_up<I: IntoIterator<Item = (String, String)>>(
250 &self,
251 envs: I,
252 out: Option<&Path>,
253 ) -> (DefaultEngine<Types>, RTSettings) {
254 let e = self.0.lock().clone();
255 let engine = match out {
256 None => e.into_engine_opts(envs, TracingOutput),
257 Some(f) => e.into_engine_opts(envs, FileOutput::new(f)),
258 };
259 let settings = RTSettings {
260 verbose: false,
261 sourcerefs: false,
262 log: true,
263 insert_font_info: false,
264 image_options: ImageOptions::AsIs,
265 };
266 (engine, settings)
267 }
268
269 pub fn run_with_envs<I: IntoIterator<Item = (String, String)>>(
271 &self,
272 file: &Path,
273 memorize: bool,
274 envs: I,
275 out: Option<&Path>,
276 ) -> Result<String, String> {
277 let (mut engine, settings) = self.set_up(envs, out);
278 let res = engine.run(file.to_str().unwrap_or_else(|| unreachable!()), settings);
279
280 res.error.as_ref().map_or_else(
281 || {
282 if memorize {
283 let mut base = self.0.lock();
284 give_back(engine, &mut base, &[b"c_stex_module_", b"c_stex_mathhub_"]);
285 }
286 Ok(res.to_string())
287 },
288 |(e, _)| Err(e.to_string()),
289 )
290 }
291
292 pub fn builder(&self) -> RusTeXRunBuilder<false> {
293 RusTeXRunBuilder {
294 inner: self.0.lock().clone().into_engine_opts(
295 std::iter::once(("FLAMS_ADMIN_PWD".to_string(), "NOPE".to_string())),
296 TracingOutput,
297 ),
298 settings: RTSettings {
299 verbose: false,
300 sourcerefs: false,
301 log: true,
302 insert_font_info: false,
303 image_options: ImageOptions::AsIs,
304 },
305 }
306 }
307}
308
309pub struct RusTeXRunBuilder<const HAS_PATH: bool> {
310 inner: DefaultEngine<Types>,
311 settings: RTSettings,
312}
313impl<const HAS_PATH: bool> RusTeXRunBuilder<HAS_PATH> {
314 pub fn set_output<O: OutputCont>(mut self, output: O) -> Self {
315 self.inner.aux.outputs = RusTeXOutput::Cont(Box::new(output));
316 self
317 }
318 pub const fn set_sourcerefs(mut self, b: bool) -> Self {
319 self.settings.sourcerefs = b;
320 self
321 }
322 pub fn set_envs<I: IntoIterator<Item = (String, String)>>(mut self, envs: I) -> Self {
323 self.inner.filesystem.add_envs(envs);
324 self
325 }
326 pub const fn set_font_debug_info(mut self, b: bool) -> Self {
327 self.settings.insert_font_info = b;
328 self
329 }
330}
331
332pub struct EngineRemnants(DefaultEngine<Types>);
333
334impl EngineRemnants {
335 pub fn memorize(self, global: &RusTeX) {
336 let mut base = global.0.lock();
337 give_back(self.0, &mut base, &[b"c_stex_module_", b"c_stex_mathhub_"]);
338 }
339 pub fn take_output<O: OutputCont>(&mut self) -> Option<O> {
340 match std::mem::replace(&mut self.0.aux.outputs, RusTeXOutput::None) {
341 RusTeXOutput::Cont(o) => o.as_any().downcast().ok().map(|b: Box<O>| *b),
342 _ => None,
343 }
344 }
345}
346
347impl RusTeXRunBuilder<true> {
348 pub fn run(mut self) -> (CompilationResult, EngineRemnants) {
349 *self.inner.aux.extension.elapsed() = std::time::Instant::now();
350 let res =
351 match tex_engine::engine::TeXEngine::run(&mut self.inner, rustex_lib::shipout::shipout)
352 {
353 Ok(()) => None,
354 Err(e) => {
355 self.inner.aux.outputs.errmessage(format!(
356 "{}\n\nat {}",
357 e,
358 self.inner
359 .mouth
360 .current_sourceref()
361 .display(&self.inner.filesystem)
362 ));
363 Some(e)
364 }
365 };
366 let res = self.inner.do_result(res, self.settings);
367 (res, EngineRemnants(self.inner))
368 }
369}
370impl RusTeXRunBuilder<false> {
371 pub fn set_path(mut self, p: &Path) -> Option<RusTeXRunBuilder<true>> {
372 let parent = p.parent()?;
373 self.inner.filesystem.set_pwd(parent.to_path_buf());
374 self.inner.aux.jobname = p.with_extension("").file_name()?.to_str()?.to_string();
375 let f = self.inner.filesystem.get(p.as_os_str().to_str()?);
376 self.inner.mouth.push_file(f);
377 Some(RusTeXRunBuilder {
378 inner: self.inner,
379 settings: self.settings,
380 })
381 }
382 pub fn set_string(mut self, in_path: &Path, content: &str) -> Option<RusTeXRunBuilder<true>> {
383 let parent = in_path.parent()?;
384 self.inner.filesystem.set_pwd(parent.to_path_buf());
385 self.inner.aux.jobname = in_path
386 .with_extension("")
387 .file_name()?
388 .to_str()?
389 .to_string();
390 self.inner
391 .filesystem
392 .add_file(in_path.to_path_buf(), content);
393 let f = self.inner.filesystem.get(in_path.file_name()?.to_str()?);
394 self.inner.mouth.push_file(f);
395 Some(RusTeXRunBuilder {
396 inner: self.inner,
397 settings: self.settings,
398 })
399 }
400 pub fn set_string_noaux(
401 mut self,
402 in_path: &Path,
403 content: &str,
404 ) -> Option<RusTeXRunBuilder<true>> {
405 let aux = in_path.with_extension("aux");
406 self.inner.filesystem.add_file(aux, "");
407 self.set_string(in_path, content)
408 }
409}
410
411fn save_macro(
412 name: InternedCSName<u8>,
413 m: &Macro<CompactToken>,
414 oldmem: &CSInterner<u8>,
415 newmem: &mut CSInterner<u8>,
416 state: &mut RusTeXState,
417) {
418 let oldname = oldmem.resolve(name);
419 let newname = convert_name(name, oldmem, newmem);
420
421 let exp = &m.expansion;
422 let newexp: TokenList<_> = exp
423 .0
424 .iter()
425 .map(|x| convert_token(*x, oldmem, newmem))
426 .collect();
427 let newsig = MacroSignature {
428 arity: m.signature.arity,
429 params: m
430 .signature
431 .params
432 .0
433 .iter()
434 .map(|x| convert_token(*x, oldmem, newmem))
435 .collect(),
436 };
437 let newmacro = Macro {
438 protected: m.protected,
439 long: m.long,
440 outer: m.outer,
441 signature: newsig,
442 expansion: newexp,
443 };
444 state.set_command_direct(newname, Some(TeXCommand::Macro(newmacro)));
445}
446
447fn convert_name(
448 oldname: InternedCSName<u8>,
449 oldmem: &CSInterner<u8>,
450 newmem: &mut CSInterner<u8>,
451) -> InternedCSName<u8> {
452 newmem.intern(oldmem.resolve(oldname))
453}
454
455fn convert_token(
456 old: CompactToken,
457 oldmem: &CSInterner<u8>,
458 newmem: &mut CSInterner<u8>,
459) -> CompactToken {
460 match old.to_enum() {
461 StandardToken::ControlSequence(cs) => {
462 CompactToken::from_cs(convert_name(cs, oldmem, newmem))
463 }
464 _ => old,
465 }
466}
467
468use tex_engine::tex::tokens::control_sequences::CSNameMap;
469use tex_engine::utils::errors::ErrorThrower;
470
471fn give_back(engine: RusTeXEngine, base: &mut EngineBase, prefixes: &'static [&'static [u8]]) {
472 let EngineBase {
473 state,
474 memory,
475 font_system,
476 } = base;
477 *font_system = engine.fontsystem;
478 let oldinterner = engine.aux.memory.cs_interner();
479 let iter = CommandIterator {
480 prefixes, cmds: engine.state.destruct().into_iter(),
482 interner: oldinterner,
483 };
484 for (n, c) in iter.filter_map(|(a, b)| match b {
485 TeXCommand::Macro(m) => Some((a, m)),
486 _ => None,
487 }) {
488 save_macro(n, &c, oldinterner, memory.cs_interner_mut(), state);
489 }
490}
491
492pub struct CommandIterator<'a, I: Iterator<Item = (InternedCSName<u8>, TeXCommand<Types>)>> {
493 prefixes: &'static [&'static [u8]],
494 cmds: I,
495 interner: &'a <InternedCSName<u8> as CSName<u8>>::Handler,
496}
497impl<I: Iterator<Item = (InternedCSName<u8>, TeXCommand<Types>)>> Iterator
498 for CommandIterator<'_, I>
499{
500 type Item = (InternedCSName<u8>, TeXCommand<Types>);
501 fn next(&mut self) -> Option<Self::Item> {
502 loop {
503 if let Some((name, cmd)) = self.cmds.next() {
504 let bname = self.interner.resolve(name);
505 if self.prefixes.iter().any(|p| bname.starts_with(p)) {
506 return Some((name, cmd));
507 }
508 } else {
509 return None;
510 }
511 }
512 }
513}