1pub mod prelude;
6use dirs::home_dir;
7use seq_map::SeqMap;
8use source_map_cache::{FileId, SourceMap};
9use std::collections::HashSet;
10use std::path::{Path, PathBuf};
11use std::{env, io};
12use swamp_ast::prelude::*;
13use swamp_parser::{AstParser, SpecificError};
14use time_dilation::ScopedTimer;
15use tracing::error;
16
17pub struct ParseRoot;
18
19#[derive(Debug)]
20pub enum ParseRootError {
21 ParserError(ParserError),
22}
23
24#[derive(Debug)]
25pub struct ParsedAstModule {
26 pub ast_module: swamp_ast::Module,
27 pub file_id: FileId,
28}
29
30#[derive(Debug)]
31pub struct RelativePath(pub String);
32
33#[derive(Debug)]
34pub struct ParserError {
35 pub node: Node,
36 pub specific: SpecificError,
37 pub file_id: FileId,
38}
39
40impl Default for ParseRoot {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46impl ParseRoot {
47 #[must_use]
48 pub const fn new() -> Self {
49 Self {}
50 }
51
52 pub fn parse(
53 &self,
54 contents: String,
55 file_id: FileId,
56 ) -> Result<ParsedAstModule, ParseRootError> {
57 let ast_program = AstParser.parse_module(&contents).map_err(|err| {
58 let new_err = ParserError {
59 node: Node { span: err.span },
60 specific: err.specific,
61 file_id,
62 };
63 ParseRootError::ParserError(new_err)
64 })?;
65
66 Ok(ParsedAstModule {
67 ast_module: ast_program,
68 file_id,
69 })
70 }
71}
72
73#[derive(Clone)]
74#[allow(unused)]
75pub struct ModuleInfo {
76 path: Vec<String>,
77 imports: Vec<Vec<String>>,
78 uses: Vec<Vec<String>>,
79 parsed: bool,
80 analyzed: bool,
81}
82
83pub struct DependencyParser {
84 pub import_scanned_modules: SeqMap<Vec<String>, ModuleInfo>,
85 already_parsed_modules: SeqMap<Vec<String>, ParsedAstModule>,
86 pub already_resolved_modules: HashSet<Vec<String>>,
87}
88
89impl Default for DependencyParser {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl DependencyParser {
96 #[must_use]
97 pub fn new() -> Self {
98 Self {
99 import_scanned_modules: SeqMap::new(),
100 already_parsed_modules: SeqMap::new(),
101 already_resolved_modules: HashSet::new(),
102 }
103 }
104
105 pub fn add_resolved_module(&mut self, module_path: Vec<String>) {
106 self.already_resolved_modules.insert(module_path);
107 }
108
109 pub fn add_ast_module(&mut self, module_path: Vec<String>, parsed_module: ParsedAstModule) {
110 self.already_parsed_modules
111 .insert(module_path, parsed_module)
112 .expect("insert");
113 }
114}
115
116#[derive(Debug)]
117pub enum DependencyError {
118 CircularDependency(Vec<String>),
119 ParseRootError(ParseRootError),
120 ReadFileError(io::Error),
121}
122
123impl From<ParseRootError> for DependencyError {
124 fn from(err: ParseRootError) -> Self {
125 Self::ParseRootError(err)
126 }
127}
128
129pub const LOCAL_ROOT_PACKAGE_PATH: &str = "crate";
130
131#[must_use]
132pub fn get_all_local_paths(
133 source_map: &SourceMap,
134 parsed_module: &ParsedAstModule,
135) -> (Vec<Vec<String>>, Vec<Vec<String>>) {
136 let mut imports = vec![];
137 let mut uses = vec![];
138
139 for def in parsed_module.ast_module.definitions() {
140 match &def.kind {
141 DefinitionKind::Mod(import) => {
142 let mut sections = Vec::new();
143 sections.push(LOCAL_ROOT_PACKAGE_PATH.to_string());
144 for section_node in &import.module_path.0 {
145 let import_path = source_map
146 .get_span_source(
147 parsed_module.file_id,
148 section_node.span.offset as usize,
149 section_node.span.length.into(),
150 )
151 .to_string();
152 sections.push(import_path);
153 }
154
155 imports.push(sections);
156 }
157
158 DefinitionKind::Use(import) => {
159 let mut sections = Vec::new();
160 for section_node in &import.module_path.0 {
161 let import_path = source_map
162 .get_span_source(
163 parsed_module.file_id,
164 section_node.span.offset as usize,
165 section_node.span.length.into(),
166 )
167 .to_string();
168 sections.push(import_path);
169 }
170 uses.push(sections);
171 }
172 _ => continue,
173 }
174 }
175
176 (imports, uses)
177}
178
179#[must_use]
180pub fn module_path_to_relative_swamp_file(module_path_vec: &[String]) -> PathBuf {
181 let mut path_buf = PathBuf::new();
182
183 let orig_len = module_path_vec.len();
184
185 let converted_path = if module_path_vec[0] == "crate" {
186 &module_path_vec[1..]
187 } else {
188 module_path_vec
189 };
190
191 path_buf.push(converted_path.join("/"));
192 if orig_len == 1 {
193 path_buf.push("lib"); }
195
196 path_buf.set_extension("swamp");
197
198 path_buf
199}
200
201#[must_use]
202pub fn module_path_to_relative_swamp_file_string(module_path_vec: &[String]) -> String {
203 module_path_to_relative_swamp_file(module_path_vec)
204 .to_str()
205 .unwrap()
206 .into()
207}
208
209#[must_use]
210pub fn mount_name_from_path(path: &[String]) -> &str {
211 if path[0] == "crate" {
212 "crate"
213 } else {
214 "registry"
215 }
216}
217
218pub fn parse_single_module(
221 source_map: &mut SourceMap,
222 module_path: &[String],
223) -> Result<ParsedAstModule, DependencyError> {
224 let debug = format!("parse module {module_path:?}");
225 let _parse_module_timer = ScopedTimer::new(&debug);
226
227 let mount_name = mount_name_from_path(module_path);
228
229 let (file_id, script) = source_map
230 .read_file_relative(
231 mount_name,
232 &module_path_to_relative_swamp_file_string(module_path),
233 )
234 .map_err(DependencyError::ReadFileError)?;
235
236 let parse_module = ParseRoot.parse(script, file_id)?;
237
238 Ok(parse_module)
239}
240
241pub fn parse_single_module_from_text(
242 source_map: &mut SourceMap,
243 module_path: &[String],
244 script: &str,
245) -> Result<ParsedAstModule, DependencyError> {
246 let debug = format!("parse module {module_path:?}");
247 let _parse_module_timer = ScopedTimer::new(&debug);
248
249 let mount_name = mount_name_from_path(module_path);
250
251 let file_id = source_map.set(
252 mount_name,
253 module_path_to_relative_swamp_file_string(module_path).as_ref(),
254 script,
255 );
256
257 let parse_module = ParseRoot.parse(script.to_string(), file_id)?;
258
259 Ok(parse_module)
260}
261
262impl DependencyParser {
263 pub fn parse_local_modules(
264 &mut self,
265 module_path: &[String],
266 source_map: &mut SourceMap,
267 ) -> Result<(), DependencyError> {
268 let mut to_parse = vec![module_path.to_vec()];
269
270 while let Some(path) = to_parse.pop() {
271 let module_path_vec = &path.clone();
272 if self.import_scanned_modules.contains_key(module_path_vec) {
273 continue;
274 }
275
276 let parsed_module_to_scan =
277 if let Some(parsed_module) = self.already_parsed_modules.get(module_path_vec) {
278 parsed_module
279 } else if self.already_resolved_modules.contains(module_path_vec) {
280 continue;
281 } else if path == ["core"] || path == ["std"] {
282 continue;
283 } else {
284 let parsed_ast_module = parse_single_module(source_map, &path)?;
285
286 self.already_parsed_modules
287 .insert(path.clone(), parsed_ast_module)
288 .expect("insert");
289
290 self.already_parsed_modules
291 .get(&path.clone())
292 .expect("we just inserted it")
293 };
294
295 let (imports, uses) = get_all_local_paths(source_map, parsed_module_to_scan);
296 let filtered_imports: Vec<Vec<String>> = imports
297 .into_iter()
298 .filter(|import| !self.already_resolved_modules.contains(import))
299 .collect();
300
301 let filtered_uses: Vec<Vec<String>> = uses
302 .into_iter()
303 .filter(|import| !self.already_resolved_modules.contains(import))
304 .collect();
305
306 self.import_scanned_modules
307 .insert(
308 path.clone(),
309 ModuleInfo {
310 path: path.clone(),
311 imports: filtered_imports.clone(),
312 uses: filtered_uses.clone(),
313 parsed: false,
314 analyzed: false,
315 },
316 )
317 .expect("insert");
318
319 to_parse.extend(filtered_imports.clone());
320
321 to_parse.extend(filtered_uses.clone());
322 }
323 Ok(())
324 }
325
326 #[must_use]
327 pub fn get_parsed_module(&self, path: &[String]) -> Option<&ParsedAstModule> {
328 self.already_parsed_modules.get(&path.to_vec())
329 }
330
331 pub fn get_parsed_module_mut(&mut self, path: &[String]) -> Option<&mut ParsedAstModule> {
332 self.already_parsed_modules.get_mut(&path.to_vec())
333 }
334
335 pub(crate) fn get_analysis_order(&self) -> Result<Vec<Vec<String>>, DependencyError> {
336 let mut order = Vec::new();
337 let mut visited = HashSet::new();
338 let mut temp_visited = HashSet::new();
339
340 fn visit(
341 graph: &DependencyParser,
342 path: &[String],
343 visited: &mut HashSet<Vec<String>>,
344 temp_visited: &mut HashSet<Vec<String>>,
345 order: &mut Vec<Vec<String>>,
346 depth: usize,
347 ) -> Result<(), DependencyError> {
348 if temp_visited.contains(path) {
349 error!(?path, depth, "already visited this path");
350 return Err(DependencyError::CircularDependency(Vec::from(path)));
351 }
352
353 if visited.contains(path) {
354 return Ok(());
355 }
356
357 temp_visited.insert(Vec::from(path));
359
360 if let Some(module) = graph.import_scanned_modules.get(&path.to_vec()) {
361 for import in &module.uses {
362 visit(graph, import, visited, temp_visited, order, depth + 1)?;
363 }
364 for import in &module.imports {
365 visit(graph, import, visited, temp_visited, order, depth + 1)?;
366 }
367 }
368
369 order.push(Vec::from(path));
370 visited.insert(Vec::from(path));
371
372 temp_visited.remove(path);
373
374 Ok(())
375 }
376
377 for path in self.import_scanned_modules.keys() {
378 if !visited.contains(path) {
379 visit(self, path, &mut visited, &mut temp_visited, &mut order, 0)?;
380 }
381 }
382
383 Ok(order)
384 }
385}
386
387#[derive(Debug)]
388pub enum DepLoaderError {
389 DependencyError(DependencyError),
390}
391
392impl From<DependencyError> for DepLoaderError {
393 fn from(e: DependencyError) -> Self {
394 Self::DependencyError(e)
395 }
396}
397
398fn os_home_relative_path(project_name: &str) -> Option<PathBuf> {
401 home_dir().map(|home_path| home_path.join(format!(".{project_name}")))
402}
403
404fn current_directory_relative_path(project_name: &str) -> PathBuf {
405 Path::new(".").join(format!(".{project_name}"))
406}
407
408#[must_use]
409pub fn path_from_environment_variable() -> Option<PathBuf> {
410 env::var("SWAMP_HOME")
411 .map(|string_value| Path::new(&string_value).to_path_buf())
412 .ok()
413}
414
415#[must_use]
416pub fn swamp_home(run_mode: &RunMode) -> Option<PathBuf> {
417 match run_mode {
419 RunMode::Development => {
420 path_from_environment_variable().or_else(|| os_home_relative_path("swamp"))
421 }
422 RunMode::Deployed => Some(Path::new(".").to_path_buf()),
423 }
424}
425
426#[must_use]
428pub fn verify_if_swamp_home_seems_correct(swamp_home: &Path) -> bool {
429 let mut swamp_packages_dir = swamp_home.to_path_buf();
430
431 swamp_packages_dir.push("packages");
432
433 swamp_packages_dir.exists() && swamp_packages_dir.is_dir()
434}
435
436pub enum RunMode {
437 Development,
438 Deployed,
439}
440
441#[must_use]
444pub fn swamp_registry_path(run_mode: &RunMode) -> Option<PathBuf> {
445 let swamp_home = swamp_home(run_mode)?;
446
447 if verify_if_swamp_home_seems_correct(&swamp_home) {
448 let mut packages_path = swamp_home;
449 packages_path.push("packages");
450
451 Some(packages_path)
452 } else {
453 None
454 }
455}
456
457pub fn parse_local_modules_and_get_order(
458 module_path: &[String],
459 dependency_parser: &mut DependencyParser,
460 source_map: &mut SourceMap,
461) -> Result<Vec<Vec<String>>, DepLoaderError> {
462 dependency_parser.parse_local_modules(module_path, source_map)?;
463
464 let module_paths_in_order = dependency_parser.get_analysis_order()?;
465
466 Ok(module_paths_in_order)
467}