Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ The current Platform contract MVP is intentionally split across two artifacts:
- a hole-backed `main` function carries the authoritative startup signature
- `main_for_host` is the Platform-owned adapter that receives the application startup function

Applications targeting `basic-cli` must implement the same startup function name/signature in their entry module. The current compiler already resolves the package's `[platform]` metadata and `src/platform_contract.sp` to validate that startup shape. Runtime support is currently specialized to package-backed `basic-cli`: imported `basic_cli.*` foreign functions route through the built-in basic-cli host profile, while generic handled-effects enforcement and startup `spec` stacking are still follow-up work.
Applications targeting `basic-cli` must implement the same startup function name/signature in their entry module. The current compiler already resolves the package's `[platform]` metadata and `src/platform_contract.sp` to validate that startup shape. Project-mode interpreter runtime support now goes through a generic package-backed host path: imported public `foreign fn` declarations bind by canonical operation name (`print`, `file_exists`, `exit`, etc.) instead of hard-coding the `basic-cli` package name. Startup `spec` stacking and native lowering for `foreign fn` platform projects are still follow-up work.

`src/host.sp` remains as a compatibility copy of the adapter for older references; current manifest-backed projects use `src/platform_contract.sp`.

Expand Down Expand Up @@ -152,7 +152,7 @@ Following Spore's [SEP-0003 (Effect System)](https://github.com/spore-lang/spore

The canonical example is the **package-backed project-mode** `examples/hello-app/` application. It already validates and runs with `[project].platform = "basic-cli"`, an in-repo path dependency, and `import basic_cli.stdout`.

The pure standalone file example (`examples/hello.sp`) stays around for quick experiments. The main remaining platform gaps are generic handled-effects enforcement, startup `spec` stacking, native lowering for `foreign fn` platform projects, and lifting the runtime from its current explicit `basic-cli` host profile to a more general package-backed mechanism.
The pure standalone file example (`examples/hello.sp`) stays around for quick experiments. The main remaining platform gaps are startup `spec` stacking and native lowering for `foreign fn` platform projects.

## License

Expand Down
41 changes: 25 additions & 16 deletions host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,20 @@ pub fn host_process_run_status(cmd: &str, args: &[String]) -> HostResult {
kind: "ExecError".into(),
message: format!("{cmd}: {e}"),
})?;
Ok(HostValue::Int(status.code().unwrap_or(-1) as i64))
let code = match status.code() {
Some(code) => {
let code = u8::try_from(code).map_err(|_| HostError {
kind: "ExecError".into(),
message: format!("{cmd}: exit code {code} is out of range for U8"),
})?;
Some(Box::new(HostValue::Int(i64::from(code))))
}
None => None,
};
Ok(HostValue::Option(code))
}

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"),
})?;

pub fn host_exit(code: u8) -> HostResult {
#[cfg(test)]
{
Err(HostError {
Expand Down Expand Up @@ -355,11 +360,22 @@ mod tests {
fn process_run_status_echo() {
let result = host_process_run_status("echo", &["hello".into()]).unwrap();
match result {
HostValue::Int(code) => assert_eq!(code, 0),
_ => panic!("expected Int"),
HostValue::Option(Some(code)) => match *code {
HostValue::Int(code) => assert_eq!(code, 0),
other => panic!("expected U8 code payload, got {other:?}"),
},
other => panic!("expected Some(U8), got {other:?}"),
}
}

#[test]
#[cfg(unix)]
fn process_run_status_signal_returns_none() {
let result = host_process_run_status("sh", &["-c".into(), "kill -TERM $$".into()])
.expect("signal termination should still report status");
assert!(matches!(result, HostValue::Option(None)));
}

#[test]
fn process_run_captures_stdout() {
let result =
Expand All @@ -385,13 +401,6 @@ mod tests {
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();
Expand Down
4 changes: 2 additions & 2 deletions src/basic_cli/cmd.sp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
pub foreign fn process_run(cmd: Str, args: List[Str]) -> Str ! ExecError uses [Spawn]

/// Run a command and return its exit code.
pub foreign fn process_run_status(cmd: Str, args: List[Str]) -> I32 ! ExecError uses [Spawn]
pub foreign fn process_run_status(cmd: Str, args: List[Str]) -> Option[U8] ! ExecError uses [Spawn]

/// Exit the current process with the provided status code.
pub foreign fn exit(code: I32) -> Never uses [Exit]
pub foreign fn exit(code: U8) -> Never uses [Exit]
Loading