Native words are Frothy words whose implementation is compiled from C. They are part of a library. There is no separate foreign-call syntax.
lib.toml adds [extension] for C sources and one [[natives]] block per C
word:
name = "neopixel"
version = "0.1.0"
boards = ["esp32_devkit_v1"]
[extension]
sources = ["native/neopixel.c"]
[[natives]]
name = "neopixel.show"
arity = 1
c_function = "fr_lib_neopixel_show"
[extension].sourceslists.cfiles, with paths relative to the library root.- Each
[[natives]]maps a Frothy wordnameandarityto a C function. - Native word names usually look like
<lib>.<word>. They may contain letters, digits,.,_, and-, but may not start with-or a digit.
C ABI
A native C function has exactly this signature:
#include "runtime.h"
#include "tagged.h"
#include "types.h"
fr_err_t fr_lib_neopixel_show(fr_runtime_t *runtime, const fr_tagged_t *args,
uint8_t arg_count, fr_tagged_t *out) {
fr_int_t count = 0;
FR_TRY(fr_tagged_decode_int(args[0], &count));
/* ... drive the hardware ... */
return fr_tagged_encode_int(count, out);
}
The return type is fr_err_t. The parameters are fixed:
(fr_runtime_t *runtime, const fr_tagged_t *args, uint8_t arg_count, fr_tagged_t *out).
Values cross the boundary as tagged values. Decode integer arguments with
fr_tagged_decode_int(args[i], &n), where n is fr_int_t. Test values with
fr_tagged_is_int, fr_tagged_is_nil, fr_tagged_is_bool, or
fr_tagged_is_falsy. Produce results by writing *out with
fr_tagged_encode_int(v, out) or fr_tagged_encode_bool(b, out).
FR_TRY(expr) propagates a non-OK fr_err_t.
Generated Build Files
The C compiles into the firmware. frothy build runs a generator that writes
derived files under .frothy/build/<board>/:
lib_natives.c, a table binding each native word name to its C functionlibs.cmake, the extension sources and includes for the firmware build
Do not edit generated files by hand.
Persistence
Values that cross the boundary are Int, Bool, Nil, and, where a word supports it, Text, Bytes, or Handle.
A Handle is a tagged Frothy value that names an entry in the runtime handle table. The entry records a resource kind, generation, and platform resource index. Do not expose a raw pointer or invent an integer resource ID.
The handle kinds and platform close path are core contracts. A manifest-only library cannot invent a safe new Handle kind; adding a handle-bearing capability requires corresponding core and platform support.
Bytes and Handles are volatile. Pack Bytes into Text for persistence. A Handle
may live in a top-level slot during a session, but save rejects the overlay
while it remains there. Neither belongs in cells or record fields. Close open
resources and rebind top-level Handle names before save; reopen them from
boot after restore.
The Concepts guide gives the complete reusable pattern in Persist the Recipe, Not the Handle .
Native words live in the firmware base image. A saved overlay references them by name. At boot, the native implementation is rebuilt from firmware, so saved images stay pointer-safe.