Declaring externals in PekoScript
Declaring externals in PekoScript
A .peko.h header is the usual way to reach C, because one file serves both
sides. But the declarations it generates are ordinary PekoScript, and you can
write them yourself.
The syntax
[external] fn strlen(str: cstr) => i64;
[external] fn memcpy(dst: pointer<void>, src: opaque, count: i64);
[external] fn peko_random_int(min: i32, max: i32) => i32;A declaration has no body: it ends at the semicolon. The name is the exact C
symbol, because [external] suppresses mangling.
Variables work the same way:
[external] let current_line: i32;
[external] let current_file: cstr;The types you may use
Only the raw FFI types cross the boundary. The boxed value types do not.
| PekoScript | C |
|---|---|
i1 |
_Bool |
i8, i16, i32, i64 |
the sized integers |
f16, f32, f64 |
the sized floats |
cstr |
const char *, unmanaged |
opaque |
void *, unmanaged |
pointer<T> |
a managed pointer the collector may move |
pointer<void> |
a managed opaque pointer |
void |
no return |
Pass a string by unwrapping it first, and box a scalar result on the way back:
let length: i64 = strlen(text.to_raw())
let count: number = new number(danger_cast<f64>(length))Modifiers on an external
[external gcsafe] fn peko_run_loop(handle: opaque);
[external variadic] fn printf(fmt: cstr) => i32;gcsafe marks a call that can allocate, block, or call back into PekoScript, so
the compiler keeps it as a safepoint. Leaving it off declares the function a leaf
and is what makes passing a managed buffer safe without pinning. Getting this
wrong is a silent correctness bug, not a compile error.
variadic allows trailing arguments.
The extern module
Where the declaration lives depends on how it arrived.
A declaration imported through a .peko.h stays in its own module, so it is
reached through the import alias:
import c::random::random as cmwc;
let value: i32 = cmwc::peko_random_int(lo, hi)A declaration written by hand with [external] moves into a reserved top-level
module called extern, and is reached through that prefix:
[external] fn __create_globals();
extern::__create_globals()extern is always present and auto-imported. Importing it explicitly is an
error.
The practical rule: if you wrote the declaration yourself, call it through
extern::. If it came from a header, call it through the header's alias.
Which to choose
Prefer a header. It keeps the C prototype and the PekoScript declaration in one place, so they cannot drift, and it gives the C compiler the same prototypes it gives the PekoScript compiler.
Write declarations by hand when there is no header to own them: a handful of libc functions, or a symbol the runtime provides that no package declares. The standard library does both, and reserves the by-hand form for exactly those cases.
Linking an object directly
A prebuilt object can be pulled into the link from source rather than through the manifest:
link vendor::libfoo as archiveThe form is link path::to::file as object|lib|archive, resolved relative to the
current source file. [native.libs] in the manifest is the maintained path and
the one to prefer; this exists for one-off cases.