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 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 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 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 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 sig,
243 meta,
244 } => {
245 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}