1use crate::PDFLATEX;
2use crate::quickparse::stex::{DiagnosticLevel, rules};
3use crate::{
4 PDFLATEX_FIRST,
5 quickparse::{
6 latex::LaTeXParser,
7 stex::structs::{ModuleReference, STeXParseState, STeXToken},
8 },
9};
10use either::Either;
11use flams_math_archives::MathArchive;
12use flams_math_archives::backend::{AnyBackend, LocalBackend};
13use flams_math_archives::formats::{BuildSpec, BuildTargetId, TaskDependency, TaskRef};
14use flams_utils::sourcerefs::{NoPosition, StringRange};
15use ftml_solver::CHECK;
16use ftml_uris::{ArchiveId, DocumentUri, Language, UriPath, UriWithArchive};
17use std::path::Path;
18
19pub enum STeXDependency {
20 ImportModule {
21 archive: ArchiveId,
22 module: std::sync::Arc<str>,
23 },
24 UseModule {
25 archive: ArchiveId,
26 module: std::sync::Arc<str>,
27 },
28 Inputref {
29 archive: Option<ArchiveId>,
30 filepath: std::sync::Arc<str>,
31 },
32 Module {
33 sig: Option<Language>,
35 meta: Option<(ArchiveId, std::sync::Arc<str>)>,
36 },
37 Img {
38 archive: Option<ArchiveId>,
39 filepath: std::sync::Arc<str>,
40 },
41 SRef {
42 filepath: std::sync::Arc<Path>,
43 in_doc: Option<std::sync::Arc<Path>>,
44 },
45}
46
47#[allow(clippy::type_complexity)]
48pub struct DepParser<'a> {
49 parser: LaTeXParser<'a, NoPosition, STeXToken<NoPosition>, STeXParseState<'a, NoPosition, ()>>,
50 stack: Vec<std::vec::IntoIter<STeXToken<NoPosition>>>,
51 curr: Option<std::vec::IntoIter<STeXToken<NoPosition>>>,
52}
53
54pub fn parse_deps<'a>(
55 source: &'a str,
56 path: &'a Path,
57 doc: &'a DocumentUri,
58 backend: &'a AnyBackend,
59 err: &'a mut dyn FnMut(String, StringRange<NoPosition>, DiagnosticLevel),
60) -> impl Iterator<Item = STeXDependency> + use<'a> {
61 let archive = doc.archive_uri();
62 let parser = LaTeXParser::with_rules(
63 source,
64 STeXParseState::<NoPosition, ()>::new(Some(archive), Some(path), doc, backend, ()),
65 err,
66 LaTeXParser::default_rules().into_iter().chain([
67 ("importmodule", rules::importmodule_deps as _),
68 ("requiremodule", rules::importmodule_deps as _),
69 ("setmetatheory", rules::setmetatheory as _),
70 ("usemodule", rules::usemodule_deps as _),
71 ("inputref", rules::inputref as _),
72 ("mhinput", rules::inputref as _),
73 ("mhgraphics", rules::mhgraphics as _),
74 ("cmhgraphics", rules::mhgraphics as _),
75 ("stexstyleassertion", rules::stexstyleassertion as _),
76 ("stexstyledefinition", rules::stexstyledefinition as _),
77 ("stexstyleparagraph", rules::stexstyleparagraph as _),
78 ("sref", rules::sref as _),
79 ]),
80 LaTeXParser::default_env_rules().into_iter().chain([(
81 "smodule",
82 (
83 rules::smodule_deps_open as _,
84 rules::smodule_deps_close as _,
85 ),
86 )]),
87 );
88 DepParser {
89 parser,
90 stack: Vec::new(),
91 curr: None,
92 }
93}
94
95impl DepParser<'_> {
96 fn convert(&mut self, t: STeXToken<NoPosition>) -> Option<STeXDependency> {
97 match t {
98 STeXToken::ImportModule {
99 module:
100 ModuleReference {
101 uri,
102 rel_path: Some(rel_path),
103 ..
104 },
105 ..
106 }
107 | STeXToken::SetMetatheory {
108 module:
109 ModuleReference {
110 uri,
111 rel_path: Some(rel_path),
112 ..
113 },
114 ..
115 } => Some(STeXDependency::ImportModule {
116 archive: uri.archive_id().clone(),
117 module: rel_path,
118 }),
119 STeXToken::UseModule {
120 module:
121 ModuleReference {
122 uri,
123 rel_path: Some(rel_path),
124 ..
125 },
126 ..
127 } => Some(STeXDependency::UseModule {
128 archive: uri.archive_id().clone(),
129 module: rel_path,
130 }),
131 STeXToken::SRef {
132 target_path,
133 in_doc,
134 ..
135 } => Some(STeXDependency::SRef {
136 filepath: target_path,
137 in_doc: in_doc.map(|(_, p)| p),
138 }),
139 STeXToken::Module {
140 sig,
141 children,
142 meta_theory,
143 ..
144 } => {
145 let old = self.curr.replace(children.into_iter());
146 if let Some(old) = old {
147 self.stack.push(old);
148 }
149 Some(STeXDependency::Module {
150 sig,
151 meta: meta_theory
152 .and_then(|m| m.rel_path.map(|p| (m.uri.archive_id().clone(), p))),
153 })
154 }
155 STeXToken::Inputref {
156 archive, filepath, ..
157 } => Some(STeXDependency::Inputref {
158 archive: archive.map(|(a, _)| a),
159 filepath: filepath.0,
160 }),
161 STeXToken::Vec(v) => {
162 let old = self.curr.replace(v.into_iter());
163 if let Some(old) = old {
164 self.stack.push(old);
165 }
166 None
167 }
168 STeXToken::MHGraphics {
169 filepath, archive, ..
170 } => Some(STeXDependency::Img {
171 archive: archive.map(|(a, _)| a),
172 filepath: filepath.0,
173 }),
174 _ => None,
175 }
176 }
177}
178
179impl Iterator for DepParser<'_> {
180 type Item = STeXDependency;
181 fn next(&mut self) -> Option<Self::Item> {
182 loop {
183 if let Some(curr) = &mut self.curr {
184 if let Some(t) = curr.next() {
185 if let Some(t) = self.convert(t) {
186 return Some(t);
187 }
188 } else {
189 self.curr = self.stack.pop();
190 }
191 } else if let Some(t) = self.parser.next() {
192 if let Some(t) = self.convert(t) {
193 return Some(t);
194 }
195 } else {
196 return None;
197 }
198 }
199 }
200}
201
202#[allow(clippy::too_many_lines)]
203#[deprecated(note = "replace Arc<str>.parse by UriPath everywhere")]
204#[allow(clippy::needless_pass_by_value)]
205pub fn get_deps(task: BuildSpec) -> Vec<(BuildTargetId, TaskDependency)> {
206 let mut deps = Vec::new();
207 let Either::Left(path) = task.source else {
208 return deps;
209 };
210 let Ok(source) = std::fs::read_to_string(path) else {
211 return deps;
212 };
213 for d in parse_deps(&source, path, task.uri, task.backend, &mut |_, _, _| {}) {
215 match d {
216 STeXDependency::ImportModule { archive, module }
217 | STeXDependency::UseModule { archive, module }
218 if !module.is_empty() =>
219 {
220 deps.push((
221 PDFLATEX_FIRST.id(),
222 TaskDependency::Physical {
223 strict: false,
224 task: TaskRef {
225 archive,
226 rel_path: module
227 .parse()
228 .unwrap_or_else(|e| panic!("this is a bug: {e}: {module}")),
229 target: PDFLATEX_FIRST.id(),
230 },
231 },
232 ));
233 }
234 STeXDependency::Inputref { archive, filepath } if !filepath.is_empty() => {
235 deps.push((
236 PDFLATEX_FIRST.id(),
237 TaskDependency::Physical {
238 strict: false,
239 task: TaskRef {
240 archive: archive.unwrap_or_else(|| task.uri.archive_id().clone()),
241 rel_path: filepath.parse().expect("this is a bug"),
242 target: PDFLATEX_FIRST.id(),
243 },
244 },
245 ));
246 }
247 STeXDependency::Module {
248 sig,
249 meta,
250 } => {
251 if let Some(lang) = sig {
253 let archive = task.uri.archive_id().clone();
254 let Some(rel_path) =
255 task.rel_path.as_ref().rsplit_once('.').and_then(|(a, _)| {
256 a.rsplit_once('.').map(|(a, _)| format!("{a}.{lang}.tex"))
257 })
258 else {
259 continue;
260 };
261 deps.push((
262 PDFLATEX_FIRST.id(),
263 TaskDependency::Physical {
264 strict: false,
265 task: TaskRef {
266 archive: archive.clone(),
267 rel_path: rel_path.clone().parse().expect("this is a bug"),
268 target: PDFLATEX_FIRST.id(),
269 },
270 },
271 ));
272 deps.push((
273 CHECK.id(),
274 TaskDependency::Physical {
275 strict: true,
276 task: TaskRef {
277 archive,
278 rel_path: rel_path.clone().parse().expect("this is a bug"),
279 target: CHECK.id(),
280 },
281 },
282 ));
283 }
284 if let Some((archive, module)) = meta {
285 deps.push((
286 PDFLATEX_FIRST.id(),
287 TaskDependency::Physical {
288 strict: false,
289 task: TaskRef {
290 archive: archive.clone(),
291 rel_path: module.parse().expect("this is a bug"),
292 target: PDFLATEX_FIRST.id(),
293 },
294 },
295 ));
296 deps.push((
297 CHECK.id(),
298 TaskDependency::Physical {
299 strict: true,
300 task: TaskRef {
301 archive,
302 rel_path: module.parse().expect("this is a bug"),
303 target: CHECK.id(),
304 },
305 },
306 ));
307 }
308 }
309 STeXDependency::SRef { filepath, in_doc } => {
310 if let Some((archive, rel_path)) = task
311 .backend
312 .archive_of(&filepath, |a, rp| {
313 let rp = rp.as_os_str().to_str()?;
314 rp.parse::<UriPath>().ok().map(|p| (a.id().clone(), p))
315 })
316 .flatten()
317 {
318 deps.push((
319 PDFLATEX.id(),
320 TaskDependency::Physical {
321 strict: false,
322 task: TaskRef {
323 archive,
324 rel_path,
325 target: PDFLATEX_FIRST.id(),
326 },
327 },
328 ));
329 }
330 if let Some(id) = in_doc
331 && let Some((archive, rel_path)) = task
332 .backend
333 .archive_of(&id, |a, rp| {
334 let rp = rp.as_os_str().to_str()?;
335 rp.parse::<UriPath>().ok().map(|p| (a.id().clone(), p))
336 })
337 .flatten()
338 {
339 deps.push((
340 PDFLATEX.id(),
341 TaskDependency::Physical {
342 strict: false,
343 task: TaskRef {
344 archive,
345 rel_path,
346 target: PDFLATEX_FIRST.id(),
347 },
348 },
349 ));
350 }
351 }
352 STeXDependency::Img { .. }
353 | STeXDependency::ImportModule { .. }
354 | STeXDependency::UseModule { .. }
355 | STeXDependency::Inputref { .. } => (),
356 }
357 }
358 deps
359}