tl;dr - starting from nothing but the provided colors, quickly dsl yourself artisanal output using an unsafe but fun concatenative DIY assembler.
Blue is a single-pass bytecode interpreter for a colorForth dialect. It has no separate macro syntax and any word can be called at assemble time. Inspired by fasmg, when using Blue you start with a clean slate and the output is completely driven by the input.
Blue has no builtin words. Programs typically go from green numbers (machine code) to higher level words which are used as macros, commands and variables via assemble-time execution within the first few hundred bytes.
This AI free project is the reference implementation for x86_64/Linux.
It is important to realize that using Blue is agreeing to step into a world of brutal simplicity and minimalism. There are zero guardrails. You will not get an error message let alone a helpful one. Segfaults will happen and debugging often means taking a walk to think about what you’ve done wrong. Hand jitting some machine code will be required to do anything non trivial.
For information on building Blue from source, check the project’s repo.
blue < in > out [&& chmod +x out]
Blue will interpret up to 8192 bytes of bytecode from stdin. When finished it will write up to 8192 bytes of output to stdout.
The cell size is 8 bytes. The dictionary can hold up to 256 entries, 2 cells each. The data stack is 16 cells. The return stack is rsp.
The dictionary and stacks only exist at assemble time.
Since Blue is a colorForth dialect, its interpretation semantics are closer to that of an assembler than a traditional Forth. Word definitions are just labels and one word can fallthrough to the next. Labels can be placed anywhere and used as call/jmp targets, variables, or have their address pushed on the data stack.
By default every word/number is immediate. Words in between a definition and
; are treated no differently than those at the top level. In fact, ;
itself is just a user defined word. For x86_64 it simply adds 0xC3 to the
output.
Any word/number can be postponed. This is useful for, but not limitied to, constructing macros and words that are run at assemble-time.
Byte values 0-127 are ignored by the interpreter and can be used to provide comments in the bytecode (among other creative things).
A quick tangent - imho the true beauty of colorForth is not the colors but the sheer simplicity of the system. Different visuals of the bytecode can used, these are just the default.
Blue uses a different/reduced color set compared to the traditional colorForth:
; which marks the end of a
word. In Blue ; is user defined, like all words. Below we will
go over its default implementation for x86_64:
; C3 C3
That's it. In x86_64, 0xC3 is the
near return to calling
procedure version of ret.
First we add a red word ; to define a label to
associate the current location of the output with the word `;`. The desire is
to be able to call semi as a blue word later and have it write 0xC3
to the output. To do this we write the postponed green number
C3 which will defer the interpretation of the
green number until semi is called. Finally we actually want to write an
0xC3 to the output, so when we call semi at assemble-time it will
return to the caller after interpreting the postponed green number.
Now that semi has been defined, we can use it as a blue word and run it at assemble-time:
new other ;
Which would have the exact same output as:
new other C3
Maybe you never liked semi and always wished it was called goback:
goback C3 C3 new other goback
Which means that with just green numbers and red words we jit'd ourselves a macro. Since these macros are just regular words, they can be called from other words, have their address taken or run at assemble-time.
This was just the tip of the iceberg, but please know that the door to dsl'ing yourself an artisanal binary is wide open. Enter all who enjoy the low-level bit twiddling and building something from (basically) nothing.
Some more code examples: