swamp_script_dep_loader/
lib.rs

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