Skip to content

CCurl/qwc

Repository files navigation

QWC: a very minimal Forth

Version 2026.02.24

QWC is a minimal Forth system that can run stand-alone or be embedded into another program.

QWC is implemented in 3 files: (qwc-vm.c, qwc-vm.h, system.c).
The QWC VM is implemented in under 200 lines of code.
QWC has 64 primitives.
The primitives are quite complete and any Forth system can be built from them.

In a QWC program, each instruction is a single CELL.

  • By default, a CELL is a QWord, 64-bits, but it can also be 32-bits.
  • If <= the last primitive (system), then it is a primitive.
  • Else, if it matches a quiet NaN pattern (64-bit only), then it is a literal.
  • Else, it is the XT (code address) of a word in the dictionary.

QWC hard-codes the following IMMEDIATE state-change words:

Word Action
: Add the next word to the dictionary, set STATE to COMPILE (1).
; Compile EXIT and change STATE to INTERPRET (0).

NOTE: '(' skips words until the next ')' word.
NOTE: '\' skips words until the end of the line.
NOTE: Setting STATE to 999 signals QWC to exit.

INLINE words

An INLINE word is somewhat similar to a macro in other languages.
When a word is INLINE, its definition is copied to the target, up to the first EXIT.
When not INLINE, a call is made to the word instead. NOTE: if the next
instruction is EXIT, it becomes a JUMP instead (the tail-call optimization).

Transient words

Words 't0' through 't9' are transient and are not added to the dictionary.
They are case sensitive: 't0' is a transient word, 'T0' is not.
They help with factoring code and and keep the dictionary uncluttered.
They can be reused as many times as desired.

Built-in variables

There are 3 built-in variables x, y, and z. There are also +L and -L that can
be used to create 3 local variables under the user's control. +L and -L can be used
at any time for any reason to create a new frame for new versions of the variables.

QWC Startup Behavior

On startup, QWC does the following:

  • Create 'argc' with the count of command-line arguments
  • For each argument, create 'argX' with the address of the argument string
  • E.G. "arg0 ztype" will print qwc
  • If arg1 exists and names a file that can be opened, load that file.
  • Else, try to load file 'qwc-boot.fth' in the local folder '.'.
  • Else, try to load file 'BIN_DIRqwc-boot.fth' in the "bin" folder.
  • On Linux, BIN_DIR is "/home/chris/bin/".
  • On Windows, BIN_DIR is "D:\bin\".
  • BIN_DIR is defined in qwc-vm.h. Adjust it in qwc-vm.h for your system if needed.

The VM Primitives

Primitive Op/Word Stack Description
0 exit (--) PC = R-TOS. Discard R-TOS. If (PC=0) then stop.
1 lit (--) Push code[PC]. Increment PC.
2 jmp (--) PC = code[PC].
3 jmpz (n--) If (n==0) then PC = code[PC] else PC = PC+1.
4 jmpnz (n--) If (n!=0) then PC = code[PC] else PC = PC+1.
5 njmpz (n--n) If (n==0) then PC = code[PC] else PC = PC+1.
6 njmpnz (n--n) If (n!=0) then PC = code[PC] else PC = PC+1.
7 dup (n--n n) Duplicate n.
8 drop (n--) Discard n.
9 swap (a b--b a) Swap a and b.
10 over (a b--a b a) Push a.
11 ! (n a--) CELL store n through a.
12 @ (a--n) CELL fetch n through a.
13 c! (b a--) BYTE store b through a.
14 c@ (a--b) BYTE fetch b through a.
15 >r (n--) Move n to the return stack.
16 r@ (--n) Copy n from the return stack.
17 r> (--n) Move n from the return stack.
18 +L (--) Create new versions of variables (x,y,z).
19 -L (--) Restore the last set of variables.
20 x! (n--) Set local variable X to n.
21 y! (n--) Set local variable Y to n.
22 z! (n--) Set local variable Z to n.
23 x@ (--n) Push local variable X.
24 y@ (--n) Push local variable Y.
25 z@ (--n) Push local variable Z.
26 x@+ (--n) Push local variable X, then increment it.
27 y@+ (--n) Push local variable Y, then increment it.
28 z@+ (--n) Push local variable Z, then increment it.
29 * (a b--c) c = a*b.
30 + (a b--c) c = a+b.
31 - (a b--c) c = a-b.
32 /mod (a b--r q) q = a/b. r = a modulo b.
33 1+ (a--b) b = a+1.
34 1- (a--b) b = a-1.
35 < (a b--f) If (a<b) then f = 1 else f = 0.
36 = (a b--f) If (a=b) then f = 1 else f = 0.
37 > (a b--f) If (a>b) then f = 1 else f = 0.
38 0= (n--f) If (n==0) then f = 1 else f = 0.
39 min (a b--c) If (a < b) c = a else b.
40 max (a b--c) If (a > b) c = a else b.
41 +! (n a--) Add n to the cell at a.
42 for (C--) Start a FOR loop starting at 0. Upper limit is C.
43 i (--I) Push current loop index I.
44 next (--) Increment I. If I < C then jump to loop start.
45 and (a b--c) c = a and b.
46 or (a b--c) c = a or b.
47 xor (a b--c) c = a xor b.
48 ztype (a--) Output null-terminated string a.
49 find (--a) Push the dictionary address a of the next word.
50 key (--n) Push the next keypress n. Wait if necessary.
51 key? (--f) Push 1 if a keypress is available, else 0.
52 emit (c--) Output char c.
53 fopen (nm md--fh) Open file nm using mode md (fh=0 if error).
54 fclose (fh--) Close file fh. Discard TOS.
55 fread (a sz fh--n) Read sz chars from file fh to a.
56 fwrite (a sz fh--n) Write sz chars to file fh from a.
57 ms (n--) Wait/sleep for n milliseconds
58 timer (--n) Push the current system time n.
59 add-word (--) Add the next word to the dictionary.
60 outer (str--) Run the outer interpreter on str.
61 cmove (f t n--) Copy n bytes from f to t.
62 s-len (str--n) Determine the length n of string str.
63 system (str--) Execute system(str).

