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
111fn 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}