calyx_frontend/
workspace.rs1use super::{
2 ast::{ComponentDef, NamespaceDef},
3 parser,
4};
5use crate::{LibrarySignatures, source_info::SourceInfoTable};
6use calyx_utils::{CalyxResult, Error, WithPos};
7use itertools::Itertools;
8use std::{
9 collections::HashSet,
10 path::{Path, PathBuf},
11};
12
13const COMPILE_LIB: &str = include_str!("../resources/compile.futil");
16
17#[derive(Default)]
41pub struct Workspace {
42 pub components: Vec<ComponentDef>,
44 pub declarations: Vec<ComponentDef>,
48 pub lib: LibrarySignatures,
50 pub original_imports: Vec<String>,
52 pub metadata: Option<String>,
54 pub source_info_table: Option<SourceInfoTable>,
56}
57
58impl Workspace {
59 fn canonicalize_import<S>(
66 import: S,
67 parent: &Path,
68 lib_paths: &[PathBuf],
69 ) -> CalyxResult<PathBuf>
70 where
71 S: AsRef<Path> + Clone + WithPos,
72 {
73 let absolute_import = import.as_ref();
74 if absolute_import.is_absolute() && absolute_import.exists() {
75 return Ok(import.as_ref().to_path_buf());
76 }
77
78 let relative_import = parent.join(&import);
79 if relative_import.exists() {
80 return Ok(relative_import);
81 }
82
83 let library_imports: Vec<_> = lib_paths
84 .iter()
85 .filter_map(|lib_path| {
86 let library_import = lib_path.join(&import);
87 library_import.exists().then_some(library_import)
88 })
89 .collect();
90
91 match library_imports.len() {
92 0 => {
93 Err(Error::invalid_file(format!(
94 "Import path `{}` found neither as an absolute path, nor in the parent ({}), nor in library path ({})",
95 import.as_ref().to_string_lossy(),
96 parent.to_string_lossy(),
97 lib_paths.iter().map(|p| p.to_string_lossy()).format(", ")
98 ))
99 .with_pos(&import))
100 }
101 1 => Ok(library_imports.into_iter().next().unwrap()),
102 _ => {
103 Err(Error::misc(format!(
104 "Import path `{}` found in multiple library paths ({})",
105 import.as_ref().to_string_lossy(),
106 library_imports
107 .iter()
108 .map(|p| p.to_string_lossy())
109 .format(", ")
110 ))
111 .with_pos(&import))
112 }
113 }
114 }
115
116 #[cfg(not(target_arch = "wasm32"))]
119 fn canonicalize_extern<S>(
120 extern_path: S,
121 parent: &Path,
122 ) -> CalyxResult<PathBuf>
123 where
124 S: AsRef<Path> + Clone + WithPos,
125 {
126 parent
127 .join(extern_path.clone())
128 .canonicalize()
129 .map_err(|_| {
130 Error::invalid_file(format!(
131 "Extern path `{}` not found in parent directory ({})",
132 extern_path.as_ref().to_string_lossy(),
133 parent.to_string_lossy(),
134 ))
135 .with_pos(&extern_path)
136 })
137 }
138
139 pub fn from_compile_lib() -> CalyxResult<Self> {
142 let mut ns = NamespaceDef::construct_from_str(COMPILE_LIB)?;
143 assert!(
145 ns.imports.is_empty(),
146 "core library should not contain any imports"
147 );
148 assert!(
150 ns.metadata.is_none(),
151 "core library should not contain any metadata"
152 );
153 assert!(
155 ns.externs.len() == 1 && ns.externs[0].0.is_none(),
156 "core library should only contain inline externs"
157 );
158 let (_, externs) = ns.externs.pop().unwrap();
159 let mut lib = LibrarySignatures::default();
160 for ext in externs {
161 lib.add_inline_primitive(ext);
162 }
163 let ws = Workspace {
164 components: ns.components,
165 lib,
166 ..Default::default()
167 };
168 Ok(ws)
169 }
170
171 pub fn construct(
174 file: &Option<PathBuf>,
175 lib_paths: &[PathBuf],
176 ) -> CalyxResult<Self> {
177 Self::construct_with_all_deps::<false>(
178 file.iter().cloned().collect(),
179 lib_paths,
180 )
181 }
182
183 pub fn construct_shallow(
186 file: &Option<PathBuf>,
187 lib_paths: &[PathBuf],
188 ) -> CalyxResult<Self> {
189 Self::construct_with_all_deps::<true>(
190 file.iter().cloned().collect(),
191 lib_paths,
192 )
193 }
194
195 fn get_parent(p: &Path) -> PathBuf {
196 let maybe_parent = p.parent();
197 match maybe_parent {
198 None => PathBuf::from("."),
199 Some(path) => {
200 if path.to_string_lossy() == "" {
201 PathBuf::from(".")
202 } else {
203 PathBuf::from(path)
204 }
205 }
206 }
207 }
208
209 pub fn merge_namespace(
213 &mut self,
214 ns: NamespaceDef,
215 is_source: bool,
216 parent: &Path,
217 shallow: bool,
218 lib_paths: &[PathBuf],
219 ) -> CalyxResult<Vec<(PathBuf, bool)>> {
220 for (path, exts) in ns.externs {
222 match path {
223 Some(p) => {
224 #[cfg(not(target_arch = "wasm32"))]
225 let abs_path = Self::canonicalize_extern(p, parent)?;
226
227 #[cfg(target_arch = "wasm32")]
232 let abs_path = p.into();
233
234 let p = self.lib.add_extern(abs_path, exts);
235 if is_source {
236 p.set_source();
237 }
238 }
239 None => {
240 for ext in exts {
241 let p = self.lib.add_inline_primitive(ext);
242 if is_source {
243 p.set_source();
244 }
245 }
246 }
247 }
248 }
249
250 if !is_source && shallow {
253 self.declarations.extend(&mut ns.components.into_iter());
254 } else {
255 self.components.extend(&mut ns.components.into_iter());
256 }
257 let deps = ns
259 .imports
260 .into_iter()
261 .map(|p| {
262 Self::canonicalize_import(p, parent, lib_paths)
263 .map(|s| (s, false))
264 })
265 .collect::<CalyxResult<_>>()?;
266
267 Ok(deps)
268 }
269
270 pub fn construct_with_all_deps<const SHALLOW: bool>(
274 mut files: Vec<PathBuf>,
275 lib_paths: &[PathBuf],
276 ) -> CalyxResult<Self> {
277 let first = files.pop();
279 let ns = NamespaceDef::construct(&first)?;
280 let parent_path = first
281 .as_ref()
282 .map(|p| Self::get_parent(p))
283 .unwrap_or_else(|| PathBuf::from("."));
284
285 let mut dependencies: Vec<(PathBuf, bool)> =
287 files.into_iter().map(|p| (p, true)).collect();
288 let mut already_imported: HashSet<PathBuf> = HashSet::new();
290
291 let mut ws = Workspace::default();
292
293 let abs_lib_paths: Vec<_> = lib_paths
294 .iter()
295 .map(|lib_path| {
296 lib_path.canonicalize().map_err(|err| {
297 Error::invalid_file(format!(
298 "Failed to canonicalize library path `{}`: {}",
299 lib_path.to_string_lossy(),
300 err
301 ))
302 })
303 })
304 .collect::<CalyxResult<_>>()?;
305
306 ws.original_imports =
308 ns.imports.iter().map(|imp| imp.to_string()).collect();
309
310 ws.metadata = ns.metadata.clone();
313 ws.source_info_table = ns.source_info_table.clone();
314
315 let parent_canonical = parent_path.canonicalize().map_err(|err| {
317 Error::invalid_file(format!(
318 "Failed to canonicalize parent path `{}`: {}",
319 parent_path.to_string_lossy(),
320 err
321 ))
322 })?;
323 let mut deps = ws.merge_namespace(
324 ns,
325 true,
326 &parent_canonical,
327 false,
328 &abs_lib_paths,
329 )?;
330 dependencies.append(&mut deps);
331
332 while let Some((p, source)) = dependencies.pop() {
333 if already_imported.contains(&p) {
334 continue;
335 }
336 let ns = parser::CalyxParser::parse_file(&p)?;
337 let parent = Self::get_parent(&p);
338
339 let mut deps = ws.merge_namespace(
340 ns,
341 source,
342 &parent,
343 SHALLOW,
344 &abs_lib_paths,
345 )?;
346 dependencies.append(&mut deps);
347
348 already_imported.insert(p);
349 }
350 Ok(ws)
351 }
352}