y_lang/loader/
mod.rs

1mod loaderror;
2
3use std::{
4    collections::{hash_map::DefaultHasher, HashMap},
5    error::Error,
6    hash::{Hash, Hasher},
7    path::PathBuf,
8};
9
10use log::error;
11use pest::iterators::Pair;
12
13use crate::{
14    ast::{Ast, Import, Rule, Statement, YParser},
15    typechecker::{extract_exports, TypeInfo, TypeScope, Typechecker},
16};
17
18use self::loaderror::FileLoadError;
19
20fn should_be_exported(pair: &Pair<Rule>) -> bool {
21    match pair.as_rule() {
22        Rule::definition => {
23            let mut inner = pair.clone().into_inner();
24            let Some(expression) = inner.nth(1) else {
25                return false;
26            };
27
28            let mut inner = expression.into_inner();
29            let Some(fn_def) = inner.next() else {
30                return false;
31            };
32            fn_def.as_rule() == Rule::fnDef
33        }
34        Rule::declaration => {
35            let mut inner = pair.clone().into_inner();
36
37            let Some(type_annotation) = inner.nth(1) else {
38                return false;
39            };
40
41            let mut inner = type_annotation.into_inner();
42            let Some(fn_type) = inner.next() else {
43                return false;
44            };
45
46            fn_type.as_rule() == Rule::fnType
47        }
48        Rule::importDirective => true,
49        Rule::compiler_directive => true,
50        _ => false,
51    }
52}
53
54#[derive(Default, Debug, Clone, PartialEq, Eq)]
55pub struct Module<T> {
56    pub name: String,
57
58    /// The absolute path of this module in the file system.
59    pub file_path: PathBuf,
60    pub ast: Ast<T>,
61
62    /// A TypeScope containing all exported members of this module.
63    pub exports: TypeScope,
64
65    /// A list of imported module. The first item in each tuple is the path under which imported module
66    /// is specified in this module, the second item specifies the absolute path of the imported
67    /// module in the file system. This is used to convert absolute modules to relative imports.
68    pub imports: Vec<(String, String)>,
69}
70
71pub type Modules<T> = HashMap<String, Module<T>>;
72
73impl<T> Module<T> {
74    /// Resolve a variable name from this module.
75    pub fn resolve(&self, var_name: &impl ToString) -> String {
76        format!("{}_{}", self.name, var_name.to_string())
77    }
78
79    /// Convert the modules currently stored with their absolute path to modules stored with a
80    /// relative path (relative to _this_ module). This is needed to determine the correct module
81    /// to import while typechecking.
82    pub fn convert_imports_to_local_names(&self, modules: &Modules<()>) -> Modules<()> {
83        let mut local_modules = Modules::default();
84
85        for (import_path, real_path) in &self.imports {
86            local_modules.insert(
87                import_path.to_owned(),
88                modules.get(real_path).unwrap().to_owned(),
89            );
90        }
91        local_modules
92    }
93}
94
95impl Module<()> {
96    pub fn type_check(
97        &self,
98        other_modules: &Modules<()>,
99    ) -> Result<Module<TypeInfo>, Box<dyn Error>> {
100        let modules = self.convert_imports_to_local_names(other_modules);
101
102        let Module {
103            name,
104            file_path,
105            exports,
106            imports,
107            ast,
108        } = self;
109
110        let typechecker = Typechecker::from_ast(ast.clone(), modules);
111        let ast = match typechecker.check() {
112            Ok(ast) => ast,
113            Err(type_error) => {
114                error!("{}", type_error);
115                std::process::exit(-1);
116            }
117        };
118
119        Ok(Module {
120            ast,
121            name: name.clone(),
122            exports: exports.clone(),
123            imports: imports.clone(),
124            file_path: file_path.clone(),
125        })
126    }
127}
128
129pub fn load_module(mut file: PathBuf) -> Result<Module<()>, Box<dyn Error>> {
130    let file_content = std::fs::read_to_string(&file)
131        .unwrap_or_else(|_| panic!("Could not read file: '{}'", file.to_string_lossy()));
132
133    let pairs = match YParser::parse_program(&file.to_string_lossy(), &file_content) {
134        Ok(pairs) => pairs,
135        Err(parse_error) => {
136            error!("{parse_error}");
137            std::process::exit(-1);
138        }
139    };
140
141    let ast = Ast::from_program(pairs.collect(), &file.to_string_lossy());
142
143    file.pop();
144
145    let folder = file.to_string_lossy();
146
147    let exports = extract_exports(&ast)?;
148
149    let imports = extract_imports(&ast)
150        .iter()
151        .map(|import_path| {
152            (
153                import_path.to_owned(),
154                convert_to_path(&folder, import_path),
155            )
156        })
157        .collect();
158
159    Ok(Module {
160        name: "_".to_owned(),
161        ast,
162        file_path: file,
163        exports,
164        imports,
165    })
166}
167
168pub fn load_modules(
169    ast: &Ast<()>,
170    mut file: PathBuf,
171    mut modules: Modules<()>,
172) -> Result<Modules<()>, Box<dyn Error>> {
173    let nodes = ast.nodes();
174
175    let imports = nodes
176        .iter()
177        .filter_map(|elem| match elem {
178            Statement::Import(import) => Some(import.clone()),
179            _ => None,
180        })
181        .collect::<Vec<_>>();
182
183    file.pop();
184
185    let folder = file.to_string_lossy();
186
187    for import in &imports {
188        let file = convert_to_path(&folder, &import.path);
189        if modules.contains_key(&file) {
190            continue;
191        }
192
193        let Ok(file_content) = std::fs::read_to_string(&file) else {
194            return Err(Box::new(FileLoadError {
195                message: format!("Could not load module: '{file}'"),
196                position: import.position.clone()
197            }));
198        };
199
200        let pairs = match YParser::parse_program(&file, &file_content) {
201            Ok(pairs) => pairs,
202            Err(parse_error) => {
203                error!("{parse_error}");
204                std::process::exit(-1);
205            }
206        };
207
208        let fns = pairs
209            .clone()
210            .filter_map(|pair| {
211                if should_be_exported(&pair) {
212                    Some(pair)
213                } else {
214                    None
215                }
216            })
217            .collect::<Vec<_>>();
218        let ast = Ast::from_program(fns.clone(), &file);
219
220        let exports = extract_exports(&ast)?;
221
222        let imports = extract_imports(&ast)
223            .iter()
224            .map(|import_path| {
225                (
226                    import_path.to_owned(),
227                    convert_to_path(&folder, import_path),
228                )
229            })
230            .collect();
231
232        let file_path = PathBuf::from(file.clone());
233
234        let mut s = DefaultHasher::new();
235        file_content.hash(&mut s);
236        let file_hash = s.finish();
237
238        modules.insert(
239            file,
240            Module {
241                name: format!(
242                    "{}_{file_hash:x}",
243                    file_path.file_stem().unwrap().to_string_lossy()
244                ),
245                ast: ast.clone(),
246                file_path: file_path.clone(),
247                exports,
248                imports,
249            },
250        );
251
252        modules = load_modules(&ast, file_path, modules)?;
253    }
254
255    Ok(modules)
256}
257
258fn convert_to_path(folder: &str, import_path: &str) -> String {
259    let is_wildcard = import_path.ends_with("::*");
260
261    let path = &import_path[0..if is_wildcard {
262        import_path.len() - 3
263    } else {
264        import_path.len()
265    }]
266        .split("::")
267        .collect::<Vec<_>>()
268        .join("/");
269
270    format!("{folder}/{path}.why")
271}
272
273pub fn extract_imports(ast: &Ast<()>) -> Vec<String> {
274    ast.nodes()
275        .iter()
276        .filter_map(|statement| match statement {
277            Statement::Import(Import { path, .. }) => Some(path.clone()),
278            _ => None,
279        })
280        .collect()
281}