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> {
let built = NativeTable::builder()
.register("print", |args| {
let mut first = true;
for value in args {
if !first {
print!(" ");
}
print!("{value}");
first = false;
}
println!();
Ok(Value::Unit)
})
.and_then(|b| {
b.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))
})
})
.and_then(|b| {
b.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)))
})
})
.and_then(|b| {
b.register("msg_sender", |args| {
Ok(Value::Pid(expect_message(args, 0, "msg_sender")?.sender))
})
})
.and_then(|b| {
b.register("msg_request_id", |args| {
Ok(Value::Int(
expect_message(args, 0, "msg_request_id")?.request_id as i64,
))
})
})
.and_then(|b| {
b.register("msg_tag", |args| {
Ok(Value::Int(i64::from(expect_message(args, 0, "msg_tag")?.tag)))
})
})
.and_then(|b| {
b.register("msg_payload", |args| {
Ok(Value::Int(
expect_message(args, 0, "msg_payload")?.payload as i64,
))
})
})
.and_then(|b| {
b.register("msg_reply_cap", |args| {
Ok(Value::Cap(
expect_message(args, 0, "msg_reply_cap")?.reply_cap,
))
})
});
match built {
Ok(b) => b.build(),
Err(_) => NativeTable::empty(),
}
}
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.get("print"), Some(&0));
assert_eq!(map.get("now_ms"), Some(&1));
assert_eq!(map.get("make_msg"), Some(&2));
assert_eq!(map.get("msg_sender"), Some(&3));
assert_eq!(map.get("msg_request_id"), Some(&4));
assert_eq!(map.get("msg_tag"), Some(&5));
assert_eq!(map.get("msg_payload"), Some(&6));
assert_eq!(map.get("msg_reply_cap"), Some(&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() -> Result<(), Box<dyn std::error::Error>> {
let table = std_native_table();
let now_ms = table.get(1).ok_or("now_ms native")?;
let value = now_ms(&[])?;
assert!(matches!(value, Value::Int(ms) if ms >= 0));
Ok(())
}
#[test]
fn make_msg_and_unpack_round_trip() -> Result<(), Box<dyn std::error::Error>> {
let table = std_native_table();
let make = table.get(2).ok_or("make_msg")?;
let msg = make(&[
Value::Pid(9),
Value::Int(3),
Value::Int(7),
Value::Int(42),
])?;
assert_eq!(msg.as_message(), Some(Message::new(9, 3, 7, 42)));
let sender = table.get(3).ok_or("msg_sender")?;
let req = table.get(4).ok_or("msg_request_id")?;
let tag = table.get(5).ok_or("msg_tag")?;
let payload = table.get(6).ok_or("msg_payload")?;
let cap = table.get(7).ok_or("msg_reply_cap")?;
assert_eq!(sender(std::slice::from_ref(&msg))?, Value::Pid(9));
assert_eq!(req(std::slice::from_ref(&msg))?, Value::Int(3));
assert_eq!(tag(std::slice::from_ref(&msg))?, Value::Int(7));
assert_eq!(payload(std::slice::from_ref(&msg))?, Value::Int(42));
assert_eq!(cap(std::slice::from_ref(&msg))?, Value::Cap(0));
Ok(())
}
#[test]
fn print_accepts_all_current_values() -> Result<(), Box<dyn std::error::Error>> {
let table = std_native_table();
let print = table.get(0).ok_or("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)?, Value::Unit);
Ok(())
}
#[test]
fn print_with_no_args_still_returns_unit() -> Result<(), Box<dyn std::error::Error>> {
let table = std_native_table();
let print = table.get(0).ok_or("print native")?;
assert_eq!(print(&[])?, Value::Unit);
Ok(())
}
}