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 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 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 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 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 sig,
248 meta,
249 } => {
250 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 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}