flams_stex/
dependencies.rs

1use crate::quickparse::stex::rules;
2use crate::{
3    quickparse::{
4        latex::LaTeXParser,
5        stex::{
6            structs::{ModuleReference, STeXParseState, STeXToken},
7            DiagnosticLevel,
8        },
9    },
10    PDFLATEX_FIRST,
11};
12use either::Either;
13use flams_math_archives::backend::AnyBackend;
14use flams_math_archives::formats::{BuildSpec, BuildTargetId, TaskDependency, TaskRef, CHECK};
15use flams_utils::{parsing::ParseStr, sourcerefs::SourceRange};
16use ftml_uris::{ArchiveId, DocumentUri, Language, 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}
42
43#[allow(clippy::type_complexity)]
44pub struct DepParser<'a> {
45    parser: LaTeXParser<
46        'a,
47        ParseStr<'a, ()>,
48        STeXToken<()>,
49        fn(String, SourceRange<()>, DiagnosticLevel),
50        STeXParseState<'a, (), ()>,
51    >,
52    stack: Vec<std::vec::IntoIter<STeXToken<()>>>,
53    curr: Option<std::vec::IntoIter<STeXToken<()>>>,
54}
55
56pub fn parse_deps<'a>(
57    source: &'a str,
58    path: &'a Path,
59    doc: &'a DocumentUri,
60    backend: &'a AnyBackend,
61) -> impl Iterator<Item = STeXDependency> + use<'a> {
62    const NOERR: fn(String, SourceRange<()>, DiagnosticLevel) = |_, _, _| {};
63    let archive = doc.archive_uri();
64    let parser = LaTeXParser::with_rules(
65        ParseStr::new(source),
66        STeXParseState::<(), ()>::new(Some(archive), Some(path), doc, backend, ()),
67        NOERR,
68        LaTeXParser::default_rules().into_iter().chain([
69            ("importmodule", rules::importmodule_deps as _),
70            ("setmetatheory", rules::setmetatheory as _),
71            ("usemodule", rules::usemodule_deps as _),
72            ("inputref", rules::inputref as _),
73            ("mhinput", rules::inputref as _),
74            ("mhgraphics", rules::mhgraphics as _),
75            ("cmhgraphics", rules::mhgraphics as _),
76            ("stexstyleassertion", rules::stexstyleassertion as _),
77            ("stexstyledefinition", rules::stexstyledefinition as _),
78            ("stexstyleparagraph", rules::stexstyleparagraph 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<()>) -> 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::Module {
132                /*uri,*/ sig,
133                children,
134                meta_theory,
135                ..
136            } => {
137                let old = std::mem::replace(&mut self.curr, Some(children.into_iter()));
138                if let Some(old) = old {
139                    self.stack.push(old);
140                }
141                Some(STeXDependency::Module {
142                    /*uri,*/ sig,
143                    meta: meta_theory
144                        .and_then(|m| m.rel_path.map(|p| (m.uri.archive_id().clone(), p))),
145                })
146            }
147            STeXToken::Inputref {
148                archive, filepath, ..
149            } => Some(STeXDependency::Inputref {
150                archive: archive.map(|(a, _)| a),
151                filepath: filepath.0,
152            }),
153            STeXToken::Vec(v) => {
154                let old = std::mem::replace(&mut self.curr, Some(v.into_iter()));
155                if let Some(old) = old {
156                    self.stack.push(old);
157                }
158                None
159            }
160            STeXToken::MHGraphics {
161                filepath, archive, ..
162            } => Some(STeXDependency::Img {
163                archive: archive.map(|(a, _)| a),
164                filepath: filepath.0,
165            }),
166            _ => None,
167        }
168    }
169}
170
171impl Iterator for DepParser<'_> {
172    type Item = STeXDependency;
173    fn next(&mut self) -> Option<Self::Item> {
174        loop {
175            if let Some(curr) = &mut self.curr {
176                if let Some(t) = curr.next() {
177                    if let Some(t) = self.convert(t) {
178                        return Some(t);
179                    }
180                } else {
181                    self.curr = self.stack.pop();
182                }
183            } else if let Some(t) = self.parser.next() {
184                if let Some(t) = self.convert(t) {
185                    return Some(t);
186                }
187            } else {
188                return None;
189            }
190        }
191    }
192}
193
194#[allow(clippy::too_many_lines)]
195#[deprecated(note = "replace Arc<str>.parse by UriPath everywhere")]
196#[allow(clippy::needless_pass_by_value)]
197pub fn get_deps(task: BuildSpec) -> Vec<(BuildTargetId, TaskDependency)> {
198    let mut deps = Vec::new();
199    let Either::Left(path) = task.source else {
200        return deps;
201    };
202    let Ok(source) = std::fs::read_to_string(path) else {
203        return deps;
204    };
205    //let mut yields = Vec::new();
206    for d in parse_deps(&source, path, task.uri, task.backend) {
207        match d {
208            STeXDependency::ImportModule { archive, module }
209            | STeXDependency::UseModule { archive, module }
210                if !module.is_empty() =>
211            {
212                deps.push((
213                    PDFLATEX_FIRST.id(),
214                    TaskDependency::Physical {
215                        strict: false,
216                        task: TaskRef {
217                            archive,
218                            rel_path: module
219                                .parse()
220                                .unwrap_or_else(|e| panic!("this is a bug: {e}: {module}")),
221                            target: PDFLATEX_FIRST.id(),
222                        },
223                    },
224                ));
225            }
226            STeXDependency::Inputref { archive, filepath } if !filepath.is_empty() => {
227                deps.push((
228                    PDFLATEX_FIRST.id(),
229                    TaskDependency::Physical {
230                        strict: false,
231                        task: TaskRef {
232                            archive: archive.unwrap_or_else(|| task.uri.archive_id().clone()),
233                            rel_path: filepath.parse().expect("this is a bug"),
234                            target: PDFLATEX_FIRST.id(),
235                        },
236                    },
237                ));
238            }
239            STeXDependency::Module {
240                /*uri:_,*/ sig,
241                meta,
242            } => {
243                //yields.push(uri);
244                if let Some(lang) = sig {
245                    let archive = task.uri.archive_id().clone();
246                    let Some(rel_path) =
247                        task.rel_path.as_ref().rsplit_once('.').and_then(|(a, _)| {
248                            a.rsplit_once('.').map(|(a, _)| format!("{a}.{lang}.tex"))
249                        })
250                    else {
251                        continue;
252                    };
253                    deps.push((
254                        PDFLATEX_FIRST.id(),
255                        TaskDependency::Physical {
256                            strict: false,
257                            task: TaskRef {
258                                archive: archive.clone(),
259                                rel_path: rel_path.clone().parse().expect("this is a bug"),
260                                target: PDFLATEX_FIRST.id(),
261                            },
262                        },
263                    ));
264                    deps.push((
265                        CHECK.id(),
266                        TaskDependency::Physical {
267                            strict: true,
268                            task: TaskRef {
269                                archive,
270                                rel_path: rel_path.clone().parse().expect("this is a bug"),
271                                target: CHECK.id(),
272                            },
273                        },
274                    ));
275                }
276                if let Some((archive, module)) = meta {
277                    deps.push((
278                        PDFLATEX_FIRST.id(),
279                        TaskDependency::Physical {
280                            strict: false,
281                            task: TaskRef {
282                                archive: archive.clone(),
283                                rel_path: module.parse().expect("this is a bug"),
284                                target: PDFLATEX_FIRST.id(),
285                            },
286                        },
287                    ));
288                    deps.push((
289                        CHECK.id(),
290                        TaskDependency::Physical {
291                            strict: true,
292                            task: TaskRef {
293                                archive,
294                                rel_path: module.parse().expect("this is a bug"),
295                                target: CHECK.id(),
296                            },
297                        },
298                    ));
299                }
300            }
301            _ => (),
302        }
303    }
304    deps
305}