use super::parse;
use super::BasicError;
use super::File;
use super::RcStr;
use super::Source;
use super::PRELUDE_NAME;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
pub struct Loader {
source_roots: Vec<PathBuf>,
map: HashMap<RcStr, Rc<Source>>,
}
impl Loader {
pub fn new() -> Self {
Self {
source_roots: vec![],
map: HashMap::new(),
}
}
pub fn add_source(&mut self, source: Rc<Source>) {
self.map.insert(source.name.clone(), source);
}
pub fn add_source_root<P: Into<PathBuf>>(&mut self, path: P) {
self.source_roots.push(path.into());
}
pub fn find_source(&mut self, module_name: &RcStr) -> Result<Option<&Rc<Source>>, BasicError> {
if !self.map.contains_key(module_name) {
let mut relpath = PathBuf::new();
let parts: Vec<_> = module_name.split(".").collect();
for part in &parts[..parts.len() - 1] {
relpath.push(part);
}
if let Some(part) = parts.last() {
relpath.push(format!("{}.kb", part));
}
for root in &self.source_roots {
let path = root.join(&relpath);
if path.is_file() {
let data = std::fs::read_to_string(path)?;
self.map.insert(
module_name.clone(),
Rc::new(Source {
name: module_name.clone(),
data: data.into(),
}),
);
break;
}
}
}
Ok(self.map.get(module_name))
}
pub fn list_child_modules(&mut self, module_name: &RcStr) -> Result<Vec<RcStr>, BasicError> {
let mut module_names = HashSet::new();
let prefix: RcStr = format!("{}.", module_name).into();
for (name, _) in &self.map {
if name == module_name || name.starts_with(prefix.as_ref()) {
module_names.insert(module_name.clone());
}
}
let relpath =
PathBuf::from(module_name.replace('.', &format!("{}", std::path::MAIN_SEPARATOR)));
for source_root in &self.source_roots {
let start_dir = source_root.join(&relpath);
module_names.extend(walk_src(&start_dir, &module_name));
}
let mut ret: Vec<_> = module_names.into_iter().collect();
ret.sort();
Ok(ret)
}
pub fn load(&mut self, module_name: &RcStr) -> Result<Vec<File>, BasicError> {
let mut files = Vec::new();
let mut stack = vec![module_name.clone(), PRELUDE_NAME.into()];
let mut seen: HashSet<RcStr> = stack.clone().into_iter().collect();
while let Some(name) = stack.pop() {
let source = match self.find_source(&name)? {
Some(source) => source,
None => {
return Err(BasicError {
marks: vec![],
message: format!("Module {:?} not found", name),
help: None,
})
}
};
let file = parse(&source)?;
for imp in &file.imports {
if !seen.contains(&imp.module_name) {
seen.insert(imp.module_name.clone());
stack.push(imp.module_name.clone());
}
}
files.push(file);
}
files.reverse();
Ok(files)
}
}
fn walk(start: &Path, extension: Option<&str>) -> Vec<PathBuf> {
let mut ret = Vec::new();
let mut stack = vec![start.to_owned()];
if let Some(ext) = extension {
if let Some(start) = start.to_str() {
stack.push(format!("{}{}", start, ext).into());
}
}
while let Some(path) = stack.pop() {
if path.is_file()
&& extension
.map(|ext| {
path.file_name()
.and_then(|p| p.to_str().map(|p| p.ends_with(ext)))
.unwrap_or(false)
})
.unwrap_or(true)
{
ret.push(path);
} else if path.is_dir() {
if let Ok(entries) = path.read_dir() {
for entry in entries {
if let Ok(entry) = entry {
stack.push(entry.path());
}
}
}
}
}
ret
}
fn walk_src(start: &Path, prefix: &str) -> Vec<RcStr> {
let startstr = start.to_str().unwrap();
let mut module_names = Vec::new();
for path in walk(start, Some(".kb")) {
if let Some(pathstr) = path.to_str() {
let pathstr = pathstr.strip_suffix(".kb").unwrap();
if let Some(pathstr) = pathstr.strip_prefix(startstr) {
if pathstr.is_empty() {
module_names.push(prefix.into());
} else {
let relname = pathstr.replace(std::path::MAIN_SEPARATOR, ".");
let relname = relname.strip_prefix(".").unwrap_or(&relname);
let module_name = if prefix.is_empty() {
relname.to_owned()
} else {
format!("{}.{}", prefix, relname)
};
module_names.push(module_name.into());
}
}
}
}
module_names
}