colorful rat Ratfactor.com > Dave's Repos

meow5

A stack-based pure inlining concatenative programming language written in NASM assembly
git clone http://ratfactor.com/repos/meow5/meow5.git

Files

README.md

Meow5: “Meow. Meow. Meow. Meow. Meow.”

SVG meow5 kitty cat logo

Update 2023-11-21 Meow5 is done! Read my conclusion here!

Meow5 is a stack-based pure inlining concatenative programming language.

Running Meow5 interactively looks like this:

5 5 +
"The answer is $." print$
The answer is 10.

In the above example, the first line puts two fives on the stack and adds them.

The second line prints the answer. (The “$” character pops a number from the stack and includes it in the printed string.)

Now we can see that 5 + 5 is 10.

A block of code can be given a name and is called a “def” (short for definition). Here’s one:

def meow
    "Meow!" print
;

meow
Meow!

Defs can include other defs. Here’s a silly example:

def five 5 ;
def plus + ;
def ten five five plus ;

ten "Ten is $" print$
Ten is 10

Meow5 can write any def as a stand-alone 32-bit Linux ELF executable. (But not all defs are position-independent at this time, so some executables will segfault!) Here’s an example session:

$ ./meow5

def forty-two
    42 exit
;

elf forty-two
Wrote to "forty-two".

$ ./forty-two
$ echo $?
42

Note that $? contains the exit code of the previous command.

The file forty-two is a 97-byte program that will run on any Linux system!

The canonical Meow5 program writes five meows. Here is a self-contained meow file:

$ cat 5.meow 
def meow
    "Meow!\n" print
;

def five-meows
    meow
    meow
    meow
    meow
    meow
    exit
;

elf five-meows

That last line tells the interpreter to write out the five-meows def as a stand-alone executable. Let’s see what happens when we redirect this file to the interpreter:

$ ./meow5 < 5.meow
Wrote to "five-meows".

It wrote a 317 byte executable named five-meows. Let’s run it:

$ ./five-meows
Meow!
Meow!
Meow!
Meow!
Meow!

Now I can have meowing in my terminal any time I want!

Now with loops!

Ah, but now it gets better! I’ve added ifs and loops. A looped version of the five-meows program is much more efficient with space. Here’s five-loop.meow:

def meow
    "Meow!\n" print

    # decrement the stack each time or
    # we'll end up with an infinite loop!
    dec
;

# Another def that loops!
def five-meows-loop
    5 loop? meow
    exit
;

# Write an executable :-)
elf five-meows-loop

It works exactly like the five copy inline version:

$ ./meow5 <five-loop.meow 
Wrote to "five-meows-loop".

$ ./five-meows-loop 
Meow!
Meow!
Meow!
Meow!
Meow!

Except this executable is a mere 160 bytes. Cool!

What is Meow5? (and what is “pure inlining”?)

A Forth-like language that is conCATenative in two ways:

  1. Concatenative data flow (traditional)
  2. Concatenated machine code (weird)

There’s a lot of information on the Web about concatenative programming in the first, traditional sense. But that second part is what’s unique about Meow5.

Luckily, really easy to explain how this works, thanks to Meow5’s introspection abilities:

Using inspect, we can view the machine code of any def:

inspect +
+: 5 bytes IMMEDIATE COMPILE
    58 5b 1 d8 50

We can see that the + def contains five bytes of machine code.

By the way, that machine code disassembles into this assembly language:

pop eax
pop ebx
add eax, ebx
push eax

If we define a new def that uses an existing def, the machine code simply gets concatenated together:

def ++
  +
  +
;

Let’s use this new def to see that it adds three numbers together:

10 10 10 ++ printnum
30

And since we can see that the contents of our new def is, indeed, the simple concatenation of its constituents (two copies of the 5 byte +):

inspect ++
++: 10 bytes IMMEDIATE COMPILE
    58 5b 1 d8 50 58 5b 1 d8 50

The consequence of this is that any def in Meow5 is a complete, stand-alone sequence of instructions. That’s why writing an executable for any def is as simple as writing its contents to disk! (Plus the ELF header to make it valid, of course.)

Q: Is this wasteful? Doesn’t this cause a lot of redundant copies of code?

A: Yes.

But it’s interesting because we’re also avoiding a lot of branching - most Meow5 code is a continuous stream of actions with very tiny relative local jumps. I think it produces unusual code.

See also:

Assembly Nights 2 - This actually part of a series, a personal exploration of the joy of computing.

ratfactor.com/meow5/ - Meow5’s page on the World Wide Web (though this README is more up to date).

Why?

My idea came about while studying Forth. A traditional “threaded interpreted” Forth goes to some pretty extreme lengths to conserve memory. The execution model is not only complicated, but seems also likely to not be all that great for efficiency on modern machines where memory is much more abundant and the bottleneck oftenseems to be getting data into the CPU fast enough.

In particular, the old Forth literature I have been reading is full of statements about needing to conserve the few kilobytes of core memory on a late 1960s machine. But even my most modest low-powered Celeron and Atom-based computers have L1 CPU caches that dwarf those quantities!

So, given the tiny size of the programs I was writing with my JONESFORTH port, I kept thinking, “how far could I get if I just inlined everything?” As in, actually made a copy of every word’s machine instructions every time it is “compiled”.

I expect this will be silly but fun and educational.