1use std::{
2 collections::HashMap,
3 ffi::OsString,
4 path::{Path, PathBuf},
5};
6use texlang::prelude as txl;
7use texlang::traits::*;
8use texlang::*;
9use texlang_common as common;
10
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct Component {
13 #[cfg_attr(feature = "serde", serde(skip))]
14 job_name: Option<PathBuf>,
15 default_job_name: PathBuf,
16 dump_format: i32,
17 dump_validate: i32,
18 #[cfg_attr(feature = "serde", serde(skip))]
19 num_dumps: usize,
20}
21
22impl Default for Component {
23 fn default() -> Self {
24 Self {
25 job_name: None,
26 default_job_name: "jobname".into(),
27 dump_format: 0,
28 dump_validate: 0,
29 num_dumps: 0,
30 }
31 }
32}
33
34impl Component {
35 pub fn job_name(&self) -> &Path {
36 match &self.job_name {
37 None => &self.default_job_name,
38 Some(job_name) => job_name,
39 }
40 }
41
42 pub fn set_job_name(&mut self, file_path: &Path) {
47 if let Some(file_stem) = file_path.file_stem() {
48 self.job_name = Some(file_stem.into())
49 }
50 }
51}
52
53pub fn get_jobname<S: HasComponent<Component>>() -> command::BuiltIn<S> {
55 command::BuiltIn::new_expansion(|token, input| {
56 let job_name: String = input
57 .state()
58 .component()
59 .job_name()
60 .to_string_lossy()
61 .into();
62 input.push_string_tokens(token, &job_name);
63 Ok(())
64 })
65}
66
67#[cfg(feature = "serde")]
69pub fn get_dump<
70 S: HasComponent<Component>
71 + serde::Serialize
72 + serde::de::DeserializeOwned
73 + common::HasFileSystem,
74>() -> command::BuiltIn<S> {
75 command::BuiltIn::new_execution(dump_primitive_fn)
76}
77
78#[cfg(feature = "serde")]
79fn dump_primitive_fn<
80 S: HasComponent<Component>
81 + serde::Serialize
82 + serde::de::DeserializeOwned
83 + common::HasFileSystem,
84>(
85 _: token::Token,
86 input: &mut vm::ExecutionInput<S>,
87) -> txl::Result<()> {
88 let component = input.state().component();
89 let mut job_name: OsString = component.job_name().into();
90 let num_dumps = component.num_dumps;
91 if num_dumps > 0 {
92 job_name.push(format!["-{}", num_dumps + 1]);
93 }
94 let mut output_file: PathBuf = job_name.into();
95 output_file.set_extension(match component.dump_format {
96 1 => "fmt.json",
97 2 => "fmt.bincode",
98 _ => "fmt.mp",
99 });
100
101 let serialized = match component.dump_format {
103 0 => rmp_serde::encode::to_vec(input.vm()).unwrap(),
104 1 => serde_json::to_vec_pretty(input.vm()).unwrap(),
105 2 => bincode::serde::encode_to_vec(input.vm(), bincode::config::standard()).unwrap(),
106 i => {
107 return Err(input.fatal_error(
108 error::SimpleFailedPreconditionError::new(format![
109 r"\dumpFormat has invalid value {i}",
110 ])
111 .with_note(r"\dumpFormat must be either 0 (message pack), 1 (json) or 2 (bincode)"),
112 ))
113 }
114 };
115
116 if component.dump_validate != 0 {
117 let built_in_commands: HashMap<&str, command::BuiltIn<S>> = input
118 .vm()
119 .commands_map
120 .built_in_commands()
121 .iter()
122 .map(|(cs_name, command)| {
123 (
124 input.vm().cs_name_interner().resolve(*cs_name).unwrap(),
125 command.clone(),
126 )
127 })
128 .collect();
129 match component.dump_format {
130 0 => {
131 let mut deserializer = rmp_serde::decode::Deserializer::from_read_ref(&serialized);
132 vm::VM::<S>::deserialize_with_built_in_commands(
134 &mut deserializer,
135 built_in_commands,
136 )
137 .unwrap();
138 }
139 1 => {
140 let mut deserializer = serde_json::Deserializer::from_slice(&serialized);
141 vm::VM::<S>::deserialize_with_built_in_commands(
142 &mut deserializer,
143 built_in_commands,
144 )
145 .unwrap();
146 }
147 2 => {
148 let deserialized: Box<vm::serde::DeserializedVM<S>> =
149 bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
150 .unwrap()
151 .0;
152 vm::serde::finish_deserialization(deserialized, built_in_commands);
153 }
154 _ => unreachable!(),
155 };
156 }
157
158 input
160 .state()
161 .file_system()
162 .borrow_mut()
163 .write_bytes(&output_file, &serialized)
164 .unwrap();
165 input.state_mut().component_mut().num_dumps += 1;
166 Ok(())
167}
168
169pub fn get_dumpformat<S: HasComponent<Component>>() -> command::BuiltIn<S> {
170 variable::Command::new_singleton(
171 |state: &S, _| &state.component().dump_format,
172 |state: &mut S, _| &mut state.component_mut().dump_format,
173 )
174 .into()
175}
176
177pub fn get_dumpvalidate<S: HasComponent<Component>>() -> command::BuiltIn<S> {
178 variable::Command::new_singleton(
179 |state: &S, _| &state.component().dump_validate,
180 |state: &mut S, _| &mut state.component_mut().dump_validate,
181 )
182 .into()
183}