chronlang_engine/
resolver.rs

1use std::{collections::HashMap, path::Path, fs};
2
3#[derive(Debug, PartialEq, Clone)]
4pub enum ResolutionError {
5    InvalidPathForResolver(String),
6    PathNotFound(String),
7}
8
9impl ToString for ResolutionError {
10    fn to_string(&self) -> String {
11        match self {
12            ResolutionError::InvalidPathForResolver(reason) => format!("{reason}. Try using a different resolver."),
13            ResolutionError::PathNotFound(path) => format!("Failed to resolve path `{path}`."),
14        }
15    }
16}
17
18pub trait Resolve {
19    fn resolve(&self, path: &[&str]) -> Result<(String, String), ResolutionError>;
20}
21
22#[derive(Debug, PartialEq)]
23pub struct FileSystemResolver<'a> {
24    base_path: &'a Path,
25    cache: HashMap<&'a str, String>,
26}
27
28impl FileSystemResolver<'_> {
29    pub fn new(base_path: &Path) -> FileSystemResolver<'_> {
30        FileSystemResolver { base_path, cache: HashMap::new() }
31    }
32}
33
34impl Resolve for FileSystemResolver<'_> {
35    fn resolve(&self, path: &[&str]) -> Result<(String, String), ResolutionError> {
36        match path.get(0) {
37            Some(segment) if segment.starts_with('@') => {
38                return Err(ResolutionError::InvalidPathForResolver("FileSystemResolver cannot resolve remote imports.".into()))
39            },
40            _ => {}
41        }
42
43        let mut dir = self.base_path.join(path.join("/"));
44        dir.set_extension("lang");
45        let file_name = dir.to_str().unwrap();
46
47        if let Some(value) = self.cache.get(file_name) {
48            return Ok((value.clone(), file_name.to_string()))
49        }
50
51        let file = fs::read_to_string(dir.clone());
52        match file {
53            Ok(contents) => Ok((contents, file_name.to_string())),
54            _ => Err(ResolutionError::PathNotFound(file_name.to_string())),
55        }
56    }
57}
58
59#[derive(Debug, PartialEq)]
60pub struct MockResolver {
61    sources: HashMap<String, String>,
62}
63
64impl MockResolver {
65    pub fn new(sources: HashMap<String, String>) -> MockResolver {
66        MockResolver { sources }
67    }
68}
69
70impl Resolve for MockResolver {
71    fn resolve(&self, path: &[&str]) -> Result<(String, String), ResolutionError> {
72        let path = path.join("/");
73        match self.sources.get(&path) {
74            Some(source) => Ok((source.clone(), path)),
75            None => Err(ResolutionError::PathNotFound(path)),
76        }
77    }
78}
79
80
81#[cfg(test)]
82mod test {
83    use super::*;
84
85    #[test]
86    fn file_system_resolver_resolves_an_existing_path() {
87        let base_path = Path::new("./");
88        let resolver = FileSystemResolver::new(base_path);
89        
90        assert!(resolver.resolve(&["demo"]).is_ok());
91    }
92
93    #[test]
94    fn file_system_resolver_rejects_an_invalid_path() {
95        let base_path = Path::new("./");
96        let resolver = FileSystemResolver::new(base_path);
97
98        assert_eq!(
99            resolver.resolve(&["invalid"]),
100            Err(ResolutionError::PathNotFound("./invalid.lang".into())),
101        );
102
103    }
104}