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