go_types/
importer.rs

1// Copyright 2022 The Goscript Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4//
5//
6// This code is adapted from the offical Go code written in Go
7// with license as follows:
8// Copyright 2013 The Go Authors. All rights reserved.
9// Use of this source code is governed by a BSD-style
10// license that can be found in the LICENSE file.
11
12use super::check::{Checker, TypeInfo};
13use super::objects::{PackageKey, TCObjects};
14use go_parser::ast;
15use go_parser::{AstObjects, ErrorList, FileSet, Map, Parser, Pos};
16use std::io;
17use std::path::{Path, PathBuf};
18
19pub struct TraceConfig {
20    //print debug info in parser
21    pub trace_parser: bool,
22    // print debug info in checker
23    pub trace_checker: bool,
24}
25
26pub trait SourceRead {
27    fn working_dir(&self) -> &Path;
28
29    fn base_dir(&self) -> Option<&Path>;
30
31    fn read_file(&self, path: &Path) -> io::Result<String>;
32
33    fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>>;
34
35    fn is_file(&self, path: &Path) -> bool;
36
37    fn is_dir(&self, path: &Path) -> bool;
38
39    fn canonicalize_import(&self, key: &ImportKey) -> io::Result<(PathBuf, String)>;
40}
41
42/// ImportKey identifies an imported package by import path and source directory
43/// (directory containing the file containing the import). In practice, the directory
44/// may always be the same, or may not matter. Given an (import path, directory), an
45/// importer must always return the same package (but given two different import paths,
46/// an importer may still return the same package by mapping them to the same package
47/// paths).
48#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
49pub struct ImportKey {
50    pub path: String,
51    pub dir: String, // makes a difference only when importing local files
52}
53
54impl ImportKey {
55    pub fn new(path: &str, dir: &str) -> ImportKey {
56        ImportKey {
57            path: path.to_string(),
58            dir: dir.to_string(),
59        }
60    }
61}
62
63pub struct Importer<'a, S: SourceRead> {
64    trace_config: &'a TraceConfig,
65    reader: &'a S,
66    fset: &'a mut FileSet,
67    pkgs: &'a mut Map<String, PackageKey>,
68    all_results: &'a mut Map<PackageKey, TypeInfo>,
69    ast_objs: &'a mut AstObjects,
70    tc_objs: &'a mut TCObjects,
71    errors: &'a ErrorList,
72    pos: Pos,
73}
74
75impl<'a, S: SourceRead> Importer<'a, S> {
76    pub fn new(
77        config: &'a TraceConfig,
78        reader: &'a S,
79        fset: &'a mut FileSet,
80        pkgs: &'a mut Map<String, PackageKey>,
81        all_results: &'a mut Map<PackageKey, TypeInfo>,
82        ast_objs: &'a mut AstObjects,
83        tc_objs: &'a mut TCObjects,
84        errors: &'a ErrorList,
85        pos: Pos,
86    ) -> Importer<'a, S> {
87        Importer {
88            trace_config: config,
89            reader: reader,
90            fset: fset,
91            pkgs: pkgs,
92            all_results: all_results,
93            ast_objs: ast_objs,
94            tc_objs: tc_objs,
95            errors: errors,
96            pos: pos,
97        }
98    }
99
100    pub fn import(&mut self, key: &'a ImportKey) -> Result<PackageKey, ()> {
101        if key.path == "unsafe" {
102            return Ok(*self.tc_objs.universe().unsafe_pkg());
103        }
104
105        match self.reader.canonicalize_import(key) {
106            Ok((path, import_path)) => match self.pkgs.get(&import_path) {
107                Some(key) => Ok(*key),
108                None => {
109                    let pkg = self.tc_objs.new_package(import_path.clone());
110                    self.pkgs.insert(import_path, pkg);
111                    let files = self.parse_path(&path)?;
112                    Checker::new(
113                        self.tc_objs,
114                        self.ast_objs,
115                        self.fset,
116                        self.errors,
117                        self.pkgs,
118                        self.all_results,
119                        pkg,
120                        self.trace_config,
121                        self.reader,
122                    )
123                    .check(files)
124                }
125            },
126            Err(e) => self.error(format!("canonicalize import error: {}", e)),
127        }
128    }
129
130    fn parse_path(&mut self, path: &Path) -> Result<Vec<ast::File>, ()> {
131        match read_content(path, self.reader) {
132            Ok(contents) => {
133                if contents.len() == 0 {
134                    self.error(format!("no source file found in dir: {}", path.display()))
135                } else {
136                    let mut afiles = vec![];
137                    for (full_name, content) in contents.into_iter() {
138                        let mut pfile = self.fset.add_file(
139                            full_name,
140                            Some(self.fset.base()),
141                            content.chars().count(),
142                        );
143                        let afile = Parser::new(
144                            self.ast_objs,
145                            &mut pfile,
146                            self.errors,
147                            &content,
148                            self.trace_config.trace_parser,
149                        )
150                        .parse_file();
151                        if afile.is_none() {
152                            // parse error, the details should be in the errorlist already.
153                            // give up
154                            return Err(());
155                        } else {
156                            afiles.push(afile.unwrap());
157                        }
158                    }
159                    Ok(afiles)
160                }
161            }
162            Err(e) => self.error(format!(
163                "failed to read from path: {}, {}",
164                path.display(),
165                e
166            )),
167        }
168    }
169
170    fn error<T>(&self, err: String) -> Result<T, ()> {
171        self.errors
172            .add(self.fset.position(self.pos), err, false, false);
173        Err(())
174    }
175}
176
177fn read_content(p: &Path, reader: &dyn SourceRead) -> io::Result<Vec<(String, String)>> {
178    let working_dir = reader.working_dir().canonicalize().ok();
179    let mut result = vec![];
180    let mut read = |path: PathBuf| -> io::Result<()> {
181        if let Some(ext) = path.extension() {
182            if ext == "gos" || ext == "go" || ext == "src" {
183                if let Some(fs) = path.file_stem() {
184                    let s = fs.to_str();
185                    if s.is_some() && !s.unwrap().ends_with("_test") {
186                        let p = path.as_path();
187                        let content = reader.read_file(p)?;
188                        // try get short display name for the file
189                        let full_name = match &working_dir {
190                            Some(wd) => p.strip_prefix(wd).unwrap_or(p),
191                            None => p,
192                        }
193                        .to_string_lossy()
194                        .to_string();
195                        result.push((full_name, content))
196                    }
197                }
198            }
199        }
200        Ok(())
201    };
202
203    if reader.is_dir(p) {
204        let mut paths = reader.read_dir(p)?;
205        paths.sort_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
206        for p in paths.into_iter() {
207            read(p)?;
208        }
209    } else if reader.is_file(p) {
210        read(p.to_path_buf())?;
211    }
212    if result.len() == 0 {
213        return Err(io::Error::new(io::ErrorKind::Other, "no file/dir found"));
214    }
215    Ok(result)
216}