hugr_model/v0/table/mod.rs
1//! Table representation of hugr modules.
2//!
3//! Instead of directly nesting data structures, we store them in tables and
4//! refer to them by their id in the table. Variables, symbols and links are
5//! fully resolved: uses refer to the id of the declaration. This allows the
6//! table representation to be read from the [binary format] and imported into
7//! the core data structures without having to perform potentially costly name
8//! resolutions.
9//!
10//! The tabling is also used for deduplication of terms. In practice, many terms
11//! will share the same subterms, and we can save memory and validation time by
12//! storing them only once. However we allow non-deduplicated terms for cases in
13//! which terms carry additional identity over just their structure. For
14//! instance, structurally identical terms could originate from different
15//! locations in a text file and therefore should be treated differently when
16//! locating type errors.
17//!
18//! This format is intended to be used as an intermediary data structure to
19//! convert between different representations (such as the [binary format], the
20//! [text format] or internal compiler data structures). To make this efficient,
21//! we use arena allocation via the [`bumpalo`] crate to efficiently construct and
22//! tear down this representation. The data structures in this module therefore carry
23//! a lifetime parameter that indicates the lifetime of the arena.
24//!
25//! [binary format]: crate::v0::binary
26//! [text format]: crate::v0::ast
27
28use smol_str::SmolStr;
29use thiserror::Error;
30
31mod view;
32use super::{Literal, RegionKind, Visibility, ast};
33pub use view::View;
34
35/// A package consisting of a sequence of [`Module`]s.
36///
37/// See [`ast::Package`] for the AST representation.
38///
39/// [`ast::Package`]: crate::v0::ast::Package
40#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
41pub struct Package<'a> {
42 /// The modules in the package.
43 pub modules: Vec<Module<'a>>,
44}
45
46impl Package<'_> {
47 /// Convert the package to the [ast] representation.
48 ///
49 /// [ast]: crate::v0::ast
50 #[must_use]
51 pub fn as_ast(&self) -> Option<ast::Package> {
52 let modules = self
53 .modules
54 .iter()
55 .map(Module::as_ast)
56 .collect::<Option<_>>()?;
57 Some(ast::Package { modules })
58 }
59}
60
61/// A module consisting of a hugr graph together with terms.
62///
63/// See [`ast::Module`] for the AST representation.
64///
65/// [`ast::Module`]: crate::v0::ast::Module
66#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
67pub struct Module<'a> {
68 /// The id of the root region.
69 pub root: RegionId,
70 /// Table of [`Node`]s.
71 pub nodes: Vec<Node<'a>>,
72 /// Table of [`Region`]s.
73 pub regions: Vec<Region<'a>>,
74 /// Table of [`Term`]s.
75 pub terms: Vec<Term<'a>>,
76}
77
78impl<'a> Module<'a> {
79 /// Return the node data for a given node id.
80 #[inline]
81 #[must_use]
82 pub fn get_node(&self, node_id: NodeId) -> Option<&Node<'a>> {
83 self.nodes.get(node_id.index())
84 }
85
86 /// Return a mutable reference to the node data for a given node id.
87 #[inline]
88 pub fn get_node_mut(&mut self, node_id: NodeId) -> Option<&mut Node<'a>> {
89 self.nodes.get_mut(node_id.index())
90 }
91
92 /// Insert a new node into the module and return its id.
93 pub fn insert_node(&mut self, node: Node<'a>) -> NodeId {
94 let id = NodeId::new(self.nodes.len());
95 self.nodes.push(node);
96 id
97 }
98
99 /// Return the term data for a given term id.
100 ///
101 /// Returns [`Term::Wildcard`] when the term id is invalid.
102 #[inline]
103 #[must_use]
104 pub fn get_term(&self, term_id: TermId) -> Option<&Term<'a>> {
105 if term_id.is_valid() {
106 self.terms.get(term_id.index())
107 } else {
108 Some(&Term::Wildcard)
109 }
110 }
111
112 /// Return a mutable reference to the term data for a given term id.
113 #[inline]
114 pub fn get_term_mut(&mut self, term_id: TermId) -> Option<&mut Term<'a>> {
115 self.terms.get_mut(term_id.index())
116 }
117
118 /// Insert a new term into the module and return its id.
119 pub fn insert_term(&mut self, term: Term<'a>) -> TermId {
120 let id = TermId::new(self.terms.len());
121 self.terms.push(term);
122 id
123 }
124
125 /// Return the region data for a given region id.
126 #[inline]
127 #[must_use]
128 pub fn get_region(&self, region_id: RegionId) -> Option<&Region<'a>> {
129 self.regions.get(region_id.index())
130 }
131
132 /// Return a mutable reference to the region data for a given region id.
133 #[inline]
134 pub fn get_region_mut(&mut self, region_id: RegionId) -> Option<&mut Region<'a>> {
135 self.regions.get_mut(region_id.index())
136 }
137
138 /// Insert a new region into the module and return its id.
139 pub fn insert_region(&mut self, region: Region<'a>) -> RegionId {
140 let id = RegionId::new(self.regions.len());
141 self.regions.push(region);
142 id
143 }
144
145 /// Attempt to view a part of this module via a [`View`] instance.
146 pub fn view<S, V: View<'a, S>>(&'a self, src: S) -> Option<V> {
147 V::view(self, src)
148 }
149
150 /// Convert the module to the [ast] representation.
151 ///
152 /// [ast]: crate::v0::ast
153 #[must_use]
154 pub fn as_ast(&self) -> Option<ast::Module> {
155 let root = self.view(self.root)?;
156 Some(ast::Module { root })
157 }
158}
159
160/// Nodes in the hugr graph.
161///
162/// See [`ast::Node`] for the AST representation.
163///
164/// [`ast::Node`]: crate::v0::ast::Node
165#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
166pub struct Node<'a> {
167 /// The operation that the node performs.
168 pub operation: Operation<'a>,
169 /// The input ports of the node.
170 pub inputs: &'a [LinkIndex],
171 /// The output ports of the node.
172 pub outputs: &'a [LinkIndex],
173 /// The regions of the node.
174 pub regions: &'a [RegionId],
175 /// The meta information attached to the node.
176 pub meta: &'a [TermId],
177 /// The signature of the node.
178 ///
179 /// Can be `None` to indicate that the node's signature should be inferred,
180 /// or for nodes with operations that do not have a signature.
181 pub signature: Option<TermId>,
182}
183
184/// Operations that nodes can perform.
185///
186/// See [`ast::Operation`] for the AST representation.
187///
188/// [`ast::Operation`]: crate::v0::ast::Operation
189#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
190pub enum Operation<'a> {
191 /// Invalid operation to be used as a placeholder.
192 /// This is useful for modules that have non-contiguous node ids, or modules
193 /// that have not yet been fully constructed.
194 #[default]
195 Invalid,
196 /// Data flow graphs.
197 Dfg,
198 /// Control flow graphs.
199 Cfg,
200 /// Basic blocks in a control flow graph.
201 Block,
202 /// Function definitions.
203 DefineFunc(&'a Symbol<'a>),
204 /// Function declarations.
205 DeclareFunc(&'a Symbol<'a>),
206 /// Custom operation.
207 Custom(TermId),
208 /// Alias definitions.
209 DefineAlias(&'a Symbol<'a>, TermId),
210 /// Alias declarations.
211 DeclareAlias(&'a Symbol<'a>),
212 /// Tail controlled loop.
213 /// Nodes with this operation contain a dataflow graph that is executed in a loop.
214 /// The loop body is executed at least once, producing a result that indicates whether
215 /// to continue the loop or return the result.
216 ///
217 /// # Port Types
218 ///
219 /// - **Inputs**: `inputs` + `rest`
220 /// - **Outputs**: `outputs` + `rest`
221 /// - **Sources**: `inputs` + `rest`
222 /// - **Targets**: `(adt [inputs outputs])` + `rest`
223 TailLoop,
224
225 /// Conditional operation.
226 ///
227 /// # Port types
228 ///
229 /// - **Inputs**: `[(adt inputs)]` + `context`
230 /// - **Outputs**: `outputs`
231 Conditional,
232
233 /// Declaration for a term constructor.
234 ///
235 /// Nodes with this operation must be within a module region.
236 DeclareConstructor(&'a Symbol<'a>),
237
238 /// Declaration for a operation.
239 ///
240 /// Nodes with this operation must be within a module region.
241 DeclareOperation(&'a Symbol<'a>),
242
243 /// Import a symbol.
244 Import {
245 /// The name of the symbol to be imported.
246 name: &'a str,
247 },
248}
249
250impl<'a> Operation<'a> {
251 /// Returns the symbol introduced by the operation, if any.
252 #[must_use]
253 pub fn symbol(&self) -> Option<&'a str> {
254 match self {
255 Operation::DefineFunc(symbol) => Some(symbol.name),
256 Operation::DeclareFunc(symbol) => Some(symbol.name),
257 Operation::DefineAlias(symbol, _) => Some(symbol.name),
258 Operation::DeclareAlias(symbol) => Some(symbol.name),
259 Operation::DeclareConstructor(symbol) => Some(symbol.name),
260 Operation::DeclareOperation(symbol) => Some(symbol.name),
261 Operation::Import { name } => Some(name),
262 _ => None,
263 }
264 }
265}
266
267/// A region in the hugr.
268///
269/// See [`ast::Region`] for the AST representation.
270///
271/// [`ast::Region`]: crate::v0::ast::Region
272#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
273pub struct Region<'a> {
274 /// The kind of the region. See [`RegionKind`] for details.
275 pub kind: RegionKind,
276 /// The source ports of the region.
277 pub sources: &'a [LinkIndex],
278 /// The target ports of the region.
279 pub targets: &'a [LinkIndex],
280 /// The nodes in the region. The order of the nodes is not significant.
281 pub children: &'a [NodeId],
282 /// The metadata attached to the region.
283 pub meta: &'a [TermId],
284 /// The signature of the region.
285 pub signature: Option<TermId>,
286 /// Information about the scope defined by this region, if the region is closed.
287 pub scope: Option<RegionScope>,
288}
289
290/// Information about the scope defined by a closed region.
291#[derive(Debug, Clone, PartialEq, Eq, Hash)]
292pub struct RegionScope {
293 /// The number of links in the scope.
294 pub links: u32,
295 /// The number of ports in the scope.
296 pub ports: u32,
297}
298
299/// A symbol.
300///
301/// See [`ast::Symbol`] for the AST representation.
302///
303/// [`ast::Symbol`]: crate::v0::ast::Symbol
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
305pub struct Symbol<'a> {
306 /// The visibility of the symbol.
307 pub visibility: &'a Option<Visibility>,
308 /// The name of the symbol.
309 pub name: &'a str,
310 /// The static parameters.
311 pub params: &'a [Param<'a>],
312 /// The constraints on the static parameters.
313 pub constraints: &'a [TermId],
314 /// The signature of the symbol.
315 pub signature: TermId,
316}
317
318/// An index of a variable within a node's parameter list.
319pub type VarIndex = u16;
320
321/// A term in the compile time meta language.
322///
323/// See [`ast::Term`] for the AST representation.
324///
325/// [`ast::Term`]: crate::v0::ast::Term
326#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
327pub enum Term<'a> {
328 /// Standin for any term.
329 #[default]
330 Wildcard,
331
332 /// A local variable.
333 Var(VarId),
334
335 /// Apply a symbol to a sequence of arguments.
336 ///
337 /// The symbol is defined by a node in the same graph. The type of this term
338 /// is derived from instantiating the symbol's parameters in the symbol's
339 /// signature.
340 Apply(NodeId, &'a [TermId]),
341
342 /// List of static data.
343 ///
344 /// Lists can include individual items or other lists to be spliced in.
345 ///
346 /// **Type:** `(core.list ?t)`
347 List(&'a [SeqPart]),
348
349 /// A static literal value.
350 Literal(Literal),
351
352 /// A constant anonymous function.
353 ///
354 /// **Type:** `(core.const (core.fn ?ins ?outs ?ext) (ext))`
355 Func(RegionId),
356
357 /// Tuple of static data.
358 ///
359 /// Tuples can include individual items or other tuples to be spliced in.
360 ///
361 /// **Type:** `(core.tuple ?types)`
362 Tuple(&'a [SeqPart]),
363}
364
365impl From<Literal> for Term<'_> {
366 fn from(value: Literal) -> Self {
367 Self::Literal(value)
368 }
369}
370
371/// A part of a list/tuple term.
372///
373/// See [`ast::SeqPart`] for the AST representation.
374///
375/// [`ast::SeqPart`]: crate::v0::ast::SeqPart
376#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
377pub enum SeqPart {
378 /// A single item.
379 Item(TermId),
380 /// A list to be spliced into the parent list/tuple.
381 Splice(TermId),
382}
383
384/// A parameter to a function or alias.
385///
386/// Parameter names must be unique within a parameter list.
387///
388/// See [`ast::Param`] for the AST representation.
389///
390/// [`ast::Param`]: crate::v0::ast::Param
391#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
392pub struct Param<'a> {
393 /// The name of the parameter.
394 pub name: &'a str,
395 /// The type of the parameter.
396 pub r#type: TermId,
397}
398
399macro_rules! define_index {
400 ($(#[$meta:meta])* $vis:vis struct $name:ident(pub u32);) => {
401 #[repr(transparent)]
402 $(#[$meta])*
403 $vis struct $name(pub u32);
404
405 impl $name {
406 /// Create a new index.
407 ///
408 /// # Panics
409 ///
410 /// Panics if the index is 2^32 or larger.
411 #[must_use] pub fn new(index: usize) -> Self {
412 assert!(index < u32::MAX as usize, "index out of bounds");
413 Self(index as u32)
414 }
415
416 /// Returns whether the index is valid.
417 #[inline]
418 #[must_use] pub fn is_valid(self) -> bool {
419 self.0 < u32::MAX
420 }
421
422 /// Returns the index as a `usize` to conveniently use it as a slice index.
423 #[inline]
424 #[must_use] pub fn index(self) -> usize {
425 self.0 as usize
426 }
427
428 /// Convert a slice of this index type into a slice of `u32`s.
429 #[must_use] pub fn unwrap_slice(slice: &[Self]) -> &[u32] {
430 // SAFETY: This type is just a newtype around `u32`.
431 unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u32, slice.len()) }
432 }
433
434 /// Convert a slice of `u32`s into a slice of this index type.
435 #[must_use] pub fn wrap_slice(slice: &[u32]) -> &[Self] {
436 // SAFETY: This type is just a newtype around `u32`.
437 unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const Self, slice.len()) }
438 }
439 }
440
441 impl Default for $name {
442 fn default() -> Self {
443 Self(u32::MAX)
444 }
445 }
446 };
447}
448
449define_index! {
450 /// Id of a node in a hugr graph.
451 #[derive(Debug, derive_more::Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
452 pub struct NodeId(pub u32);
453}
454
455define_index! {
456 /// Index of a link in a hugr graph.
457 #[derive(Debug, derive_more::Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
458 pub struct LinkIndex(pub u32);
459}
460
461define_index! {
462 /// Id of a region in a hugr graph.
463 #[derive(Debug, derive_more::Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
464 pub struct RegionId(pub u32);
465}
466
467define_index! {
468 /// Id of a term in a hugr graph.
469 #[derive(Debug, derive_more::Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
470 pub struct TermId(pub u32);
471}
472
473/// The id of a link consisting of its region and the link index.
474#[derive(Debug, derive_more::Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
475#[display("{_0}#{_1}")]
476pub struct LinkId(pub RegionId, pub LinkIndex);
477
478/// The id of a variable consisting of its node and the variable index.
479#[derive(Debug, derive_more::Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
480#[display("{_0}#{_1}")]
481pub struct VarId(pub NodeId, pub VarIndex);
482
483/// Errors that can occur when traversing and interpreting the model.
484#[derive(Debug, Clone, Error)]
485#[non_exhaustive]
486pub enum ModelError {
487 /// There is a reference to a node that does not exist.
488 #[error("node not found: {0}")]
489 NodeNotFound(NodeId),
490 /// There is a reference to a term that does not exist.
491 #[error("term not found: {0}")]
492 TermNotFound(TermId),
493 /// There is a reference to a region that does not exist.
494 #[error("region not found: {0}")]
495 RegionNotFound(RegionId),
496 /// Invalid variable reference.
497 #[error("variable {0} invalid")]
498 InvalidVar(VarId),
499 /// Invalid symbol reference.
500 #[error("symbol reference {0} invalid")]
501 InvalidSymbol(NodeId),
502 /// The model contains an operation in a place where it is not allowed.
503 #[error("unexpected operation on node: {0}")]
504 UnexpectedOperation(NodeId),
505 /// There is a term that is not well-typed.
506 #[error("type error in term: {0}")]
507 TypeError(TermId),
508 /// There is a node whose regions are not well-formed according to the node's operation.
509 #[error("node has invalid regions: {0}")]
510 InvalidRegions(NodeId),
511 /// There is a name that is not well-formed.
512 #[error("malformed name: {0}")]
513 MalformedName(SmolStr),
514 /// There is a condition node that lacks a case for a tag or
515 /// defines two cases for the same tag.
516 #[error("condition node is malformed: {0}")]
517 MalformedCondition(NodeId),
518 /// There is a node that is not well-formed or has the invalid operation.
519 #[error("invalid operation on node: {0}")]
520 InvalidOperation(NodeId),
521}