use std::sync::Arc;
use harn_vm::{Vm, VmError, VmValue};
use crate::error::HostlibError;
pub type SyncHandler = Arc<dyn Fn(&[VmValue]) -> Result<VmValue, HostlibError> + Send + Sync>;
#[derive(Clone)]
pub struct RegisteredBuiltin {
pub name: &'static str,
pub module: &'static str,
pub method: &'static str,
pub handler: SyncHandler,
}
impl std::fmt::Debug for RegisteredBuiltin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RegisteredBuiltin")
.field("name", &self.name)
.field("module", &self.module)
.field("method", &self.method)
.finish()
}
}
#[derive(Default)]
pub struct BuiltinRegistry {
builtins: Vec<RegisteredBuiltin>,
}
impl BuiltinRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, builtin: RegisteredBuiltin) {
self.builtins.push(builtin);
}
pub fn register_unimplemented(
&mut self,
name: &'static str,
module: &'static str,
method: &'static str,
) {
let handler: SyncHandler =
Arc::new(move |_args| Err(HostlibError::Unimplemented { builtin: name }));
self.register(RegisteredBuiltin {
name,
module,
method,
handler,
});
}
pub fn iter(&self) -> impl Iterator<Item = &RegisteredBuiltin> {
self.builtins.iter()
}
pub fn len(&self) -> usize {
self.builtins.len()
}
pub fn is_empty(&self) -> bool {
self.builtins.is_empty()
}
pub fn find(&self, name: &str) -> Option<&RegisteredBuiltin> {
self.builtins.iter().find(|b| b.name == name)
}
}
pub trait HostlibCapability: 'static {
fn module_name(&self) -> &'static str;
fn register_builtins(&self, registry: &mut BuiltinRegistry);
}
pub struct HostlibRegistry {
builtins: BuiltinRegistry,
modules: Vec<&'static str>,
}
impl Default for HostlibRegistry {
fn default() -> Self {
Self::new()
}
}
impl HostlibRegistry {
pub fn new() -> Self {
Self {
builtins: BuiltinRegistry::new(),
modules: Vec::new(),
}
}
#[must_use]
pub fn with<C: HostlibCapability>(mut self, capability: C) -> Self {
let module = capability.module_name();
capability.register_builtins(&mut self.builtins);
self.modules.push(module);
self
}
pub fn register_into_vm(&mut self, vm: &mut Vm) {
for builtin in self.builtins.iter().cloned().collect::<Vec<_>>() {
let handler = builtin.handler.clone();
vm.register_builtin(
builtin.name,
move |args, _out| -> Result<VmValue, VmError> {
handler(args).map_err(VmError::from)
},
);
}
}
pub fn builtins(&self) -> &BuiltinRegistry {
&self.builtins
}
pub fn modules(&self) -> &[&'static str] {
&self.modules
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unimplemented_builtins_route_through_error() {
let mut registry = BuiltinRegistry::new();
registry.register_unimplemented("hostlib_demo", "demo", "ping");
let entry = registry.find("hostlib_demo").expect("registered");
let err = (entry.handler)(&[]).expect_err("should be unimplemented");
assert!(
matches!(err, HostlibError::Unimplemented { builtin } if builtin == "hostlib_demo")
);
}
#[test]
fn registry_records_modules_in_order() {
struct First;
impl HostlibCapability for First {
fn module_name(&self) -> &'static str {
"first"
}
fn register_builtins(&self, _registry: &mut BuiltinRegistry) {}
}
struct Second;
impl HostlibCapability for Second {
fn module_name(&self) -> &'static str {
"second"
}
fn register_builtins(&self, _registry: &mut BuiltinRegistry) {}
}
let registry = HostlibRegistry::new().with(First).with(Second);
assert_eq!(registry.modules(), &["first", "second"]);
}
}