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!