calyx_frontend/
lib_sig.rs

1use crate::Primitive;
2use calyx_utils::Id;
3use itertools::Itertools;
4use linked_hash_map::LinkedHashMap;
5use std::path::PathBuf;
6
7#[derive(Debug)]
8/// Tracks the information for [Primitive]s defined in the program.
9pub enum PrimitiveInfo {
10    /// An extern block that defines multiple primitives
11    Extern {
12        path: PathBuf,
13        primitives: LinkedHashMap<Id, Primitive>,
14        is_source: bool,
15    },
16    /// An inline primitive
17    Inline {
18        primitive: Primitive,
19        is_source: bool,
20    },
21}
22impl PrimitiveInfo {
23    pub fn ext(
24        path: PathBuf,
25        primitives: LinkedHashMap<Id, Primitive>,
26    ) -> Self {
27        PrimitiveInfo::Extern {
28            path,
29            primitives,
30            is_source: false,
31        }
32    }
33
34    pub fn inline(primitive: Primitive) -> Self {
35        PrimitiveInfo::Inline {
36            primitive,
37            is_source: false,
38        }
39    }
40
41    /// Check if this primitive is a source primitive
42    pub fn is_source(&self) -> bool {
43        match self {
44            PrimitiveInfo::Extern { is_source, .. } => *is_source,
45            PrimitiveInfo::Inline { is_source, .. } => *is_source,
46        }
47    }
48
49    /// Mark this primitive as a source primitive
50    pub fn set_source(&mut self) {
51        match self {
52            PrimitiveInfo::Extern {
53                ref mut is_source, ..
54            } => *is_source = true,
55            PrimitiveInfo::Inline {
56                ref mut is_source, ..
57            } => *is_source = true,
58        }
59    }
60}
61
62/// A representation of all the primitive definitions found while parsing
63/// the root program.
64#[derive(Debug, Default)]
65pub struct LibrarySignatures {
66    /// The primitives defined in the current context.
67    prims: Vec<PrimitiveInfo>,
68}
69
70impl LibrarySignatures {
71    /// Add a new inline primitive to the context.
72    /// Panics if a primitive with the same name is already defined.
73    pub fn add_inline_primitive(
74        &mut self,
75        primitive: Primitive,
76    ) -> &mut PrimitiveInfo {
77        assert!(
78            primitive.body.is_some(),
79            "inline primitive must have a body"
80        );
81        let name = primitive.name;
82        if self.find_primitive(name).is_some() {
83            panic!("Primitive `{}` is already defined in the context.", name);
84        }
85        let prim = PrimitiveInfo::inline(primitive);
86        self.prims.push(prim);
87        self.prims.last_mut().unwrap()
88    }
89
90    /// Add a new, non-inline primitive to the context.
91    /// Panics if a primitive with the same name is already defined.
92    /// Requires that the file path is absolute and canonical.
93    pub fn add_extern_primitive(
94        &mut self,
95        file: PathBuf,
96        primitive: Primitive,
97    ) {
98        assert!(
99            primitive.body.is_none(),
100            "non-inline primitive must not have a body"
101        );
102        let name = primitive.name;
103        if self.find_primitive(name).is_some() {
104            panic!("Primitive `{}` is already defined in the context.", name);
105        }
106        let definined_ext = self.prims.iter_mut().find(|prim| match prim {
107            PrimitiveInfo::Extern { path, .. } => path == &file,
108            _ => false,
109        });
110        if let Some(PrimitiveInfo::Extern { primitives, .. }) = definined_ext {
111            primitives.insert(name, primitive);
112        } else {
113            let mut primitives = LinkedHashMap::new();
114            primitives.insert(name, primitive);
115            self.prims.push(PrimitiveInfo::ext(file, primitives));
116        }
117    }
118
119    pub(crate) fn add_extern(
120        &mut self,
121        file: PathBuf,
122        prims: Vec<Primitive>,
123    ) -> &mut PrimitiveInfo {
124        let definined_ext = self.prims.iter().any(|prim| match prim {
125            PrimitiveInfo::Extern { path, .. } => path == &file,
126            _ => false,
127        });
128        if definined_ext {
129            panic!(
130                "Extern block with file `{}` is already defined in the context",
131                file.display()
132            );
133        }
134
135        let ext = PrimitiveInfo::ext(
136            file,
137            prims.into_iter().map(|p| (p.name, p)).collect(),
138        );
139        self.prims.push(ext);
140        self.prims.last_mut().unwrap()
141    }
142
143    /// Return the [Primitive] associated with the given name if defined, otherwise return None.
144    pub fn find_primitive<S>(&self, name: S) -> Option<&Primitive>
145    where
146        S: Into<Id>,
147    {
148        let key = name.into();
149        self.prims.iter().find_map(|prim| match prim {
150            PrimitiveInfo::Extern { primitives, .. } => primitives.get(&key),
151            PrimitiveInfo::Inline { primitive, .. } => {
152                if primitive.name == key {
153                    Some(primitive)
154                } else {
155                    None
156                }
157            }
158        })
159    }
160
161    /// Return the [Primitive] associated to this Id.
162    pub fn get_primitive<S>(&self, name: S) -> &Primitive
163    where
164        S: Into<Id>,
165    {
166        let key = name.into();
167        self.find_primitive(key).unwrap_or_else(|| {
168            panic!("Primitive `{}` is not defined in the context.", key)
169        })
170    }
171
172    /// Mark an inlined primitive as a part of the source.
173    /// This is useful when using file mode compilation and printing only the source primitives.
174    /// Panics if the primitive is not defined.
175    pub fn mark_inline_source(&mut self, name: Id) {
176        let Some(inlined) = self.prims.iter_mut().find(|prim| match prim {
177            PrimitiveInfo::Inline { primitive, .. } => primitive.name == name,
178            PrimitiveInfo::Extern { .. } => false,
179        }) else {
180            panic!("Primitive `{}` is not defined in the context.", name);
181        };
182        inlined.set_source()
183    }
184
185    /// Marks an `import`ed extern block as a part of the source.
186    /// There is no way to mark an individual primitive as a part of the source since the entire file will be linked.
187    /// Panics if the file path is not defined
188    pub fn mark_extern_source(&mut self, path: PathBuf) {
189        let Some(ext_def) = self.prims.iter_mut().find(|prim| match prim {
190            PrimitiveInfo::Extern { path: p, .. } => p == &path,
191            PrimitiveInfo::Inline { .. } => false,
192        }) else {
193            panic!(
194                "extern file `{}` is not defined in the context",
195                path.to_string_lossy()
196            );
197        };
198        ext_def.set_source()
199    }
200
201    /// Return an iterator over all defined primitives.
202    pub fn signatures(&self) -> impl Iterator<Item = &Primitive> + '_ {
203        self.prims.iter().flat_map(|prim| match prim {
204            PrimitiveInfo::Extern { primitives, .. } => {
205                primitives.values().collect_vec()
206            }
207            PrimitiveInfo::Inline { primitive, .. } => vec![primitive],
208        })
209    }
210
211    /// Returns all the underlying primitive information.
212    /// If you want all the signatures, use [LibrarySignatures::signatures] instead.
213    pub fn prim_infos(&self) -> &Vec<PrimitiveInfo> {
214        &self.prims
215    }
216
217    /// Return the underyling inlined primitives and whether they are source defined
218    pub fn prim_inlines(
219        &self,
220    ) -> impl Iterator<Item = (&Primitive, bool)> + '_ {
221        self.prims.iter().flat_map(|prim| match prim {
222            PrimitiveInfo::Extern { .. } => None,
223            PrimitiveInfo::Inline {
224                primitive,
225                is_source,
226            } => Some((primitive, *is_source)),
227        })
228    }
229
230    /// Return the paths for the extern defining files along with whether they are source defined.
231    pub fn extern_paths(&self) -> Vec<&PathBuf> {
232        self.prims
233            .iter()
234            .filter_map(|p| match p {
235                PrimitiveInfo::Extern { path, .. } => Some(path),
236                PrimitiveInfo::Inline { .. } => None,
237            })
238            .collect_vec()
239    }
240}