- cf is inspired by ColorForth, but it is NOT ColorForth.
- cf has 4 states: DEFINE, COMPILE, INTERPRET, and COMMENT.
- cf uses markers in the source whitespace to control its state.
- cf also supports using the standard words to change the state.
- cf has
aandbregisters like ColorForth. It also has atregister. - Unlike ColorForth, they are actually stacks. See below for more information.
| Byte | Word(s) | New State |
|---|---|---|
| $01 | ":" | DEFINE |
| $02 | "]" or ")" | COMPILE |
| $03 | "[" or "))" | INTERPRET |
| $04 | "(" or "((" | COMMENT |
| ";" | INTERPRET |
- DEFINE changes the state to COMPILE after adding the word to the dictionary.
- ";" compiles EXIT and changes the state to INTERPRET.
- There is no difference between
(and((, they make the code more readable. - CF still supports IMMEDIATE words.
- Flow control words like IF/THEN would be marked as IMMEDIATE.
- They are defined in the source file.
(( A comment in INTERPRET mode ))
: hello ( A comment in COMPILE mode ) ." Hello World!" ;
hello
In cf, a program is a sequence of OPCODEs.
An OPCODE is an unsigned CELL (32 or 64 bits).
Primitives are assigned sequentially from 0 to (bye).
If an OPCODE is less than or equal to (bye), it is a primitive.
Else if the top 3 bits are set, it is a literal with the top 3 bits masked off.
Else, it is the CODE slot (XT) of a word to execute.
CODE slots 0-24 are used by cf. Slots 25-(bye) are unused by CF.
They are free CELL-sized slots that can be used for any purpose.
HERE starts at (bye)+1.
In ColorForth, 'a', 'b' and 't' are registers. In CF, they are stacks.
They have operations similar to those for the return stack (>r, r@, r>).
The operations for the 'a' stack are: >a, a@, a>, a!, a@+, and a@-.
The operations for the 'b' and 't' stacks are the same.
CF is really just a Forth VM, upon which any Forth system can be built.
To that end, cf defines a set of primitives and the inner/outer interpreters.
The list of primitives and their definitions is detailed below.
The rest of the system is defined by the source code file.
CF takes a source file name as its only argument.
If cf is executed without arguments, the source file defaults to BOOT_FN1.
If BOOT_FN1 (cf-boot.fth) is not found, it tries BOOT_FN2.
BOOT_FN1 and BOOT_FN2 are defined in cf.h. Change them as appropriate.
CF provides a single chunk of memory (see cf.h, MEM_SZ) for data and code.
The CODE area starts at the beginning of the memory.
The start of the data area (VHERE) is defined in the source file.
CF can easily be embedded into a C program.
The cf VM is implemented in cf.c.
The configuration settings and API for cf are located in cf.h.
File system.c embeds the CF VM into an executable.
Building cf is simple and fast since there are only 3 small source files.
32-bit or 64-bit builds are supported.
Windows
- There is a
cf.slnfile for Visual Studio.
Linux, OpenBSD, and FreeBSD
- There is a makefile, which uses the system C compiler (specified by the CC variable).
- Or you can easily build cf from the command line:
- Example:
# the default is 64 bit cells:
make
# for 32 bit cells:
ARCH=32 make
# or manually:
$CC -m32 -O3 -o cf *.c
$CC -m64 -O3 -o cf *.c
The CF primitives defined in the PRIMS macro in cf.c.
| Word | Stack Effect | Description |
|---|---|---|
dup |
(n--n n) | Duplicate top of stack |
swap |
(a b--b a) | Swap top two stack items |
drop |
(n--) | Remove top of stack |
over |
(a b--a b a) | Copy second item to top |
| Word | Stack Effect | Description |
|---|---|---|
@ |
(addr--n) | Fetch cell from address |
! |
(n addr--) | Store cell to address |
c@ |
(addr--c) | Fetch byte from address |
c! |
(c addr--) | Store byte to address |
+! |
(n addr--) | Add n to cell at address |
| Word | Stack Effect | Description |
|---|---|---|
+ |
(a b--sum) | Add two numbers |
- |
(a b--diff) | Subtract (a-b) |
* |
(a b--prod) | Multiply two numbers |
/ |
(a b--quot) | Divide (a/b) |
/mod |
(a b--rem quot) | Divide, return remainder and quotient |
1+ |
(n--n+1) | Increment by 1 |
1- |
(n--n-1) | Decrement by 1 |
| Word | Stack Effect | Description |
|---|---|---|
< |
(a b--flag) | Less than comparison |
= |
(a b--flag) | Equality comparison |
> |
(a b--flag) | Greater than comparison |
0= |
(n--flag) | Test if zero |
| Word | Stack Effect | Description |
|---|---|---|
and |
(a b--n) | Bitwise AND |
or |
(a b--n) | Bitwise OR |
xor |
(a b--n) | Bitwise XOR |
com |
(n--~n) | Bitwise complement |
| Word | Stack Effect | Description |
|---|---|---|
exit |
(--) | Exit current word definition |
for |
(limit--) | Begin counted loop |
i |
(--index) | Current loop index |
next |
(--) | Increment and loop if not done |
| Word | Stack Effect | Description |
|---|---|---|
>r |
(n--) (R:--n) | Move n to return stack |
r@ |
(--n) (R: n--n) | Copy n from return stack |
r> |
(--n) (R: n--) | Move n from return stack |
rdrop |
(--) (R: n--) | Drop from return stack |
| Word | Stack Effect | Description |
|---|---|---|
>a |
(n--) (A:--n) | Move n to the A stack |
a! |
(n--) | Store n to A TOS |
a@ |
(--n) (A: n--n) | Copy n from A |
a@+ |
(--n) (A: n--n+1) | Copy n from A, then increment A TOS |
a@- |
(--n) (A: n--n-1) | Copy n from A, then decrement A TOS |
a> |
(--n) (A: n--) | Move n from A |
| Word | Stack Effect | Description |
|---|---|---|
>b |
(n--) (B:--n) | Move n to the B stack |
b! |
(n--) | Store n to B TOS |
b@ |
(--n) (B: n--n) | Copy n from B |
b@+ |
(--n) (B: n--n+1) | Copy n from B, then increment B TOS |
b@- |
(--n) (B: n--n-1) | Copy n from B, then decrement B TOS |
b> |
(--n) (B: n--) | Move from B stack |
| Word | Stack Effect | Description |
|---|---|---|
>t |
(n--) (T:--n) | Move n to the T stack |
t! |
(n--) | Store n to T TOS |
t@ |
(--n) (T: n--n) | Copy n from T stack |
t@+ |
(--n) (T: n--n+1) | Copy n from T, then increment T TOS |
t@- |
(--n) (T: n--n-1) | Copy n from T, then decrement T TOS |
t> |
(--n) (T: n--) | Move n from T stack |
| Word | Stack Effect | Description |
|---|---|---|
emit |
(c--) | Output character |
key |
(--c) | Wait for and read a character |
?key |
(--f) | f: 1 if a key was pressed, 0 otherwise |
ztype |
(addr--) | Print null-terminated string at addr |
| Word | Stack Effect | Description |
|---|---|---|
fopen |
(name mode--fh) | Open file, return file handle fh |
fclose |
(fh--) | Close file fh |
fread |
(buf sz fh--n) | Read from file, return bytes read |
fwrite |
(buf sz fh--n) | Write to file, return bytes written |
fseek |
(fh offset--) | Seek to position in file |
| Word | Stack Effect | Description |
|---|---|---|
s-cpy |
(dest src--dst) | Copy null-terminated string |
s-eqi |
(s1 s2--flag) | Case-insensitive string comparison |
s-len |
(addr--len) | Get string length |
cmove |
(src dest n--) | Move n bytes from src to dest |
| Word | Stack Effect | Description |
|---|---|---|
lit, |
(n--) | Compile literal value |
outer |
(addr--) | Execute Forth source at addr |
addword |
(--) | Add word to dictionary (uses >in) |
find |
(--xt addr) | Find word (uses >in), return xt and dict addr |
timer |
(--n) | Get current time |
ms |
(n--) | Sleep for n milliseconds |
system |
(cmd--) | Execute system(cmd) |
bye |
(--) | Exit CF |
Adding a primitive to CF is easy. Add an X() line to the PRIMS macro.
The embedded X() macro in PRIMS is a powerful use of C macros.
The X(op, name, code) macro takes 3 parameters:
- A name for the ENUM of opcodes (used by the inner interpreter)
- A name for the word to be created in the Forth dictionary
- Code that implements the primitive's action. This can be a call to any function.
PRIMS is used create the opcode values and code for the switch statement in cfInner(), and to create the dictionary entries in cfInit().
For example, in the following example, SCOPY is the name for the enum, s-cpy is the name for the Forth dictionary entry, and t=pop(); strcpy((char*)TOS, (char*)t); is the code to be executed by the inner interpreter when SCOPY is encountered by the inner interpreter.
X(SCOPY, "s-cpy", t=pop(); strcpy((char*)TOS, (char*)t); ) \
- Blocks are not native to cf.
- They are implemented in the default source file.
- A block editor is implemented in the default source file.
- The editor has a vi-like feel.
