Skip to main content

flams_stex/
rustex.rs

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::filesystem::FileSystem;
137        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    /// # Errors
225    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    /// ### Errors
239    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    /// ### Errors
249    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: true,
262            log: true,
263            insert_font_info: false,
264            image_options: ImageOptions::AsIs,
265        };
266        (engine, settings)
267    }
268
269    /// ### Errors
270    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        //println!("Setup done; compiling...");
279        let res = engine.run(file.to_str().unwrap_or_else(|| unreachable!()), settings);
280        //println!("Done. Memorizing / evaluating result...");
281
282        res.error.as_ref().map_or_else(
283            || {
284                if memorize {
285                    let mut base = self.0.lock();
286                    give_back(engine, &mut base, &[b"c_stex_module_", b"c_stex_mathhub_"]);
287                }
288                Ok(res.to_string())
289            },
290            |(e, _)| Err(e.to_string()),
291        )
292    }
293
294    pub fn builder(&self) -> RusTeXRunBuilder<false> {
295        RusTeXRunBuilder {
296            inner: self.0.lock().clone().into_engine_opts(
297                std::iter::once(("FLAMS_ADMIN_PWD".to_string(), "NOPE".to_string())),
298                TracingOutput,
299            ),
300            settings: RTSettings {
301                verbose: false,
302                sourcerefs: false,
303                log: true,
304                insert_font_info: false,
305                image_options: ImageOptions::AsIs,
306            },
307        }
308    }
309}
310
311pub struct RusTeXRunBuilder<const HAS_PATH: bool> {
312    inner: DefaultEngine<Types>,
313    settings: RTSettings,
314}
315impl<const HAS_PATH: bool> RusTeXRunBuilder<HAS_PATH> {
316    pub fn set_output<O: OutputCont>(mut self, output: O) -> Self {
317        self.inner.aux.outputs = RusTeXOutput::Cont(Box::new(output));
318        self
319    }
320    pub const fn set_sourcerefs(mut self, b: bool) -> Self {
321        self.settings.sourcerefs = b;
322        self
323    }
324    pub fn set_envs<I: IntoIterator<Item = (String, String)>>(mut self, envs: I) -> Self {
325        self.inner.filesystem.add_envs(envs);
326        self
327    }
328    pub const fn set_font_debug_info(mut self, b: bool) -> Self {
329        self.settings.insert_font_info = b;
330        self
331    }
332}
333
334pub struct EngineRemnants(DefaultEngine<Types>);
335
336impl EngineRemnants {
337    pub fn memorize(self, global: &RusTeX) {
338        let mut base = global.0.lock();
339        give_back(self.0, &mut base, &[b"c_stex_module_", b"c_stex_mathhub_"]);
340    }
341    pub fn take_output<O: OutputCont>(&mut self) -> Option<O> {
342        match std::mem::replace(&mut self.0.aux.outputs, RusTeXOutput::None) {
343            RusTeXOutput::Cont(o) => o.as_any().downcast().ok().map(|b: Box<O>| *b),
344            _ => None,
345        }
346    }
347}
348
349impl RusTeXRunBuilder<true> {
350    pub fn run(mut self) -> (CompilationResult, EngineRemnants) {
351        *self.inner.aux.extension.elapsed() = std::time::Instant::now();
352        let res =
353            match tex_engine::engine::TeXEngine::run(&mut self.inner, rustex_lib::shipout::shipout)
354            {
355                Ok(()) => None,
356                Err(e) => {
357                    self.inner.aux.outputs.errmessage(format!(
358                        "{}\n\nat {}",
359                        e,
360                        self.inner
361                            .mouth
362                            .current_sourceref()
363                            .display(&self.inner.filesystem)
364                    ));
365                    Some(e)
366                }
367            };
368        let res = self.inner.do_result(res, self.settings);
369        (res, EngineRemnants(self.inner))
370    }
371}
372impl RusTeXRunBuilder<false> {
373    pub fn set_path(mut self, p: &Path) -> Option<RusTeXRunBuilder<true>> {
374        let parent = p.parent()?;
375        self.inner.filesystem.set_pwd(parent.to_path_buf());
376        self.inner.aux.jobname = p.with_extension("").file_name()?.to_str()?.to_string();
377        let f = self.inner.filesystem.get(p.as_os_str().to_str()?);
378        self.inner.mouth.push_file(f);
379        Some(RusTeXRunBuilder {
380            inner: self.inner,
381            settings: self.settings,
382        })
383    }
384    pub fn set_string(mut self, in_path: &Path, content: &str) -> Option<RusTeXRunBuilder<true>> {
385        let parent = in_path.parent()?;
386        self.inner.filesystem.set_pwd(parent.to_path_buf());
387        self.inner.aux.jobname = in_path
388            .with_extension("")
389            .file_name()?
390            .to_str()?
391            .to_string();
392        self.inner
393            .filesystem
394            .add_file(in_path.to_path_buf(), content);
395        let f = self.inner.filesystem.get(in_path.file_name()?.to_str()?);
396        self.inner.mouth.push_file(f);
397        Some(RusTeXRunBuilder {
398            inner: self.inner,
399            settings: self.settings,
400        })
401    }
402    pub fn set_string_noaux(
403        mut self,
404        in_path: &Path,
405        content: &str,
406    ) -> Option<RusTeXRunBuilder<true>> {
407        let aux = in_path.with_extension("aux");
408        self.inner.filesystem.add_file(aux, "");
409        self.set_string(in_path, content)
410    }
411}
412
413fn save_macro(
414    name: InternedCSName<u8>,
415    m: &Macro<CompactToken>,
416    oldmem: &CSInterner<u8>,
417    newmem: &mut CSInterner<u8>,
418    state: &mut RusTeXState,
419) {
420    let oldname = oldmem.resolve(name);
421    let newname = convert_name(name, oldmem, newmem);
422
423    let exp = &m.expansion;
424    let newexp: TokenList<_> = exp
425        .0
426        .iter()
427        .map(|x| convert_token(*x, oldmem, newmem))
428        .collect();
429    let newsig = MacroSignature {
430        arity: m.signature.arity,
431        params: m
432            .signature
433            .params
434            .0
435            .iter()
436            .map(|x| convert_token(*x, oldmem, newmem))
437            .collect(),
438    };
439    let newmacro = Macro {
440        protected: m.protected,
441        long: m.long,
442        outer: m.outer,
443        signature: newsig,
444        expansion: newexp,
445    };
446    state.set_command_direct(newname, Some(TeXCommand::Macro(newmacro)));
447}
448
449fn convert_name(
450    oldname: InternedCSName<u8>,
451    oldmem: &CSInterner<u8>,
452    newmem: &mut CSInterner<u8>,
453) -> InternedCSName<u8> {
454    newmem.intern(oldmem.resolve(oldname))
455}
456
457fn convert_token(
458    old: CompactToken,
459    oldmem: &CSInterner<u8>,
460    newmem: &mut CSInterner<u8>,
461) -> CompactToken {
462    match old.to_enum() {
463        StandardToken::ControlSequence(cs) => {
464            CompactToken::from_cs(convert_name(cs, oldmem, newmem))
465        }
466        _ => old,
467    }
468}
469
470use tex_engine::tex::tokens::control_sequences::CSNameMap;
471use tex_engine::utils::errors::ErrorThrower;
472
473fn give_back(engine: RusTeXEngine, base: &mut EngineBase, prefixes: &'static [&'static [u8]]) {
474    let EngineBase {
475        state,
476        memory,
477        font_system,
478    } = base;
479    *font_system = engine.fontsystem;
480    let oldinterner = engine.aux.memory.cs_interner();
481    let iter = CommandIterator {
482        prefixes, //: &[b"c_stex_module_", b"c_stex_mathhub_"],
483        cmds: engine.state.destruct().into_iter(),
484        interner: oldinterner,
485    };
486    for (n, c) in iter.filter_map(|(a, b)| match b {
487        TeXCommand::Macro(m) => Some((a, m)),
488        _ => None,
489    }) {
490        save_macro(n, &c, oldinterner, memory.cs_interner_mut(), state);
491    }
492}
493
494pub struct CommandIterator<'a, I: Iterator<Item = (InternedCSName<u8>, TeXCommand<Types>)>> {
495    prefixes: &'static [&'static [u8]],
496    cmds: I,
497    interner: &'a <InternedCSName<u8> as CSName<u8>>::Handler,
498}
499impl<I: Iterator<Item = (InternedCSName<u8>, TeXCommand<Types>)>> Iterator
500    for CommandIterator<'_, I>
501{
502    type Item = (InternedCSName<u8>, TeXCommand<Types>);
503    fn next(&mut self) -> Option<Self::Item> {
504        loop {
505            if let Some((name, cmd)) = self.cmds.next() {
506                let bname = self.interner.resolve(name);
507                if self.prefixes.iter().any(|p| bname.starts_with(p)) {
508                    return Some((name, cmd));
509                }
510            } else {
511                return None;
512            }
513        }
514    }
515}