use std::{
collections::BTreeMap,
sync::{Arc, OnceLock},
};
use sim_kernel::{
Args, Callable, Cx, Error, Expr, Object, ObjectCompat, Result, Shape, Symbol, Value,
};
use crate::{AdtShape, VariantShape};
#[derive(Clone)]
pub struct PatternField {
name: Symbol,
shape: Arc<dyn Shape>,
}
impl PatternField {
pub fn new(name: Symbol, shape: Arc<dyn Shape>) -> Self {
Self { name, shape }
}
pub fn name(&self) -> &Symbol {
&self.name
}
pub fn shape(&self) -> &Arc<dyn Shape> {
&self.shape
}
}
#[derive(Clone)]
pub struct VariantDeclaration {
symbol: Symbol,
fields: Vec<PatternField>,
}
impl VariantDeclaration {
pub fn new(symbol: Symbol, fields: Vec<PatternField>) -> Self {
Self { symbol, fields }
}
pub fn nullary(symbol: Symbol) -> Self {
Self {
symbol,
fields: Vec::new(),
}
}
pub fn symbol(&self) -> &Symbol {
&self.symbol
}
pub fn fields(&self) -> &[PatternField] {
&self.fields
}
}
#[derive(Clone)]
pub struct AlgebraicDataType {
symbol: Symbol,
variants: BTreeMap<Symbol, VariantDeclaration>,
}
impl AlgebraicDataType {
pub fn new(symbol: Symbol, variants: Vec<VariantDeclaration>) -> Result<Self> {
let mut by_symbol = BTreeMap::new();
for variant in variants {
if by_symbol
.insert(variant.symbol().clone(), variant.clone())
.is_some()
{
return Err(Error::Eval(format!(
"duplicate ADT variant {}",
variant.symbol()
)));
}
}
Ok(Self {
symbol,
variants: by_symbol,
})
}
pub fn symbol(&self) -> &Symbol {
&self.symbol
}
pub fn variants(&self) -> impl Iterator<Item = &VariantDeclaration> {
self.variants.values()
}
pub fn constructor(&self, variant: &Symbol) -> Option<VariantConstructor> {
self.variants
.get(variant)
.cloned()
.map(|variant| VariantConstructor::new(self.symbol.clone(), variant))
}
pub fn constructors(&self) -> Vec<VariantConstructor> {
self.variants
.values()
.cloned()
.map(|variant| VariantConstructor::new(self.symbol.clone(), variant))
.collect()
}
pub fn shape(&self) -> Arc<dyn Shape> {
Arc::new(AdtShape::new(
self.symbol.clone(),
self.constructors()
.into_iter()
.map(|constructor| constructor.variant_shape())
.collect(),
))
}
}
#[derive(Clone)]
pub struct VariantConstructor {
adt: Symbol,
variant: VariantDeclaration,
}
impl VariantConstructor {
pub fn new(adt: Symbol, variant: VariantDeclaration) -> Self {
Self { adt, variant }
}
pub fn adt(&self) -> &Symbol {
&self.adt
}
pub fn variant(&self) -> &Symbol {
self.variant.symbol()
}
pub fn fields(&self) -> &[PatternField] {
self.variant.fields()
}
pub fn variant_shape(&self) -> VariantShape {
VariantShape::new(
self.adt.clone(),
self.variant.symbol().clone(),
self.variant.fields().to_vec(),
)
}
pub fn shape(&self) -> Arc<dyn Shape> {
Arc::new(self.variant_shape())
}
pub fn construct(&self, cx: &mut Cx, fields: Vec<Value>) -> Result<Value> {
if fields.len() != self.variant.fields().len() {
return Err(Error::Eval(format!(
"constructor {} expected {} fields, got {}",
self.variant.symbol(),
self.variant.fields().len(),
fields.len()
)));
}
for (field, value) in self.variant.fields().iter().zip(fields.iter()) {
let matched = field.shape().check_value(cx, value.clone())?;
if !matched.accepted {
return Err(Error::Eval(format!(
"constructor {} rejected field {}: {}",
self.variant.symbol(),
field.name(),
diagnostic_summary(&matched.diagnostics)
)));
}
}
let fields = self
.variant
.fields()
.iter()
.map(|field| field.name().clone())
.zip(fields)
.collect();
cx.factory().opaque(Arc::new(TaggedValue::new(
self.adt.clone(),
self.variant.symbol().clone(),
fields,
)))
}
pub fn as_value(&self, cx: &mut Cx) -> Result<Value> {
cx.factory().opaque(Arc::new(self.clone()))
}
}
impl Object for VariantConstructor {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("#<constructor {}>", self.variant.symbol()))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ObjectCompat for VariantConstructor {
fn as_callable(&self) -> Option<&dyn Callable> {
Some(self)
}
fn as_table(&self, cx: &mut Cx) -> Result<Value> {
cx.factory().table(vec![
(Symbol::new("adt"), cx.factory().symbol(self.adt().clone())?),
(
Symbol::new("variant"),
cx.factory().symbol(self.variant().clone())?,
),
(
Symbol::new("arity"),
cx.factory().number_literal(
Symbol::qualified("numbers", "f64"),
self.fields().len().to_string(),
)?,
),
])
}
}
impl Callable for VariantConstructor {
fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
self.construct(cx, args.into_vec())
}
}
#[sim_citizen_derive::non_citizen(
reason = "dynamic ADT variant value; canonical data is the variant symbol and field table",
kind = "marker"
)]
#[derive(Clone)]
pub struct TaggedValue {
adt: Symbol,
variant: Symbol,
fields: Vec<(Symbol, Value)>,
header: OnceLock<sim_kernel::ObjectHeader>,
}
impl TaggedValue {
pub fn new(adt: Symbol, variant: Symbol, fields: Vec<(Symbol, Value)>) -> Self {
Self {
adt,
variant,
fields,
header: OnceLock::new(),
}
}
pub fn adt(&self) -> &Symbol {
&self.adt
}
pub fn variant(&self) -> &Symbol {
&self.variant
}
pub fn fields(&self) -> &[(Symbol, Value)] {
&self.fields
}
pub fn field(&self, name: &Symbol) -> Option<&Value> {
self.fields
.iter()
.find_map(|(field, value)| (field == name).then_some(value))
}
}
impl Object for TaggedValue {
fn header(&self) -> &sim_kernel::ObjectHeader {
self.header.get_or_init(|| sim_kernel::ObjectHeader {
id: sim_kernel::Ref::Symbol(self.variant.clone()),
kind: Symbol::qualified("pattern", "tagged-value"),
trust: sim_kernel::TrustLevel::HostInternal,
})
}
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("#<{} {}>", self.adt, self.variant))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ObjectCompat for TaggedValue {
fn as_table(&self, cx: &mut Cx) -> Result<Value> {
let fields = self
.fields
.iter()
.map(|(name, value)| (name.clone(), value.clone()))
.collect();
let fields = cx.factory().table(fields)?;
cx.factory().table(vec![
(Symbol::new("adt"), cx.factory().symbol(self.adt.clone())?),
(
Symbol::new("variant"),
cx.factory().symbol(self.variant.clone())?,
),
(Symbol::new("fields"), fields),
])
}
fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
let args = self
.fields
.iter()
.map(|(_, value)| value.object().as_expr(cx))
.collect::<Result<Vec<_>>>()?;
Ok(Expr::Call {
operator: Box::new(Expr::Symbol(self.variant.clone())),
args,
})
}
}
pub fn tagged_value(value: &Value) -> Option<&TaggedValue> {
value.object().downcast_ref::<TaggedValue>()
}
fn diagnostic_summary(diagnostics: &[sim_kernel::Diagnostic]) -> String {
diagnostics
.first()
.map(|diagnostic| diagnostic.message.clone())
.unwrap_or_else(|| "field shape rejected value".to_owned())
}