use std::sync::Arc;
use crate::sort::SortExpr;
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
)]
pub enum Implicit {
#[default]
No,
Yes,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Operation {
pub name: Arc<str>,
pub inputs: Vec<(Arc<str>, SortExpr, Implicit)>,
pub output: SortExpr,
}
impl Operation {
#[must_use]
pub fn new(
name: impl Into<Arc<str>>,
inputs: Vec<(Arc<str>, SortExpr)>,
output: impl Into<SortExpr>,
) -> Self {
Self {
name: name.into(),
inputs: inputs
.into_iter()
.map(|(n, s)| (n, s, Implicit::No))
.collect(),
output: output.into(),
}
}
#[must_use]
pub fn with_implicit(
name: impl Into<Arc<str>>,
inputs: Vec<(Arc<str>, SortExpr, Implicit)>,
output: impl Into<SortExpr>,
) -> Self {
Self {
name: name.into(),
inputs,
output: output.into(),
}
}
#[must_use]
pub fn unary(
name: impl Into<Arc<str>>,
input_name: impl Into<Arc<str>>,
input_sort: impl Into<SortExpr>,
output: impl Into<SortExpr>,
) -> Self {
Self {
name: name.into(),
inputs: vec![(input_name.into(), input_sort.into(), Implicit::No)],
output: output.into(),
}
}
#[must_use]
pub fn nullary(name: impl Into<Arc<str>>, output: impl Into<SortExpr>) -> Self {
Self {
name: name.into(),
inputs: Vec::new(),
output: output.into(),
}
}
#[must_use]
pub fn arity(&self) -> usize {
self.inputs.len()
}
#[must_use]
pub fn explicit_arity(&self) -> usize {
self.inputs
.iter()
.filter(|(_, _, imp)| matches!(imp, Implicit::No))
.count()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eq::Term;
#[test]
fn unary_op() {
let op = Operation::unary("src", "e", "Edge", "Vertex");
assert_eq!(op.arity(), 1);
assert_eq!(&**op.output.head(), "Vertex");
}
#[test]
fn nullary_op() {
let op = Operation::nullary("zero", "Int");
assert_eq!(op.arity(), 0);
assert_eq!(&**op.output.head(), "Int");
}
#[test]
fn binary_op() {
let op = Operation::new(
"add",
vec![
(Arc::from("a"), SortExpr::from("Int")),
(Arc::from("b"), SortExpr::from("Int")),
],
"Int",
);
assert_eq!(op.arity(), 2);
assert_eq!(op.explicit_arity(), 2);
assert!(
op.inputs
.iter()
.all(|(_, _, imp)| matches!(imp, Implicit::No))
);
}
#[test]
fn with_implicit_inputs_preserves_tags() {
let op = Operation::with_implicit(
"app",
vec![
(Arc::from("a"), SortExpr::from("Ty"), Implicit::Yes),
(Arc::from("b"), SortExpr::from("Ty"), Implicit::Yes),
(
Arc::from("f"),
SortExpr::App {
name: Arc::from("arrow"),
args: vec![Term::var("a"), Term::var("b")],
},
Implicit::No,
),
],
SortExpr::Name(Arc::from("Ty")),
);
assert_eq!(op.arity(), 3);
assert_eq!(op.explicit_arity(), 1);
}
#[test]
fn dependent_output() {
let op = Operation::unary(
"id",
"x",
"Ob",
SortExpr::App {
name: Arc::from("Hom"),
args: vec![Term::var("x"), Term::var("x")],
},
);
assert_eq!(&**op.output.head(), "Hom");
assert_eq!(op.output.args().len(), 2);
}
}