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