vm/builtins/runtime/
print.rs1use crate::vm::{CallOutcome, CallReturn, HostFunction, Value, Vm, VmResult};
2
3pub fn format_value(value: &Value) -> String {
4 match value {
5 Value::Null => "null".to_string(),
6 Value::Int(value) => value.to_string(),
7 Value::Float(value) => value.to_string(),
8 Value::Bool(value) => value.to_string(),
9 Value::String(value) => value.as_str().to_string(),
10 Value::Bytes(value) => format_bytes(value.as_ref()),
11 Value::Array(values) => {
12 let parts = values
13 .iter()
14 .map(format_value)
15 .collect::<Vec<_>>()
16 .join(", ");
17 format!("[{parts}]")
18 }
19 Value::Map(entries) => {
20 let parts = entries
21 .iter()
22 .map(|(key, value)| format!("{}: {}", format_value(key), format_value(value)))
23 .collect::<Vec<_>>()
24 .join(", ");
25 format!("{{{parts}}}")
26 }
27 }
28}
29
30fn format_bytes(bytes: &[u8]) -> String {
31 let preview_len = bytes.len().min(16);
32 let mut preview = String::with_capacity(preview_len * 2);
33 for byte in &bytes[..preview_len] {
34 preview.push(hex_nibble(byte >> 4));
35 preview.push(hex_nibble(byte & 0x0F));
36 }
37 if bytes.len() > preview_len {
38 format!("bytes[len={} hex={}..]", bytes.len(), preview)
39 } else {
40 format!("bytes[len={} hex={}]", bytes.len(), preview)
41 }
42}
43
44fn hex_nibble(value: u8) -> char {
45 match value {
46 0..=9 => char::from(b'0' + value),
47 10..=15 => char::from(b'a' + (value - 10)),
48 _ => unreachable!("hex nibble out of range"),
49 }
50}
51
52fn format_values(args: &[Value]) -> String {
53 args.iter().map(format_value).collect::<Vec<_>>().join(" ")
54}
55
56fn borrowed_args_return(args: &[Value]) -> CallReturn {
57 match args {
58 [] => CallReturn::none(),
59 [value] => CallReturn::one(value.clone()),
60 _ => CallReturn::one(Value::array(args.to_vec())),
61 }
62}
63
64pub struct PrintHostFunction<F>
65where
66 F: FnMut(String) + Send + 'static,
67{
68 sink: F,
69}
70
71impl<F> PrintHostFunction<F>
72where
73 F: FnMut(String) + Send + 'static,
74{
75 pub fn new(sink: F) -> Self {
76 Self { sink }
77 }
78}
79
80impl<F> HostFunction for PrintHostFunction<F>
81where
82 F: FnMut(String) + Send + 'static,
83{
84 fn call(&mut self, _vm: &mut Vm, args: &[Value]) -> VmResult<CallOutcome> {
85 let rendered = format_values(args);
86 (self.sink)(rendered);
87 Ok(CallOutcome::Return(borrowed_args_return(args)))
88 }
89}
90
91pub struct PrintlnHostFunction<F>
92where
93 F: FnMut(String) + Send + 'static,
94{
95 sink: F,
96}
97
98impl<F> PrintlnHostFunction<F>
99where
100 F: FnMut(String) + Send + 'static,
101{
102 pub fn new(sink: F) -> Self {
103 Self { sink }
104 }
105}
106
107impl<F> HostFunction for PrintlnHostFunction<F>
108where
109 F: FnMut(String) + Send + 'static,
110{
111 fn call(&mut self, _vm: &mut Vm, args: &[Value]) -> VmResult<CallOutcome> {
112 let mut rendered = format_values(args);
113 rendered.push('\n');
114 (self.sink)(rendered);
115 Ok(CallOutcome::Return(borrowed_args_return(args)))
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::sync::{Arc, Mutex};
122
123 use crate::bytecode::Program;
124 use crate::vm::{HostFunction, Value, Vm};
125
126 use super::{PrintHostFunction, PrintlnHostFunction, format_value};
127
128 fn vm_for_host_call() -> Vm {
129 Vm::new(Program::new(
130 Vec::new(),
131 vec![crate::bytecode::OpCode::Ret as u8],
132 ))
133 }
134
135 #[test]
136 fn format_value_renders_nested_values() {
137 let value = Value::map(vec![(
138 Value::string("items"),
139 Value::array(vec![Value::Int(1), Value::Bool(true)]),
140 )]);
141 assert_eq!(format_value(&value), "{items: [1, true]}");
142 }
143
144 #[test]
145 fn print_host_function_writes_to_sink() {
146 let lines = Arc::new(Mutex::new(Vec::<String>::new()));
147 let sink_lines = Arc::clone(&lines);
148 let mut host = PrintHostFunction::new(move |rendered| {
149 if let Ok(mut guard) = sink_lines.lock() {
150 guard.push(rendered);
151 }
152 });
153 let mut vm = vm_for_host_call();
154
155 host.call(&mut vm, &[Value::Int(2), Value::string("ok")])
156 .expect("print host call should succeed");
157
158 let guard = lines.lock().expect("sink should be lockable");
159 assert_eq!(guard.as_slice(), ["2 ok"]);
160 }
161
162 #[test]
163 fn println_host_function_appends_newline() {
164 let lines = Arc::new(Mutex::new(Vec::<String>::new()));
165 let sink_lines = Arc::clone(&lines);
166 let mut host = PrintlnHostFunction::new(move |rendered| {
167 if let Ok(mut guard) = sink_lines.lock() {
168 guard.push(rendered);
169 }
170 });
171 let mut vm = vm_for_host_call();
172
173 host.call(&mut vm, &[Value::string("line")])
174 .expect("println host call should succeed");
175
176 let guard = lines.lock().expect("sink should be lockable");
177 assert_eq!(guard.as_slice(), ["line\n"]);
178 }
179}