use std::sync::Arc;
use crate::bytecode::Value;
use super::fault::Fault;
pub type NativeResult = Result<Value, Fault>;
pub type NativeFn = Arc<dyn Fn(&[Value]) -> NativeResult + Send + Sync>;
pub struct NativeTable {
entries: Vec<Option<(String, NativeFn)>>,
}
impl NativeTable {
pub fn builder() -> NativeTableBuilder {
NativeTableBuilder {
entries: Vec::new(),
}
}
pub fn empty() -> Arc<NativeTable> {
Arc::new(NativeTable {
entries: Vec::new(),
})
}
#[inline]
pub fn get(&self, index: u32) -> Option<&NativeFn> {
self.entries
.get(index as usize)
.and_then(|slot| slot.as_ref())
.map(|(_, f)| f)
}
pub fn index_of(&self, name: &str) -> Option<u32> {
self.entries
.iter()
.enumerate()
.find(|(_, slot)| slot.as_ref().is_some_and(|(n, _)| n == name))
.map(|(i, _)| i as u32)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.entries
.iter()
.filter_map(|slot| slot.as_ref().map(|(n, _)| n.as_str()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NativeTableError {
DuplicateName(String),
SlotOccupied { index: u32, name: String },
}
impl std::fmt::Display for NativeTableError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NativeTableError::DuplicateName(name) => {
write!(f, "duplicate native function registered: '{name}'")
}
NativeTableError::SlotOccupied { index, name } => {
write!(f, "native slot {index} already occupied (registering '{name}')")
}
}
}
}
impl std::error::Error for NativeTableError {}
pub struct NativeTableBuilder {
entries: Vec<Option<(String, NativeFn)>>,
}
impl NativeTableBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn register<F>(self, name: impl Into<String>, f: F) -> Result<Self, NativeTableError>
where
F: Fn(&[Value]) -> NativeResult + Send + Sync + 'static,
{
let index = self.entries.len() as u32;
self.register_at(index, name, f)
}
pub fn register_at<F>(
mut self,
index: u32,
name: impl Into<String>,
f: F,
) -> Result<Self, NativeTableError>
where
F: Fn(&[Value]) -> NativeResult + Send + Sync + 'static,
{
let name = name.into();
for (n, _) in self.entries.iter().flatten() {
if n == &name {
return Err(NativeTableError::DuplicateName(name));
}
}
let index_usize = index as usize;
if index_usize >= self.entries.len() {
self.entries.resize_with(index_usize + 1, || None);
}
if self.entries[index_usize].is_some() {
return Err(NativeTableError::SlotOccupied { index, name });
}
self.entries[index_usize] = Some((name, Arc::new(f)));
Ok(self)
}
pub fn build(self) -> Arc<NativeTable> {
Arc::new(NativeTable {
entries: self.entries,
})
}
}
impl Default for NativeTableBuilder {
fn default() -> Self {
Self::new()
}
}
pub fn expect_arg<'a>(
args: &'a [Value],
index: usize,
fn_name: &str,
) -> Result<&'a Value, Fault> {
args.get(index).ok_or(Fault::NativeError(format!(
"{fn_name}: missing argument {index}"
)))
}
pub fn expect_int(args: &[Value], index: usize, fn_name: &str) -> Result<i64, Fault> {
expect_arg(args, index, fn_name)?
.as_int()
.ok_or(Fault::NativeError(format!(
"{fn_name}: argument {index} is not an int"
)))
}
pub fn expect_bool(args: &[Value], index: usize, fn_name: &str) -> Result<bool, Fault> {
match expect_arg(args, index, fn_name)? {
Value::Bool(b) => Ok(*b),
Value::Int(i) => Ok(*i != 0),
other => Err(Fault::NativeError(format!(
"{fn_name}: argument {index} is not a bool/int (got {})",
other.type_name()
))),
}
}
pub fn expect_message(
args: &[Value],
index: usize,
fn_name: &str,
) -> Result<crate::Message, Fault> {
expect_arg(args, index, fn_name)?
.as_message()
.ok_or(Fault::NativeError(format!(
"{fn_name}: argument {index} is not a message"
)))
}
pub fn expect_u64(args: &[Value], index: usize, fn_name: &str) -> Result<u64, Fault> {
match expect_arg(args, index, fn_name)? {
Value::Pid(p) => Ok(*p),
Value::Cap(c) => Ok(*c),
Value::Int(i) if *i >= 0 => Ok(*i as u64),
Value::Bool(b) => Ok(u64::from(*b)),
other => Err(Fault::NativeError(format!(
"{fn_name}: argument {index} is not a non-negative int/pid/cap (got {})",
other.type_name()
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn register_at_leaves_holes_as_none() -> Result<(), Box<dyn std::error::Error>> {
let table = NativeTable::builder()
.register_at(10, "answer", |_| Ok(Value::Int(42)))?
.build();
assert_eq!(table.len(), 11);
assert_eq!(table.index_of("answer"), Some(10));
let f = table.get(10).ok_or("missing native")?;
assert!(matches!(f(&[])?, Value::Int(42)));
assert!(table.get(2).is_none());
Ok(())
}
#[test]
fn register_at_errors_on_duplicate_slot() {
let result = NativeTable::builder()
.register_at(3, "a", |_| Ok(Value::Unit))
.and_then(|b| b.register_at(3, "b", |_| Ok(Value::Unit)));
assert!(matches!(
result,
Err(NativeTableError::SlotOccupied { index: 3, .. })
));
}
#[test]
fn register_errors_on_duplicate_name() {
let result = NativeTable::builder()
.register("x", |_| Ok(Value::Unit))
.and_then(|b| b.register("x", |_| Ok(Value::Unit)));
assert!(matches!(result, Err(NativeTableError::DuplicateName(_))));
}
}