From 01bfc138b3b988f60c2f8f7f851d5e144c76e65d Mon Sep 17 00:00:00 2001 From: Lukas Heidemann Date: Fri, 9 Aug 2024 18:43:08 +0100 Subject: [PATCH] `hugr-model` draft and export from `hugr-core`. --- Cargo.toml | 4 +- hugr-core/Cargo.toml | 3 + hugr-core/src/export.rs | 417 +++++++++++++++++++++++++++++++++++++++ hugr-core/src/lib.rs | 1 + hugr-core/src/types.rs | 4 +- hugr-model/Cargo.toml | 15 ++ hugr-model/src/lib.rs | 2 + hugr-model/src/v0/mod.rs | 347 ++++++++++++++++++++++++++++++++ 8 files changed, 791 insertions(+), 2 deletions(-) create mode 100644 hugr-core/src/export.rs create mode 100644 hugr-model/Cargo.toml create mode 100644 hugr-model/src/lib.rs create mode 100644 hugr-model/src/v0/mod.rs diff --git a/Cargo.toml b/Cargo.toml index ae5453295..54418459e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ lto = "thin" [workspace] resolver = "2" -members = ["hugr", "hugr-core", "hugr-passes", "hugr-cli"] +members = ["hugr", "hugr-core", "hugr-passes", "hugr-cli", "hugr-model"] [workspace.package] rust-version = "1.75" @@ -62,6 +62,8 @@ clap-verbosity-flag = "2.2.0" assert_cmd = "2.0.14" assert_fs = "1.1.1" predicates = "3.1.0" +tinyvec = { version = "1.8.0", features = ["alloc"] } +indexmap = "2.3.0" [profile.dev.package] insta.opt-level = 3 diff --git a/hugr-core/Cargo.toml b/hugr-core/Cargo.toml index c52e4f06e..85bd8cb09 100644 --- a/hugr-core/Cargo.toml +++ b/hugr-core/Cargo.toml @@ -46,6 +46,9 @@ paste = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } semver = { version = "1.0.23", features = ["serde"] } +hugr-model = { path = "../hugr-model" } +indexmap.workspace = true +tinyvec.workspace = true [dev-dependencies] rstest = { workspace = true } diff --git a/hugr-core/src/export.rs b/hugr-core/src/export.rs new file mode 100644 index 000000000..1f45f9b42 --- /dev/null +++ b/hugr-core/src/export.rs @@ -0,0 +1,417 @@ +//! Text syntax. +use crate::{ + extension::ExtensionSet, + ops::{NamedOp as _, OpType}, + types::{ + type_param::{TypeArgVariable, TypeParam}, + type_row::TypeRowBase, + CustomType, FuncTypeBase, MaybeRV, PolyFuncTypeBase, RowVariable, SumType, TypeArg, + TypeBase, TypeEnum, + }, + Direction, Hugr, HugrView, Node, Port, +}; +use hugr_model::v0 as model; +use indexmap::IndexMap; +use smol_str::ToSmolStr; +use tinyvec::TinyVec; + +/// Export a [`Hugr`] graph to its representation in the model. +pub fn export(hugr: &Hugr) -> model::Module { + let mut context = Context::new(hugr); + hugr.root().export(&mut context); + context.module +} + +/// State for converting a HUGR graph to its representation in the model. +struct Context<'a> { + /// The HUGR graph to convert. + hugr: &'a Hugr, + module: model::Module, + /// Mapping from ports to edge indices. + /// This only includes the minimum port among groups of linked ports. + edges: IndexMap<(Node, Port), usize>, +} + +impl<'a> Context<'a> { + pub fn new(hugr: &'a Hugr) -> Self { + Self { + hugr, + module: model::Module::default(), + edges: IndexMap::new(), + } + } + + pub fn make_term(&mut self, term: model::Term) -> model::TermId { + let index = self.module.terms.len(); + self.module.terms.push(term); + model::TermId(index as _) + } + + pub fn make_node(&mut self, node: model::Node) -> model::NodeId { + let index = self.module.terms.len(); + self.module.nodes.push(node); + model::NodeId(index as _) + } + + pub fn make_port(&mut self, node: Node, port: impl Into) -> model::PortId { + let port = port.into(); + let index = self.module.ports.len(); + let port_id = model::PortId(index as _); + let r#type = self.make_term(model::Term::Wildcard); // TODO + self.module.ports.push(model::Port { + r#type, + meta: Vec::new(), + }); + + // To ensure that linked ports are represented by the same edge, we take the minimum port + // among all the linked ports, including the one we started with. + let linked_ports = self.hugr.linked_ports(node, port); + let all_ports = std::iter::once((node, port)).chain(linked_ports); + let repr = all_ports.min().unwrap(); + + let edge_id = *self.edges.entry(repr).or_insert_with(|| { + let edge_id = self.module.edges.len(); + let edge = model::Edge::default(); + self.module.edges.push(edge); + edge_id + }); + + match port.direction() { + Direction::Incoming => self.module.edges[edge_id].inputs.push(port_id), + Direction::Outgoing => self.module.edges[edge_id].outputs.push(port_id), + } + + port_id + } + + /// Get the name of the function associated with a node that takes a static input + /// connected to a function definition or declaration. Returns `None` otherwise. + fn get_func_name(&self, node: Node) -> Option { + let port = self.hugr.node_inputs(node).last()?; + let (defn_node, _) = self.hugr.linked_outputs(node, port).next()?; + match self.hugr.get_optype(defn_node) { + OpType::FuncDecl(func_decl) => Some(model::Symbol(func_decl.name())), + OpType::FuncDefn(func_defn) => Some(model::Symbol(func_defn.name())), + _ => None, + } + } +} + +/// Trait for core types that can be exported into the model format. +trait Export { + /// The target type to export to. + type Target; + + /// Export the value into the target type. + fn export(&self, ctx: &mut Context) -> Self::Target; +} + +impl Export for Node { + type Target = model::NodeId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + let mut params = TinyVec::new(); + + let inputs = ctx + .hugr + .node_inputs(*self) + .map(|port| ctx.make_port(*self, port)) + .collect(); + + let outputs = ctx + .hugr + .node_outputs(*self) + .map(|port| ctx.make_port(*self, port)) + .collect(); + + let children = ctx + .hugr + .children(*self) + .map(|child| child.export(ctx)) + .collect(); + + fn make_custom(name: &'static str) -> model::Operation { + model::Operation::Custom(model::operation::Custom { + name: model::Symbol(name.to_smolstr()), + }) + } + + let operation = match ctx.hugr.get_optype(*self) { + OpType::Module(_) => model::Operation::Module, + OpType::Input(_) => model::Operation::Input, + OpType::Output(_) => model::Operation::Output, + OpType::DFG(_) => model::Operation::Dfg, + OpType::CFG(_) => model::Operation::Cfg, + OpType::ExitBlock(_) => model::Operation::Exit, + OpType::Case(_) => model::Operation::Case, + OpType::DataflowBlock(_) => model::Operation::Block, + + OpType::FuncDefn(func) => { + // TODO: If the node is not connected to a function, we should do better than panic. + let name = ctx.get_func_name(*self).unwrap(); + let r#type = Box::new(func.signature.export(ctx)); + model::Operation::DefineFunc(model::operation::DefineFunc { name, r#type }) + } + + OpType::FuncDecl(func) => { + // TODO: If the node is not connected to a function, we should do better than panic. + let name = ctx.get_func_name(*self).unwrap(); + let r#type = Box::new(func.signature.export(ctx)); + model::Operation::DeclareFunc(model::operation::DeclareFunc { name, r#type }) + } + + OpType::AliasDecl(alias) => { + let name = model::Symbol(alias.name().to_smolstr()); + // TODO: We should support aliases with different types + let r#type = ctx.make_term(model::Term::Type); + model::Operation::DeclareAlias(model::operation::DeclareAlias { name, r#type }) + } + + OpType::AliasDefn(alias) => { + let name = model::Symbol(alias.name().to_smolstr()); + let value = alias.definition.export(ctx); + model::Operation::DefineAlias(model::operation::DefineAlias { name, value }) + } + + OpType::Call(call) => { + // TODO: If the node is not connected to a function, we should do better than panic. + let name = ctx.get_func_name(*self).unwrap(); + params.extend(call.type_args.iter().map(|arg| arg.export(ctx))); + model::Operation::CallFunc(model::operation::CallFunc { name }) + } + + OpType::LoadFunction(func) => { + // TODO: If the node is not connected to a function, we should do better than panic. + let name = ctx.get_func_name(*self).unwrap(); + params.extend(func.type_args.iter().map(|arg| arg.export(ctx))); + model::Operation::LoadFunc(model::operation::LoadFunc { name }) + } + + OpType::Const(_) => todo!("Export const nodes?"), + OpType::LoadConstant(_) => todo!("Export load constant?"), + + OpType::CallIndirect(_) => make_custom("core.call-indirect"), + OpType::Noop(_) => make_custom("core.id"), + OpType::MakeTuple(_) => make_custom("core.make-tuple"), + OpType::UnpackTuple(_) => make_custom("core.unpack-tuple"), + OpType::Tag(_) => make_custom("core.make-tagged"), + OpType::Lift(_) => make_custom("core.lift"), + OpType::TailLoop(_) => make_custom("core.tail-loop"), + OpType::Conditional(_) => make_custom("core.cond"), + OpType::ExtensionOp(op) => { + let name = model::Symbol(op.name()); + params.extend(op.args().iter().map(|arg| arg.export(ctx))); + model::Operation::Custom(model::operation::Custom { name }) + } + OpType::OpaqueOp(op) => { + let name = model::Symbol(op.name()); + params.extend(op.args().iter().map(|arg| arg.export(ctx))); + model::Operation::Custom(model::operation::Custom { name }) + } + }; + + ctx.make_node(model::Node { + operation, + params, + inputs, + outputs, + children, + meta: Vec::new(), + }) + } +} + +impl Export for PolyFuncTypeBase { + type Target = model::Scheme; + + fn export(&self, ctx: &mut Context) -> Self::Target { + let params = self + .params() + .iter() + .enumerate() + .map(|(i, param)| model::SchemeParam { + name: model::TermVar(i.to_smolstr()), + r#type: param.export(ctx), + }) + .collect(); + let constraints = TinyVec::new(); + let body = self.body().export(ctx); + model::Scheme { + params, + constraints, + body, + } + } +} + +impl Export for TypeBase { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + self.as_type_enum().export(ctx) + } +} + +impl Export for TypeEnum { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + match self { + TypeEnum::Extension(ext) => ext.export(ctx), + TypeEnum::Alias(alias) => { + let name = model::Symbol(alias.name().to_smolstr()); + let args = TinyVec::new(); + ctx.make_term(model::Term::Named(model::term::Named { name, args })) + } + TypeEnum::Function(func) => func.export(ctx), + TypeEnum::Variable(index, _) => { + // This ignores the type bound for now + ctx.make_term(model::Term::Var(model::TermVar(index.to_smolstr()))) + } + TypeEnum::RowVar(rv) => rv.as_rv().export(ctx), + TypeEnum::Sum(sum) => sum.export(ctx), + } + } +} + +impl Export for FuncTypeBase { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + let inputs = self.input().export(ctx); + let outputs = self.output().export(ctx); + let extensions = self.extension_reqs.export(ctx); + ctx.make_term(model::Term::FuncType(model::term::FuncType { + inputs, + outputs, + extensions, + })) + } +} + +impl Export for CustomType { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + let name = model::Symbol(format!("{}.{}", self.extension(), self.name()).into()); + let args = self.args().iter().map(|arg| arg.export(ctx)).collect(); + ctx.make_term(model::Term::Named(model::term::Named { name, args })) + } +} + +impl Export for TypeArg { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + match self { + TypeArg::Type { ty } => ty.export(ctx), + TypeArg::BoundedNat { n } => ctx.make_term(model::Term::Nat(*n)), + TypeArg::String { arg } => ctx.make_term(model::Term::Str(arg.into())), + TypeArg::Sequence { elems } => { + // For now we assume that the sequence is meant to be a list. + let items = elems.iter().map(|elem| elem.export(ctx)).collect(); + ctx.make_term(model::Term::List(model::term::List { items, tail: None })) + } + TypeArg::Extensions { es } => es.export(ctx), + TypeArg::Variable { v } => v.export(ctx), + } + } +} + +impl Export for TypeArgVariable { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + ctx.make_term(model::Term::Var(model::TermVar(self.index().to_smolstr()))) + } +} + +impl Export for RowVariable { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + ctx.make_term(model::Term::Var(model::TermVar(self.0.to_smolstr()))) + } +} + +impl Export for SumType { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + match self { + SumType::Unit { size } => { + let items = (0..*size) + .map(|_| { + let types = ctx.make_term(model::Term::List(model::term::List { + items: TinyVec::new(), + tail: None, + })); + ctx.make_term(model::Term::ProductType(model::term::ProductType { types })) + }) + .collect(); + let types = + ctx.make_term(model::Term::List(model::term::List { items, tail: None })); + ctx.make_term(model::Term::SumType(model::term::SumType { types })) + } + SumType::General { rows } => { + let items = rows.iter().map(|row| row.export(ctx)).collect(); + let types = + ctx.make_term(model::Term::List(model::term::List { items, tail: None })); + ctx.make_term(model::Term::SumType(model::term::SumType { types })) + } + } + } +} + +impl Export for TypeRowBase { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + let items = self + .as_slice() + .iter() + .map(|item| item.export(ctx)) + .collect(); + ctx.make_term(model::Term::List(model::term::List { items, tail: None })) + } +} + +impl Export for TypeParam { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + match self { + // This ignores the type bound for now. + TypeParam::Type { .. } => ctx.make_term(model::Term::Type), + // This ignores the type bound for now. + TypeParam::BoundedNat { .. } => ctx.make_term(model::Term::NatType), + TypeParam::String => ctx.make_term(model::Term::StrType), + TypeParam::List { param } => { + let item_type = param.export(ctx); + ctx.make_term(model::Term::ListType(model::term::ListType { item_type })) + } + TypeParam::Tuple { params } => { + let items = params.iter().map(|param| param.export(ctx)).collect(); + let types = + ctx.make_term(model::Term::List(model::term::List { items, tail: None })); + ctx.make_term(model::Term::ProductType(model::term::ProductType { types })) + } + TypeParam::Extensions => ctx.make_term(model::Term::ExtSetType), + } + } +} + +impl Export for ExtensionSet { + type Target = model::TermId; + + fn export(&self, ctx: &mut Context) -> Self::Target { + // This ignores that type variables in the extension set are represented + // by converting their index into a string. We should probably have a deeper + // look at how extension sets are represented. + let extensions = self.iter().map(|ext| ext.to_smolstr()).collect(); + ctx.make_term(model::Term::ExtSet(model::term::ExtSet { + extensions, + rest: None, + })) + } +} diff --git a/hugr-core/src/lib.rs b/hugr-core/src/lib.rs index 6bd2a262d..ce16db325 100644 --- a/hugr-core/src/lib.rs +++ b/hugr-core/src/lib.rs @@ -10,6 +10,7 @@ pub mod builder; pub mod core; +pub mod export; pub mod extension; pub mod hugr; pub mod macros; diff --git a/hugr-core/src/types.rs b/hugr-core/src/types.rs index 039c1b6e0..5afab2294 100644 --- a/hugr-core/src/types.rs +++ b/hugr-core/src/types.rs @@ -8,7 +8,7 @@ mod serialize; mod signature; pub mod type_param; pub mod type_row; -use row_var::MaybeRV; +pub(crate) use row_var::MaybeRV; pub use row_var::{NoRV, RowVariable}; pub use crate::ops::constant::{ConstTypeError, CustomCheckFailure}; @@ -16,7 +16,9 @@ use crate::types::type_param::check_type_arg; use crate::utils::display_list_with_separator; pub use check::SumTypeError; pub use custom::CustomType; +pub(crate) use poly_func::PolyFuncTypeBase; pub use poly_func::{PolyFuncType, PolyFuncTypeRV}; +pub(crate) use signature::FuncTypeBase; pub use signature::{FuncValueType, Signature}; use smol_str::SmolStr; pub use type_param::TypeArg; diff --git a/hugr-model/Cargo.toml b/hugr-model/Cargo.toml new file mode 100644 index 000000000..4f95b9e48 --- /dev/null +++ b/hugr-model/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "hugr-model" +version = "0.1.0" +rust-version.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +smol_str.workspace = true +tinyvec.workspace = true + +[lints] +workspace = true diff --git a/hugr-model/src/lib.rs b/hugr-model/src/lib.rs new file mode 100644 index 000000000..2713d22b1 --- /dev/null +++ b/hugr-model/src/lib.rs @@ -0,0 +1,2 @@ +//! The data structures for the HUGR IR. +pub mod v0; diff --git a/hugr-model/src/v0/mod.rs b/hugr-model/src/v0/mod.rs new file mode 100644 index 000000000..ca94e6945 --- /dev/null +++ b/hugr-model/src/v0/mod.rs @@ -0,0 +1,347 @@ +//! Version 0. +use smol_str::SmolStr; +use tinyvec::TinyVec; + +/// Index of a node in a hugr graph. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct NodeId(pub u32); + +/// Index of a port in a hugr graph. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct PortId(pub u32); + +/// Index of a term in a hugr graph. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct TermId(pub u32); + +/// An identifier referring to types, terms, or functions. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Symbol(pub SmolStr); + +/// A local variable in terms. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TermVar(pub SmolStr); + +/// The name of an edge. +/// +/// This is to be used in the textual representation of the graph +/// to indicate that two ports are connected by assigning them the same +/// edge variable. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct EdgeVar(pub SmolStr); + +/// A module consisting of a hugr graph together with terms. +#[derive(Debug, Clone, Default)] +pub struct Module { + /// Table of [`Node`]s. + pub nodes: Vec, + /// Table of [`Port`]s. + pub ports: Vec, + /// Table of [`Edge`]s. + pub edges: Vec, + /// Table of [`Term`]s. + pub terms: Vec, +} + +/// Nodes in the hugr graph. +#[derive(Debug, Clone)] +pub struct Node { + /// The operation that the node performs. + pub operation: Operation, + /// Parameters that are passed to the operation. + pub params: TinyVec<[TermId; 3]>, + /// The input ports of the node. + pub inputs: TinyVec<[PortId; 3]>, + /// The output ports of the node. + pub outputs: TinyVec<[PortId; 3]>, + /// The children of the node. + pub children: TinyVec<[NodeId; 3]>, + /// Metadata attached to the node. + pub meta: Vec, +} + +/// Operations that nodes can perform. +#[derive(Debug, Clone)] +pub enum Operation { + /// Root node of the hugr graph. + Module, + /// Internal inputs of the parent node. + Input, + /// Internal outputs of the parent node. + Output, + /// Data flow graphs. + Dfg, + /// Control flow graphs. + Cfg, + /// Basic blocks. + Block, + /// The exit node of a control flow graph. + Exit, + /// Cases in a conditional node. + Case, + /// Function definitions. + DefineFunc(operation::DefineFunc), + /// Function declarations. + DeclareFunc(operation::DeclareFunc), + /// Function calls. + CallFunc(operation::CallFunc), + /// Function loads. + LoadFunc(operation::LoadFunc), + /// Custom operations. + Custom(operation::Custom), + /// Alias definitions. + DefineAlias(operation::DefineAlias), + /// Alias declarations. + DeclareAlias(operation::DeclareAlias), +} + +/// The variants for [`Operation`]. +pub mod operation { + #[allow(unused_imports)] + use super::Operation; + use super::{Scheme, Symbol, TermId}; + + /// See [`Operation::DefineFunc`]. + #[derive(Debug, Clone)] + pub struct DefineFunc { + /// The name of the function to be defined. + pub name: Symbol, + /// The type scheme of the function. + pub r#type: Box, + } + + /// See [`Operation::DeclareFunc`]. + #[derive(Debug, Clone)] + pub struct DeclareFunc { + /// The name of the function to be declared. + pub name: Symbol, + /// The type scheme of the function. + pub r#type: Box, + } + + /// See [`Operation::CallFunc`]. + #[derive(Debug, Clone)] + pub struct CallFunc { + /// The name of the function to be called. + pub name: Symbol, + } + + /// See [`Operation::LoadFunc`]. + #[derive(Debug, Clone)] + pub struct LoadFunc { + /// The name of the function to be loaded. + pub name: Symbol, + } + + /// See [`Operation::Custom`]. + #[derive(Debug, Clone)] + pub struct Custom { + /// The name of the custom operation. + pub name: Symbol, + } + + /// See [`Operation::DefineAlias`]. + #[derive(Debug, Clone)] + pub struct DefineAlias { + /// The name of the alias to be defined. + pub name: Symbol, + /// The value of the alias. + pub value: TermId, + } + + /// See [`Operation::DeclareAlias`]. + #[derive(Debug, Clone)] + pub struct DeclareAlias { + /// The name of the alias to be declared. + pub name: Symbol, + /// The type of the alias. + pub r#type: TermId, + } +} + +/// A metadata item. +#[derive(Debug, Clone)] +pub struct MetaItem { + /// Name of the metadata item. + pub name: SmolStr, + /// Value of the metadata item. + pub value: Term, +} + +/// A port in the graph. +#[derive(Debug, Clone)] +pub struct Port { + /// The type of the port. + /// + /// This must be a term of type `Type`. + /// If the type is unknown, this will be a wildcard term. + pub r#type: TermId, + + /// Metadata attached to the port. + pub meta: Vec, +} + +/// An edge in the graph. +/// +/// An edge connects input ports to output ports. +/// A port may only be part of at most one edge. +#[derive(Debug, Clone, Default)] +pub struct Edge { + /// The input ports of the edge. + pub inputs: TinyVec<[PortId; 3]>, + /// The output ports of the edge. + pub outputs: TinyVec<[PortId; 3]>, +} + +/// Parameterized terms. +#[derive(Debug, Clone)] +pub struct Scheme { + /// The named parameters of the scheme. + /// + /// Within any term, the previous terms of the parameter list are available as variables. + pub params: Vec, + /// Constraints on the parameters of the scheme. + /// + /// All parameters are available as variables within the constraints. + pub constraints: TinyVec<[TermId; 3]>, + /// The body of the scheme. + /// + /// All parameters are available as variables within the body. + pub body: TermId, +} + +/// A named parameter of a scheme. +#[derive(Debug, Clone)] +pub struct SchemeParam { + /// The name of the parameter. + pub name: TermVar, + /// The type of the parameter. + pub r#type: TermId, +} + +/// A term in the compile time meta language. +#[derive(Debug, Clone)] +pub enum Term { + /// The type of types. + Type, + /// Standin for any term. + Wildcard, + /// A variable. + Var(TermVar), + /// A symbolic function application. + Named(term::Named), + /// A list, with an optional tail. + List(term::List), + /// The type of lists, given a type for the items. + ListType(term::ListType), + /// A string. + Str(SmolStr), + /// The type of strings. + StrType, + /// A natural number. + Nat(u64), + /// The type of natural numbers. + NatType, + /// Extension set. + ExtSet(term::ExtSet), + /// The type of extension sets. + ExtSetType, + /// A tuple of values. + Tuple(term::Tuple), + /// A product type, given a list of types for the fields. + ProductType(term::ProductType), + /// A variant of a sum type, given a tag and its value. + Tagged(term::Tagged), + /// A sum type, given a list of variants. + SumType(term::SumType), + /// The type of functions, given lists of input and output types and an extension set. + FuncType(term::FuncType), +} + +/// The variants for [`Term`]. +pub mod term { + use super::{Symbol, TermId}; + use smol_str::SmolStr; + use tinyvec::TinyVec; + + /// Named terms. + #[derive(Debug, Clone)] + pub struct Named { + /// The name of the term. + pub name: Symbol, + /// Arguments to the term. + pub args: TinyVec<[TermId; 3]>, + } + + /// A homogeneous list of terms. + #[derive(Debug, Clone)] + pub struct List { + /// The items that are contained in the list. + pub items: TinyVec<[TermId; 3]>, + /// Optionally, a term that represents the remainder of the list. + pub tail: Option, + } + + /// The type of a list of terms. + #[derive(Debug, Clone)] + pub struct ListType { + /// The type of the items contained in the list. + pub item_type: TermId, + } + + /// A heterogeneous list of terms. + #[derive(Debug, Clone)] + pub struct Tuple { + /// The items that are contained in the tuple. + pub items: TinyVec<[TermId; 3]>, + } + + /// A product type. + #[derive(Debug, Clone)] + pub struct ProductType { + /// The types that are contained in the product type. + pub types: TermId, + } + + /// Function type. + #[derive(Debug, Clone)] + pub struct FuncType { + /// The type of the inputs to the function. + /// + /// This must be a list of types. + pub inputs: TermId, + /// The type of the outputs of the function. + /// + /// This must be a list of types. + pub outputs: TermId, + /// The extensions that are required to run the function. + /// + /// This must be an extension set. + pub extensions: TermId, + } + + /// Sum type. + #[derive(Debug, Clone)] + pub struct SumType { + /// The types of the variants in the sum. + pub types: TermId, + } + + /// Tagged term. + #[derive(Debug, Clone)] + pub struct Tagged { + /// The tag of the tagged term. + pub tag: u8, + /// The term that is tagged. + pub term: TermId, + } + + /// Extension set. + #[derive(Debug, Clone, Default)] + pub struct ExtSet { + /// The extensions that are contained in the extension set. + pub extensions: Vec, + /// The rest of the extension set. + pub rest: Option, + } +}