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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"crates/shengen-rust",
"crates/klcompile",
"crates/ratatoskr-build",
"crates/shenffi",
"bin/shen-rust",
"examples/shen-cedar-authz",
]
Expand Down
15 changes: 15 additions & 0 deletions crates/shenffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "shenffi"
version.workspace = true
edition.workspace = true

# A thin C-ABI surface over shen-rust, so the interpreter can be embedded in
# non-Rust hosts (Swift / iOS, etc.) as a static library or framework.
# - staticlib -> libshenffi.a (link into an Xcode app / .xcframework)
# - cdylib -> libshenffi.dylib (handy for desktop linking / testing)
[lib]
name = "shenffi"
crate-type = ["staticlib", "cdylib", "rlib"]

[dependencies]
shen-rust = { path = "../shen-rust" }
99 changes: 99 additions & 0 deletions crates/shenffi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# shenffi — Swift / C bindings for shen-rust

A thin C-ABI wrapper that embeds the [shen-rust](../shen-rust) interpreter in
non-Rust hosts — primarily **Swift on iOS/macOS**. shen-rust runs the Shen
kernel via AOT-compiled native code + a bytecode VM (no JIT in the default
build), so it's App Store-safe: it links into your app as a static library, no
runtime codegen.

## The C surface (`include/shenffi.h`)

```c
ShenCtx *shen_boot(const char *kernel_dir); // NULL -> auto-locate kernel
char *shen_eval(ShenCtx *ctx, const char *src); // -> rendered result / "error: …"
void shen_string_free(char *s);
void shen_free(ShenCtx *ctx);
```

`shen_eval` runs a **Shen-level** expression through the kernel's own pipeline
(`read-from-string` → `head` → `eval`) and renders the result with `shen.app`,
so output matches the REPL.

## Swift wrapper (`swift/ShenRust.swift`)

```swift
let shen = try ShenRust(kernelDir: bundleKernelPath) // nil to auto-locate
print(shen.eval("(map (/. X (* X X)) [1 2 3 4])")) // -> [1 4 9 16]
```

## Build

**macOS (desktop / quick test):**
```sh
cargo build --release -p shenffi # -> target/release/libshenffi.{a,dylib}
# round-trip demo:
cd crates/shenffi
swiftc -O -import-objc-header include/shenffi.h swift/ShenRust.swift swift/main.swift \
-L ../../target/release -lshenffi -o /tmp/shenrust_demo
DYLD_LIBRARY_PATH=../../target/release /tmp/shenrust_demo
```

**iOS (device + simulator XCFramework):**
```sh
crates/shenffi/build-xcframework.sh # -> target/ShenRust.xcframework
```
Then in Xcode: add `ShenRust.xcframework`, set `shenffi.h` as the bridging
header (or wrap in a module map), and drop in `swift/ShenRust.swift`.

## Kernel on iOS

`shen_boot(NULL)` auto-locates the kernel by walking the filesystem — fine on
desktop, not on a sandboxed device. Two device-friendly options:

1. **Bundle the `.kl` kernel** as an app resource and pass its directory path to
`shen_boot(path)`.
2. **Embed a shaken `kernel.kl`** (produced by [ratatoskr](../../ratatoskr)) and
boot from the in-memory source via shen-rust's `boot_from_kl_source` — no
filesystem dependency. (Exposing that through the C ABI is a small addition;
the current `shen_boot` takes a directory path.)

## Embedding a tree-shaken Shen program (shen-cas)

The `cas/` directory holds a worked example: the **shen-cas computer algebra
system**, tree-shaken by [ratatoskr](../../ratatoskr) and embedded in the binary.

Pipeline:
1. Flatten shen-cas's modules into one `.shen` (strip its `(load …)` directives).
2. `ratatoskr shake cas-all.shen out/` → `kernel.kl` (only the kernel functions
the CAS reaches — 298 KB vs the 749 KB full kernel) + `cas-all.kl`.
3. `include_str!` both into the crate; `shen_cas_boot` boots the slice via
`boot_from_kl_source` (kernel + `shen.initialise`) then loads the CAS program.
4. `shen_cas_reduce` calls the CAS's own `parse-expr-string → reduce →
pretty-expr` — no Shen-level `eval` required, so eval-stripping is fine.

```c
ShenCtx *shen_cas_boot(void);
char *shen_cas_reduce(ShenCtx *ctx, const char *expr); // "D[Sin[x],x]" -> "[Cos x]"
```

Verified Swift → Rust → CAS results (`swift/cas-demo.swift`):

```
D[Sin[x],x] => [Cos x]
D[x^3,x] => [Times [Power x 2] 3]
D[Exp[x],x] => [Exp x]
6/4 => [3 / 2]
```

> The CAS reducer is deeply recursive and runs tree-walked, so call the FFI from
> a thread with a large stack (the demo uses a 512 MB `Thread`; the shen-rust CLI
> does the same). This is the same reason an app should call Shen off the main
> thread.

## Status

Verified: the full shen-rust + shenffi stack **compiles for `aarch64-apple-ios`
and `aarch64-apple-ios-sim`**, packages into an `.xcframework`, and the
Swift→Rust→Shen round-trip runs correctly on macOS. The default shen-rust build
has **no JIT** (the optional Cranelift JIT is off by default), so nothing relies
on runtime code generation.
28 changes: 28 additions & 0 deletions crates/shenffi/build-xcframework.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Build ShenRust.xcframework (iOS device + simulator + macOS) from the shenffi
# crate. Output: <workspace>/target/ShenRust.xcframework — drag into an Xcode app,
# add the bridging header (shenffi.h) + ShenRust.swift, and you're done.
#
# The macOS slice (aarch64-apple-darwin) lets a native macOS target embed the
# same CAS — and, unlike the iOS simulator, MLX/Metal runs there, so the
# on-device model is exercisable on an Apple-silicon Mac.
set -euo pipefail

cd "$(dirname "$0")/../.." # workspace root (shen-rust/)
HDRS="crates/shenffi/include"

for tgt in aarch64-apple-ios aarch64-apple-ios-sim aarch64-apple-darwin; do
rustup target add "$tgt" >/dev/null 2>&1 || true
echo "building shenffi for $tgt ..."
cargo build --release -p shenffi --target "$tgt"
done

echo "packaging ShenRust.xcframework ..."
rm -rf target/ShenRust.xcframework
xcodebuild -create-xcframework \
-library target/aarch64-apple-ios/release/libshenffi.a -headers "$HDRS" \
-library target/aarch64-apple-ios-sim/release/libshenffi.a -headers "$HDRS" \
-library target/aarch64-apple-darwin/release/libshenffi.a -headers "$HDRS" \
-output target/ShenRust.xcframework

echo "done -> target/ShenRust.xcframework"
Loading