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