mimium_lang/utils/
fileloader.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
use std::{env, fmt, path::PathBuf};

#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;

#[derive(Debug)]
pub enum Error {
    IoError(std::io::Error),
    FileNotFound(String, PathBuf),
    UtfConversionError(std::string::FromUtf8Error),
    PathJoinError(env::JoinPathsError),
    SelfReference(PathBuf),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::IoError(e) => write!(f, "IoError: {}", e),
            Error::FileNotFound(e, p) => write!(f, "File {} not found: {}", p.display(), e),
            Error::UtfConversionError(e) => write!(f, "Failed to convert into UTF: {}", e),
            Error::PathJoinError(e) => write!(f, "Failed to join path: {}", e),
            Error::SelfReference(path_buf) => write!(
                f,
                "File tried to include itself recusively: {}",
                path_buf.to_string_lossy()
            ),
        }
    }
}

impl std::error::Error for Error {}

impl From<std::io::Error> for Error {
    fn from(e: std::io::Error) -> Self {
        Error::IoError(e)
    }
}
impl From<std::string::FromUtf8Error> for Error {
    fn from(e: std::string::FromUtf8Error) -> Self {
        Error::UtfConversionError(e)
    }
}
impl From<env::JoinPathsError> for Error {
    fn from(e: env::JoinPathsError) -> Self {
        Error::PathJoinError(e)
    }
}
fn get_default_library_path() -> Option<PathBuf> {
    #[cfg(not(target_arch = "wasm32"))]
    let home = homedir::my_home().ok().flatten();
    #[cfg(target_arch = "wasm32")]
    let home: Option<PathBuf> = None;
    if home.is_none() {
        log::warn!("default library search path is not available on this platform.");
        return None;
    }
    let p = home.unwrap().join(PathBuf::from(".mimium/lib"));
    Some(p)
}

pub fn get_canonical_path(current_file_or_dir: &str, relpath: &str) -> Result<PathBuf, Error> {
    let parent_dir = get_parent_dir(current_file_or_dir)?;
    let relpath2 = std::path::PathBuf::from(relpath);
    let abspath = [parent_dir, relpath2]
        .into_iter()
        .collect::<std::path::PathBuf>();
    if cfg!(target_arch = "wasm32") {
        //canonicalize is platform-dependent and always returns Err on wasm32
        Ok(abspath)
    } else {
        abspath
            .canonicalize()
            .map_err(|e| Error::FileNotFound(e.to_string(), abspath))
    }
}

fn get_parent_dir(current_file: &str) -> Result<PathBuf, Error> {
    let current_filepath = std::path::Path::new(current_file);
    if current_filepath.is_dir() {
        Ok(current_filepath.into())
    } else {
        #[cfg(not(target_arch = "wasm32"))]
        let cwd = env::current_dir()?;
        #[cfg(target_arch = "wasm32")]
        let cwd = std::path::PathBuf::new();
        Ok(current_filepath.parent().map_or_else(|| cwd, PathBuf::from))
    }
}

/// Used for resolving include.
/// If the filename is given it searches ~/.mimium/lib first. If not found, tries to find in relative path.
/// If the relative path is given explicitly, do not find in standard library path.

pub fn load_mmmlibfile(current_file_or_dir: &str, path: &str) -> Result<(String, PathBuf), Error> {
    let path = std::path::Path::new(path);
    let search_default_lib = !(path.is_absolute() || path.starts_with("."));
    if let (true, Some(stdlibpath)) = (search_default_lib, get_default_library_path()) {
        let cpath = stdlibpath.join(path).canonicalize();
        if let Ok(cpath) = cpath {
            if let Ok(content) = load(&cpath.to_string_lossy()) {
                return Ok((content, cpath));
                // if not found in the stdlib, continue to find in a relative path.
            }
        }
    };
    let cpath = get_canonical_path(current_file_or_dir, &path.to_string_lossy())?;
    if current_file_or_dir == cpath.to_string_lossy() {
        return Err(Error::SelfReference(cpath.clone()));
    }
    let content = load(&cpath.to_string_lossy())?;
    Ok((content, cpath))
}

#[cfg(not(target_arch = "wasm32"))]
pub fn load(canonical_path: &str) -> Result<String, Error> {
    // debug_assert!(std::path::Path::new(canonical_path).is_absolute());
    let content = std::fs::read(canonical_path)
        .map_err(|e| Error::FileNotFound(e.to_string(), PathBuf::from(canonical_path)))?;

    let content_r = String::from_utf8(content).map_err(Error::from)?;
    Ok(content_r)
}

#[cfg(target_arch = "wasm32")]
pub fn load(canonical_path: &str) -> Result<String, Error> {
    let content_r = read_file(canonical_path)
        .map_err(|e| Error::FileNotFound(format!("{:?}", e), canonical_path.into()))?;
    Ok(content_r)
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(module = "/src/utils/fileloader.cjs")]
extern "C" {
    #[wasm_bindgen(catch)]
    fn read_file(path: &str) -> Result<String, JsValue>;
    #[wasm_bindgen(catch)]
    pub fn get_env(key: &str) -> Result<String, JsValue>;
}

#[cfg(all(test, target_arch = "wasm32"))]
mod test {
    use super::*;
    use wasm_bindgen_test::*;
    fn setup_file() -> (String, String) {
        let ans = r#"include("error_include_itself.mmm")
fn dsp(){
    0.0
}"#;
        let file = format!(
            "{}/../mimium-test/tests/mmm/{}",
            get_env("TEST_ROOT").expect("TEST_ROOT is not set"),
            "error_include_itself.mmm"
        );
        (ans.to_string(), file)
    }
    #[wasm_bindgen_test] //wasm only test
    fn fileloader_test() {
        let (ans, file) = setup_file();
        let res = load(&file).expect("failed to load file");
        assert_eq!(res, ans);
    }
    #[wasm_bindgen_test] //wasm only test
    fn loadlib_test() {
        use super::*;
        let (ans, file) = setup_file();
        let (res, _path) = load_mmmlibfile("/", &file).expect("failed to load file");
        assert_eq!(res, ans);
    }
    #[wasm_bindgen_test] //wasm only test
    fn loadlib_test_selfinclude() {
        use super::*;
        let (_, file) = setup_file();
        let err = load_mmmlibfile(&file, &file).expect_err("should be an error");

        assert!(matches!(err,Error::SelfReference(_)));
    }
}