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_END

PEKO_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.