Skip to main content

kb/
loader.rs

1use super::parse;
2use super::BasicError;
3use super::File;
4use super::RcStr;
5use super::Source;
6use super::PRELUDE_NAME;
7use std::collections::HashMap;
8use std::collections::HashSet;
9use std::path::Path;
10use std::path::PathBuf;
11use std::rc::Rc;
12
13pub struct Loader {
14    source_roots: Vec<PathBuf>,
15    map: HashMap<RcStr, Rc<Source>>,
16}
17
18impl Loader {
19    pub fn new() -> Self {
20        Self {
21            source_roots: vec![],
22            map: HashMap::new(),
23        }
24    }
25
26    pub fn add_source(&mut self, source: Rc<Source>) {
27        self.map.insert(source.name.clone(), source);
28    }
29
30    pub fn add_source_root<P: Into<PathBuf>>(&mut self, path: P) {
31        self.source_roots.push(path.into());
32    }
33
34    pub fn find_source(&mut self, module_name: &RcStr) -> Result<Option<&Rc<Source>>, BasicError> {
35        if !self.map.contains_key(module_name) {
36            let mut relpath = PathBuf::new();
37            let parts: Vec<_> = module_name.split(".").collect();
38            for part in &parts[..parts.len() - 1] {
39                relpath.push(part);
40            }
41            if let Some(part) = parts.last() {
42                relpath.push(format!("{}.kb", part));
43            }
44            for root in &self.source_roots {
45                let path = root.join(&relpath);
46                if path.is_file() {
47                    let data = std::fs::read_to_string(path)?;
48                    self.map.insert(
49                        module_name.clone(),
50                        Rc::new(Source {
51                            name: module_name.clone(),
52                            data: data.into(),
53                        }),
54                    );
55                    break;
56                }
57            }
58        }
59        Ok(self.map.get(module_name))
60    }
61
62    pub fn list_child_modules(&mut self, module_name: &RcStr) -> Result<Vec<RcStr>, BasicError> {
63        let mut module_names = HashSet::new();
64        let prefix: RcStr = format!("{}.", module_name).into();
65        for (name, _) in &self.map {
66            if name == module_name || name.starts_with(prefix.as_ref()) {
67                module_names.insert(module_name.clone());
68            }
69        }
70        let relpath =
71            PathBuf::from(module_name.replace('.', &format!("{}", std::path::MAIN_SEPARATOR)));
72        for source_root in &self.source_roots {
73            let start_dir = source_root.join(&relpath);
74            module_names.extend(walk_src(&start_dir, &module_name));
75        }
76
77        let mut ret: Vec<_> = module_names.into_iter().collect();
78        ret.sort();
79        Ok(ret)
80    }
81
82    pub fn load(&mut self, module_name: &RcStr) -> Result<Vec<File>, BasicError> {
83        let mut files = Vec::new();
84        let mut stack = vec![module_name.clone(), PRELUDE_NAME.into()];
85        let mut seen: HashSet<RcStr> = stack.clone().into_iter().collect();
86        while let Some(name) = stack.pop() {
87            let source = match self.find_source(&name)? {
88                Some(source) => source,
89                None => {
90                    return Err(BasicError {
91                        marks: vec![],
92                        message: format!("Module {:?} not found", name),
93                        help: None,
94                    })
95                }
96            };
97            let file = parse(&source)?;
98            for imp in &file.imports {
99                if !seen.contains(&imp.module_name) {
100                    seen.insert(imp.module_name.clone());
101                    stack.push(imp.module_name.clone());
102                }
103            }
104            files.push(file);
105        }
106        files.reverse();
107        Ok(files)
108    }
109}
110
111/// walks the directory returning all files it finds on a best effort
112/// basis (ignores any folders/files it can't opene)
113fn walk(start: &Path, extension: Option<&str>) -> Vec<PathBuf> {
114    let mut ret = Vec::new();
115    let mut stack = vec![start.to_owned()];
116    if let Some(ext) = extension {
117        if let Some(start) = start.to_str() {
118            stack.push(format!("{}{}", start, ext).into());
119        }
120    }
121    while let Some(path) = stack.pop() {
122        if path.is_file()
123            && extension
124                .map(|ext| {
125                    path.file_name()
126                        .and_then(|p| p.to_str().map(|p| p.ends_with(ext)))
127                        .unwrap_or(false)
128                })
129                .unwrap_or(true)
130        {
131            ret.push(path);
132        } else if path.is_dir() {
133            if let Ok(entries) = path.read_dir() {
134                for entry in entries {
135                    if let Ok(entry) = entry {
136                        stack.push(entry.path());
137                    }
138                }
139            }
140        }
141    }
142    ret
143}
144
145fn walk_src(start: &Path, prefix: &str) -> Vec<RcStr> {
146    let startstr = start.to_str().unwrap();
147    let mut module_names = Vec::new();
148    for path in walk(start, Some(".kb")) {
149        if let Some(pathstr) = path.to_str() {
150            let pathstr = pathstr.strip_suffix(".kb").unwrap();
151            if let Some(pathstr) = pathstr.strip_prefix(startstr) {
152                if pathstr.is_empty() {
153                    module_names.push(prefix.into());
154                } else {
155                    let relname = pathstr.replace(std::path::MAIN_SEPARATOR, ".");
156                    let relname = relname.strip_prefix(".").unwrap_or(&relname);
157                    let module_name = if prefix.is_empty() {
158                        relname.to_owned()
159                    } else {
160                        format!("{}.{}", prefix, relname)
161                    };
162                    module_names.push(module_name.into());
163                }
164            }
165        }
166    }
167    module_names
168}