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/log14.txt

Download raw file: log14.txt

1 Hi! I've decided to treat myself to some yak shaving for 2 a bit. First off, the "colon" and "word" terminology 3 makes this seem more like a traditional Forth than it 4 actually is. So I'm gonna rename some stuff... 5 6 First up, I'm going to change the term "word" to "def" 7 as in "definition". I was tempted to call them 8 "functions", but I don't think these inlined chunks of 9 code are actually functions. 10 11 Visibly, the ":" (colon) will change to "def". 12 13 Ten minutes later: 14 15 $ ./meow5 16 17 def meow 18 "Meow!" say 19 ; 20 21 meow 22 Meow! 23 24 By the way, my little test script has been invaluable 25 for changes like this. I made a pass of renames and then 26 made sure I hadn't broken anything each time: 27 28 $ ./test.sh 29 ............. 30 Passed! 31 32 Okay, that was trivial. Now I want to improve the def 33 inspection experience by combining my "inspect" (gives 34 def stats) and "dump" (dumps a def's machine code) into 35 a single def because I always end up calling both of 36 them anyway. 37 38 And I also want to be able to call it like this: 39 40 inspect foo 41 42 instead of what I currently have to do: 43 44 "foo" find inspect 45 46 That turned out to also be trivial: 47 48 def m "meow" print ; 49 inspect m 50 m: 43 bytes IMMEDIATE COMPILE 51 e8 5 0 0 0 6d 65 6f 77 0 58 50 50 58 b9 0 0 0 0 80 3c 8 0 74 3 41 eb f7 51 5a 59 bb 1 0 0 0 b8 4 0 0 0 cd 80 52 53 54 Now it's easy and handy to make a pretty good 55 illustration of a pure inlining concatenative language: 56 57 def ++ + + ; 58 59 10 10 10 ++ ps 60 30 61 62 inspect + 63 +: 5 bytes IMMEDIATE COMPILE 64 58 5b 1 d8 50 65 66 inspect ++ 67 ++: 10 bytes IMMEDIATE COMPILE 68 58 5b 1 d8 50 58 5b 1 d8 50 69 70 Clearly "++" contains two copies of "+". 71 72 I'm also renaming "make_elf" to just "elf". 73 74 Here's what's fun about being able to write out a 75 program from any definition is that "compiling" from the 76 command line can look like this: 77 78 $ cat 5.meow 79 def meow 80 "Meow!\n" print 81 ; 82 83 def five-meows 84 meow 85 meow 86 meow 87 meow 88 meow 89 exit 90 ; 91 92 elf five-meows 93 94 $ ./meow5 < 5.meow 95 Wrote to "five-meows". 96 97 $ ./five-meows 98 Meow! 99 Meow! 100 Meow! 101 Meow! 102 Meow! 103 104 Gosh, this is really fun! 105 106 Something I discovered last night was that I don't have 107 a couple really obvious defs: dup and pop! 108 109 Well, *that* was easy: 110 111 STARTDEF popstack 112 pop eax 113 ENDDEF popstack, 'pop', (IMMEDIATE | COMPILE) 114 115 STARTDEF dupstack 116 pop eax 117 push eax 118 push eax 119 ENDDEF dupstack, 'dup', (IMMEDIATE | COMPILE) 120 121 $ ./meow5 122 1 2 3 4 ps 123 1 2 3 4 124 pop ps 125 1 2 3 126 dup ps 127 1 2 3 3 128 129 Now for conditionals! 130 131 I think the easiest one will be immediate-mode "if". I'm 132 going to have it just check the value on the stack (0 133 for false, 1 for true) and if will always either 134 continue for true or skip a single token of input for 135 false. 136 137 STARTDEF if 138 pop eax 139 test eax, eax ; zero? 140 jnz .continue_for_true 141 EAT_SPACES_CODE 142 GET_TOKEN_CODE 143 pop eax ; throw away token! 144 .continue_for_true: 145 ENDDEF if, '?', (IMMEDIATE) 146 147 Could it really be that easy? 148 149 def hi "Hello\n" print ; 150 0 ? hi 151 1 ? hi 152 Hello 153 154 Wow, yup. It really was that easy. 155 156 A compiled "if" wasn't nearly as easy, but I still got 157 it in a single evening. Here I'm making a silly 158 conditional plus def called "foo" with a lot of debug 159 printing turned on and comparing the compiled opcodes 160 for the conditional jump followed by the inlined "+" 161 def: 162 163 $ ./build.sh && ./meow5 164 def foo ? + ; 165 tail of def:0804a0db 166 len of def:00000005 167 here:0804b1ac 168 len of def:00000005 169 here:0804b1b5 170 inlining:0804a0db 171 inspect + 172 +: 5 bytes IMMEDIATE COMPILE 173 58 5b 1 d8 50 174 inspect foo 175 foo: 14 bytes IMMEDIATE COMPILE 176 58 85 c0 f 85 5 0 0 0 58 5b 1 d8 50 177 1 1 1 foo ps 178 1 1 179 1 1 0 foo ps 180 1 1 2 181 182 Very cool! 183 184 The key part of making this work was remembering that it 185 needed to be set as a "RUNCOMP" def - so it would 186 actually *execute* in compile mode rather than be 187 compiled in as-is! 188 189 Now for looping! 190 191 The next thing is "loop?", which will be nearly identical 192 except it'll insert an unconditional jump after the def 193 that jumps back to perform the test again. 194 195 loop? <def> 196 197 Which should be fairly painless to use, I hope. 198 199 Hey, it works! I had some bugs, but nothing worth 200 writing up here. 201 202 Oh, and I'm now a HUGE fan of Evan Teran's EDB! Even 203 though it's a GUI program, it has now completely 204 replaced GDB for me with assembly language debugging. It 205 automatically halts on the entry instruction and 206 displays a disassembly - GDB makes this *brutally* hard 207 if you don't have a C program with debugging symbols. 208 EDB also shows graphical arrows to illustrate jumps, 209 multiple memory display areas, registers, etc. Pretty 210 intuitive to use. 211 212 https://github.com/eteran/edb-debugger 213 214 Oh, I also added comments! It's pretty much a copy of 215 "eat_spaces" for input, but the name of the def is "#" 216 to start the comment and it advances the input pointer 217 until it gets to a newline character. It's a RUNCOMP 218 def, so comments can also apepar in defs. 219 220 So here's my first loop test program: 221 222 ====================================================== 223 224 $ cat loop.meow 225 # A meow5 program to test looping 226 227 # A chunk of code to be looped! 228 def foo 229 dup # duplicate the number on the stack 230 printnum # print it 231 " " print # and a space 232 dec # reduce the one still on the stack 233 234 # Another def that loops! 235 def foo-loop 236 loop? foo 237 "\n" print # put a newline after calling loop 238 ; 239 240 # Try some loops! 241 5 foo-loop 242 10 foo-loop 243 244 ====================================================== 245 246 And let's run it: 247 248 $ ./meow5 < loop.meow 249 5 4 3 2 1 250 10 9 8 7 6 5 4 3 2 1 251 252 Heck yeah! 253 254 (Note that printnum is NOT position-independent code, so 255 exporting an ELF with this particular example explodes.) 256 257 Now I can do a loop version of the five meow printing 258 program: 259 260 261 ====================================================== 262 263 $ cat five-loop.meow 264 def meow 265 "Meow!\n" print 266 267 # decrement the stack each time or 268 # we'll end up with an infinite loop! 269 dec 270 ; 271 272 # Another def that loops five times and exits 273 def five-meows-loop 274 5 loop? meow 275 exit 276 ; 277 278 # Compile as an ELF executable 279 elf five-meows-loop 280 281 ====================================================== 282 283 Let's try it! 284 285 $ ./meow5 <five-loop.meow 286 Wrote to "five-meows-loop". 287 288 $ ./five-meows-loop 289 Meow! 290 Meow! 291 Meow! 292 Meow! 293 Meow! 294 295 What's really cool about this is that the five-meows 296 program that has five copies of the 'meow' def is 317 297 bytes and this looping version is only 160 bytes! 298 299