Other built-in words

Word Stack Description
version (--n) Current version number.
output-fp (--a) Address of the output file handle. 0 means STDOUT.
(h) (--a) Address of HERE.
(l) (--a) Address of LAST.
(lsp) (--a) Address of the loop stack pointer.
lstk (--a) Address of the loop stack.
(rsp) (--a) Address of the return stack pointer.
rstk (--a) Address of the return stack.
(sp) (--a) Address of the data stack pointer.
stk (--a) Address of the data stack.
state (--a) Address of STATE.
base (--a) Address of BASE.
mem (--a) Address of the beginning of the memory area.
mem-sz (--n) The number of BYTEs in the memory area.
>in (--a) Address of the text input buffer pointer.
cell (--n) The size of a CELL in bytes (4 or 8).

VM Architecture Overview

QWC uses a stack-based virtual machine with three main stacks:

  • Data Stack: For operands and results (pointers: dsp, stk).
  • Return Stack: For return addresses and loop indices (pointers: rsp, rstk).
  • Loop Stack: For FOR/NEXT loops (pointers: lsp, lstk).

Memory is divided into:

  • Code Area: Starts at mem[0], holds compiled code and dictionary.
  • Dictionary: Grows downward from mem[MEM_SZ], storing word definitions.
  • Variables: User variables in the vars area.

Instructions are CELL-sized (32/64-bit). Primitives (0-63) execute directly; literals use NaN boxing (64-bit); others are XT calls.

Compilation and Execution Details

  • STATE: 0 (INTERPRET) executes words; 1 (COMPILE) adds them to the dictionary.
  • Literals: Small numbers (fitting in 51-bit payload) use NaN boxing; larger ones use LIT + value.
  • Inlining: Copies word definitions up to EXIT for macros.
  • Tail-Call Optimization: EXIT after a call becomes a JMP.
  • Execution starts with inner(pc), dispatching via a switch on the opcode.

Build and Run Instructions

  • Compile: Run make (requires a C compiler, sets ARCH=64 by default).
  • Run REPL: ./qwc
  • Load file: ./qwc filename.fth
  • Clean: make clean

Forth Code Examples

Define a word: : square dup * ; (squares TOS).
Loop: 10 for i . next (prints 0 to 9).
Conditional: 5 3 > if 42 . then (prints 42 if true).

Internal Functions Summary

Function Description
inner(pc) Executes code starting at pc, dispatching primitives/literals/calls.
outer(src) Parses and interprets/executes Forth source string.
addToDict(w) Adds word w to dictionary, returns entry pointer.
findInDict(w) Searches dictionary for word w, returns pointer or 0.
compileNum(n) Compiles number n as literal.
qwcInit() Initializes primitives and built-in words.

qwc-boot.fth Role

qwc-boot.fth is the bootstrap file loaded on startup, defining higher-level Forth words (e.g., if, begin, variables). It builds on primitives to create a usable language. Edit it to customize the system.

Embedding QWC in your C or C++ project

See system.c. It embeds the QWC VM into a C program.

Example usage:

#include "qwc-vm.h"
qwcInit();
outer("your forth code here");

About

A very minimal QWord cell based Forth

Resources

License

Stars

Watchers

Forks

Contributors