goscript_types/
importer.rs

1#![allow(dead_code)]
2use super::check::check::{Checker, TypeInfo};
3use super::objects::{PackageKey, TCObjects};
4use goscript_parser::ast;
5use goscript_parser::errors::{ErrorList, FilePosErrors};
6use goscript_parser::objects::Objects as AstObjects;
7use goscript_parser::position;
8use goscript_parser::{FileSet, Parser};
9use std::collections::HashMap;
10use std::env;
11use std::fs;
12use std::io;
13use std::path::{Path, PathBuf};
14
15pub struct Config {
16    // working directory
17    pub work_dir: Option<String>,
18    // base path for non-local imports
19    pub base_path: Option<String>,
20    // print debug info in parser
21    pub trace_parser: bool,
22    // print debug info in checker
23    pub trace_checker: bool,
24}
25
26impl Config {
27    fn get_working_dir(&self) -> io::Result<PathBuf> {
28        if let Some(wd) = &self.work_dir {
29            let mut buf = PathBuf::new();
30            buf.push(wd);
31            Ok(buf)
32        } else {
33            env::current_dir()
34        }
35    }
36}
37
38/// ImportKey identifies an imported package by import path and source directory
39/// (directory containing the file containing the import). In practice, the directory
40/// may always be the same, or may not matter. Given an (import path, directory), an
41/// importer must always return the same package (but given two different import paths,
42/// an importer may still return the same package by mapping them to the same package
43/// paths).
44#[derive(PartialEq, Eq, Hash, Debug)]
45pub struct ImportKey {
46    pub path: String,
47    pub dir: String, // makes a difference only when importing local files
48}
49
50impl ImportKey {
51    pub fn new(path: &str, dir: &str) -> ImportKey {
52        ImportKey {
53            path: path.to_string(),
54            dir: dir.to_string(),
55        }
56    }
57}
58
59pub struct Importer<'a> {
60    config: &'a Config,
61    fset: &'a mut FileSet,
62    pkgs: &'a mut HashMap<String, PackageKey>,
63    all_results: &'a mut HashMap<PackageKey, TypeInfo>,
64    ast_objs: &'a mut AstObjects,
65    tc_objs: &'a mut TCObjects,
66    errors: &'a ErrorList,
67    pos: position::Pos,
68}
69
70impl<'a> Importer<'a> {
71    pub fn new(
72        config: &'a Config,
73        fset: &'a mut FileSet,
74        pkgs: &'a mut HashMap<String, PackageKey>,
75        all_results: &'a mut HashMap<PackageKey, TypeInfo>,
76        ast_objs: &'a mut AstObjects,
77        tc_objs: &'a mut TCObjects,
78        errors: &'a ErrorList,
79        pos: position::Pos,
80    ) -> Importer<'a> {
81        Importer {
82            config: config,
83            fset: fset,
84            pkgs: pkgs,
85            all_results: all_results,
86            ast_objs: ast_objs,
87            tc_objs: tc_objs,
88            errors: errors,
89            pos: pos,
90        }
91    }
92
93    pub fn import(&mut self, key: &'a ImportKey) -> Result<PackageKey, ()> {
94        if key.path == "unsafe" {
95            return Ok(*self.tc_objs.universe().unsafe_pkg());
96        }
97        let pb = self.validate_path(key)?;
98        let path = pb.0.as_path();
99        let import_path = pb.1;
100        let pkg = self.tc_objs.new_package(import_path.clone());
101        self.pkgs.insert(import_path, pkg);
102        let files = self.parse_dir(path)?;
103        Checker::new(
104            self.tc_objs,
105            self.ast_objs,
106            self.fset,
107            self.errors,
108            self.pkgs,
109            self.all_results,
110            pkg,
111            self.config,
112        )
113        .check(files)
114    }
115
116    fn validate_path(&mut self, key: &'a ImportKey) -> Result<(PathBuf, String), ()> {
117        let mut import_path = key.path.clone();
118        let path = if is_local(&key.path) {
119            let working_dir = self.config.get_working_dir();
120            if working_dir.is_err() {
121                self.error(format!("failed to get working dir for: {}", key.path));
122                return Err(());
123            }
124            let mut wd = working_dir.unwrap();
125            wd.push(&key.dir);
126            wd.push(&key.path);
127            if let Some(base) = &self.config.base_path {
128                if let Ok(rel) = wd.as_path().strip_prefix(base) {
129                    import_path = rel.to_string_lossy().to_string()
130                }
131            }
132            wd
133        } else {
134            if let Some(base) = &self.config.base_path {
135                let mut p = PathBuf::new();
136                p.push(base);
137                p.push(&key.path);
138                p
139            } else {
140                self.error(format!("base dir required for path: {}", key.path));
141                return Err(());
142            }
143        };
144        if !path.exists() {
145            self.error(format!("failed to locate path: {}", key.path));
146            return Err(());
147        }
148        match path.canonicalize() {
149            Ok(p) => Ok((p, import_path)),
150            Err(_) => {
151                self.error(format!("failed to canonicalize path: {}", key.path));
152                return Err(());
153            }
154        }
155    }
156
157    fn parse_dir(&mut self, path: &Path) -> Result<Vec<ast::File>, ()> {
158        let working_dir = self
159            .config
160            .get_working_dir()
161            .ok()
162            .map(|x| x.canonicalize().ok())
163            .flatten();
164        match read_content(path) {
165            Ok(contents) => {
166                if contents.len() == 0 {
167                    self.error(format!("no source file found in dir: {}", path.display()));
168                    Err(())
169                } else {
170                    let mut afiles = vec![];
171                    for (path_buf, content) in contents.into_iter() {
172                        // try get short display name for the file
173                        let p = path_buf.as_path();
174                        let full_name = match &working_dir {
175                            Some(wd) => p.strip_prefix(wd).unwrap_or(p),
176                            None => p,
177                        }
178                        .to_string_lossy()
179                        .to_string();
180                        let mut pfile = self.fset.add_file(
181                            full_name,
182                            Some(self.fset.base()),
183                            content.chars().count(),
184                        );
185                        let afile = Parser::new(
186                            self.ast_objs,
187                            &mut pfile,
188                            self.errors,
189                            &content,
190                            self.config.trace_parser,
191                        )
192                        .parse_file();
193                        if afile.is_none() {
194                            // parse error, the details should be in the errorlist already.
195                            // give up
196                            return Err(());
197                        } else {
198                            afiles.push(afile.unwrap());
199                        }
200                    }
201                    Ok(afiles)
202                }
203            }
204            Err(_) => {
205                self.error(format!("failed to read dir: {}", path.display()));
206                Err(())
207            }
208        }
209    }
210
211    fn error(&self, err: String) {
212        let pos_file = self.fset.file(self.pos).unwrap();
213        FilePosErrors::new(pos_file, self.errors).add(self.pos, err, false);
214    }
215}
216
217fn read_content(p: &Path) -> io::Result<Vec<(PathBuf, String)>> {
218    let mut result = vec![];
219    let mut read = |path: PathBuf| -> io::Result<()> {
220        if let Some(ext) = path.extension() {
221            if ext == "gos" || ext == "go" || ext == "src" {
222                if let Some(fs) = path.file_stem() {
223                    let s = fs.to_str();
224                    if s.is_some() && !s.unwrap().ends_with("_test") {
225                        let content = fs::read_to_string(path.as_path())?;
226                        result.push((path, content))
227                    }
228                }
229            }
230        }
231        Ok(())
232    };
233
234    if p.is_dir() {
235        let mut paths = vec![];
236        for entry in fs::read_dir(p)? {
237            let entry = entry?;
238            let path = entry.path();
239            if !path.is_dir() {
240                paths.push(path);
241            }
242        }
243        paths.sort_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
244        for p in paths.into_iter() {
245            read(p)?;
246        }
247    } else if p.is_file() {
248        read(p.to_path_buf())?;
249    }
250    if result.len() == 0 {
251        return Err(io::Error::new(io::ErrorKind::Other, "no file/dir found"));
252    }
253    Ok(result)
254}
255
256fn is_local(path: &str) -> bool {
257    path == "." || path == ".." || path.starts_with("./") || path.starts_with("../")
258}