fecs: a lisp interpreter running in wasm

Repl version - Repl+Sqlite3 version

fecs is a programming language in the style of PicoLisp and implemented as a lisp interpreter running in wasm. The implementation has the basic lisp read, eval and print (REPL), garbage collection, fixedpoint big numbers, conses/lists, symbols and strings. Only features from the WebAssembly lime1 standard are used. Therefore the wasm code can run in nearly all WebAssembly tools and settings.

The name 'fecs' stands for 'FExprs', 'cells' and 'symbols', which refer to the building blocks of the PicoLisp system. 'FExprs' are functions that get passed the unevaluated arguments, and are an alternative to Lisp macros. The 'cell' in PicoLisp is the only datastructure as a cons cell of 2 pointers (fecs uses 4 bytes per pointer). And 'symbols' are dynamically bound, rather than the more common lexical binding and scoping, which makes symbols represent an actual place in memory. These and other choices make fecs a useful and small system; the size of the .wasm file is 19kB.


Built-in functions
+ - / * % */
Math on fixed point numbers without size limitation: + addition, - subtraction, / division, * multiplication, % remainder
*/ is multiplication of all but the last arguments divided by the last argument
quote '
Returns all arguments unevaluated
prog
Excutes all expressions and returns the last result
set
Set the value of a symbol or the car of a cons
setq
Set the value of an unevaluated symbol
prop
(prop 'sym 'key) Gets the property (value . key) pair from the symbol. Will insert the property with value NIL if it does not yet exist
put
(put 'sym 'key 'value) Sets the property 'key on 'sym to 'value
get
(get 'sym 'key) Gets the value of property 'key on 'sym
getl
Gets the list of properties of a symbol
name
Gets the name of a symbol or string
cons
(cons 'any 'any) Creates a cons cell with the arguments in its car and cdr
car
Gets the car of a cons cell
cdr
Gets the cdr of a cons cell
con
(con 'cons 'val) Sets the cdr of a 'cons cell to 'val
loop
(loop .. (NIL 'cond . final-exprs) .. (T 'cond . final-exprs) .. ) Looping and conditional evaluation construct. The body of the loop is executed endlessly until a top level list clause starting with NIL or T its 'cond-itional evaluates to NIL or non-NIL respectively
eval
Evaluates an expression and returns the result
let
(let (sym 'val sym 'val ...) ...) Binds the symbols consecutively to the 'val and saves their previous values. The let form returns the final result and rebinds the symbols to the saved values
rest
('(@ (rest))) In a function with variable number of arguments (using @ as the parameter), (rest) returns the list of arguments
make
(make ... (link 1) .. (link 2) .. (link 3) ...) Convenient list constructor, the example returns (1 2 3), @ contains the last cons
link
Adds an element to end of the list of the surrounding make
prin
Prints its arguments to the output channel. Strings will be printed without quotes ("") and lists without parentheses
print
Prints its arguments to the output channel in their canonical form. String will be printed with quotes ("")
pack
Concatenates its arguments into a string
chop
Turns a string into a list of single character strings
any
(any 'string) Reads the 'string into data
quit
(quit 'msg) (quit 'msg 'arg) Stops the current execution with the error 'msg
up
(up 'sym 'val) (up 'level 'sym 'val) (up 'sym) Sets or gets the binding of 'sym in the 'level-nth scope
trail
(trail) (trail T) Returns the backtrace of all function calls. With T also returns the symbol bindings for each stack
atom
Returns true T if the argument is an atom (number, symbol or string)
=
Structured equality
==
Pointer equality
sort
(sort 'list) Sorts the list, NIL is always ordered lowest, numbers sort before symbols and symbols before lists, T is always ordered highest. Sort is destructive by reusing the cells from 'list but altering their cdr's
<
Less than. Defined for all data in fecs to create the order as specified for (sort)
flip
Destructively reverses a list
box
Creates a new uninterned symbol
catch
(catch '(NIL) Prg) If the evaluation of Prg includes a (quit "any string msg") then catch will return the string quit string and @@ will be T
zap
(zap 'Symbol) Unintern the symbol from the namespace, thus the next read of 'Symbol will be a new symbol
Loaded functions from prelude
# (de 'sym 'def) (setq de '(E (set (car E) (cdr E)) ) ) (zap 'E) # (range A B) Returns a list from A upto and including B (de range (A B) (make (loop (link A) (T (= A B)) (setq A (+ A 1)) ) ) ) # (apply 'fn 'list) Applies the 'fn to the 'list of arguments (de apply E (eval (let (Ls (eval (car (cdr E))) Ls (make (loop (NIL Ls) (link (cons 'quote (car Ls))) (setq Ls (cdr Ls)) ) ) ) (cons (car E) Ls)) ) ) (zap 'E) (de list @ (rest)) # Logical and shortcuts on first NIL result. Non-NIL results are set into @ (de and E (loop (NIL (eval (car E))) (up @ @) (T (atom (setq E (cdr E))) (up @)) ) ) (zap 'E) (de or E (loop (T (eval (car E)) (up @ @)) (NIL (setq E (cdr E))) ) ) (zap 'E) (de not E (loop (NIL (eval (car E)) T) (up @ @) (NIL) ) ) (zap 'E) (de if E (loop (T (eval (car E)) (eval (car (cdr E)))) (NIL NIL (run (cdr (cdr E))))) ) (zap 'E) (de run (E) (let (Res NIL) (loop (T (== NIL E) Res) ;; don't set @ (setq Res (eval (car E))) (setq E (cdr E))) ) ) (zap 'E) (zap 'Res) (de unless E (let (Cond (eval (car E))) (loop (NIL Cond (run (cdr E))) (T T) ) ) ) (zap 'E) (zap 'Cond) (de while E (loop (NIL (eval (car E))) (run (cdr E)) ) ) (zap 'E) # (for Elem List ... (NIL Cond . Prg) ... (T Cond . Prg) ...) # (for (Idx . Elem) List ... (NIL Cond . Prg) ... (T Cond . Prg) ...) (de for E (eval (let (IdxSym NIL ItemSym (car E) ItemSym (if (atom ItemSym) # I or (Idx . X) ItemSym (setq IdxSym (car ItemSym)) (set IdxSym 0) (cdr ItemSym) ) LBox (box) Body (cdr (cdr E)) BodyTail Body ) (loop (NIL (cdr BodyTail)) (setq BodyTail (cdr BodyTail)) ) (con BodyTail (cons (list 'NIL (list 'setq LBox (list 'cdr LBox))))) (set LBox (eval (car (cdr E)))) (cons 'loop (cons (list NIL LBox T IdxSym ItemSym (atom (car E))) (cons (list 'setq ItemSym (list 'car LBox)) (if IdxSym (cons (list 'setq IdxSym (list '+ IdxSym 1)) Body ) Body) ) ) ) ) ) ) (zap 'E) # (finally Exe . Prg) Always executes Exe after Prg, also if Prg had a (quit) (de finally E (let (Res (catch '(NIL) (run (cdr E)) ) ) (if @@ (prog (eval (car E)) (quit Res) ) (prog (eval (car E)) Res) ) ) ) (zap 'E) (zap 'Res) (prin "Prelude loaded" "\n" T)
Examples
Large numbers
(apply * (range 1 40))
Symbols
(setq Lang "fecs") (set (prop 'Lang 'impl) 'wasm) (put 'Lang 'gc T) (put 'Lang 'strings "uninterned") (cons Lang (getl 'Lang))
Functions
(setq triplist '((A B C) (cons A (cons B (cons C))) ) ) (triplist 10 (/ 160 8) 30)
Vararg function
(de squarelist @ (let (Args (rest)) (make (loop (NIL Args) (link (* (car Args) (car Args))) (NIL (setq Args (cdr Args))) ) ) ) ) (squarelist 1 2 3 4)
Fexpr
(de if Exe (loop (T (eval (car Exe)) (eval (car (cdr Exe)))) (NIL NIL (run (cdr (cdr Exe)))) ) ) (if (= 4 (* 2 2)) (print "True branch" @) (print "False branch") )
Sort
(sort '(T abc NIL 0 -10 'A 4 "hello" "WORLD" (12 . "haha") '(A B "LOL") '(A B C)))
@ result
(and 10 (+ @ 2) )
Recur/recurse
(de recur recurse (run (cdr recurse)) ) (de fibo (N) (recur (N) (if (< N 3) 1 (+ (recurse (- N 1)) (recurse (- N 2)) ) ) ) ) (fibo 10)
While and @ result
(setq L (1 2 3 4 5)) (while (car L) # @ holds result of conditional (prin @ "\n") (setq L (cdr L)))

Output: Click on output to copy to input
Input: Enter to submit, Shift+Enter for newline

(c) 2026 thegeez.net