Blue v7

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.

Fair Warning

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.

Obtaining Blue

For information on building Blue from source, check the project’s repo.

Running

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.

Interpretation Semantics

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).

Colors

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:

Green Word
Compile a call or jmp to word in the output. (Postponed: call or jmp)
Blue Word
Execute a call or jmp to word at assemble-time. (Postponed: call or jmp)
Red Word
Define a label for word. (Postponed: label)
Magenta Word
Push the asmtime or runtime address of word on the data stack. (Postponed: asmtime or runtime)
Green Number
Write number to output. (Postponed: number)
Blue Number
Push number on the data stack. (Postponed: number)

Quick Example: `;` for x86_64

One of the core words for a Forth is ; 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.

Going Forward

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:


Blue, v7