- C4A supports control characters in the whitespace that change the state.
- C4A has 4 states: INTERPRET, COMPILE, DEFINE, AND COMMENT,
- C4A 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 | |
( | 4 | Comment, save current state | |
) | End comment, restores saved state |
NOTE: In the DEFINE state, C4A changes the state to COMPILE after adding the next word.
NOTE: Unlike ColorForth, ';' compiles EXIT and then changes the state to INTERPRET.
- In C4A, a program is a sequence of WORD-CODEs.
- A WORD-CODE is a 32-bit unsigned number (i.e. - a DWORD).
- Primitives are assigned numbers sequentially from 0 to [BYE].
- If a WORD-CODE is less than or equal to [BYE], it is a primitive.
- If the top 3 bits are set, it is a 29-bit unsigned literal, 0-$1FFFFFFF.
- If it is between [BYE], and $E0000000, it is the code address of a word to execute.
- A CELL in C4A is 32-bits, the same size as a WORD-CODE.
- Since development boards don't support 64-bits, neither does c4a.
- For PCs, only 32-bit platforms are supported.
- For 64-bit systems, see c4: https://github.com/CCurl/c4).
- Windows: there is a c4a.sln file for Visual Studio
- only the x86 target is supported
- Linux: there is a makefile
- only the 32-bit configuration (-m32) is supported
- Others:
- c4a is simple enough that it should be easy to migrate it to any platform
- I use the Arduino IDE v2.0
- There is a c3.ino file
- File
c4a.h
controls parameters for the target board - Edit the section where isBOARD is defined to set the configuration for the board
- Use
#define FILE_NONE
to disable support for blocks and LittleFS - For the RPI Pico:
- Use the arduino-pico from earlephilhower (https://github.com/earlephilhower/arduino-pico)
- The version must be 4.2.0 or later. Versions older than 4.0.0 do not support boards using the RP2350 microcontroller.
- Use
#define FILE_PICO
to include support for LittleFS
- For the Teensy-4.x:
- Use
#define FILE_TEENSY
to include support for LittleFS
- Use
C4A provides a single memory area. See 'mem-sz' (MEM_SZ in c4a.h) for its size.
- It is broken into 3 areas: CODE, VARS, and DICT.
- The CODE area is an aray of WORD-CODEs starting at the beginning of the memory.
here
is an offset into the CODE area.- The size of the CODE area is
code-sz
. See 'code-sz' (CODE_SZ in c4a.h). - NOTE: Use
wc@
andwc!
to get and set WORD-CODE values in the code area. - NOTE: CODE slots 0-25 (
0 wc@ .. 25 wc@
) are reserved for C4A system values. - NOTE: CODE slots 26-[BYE] (
26 wc@
..[BYE] wc@
) are unused by C4A. - NOTE: So c4a provides space for about 75 'free' variables.
- NOTE: These are free for the user/application to use as desired.
- The VARS area is defined to begin at address
code-sz wc-sz * memory +
.vhere
is the absolute address of the first free byte the VARS area.
- The DICT is at the end of the memory. 'last' grows toward the beginning of the memory.
last
is an offset into the memory area.
- Use
->memory
to turn an offset into an address.
WORD | STACK | DESCRIPTION |
---|---|---|
memory | (--A) | A: starting address of the C4A memory |
mem-sz | (--N) | N: size in BYTEs of the C4A memory |
code-sz | (--N) | N: number of in WORD-CODE slots in the 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 a WORD-CODE |
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 |
(here) | (--N) | N: CODE slot for the HERE variable |
(last) | (--N) | N: CODE slot for the LAST variable |
base | (--N) | N: CODE slot for the BASE variable |
state | (--N) | N: CODE slot for the STATE variable |
Strings in C4A are NULL-terminated with no count byte.
Similar to the printf() function in C, C4A supports formatted output using '%'.
For example : ascii 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 " (#34). |
%s | (A--) | Print TOS as a string (formatted). |
%S | (A--) | Print TOS as a string (unformatted). |
%x | (N--) | Print TOS in base 16. |
%[x] | (--) | EMIT [x]. |
C4A includes an A stack.
This is somewhat similar to MachineForth's operations for 'a', but in C4A, it is a stack.
The size of the A stack is configurable (see tstk-sz
).
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 |
C4A includes a T stack, with tge same operations as the A stack.
Note that there are also additional words for the return stack.
WORD | STACK | DESCRIPTION |
---|---|---|
>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 |
Stack effect notation conventions:
TERM | DESCRIPTION |
---|---|
SZ/NM/MD | String, uncounted, NULL terminated |
A | Address |
C | Number, 8-bits |
WC | WORD-CODE, 32-bits |
N/X/Y | Number, CELL sized |
F | Flag: 0 mean0 false, <>0 means true |
R | Register number |
FH | File handle: 0 means no file |
I | For loop index counter |
The primitives:
WORD | STACK | DESCRIPTION |
---|---|---|
(lit) | (--WC) | WC: WORD-CODE for LIT primitive |
(jmp) | (--WC) | WC: WORD-CODE for JMP primitive |
(jmpz) | (--WC) | WC: WORD-CODE for JMPZ primitive |
(jmpnz) | (--WC) | WC: WORD-CODE for JMPNZ primitive |
(njmpz) | (--WC) | WC: WORD-CODE for NJMPZ primitive |
(njmpnz) | (--WC) | WC: WORD-CODE for NJMPNZ primitive |
(exit) | (--WC) | WC: WORD-CODE for EXIT primitive |
exit | (--) | EXIT word |
dup | (X--X X) | 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 WORD at absolute address A |
wc@ | (N--WC) | WC: the WORD-CODE 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 WORD W to absolute address A |
wc! | (WC N--) | Store WORD-CODE 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 |
< | (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 | (N--) | Begin FOR loop with bounds 0 and N-1. |
i | (--I) | N: Current FOR loop index. |
next | (--) | Increment I. If I >= N, exit, else start loop again. |
unloop | (--) | Unwind the loop stack. NOTE: 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 |
emit | (C--) | Output char C |
; | (--) | Compile EXIT, set STATE=INTERPRET |
lit, | (N--) | Compile a push of number N |
next-wd | (--L) | L: length of the next word from the input stream |
immediate | (--) | Mark the last created word as IMMEDIATE |
inline | (--) | Mark the last created word as INLINE |
outer | (S--) | Send string S to the C4A outer interpreter |
addword | (--) | Add the next word to the dictionary |
timer | (--N) | N: Current time |
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-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" | (--) | -COMPILE: Create string S to next " |
(--S) | -RUN: push address S of string | |
." | (--) | -COMPILE: execute z" , compile ftype |
(--) | -RUN: ftype on string |
|
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 |
fseek | (N FH--X) | N: Size, FH: File Handle, X: return from func |
fsize | (FH--N) | FH: File Handle, N: file size |
fpos | (FH--N) | FH: File Handle, N: current file read/write position |
load | (N--) | N: Block number to load |
load-next | (N--) | Close the current block and load block N next |
blocks | (--) | Dump block cache |
block-addr | (N--A) | N: Block number, A: Address in cache |
flush | (F--) | F: True => clear cache |
flush-block | (N F--) | N: Block number, F: True => clear cache entry |
edit | (N--) | N: Block number to edit |
system | (S--) | PC ONLY: S: String to send to system() |
bye | (--) | PC ONLY: Exit C4A |
Default words are defined in function sys_load()
in file sys-load.cpp.
For details, or to add or change the default words, modify that function.