Fe Lisp — Built-ins
Fe ships a tight set of compiled-in primitives — arithmetic, list ops, predicates, control flow, and minimal I/O. This page indexes them with one-line semantics and the type signature each accepts. Cartridge authors and runtime engineers read this to know what’s resident in the language vs. what comes from the platform FFI.
language-reference.md— syntax, types, special-form mechanics.memory-model.md— arena rules that govern allocation here.../../../adr/ADR-0001-embedded-lisp-scripting-layer.md,../../../adr/ADR-0004-vm-selection.md— Fe selection rationale.../nosh-api/primitives-by-category.md— the runtime surface (display, input, audio, mission, cell). Built-ins on this page are language-resident; NoshAPI primitives are runtime-resident.
What “built-in” means here
Section titled “What “built-in” means here”Fe distinguishes three flavors of callable:
| Source | Type tag | Defined where | Examples |
|---|---|---|---|
| Built-in (this page) | FE_TPRIM | Compiled into fe.c, dispatched by P_* enum | +, cons, if |
FFI primitive (../nosh-api/) | FE_TCFUNC | C function, registered via fe_set + fe_cfunc | text-puts, psg-write, spawn-cell |
| Cart-defined | FE_TFUNC / FE_TMACRO | Lisp source in cartridge | defcell, defmission, anything cart-authored |
Built-ins are the same on every device, every cart, every load — they are the language. NoshAPI primitives are the runtime contract (ADR-0005, currently 54 primitives). See ../nosh-api/ for the full FFI surface — built-ins below are distinct from NoshAPI; everything player-facing or device-side lives in NoshAPI.
Arithmetic
Section titled “Arithmetic”All numeric ops operate on fe_Number (single-precision float). Variadic forms fold left.
| Built-in | Signature | Semantics |
|---|---|---|
(+ a b ...) | Number → Number → ... → Number | Sum. (+) returns 0 by Fe convention. |
(- a b ...) | Number → Number → ... → Number | Subtract. Unary form negates: (- 5) → -5. |
(* a b ...) | Number → Number → ... → Number | Product. |
(/ a b ...) | Number → Number → ... → Number | Division. Float division — (/ 7 2) → 3.5. |
There is no integer-vs-float distinction (everything is float), no modulo built-in, no abs/sqrt/trig — those would be FFI additions if any cart ever needed them.
Comparison
Section titled “Comparison”| Built-in | Signature | Semantics |
|---|---|---|
(< a b) | Number → Number → Bool | Strict less-than. |
(<= a b) | Number → Number → Bool | Less-or-equal. |
(is a b) | Any → Any → Bool | Identity / structural equality. Numbers compare by value; strings compare structurally; symbols and other atoms compare by reference. |
There is no >, >=, or = built in. Carts wrap inverted comparison with (not (<= a b)) or rely on the runtime to ship comparison helpers. (The reference cartridge samples/icebreaker.lsp uses > as a domain-vocabulary token via NoshAPI; it is not Fe-resident.)
List operations
Section titled “List operations”| Built-in | Signature | Semantics |
|---|---|---|
(cons a b) | Any → Any → Pair | Construct a pair. The fundamental allocator (allocates from the arena). |
(car p) | Pair → Any | First element. Errors if p is not a pair. |
(cdr p) | Pair → Any | Rest. Errors if p is not a pair. |
(setcar p v) | Pair → Any → Pair | In-place mutation of car. Returns p. |
(setcdr p v) | Pair → Any → Pair | In-place mutation of cdr. Returns p. |
(list a b ...) | Any → ... → List | Build a proper list of the evaluated arguments. |
No nth, length, append, reverse, member, or assoc are built-in. These are commonly shipped as runtime helpers (Lisp source loaded once at boot) when carts need them. Cartridge authors writing performance-sensitive list traversal use while + setcar/setcdr rather than recursive helpers.
Predicates
Section titled “Predicates”| Built-in | Signature | Semantics |
|---|---|---|
(not x) | Any → Bool | True if x is nil. |
(atom x) | Any → Bool | True if x is not a pair (i.e., is nil, number, symbol, string, function, macro, prim, cfunc, or ptr). |
(is a b) | Any → Any → Bool | See “Comparison” above; doubles as the equality predicate. |
There is no separate null?, pair?, number?, symbol?, string?, function?. The runtime exposes type-tag inspection via FFI if a cart needs it. In practice, carts use (not x) for null tests and atom for leaf detection.
Higher-order forms
Section titled “Higher-order forms”Fe does not ship map, filter, fold, reduce as built-ins. These are runtime-shipped helpers loaded into every cart’s environment at boot. Cartridge code such as samples/icebreaker.lsp calls (map ...) and (filter ...) as if they were primitives — they’re cart-environment functions, not FE_TPRIM.
If a cart wants its own iteration:
(= my-map (fn (f xs) (if xs (cons (f (car xs)) (my-map f (cdr xs))) nil)))…or, for tight loops, while with explicit accumulators (no recursion depth concern; see TCO note in language-reference.md).
Control flow
Section titled “Control flow”Documented in detail in language-reference.md under “Special forms”; listed here for completeness:
| Built-in | Semantics |
|---|---|
(if cond then else?) | Conditional. |
(and a b ...) | Short-circuit AND. |
(or a b ...) | Short-circuit OR. |
(while cond body...) | Iteration. |
(do expr...) | Sequence. |
(quote x) / 'x | Suppress evaluation. |
Binding and definition
Section titled “Binding and definition”| Built-in | Semantics |
|---|---|
(let sym value) | Bind sym in current scope to value. Returns value. |
(= sym value) | Assignment. Same shape as let; conventionally used for re-binding existing names and for top-level definitions. |
(fn (params) body...) | Construct a closure. |
(mac (params) body...) | Construct a macro. |
There is no define, defun, defn, or defmacro. Cartridge authoring macros (defcell, defmission, defstruct, defdomain) are runtime-defined macros loaded into the cart environment, not Fe primitives. They expand to (= name (fn ...)) shapes plus runtime registration calls.
Fe’s only I/O primitive is:
| Built-in | Semantics |
|---|---|
(print x ...) | Write each x to the configured fe_WriteFn, then a newline. The REPL routes this to its output buffer; cartridges generally do not call print because text rendering on the device goes through NoshAPI’s text-puts / text-printf (see ../nosh-api/primitives-by-category.md). |
There is no file I/O, no socket I/O, no read-from-string. The reader (fe_read, fe_readfp) is C-only and not exposed to Lisp. Scripted missions are explicitly forbidden from re-entering eval or loading code at runtime (ADR-0007 Tier 3).
Quick lookup: full built-in list (source order)
Section titled “Quick lookup: full built-in list (source order)”From primnames[] in kn86-emulator/vendor/fe/fe.c:
let, =, if, fn, mac, while, quote, and, or, do,cons, car, cdr, setcar, setcdr, list, not, is, atom, print,<, <=, +, -, *, /26 entries total. Anything you call that isn’t in this list and isn’t cart-defined is coming from the runtime FFI layer — go read ../nosh-api/primitives-by-category.md.