✓
Passing This code compiles and runs correctly.
Code
// PINS: how a run of NUMBERS crosses the C boundary under `koruc lib`.
//
// Text already crosses as a pointer and a length (140_018). A numeric buffer
// is the same move, because C has exactly one way to hand over a run of values
// it does not own:
//
// pub tor apply-gain { input: []const f32, output: []f32, gain: f32 }
// export fn apply_gain(input_ptr: [*]const f32, input_len: usize,
// output_ptr: [*]f32, output_len: usize, gain: f32) void
//
// The MUTABLE side is the load-bearing half: `[]f32` becomes `[*]f32`, and the
// callee writes back through it. That is the entire calling convention of every
// audio and signal-processing C API — a host owns both buffers, hands over
// borrowed views, and reads the result out afterwards.
//
// `u8` is deliberately NOT a buffer element here: a run of bytes is text, it is
// spelled `string`, and two Koru spellings reaching one C shape would leave an
// exported header ambiguous about which one a caller is looking at.
//
// This runs as a PROGRAM, pinning the shapes still work normally. `post.sh`
// then builds the SAME file as a library, links hand-written C against it, and
// checks the samples — the C boundary is checked at the boundary, not by
// reading the emitted source.
~import std/io
~pub tor apply-gain { input: []const f32, output: []f32, gain: f32 }
~proc apply-gain|zig {
for (input, output) |s, *o| {
o.* = s * gain;
}
}
~pub tor sum-buffer { values: []const f32 } -> f32
~proc sum-buffer|zig {
var total: f32 = 0;
for (values) |v| total += v;
return total;
}
~sum-buffer(values: [0.5, 0.25, 0.25]): t |> std/io:print.ln("sum is {{ t:f }}")
Actual
sum is 1
Expected output
sum is 1
Flows
flow ~sum-buffer click a branch to expand · @labels scroll to their anchor
sum-buffer (values: [0.5, 0.25, 0.25])
Test Configuration
MUST_RUN
Post-validation Script:
#!/bin/bash
# Check the C boundary AT the boundary: build this same file as a library,
# link hand-written C against it, and compare the samples. Reading the emitted
# Zig would only prove the emitter wrote what the emitter meant to write.
set -e
cd "$(dirname "$0")"
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
cp input.kz host.c "$work"/
cd "$work"
koruc lib input.kz > lib.out 2>&1 || { echo "FAIL: koruc lib did not build"; cat lib.out; exit 1; }
# Both buffers must arrive as a pointer AND a length, and the OUTPUT one must
# be mutable — that is the half an audio plugin writes back through.
sig=$(grep -m1 '^export fn apply_gain' output_emitted.zig || true)
if [ -z "$sig" ]; then
echo "FAIL: apply_gain was not exported at all"
grep -A4 'C ABI exports' output_emitted.zig || true
exit 1
fi
case "$sig" in
*"input_ptr: [*]const f32, input_len: usize"*) ;;
*) echo "FAIL: the const buffer did not cross as pointer+length"; echo " $sig"; exit 1 ;;
esac
case "$sig" in
*"output_ptr: [*]f32, output_len: usize"*) ;;
*) echo "FAIL: the mutable buffer did not cross as a writable pointer+length"; echo " $sig"; exit 1 ;;
esac
zig build-lib output_emitted.zig -dynamic -lc -O ReleaseFast --name korugain > build.out 2>&1 || {
echo "FAIL: the emitted library did not build"; tail -20 build.out; exit 1; }
cc host.c -L. -lkorugain -Wl,-rpath,. -o host 2> link.err || {
echo "FAIL: hand-written C did not link against the Koru library"; cat link.err; exit 1; }
./host