1use std::collections::HashMap;
40use std::sync::Arc;
41use std::time::{SystemTime, UNIX_EPOCH};
42
43use crate::bytecode::{Message, Value};
44use crate::output::{OutputSink, StdoutSink};
45use crate::vm::{expect_arg, expect_message, expect_u64, Fault, NativeTable};
46
47pub mod std_native {
50 pub const PRINT: u32 = 0;
51 pub const NOW_MS: u32 = 1;
52 pub const MAKE_MSG: u32 = 2;
53 pub const MSG_SENDER: u32 = 3;
54 pub const MSG_REQUEST_ID: u32 = 4;
55 pub const MSG_TAG: u32 = 5;
56 pub const MSG_PAYLOAD: u32 = 6;
57 pub const MSG_REPLY_CAP: u32 = 7;
58}
59
60pub fn std_native_map() -> HashMap<String, u32> {
66 HashMap::from([
67 ("print".to_owned(), 0),
68 ("now_ms".to_owned(), 1),
69 ("make_msg".to_owned(), 2),
70 ("msg_sender".to_owned(), 3),
71 ("msg_request_id".to_owned(), 4),
72 ("msg_tag".to_owned(), 5),
73 ("msg_payload".to_owned(), 6),
74 ("msg_reply_cap".to_owned(), 7),
75 ])
76}
77
78pub fn std_native_table() -> Arc<NativeTable> {
81 std_native_table_with(Arc::new(StdoutSink))
82}
83
84pub fn std_native_table_with(output: Arc<dyn OutputSink>) -> Arc<NativeTable> {
86 let built = NativeTable::builder()
87 .register("print", {
88 let output = Arc::clone(&output);
89 move |args| {
90 output.write(args);
91 Ok(Value::Unit)
92 }
93 })
94 .and_then(|b| {
95 b.register("now_ms", |_| {
96 let duration = SystemTime::now()
97 .duration_since(UNIX_EPOCH)
98 .map_err(|e| Fault::NativeError(format!("system clock error: {e}")))?;
99 let millis = i64::try_from(duration.as_millis())
100 .map_err(|_| Fault::NativeError("system clock value exceeds i64".into()))?;
101 Ok(Value::Int(millis))
102 })
103 })
104 .and_then(|b| {
105 b.register("make_msg", |args| {
106 let (request_id, tag, payload) = if args.len() >= 4 {
111 (
112 expect_u64(args, 1, "make_msg")?,
113 expect_u64(args, 2, "make_msg")?,
114 expect_arg(args, 3, "make_msg")?.clone(),
115 )
116 } else {
117 (
118 expect_u64(args, 0, "make_msg")?,
119 expect_u64(args, 1, "make_msg")?,
120 expect_arg(args, 2, "make_msg")?.clone(),
121 )
122 };
123 let tag = u16::try_from(tag).map_err(|_| {
124 Fault::NativeError(format!("make_msg: tag {tag} does not fit in u16"))
125 })?;
126 Ok(Value::Message(Message::new(0, request_id, tag, payload)))
127 })
128 })
129 .and_then(|b| {
130 b.register("msg_sender", |args| {
131 Ok(Value::Pid(expect_message(args, 0, "msg_sender")?.sender))
132 })
133 })
134 .and_then(|b| {
135 b.register("msg_request_id", |args| {
136 Ok(Value::Int(
137 expect_message(args, 0, "msg_request_id")?.request_id as i64,
138 ))
139 })
140 })
141 .and_then(|b| {
142 b.register("msg_tag", |args| {
143 Ok(Value::Int(i64::from(expect_message(args, 0, "msg_tag")?.tag)))
144 })
145 })
146 .and_then(|b| {
147 b.register("msg_payload", |args| {
148 Ok(expect_message(args, 0, "msg_payload")?.payload.as_ref().clone())
149 })
150 })
151 .and_then(|b| {
152 b.register("msg_reply_cap", |args| {
153 Ok(Value::Cap(
154 expect_message(args, 0, "msg_reply_cap")?.reply_cap,
155 ))
156 })
157 });
158 match built {
159 Ok(b) => b.build(),
160 Err(_) => NativeTable::empty(),
161 }
162}
163
164pub fn std_natives() -> (Arc<NativeTable>, HashMap<String, u32>) {
170 let table = std_native_table();
171 let map: HashMap<String, u32> = table
172 .names()
173 .filter_map(|n| table.index_of(n).map(|i| (n.to_string(), i)))
174 .collect();
175 (table, map)
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn std_native_indices_are_stable() {
184 let map = std_native_map();
185 assert_eq!(map.get("print"), Some(&0));
186 assert_eq!(map.get("now_ms"), Some(&1));
187 assert_eq!(map.get("make_msg"), Some(&2));
188 assert_eq!(map.get("msg_sender"), Some(&3));
189 assert_eq!(map.get("msg_request_id"), Some(&4));
190 assert_eq!(map.get("msg_tag"), Some(&5));
191 assert_eq!(map.get("msg_payload"), Some(&6));
192 assert_eq!(map.get("msg_reply_cap"), Some(&7));
193 }
194
195 #[test]
196 fn std_native_table_matches_map() {
197 let (table, map) = std_natives();
198 assert_eq!(table.len(), 8);
199 for (name, idx) in &map {
200 assert_eq!(table.index_of(name), Some(*idx));
201 }
202 }
203
204 #[test]
205 fn now_ms_returns_non_negative_int() -> Result<(), Box<dyn std::error::Error>> {
206 let table = std_native_table();
207 let now_ms = table.get(1).ok_or("now_ms native")?;
208 let value = now_ms(&[])?;
209 assert!(matches!(value, Value::Int(ms) if ms >= 0));
210 Ok(())
211 }
212
213 use crate::bytecode::CapId;
214
215 #[test]
216 fn make_msg_and_unpack_round_trip() -> Result<(), Box<dyn std::error::Error>> {
217 let table = std_native_table();
218 let make = table.get(2).ok_or("make_msg")?;
219 let msg = make(&[
220 Value::Int(3),
221 Value::Int(7),
222 Value::Int(42),
223 ])?;
224 let expected = Message::new(0, 3, 7, 42);
225 assert_eq!(msg.as_message(), Some(&expected));
226 let sender = table.get(3).ok_or("msg_sender")?;
227 let req = table.get(4).ok_or("msg_request_id")?;
228 let tag = table.get(5).ok_or("msg_tag")?;
229 let payload = table.get(6).ok_or("msg_payload")?;
230 let cap = table.get(7).ok_or("msg_reply_cap")?;
231 assert_eq!(sender(std::slice::from_ref(&msg))?, Value::Pid(0));
232 assert_eq!(req(std::slice::from_ref(&msg))?, Value::Int(3));
233 assert_eq!(tag(std::slice::from_ref(&msg))?, Value::Int(7));
234 assert_eq!(payload(std::slice::from_ref(&msg))?, Value::Int(42));
235 assert_eq!(cap(std::slice::from_ref(&msg))?, Value::Cap(CapId::NONE));
236 Ok(())
237 }
238
239 #[test]
240 fn print_accepts_all_current_values() -> Result<(), Box<dyn std::error::Error>> {
241 let table = std_native_table();
242 let print = table.get(0).ok_or("print native")?;
243 let values = [
244 Value::Unit,
245 Value::Bool(true),
246 Value::Int(42),
247 Value::Float(1.5),
248 Value::Pid(7),
249 Value::Message(Message::new(1, 2, 3, 4)),
250 Value::Cap(CapId::from_raw(9)),
251 Value::str("hello"),
252 Value::bytes([1u8, 2, 3]),
253 ];
254 assert_eq!(print(&values)?, Value::Unit);
255 Ok(())
256 }
257
258 #[test]
259 fn print_with_no_args_still_returns_unit() -> Result<(), Box<dyn std::error::Error>> {
260 let table = std_native_table();
261 let print = table.get(0).ok_or("print native")?;
262 assert_eq!(print(&[])?, Value::Unit);
263 Ok(())
264 }
265}