1use std::sync::Arc;
10
11use crate::bytecode::Value;
12
13use super::fault::Fault;
14
15pub type NativeResult = Result<Value, Fault>;
17
18pub type NativeFn = Arc<dyn Fn(&[Value]) -> NativeResult + Send + Sync>;
20
21pub struct NativeTable {
24 entries: Vec<Option<(String, NativeFn)>>,
25}
26
27impl NativeTable {
28 pub fn builder() -> NativeTableBuilder {
29 NativeTableBuilder {
30 entries: Vec::new(),
31 }
32 }
33
34 pub fn empty() -> Arc<NativeTable> {
36 Arc::new(NativeTable {
37 entries: Vec::new(),
38 })
39 }
40
41 #[inline]
42 pub fn get(&self, index: u32) -> Option<&NativeFn> {
43 self.entries
44 .get(index as usize)
45 .and_then(|slot| slot.as_ref())
46 .map(|(_, f)| f)
47 }
48
49 pub fn index_of(&self, name: &str) -> Option<u32> {
50 self.entries
51 .iter()
52 .enumerate()
53 .find(|(_, slot)| slot.as_ref().is_some_and(|(n, _)| n == name))
54 .map(|(i, _)| i as u32)
55 }
56
57 pub fn len(&self) -> usize {
60 self.entries.len()
61 }
62
63 pub fn is_empty(&self) -> bool {
64 self.entries.is_empty()
65 }
66
67 pub fn names(&self) -> impl Iterator<Item = &str> {
68 self.entries
69 .iter()
70 .filter_map(|slot| slot.as_ref().map(|(n, _)| n.as_str()))
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum NativeTableError {
80 DuplicateName(String),
81 SlotOccupied { index: u32, name: String },
82}
83
84impl std::fmt::Display for NativeTableError {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self {
87 NativeTableError::DuplicateName(name) => {
88 write!(f, "duplicate native function registered: '{name}'")
89 }
90 NativeTableError::SlotOccupied { index, name } => {
91 write!(f, "native slot {index} already occupied (registering '{name}')")
92 }
93 }
94 }
95}
96
97impl std::error::Error for NativeTableError {}
98
99pub struct NativeTableBuilder {
104 entries: Vec<Option<(String, NativeFn)>>,
105}
106
107impl NativeTableBuilder {
108 pub fn new() -> Self {
109 Self {
110 entries: Vec::new(),
111 }
112 }
113
114 pub fn register<F>(self, name: impl Into<String>, f: F) -> Result<Self, NativeTableError>
116 where
117 F: Fn(&[Value]) -> NativeResult + Send + Sync + 'static,
118 {
119 let index = self.entries.len() as u32;
120 self.register_at(index, name, f)
121 }
122
123 pub fn register_at<F>(
126 mut self,
127 index: u32,
128 name: impl Into<String>,
129 f: F,
130 ) -> Result<Self, NativeTableError>
131 where
132 F: Fn(&[Value]) -> NativeResult + Send + Sync + 'static,
133 {
134 let name = name.into();
135 for slot in &self.entries {
136 if let Some((n, _)) = slot {
137 if n == &name {
138 return Err(NativeTableError::DuplicateName(name));
139 }
140 }
141 }
142 let index_usize = index as usize;
143 if index_usize >= self.entries.len() {
144 self.entries.resize_with(index_usize + 1, || None);
145 }
146 if self.entries[index_usize].is_some() {
147 return Err(NativeTableError::SlotOccupied { index, name });
148 }
149 self.entries[index_usize] = Some((name, Arc::new(f)));
150 Ok(self)
151 }
152
153 pub fn build(self) -> Arc<NativeTable> {
154 Arc::new(NativeTable {
155 entries: self.entries,
156 })
157 }
158}
159
160impl Default for NativeTableBuilder {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166pub fn expect_arg<'a>(
168 args: &'a [Value],
169 index: usize,
170 fn_name: &str,
171) -> Result<&'a Value, Fault> {
172 args.get(index).ok_or(Fault::NativeError(format!(
173 "{fn_name}: missing argument {index}"
174 )))
175}
176
177pub fn expect_int(args: &[Value], index: usize, fn_name: &str) -> Result<i64, Fault> {
179 expect_arg(args, index, fn_name)?
180 .as_int()
181 .ok_or(Fault::NativeError(format!(
182 "{fn_name}: argument {index} is not an int"
183 )))
184}
185
186pub fn expect_bool(args: &[Value], index: usize, fn_name: &str) -> Result<bool, Fault> {
188 match expect_arg(args, index, fn_name)? {
189 Value::Bool(b) => Ok(*b),
190 Value::Int(i) => Ok(*i != 0),
191 other => Err(Fault::NativeError(format!(
192 "{fn_name}: argument {index} is not a bool/int (got {})",
193 other.type_name()
194 ))),
195 }
196}
197
198pub fn expect_message(
203 args: &[Value],
204 index: usize,
205 fn_name: &str,
206) -> Result<crate::Message, Fault> {
207 expect_arg(args, index, fn_name)?
208 .as_message()
209 .ok_or(Fault::NativeError(format!(
210 "{fn_name}: argument {index} is not a message"
211 )))
212}
213
214pub fn expect_u64(args: &[Value], index: usize, fn_name: &str) -> Result<u64, Fault> {
220 match expect_arg(args, index, fn_name)? {
221 Value::Pid(p) => Ok(*p),
222 Value::Cap(c) => Ok(*c),
223 Value::Int(i) if *i >= 0 => Ok(*i as u64),
224 Value::Bool(b) => Ok(u64::from(*b)),
225 other => Err(Fault::NativeError(format!(
226 "{fn_name}: argument {index} is not a non-negative int/pid/cap (got {})",
227 other.type_name()
228 ))),
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn register_at_leaves_holes_as_none() -> Result<(), Box<dyn std::error::Error>> {
238 let table = NativeTable::builder()
239 .register_at(10, "answer", |_| Ok(Value::Int(42)))?
240 .build();
241 assert_eq!(table.len(), 11);
242 assert_eq!(table.index_of("answer"), Some(10));
243 let f = table.get(10).ok_or("missing native")?;
244 assert!(matches!(f(&[])?, Value::Int(42)));
245 assert!(table.get(2).is_none());
246 Ok(())
247 }
248
249 #[test]
250 fn register_at_errors_on_duplicate_slot() {
251 let result = NativeTable::builder()
252 .register_at(3, "a", |_| Ok(Value::Unit))
253 .and_then(|b| b.register_at(3, "b", |_| Ok(Value::Unit)));
254 assert!(matches!(
255 result,
256 Err(NativeTableError::SlotOccupied { index: 3, .. })
257 ));
258 }
259
260 #[test]
261 fn register_errors_on_duplicate_name() {
262 let result = NativeTable::builder()
263 .register("x", |_| Ok(Value::Unit))
264 .and_then(|b| b.register("x", |_| Ok(Value::Unit)));
265 assert!(matches!(result, Err(NativeTableError::DuplicateName(_))));
266 }
267}