Skip to main content

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