- c4a supports control characters (tags) in the whitespace that change the state.
- c4a has 4 states: INTERPRET, COMPILE, DEFINE, AND COMMENT.
- c4a also supports the standard state-change words.
- c4a has a built-in editor. It has a VI-like feel.
-
- For details, see the Editor documentation.
- c4a has 'a', 'b', and 't' words.
- c4a reads 'blocks.fth' into memory on load.
-
- This is what it uses to edit and load blocks.
| Tag | Word | State | Description |
|---|---|---|---|
| $01 | ] | 1 | Compile |
| $02 | : | 2 | Define |
| $03 | [ | 3 | Interpret/execute/immediate |
| $04 | 4 | Comment | |
| ( | Comment, skip until the next word is ')' |
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 16-bit unsigned number.
- 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 4 bits are set, it is 13-bit unsigned literal, 0-$0FFF.
- If it is between 'BYE' and $F000, it is the code address of a word to execute.
- c4a is intended to be used with development boards via the Arduino IDE.
- However, it can be built for PCs as well for testing.
- I am not aware of 64-bit dev boards.
- So a CELL in c4a is 32-bits.
- C4A has many built-in primitives, for the following reasons:
-
- Program flash is often quite large, so why not use it?
-
- To give C4A more functionality out of the box.
-
- C4A also runs faster because of it.
- Windows: there is a c4a.sln file for Visual Studio
- only the x86 target (32-bit) 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.x
- There is a c4a.ino file
- File 'c4a.h' controls parameters for the target board
- Edit the section where
IS_BOARDis defined to set the configuration for the board - Use
#define FILE_NONEto 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_PICOto include support for LittleFS
- For the Teensy-4.x:
- Use
#define FILE_TEENSYto 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 DICTIONARY.
- The CODE area is an aray of WORD-CODEs starting at the beginning of the memory.
hereis an offset into the CODE area.- The size of the CODE area is
code-sz. See CODE_SZ in c4a.h. - NOTE: Use
wc@andwc!to get and set 16-bit values in the code area. - NOTE: Use
cv@andcv!to get and set 32-bit 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) are unused by c4a.
- NOTE: Using
wc@andwc!, c4a provides storage space for many 16-bit variables. - NOTE: Using
cv@andcv!, c4a provides storage space for many 32-bit 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 +.vhereis the absolute address of the first free byte the VARS area.
- The DICTIONARY is at the end of the memory. 'last' grows toward the beginning of the memory.
lastis the address of the most recently created word.- A dictionary entry is [xt:2][flags:1][len:1][name:NAME_LEN][0:1]
- The default NAME_LEN is 11 (see c4a.h), so 'de-sz' is 16.
- Use
->memoryto 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 |
| stk-sz | (--N) | N: size in CELLs of the built-in 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: Address of the data stack pointer |
| (rsp) | (--N) | N: Address of the return stack pointer |
| (lsp) | (--N) | N: Address of the loop stack pointer |
| (asp) | (--N) | N: Address of the A stack pointer |
| (bsp) | (--N) | N: Address of the B stack pointer |
| (tsp) | (--N) | N: Address of the T stack pointer |
| (here) | (--N) | N: Address of the HERE variable |
| (last) | (--N) | N: Address of the LAST variable |
| base | (--N) | N: Address of the BASE variable |
| state | (--N) | N: Address of 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. |
| %B | (--) | Change foreground to blue. |
| %G | (--) | Change foreground to green. |
| %P | (--) | Change foreground to purple. |
| %R | (--) | Change foreground to red. |
| %W | (--) | Change foreground to white. |
| %Y | (--) | Change foreground to yellow. |
| %[x] | (--) | EMIT [x]. |
c4a has a built-in editor. It colorizes the code based on tags in the text.
For details, see the Editor documentation.
NOTE: Saving a block updates the block in the RAM-disk.
NOTE: It does NOT automatically write that block to disk.
NOTE: Use 'flush' to write it back to disk/flash.
c4a includes A, B, and T stacks.
These are somewhat similar to ColorForth's operations for a and b, but in c4a, they are stacks.
The size of the stacks is configurable (see stk-sz).
The words below are available for all 3 stacks.
Note that there are also additional words r! r@+ and r@- for the return stack.
| WORD | STACK | DESCRIPTION |
|---|---|---|
| >a | (N--) | Push N onto the A stack. |
| a! | (N--) | Set A to N. |
| a@ | (--N) | N: value of A. |
| a@+ | (--N) | N: value of A, then increment A. |
| a@- | (--N) | N: value of A, then decrement A. |
| !a | (N--) | Store N through A. |
| !a+ | (N--) | Store N through A, then increment A. |
| !a- | (N--) | Store N through A, then decrement A. |
| @a | (--N) | Fetch N through A. |
| @a+ | (--N) | Fetch N through A, then increment A. |
| @a- | (--N) | Fetch N through A, then decrement A. |
| a> | (--N) | Pop N from the A stack. |
| adrop | (--) | Drop A-TOS |
c4a provides 10 temporary words, 't0' .. 't9'.
- They are case-sensitive.
-
- 't0' is a temporary word; 'T0' is NOT.
- They do not take valuable dictionary space.
- They can be used to improve factoring, or as variable or constant names.
- t0:t5 are normal words, t6:t9 are INLINE.
- c4a supports a simple cooperative multi-tasking system.
- The task system is not preemptive.
- Words
add-task (xt--n),yield (--),del-task (n--)are provided. - Each task has its own data stack, return stack, and loop stack.
- The A, B, and T stacks are shared by all tasks.
NOTE: Since c4a is intended for dev boards, it has many more primitives than C4.
This is primarily because there is more program memory than RAM.
It provides more built-in functionality.
And the system is faster.
The primitives in c4a:
| BASE | 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) |
| ?dup | (X--X | X X) |
| swap | (X Y--Y X) | Swap TOS and NOS (Next-On-Stack) |
| drop | (N--) | Drop TOS |
| 2dup | (X Y--X Y X Y) | Duplicate TOS and NOS |
| 2drop | (X Y--) | Drop TOS and NOS |
| over | (X Y--X Y X) | Push NOS |
| nip | (X Y--Y) | Drop NOS |
| tuck | (X Y--Y X Y) | Perform SWAP, OVER |
| c@ | (A--C) | C: the CHAR at absolute address A |
| w@ | (A--W) | W: the WORD at absolute address A |
| @ | (A--N) | N: the CELL at absolute address A |
| wc@ | (N--WC) | WC: the WORD-CODE in CODE slot N |
| cv@ | (N--) | Code-Variable: Fetch a 32-bit value from CODE slots N/N+1 |
| c! | (C A--) | Store CHAR C to absolute address A |
| w! | (W A--) | Store WORD W to absolute address A |
| ! | (N A--) | Store CELL N to absolute address A |
| wc! | (WC N--) | Store WORD-CODE WC to CODE slot N |
| cv! | (N--) | Code-Variable: Store a 32-bit value to CODE slots N/N+1 |
| if | (X--) | Jump to 'then' if X == 0 (IMMEDIATE) |
| if0 | (X--) | Jump to 'then' if X <> 0 (IMMEDIATE) |
| -if | (X--X) | Jump to 'then' if X == 0 (IMMEDIATE) |
| then | (--) | Target for 'if' branch (IMMEDIATE) |
| begin | (--) | Begin a loop (IMMEDIATE) |
| again | (--) | Jump to matching 'begin' (IMMEDIATE) |
| while | (X--) | Jump to matching 'begin' if X <> 0 (IMMEDIATE) |
| -while | (X--X) | Jump to matching 'begin' if X <> 0 (IMMEDIATE) |
| until | (X--) | Jump to matching 'begin' if X == 0 (IMMEDIATE) |
| MATH | STACK | DESCRIPTION |
|---|---|---|
| + | (X Y--N) | N: X + Y |
| - | (X Y--N) | N: X - Y |
| * | (X Y--N) | N: X * Y |
| */ | (N X Y--N') | N': (N * X) / Y - Scale N by X/Y |
| / | (X Y--Q) | Q: X / Y quotient (integer division) |
| mod | (X Y--M) | M: X modulo Y |
| /mod | (X Y--M Q) | M: X modulo Y, Q: quotient of X / Y |
| << | (X Y--Z) | Z: X left-shifted Y bits |
| >> | (X Y--Z) | Z: X right-shifted Y bits |
| 1+ | (X--Y) | Increment TOS |
| 1- | (X--Y) | Decrement TOS |
| 2+ | (X--Y) | Y: X + 2 |
| 2* | (X--Y) | Y: X * 2 |
| 2/ | (X--Y) | Y: X / 2 |
| CELLS | (X--Y) | Y: X * CELL |
| CELL+ | (X--Y) | Y: X + CELL |
| < | (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 |
| >= | (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 (one's complement) |
| min | (X Y--Z) | Z: the minimum of (X and Y) |
| max | (X Y--Z) | Z: the maximum of (X and Y) |
| negate | (X--Y) | Y: -X |
| abs | (X--Y) | Y: the absolute value of X |
| MORE PRIMS | STACK | DESCRIPTION |
|---|---|---|
| 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 to N |
| r@ | (--N) | N: copy of R |
| r@+ | (--N) | N: copy of R, then increment it |
| r@- | (--N) | N: copy of R, 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 to N |
| t@ | (--N) | N: copy of T |
| t@+ | (--N) | N: copy of T, then increment T |
| t@- | (--N) | N: copy of T, then decrement T |
| t> | (--N) | Pop N from the T stack |
| tdrop | (--) | Drop T-TOS |
| >a | (N--) | Push N onto the A stack |
| a! | (N--) | Set A to N |
| a@ | (--N) | N: copy of A |
| a@+ | (--N) | N: copy of A, then increment A |
| a@- | (--N) | N: copy of A, then decrement A |
| @a | (--C) | Fetch BYTE C through A |
| @a+ | (--C) | Fetch BYTE C through A, then increment A |
| @a- | (--C) | Fetch BYTE C through A, then decrement A |
| !a | (C--) | Store BYTE C through A |
| !a+ | (C--) | Store BYTE C through A, then increment A |
| !a- | (C--) | Store BYTE C through A, then decrement A |
| a> | (--N) | Pop N from the A stack |
| adrop | (--) | Drop A-TOS |
| >b | (N--) | Push N onto the B stack |
| b! | (N--) | Set B to N |
| b@ | (--N) | N: copy of B |
| b@+ | (--N) | N: copy of B, then increment B |
| b@- | (--N) | N: copy of B, then decrement B |
| @b | (--C) | Fetch BYTE C through B |
| @b+ | (--C) | Fetch BYTE C through B, then increment B |
| @b- | (--C) | Fetch BYTE C through B, then decrement B |
| !b | (C--) | Store BYTE C through B |
| !b+ | (C--) | Store BYTE C through B, then increment B |
| !b- | (C--) | Store BYTE C through B, then decrement B |
| b> | (--N) | Pop N from the B stack |
| bdrop | (--) | Drop B-TOS |
| STRINGS | STACK | DESCRIPTION |
|---|---|---|
| s-len | (S--N) | N: Length of string S |
| s-cpy | (D S--D) | Copy string S to D |
| s-cat | (D S--D) | Concatenate 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) |
| ltrim | (S1--S2) | S2: Trim whitespace from the beginning of S1 |
| rtrim | (S--S) | Trim whitespace from the end of S |
| fill | (A N C--) | Fill N bytes from A with BYTE C |
| cmove | (F T N--) | Copy N bytes from F to T - low to high |
| cmove> | (F T N--) | Copy N bytes from F to T - high to low |
| lower | (X--Y) | Y: lower-case of X if 'A' <= X <= 'Z', else X |
| upper | (X--Y) | Y: upper-case of X if 'a' <= X <= 'a', else X |
| z" | (--) | -COMPILE: Create string S to next " (IMMEDIATE) |
| (--S) | -RUN: push address S of string | |
| ." | (--) | -COMPILE: execute z", compile ftype (IMMEDIATE) |
| (--) | -RUN: ftype on string |
|
| ztype | (S--) | Print string at S (unformatted) |
| ftype | (S--) | Print string at S (formatted) |
| FILES | STACK | DESCRIPTION |
|---|---|---|
| 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 |
| loaded? | (XT A--) | Stops current load if A <> 0 (see find) |
| 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 | (--) | Write RAM disk to flash/disk |
| edit | (N--) | N: Block number to edit |
| SYSTEM | STACK | DESCRIPTION |
|---|---|---|
| emit | (C--) | Output char C |
| key | (--C) | Read char C |
| key? | (--F) | F: 1 if key available, else 0 |
| ; | (--) | Compile EXIT, set STATE=INTERPRET (IMMEDIATE) |
| lit, | (N--) | Compile a push of number N |
| , | (WC--) | WC: WORD-CODE to store at HERE, HERE += WC-SZ |
| v, | (N--) | N: CELL to store at VHERE, VHERE += CELL |
| vc, | (B--) | N: BYTE to store at VHERE, VHERE += 1 |
| const | (N--) | ADD-WORD, generate LIT [N] EXIT |
| var | (N--) | ADD-WORD, generate LIT [VHERE] EXIT, VHERE += N |
| val | (--) | ADD-WORD, generate LIT 0 EXIT |
| (val) | (--) | ADD-WORD, generate LIT [HERE-4] EXIT |
| 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 |
| ms | (MS--) | Sleep for MS milliseconds |
| see X | (--) | Output the definition of word X |
| find | (--XT A) | XT: Execution Token, A: Dict Entry address (0 0 if not found) |
| add-task | (XT--N) | XT: addr of code, N: task slot |
| del-task | (N--) | Delete task in slot N |
| .tasks | (--) | Print all tasks |
| yield | (--) | Yield to the next task |
| here | (--) | Current value of (HERE) |
| last | (--) | Current value of (LAST) |
| vhere | (--) | Current value of (VHERE) |
| words | (--) | Print list of words and primitives |
| words-n | (N--) | Print list of N most recently defined words |
| (.) | (N--) | Output N in the current BASE |
| . | (N--) | Output N in the current BASE, followed by a SPACE |
| .s | (--) | Output the stack in the current BASE |
| allot | (N--) | VHERE += N |
| bl | (--N) | N: 32 - the ascii value for a space |
| tab | (--) | EMIT a tab (ascii 9) |
| cr | (--) | EMIT CR/LF (13, 10) |
| space | (--) | EMIT a single space |
| PC ONLY | STACK | DESCRIPTION |
|---|---|---|
| system | (S--) | S: String to send to system() |
| bye | (--) | Exit c4a |
| DEV BOARD | STACK | DESCRIPTION |
|---|---|---|
| pin-input | (P--) | Open pin P for input |
| pin-output | (P--) | Open pin P for output |
| pin-pullup | (P--) | Open pin P for input with pullup |
| dpin@ | (P--N) | N: value at digital pin P |
| dpin! | (N P--) | Write N to digital pin P |
| apin@ | (P--N) | N: value at analog pin P |
| apin! | (N P--) | Write N to analog pin P |
| bye | (--) | No-op |
Default/built-in 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.