From 54928fce1d344f6e091d70c70c94a5ea882a3637 Mon Sep 17 00:00:00 2001 From: zrr1999 <2742392377@qq.com> Date: Wed, 15 Apr 2026 00:51:32 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20basic-cli=20exit=20ef?= =?UTF-8?q?fect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 2 +- host/src/lib.rs | 38 +++++++++++++++++++++++++++++++++++++- spore.toml | 2 +- src/basic_cli/cmd.sp | 3 +++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6a2c0fc..4c4ade8 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Operating System | `basic_cli.file` | `uses [FileRead]` or `uses [FileWrite]` | File system operations | | `basic_cli.dir` | `uses [FileRead]` or `uses [FileWrite]` | Directory listing and creation | | `basic_cli.env` | `uses [Env]` | Environment variables | -| `basic_cli.cmd` | `uses [Spawn]` | Process execution | +| `basic_cli.cmd` | `uses [Spawn]` or `uses [Exit]` | Process execution and explicit exit | ## Quick Start diff --git a/host/src/lib.rs b/host/src/lib.rs index 59830aa..f44709e 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -195,6 +195,26 @@ pub fn host_process_run_status(cmd: &str, args: &[String]) -> HostResult { Ok(HostValue::Int(status.code().unwrap_or(-1) as i64)) } +pub fn host_exit(code: i64) -> HostResult { + let code = u8::try_from(code).map_err(|_| HostError { + kind: "ExitError".into(), + message: format!("exit code {code} is out of range for 0..=255"), + })?; + + #[cfg(test)] + { + Err(HostError { + kind: "Exit".into(), + message: format!("process exit requested with code {code}"), + }) + } + + #[cfg(not(test))] + { + std::process::exit(i32::from(code)) + } +} + // ── Dispatch table ────────────────────────────────────────────────── /// Build the host function dispatch table. @@ -216,6 +236,7 @@ pub fn dispatch_table() -> HashMap<&'static str, &'static str> { table.insert("env_set", "host_env_set"); table.insert("process_run", "host_process_run"); table.insert("process_run_status", "host_process_run_status"); + table.insert("exit", "host_exit"); table } @@ -357,14 +378,29 @@ mod tests { assert!(err.message.contains("boom")); } + #[test] + fn exit_returns_test_sentinel_error() { + let err = host_exit(17).unwrap_err(); + assert_eq!(err.kind, "Exit"); + assert!(err.message.contains("17")); + } + + #[test] + fn exit_rejects_out_of_range_codes() { + let err = host_exit(256).unwrap_err(); + assert_eq!(err.kind, "ExitError"); + assert!(err.message.contains("0..=255")); + } + #[test] fn dispatch_table_has_all_functions() { let table = dispatch_table(); - assert_eq!(table.len(), 15); + assert_eq!(table.len(), 16); assert!(table.contains_key("println")); assert!(table.contains_key("file_read")); assert!(table.contains_key("process_run")); assert_eq!(table.get("process_run"), Some(&"host_process_run")); assert_eq!(table.get("dir_mkdir"), Some(&"host_dir_mkdir")); + assert_eq!(table.get("exit"), Some(&"host_exit")); } } diff --git a/spore.toml b/spore.toml index d3bf6ee..a234aa6 100644 --- a/spore.toml +++ b/spore.toml @@ -8,7 +8,7 @@ spore-version = ">=0.1.0" contract-module = "platform_contract" startup-contract = "main" adapter-function = "main_for_host" -handles = ["Console", "FileRead", "FileWrite", "Env", "Spawn"] +handles = ["Console", "FileRead", "FileWrite", "Env", "Spawn", "Exit"] [capabilities] allow = ["Compute"] diff --git a/src/basic_cli/cmd.sp b/src/basic_cli/cmd.sp index cc23a68..ce4a037 100644 --- a/src/basic_cli/cmd.sp +++ b/src/basic_cli/cmd.sp @@ -8,3 +8,6 @@ pub foreign fn process_run(cmd: Str, args: List[Str]) -> Str ! ExecError uses [S /// Run a command and return its exit code. pub foreign fn process_run_status(cmd: Str, args: List[Str]) -> Int ! ExecError uses [Spawn] + +/// Exit the current process with the provided status code. +pub foreign fn exit(code: Int) -> Never uses [Exit]