use std::collections::HashMap;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::bytecode::{Message, Value};
use crate::vm::{expect_message, expect_u64, Fault, NativeTable};
pub fn std_native_map() -> HashMap<String, u32> {
HashMap::from([
("print".to_owned(), 0),
("now_ms".to_owned(), 1),
("make_msg".to_owned(), 2),
("msg_sender".to_owned(), 3),
("msg_request_id".to_owned(), 4),
("msg_tag".to_owned(), 5),
("msg_payload".to_owned(), 6),
("msg_reply_cap".to_owned(), 7),
])
}
pub fn std_native_table() -> Arc<NativeTable> {
NativeTable::builder()
.register("print", |args| {
let mut first = true;
for value in args {
if !first {
print!(" ");
}
print!("{value}");
first = false;
}
println!();
Ok(Value::Unit)
})
.register("now_ms", |_| {
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| Fault::NativeError(format!("system clock error: {e}")))?;
let millis = i64::try_from(duration.as_millis())
.map_err(|_| Fault::NativeError("system clock value exceeds i64".into()))?;
Ok(Value::Int(millis))
})
.register("make_msg", |args| {
let sender = expect_u64(args, 0, "make_msg")?;
let request_id = expect_u64(args, 1, "make_msg")?;
let tag = expect_u64(args, 2, "make_msg")?;
let payload = expect_u64(args, 3, "make_msg")?;
let tag = u16::try_from(tag).map_err(|_| {
Fault::NativeError(format!("make_msg: tag {tag} does not fit in u16"))
})?;
Ok(Value::Message(Message::new(sender, request_id, tag, payload)))
})
.register("msg_sender", |args| {
Ok(Value::Pid(expect_message(args, 0, "msg_sender")?.sender))
})
.register("msg_request_id", |args| {
Ok(Value::Int(
expect_message(args, 0, "msg_request_id")?.request_id as i64,
))
})
.register("msg_tag", |args| {
Ok(Value::Int(i64::from(
expect_message(args, 0, "msg_tag")?.tag,
)))
})
.register("msg_payload", |args| {
Ok(Value::Int(
expect_message(args, 0, "msg_payload")?.payload as i64,
))
})
.register("msg_reply_cap", |args| {
Ok(Value::Cap(
expect_message(args, 0, "msg_reply_cap")?.reply_cap,
))
})
.build()
}
pub fn std_natives() -> (Arc<NativeTable>, HashMap<String, u32>) {
let table = std_native_table();
let map: HashMap<String, u32> = table
.names()
.filter_map(|n| table.index_of(n).map(|i| (n.to_string(), i)))
.collect();
(table, map)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn std_native_indices_are_stable() {
let map = std_native_map();
assert_eq!(map["print"], 0);
assert_eq!(map["now_ms"], 1);
assert_eq!(map["make_msg"], 2);
assert_eq!(map["msg_sender"], 3);
assert_eq!(map["msg_request_id"], 4);
assert_eq!(map["msg_tag"], 5);
assert_eq!(map["msg_payload"], 6);
assert_eq!(map["msg_reply_cap"], 7);
}
#[test]
fn std_native_table_matches_map() {
let (table, map) = std_natives();
assert_eq!(table.len(), 8);
for (name, idx) in &map {
assert_eq!(table.index_of(name), Some(*idx));
}
}
#[test]
fn now_ms_returns_non_negative_int() {
let table = std_native_table();
let now_ms = table.get(1).expect("now_ms native");
let value = now_ms(&[]).expect("clock read");
assert!(matches!(value, Value::Int(ms) if ms >= 0));
}
#[test]
fn make_msg_and_unpack_round_trip() {
let table = std_native_table();
let make = table.get(2).expect("make_msg");
let msg = make(&[
Value::Pid(9),
Value::Int(3),
Value::Int(7),
Value::Int(42),
])
.unwrap();
assert_eq!(msg.as_message(), Some(Message::new(9, 3, 7, 42)));
assert_eq!(
table.get(3).unwrap()(std::slice::from_ref(&msg)).unwrap(),
Value::Pid(9)
);
assert_eq!(
table.get(4).unwrap()(std::slice::from_ref(&msg)).unwrap(),
Value::Int(3)
);
assert_eq!(
table.get(5).unwrap()(std::slice::from_ref(&msg)).unwrap(),
Value::Int(7)
);
assert_eq!(
table.get(6).unwrap()(std::slice::from_ref(&msg)).unwrap(),
Value::Int(42)
);
assert_eq!(
table.get(7).unwrap()(std::slice::from_ref(&msg)).unwrap(),
Value::Cap(0)
);
}
#[test]
fn print_accepts_all_current_values() {
let table = std_native_table();
let print = table.get(0).expect("print native");
let values = [
Value::Unit,
Value::Bool(true),
Value::Int(42),
Value::Float(1.5),
Value::Pid(7),
Value::Message(Message::new(1, 2, 3, 4)),
Value::Cap(9),
Value::str("hello"),
Value::bytes([1u8, 2, 3]),
];
assert_eq!(print(&values).unwrap(), Value::Unit);
}
#[test]
fn print_with_no_args_still_returns_unit() {
let table = std_native_table();
let print = table.get(0).expect("print native");
assert_eq!(print(&[]).unwrap(), Value::Unit);
}
}