swamp_dep_loader/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5pub 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"); // lib is default if the path only contains the package root
194    }
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
218/// Parses just a single module. Any `mod` keywords in this file will be ignored. So this
219/// is mainly for internal use.
220pub 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            //trace!(?path, depth, "visit path");
358            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
398/// # Errors
399///
400fn 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    // First try environment variable
418    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// Verifies the structure of swamp_home to make sure if we can use it
427#[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/// # Errors
442///
443#[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}