The .peko.h header
The .peko.h header
A .peko.h is a C header that doubles as an FFI surface. The compiler reads it
and generates the PekoScript declarations, so one file serves both sides.
#include <peko.h>
PEKO_BEGIN
/* FNV-1a over a managed byte buffer. */
p_fn p_i64 peko_hash_bytes(p_gc(p_i8) data, p_i64 len);
/* Hash an f64 by its full bit pattern. */
p_fn p_i64 peko_hash_f64(p_f64 value);
PEKO_ENDPEKO_BEGIN and PEKO_END give the declarations C linkage under a C++
compiler. The FFI parser ignores them.
The markers
Only two forms are visible to the parser, and each needs its marker:
| Form | Becomes |
|---|---|
p_fn RET name(T a, ...); |
an external function |
p_var T name; |
an external variable |
A declaration without its marker is invisible. That is the mechanism for
declaring C-internal helpers in the same header: leave off p_fn and the
compiler never sees them. It is also an easy mistake, because forgetting the
marker produces no error, just a missing symbol at link time.
The type map
| C alias | PekoScript |
|---|---|
p_i1, p_bool |
i1 |
p_ch, p_i8 |
i8 |
p_i16, p_i32, p_i64 |
i16, i32, i64 |
p_f16, p_f32, p_f64 |
f16, f32, f64 |
p_cstr |
cstr |
p_opaque |
opaque |
p_gc_opaque |
pointer<void> |
p_gc(T) |
pointer<T> |
void |
void |
p_gc(...) marks a managed pointer, which the collector traces and may move.
p_opaque is unmanaged: a malloc block or an OS handle.
Raw * is rejected. Use one of the aliases so the managed and unmanaged
cases stay distinguishable.
There is no way to export a C struct, enum, or #define constant. Struct-shaped
state crosses as p_opaque, a handle the PekoScript side treats as a blob.
Reserved words
The generated declarations are parsed as ordinary PekoScript, so every PekoScript keyword is illegal as a parameter, function, or variable name in a header.
Three cause almost all the trouble, because they read as ordinary C names:
fn, in, and arch.
p_fn p_i32 demo_call(p_opaque fn, p_i32 in); /* fails to parse */
p_fn p_i32 demo_call(p_opaque callback, p_i32 count); /* fine */C parameter names are not part of the ABI, so the header and the C definition may name them differently. The standard library does exactly this where a name would collide.
The full reserved list is in the language reference.
p_gcsafe
By default a C function is assumed to be a leaf: it does not allocate, does not block, and does not call back into PekoScript. That assumption is what makes passing a managed buffer into a call safe without pinning.
When it is not true, say so:
p_fn p_gcsafe p_i32 peko_run_event_loop(p_opaque handle);p_gcsafe goes before the return type. It tells the compiler to keep the call as
a safepoint, so live managed pointers stay correct across it. Mark any function
that can allocate, block, or re-enter PekoScript.
Importing it
A header is resolved like any other module, and it is the last candidate: a
sibling .peko file with the same name shadows it.
import c::random::random as cmwc;
let value: i32 = cmwc::peko_random_int(lo, hi)The declarations stay in their own module, so they are reached through the alias rather than through a global namespace. Names are not mangled, so the C symbol is exactly the declared name, and two packages exporting the same name collide at link time.