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_ontology::{
14    languages::Language,
15    uris::{ArchiveId, ArchiveURITrait, DocumentURI},
16};
17use flams_system::{
18    backend::AnyBackend,
19    building::{BuildTask, Dependency, TaskRef},
20    formats::CHECK,
21};
22use flams_utils::{parsing::ParseStr, sourcerefs::SourceRange};
23use std::path::Path;
24
25pub enum STeXDependency {
26    ImportModule {
27        archive: ArchiveId,
28        module: std::sync::Arc<str>,
29    },
30    UseModule {
31        archive: ArchiveId,
32        module: std::sync::Arc<str>,
33    },
34    Inputref {
35        archive: Option<ArchiveId>,
36        filepath: std::sync::Arc<str>,
37    },
38    Module {
39        //uri:ModuleURI,
40        sig: Option<Language>,
41        meta: Option<(ArchiveId, std::sync::Arc<str>)>,
42    },
43    Img {
44        archive: Option<ArchiveId>,
45        filepath: std::sync::Arc<str>,
46    },
47}
48
49#[allow(clippy::type_complexity)]
50pub struct DepParser<'a> {
51    parser: LaTeXParser<
52        'a,
53        ParseStr<'a, ()>,
54        STeXToken<()>,
55        fn(String, SourceRange<()>, DiagnosticLevel),
56        STeXParseState<'a, (), ()>,
57    >,
58    stack: Vec<std::vec::IntoIter<STeXToken<()>>>,
59    curr: Option<std::vec::IntoIter<STeXToken<()>>>,
60}
61
62pub(super) fn parse_deps<'a>(
63    source: &'a str,
64    path: &'a Path,
65    doc: &'a DocumentURI,
66    backend: &'a AnyBackend,
67) -> impl Iterator<Item = STeXDependency> + use<'a> {
68    const NOERR: fn(String, SourceRange<()>, DiagnosticLevel) = |_, _, _| {};
69    let archive = doc.archive_uri();
70    let parser = LaTeXParser::with_rules(
71        ParseStr::new(source),
72        STeXParseState::<(), ()>::new(Some(archive), Some(path), doc, backend, ()),
73        NOERR,
74        LaTeXParser::default_rules().into_iter().chain([
75            ("importmodule", rules::importmodule_deps as _),
76            ("setmetatheory", rules::setmetatheory as _),
77            ("usemodule", rules::usemodule_deps as _),
78            ("inputref", rules::inputref as _),
79            ("mhinput", rules::inputref as _),
80            ("mhgraphics", rules::mhgraphics as _),
81            ("cmhgraphics", rules::mhgraphics as _),
82            ("stexstyleassertion", rules::stexstyleassertion as _),
83            ("stexstyledefinition", rules::stexstyledefinition as _),
84            ("stexstyleparagraph", rules::stexstyleparagraph as _),
85        ]),
86        LaTeXParser::default_env_rules().into_iter().chain([(
87            "smodule",
88            (
89                rules::smodule_deps_open as _,
90                rules::smodule_deps_close as _,
91            ),
92        )]),
93    );
94    DepParser {
95        parser,
96        stack: Vec::new(),
97        curr: None,
98    }
99}
100
101impl DepParser<'_> {
102    fn convert(&mut self, t: STeXToken<()>) -> Option<STeXDependency> {
103        match t {
104            STeXToken::ImportModule {
105                module:
106                    ModuleReference {
107                        uri,
108                        rel_path: Some(rel_path),
109                        ..
110                    },
111                ..
112            }
113            | STeXToken::SetMetatheory {
114                module:
115                    ModuleReference {
116                        uri,
117                        rel_path: Some(rel_path),
118                        ..
119                    },
120                ..
121            } => Some(STeXDependency::ImportModule {
122                archive: uri.archive_id().clone(),
123                module: rel_path,
124            }),
125            STeXToken::UseModule {
126                module:
127                    ModuleReference {
128                        uri,
129                        rel_path: Some(rel_path),
130                        ..
131                    },
132                ..
133            } => Some(STeXDependency::UseModule {
134                archive: uri.archive_id().clone(),
135                module: rel_path,
136            }),
137            STeXToken::Module {
138                /*uri,*/ sig,
139                children,
140                meta_theory,
141                ..
142            } => {
143                let old = std::mem::replace(&mut self.curr, Some(children.into_iter()));
144                if let Some(old) = old {
145                    self.stack.push(old);
146                }
147                Some(STeXDependency::Module {
148                    /*uri,*/ sig,
149                    meta: meta_theory
150                        .and_then(|m| m.rel_path.map(|p| (m.uri.archive_id().clone(), p))),
151                })
152            }
153            STeXToken::Inputref {
154                archive, filepath, ..
155            } => Some(STeXDependency::Inputref {
156                archive: archive.map(|(a, _)| a),
157                filepath: filepath.0,
158            }),
159            STeXToken::Vec(v) => {
160                let old = std::mem::replace(&mut self.curr, Some(v.into_iter()));
161                if let Some(old) = old {
162                    self.stack.push(old);
163                }
164                None
165            }
166            STeXToken::MHGraphics {
167                filepath, archive, ..
168            } => Some(STeXDependency::Img {
169                archive: archive.map(|(a, _)| a),
170                filepath: filepath.0,
171            }),
172            _ => None,
173        }
174    }
175}
176
177impl Iterator for DepParser<'_> {
178    type Item = STeXDependency;
179    fn next(&mut self) -> Option<Self::Item> {
180        loop {
181            if let Some(curr) = &mut self.curr {
182                if let Some(t) = curr.next() {
183                    if let Some(t) = self.convert(t) {
184                        return Some(t);
185                    }
186                } else {
187                    self.curr = self.stack.pop();
188                }
189            } else if let Some(t) = self.parser.next() {
190                if let Some(t) = self.convert(t) {
191                    return Some(t);
192                }
193            } else {
194                return None;
195            }
196        }
197    }
198}
199
200#[allow(clippy::too_many_lines)]
201pub fn get_deps(backend: &AnyBackend, task: &BuildTask) {
202    let Either::Left(path) = task.source() else {
203        return;
204    };
205    let Ok(uri) = task.document_uri() else { return };
206    let source = std::fs::read_to_string(path);
207    if let Ok(source) = source {
208        //let mut yields = Vec::new();
209        for d in parse_deps(&source, path, &uri, backend) {
210            match d {
211                STeXDependency::ImportModule { archive, module }
212                | STeXDependency::UseModule { archive, module } => {
213                    if let Some(step) = task.get_step(PDFLATEX_FIRST) {
214                        step.add_dependency(Dependency::Physical {
215                            strict: false,
216                            task: TaskRef {
217                                archive: archive.clone(),
218                                rel_path: module.clone(),
219                                target: PDFLATEX_FIRST,
220                            },
221                        });
222                    }
223                    if let Some(step) = task.get_step(CHECK) {
224                        step.add_dependency(Dependency::Physical {
225                            strict: true,
226                            task: TaskRef {
227                                archive,
228                                rel_path: module,
229                                target: CHECK,
230                            },
231                        });
232                    }
233                }
234                STeXDependency::Inputref { archive, filepath } => {
235                    if let Some(step) = task.get_step(PDFLATEX_FIRST) {
236                        step.add_dependency(Dependency::Physical {
237                            strict: false,
238                            task: TaskRef {
239                                archive: archive.unwrap_or_else(|| task.archive().id().clone()),
240                                rel_path: filepath,
241                                target: PDFLATEX_FIRST,
242                            },
243                        });
244                    }
245                }
246                STeXDependency::Module {
247                    /*uri:_,*/ sig,
248                    meta,
249                } => {
250                    //yields.push(uri);
251                    if let Some(lang) = sig {
252                        let archive = task.archive().id().clone();
253                        let Some(rel_path) = task.rel_path().rsplit_once('.').and_then(|(a, _)| {
254                            a.rsplit_once('.').map(|(a, _)| format!("{a}.{lang}.tex"))
255                        }) else {
256                            continue;
257                        };
258                        //tracing::debug!("Adding dependency: {:?}", rf);
259                        if let Some(step) = task.get_step(PDFLATEX_FIRST) {
260                            step.add_dependency(Dependency::Physical {
261                                strict: false,
262                                task: TaskRef {
263                                    archive: archive.clone(),
264                                    rel_path: rel_path.clone().into(),
265                                    target: PDFLATEX_FIRST,
266                                },
267                            });
268                        }
269                        if let Some(step) = task.get_step(CHECK) {
270                            step.add_dependency(Dependency::Physical {
271                                strict: true,
272                                task: TaskRef {
273                                    archive,
274                                    rel_path: rel_path.into(),
275                                    target: CHECK,
276                                },
277                            });
278                        }
279                    }
280                    if let Some((archive, module)) = meta {
281                        if let Some(step) = task.get_step(PDFLATEX_FIRST) {
282                            step.add_dependency(Dependency::Physical {
283                                strict: false,
284                                task: TaskRef {
285                                    archive: archive.clone(),
286                                    rel_path: module.clone(),
287                                    target: PDFLATEX_FIRST,
288                                },
289                            });
290                        }
291                        if let Some(step) = task.get_step(CHECK) {
292                            step.add_dependency(Dependency::Physical {
293                                strict: true,
294                                task: TaskRef {
295                                    archive,
296                                    rel_path: module,
297                                    target: CHECK,
298                                },
299                            });
300                        }
301                    }
302                }
303                STeXDependency::Img { .. } => (),
304            }
305        }
306    }
307}