@@ -5,6 +5,12 @@ use std::process::ExitCode;
55use std:: thread;
66use std:: time:: { Duration , Instant , SystemTime , UNIX_EPOCH } ;
77
8+ use rsscript:: {
9+ EvalError , EvalOutput , NativeValue , eval_package_main_with_args_and_native_bindings,
10+ format_diagnostics_human, format_diagnostics_json, load_package_native_bindings,
11+ reg_vm_eval_source_main_with_args,
12+ } ;
13+
814use super :: check:: run_check;
915use super :: lint:: run_lint;
1016use super :: run_cmd:: run_generated_rust;
@@ -165,7 +171,12 @@ fn run_once(options: &DevOptions<'_>, path: &str) -> ExitCode {
165171 }
166172 run_check ( & check_args ( options, path) )
167173 }
168- DevAction :: Run => run_generated_rust ( & run_args ( options, path) ) ,
174+ // Two-tier execution (spec §20.2-4): the inner edit→run loop uses the
175+ // reg-VM interpreter tier (no rustc cost); `--release` switches to the
176+ // Rust-lowering AOT tier. Both observe identical semantics + diagnostics
177+ // (the VM↔compiled parity invariant), so the fast tier is trustworthy.
178+ DevAction :: Run if options. release => run_generated_rust ( & run_args ( options, path) ) ,
179+ DevAction :: Run => run_via_vm ( options, path) ,
169180 } ;
170181
171182 if !options. json {
@@ -180,6 +191,68 @@ fn run_once(options: &DevOptions<'_>, path: &str) -> ExitCode {
180191 status
181192}
182193
194+ /// Run `path` through the reg-VM dev tier (fast, no rustc). A package directory
195+ /// runs through the package VM with its native host bindings loaded; a single
196+ /// file runs through the source VM, mirroring `rss eval`.
197+ fn run_via_vm ( options : & DevOptions < ' _ > , path : & str ) -> ExitCode {
198+ let result = if is_package_directory ( path) {
199+ run_package_via_vm ( path)
200+ } else {
201+ match fs:: read_to_string ( path) {
202+ Ok ( source) => {
203+ reg_vm_eval_source_main_with_args ( path, & source, std:: iter:: empty :: < & str > ( ) )
204+ }
205+ Err ( error) => {
206+ eprintln ! ( "failed to read {path}: {error}" ) ;
207+ return ExitCode :: from ( 2 ) ;
208+ }
209+ }
210+ } ;
211+ finish_vm_run ( options, result)
212+ }
213+
214+ fn run_package_via_vm ( path : & str ) -> Result < EvalOutput , EvalError > {
215+ let package_dir = Path :: new ( path) ;
216+ let bindings = load_package_native_bindings ( package_dir) . map_err ( EvalError :: Runtime ) ?;
217+ eval_package_main_with_args_and_native_bindings (
218+ package_dir,
219+ std:: iter:: empty :: < & str > ( ) ,
220+ bindings,
221+ )
222+ }
223+
224+ /// Render a VM run result the same way `rss eval` does: program stdout/stderr,
225+ /// then the `main` return value, with an `Err` return reported as a failed run
226+ /// (matching the AOT backend's exit behavior).
227+ fn finish_vm_run ( options : & DevOptions < ' _ > , result : Result < EvalOutput , EvalError > ) -> ExitCode {
228+ match result {
229+ Ok ( output) => {
230+ print ! ( "{}" , output. stdout) ;
231+ eprint ! ( "{}" , output. stderr) ;
232+ if let Some ( NativeValue :: Variant { name, .. } ) = & output. native_value
233+ && name == "Err"
234+ {
235+ eprintln ! ( "RSScript main returned an error: {}" , output. value) ;
236+ return ExitCode :: from ( 1 ) ;
237+ }
238+ println ! ( "{}" , output. value) ;
239+ ExitCode :: SUCCESS
240+ }
241+ Err ( EvalError :: Diagnostics ( diagnostics) ) => {
242+ if options. json {
243+ println ! ( "{}" , format_diagnostics_json( & diagnostics) ) ;
244+ } else {
245+ print ! ( "{}" , format_diagnostics_human( & diagnostics) ) ;
246+ }
247+ ExitCode :: from ( 1 )
248+ }
249+ Err ( EvalError :: Runtime ( error) ) => {
250+ eprintln ! ( "{error}" ) ;
251+ ExitCode :: from ( 1 )
252+ }
253+ }
254+ }
255+
183256fn check_args ( options : & DevOptions < ' _ > , path : & str ) -> Vec < String > {
184257 let mut args = Vec :: new ( ) ;
185258 if options. json {
@@ -212,7 +285,8 @@ fn run_args(options: &DevOptions<'_>, path: &str) -> Vec<String> {
212285
213286fn action_label ( options : & DevOptions < ' _ > ) -> & ' static str {
214287 match ( options. action , options. lint ) {
215- ( DevAction :: Run , _) => "run" ,
288+ ( DevAction :: Run , _) if options. release => "run (release AOT)" ,
289+ ( DevAction :: Run , _) => "run (dev VM)" ,
216290 ( DevAction :: Check , true ) => "check + lint" ,
217291 ( DevAction :: Check , false ) => "check" ,
218292 }
@@ -345,6 +419,22 @@ mod tests {
345419 assert_eq ! ( options. path, Some ( "pkg" ) ) ;
346420 }
347421
422+ #[ test]
423+ fn action_label_distinguishes_vm_and_aot_run_tiers ( ) {
424+ let run = |release| super :: DevOptions {
425+ action : super :: DevAction :: Run ,
426+ lint : false ,
427+ json : false ,
428+ release,
429+ use_core : true ,
430+ interfaces : Vec :: new ( ) ,
431+ path : Some ( "demo.rss" ) ,
432+ once : true ,
433+ } ;
434+ assert_eq ! ( super :: action_label( & run( false ) ) , "run (dev VM)" ) ;
435+ assert_eq ! ( super :: action_label( & run( true ) ) , "run (release AOT)" ) ;
436+ }
437+
348438 #[ test]
349439 fn parse_dev_args_rejects_json_without_once ( ) {
350440 let values = args ( & [ "--json" , "demo.rss" ] ) ;
0 commit comments