Skip to main content

vm/builtins/runtime/
print.rs

1use 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}