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 pub file_path: PathBuf,
60 pub ast: Ast<T>,
61
62 pub exports: TypeScope,
64
65 pub imports: Vec<(String, String)>,
69}
70
71pub type Modules<T> = HashMap<String, Module<T>>;
72
73impl<T> Module<T> {
74 pub fn resolve(&self, var_name: &impl ToString) -> String {
76 format!("{}_{}", self.name, var_name.to_string())
77 }
78
79 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}