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

meow5/README.md

Download raw file: README.md

1 # Meow5: "Meow. Meow. Meow. Meow. Meow." 2 3 <img src="raw/meow5cat.svg" alt="SVG meow5 kitty cat logo" align="right"> 4 5 **Update 2023-11-21** Meow5 is done! 6 <a href="https://www.ratfactor.com/meow5/done">Read my conclusion here!</a> 7 8 Meow5 is a stack-based pure inlining concatenative programming language. 9 10 Running Meow5 interactively looks like this: 11 12 5 5 + 13 "The answer is $." print$ 14 The answer is 10. 15 16 In the above example, the first line puts two fives on the 17 stack and adds them. 18 19 The second line prints the answer. (The "$" character pops a number from the stack and includes it in the printed string.) 20 21 Now we can see that 5 + 5 is 10. 22 23 A block of code can be given a name and is called a "def" 24 (short for definition). Here's one: 25 26 def meow 27 "Meow!" print 28 ; 29 30 meow 31 Meow! 32 33 Defs can include other defs. Here's a silly example: 34 35 def five 5 ; 36 def plus + ; 37 def ten five five plus ; 38 39 ten "Ten is $" print$ 40 Ten is 10 41 42 Meow5 can write *any* def as a stand-alone 32-bit Linux ELF 43 executable. (But not all defs are position-independent at 44 this time, so some executables will segfault!) Here's an 45 example session: 46 47 $ ./meow5 48 49 def forty-two 50 42 exit 51 ; 52 53 elf forty-two 54 Wrote to "forty-two". 55 56 $ ./forty-two 57 $ echo $? 58 42 59 60 Note that `$?` contains the exit code of the previous command. 61 62 The file `forty-two` is a 97-byte program that will run on 63 any Linux system! 64 65 The canonical Meow5 program writes five meows. Here is a 66 self-contained meow file: 67 68 $ cat 5.meow 69 def meow 70 "Meow!\n" print 71 ; 72 73 def five-meows 74 meow 75 meow 76 meow 77 meow 78 meow 79 exit 80 ; 81 82 elf five-meows 83 84 That last line tells the interpreter to write out the 85 five-meows def as a stand-alone executable. Let's see what 86 happens when we redirect this file to the interpreter: 87 88 $ ./meow5 < 5.meow 89 Wrote to "five-meows". 90 91 It wrote a 317 byte executable named `five-meows`. Let's run it: 92 93 $ ./five-meows 94 Meow! 95 Meow! 96 Meow! 97 Meow! 98 Meow! 99 100 Now I can have meowing in my terminal any time I want! 101 102 ### Now with loops! 103 104 Ah, but now it gets better! I've added ifs and loops. 105 A looped version of the five-meows program is much 106 more efficient with space. Here's `five-loop.meow`: 107 108 def meow 109 "Meow!\n" print 110 111 # decrement the stack each time or 112 # we'll end up with an infinite loop! 113 dec 114 ; 115 116 # Another def that loops! 117 def five-meows-loop 118 5 loop? meow 119 exit 120 ; 121 122 # Write an executable :-) 123 elf five-meows-loop 124 125 It works exactly like the five copy inline version: 126 127 $ ./meow5 <five-loop.meow 128 Wrote to "five-meows-loop". 129 130 $ ./five-meows-loop 131 Meow! 132 Meow! 133 Meow! 134 Meow! 135 Meow! 136 137 Except this executable is a mere **160 bytes**. Cool! 138 139 140 ## What is Meow5? (and what is "pure inlining"?) 141 142 A Forth-like language that is conCATenative in two ways: 143 144 1. Concatenative data flow (traditional) 145 2. Concatenated machine code (weird) 146 147 There's a lot of information on the Web about concatenative 148 programming in the first, traditional sense. But that second 149 part is what's unique about Meow5. 150 151 Luckily, really easy to explain how this works, thanks to 152 Meow5's introspection abilities: 153 154 Using `inspect`, we can view the machine code of any def: 155 156 inspect + 157 +: 5 bytes IMMEDIATE COMPILE 158 58 5b 1 d8 50 159 160 We can see that the `+` def contains five bytes of machine 161 code. 162 163 By the way, that machine code disassembles into this assembly 164 language: 165 166 pop eax 167 pop ebx 168 add eax, ebx 169 push eax 170 171 If we define a new def that uses an existing def, the 172 machine code simply gets concatenated together: 173 174 def ++ 175 + 176 + 177 ; 178 179 Let's use this new def to see that it adds three numbers 180 together: 181 182 10 10 10 ++ printnum 183 30 184 185 And since we can see that the contents of our new def is, 186 indeed, the simple concatenation of its constituents (two 187 copies of the 5 byte `+`): 188 189 inspect ++ 190 ++: 10 bytes IMMEDIATE COMPILE 191 58 5b 1 d8 50 58 5b 1 d8 50 192 193 The consequence of this is that any def in Meow5 is a 194 _complete, stand-alone sequence of instructions_. That's why 195 writing an executable for any def is as simple as writing 196 its contents to disk! (Plus the ELF header to make it valid, 197 of course.) 198 199 Q: Is this wasteful? Doesn't this cause a lot of redundant 200 copies of code? 201 202 A: Yes. 203 204 But it's interesting because we're also avoiding a lot of 205 branching - most Meow5 code is a continuous stream of 206 actions with very tiny relative local jumps. I think it 207 produces unusual code. 208 209 See also: 210 211 <a href="http://ratfactor.com/assembly-nights2">Assembly Nights 2</a> - This actually part of a series, a personal exploration of the joy of computing. 212 213 <a href="http://ratfactor.com/meow5/">ratfactor.com/meow5/</a> - Meow5's page on the World Wide Web (though this README is more up to date). 214 215 216 ## Why? 217 218 My idea came about while studying Forth. A traditional 219 "threaded interpreted" Forth goes to some pretty extreme 220 lengths to conserve memory. The execution model is not only 221 complicated, but seems also likely to not be all that great 222 for efficiency on modern machines where memory is much more 223 abundant and the bottleneck oftenseems to be getting data 224 into the CPU fast enough. 225 226 In particular, the old Forth literature I have been reading 227 is full of statements about needing to conserve the **few 228 kilobytes of core memory** on a late 1960s machine. But 229 even my most modest low-powered Celeron and Atom-based 230 computers have **L1 CPU caches** that dwarf those 231 quantities! 232 233 So, given the tiny size of the programs I was writing with 234 my JONESFORTH port, I kept thinking, "how far could I get if 235 I just inlined _everything_?" As in, actually made a copy of 236 every word's machine instructions every time it is 237 "compiled". 238 239 I expect this will be silly but fun and educational.