11use std:: time:: Duration ;
22
33use super :: super :: { CallOutcome , HostFunctionRegistry , Value , Vm , VmError , VmResult } ;
4+ use super :: print:: format_value;
45
6+ pub ( crate ) const PRINT_NAME : & str = "print" ;
7+ pub ( crate ) const PRINTLN_NAME : & str = "println" ;
58pub ( crate ) const RUNTIME_SLEEP_NAME : & str = "runtime::sleep" ;
69
710pub ( crate ) fn register_default_host_functions ( registry : & mut HostFunctionRegistry ) {
11+ registry. register_static ( PRINT_NAME , 1 , runtime_print) ;
12+ registry. register_static ( PRINTLN_NAME , 1 , runtime_println) ;
813 registry. register_static ( RUNTIME_SLEEP_NAME , 1 , runtime_sleep) ;
914}
1015
1116pub ( crate ) fn bind_default_host_function ( vm : & mut Vm , name : & str ) -> bool {
1217 match name {
18+ PRINT_NAME => {
19+ vm. bind_static_function ( PRINT_NAME , runtime_print) ;
20+ true
21+ }
22+ PRINTLN_NAME => {
23+ vm. bind_static_function ( PRINTLN_NAME , runtime_println) ;
24+ true
25+ }
1326 RUNTIME_SLEEP_NAME => {
1427 vm. bind_static_function ( RUNTIME_SLEEP_NAME , runtime_sleep) ;
1528 true
@@ -18,6 +31,24 @@ pub(crate) fn bind_default_host_function(vm: &mut Vm, name: &str) -> bool {
1831 }
1932}
2033
34+ fn render_print_args ( args : & [ Value ] , newline : bool ) -> String {
35+ let mut rendered = args. iter ( ) . map ( format_value) . collect :: < Vec < _ > > ( ) . join ( " " ) ;
36+ if newline {
37+ rendered. push ( '\n' ) ;
38+ }
39+ rendered
40+ }
41+
42+ fn runtime_print ( vm : & mut Vm , args : & [ Value ] ) -> VmResult < CallOutcome > {
43+ vm. write_runtime_print ( render_print_args ( args, false ) ) ?;
44+ Ok ( CallOutcome :: Return ( args. to_vec ( ) ) )
45+ }
46+
47+ fn runtime_println ( vm : & mut Vm , args : & [ Value ] ) -> VmResult < CallOutcome > {
48+ vm. write_runtime_print ( render_print_args ( args, true ) ) ?;
49+ Ok ( CallOutcome :: Return ( args. to_vec ( ) ) )
50+ }
51+
2152fn sleep_duration ( args : & [ Value ] ) -> VmResult < Duration > {
2253 let millis = match args. first ( ) {
2354 Some ( Value :: Int ( value) ) => * value,
@@ -47,10 +78,29 @@ fn runtime_sleep(_vm: &mut Vm, args: &[Value]) -> VmResult<CallOutcome> {
4778
4879#[ cfg( test) ]
4980mod tests {
50- use crate :: bytecode:: Program ;
51- use crate :: vm:: { Value , Vm } ;
81+ use std:: sync:: { Arc , Mutex } ;
82+
83+ use crate :: assembler:: BytecodeBuilder ;
84+ use crate :: bytecode:: { HostImport , Program } ;
85+ use crate :: vm:: { HostFunctionRegistry , Value , Vm , VmStatus } ;
5286
53- use super :: { RUNTIME_SLEEP_NAME , runtime_sleep} ;
87+ use super :: { PRINT_NAME , PRINTLN_NAME , RUNTIME_SLEEP_NAME , runtime_sleep} ;
88+
89+ fn host_call_program ( name : & str ) -> Program {
90+ let mut bc = BytecodeBuilder :: new ( ) ;
91+ bc. ldc ( 0 ) ;
92+ bc. call ( 0 , 1 ) ;
93+ bc. ret ( ) ;
94+ Program :: with_imports_and_debug (
95+ vec ! [ Value :: string( "line" ) ] ,
96+ bc. finish ( ) ,
97+ vec ! [ HostImport {
98+ name: name. to_string( ) ,
99+ arity: 1 ,
100+ } ] ,
101+ None ,
102+ )
103+ }
54104
55105 #[ test]
56106 fn runtime_sleep_rejects_negative_milliseconds ( ) {
@@ -71,4 +121,68 @@ mod tests {
71121 fn runtime_sleep_name_is_stable ( ) {
72122 assert_eq ! ( RUNTIME_SLEEP_NAME , "runtime::sleep" ) ;
73123 }
124+
125+ #[ test]
126+ fn default_print_binding_uses_vm_runtime_sink ( ) {
127+ let lines = Arc :: new ( Mutex :: new ( Vec :: < String > :: new ( ) ) ) ;
128+ let sink_lines = Arc :: clone ( & lines) ;
129+ let mut vm = Vm :: new ( host_call_program ( PRINT_NAME ) ) ;
130+ vm. set_runtime_print_sink ( move |rendered| {
131+ sink_lines
132+ . lock ( )
133+ . expect ( "sink should be lockable" )
134+ . push ( rendered) ;
135+ } ) ;
136+
137+ let status = vm. run ( ) . expect ( "vm should run" ) ;
138+ assert_eq ! ( status, VmStatus :: Halted ) ;
139+ assert_eq ! (
140+ lines. lock( ) . expect( "sink should be lockable" ) . as_slice( ) ,
141+ [ "line" ]
142+ ) ;
143+ }
144+
145+ #[ test]
146+ fn host_function_registry_includes_default_print_binding ( ) {
147+ let lines = Arc :: new ( Mutex :: new ( Vec :: < String > :: new ( ) ) ) ;
148+ let sink_lines = Arc :: clone ( & lines) ;
149+ let mut vm = Vm :: new ( host_call_program ( PRINT_NAME ) ) ;
150+ vm. set_runtime_print_sink ( move |rendered| {
151+ sink_lines
152+ . lock ( )
153+ . expect ( "sink should be lockable" )
154+ . push ( rendered) ;
155+ } ) ;
156+ let mut registry = HostFunctionRegistry :: new ( ) ;
157+ registry
158+ . bind_vm_cached ( & mut vm)
159+ . expect ( "registry should bind print" ) ;
160+
161+ let status = vm. run ( ) . expect ( "vm should run" ) ;
162+ assert_eq ! ( status, VmStatus :: Halted ) ;
163+ assert_eq ! (
164+ lines. lock( ) . expect( "sink should be lockable" ) . as_slice( ) ,
165+ [ "line" ]
166+ ) ;
167+ }
168+
169+ #[ test]
170+ fn default_println_binding_appends_newline_before_sink ( ) {
171+ let lines = Arc :: new ( Mutex :: new ( Vec :: < String > :: new ( ) ) ) ;
172+ let sink_lines = Arc :: clone ( & lines) ;
173+ let mut vm = Vm :: new ( host_call_program ( PRINTLN_NAME ) ) ;
174+ vm. set_runtime_print_sink ( move |rendered| {
175+ sink_lines
176+ . lock ( )
177+ . expect ( "sink should be lockable" )
178+ . push ( rendered) ;
179+ } ) ;
180+
181+ let status = vm. run ( ) . expect ( "vm should run" ) ;
182+ assert_eq ! ( status, VmStatus :: Halted ) ;
183+ assert_eq ! (
184+ lines. lock( ) . expect( "sink should be lockable" ) . as_slice( ) ,
185+ [ "line\n " ]
186+ ) ;
187+ }
74188}
0 commit comments