calyx_frontend/
workspace.rs1use super::{
2 ast::{ComponentDef, NamespaceDef},
3 parser,
4};
5use crate::LibrarySignatures;
6use calyx_utils::{CalyxResult, Error};
7use std::{
8 collections::HashSet,
9 path::{Path, PathBuf},
10};
11
12const COMPILE_LIB: &str = include_str!("../resources/compile.futil");
15
16#[derive(Default)]
40pub struct Workspace {
41 pub components: Vec<ComponentDef>,
43 pub declarations: Vec<ComponentDef>,
47 pub lib: LibrarySignatures,
49 pub original_imports: Vec<String>,
51 pub metadata: Option<String>,
53}
54
55impl Workspace {
56 fn canonicalize_import<S>(
60 import: S,
61 parent: &Path,
62 lib_path: &Path,
63 ) -> CalyxResult<PathBuf>
64 where
65 S: AsRef<Path> + Clone,
66 {
67 let parent_path = parent.join(import.clone());
68 if parent_path.exists() {
69 return Ok(parent_path);
70 }
71 let lib = lib_path.join(import.clone());
72 if lib.exists() {
73 return Ok(lib);
74 }
75
76 Err(Error::invalid_file(
77 format!("Import path `{}` found neither in the parent ({}) nor library path ({})",
78 import.as_ref().to_string_lossy(),
79 parent.to_string_lossy(),
80 lib_path.to_string_lossy()
81 )))
82 }
83
84 fn canonicalize_extern<S>(
87 extern_path: S,
88 parent: &Path,
89 ) -> CalyxResult<PathBuf>
90 where
91 S: AsRef<Path> + Clone,
92 {
93 let parent_path = parent.join(extern_path.clone()).canonicalize()?;
94 if parent_path.exists() {
95 return Ok(parent_path);
96 }
97 Err(Error::invalid_file(format!(
98 "Extern path `{}` not found in parent directory ({})",
99 extern_path.as_ref().to_string_lossy(),
100 parent.to_string_lossy(),
101 )))
102 }
103
104 pub fn from_compile_lib() -> CalyxResult<Self> {
107 let mut ns = NamespaceDef::construct_from_str(COMPILE_LIB)?;
108 assert!(
110 ns.imports.is_empty(),
111 "core library should not contain any imports"
112 );
113 assert!(
115 ns.metadata.is_none(),
116 "core library should not contain any metadata"
117 );
118 assert!(
120 ns.externs.len() == 1 && ns.externs[0].0.is_none(),
121 "core library should only contain inline externs"
122 );
123 let (_, externs) = ns.externs.pop().unwrap();
124 let mut lib = LibrarySignatures::default();
125 for ext in externs {
126 lib.add_inline_primitive(ext);
127 }
128 let ws = Workspace {
129 components: ns.components,
130 lib,
131 ..Default::default()
132 };
133 Ok(ws)
134 }
135
136 pub fn construct(
139 file: &Option<PathBuf>,
140 lib_path: &Path,
141 ) -> CalyxResult<Self> {
142 Self::construct_with_all_deps::<false>(
143 file.iter().cloned().collect(),
144 lib_path,
145 )
146 }
147
148 pub fn construct_shallow(
151 file: &Option<PathBuf>,
152 lib_path: &Path,
153 ) -> CalyxResult<Self> {
154 Self::construct_with_all_deps::<true>(
155 file.iter().cloned().collect(),
156 lib_path,
157 )
158 }
159
160 fn get_parent(p: &Path) -> PathBuf {
161 let maybe_parent = p.parent();
162 match maybe_parent {
163 None => PathBuf::from("."),
164 Some(path) => {
165 if path.to_string_lossy() == "" {
166 PathBuf::from(".")
167 } else {
168 PathBuf::from(path)
169 }
170 }
171 }
172 }
173
174 pub fn merge_namespace(
178 &mut self,
179 ns: NamespaceDef,
180 is_source: bool,
181 parent: &Path,
182 shallow: bool,
183 lib_path: &Path,
184 ) -> CalyxResult<Vec<(PathBuf, bool)>> {
185 for (path, exts) in ns.externs {
187 match path {
188 Some(p) => {
189 let abs_path = Self::canonicalize_extern(p, parent)?;
190 let p = self.lib.add_extern(abs_path, exts);
191 if is_source {
192 p.set_source();
193 }
194 }
195 None => {
196 for ext in exts {
197 let p = self.lib.add_inline_primitive(ext);
198 if is_source {
199 p.set_source();
200 }
201 }
202 }
203 }
204 }
205
206 if !is_source && shallow {
209 self.declarations.extend(&mut ns.components.into_iter());
210 } else {
211 self.components.extend(&mut ns.components.into_iter());
212 }
213
214 let deps = ns
216 .imports
217 .into_iter()
218 .map(|p| {
219 Self::canonicalize_import(p, parent, lib_path)
220 .map(|s| (s, false))
221 })
222 .collect::<CalyxResult<_>>()?;
223
224 Ok(deps)
225 }
226
227 pub fn construct_with_all_deps<const SHALLOW: bool>(
231 mut files: Vec<PathBuf>,
232 lib_path: &Path,
233 ) -> CalyxResult<Self> {
234 let first = files.pop();
236 let ns = NamespaceDef::construct(&first)?;
237 let parent_path = first
238 .as_ref()
239 .map(|p| Self::get_parent(p))
240 .unwrap_or_else(|| PathBuf::from("."));
241
242 let mut dependencies: Vec<(PathBuf, bool)> =
244 files.into_iter().map(|p| (p, true)).collect();
245 let mut already_imported: HashSet<PathBuf> = HashSet::new();
247
248 let mut ws = Workspace::default();
249 let abs_lib_path = lib_path.canonicalize().map_err(|err| {
250 Error::invalid_file(format!(
251 "Failed to canonicalize library path `{}`: {}",
252 lib_path.to_string_lossy(),
253 err
254 ))
255 })?;
256
257 ws.original_imports = ns.imports.clone();
259
260 ws.metadata = ns.metadata.clone();
263
264 let parent_canonical = parent_path.canonicalize().map_err(|err| {
266 Error::invalid_file(format!(
267 "Failed to canonicalize parent path `{}`: {}",
268 parent_path.to_string_lossy(),
269 err
270 ))
271 })?;
272 let mut deps = ws.merge_namespace(
273 ns,
274 true,
275 &parent_canonical,
276 false,
277 &abs_lib_path,
278 )?;
279 dependencies.append(&mut deps);
280
281 while let Some((p, source)) = dependencies.pop() {
282 if already_imported.contains(&p) {
283 continue;
284 }
285 let ns = parser::CalyxParser::parse_file(&p)?;
286 let parent = Self::get_parent(&p);
287
288 let mut deps = ws.merge_namespace(
289 ns,
290 source,
291 &parent,
292 SHALLOW,
293 &abs_lib_path,
294 )?;
295 dependencies.append(&mut deps);
296
297 already_imported.insert(p);
298 }
299 Ok(ws)
300 }
301}