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
74pub struct NativeTableBuilder {
79 entries: Vec<Option<(String, NativeFn)>>,
80}
81
82impl NativeTableBuilder {
83 pub fn new() -> Self {
84 Self {
85 entries: Vec::new(),
86 }
87 }
88
89 pub fn register<F>(self, name: impl Into<String>, f: F) -> Self
92 where
93 F: Fn(&[Value]) -> NativeResult + Send + Sync + 'static,
94 {
95 let index = self.entries.len() as u32;
96 self.register_at(index, name, f)
97 }
98
99 pub fn register_at<F>(mut self, index: u32, name: impl Into<String>, f: F) -> Self
104 where
105 F: Fn(&[Value]) -> NativeResult + Send + Sync + 'static,
106 {
107 let name = name.into();
108 assert!(
109 !self
110 .entries
111 .iter()
112 .any(|slot| slot.as_ref().is_some_and(|(n, _)| n == &name)),
113 "byteflow: duplicate native function registered: '{name}'"
114 );
115 let index = index as usize;
116 if index >= self.entries.len() {
117 self.entries.resize_with(index + 1, || None);
118 }
119 assert!(
120 self.entries[index].is_none(),
121 "byteflow: native slot {index} already occupied (registering '{name}')"
122 );
123 self.entries[index] = Some((name, Arc::new(f)));
124 self
125 }
126
127 pub fn build(self) -> Arc<NativeTable> {
128 Arc::new(NativeTable {
129 entries: self.entries,
130 })
131 }
132}
133
134impl Default for NativeTableBuilder {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140pub fn expect_arg<'a>(
142 args: &'a [Value],
143 index: usize,
144 fn_name: &str,
145) -> Result<&'a Value, Fault> {
146 args.get(index).ok_or_else(|| {
147 Fault::NativeError(format!("{fn_name}: missing argument {index}"))
148 })
149}
150
151pub fn expect_int(args: &[Value], index: usize, fn_name: &str) -> Result<i64, Fault> {
153 expect_arg(args, index, fn_name)?
154 .as_int()
155 .ok_or_else(|| {
156 Fault::NativeError(format!("{fn_name}: argument {index} is not an int"))
157 })
158}
159
160pub fn expect_bool(args: &[Value], index: usize, fn_name: &str) -> Result<bool, Fault> {
162 match expect_arg(args, index, fn_name)? {
163 Value::Bool(b) => Ok(*b),
164 Value::Int(i) => Ok(*i != 0),
165 other => Err(Fault::NativeError(format!(
166 "{fn_name}: argument {index} is not a bool/int (got {})",
167 other.type_name()
168 ))),
169 }
170}
171
172pub fn expect_message(
177 args: &[Value],
178 index: usize,
179 fn_name: &str,
180) -> Result<crate::Message, Fault> {
181 expect_arg(args, index, fn_name)?
182 .as_message()
183 .ok_or_else(|| {
184 Fault::NativeError(format!("{fn_name}: argument {index} is not a message"))
185 })
186}
187
188pub fn expect_u64(args: &[Value], index: usize, fn_name: &str) -> Result<u64, Fault> {
194 match expect_arg(args, index, fn_name)? {
195 Value::Pid(p) => Ok(*p),
196 Value::Cap(c) => Ok(*c),
197 Value::Int(i) if *i >= 0 => Ok(*i as u64),
198 Value::Bool(b) => Ok(u64::from(*b)),
199 other => Err(Fault::NativeError(format!(
200 "{fn_name}: argument {index} is not a non-negative int/pid/cap (got {})",
201 other.type_name()
202 ))),
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn register_at_leaves_holes_as_none() {
212 let table = NativeTable::builder()
213 .register_at(10, "answer", |_| Ok(Value::Int(42)))
214 .build();
215 assert_eq!(table.len(), 11);
216 assert_eq!(table.index_of("answer"), Some(10));
217 assert!(table.get(10).unwrap()(&[]).unwrap() == Value::Int(42));
218 assert!(table.get(2).is_none());
219 }
220
221 #[test]
222 #[should_panic(expected = "already occupied")]
223 fn register_at_panics_on_duplicate_slot() {
224 let _ = NativeTable::builder()
225 .register_at(3, "a", |_| Ok(Value::Unit))
226 .register_at(3, "b", |_| Ok(Value::Unit));
227 }
228
229 #[test]
230 #[should_panic(expected = "duplicate native")]
231 fn register_panics_on_duplicate_name() {
232 let _ = NativeTable::builder()
233 .register("x", |_| Ok(Value::Unit))
234 .register("x", |_| Ok(Value::Unit));
235 }
236}