seahorse_dev/core/preprocess/
mod.rs1use crate::core::{
3 clean::{ast as ca, clean},
4 parse::parse,
5 util::*,
6 CoreError,
7};
8use std::{
9 collections::HashMap,
10 ffi::OsStr,
11 fmt::{self, Display, Formatter},
12 fs::{read_dir, File},
13 io::Read,
14 path::PathBuf,
15};
16
17use super::compile::builtin::prelude::path_to_string;
18
19enum Error {
20 CouldNotAddModule,
21 CouldNotFind(ComboPath),
22 RelativeSeahorseImport,
23 PathOutsideRoot(ComboPath, usize),
24 Os,
25}
26
27impl Error {
28 fn core(&self) -> CoreError {
29 match self {
30 Self::CouldNotAddModule => CoreError::make_raw(
31 "could not add module",
32 "",
33 ),
34 Self::CouldNotFind(path) => CoreError::make_raw(
35 format!("could not find import at {}", path),
36 "",
37 ),
38 Self::RelativeSeahorseImport => CoreError::make_raw(
39 "attempted to import local Seahorse files",
40 "Help: `seahorse` is a builtin package, you should import it like a regular Python package:\n\n from seahorse.prelude import *"
41 ),
42 Self::PathOutsideRoot(path, level) => CoreError::make_raw(
43 "relative import would go outside of source code root",
44 format!(
45 "Hint: you can't go up {} directory levels from {}.",
46 level, path
47 ),
48 ),
49 Self::Os => CoreError::make_raw(
50 "OS error",
51 "",
52 ),
53 }
54 }
55}
56
57#[derive(Clone, Debug)]
58pub struct ModuleRegistry {
59 pub tree: Tree<Module>,
60 pub origin: Vec<String>,
61 pub order: Vec<String>,
62}
63
64impl ModuleRegistry {
65 pub fn get_abs_path(&self, from: &Vec<String>, ext: &Vec<String>) -> Option<Vec<String>> {
71 let mut path = from.clone();
72 path.append(&mut ext.clone());
73
74 if self.tree.get(&path).is_some() {
75 return Some(path);
76 }
77
78 for src in self.order.iter() {
79 let mut path = vec![src.clone()];
80 path.append(&mut ext.clone());
81
82 if self.tree.get(&path).is_some() {
83 return Some(path);
84 }
85 }
86
87 return None;
88 }
89}
90
91fn load_module(path: PathBuf) -> CResult<Module> {
94 let mut input = File::open(path.clone())
95 .map_err(|_| CoreError::make_raw(format!("could not open file {}", path.display()), ""))?;
96 let mut py_src = String::new();
97 input
98 .read_to_string(&mut py_src)
99 .map_err(|_| CoreError::make_raw("I/O error", ""))?;
100
101 let parsed = parse(py_src.clone())?;
102 let module = clean(parsed, py_src)?;
103
104 return Ok(Module::Python(module));
105}
106
107fn from_os_string(os: &OsStr) -> String {
108 os.to_str().unwrap().to_string()
109}
110
111#[derive(Clone, Debug)]
112pub enum Module {
113 Python(ca::Module),
114 SeahorsePrelude,
115 SeahorsePyth,
116}
117
118#[derive(Clone, Debug)]
130struct ComboPath {
131 base: String,
132 path: Vec<String>,
133 fs_base: PathBuf,
134}
135
136impl ComboPath {
137 fn new(mut path: Vec<String>, fs_base: PathBuf) -> Self {
138 let base = path.remove(0);
139
140 return Self {
141 base,
142 path,
143 fs_base,
144 };
145 }
146
147 fn get_path(&self) -> Vec<String> {
149 let mut path = self.path.clone();
150 path.insert(0, self.base.clone());
151 return path;
152 }
153
154 fn get_fs_module_path(&self) -> PathBuf {
156 let mut fs_path = self.fs_base.clone();
157 for part in self.path.iter().take(self.path.len() - 1) {
158 fs_path.push(part);
159 }
160 fs_path.push(format!("{}.py", self.path.last().unwrap()));
161
162 return fs_path;
163 }
164
165 fn get_fs_package_path(&self) -> PathBuf {
167 let mut fs_path = self.fs_base.clone();
168 for part in self.path.iter() {
169 fs_path.push(part);
170 }
171
172 return fs_path;
173 }
174
175 fn push(&mut self, part: String) {
176 self.path.push(part);
177 }
178
179 fn pop(&mut self) {
180 self.path.pop();
181 }
182
183 fn is_module(&self) -> bool {
184 let fs_path = self.get_fs_module_path();
185 return fs_path.is_file();
186 }
187
188 fn is_package(&self) -> bool {
189 let fs_path = self.get_fs_package_path();
190 return fs_path.is_dir();
191 }
192}
193
194impl Display for ComboPath {
195 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
196 write!(
197 f,
198 "{} + {}",
199 self.fs_base.display(),
200 path_to_string(&self.path)
201 )
202 }
203}
204
205struct ModuleTreeBuilder {
206 tree: Tree<Module>,
207}
208
209impl ModuleTreeBuilder {
210 fn new() -> Self {
211 Self {
212 tree: Tree::Node(HashMap::new()),
213 }
214 }
215
216 fn add_module(&mut self, module: Module, path: ComboPath) -> CResult<()> {
218 match module {
219 Module::Python(module) => {
220 if !self
223 .tree
224 .insert(path.get_path(), Tree::Leaf(Module::SeahorsePrelude), false)
225 {
226 return Ok(());
227 }
228
229 for Located(loc, obj) in module.statements.iter() {
230 match obj {
231 ca::TopLevelStatementObj::Import { symbols } => {
232 for ca::ImportSymbol { symbol, .. } in symbols.iter() {
233 {
235 let mut path =
236 ComboPath::new(vec!["sh".to_string()], PathBuf::new());
237 path.push(symbol.clone());
238
239 if self.tree.get(&path.get_path()).is_some() {
240 continue;
241 }
242 }
243
244 {
246 let mut path = path.clone();
247 path.pop();
248 path.push(symbol.clone());
249
250 if path.is_module() {
251 let module = load_module(path.get_fs_module_path())?;
252 self.add_module(module, path)?;
253 } else if path.is_package() {
254 self.add_package(path)?;
255 }
256 else {
258 return Err(Error::CouldNotFind(path)
259 .core()
260 .located(loc.clone()));
261 }
262 }
263 }
264 }
265 ca::TopLevelStatementObj::ImportFrom {
266 level,
267 path: symbol_path,
268 ..
269 } => {
270 if *level > path.path.len() {
271 return Err(Error::PathOutsideRoot(path, *level)
272 .core()
273 .located(loc.clone()));
274 }
275
276 if *level == path.path.len()
277 && symbol_path.get(0).unwrap() == "seahorse"
278 {
279 return Err(Error::RelativeSeahorseImport
280 .core()
281 .located(loc.clone()));
282 }
283
284 'done: loop {
286 if *level == 0 {
288 let mut path =
289 ComboPath::new(vec!["sh".to_string()], PathBuf::new());
290 for part in symbol_path.iter() {
291 path.push(part.clone());
292
293 if self.tree.get(&path.get_path()).is_some() {
294 break 'done;
295 }
296 }
297 }
298
299 {
301 let mut path = path.clone();
302 path.pop();
304 for _ in 0..*level {
305 path.pop();
306 }
307
308 for part in symbol_path.iter() {
309 path.push(part.clone());
310
311 if path.is_module() {
312 let module = load_module(path.get_fs_module_path())?;
313 self.add_module(module, path)?;
314 break 'done;
315 }
316 }
317
318 if path.is_package() {
319 self.add_package(path)?;
320 } else {
321 return Err(Error::CouldNotFind(path)
322 .core()
323 .located(loc.clone()));
324 }
325 }
326 break;
327 }
328 }
329 _ => {}
330 }
331 }
332
333 self.tree
334 .insert(path.get_path(), Tree::Leaf(Module::Python(module)), true);
335 }
336 module => {
337 self.tree.insert(path.get_path(), Tree::Leaf(module), false);
338 }
339 }
340
341 return Ok(());
342 }
343
344 fn add_package(&mut self, path: ComboPath) -> CResult<()> {
348 let fs_path = path.get_fs_package_path();
349 for entry in read_dir(&fs_path).map_err(|_| Error::Os.core())? {
350 let entry = entry.map_err(|_| Error::Os.core())?;
351 let name = from_os_string(entry.file_name().as_os_str());
352
353 if entry.path().is_file() && name.ends_with(".py") {
354 let name = name.strip_suffix(".py").unwrap().to_string();
355 let mut path = path.clone();
356 path.push(name);
357
358 let module = load_module(path.get_fs_module_path())?;
359 self.add_module(module, path)?;
360 } else if entry.path().is_dir() {
361 let mut path = path.clone();
362 path.push(name);
363
364 if path.is_package() {
365 self.add_package(path)?;
366 }
367 }
368 }
369
370 return Ok(());
371 }
372}
373
374pub fn preprocess(module: ca::Module, working_dir: PathBuf) -> Result<ModuleRegistry, CoreError> {
376 let mut builder = ModuleTreeBuilder::new();
393
394 builder.add_module(
395 Module::SeahorsePrelude,
396 ComboPath::new(
397 vec![
398 "sh".to_string(),
399 "seahorse".to_string(),
400 "prelude".to_string(),
401 ],
402 PathBuf::new(),
403 ),
404 )?;
405
406 builder.add_module(
407 Module::SeahorsePyth,
408 ComboPath::new(
409 vec!["sh".to_string(), "seahorse".to_string(), "pyth".to_string()],
410 PathBuf::new(),
411 ),
412 )?;
413
414 builder.add_module(
415 Module::Python(module),
416 ComboPath::new(vec!["dot".to_string(), "program".to_string()], working_dir),
417 )?;
418
419 let registry = ModuleRegistry {
420 tree: builder.tree,
421 origin: vec!["dot".to_string(), "program".to_string()],
422 order: vec!["sh".to_string(), "dot".to_string()],
423 };
424
425 return Ok(registry);
426}