use std::{
fmt::Debug,
marker::PhantomData,
sync::{
Arc,
atomic::{AtomicU32, Ordering},
},
};
use sim_kernel::{
Args, Callable, Class, ClassId, ClassRef, Cx, DefaultFactory, Expr, Factory, Object,
ObjectEncode, ObjectEncoding, ReadConstructor, ReadConstructorRef, Result, ShapeRef, Symbol,
TableRef, Value,
};
use crate::{arity_error, parse_symbol};
pub trait Citizen: Clone + Send + Sync + 'static {
fn citizen_symbol() -> Symbol;
fn citizen_version() -> u32;
fn citizen_arity() -> usize;
fn citizen_fields() -> &'static [&'static str];
}
pub trait CitizenRuntime:
Citizen + Object + sim_kernel::ObjectCompat + ObjectEncode + PartialEq + Debug
{
fn construct_from_values(cx: &mut Cx, args: Vec<Value>) -> Result<Self>;
fn example() -> Self;
}
pub fn install_derived<T>(linker: &mut sim_kernel::Linker<'_>) -> Result<()>
where
T: CitizenRuntime,
{
let class = Arc::new(DerivedCitizenClass::<T>::new());
let id = linker.class_value(
T::citizen_symbol(),
DefaultFactory
.opaque(class.clone())
.expect("citizen class should be boxable"),
)?;
class.set_id(id);
Ok(())
}
pub fn constructor_expr<T>(cx: &mut Cx, value: &T) -> Result<Expr>
where
T: Citizen + ObjectEncode,
{
match value.object_encoding(cx)? {
ObjectEncoding::Constructor { class, args } => Ok(Expr::Extension {
tag: Symbol::qualified("citizen", "read-construct"),
payload: Box::new(Expr::Vector(
std::iter::once(Expr::Symbol(class)).chain(args).collect(),
)),
}),
ObjectEncoding::TaggedData { tag, fields } => Ok(Expr::Extension {
tag,
payload: Box::new(Expr::Map(
fields
.into_iter()
.map(|(key, value)| (Expr::Symbol(key), value))
.collect(),
)),
}),
ObjectEncoding::Opaque { class, stable_id } => Ok(Expr::Extension {
tag: class,
payload: Box::new(Expr::String(stable_id)),
}),
}
}
pub struct DerivedCitizenClass<T> {
id: AtomicU32,
marker: PhantomData<T>,
}
impl<T> DerivedCitizenClass<T> {
fn new() -> Self {
Self {
id: AtomicU32::new(0),
marker: PhantomData,
}
}
fn set_id(&self, id: ClassId) {
self.id.store(id.0, Ordering::Relaxed);
}
}
impl<T> Object for DerivedCitizenClass<T>
where
T: CitizenRuntime,
{
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("#<class {}>", T::citizen_symbol()))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl<T> sim_kernel::ObjectCompat for DerivedCitizenClass<T>
where
T: CitizenRuntime,
{
fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
if let Some(value) = cx
.registry()
.class_by_symbol(&Symbol::qualified("core", "Class"))
{
return Ok(value.clone());
}
cx.factory().class_stub(
sim_kernel::CORE_CLASS_CLASS_ID,
Symbol::qualified("core", "Class"),
)
}
fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
Ok(Expr::Symbol(T::citizen_symbol()))
}
fn as_callable(&self) -> Option<&dyn Callable> {
Some(self)
}
fn as_class(&self) -> Option<&dyn Class> {
Some(self)
}
fn as_read_constructor(&self) -> Option<&dyn ReadConstructor> {
Some(self)
}
}
impl<T> Callable for DerivedCitizenClass<T>
where
T: CitizenRuntime,
{
fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
let value = T::construct_from_values(cx, args.into_vec())?;
cx.factory().opaque(Arc::new(value))
}
}
impl<T> Class for DerivedCitizenClass<T>
where
T: CitizenRuntime,
{
fn id(&self) -> ClassId {
ClassId(self.id.load(Ordering::Relaxed))
}
fn symbol(&self) -> Symbol {
T::citizen_symbol()
}
fn constructor_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
cx.factory().nil()
}
fn instance_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
cx.factory().nil()
}
fn read_constructor(&self, cx: &mut Cx) -> Result<Option<ReadConstructorRef>> {
Ok(cx.registry().class_by_symbol(&T::citizen_symbol()).cloned())
}
fn members(&self, cx: &mut Cx) -> Result<TableRef> {
let fields = T::citizen_fields()
.iter()
.map(|field| cx.factory().symbol(Symbol::new((*field).to_owned())))
.collect::<Result<Vec<_>>>()?;
cx.factory().table(vec![
(
Symbol::new("version"),
cx.factory().number_literal(
parse_symbol("citizen/int"),
T::citizen_version().to_string(),
)?,
),
(Symbol::new("fields"), cx.factory().list(fields)?),
])
}
}
impl<T> ReadConstructor for DerivedCitizenClass<T>
where
T: CitizenRuntime,
{
fn symbol(&self) -> Symbol {
T::citizen_symbol()
}
fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
cx.factory().nil()
}
fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
if args.len() != T::citizen_arity() + 1 {
return Err(arity_error(
T::citizen_symbol(),
T::citizen_arity() + 1,
args.len(),
));
}
self.call(cx, Args::new(args))
}
}