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 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 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 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 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 sig,
241 meta,
242 } => {
243 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}