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::io::ErrorKind;
11use std::path::{Path, PathBuf};
12use std::{env, io};
13use swamp_ast::Function;
14use swamp_ast::prelude::*;
15use swamp_parser::{AstParser, SpecificError};
16use time_dilation::ScopedTimer;
17
18pub struct ParseRoot;
19
20#[derive(Debug)]
21pub enum ParseRootError {
22    ParserError(ParserError),
23}
24
25#[derive(Debug)]
26pub struct ParsedAstModule {
27    pub ast_module: swamp_ast::Module,
28    pub file_id: FileId,
29}
30
31impl ParsedAstModule {
32    // TODO: HACK: declare_external_function() should be removed
33    pub fn declare_external_function(
34        &mut self,
35        parameters: Vec<Parameter>,
36        return_type: Option<Type>,
37    ) {
38        let fake_identifier = Node::default();
39
40        let signature = FunctionDeclaration {
41            name: fake_identifier,
42            params: parameters,
43            self_parameter: None,
44            return_type,
45            generic_variables: vec![],
46        };
47        let external_signature = Function::External(signature);
48
49        self.ast_module.definitions.insert(
50            0, // add it first
51            Definition::FunctionDef(external_signature),
52        );
53    }
54}
55
56#[derive(Debug)]
57pub struct RelativePath(pub String);
58
59#[derive(Debug)]
60pub struct ParserError {
61    pub node: Node,
62    pub specific: SpecificError,
63    pub file_id: FileId,
64}
65
66impl Default for ParseRoot {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl ParseRoot {
73    #[must_use]
74    pub const fn new() -> Self {
75        Self {}
76    }
77
78    pub fn parse(
79        &self,
80        contents: String,
81        file_id: FileId,
82    ) -> Result<ParsedAstModule, ParseRootError> {
83        let ast_program = AstParser.parse_module(&contents).map_err(|err| {
84            let new_err = ParserError {
85                node: Node { span: err.span },
86                specific: err.specific,
87                file_id,
88            };
89            ParseRootError::ParserError(new_err)
90        })?;
91
92        Ok(ParsedAstModule {
93            ast_module: ast_program,
94            file_id,
95        })
96    }
97}
98
99#[derive(Clone)]
100#[allow(unused)]
101pub struct ModuleInfo {
102    path: Vec<String>,
103    imports: Vec<Vec<String>>,
104    uses: Vec<Vec<String>>,
105    parsed: bool,
106    analyzed: bool,
107}
108
109pub struct DependencyParser {
110    pub import_scanned_modules: SeqMap<Vec<String>, ModuleInfo>,
111    already_parsed_modules: SeqMap<Vec<String>, ParsedAstModule>,
112    pub already_resolved_modules: HashSet<Vec<String>>,
113}
114
115impl Default for DependencyParser {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121impl DependencyParser {
122    pub fn new() -> Self {
123        Self {
124            import_scanned_modules: SeqMap::new(),
125            already_parsed_modules: SeqMap::new(),
126            already_resolved_modules: HashSet::new(),
127        }
128    }
129
130    pub fn add_resolved_module(&mut self, module_path: Vec<String>) {
131        self.already_resolved_modules.insert(module_path);
132    }
133
134    pub fn add_ast_module(&mut self, module_path: Vec<String>, parsed_module: ParsedAstModule) {
135        self.already_parsed_modules
136            .insert(module_path, parsed_module)
137            .expect("can not add parsed module")
138    }
139}
140
141#[derive(Debug)]
142pub enum DependencyError {
143    CircularDependency(Vec<String>),
144    ParseRootError(ParseRootError),
145    ReadFileError(io::Error),
146}
147
148impl From<ParseRootError> for DependencyError {
149    fn from(err: ParseRootError) -> Self {
150        Self::ParseRootError(err)
151    }
152}
153
154pub const LOCAL_ROOT_PACKAGE_PATH: &str = "crate";
155
156pub fn get_all_local_paths(
157    source_map: &SourceMap,
158    parsed_module: &ParsedAstModule,
159) -> (Vec<Vec<String>>, Vec<Vec<String>>) {
160    let mut imports = vec![];
161    let mut uses = vec![];
162
163    for def in parsed_module.ast_module.definitions() {
164        match def {
165            Definition::Mod(import) => {
166                let mut sections = Vec::new();
167                sections.push(LOCAL_ROOT_PACKAGE_PATH.to_string());
168                for section_node in &import.module_path.0 {
169                    let import_path = source_map
170                        .get_span_source(
171                            parsed_module.file_id,
172                            section_node.span.offset as usize,
173                            section_node.span.length.into(),
174                        )
175                        .to_string();
176                    sections.push(import_path);
177                }
178
179                imports.push(sections);
180            }
181
182            Definition::Use(import) => {
183                let mut sections = Vec::new();
184                for section_node in &import.module_path.0 {
185                    let import_path = source_map
186                        .get_span_source(
187                            parsed_module.file_id,
188                            section_node.span.offset as usize,
189                            section_node.span.length.into(),
190                        )
191                        .to_string();
192                    sections.push(import_path);
193                }
194                uses.push(sections);
195            }
196            _ => continue,
197        }
198    }
199
200    (imports, uses)
201}
202
203#[must_use]
204pub fn module_path_to_relative_swamp_file(module_path_vec: &[String]) -> PathBuf {
205    let mut path_buf = PathBuf::new();
206
207    let orig_len = module_path_vec.len();
208
209    let converted_path = if module_path_vec[0] == "crate" {
210        &module_path_vec[1..]
211    } else {
212        module_path_vec
213    };
214
215    path_buf.push(converted_path.join("/"));
216    if orig_len == 1 {
217        path_buf.push("lib"); // lib is default if the path only contains the package root
218    }
219
220    path_buf.set_extension("swamp");
221
222    path_buf
223}
224
225#[must_use]
226pub fn module_path_to_relative_swamp_file_string(module_path_vec: &[String]) -> String {
227    module_path_to_relative_swamp_file(module_path_vec)
228        .to_str()
229        .unwrap()
230        .into()
231}
232
233#[must_use]
234pub fn mount_name_from_path(path: &[String]) -> &str {
235    if path[0] == "crate" {
236        "crate"
237    } else {
238        "registry"
239    }
240}
241
242/// Parses just a single module. Any `mod` keywords in this file will be ignored. So this
243/// is mainly for internal use.
244pub fn parse_single_module(
245    source_map: &mut SourceMap,
246    module_path: &[String],
247) -> Result<ParsedAstModule, DependencyError> {
248    let debug = format!("parse module {module_path:?}");
249    let _parse_module_timer = ScopedTimer::new(&debug);
250
251    let mount_name = mount_name_from_path(module_path);
252
253    let (file_id, script) = source_map
254        .read_file_relative(
255            mount_name,
256            &module_path_to_relative_swamp_file_string(module_path),
257        )
258        .map_err(DependencyError::ReadFileError)?;
259
260    let parse_module = ParseRoot.parse(script, file_id)?;
261
262    Ok(parse_module)
263}
264
265impl DependencyParser {
266    pub fn parse_local_modules(
267        &mut self,
268        module_path: &[String],
269        source_map: &mut SourceMap,
270    ) -> Result<(), DependencyError> {
271        let mut to_parse = vec![module_path.to_vec()];
272
273        while let Some(path) = to_parse.pop() {
274            let module_path_vec = &path.clone();
275            if self.import_scanned_modules.contains_key(module_path_vec) {
276                continue;
277            }
278
279            let parsed_module_to_scan =
280                if let Some(parsed_module) = self.already_parsed_modules.get(module_path_vec) {
281                    parsed_module
282                } else if self.already_resolved_modules.contains(module_path_vec) {
283                    continue;
284                } else if path == ["core"] {
285                    continue;
286                } else {
287                    let parsed_ast_module = parse_single_module(source_map, &path)?;
288
289                    self.already_parsed_modules
290                        .insert(path.clone(), parsed_ast_module)
291                        .expect("TODO: panic message");
292
293                    self.already_parsed_modules
294                        .get(&path.clone())
295                        .expect("we just inserted it")
296                };
297
298            let (imports, uses) = get_all_local_paths(source_map, parsed_module_to_scan);
299            let filtered_imports: Vec<Vec<String>> = imports
300                .into_iter()
301                .filter(|import| !self.already_resolved_modules.contains(import))
302                .collect();
303
304            let filtered_uses: Vec<Vec<String>> = uses
305                .into_iter()
306                .filter(|import| !self.already_resolved_modules.contains(import))
307                .collect();
308
309            self.import_scanned_modules
310                .insert(
311                    path.clone(),
312                    ModuleInfo {
313                        path: path.clone(),
314                        imports: filtered_imports.clone(),
315                        uses: filtered_uses.clone(),
316                        parsed: false,
317                        analyzed: false,
318                    },
319                )
320                .expect("TODO: panic message");
321
322            to_parse.extend(filtered_imports.clone());
323
324            to_parse.extend(filtered_uses.clone());
325        }
326        Ok(())
327    }
328
329    #[must_use]
330    pub fn get_parsed_module(&self, path: &[String]) -> Option<&ParsedAstModule> {
331        self.already_parsed_modules.get(&path.to_vec())
332    }
333
334    pub fn get_parsed_module_mut(&mut self, path: &[String]) -> Option<&mut ParsedAstModule> {
335        self.already_parsed_modules.get_mut(&path.to_vec())
336    }
337
338    pub(crate) fn get_analysis_order(&self) -> Result<Vec<Vec<String>>, DependencyError> {
339        let mut order = Vec::new();
340        let mut visited = HashSet::new();
341        let mut temp_visited = HashSet::new();
342
343        fn visit(
344            graph: &DependencyParser,
345            path: &[String],
346            visited: &mut HashSet<Vec<String>>,
347            temp_visited: &mut HashSet<Vec<String>>,
348            order: &mut Vec<Vec<String>>,
349        ) -> Result<(), DependencyError> {
350            if temp_visited.contains(path) {
351                return Err(DependencyError::CircularDependency(Vec::from(path)));
352            }
353
354            if visited.contains(path) {
355                return Ok(());
356            }
357
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)?;
363                }
364                for import in &module.imports {
365                    visit(graph, import, visited, temp_visited, order)?;
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)?;
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///
400pub fn os_home_relative_path(project_name: &str) -> io::Result<PathBuf> {
401    home_dir().map_or_else(
402        || {
403            Err(io::Error::new(
404                io::ErrorKind::Other,
405                "Could not determine home directory",
406            ))
407        },
408        |home_path| {
409            let mut path = home_path;
410            path.push(format!(".{project_name}"));
411            Ok(path)
412        },
413    )
414}
415
416pub fn path_from_environment_variable() -> io::Result<PathBuf> {
417    env::var("SWAMP_HOME").as_ref().map_or_else(
418        |_| Err(io::Error::new(ErrorKind::InvalidData, "missing SWAMP_HOME")),
419        |string_value| Ok(Path::new(string_value).to_path_buf()),
420    )
421}
422pub fn swamp_home() -> io::Result<PathBuf> {
423    path_from_environment_variable().map_or_else(|_| os_home_relative_path("swamp"), Ok)
424}
425
426/// # Errors
427///
428pub fn swamp_registry_path() -> io::Result<PathBuf> {
429    let mut swamp_home = swamp_home()?;
430    swamp_home.push("packages");
431    Ok(swamp_home)
432}
433
434pub fn parse_local_modules_and_get_order(
435    module_path: &[String],
436    dependency_parser: &mut DependencyParser,
437    source_map: &mut SourceMap,
438) -> Result<Vec<Vec<String>>, DepLoaderError> {
439    dependency_parser.parse_local_modules(&module_path, source_map)?;
440
441    let module_paths_in_order = dependency_parser.get_analysis_order()?;
442
443    Ok(module_paths_in_order)
444}