c4 supports control characters in the whitespace that change the state.
c4 has 'A', 'B' and 'T' stacks, inspired by ColorForth's 'a' register.
c4 has 4 states: INTERPRET, COMPILE, DEFINE, AND COMMENT,
c4 also supports the standard state-change words.
| Ascii | Word | State | Description |
|---|---|---|---|
| $01 | ] | 1 | Compile |
| $02 | : | 2 | Define |
| $03 | [ | 3 | Interpret/execute/immediate |
| $04 | 4 | Comment |
NOTE: In the DEFINE state, c4 changes the state to COMPILE after adding the next word.
NOTE: Unlike ColorForth, ';' compiles EXIT and then changes the state to INTERPRET.
NOTE: The ( word simply skips to the next ) word, no nesting.
In c4, a program is a sequence of OPCODEs.
An OPCODE is an unsigned CELL, 32 or 64 bits.
Primitives are assigned numbers sequentially from 0 to BYE.
If the OPCODE is less than or equal to BYE, it is a primitive.
Else if the top 3 bits are set, it is an unsigned literal.
Else, it is the offset in the CODE area (XT) of a word to execute.
c4 has a built-in editor. See Editor.md for details.
The editor can be excluded from c4 by undefining EDITOR in c4.h.
It is built-in so that the editor is available when running c4 from any folder.
In c4, the size if a block is 3072 bytes (3x1024).
The editor is 32 lines, 96 columns, and has a VI like feel.
- There is a Visual Studio solution file, c4.sln (either 32- or 64-bit)
- There is a makefile.
- The default architecture is 64-bits. That is a little faster on my systems.
- 64-bit: use 'make'
- 32-bit: use 'ARCH=32 make'
c4 provides a single memory area with size 'mem-sz' (see c4.h, MEM_SZ).
- It is divided into 3 areas as follows [CODE][VARS][Dictionary].
- The CODE area is an array of OPCODEs starting at the beginning of the memory.
- The VARS area is defined to begin at address &memory[CODE_SLOTS*WC_SZ].
- The Dictionary starts at the end and grows downward.
- The size of the CODE area is 'code-sz' (see c4.h, CODE_SLOTS).
hereis an offset into the CODE area, the next slot to be allocated.lastis the address of the most recently created word.vhereis the address of the first free byte the VARS area.- Use
->memoryto turn an offset into an address into the memory area. - Use
->codeto turn an offset into an address of an OPCODE. - NOTE: CODE slots 0-25 (
0 wc@..25 wc@) are reserved for c4 system values. - NOTE: CODE slots 26-BYE (
26 wc@..<bye> wc@) are unused by c4. - NOTE: These are free for the application to use as desired.
- NOTE: Use
wc@andwc!to get and set OPCODE values in the CODE area.
| WORD | STACK | DESCRIPTION |
|---|---|---|
| memory | (--A) | A: starting address of the c4 memory |
| vars | (--A) | A: starting address of the VARS area |
| mem-sz | (--N) | N: size in BYTEs of the c4 memory |
| code-sz | (--N) | N: size in CODE entries of the c4 CODE area |
| dstk-sz | (--N) | N: size in CELLs of the DATA and RETURN stacks |
| tstk-sz | (--N) | N: size in CELLs of the A and T stacks |
| wc-sz | (--N) | N: size in BYTEs of an OPCODE |
| de-sz | (--N) | N: size in BYTEs of a dictionary entry |
| (dsp) | (--N) | N: CODE slot for the data stack pointer |
| (rsp) | (--N) | N: CODE slot for the return stack pointer |
| (lsp) | (--N) | N: CODE slot for the loop stack pointer |
| (tsp) | (--N) | N: CODE slot for the T stack pointer |
| (asp) | (--N) | N: CODE slot for the A stack pointer |
| (bsp) | (--N) | N: CODE slot for the B stack pointer |
| (here) | (--N) | N: CODE slot for the HERE variable |
| (vhere) | (--A) | A: address of the VHERE variable |
| (last) | (--A) | A: address of the LAST variable |
| base | (--N) | N: CODE slot for the BASE variable |
| state | (--N) | N: CODE slot for the STATE variable |
| (block) | (--N) | N: CODE slot for the BLOCK variable |
Strings in c4 are NULL-terminated with no count byte.
Similar to the printf() function in C, c4 supports formatted output using '%'.
For example : ascii ( n-- ) dup dup dup ." char %c, decimal #%d, binary: %%%b, hex: $%x%n" ;.
| Format | Stack | Description |
|---|---|---|
| %b | (N--) | Print TOS in base 2. |
| %c | (N--) | EMIT TOS. |
| %d | (N--) | Print TOS in base 10. |
| %e | (--) | EMIT escape (#27). |
| %i | (N--) | Print TOS in the current base. |
| %n | (--) | Print CR/LF (13/10). |
| %q | (--) | EMIT a double-quote (#34). |
| %s | (A--) | Print TOS as a string (formatted). |
| %S | (A--) | Print TOS as a string (unformatted). |
| %x | (N--) | Print TOS in base 16. |
| %B | (--) | Change foreground to Blue |
| %G | (--) | Change foreground to Green |
| %P | (--) | Change foreground to Purple |
| %R | (--) | Change foreground to Reg |
| %W | (--) | Change foreground to White |
| %Y | (--) | Change foreground to Yellow |
| %[x] | (--) | EMIT [x]. |
c4 includes A, B and T stacks.
These are similar to ColorForth's operations for 'a', but in c4, they are stacks.
The size of the stacks is 'tstk-sz' (see c4.h, TSTK_SZ).
Note that there are also additional words for the return stack.
| WORD | STACK | DESCRIPTION |
|---|---|---|
| >a | (N--) | Push N onto the A stack. |
| a! | (N--) | Set A-TOS to N. |
| a@ | (--N) | N: copy of A-TOS. |
| a@+ | (--N) | N: copy of A-TOS, then increment A-TOS. |
| a@- | (--N) | N: copy of A-TOS, then decrement A-TOS. |
| a> | (--N) | Pop N from the A stack. |
| adrop | (--) | Drop A-TOS. |
In c4, an "INLINE" word is similar to a macro. When compiling a word that is INLINE, c4 copies the contents of the word (up to, but not including the first EXIT) to the target, as opposed to compiling a CALL to the word. This improves performance, but uses extra space. Note that if a word might have an embedded 7 (the opcode value for EXIT) in its implementation, then it should not be marked as INLINE.
c4 provides 10 temporary words, 't0' thru 't9'.
- Defining a temporary word does not add an entry to the dictionary.
- Temporary words are intended to be helpful in factoring code.
- Their names are case-sensitive (t0-t9, not T0-T9).
- A temporary word can be redefined as often as desired.
- When redefined, code references to the previous definition are unchanged.
- t0 thru t5 are 'normal', t6 thru t9 are 'inline'.
The primitives:
| WORD | STACK | DESCRIPTION |
|---|---|---|
| : | (--) ST=2 | Set STATE to DEFINE (RED) |
| ; | (--) ST=3 | Compile EXIT and set STATE to INTERPRET (YELLOW) |
| [ | (--) ST=3 | Set STATE to INTERPRET (YELLOW) |
| ] | (--) ST=1 | Set STATE to COMPILE (GREEN) |
| ( | (--) | Skip to next word ')' |
| (lit) | (--WC) | WC: OPCODE for the LIT primitive |
| (jmp) | (--WC) | WC: OPCODE for the JMP primitive |
| (jmpz) | (--WC) | WC: OPCODE for the JMPZ primitive |
| (jmpnz) | (--WC) | WC: OPCODE for the JMPNZ primitive |
| (njmpz) | (--WC) | WC: OPCODE for the NJMPZ primitive |
| (njmpnz) | (--WC) | WC: OPCODE for the NJMPNZ primitive |
| (exit) | (--WC) | WC: OPCODE for the EXIT primitive |
| exit | (--) | EXIT word |
| dup | (N--N N) | Duplicate TOS (Top-Of-Stack) |
| swap | (X Y--Y X) | Swap TOS and NOS (Next-On-Stack) |
| drop | (N--) | Drop TOS |
| over | (N X--N X N) | Push NOS |
| @ | (A--N) | N: the CELL at absolute address A |
| c@ | (A--C) | C: the CHAR at absolute address A |
| w@ | (A--W) | W: the 16-bit WORD at absolute address A |
| d@ | (A--D) | D: the 32-bit DWORD at absolute address A |
| wc@ | (N--WC) | WC: the CODE entry in CODE slot N |
| ! | (N A--) | Store CELL N to absolute address A |
| c! | (C A--) | Store CHAR C to absolute address A |
| w! | (W A--) | Store 16-bit WORD W to absolute address A |
| d! | (D A--) | Store 32-bit DWORD D to absolute address A |
| wc! | (WC N--) | Store WC to CODE slot N |
| + | (X Y--N) | N: X + Y |
| - | (X Y--N) | N: X - Y |
| * | (X Y--N) | N: X * Y |
| / | (X Y--N) | N: X / Y (integer division) |
| /mod | (X Y--M Q) | M: X modulo Y, Q: quotient of X / Y |
| 1+ | (X--Y) | Increment TOS |
| 1- | (X--Y) | Decrement TOS |
| +! | (N A--) | Add N to the CELL at A |
| < | (X Y--F) | F: 1 if (X < Y), else 0 |
| = | (X Y--F) | F: 1 if (X = Y), else 0 |
| > | (X Y--F) | F: 1 if (X > Y), else 0 |
| 0= | (N--F) | F: 1 if (N=0), else 0 |
| and | (X Y--N) | N: X AND Y |
| or | (X Y--N) | N: X OR Y |
| xor | (X Y--N) | N: X XOR Y |
| com | (X--Y) | Y: X with all bits flipped (complement) |
| for | (CNT--) | Begin FOR loop with bounds 0 and CNT-1. |
| i | (--I) | I: Current FOR loop index. |
| next | (--) | Increment I. If I >= CNT, exit, else start loop again. |
| unloop | (--) | Unwind the loop stack. DOES NOT EXIT THE LOOP. |
| >r | (N--) | Push N onto the return stack |
| r! | (N--) | Set R-TOS to N |
| r@ | (--N) | N: copy of R-TOS |
| r@+ | (--N) | N: copy of R-TOS, then increment it |
| r@- | (--N) | N: copy of R-TOS, then decrement it |
| r> | (--N) | Pop N from the return stack |
| rdrop | (--) | Drop R-TOS |
| >t | (N--) | Push N onto the T stack |
| t! | (N--) | Set T-TOS to N |
| t@ | (--N) | N: copy of T-TOS |
| t@+ | (--N) | N: copy of T-TOS, then increment T-TOS |
| t@- | (--N) | N: copy of T-TOS, then decrement T-TOS |
| t> | (--N) | Pop N from the T stack |
| tdrop | (--) | Drop T-TOS |
| >a | (N--) | Push N onto the A stack |
| a! | (N--) | Set A-TOS to N |
| a@ | (--N) | N: copy of A-TOS |
| a@+ | (--N) | N: copy of A-TOS, then increment A-TOS |
| a@- | (--N) | N: copy of A-TOS, then decrement A-TOS |
| a> | (--N) | Pop N from the A stack |
| adrop | (--) | Drop A-TOS |
| >b | (N--) | Push N onto the B stack |
| b! | (N--) | Set B-TOS to N |
| b@ | (--N) | N: copy of B-TOS |
| b@+ | (--N) | N: copy of B-TOS, then increment B-TOS |
| b@- | (--N) | N: copy of B-TOS, then decrement B-TOS |
| b> | (--N) | Pop N from the B stack |
| bdrop | (--) | Drop B-TOS |
| emit | (C--) | Output char C |
| key | (--C) | Read char C from stdin, block if needed |
| key? | (--F) | F: 1 if key was pressed, else 0 |
| lit, | (N--) | Compile a push of number N |
| next-wd | (--A L) | L: length of the next word (A) from the input stream |
| - If L=0, then A is an empty string (end of input) | ||
| immediate | (--) | Mark the last created word as IMMEDIATE |
| inline | (--) | Mark the last created word as INLINE |
| outer | (S--) | Send string S to the c4 outer interpreter |
| addword | (--) | Add the next word to the dictionary |
| timer | (--N) | N: Current time |
| ms | (MS--) | MS: Number of milliseconds to sleep |
| see X | (--) | Output the definition of word X |
| ztype | (S--) | Print string at S (unformatted) |
| ftype | (S--) | Print string at S (formatted) |
| s-cpy | (D S--D) | Copy string S to D |
| s-find | (B L S--N) | Find string L in B starting at char S. N = -1 if not found |
| s-eq | (D S--F) | F: 1 if string S is equal to D (case sensitive) |
| s-eqi | (D S--F) | F: 1 if string S is equal to D (NOT case sensitive) |
| s-len | (S--N) | N: Length of string S |
| z" str" | (--) | - COMPILE: generate code to push address of str (vhere) |
| (--A) | - RUN: A=address of str |
|
| (--A) | - INTERPRET: A=address of str (only 1 string supported) |
|
| ." msg" | (--) | - COMPILE: execute z", compile ftype |
| (--) | - RUN: perform ftype on msg |
|
| (--) | - INTERPRET: output msg using ftype |
|
| find | (--XT A) | XT: Execution Token, A: Dict Entry address (0 0 if not found) |
| loaded? | (XT A--) | Stops current load if A <> 0 (see find) |
| fopen | (NM MD--FH) | NM: File Name, MD: Mode, FH: File Handle (0 if error/not found) |
| fclose | (FH--) | FH: File Handle to close |
| fdelete | (NM--) | NM: File Name to delete |
| fread | (A N FH--X) | A: Buffer, N: Size, FH: File Handle, X: num chars read |
| fwrite | (A N FH--X) | A: Buffer, N: Size, FH: File Handle, X: num chars written |
| fgets | (A N FH--X) | A: Buffer, N: Size, X: num chars read (0 if EOF/Error) |
| include X | (--) | Load file named X (X: next word) |
| load | (N--) | N: Block number to load (file named "block-NNN.fth") |
| load-next | (N--) | Close the current block and load block N next |
| system | (S--) | PC ONLY: S: String to send to system() |
| bye | (--) | PC ONLY: Exit c4 |
If SYS_LOAD is defined in file c4.h, call function sys_load().
Else, load file block 0 (file block-000.fth).
For details, or to add or change the default words, modify that function.