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

Download raw file: log09.txt

1 It's pretty wild what I *don't* yet have in this 2 language: 3 4 * Basic math operations 5 * Conditionals 6 * Loops 7 * User-defined variables 8 9 The easiest one to rectify, I think, will be some basic 10 math operations. With that, I can at least have a decent 11 RPN calculator. 12 13 Before I do that, I'd like to now start separating the 14 language primitives I used in the creation of the 15 interpreter versus everyhing else. 16 17 Putting everything else in another file and including it 18 turns out to be super easy with NASM: 19 20 %include 'stdlib.asm' 21 22 So far, stdlib.asm contains just these: 23 24 ps (print stack) 25 inspect 26 inspect_all 27 all (all names) 28 29 So now I'll...YUCK, nevermind! NASM doesn't report the 30 line number of an error in an included file - it really 31 pretends the included content is actually in the file 32 that did the including. It was probably premature to 33 split this up anyway. 34 35 So everything will stay in meow5.asm as before. 36 37 Okay, so the math operations are easy because I'm just 38 popping the arguments, calling the CPU instructions, and 39 pushing the answer: 40 41 $ mr 42 20 8 - ps 43 12 44 4 * ps 45 48 46 3 / ps 47 0 16 48 "4 divided by 3 is $ remainder $\n" print 49 4 divided by 3 is 16 remainder 0 50 bin 51 1011 0100 + ps 52 1111 53 1111 hex ps 54 f f 55 0beef ps 56 f f beef 57 + ps 58 f befe 59 + ps 60 bf0d 61 Goodbye. 62 Exit status: 0 63 64 Increment and decrement! Bam! Easy! 65 66 45 inc ps 67 46 68 dec dec dec ps 69 43 70 71 I love easy stuff! 72 73 I'll need to figure out some more substantial stuff 74 next. Variables, maybe. Or conditionals. 75 76 Next week: I chose to implement variables first. Then I 77 took some time off to rest a bit. 78 79 Now I've got a first stab at 'var'. 80 81 (I originally planned to have variables put their VALUES 82 on the stack when called, but after I started 83 implementing them, I've decided to follow yet another 84 Forth convention: variables will put their ADDRESSES on 85 the stack. It's just so much simpler and more flexible 86 that way.) 87 88 So far, 'var' borrows from three existing words: 89 90 1. From 'colon', it takes the idea of getting the 91 next token from the input stream as a name. 92 93 2. From 'quote', it takes the compiling of the 94 machine code to push the immediate value 95 containing the address of the variable's memory. 96 97 3. From 'semicolon' - actually, it doesn't borrow, 98 it flat out *includes* semicolon. 99 100 Does it work? 101 102 var foo 103 hex foo "Address of foo: $\n" print 104 Address of foo: 804c16c 105 var bar 106 hex bar "Address of bar: $\n" print 107 Address of bar: 804c189 108 109 Seems plausible! So I guess I need new words to get 110 values to/from that address and the stack. 111 112 I think I'll call them "get" and "set" rather than the ! 113 ("store") and @ ("fetch") terminology from Forth. 114 115 They're easy to write in assembly. Just a handful of 116 instructions. 117 118 Here goes: 119 120 var foo 121 42 foo set 122 : foo? foo "Foo contains $\n" print ; 123 foo? 124 ./build.sh: line 34: 1465 Segmentation fault ./$F 125 Exit status: 139 126 127 Oops, I didn't even write that correctly. I forgot the 128 'get' in the definition of 'foo?'. But it should still 129 have printed the address of foo. So something has gone 130 awry. Now I know what I'm doing tomorrow night. 131 132 Next night: Okay, let's see where this crashes using 133 GDB. First, Can I set and get the variable? 134 135 Starting program: /home/dave/meow5/meow5 136 var foo 137 55 foo set 138 foo get 139 ps 140 134525204 55 141 142 Yes! Okay, I've got some extra garbage on the stack 143 (looks like an address that I'm not cleaing up at some 144 point). I'll deal with that in good time. 145 146 But 55 on the stack totally means 'foo get' worked. 147 Neat! 148 149 And can I print it from a string in immediate mode? 150 151 "foo is $\n" print 152 foo is 55 153 154 No problemo. 155 156 Now in a compiled word where it crashed last night. 157 (This time I remembered to do a 'get' as well): 158 159 : foo? foo get "foo=$\n" ; 160 foo? 161 162 Program received signal SIGSEGV, Segmentation fault. 163 0x0804b114 in token_buffer () 164 165 Yeah, so there we are. Apparently it crashes 166 while...what? Trying to execute code in token_buffer? 167 168 Well, I think the real problem is probably something 169 fundamental. Like, I haven't really thought through how 170 something works in a compiled word versus immediate 171 mode. 172 173 Next night: Okay, LOL, so first of all, I never 174 implemented numeric literals for compile mode. So I 175 can't even test strings like this: 176 177 $ mr 178 : x 5 "five is $" print ; 179 TODO: a new compile number word?Exit status: 0 180 181 And second of all, I don't think I ever tested number 182 interpolation (via '$' placeholders) in compiled words 183 either. Because that crashes: 184 185 $ mr 186 : x "stack has $" print ; 187 5 x 188 ./build.sh: line 34: 1390 Segmentation fault ./$F 189 Exit status: 139 190 191 So I should probably get those working. I'll start with 192 the second problem and work my way back to variable 193 printing: 194 195 [ ] Get $ placeholders working in compiled words 196 [ ] Implement compiled number literals 197 [ ] Implement compiled variable get/set 198 199 I'm not sure yet if that third item is even needed. 200 201 Okay, so I'm reviewing my 'quote' definition and the 202 thing that immediately stands out is that since I call 203 'quote' from the interpreter in immediate mode, it 204 should be "baking" in the placeholder value when the 205 string is put in memory. 206 207 So, for example: 208 209 12 210 211 : foo "I have $ coins." ; 212 213 6 214 215 foo 216 217 Should be printing "I have 12 coins." instead of "I have 6 218 coins." 219 220 Instead, it's crashing. 221 222 But before I fix that, I think I should re-think how 223 this works. Because I'm pretty sure as a programmer, I 224 would expect to have 'foo' output "I have 6 coins." 225 226 I mean, both ways have advantages and disadvantages. But 227 doing "late binding" on the placeholder seems much more 228 sensible and useful. I want the string to reflect the 229 value on the stack when I *run* 'foo', not when I define 230 it. 231 232 So I think what I need is for 'print' to contain the 233 placeholder stuff, not 'quote'! 234 235 Yuck, that's a pretty significant chnage. But I don't 236 really see a way around it if I want the language to 237 make any sense. 238 239 So, the new TODO is gonna have to be: 240 241 [ ] Make 'quote' just store the string with '$' 242 [ ] Make 'print' evaluate '$' placeholders in 243 strings (at "run time") 244 245 I guess this will also require a new output buffer so I 246 can build up strings to output. Or maybe I store them in 247 my general "free" memory as temporary storage? 248 249 Next night: Okay, let's get to this. 250 251 Step one is to not have 'quote' replace placeholder '$' 252 with numbers anymore. I'll just comment them out for 253 now. 254 255 Here's the simple example that crashed above: 256 257 : x "stack has $" print ; 258 5 x 259 stack has $ 260 261 Easy. 262 263 Next, 'print' needs to print a number from the stack 264 when it sees a '$' in the string. 265 266 There are two ways I could do this: 267 268 1. Make yet *another* copy of this string for 269 temporary printing purposes that has the number 270 string incorporated and print that. 271 272 2. Print the string up to the place holder, then 273 print the number, then print the next bit of 274 string after the placeholder, etc. 275 276 Hmmm... I'm not *loving* either of those options, The 277 disadvantage of #1 is that I need to find another place 278 to store the string - ideally temporary. The 279 disadvantage of #2 is that it's more complicated. 280 281 I feel like #1 can't be made "nicer". But maybe with 282 some thoughtful design, #2 could be broken into 283 palatable chunks of functionality. 284 285 Next night: Okay, after some painful stack and register 286 management, I've got it! 287 288 var foo 289 : foo? foo get "foo=$\n" print$ ; 290 55 foo set 291 foo? 292 foo=55 293 294 Okay, so I stuck with the plan to put the '$' 295 placeholder functionality in the printing mechanism 296 rather than the string building mechanism ('quote'). 297 298 Rather than turn 'print' into an abomination, I simply 299 added a 'print$' word that does number interpolation. 300 301 Here you can see 'print' vs 'print$' for comparison: 302 303 42 304 305 "--$--" print 306 --$-- 307 308 "--$--" print$ 309 --42-- 310 311 And 'print$' really wasn't too bad. I mean, it's still 312 quite compact. It was no fun to write. 313 314 I like how it cleaned up 'quote' a bit as well. That 315 word was way too long. 316 317 Okay, I need to add a *bunch* of stuff to my test script 318 next to double-check that what I have so far is 319 rock-solid. 320 321 I already know that something (semicolon?) is leaving an 322 address on the stack that it shouldn't. So I'll be 323 fixing that as well. 324 325 [ ] Fix whatever is leaving an addr on stack 326 327 Wait, I almost forgot the fun part: I was going to add a 328 new printing convenience word to wrap up 'print$' plus a 329 newline: 'say'. I love convenience! 330 331 1 2 + "I have $ coins." say 332 I have 3 coins. 333 334 Ah! I love it so much. Yes! What a difference one little 335 handy word makes. 336 337 I'm going update my hello world examples with 'say' now. 338 339 Done. 340 341 And now a confession: I've not been happy with my 342 Expect-based tests. Expect waits for the input you asked 343 for (until a timeout is reached) and it is perfectly okay with getting things you 344 *didn't* ask for. Which makes plenty of sense for 345 interacting with some applications. 346 347 But that does fit what I want to see, which is *exact* 348 output. And I want *immediate* failure when something 349 other than that is provided. 350 351 No doubt I can force Expect to do that, but I'm gonna 352 give shell scripts a shot. 353 354 First of all, I can't remember if I've tried piping 355 input into my interpreter yet, so here goes: 356 357 $ echo '"Hello" say' | ./meow5 358 Hello 359 Goodbye. 360 361 And we can make sure that I get my exact output with a 362 grep search: 363 364 $ echo '"Hello" say' | ./meow5 | grep '^Hello$' 365 Hello 366 $ echo $? 367 0 368 369 grep's 0 exit status means match was found. 370 371 $ echo '"Hello" say' | ./meow5 | grep '^foobar$' 372 $ echo $? 373 1 374 375 And 1 exit status is not found. 376 377 So scripting this is no problem. Here's my shell 378 function: 379 380 function try { 381 # -q tells grep to not echo, just return match status 382 if echo "$1" | ./meow5 | grep -q "^$2\$" 383 then 384 printf '.' 385 else 386 echo 387 echo "Error!" 388 echo "Wanted:" 389 echo "-------------------------------------------" 390 echo $2 391 echo "-------------------------------------------" 392 echo 393 echo "But got:" 394 echo "-------------------------------------------" 395 echo "$1" | ./meow5 396 echo "-------------------------------------------" 397 echo 398 fi 399 } 400 401 And some simple tests: 402 403 try '"Hello" say' 'Hello' 404 try '"Hello" say' 'beans' 405 406 $ ./test.sh 407 . 408 Error! 409 Wanted: 410 ------------------------------------------- 411 beans 412 ------------------------------------------- 413 414 But got: 415 ------------------------------------------- 416 Hello 417 Goodbye. 418 ------------------------------------------- 419 420 Cool, then I can re-write the Expect script with this 421 and verify the functionality so far. 422 423 Oh, and implement compiled numeric literals. That's 424 still an open TODO. 425 426 Next night: Cool as a consequence of the new test 427 script, I've fixed the address being left on the stack 428 (I had guessed 'semicolon', but it was actually 'colon'. 429 I was pushing the source address to copy the token 430 string for the new word/function name, but 'gettoken' 431 was already doing that, so it was redundant.) 432 433 I also implemented numeric literals in compile mode. I'm 434 getting a LOT of mileage out of that x86 opcode to push 435 an immediate 32 bit value onto the stack! 436 437 Now my tests look like: 438 439 # Input Expected Result 440 # ------------------- ------------------------ 441 try 'ps' '' 442 try '5 ps' '5 ' # note space 443 try '5 5 5 + ps' '5 10 ' 444 try '9 2 * ps' '18 ' 445 try '18 5 / ps' '3 3 ' 446 try '5 2 - ps' '3 ' 447 try '"Hello$\\\$\n" print' 'Hello$\\$' 448 try '"Hello" say' 'Hello' 449 try ': five 5 ; five ps' '5 ' 450 try ': m "M." print ; m '' say' 'M.' 451 try ': m "M." print ; : m5 m m m m m ; m5 '' say' 'M.M.M.M.M.' 452 453 And they all pass: 454 455 $ ./test.sh 456 ........... 457 Passed! 458 459 However, it looks like 'var' is pushing one to many 460 addresses because there's still one on my precious stack 461 that fouls up this test: 462 463 $ ./test.sh 464 ........... 465 Error! 466 Wanted: 467 ------------------------------------------- 468 var x 4 x set x get x ps 469 4 470 ------------------------------------------- 471 472 But got: 473 ------------------------------------------- 474 var x 4 x set x get x ps 475 134525172 4 134529332 476 Goodbye. 477 ------------------------------------------- 478 479 But I'm getting there! Feeling good about having these 480 tests to check against regressions as i add features. 481 482 I'll debug the stack issue with a bunch of DEBUG 483 statements to narrow it down like I did for 'colon'. 484 485 Next night: Yup, in fact it was the exact same bug I had 486 in 'colon'. Shame. 487 488 By the way, I found both of these by just peppering the 489 offending area my DEBUG macros and printing the value of 490 the esp register to see where something was being left 491 on the stack that shouldn't have been. It looked like 492 this once I fixed it: 493 494 var x 495 var start: ffafb9c0 496 var copys: ffafb9c0 497 var push: ffafb9bc 498 var end: ffafb9c0 499 4 x set 500 x get ps 501 4 502 x ps 503 4 134529376 504 get ps 505 4 4 506 507 Now I've fixed my test: 508 509 try 'var x 4 x set x get ps' '4 ' 510 511 And all is well: 512 513 $ mt 514 ............ 515 Passed! 516 517 And to finish it off, I need to test using (not 518 creating...yet) variables within compiled words. 519 520 The test: 521 522 var x 523 4 x set 524 : x? x get "x=$" say ; 525 x? 526 527 Expected result: 528 529 x=4 530 531 Crossing fingers: 532 533 $ mt 534 ............. 535 Passed! 536 537 Yay! 538 539 So it looks like that checks everything off for this 540 log: 541 542 [x] Get $ placeholders working in compiled words 543 [x] Implement compiled number literals 544 [x] Implement compiled variable get/set 545 [x] Make 'quote' just store the string with '$' 546 [x] Make 'print' evaluate '$' placeholders in 547 strings (at "run time") 548 [x] Fix whatever is leaving an addr on stack (twice) 549 550 See you in log10.txt!