Skip to main content

flams_stex/
dependencies.rs

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        //uri:ModuleUri,
34        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                /*uri,*/ 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                    /*uri,*/ 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    //let mut yields = Vec::new();
214    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                /*uri:_,*/ sig,
249                meta,
250            } => {
251                //yields.push(uri);
252                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}