Skip to content

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.


Fe distinguishes three flavors of callable:

SourceType tagDefined whereExamples
Built-in (this page)FE_TPRIMCompiled into fe.c, dispatched by P_* enum+, cons, if
FFI primitive (../nosh-api/)FE_TCFUNCC function, registered via fe_set + fe_cfunctext-puts, psg-write, spawn-cell
Cart-definedFE_TFUNC / FE_TMACROLisp source in cartridgedefcell, 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.


All numeric ops operate on fe_Number (single-precision float). Variadic forms fold left.

Built-inSignatureSemantics
(+ a b ...)Number → Number → ... → NumberSum. (+) returns 0 by Fe convention.
(- a b ...)Number → Number → ... → NumberSubtract. Unary form negates: (- 5) → -5.
(* a b ...)Number → Number → ... → NumberProduct.
(/ a b ...)Number → Number → ... → NumberDivision. 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.


Built-inSignatureSemantics
(< a b)Number → Number → BoolStrict less-than.
(<= a b)Number → Number → BoolLess-or-equal.
(is a b)Any → Any → BoolIdentity / 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.)


Built-inSignatureSemantics
(cons a b)Any → Any → PairConstruct a pair. The fundamental allocator (allocates from the arena).
(car p)Pair → AnyFirst element. Errors if p is not a pair.
(cdr p)Pair → AnyRest. Errors if p is not a pair.
(setcar p v)Pair → Any → PairIn-place mutation of car. Returns p.
(setcdr p v)Pair → Any → PairIn-place mutation of cdr. Returns p.
(list a b ...)Any → ... → ListBuild 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.


Built-inSignatureSemantics
(not x)Any → BoolTrue if x is nil.
(atom x)Any → BoolTrue if x is not a pair (i.e., is nil, number, symbol, string, function, macro, prim, cfunc, or ptr).
(is a b)Any → Any → BoolSee “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.


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


Documented in detail in language-reference.md under “Special forms”; listed here for completeness:

Built-inSemantics
(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) / 'xSuppress evaluation.

Built-inSemantics
(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-inSemantics
(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.