1 Welcome back! In the last log, I got string literals
2 working with the 'quote' word.
3
4 One thing lead to another and I ended up solving a lot
5 of different problems so that now I can define words
6 not just from "primitives" (machine code words written
7 in assembler), but also words composed from *other*
8 non-primitive words.
9
10 So yeah, you can only make programs that print strings,
11 but they are real programs.
12
13 Okay, so now the fun TODOs!
14
15 [ ] Pretty-print meta info about word!
16 [ ] Loop through dictionary, list all names
17 [ ] Loop through dictionary, pretty-print all
18
19 (And I'm not being sarcastic or something - I'm really
20 looking forward to these.)
21
22 First, I need general number handling! I need to parse
23 numbers from input and I need to be able to print
24 numbers as strings.
25
26 [ ] New word: str2num (ascii to integer)
27 [ ] New word: num2str (integer to ascii)
28
29 I'd also like to have the ability to intuitively append
30 strings. Ideally:
31
32 "There are " 5 " chickens."
33 "Your name is '" username "'?"
34
35 (I also don't have variables yet, so the username one is
36 even more hypothetical. But you get the idea.)
37
38 My new 'str2num' word seems to work. I realized I could
39 use the exit status of the epplication since it is
40 already popping a number from the stack (which I'd
41 completely forgotten about, ha ha).
42
43 I updated my `mr` (meow5 run) alias to print the exit
44 status:
45
46 alias mr=`./build.sh run ; echo "Exit status: $?"'
47
48 The current parse/print number radix is set in a new
49 "variable" currently set to decimal on startup:
50
51 mov dword [radix], 10
52
53 And created these words to manage the radix:
54
55 radix (takes number from stack)
56 dec - sets radix to 10
57 hex - sets radix to 16
58 oct - sets radix to 8
59 bin - sets radix to 2
60
61 Now I'll try str2num out with a decimal number:
62
63 42 exit
64
65 $ mr
66 Exit status: 42
67
68 And I'll mess with it by giving it a hex number without
69 updating the radix to hex:
70
71 ff exit
72
73 $ mr
74 Could not find word "ff" while looking in IMMEDIATE mode.
75 Exit status: 1
76
77 Now I'll compact the results a bit to not take up too
78 much room here:
79
80 hex ff exit <-- hexadecimal
81 Exit status: 255
82
83
84 oct 100 exit <-- octal
85 Exit status: 64
86
87 bin 101010 exit <-- binary
88 Exit status: 42 (uh...seems right)
89 bin 11111111 exit
90 Exit status: 255 (yup, that's definitely right)
91
92 Alright! Now this is getting fun. It'll be even better
93 when it's taking input from STDIN to be fully
94 interactive.
95
96 Number printing is interesting. So I could just have it
97 print numbers when requested, like Forth's DOT (.) word.
98 But to print some mixed strings and numbers would look
99 like this:
100
101 "You own " print 16 printnum " snakes." print
102
103 And like I wrote above in this log, I'd rather have
104 strings and numbers automatically appended like this:
105
106 "You own " 16 " snakes." print
107
108 But I'm not sure if that's a good idea or not because
109 I'm going to either have to further complicate my
110 interpreter (it's already doing look-ahead for quotes so
111 I don't have to have a space like in Forth: " foo") or
112 some up with something else.
113
114 One thing I've thought about is having interpolated
115 strings. As long as I have some unambiguous symbol, I
116 could have it pop numbers off the stack and append them
117 to the string as I go:
118
119 16 "You own $ snakes." print
120
121 I planed to have escapes anyway, so \$ for a literal '$'
122 is no problem.
123
124 Okay, I like that.
125
126 So I need to write num2str now and then add number
127 interpolation to my 'quote' word. This is gonna be cool!
128
129 Next night: Wrote num2str. I've got a division error.
130 Here's how I'm testing:
131
132 ; test num2str
133 push 42 ; num
134 push dword [free] ; store here
135 CALLWORD num2str
136 pop eax ; chars written
137
138 First, let's double-check the input:
139
140 (gdb) break num2str
141 528 pop ebp ; address of string destination
142 num2str () at meow5.asm:529
143 529 pop eax ; number
144 num2str () at meow5.asm:530
145 530 mov ecx, 0 ; counter of digit characters
146 531 mov ebx, [var_radix]
147 532 mov edx, 0
148 (gdb) p/a $ebp
149 $1 = 0x804b114 <data_area> <-- addr for string storage
150 (gdb) p $eax
151 $2 = 42 <-- num to convert
152 (gdb) p $ebx
153 $3 = 10 <-- radix
154
155 That's all good:
156
157 string storage addr: 0x804b114
158 num to convert: 42
159 radix: 10
160
161 Now we'll start converting. I divide the number by the
162 radix. The remainder is the digit that was less than the
163 radix (so 0-9 for dec, 0-f for hex, etc.). The result of
164 the division is the rest of the number.
165
166 (gdb) s
167 num2str.divide_next () at meow5.asm:534
168 534 idiv ebx ; eax / ebx = eax, remainder in edx
169 (gdb) p $eax
170 $6 = 4 <-- quotient
171 (gdb) p $ebx
172 $7 = 10 <-- radix (double-checking)
173 (gdb) p $edx
174 $8 = 2 <-- remainder (digit to convert)
175
176 Yup, that looks good. We've got the first digit (working
177 lowest to highest) to convert to a character (2) and the
178 rest of the number with the first digit gone (4).
179
180 I convert to the character equivalent and store that on
181 the stack temporarily. This is because I convert
182 starting from the least significant digit, but we want
183 the string to have the number in the expected order,
184 starting with the greatest significant digit.
185
186 (gdb) s
187 535 cmp edx, 9 ; digit bigger than 9? (radix allows a-z)
188 536 jg .toalpha ; yes, convert to 'a'-'z'
189 537 add edx, '0' ; no, convert to '0'-'9'
190 538 jmp .store_char
191 num2str.store_char () at meow5.asm:542
192 542 push edx ; put on stack (pop later to reverse order)
193 543 inc ecx
194 544 cmp eax, 0 ; are we done converting?
195 545 jne .divide_next ; no, loop
196
197 Now it's on to the next division:
198
199 Program received signal SIGFPE, Arithmetic exception.
200 num2str.divide_next () at meow5.asm:534
201 534 idiv ebx ; eax / ebx = eax, remainder in edx
202
203 There it is. Well, I'm not dividing by 0, so that's
204 good. But then I remember idiv is signed division, and
205 I'm not doing signed integers just yet. Maybe that's it?
206
207 Ah, a quick look at a reference:
208
209 "32-bit division with IDIV requires EAX be
210 sign-extended into EDX."
211
212 Yeah, I don't want to do signed division anyway because
213 I don't have the ability to parse or print negative
214 numbers (yet?) so I should really be using DIV anyway:
215
216 Program received signal SIGFPE, Arithmetic exception.
217
218 Nope! I know this isn't a divide-by-zero problem...
219
220 Oh, I needed to read a bit more:
221
222 "DIV Always divides the 64 bits value accross
223 EDX:EAX by a value."
224
225 Oh, right, I need to clear EDX to 0 in my division loop
226 because it currently holds the character to convert. It
227 makes total sense that the answer is overflowing. I was
228 dividing by a huge 64-bit number the second time around!
229
230 So I just need to clear edx before my division:
231
232 533 mov edx, 0 ; div actually divides edx:eax / ebx!
233 534 div ebx ; eax / ebx = eax, remainder in edx
234 535 cmp edx, 9 ; digit bigger than 9? (radix allows a-z)
235 (gdb) p $eax
236 $1 = 0
237 (gdb) p $edx
238 $2 = 4
239
240 That's got it! The final digit (remainder after
241 division) is 4 and there's nothing left to divide after
242 that (quotient is 0).
243
244 Once it sees the rest of the number is now 0, I store
245 the string at the address provided and return the number
246 of characters written (digits in radix) on the stack:
247
248 554 push eax ; return num chars written
249 end_num2str () at meow5.asm:149
250 149 mov eax, [return_addr] ; RETURN
251 (gdb) p $eax
252 $1 = 2
253 (gdb) x/s $ebp
254 0x804b114 <data_area>: "42"
255
256 Correct! Two characters were written, and the string is
257 "42", my value in decimal!
258
259 Since I can now, I'll make the test print real nice and
260 use the exit value to show the number of digits written
261 (to make sure it's correct):
262
263 ; test num2str
264 CALLWORD decimal
265 push dword 42 ; num
266 push dword [free] ; store here
267 CALLWORD num2str
268 PRINTSTR "Answer: "
269 push dword [free] ; print from here
270 CALLWORD print
271 CALLWORD newline
272 CALLWORD exit ; stack still has chars written
273
274 $ mr
275 Answer: 42
276 Exit status: 2
277
278 Correct. Let's see some others:
279
280 CALLWORD oct
281 push dword 64
282
283 Answer: 100
284 Exit status: 3
285
286 CALLWORD hex
287 push dword 3735928559
288
289 Answer: deadbeef
290 Exit status: 8
291
292 CALLWORD bin
293 push dword 7
294
295 Answer: 111
296 Exit status: 3
297
298 CALLWORD bin
299 push dword 257
300
301 Answer: 100000001
302 Exit status: 9
303
304 Looks good! Now I'm kinda wishing the interpreter was
305 interactive because that would have been way more fun
306 than changing the assembly test for each run. But
307 that'll be coming up soon enough.
308
309 After sleeping on it, I'm really excited about the idea
310 of string interpolation using '$' as a placeholder. So
311 I'll make that the next todo:
312
313 [ ] Add "$" placeholders so the 'quote' word so it
314 can interpolate numbers from the stack into the
315 string.
316
317 I suspect the hard part will be remembering to push the
318 stack values in the opposite order that they appear in
319 the string...so we'll see what that's like in practice.
320
321 Okay, I've got it working, but only just barely. It
322 feels incredibly fragile. In particular, the interpreter
323 has become way too complicated for my taste. Juggling
324 registers willy-nilly and using the stack when I run out
325 of places to put things is just sloppy.
326
327 It does work. Cleaned up example:
328
329 42 "The answer is $." print
330 42 hex "The answer is 0x$ in hex." print
331 42 bin "The answer is $ in computer." print
332 42 oct "The answer is $ in octal." print
333
334 Output:
335
336 The answer is 42.
337 The answer is 0x2a in hex.
338 The answer is 101010 in computer.
339 The answer is 52 in octal.
340
341 Which is really cool. But I need to get my mess under
342 control.
343
344 For one thing, I've been using the registers very
345 inconsistantly because I don't have any plan regarding
346 where to put stuff.
347
348 This article has helped a ton:
349
350 https://www.swansontec.com/sregisters.html
351
352 I'm not sure I'm going to re-write EVERYTHING to
353 follow these guidelines, but new stuff definitely will
354 and I'm going to immediately switch the 'quote' word to
355 use the esi and edi registers!
356
357 Using esi and edi in the 'quote' word helped make the
358 usage much clearer and simplified some of the address
359 manipulation considerably (the source and destination
360 pointers aren't always moving in sync because escapes
361 and placeholders in the source string may expand to a
362 different number of characters in the destination
363 string.
364
365 Another thing that makes the interpreter so fragile is
366 the way I have to juggle registers and the stack around
367 to check for a valid numeric literal when an input token
368 doesn't match any of the exisitng words.
369
370 So what I'm thinking is that just like quote, I'll test
371 for tokens that start with a number and handle them
372 _before_ trying to find them in the dictionary.
373
374 Consequently, I won't be able to start any word or
375 variable names with a number. (Like Forth allows.)
376 In exchange, it should simplify the interpreter a fair
377 amount in some messy areas.
378
379 [ ] Break out 'number' into separate word that does
380 pre-check like 'quote'.
381
382 This will also give it a nice symetry with the way I'm
383 handling string literals (my first big departure from
384 pur Forth syntax).
385
386 Three sleepy nights later: Well, it works...but I've now
387 exposed another problem.
388
389 This is what the top of my interpreter loop looks like:
390
391 get_next_token:
392 CALLWORD eat_spaces ; skip whitespace
393 CALLWORD quote ; handle any string literals
394 CALLWORD number ; handle any number literals
395 CALLWORD get_token
396
397 It's nice and neat and readable. The problem is that it
398 doesn't work unless you have an optional quote followed
399 by an optional number followed by a non-string,
400 non-quote token.
401
402 Two strings in a row or a number followed by a string or
403 various other combos won't work.
404
405 Here's what needs to happen:
406
407 interpreter loop:
408 eat spaces
409 if quote
410 make string literal
411 restart interpreter loop
412 if number
413 make number literal
414 restart interpreter loop
415 if token
416 find/execute/compile
417 restart interpreter loop
418 else
419 error
420
421 So as much as I liked having 'quote' and 'number' handle
422 all aspects of those respective inputs, it's just making
423 everying too interdependant. I need to break them up
424 into even smaller words and give more control back to
425 the interpreter where it belongs.
426
427 So more refactoring awaits me before I can do the fun
428 stuff. Ah well, this is where the "slow and steady"
429 approach wins - I know I'll eventually push through this
430 kinda boring housekeeping stuff.
431
432 Next night: Progress made on that refactor. Then there's
433 a segfault. So that's what I'll work on tomorrow night.
434
435 Next night: Looks like the segfault is happening in old
436 code (specifically inline, but because of wrong
437 calculations in semicolon ";"). Since the old code won't
438 just start failing for no reason, this has to be due to
439 bad input from all the changes I've made in the outer
440 interpreter loop.
441
442 Hard to track down with GDB with breakpoints and watch
443 statements. So I added some print debugging and sure
444 enough:
445
446 COLON here: 0804a290
447 inlining print
448 SEMICOLON there: 0804b2e6
449
450 That's the problem. 'colon' is trying to save the
451 machine code start address for 'meow' on the stack and
452 'semicolon' should be getting that same value, but it's
453 not. It's while executing this, by the way:
454
455 : meow "Meow." print ;
456
457 The botched semicolon tail writing is why trying to use
458 'meow' in 'meow5' fails - the calculations are wildly
459 wrong (it thinks the machine code length is -4144).
460
461 So I need to figure out where I'm leaving something on
462 the stack. But how?
463
464 Oh, I've got an idea, That wrong value from the stack
465 looks like an address. I'll fire up GDB and see what's at
466 that address. Maybe that will help.
467
468 (gdb) x 0x0804b2e6
469 0x804b2e6: 0x776f654d
470
471 Hmmm. Looks like a string?
472
473 (gdb) x/s 0x0804b2e6
474 0x804b2e6: "Meow."
475
476 Aha! Interesting. It's the address from the string
477 literal before the print. What's that doing there? In
478 compile mode, 'quote' should be compiling that address
479 into the new word's machine code memory, not on the
480 stack.
481
482 Oh, right, I knew that would come back to haunt me. At
483 some point, my run/compile logic got lost (could be a
484 dozen commits ago by now). I'm currently compiling the
485 instruction to push the string's address *and* I'm also
486 actually pushing the string's address every single time.
487 So this has actually been broken for a while and my
488 refactoring has (almost?) nothing to do with it.
489
490 This is why I need a set of proper regression tests
491 ASAP. Well, I'll get there soon. Gotta be able to take
492 input before I can do that!
493
494 Anyway, time to add (back) some conditional jumps in
495 'quote'!
496
497 The answer is 42.
498 The answer is 0x2a in hex.
499 The answer is 101010 in computer.
500 The answer is 52 in octal.
501 Meow.Meow.Meow.Meow.Meow.
502
503 Much better.
504
505 And I was reminded that I had been in the middle of
506 adding escape sequences to string literals! Let's try
507 them out:
508
509 42 "I paid \$$ for beans\\cheese.\nOkay?\n" print
510
511 $ mr
512 I paid $42 for beans\cheese.
513 Okay?
514
515 Wow, that was super satisfying.
516
517 Okay, I think *now* I'm finally ready to make those
518 introspective word-printing words!
519
520 Next night: Uh, so I didn't think far enough ahead. I
521 made some really nice string making and printing
522 facilities, but I can't use them easily from assembly.
523 Nor can I define something as complex as a "dictionary"
524 printing word (to use the Forth term for all the defined
525 words.)
526
527 Hmmm... this is a quandary.
528
529 Okay, it wasn't so bad. I just needed to write an
530 additional word 'printnum' (with macro PRINTNUM_CODE) to
531 help it out.
532
533 After that, this is all it takes to write an 'inspect'
534 that takes a word tail address and prints the word name
535 and bytes of machine code (from the tail metadata):
536
537 DEFWORD inspect
538 pop esi ; get tail addr
539 lea eax, [esi + T_NAME]
540 push eax
541 PRINT_CODE
542 PRINTSTR ": "
543 mov eax, [esi + T_CODE_LEN]
544 push esi ; preserve tail addr
545 ; param 1: num to be stringified
546 push eax
547 PRINTNUM_CODE
548 PRINTSTR " bytes "
549 NEWLINE_CODE
550 ENDWORD inspect, 'inspect', (IMMEDIATE)
551
552
553 Here it is printing the word 'find':
554
555 "find" find inspect
556
557 find: 58 bytes
558
559 Neat!
560
561 Oh, and I guess I can show my new 'printnum' in
562 standalone action:
563
564 bin 11101111 hex printnum
565
566 ef
567
568 Nice.
569
570 +--------------------------------------------------------+
571 | |
572 | Just so it's clear I'm not trying to trick anybody, |
573 | the interpreter input is still hard-coded into |
574 | memory, so that calling line really looks like this |
575 | in assembly: |
576 | |
577 | db 'bin 11101111 hex printnum ' |
578 | |
579 | I'm just trying to make the log readable, so I leave |
580 | off the 'db' and quotes. |
581 | |
582 | I'll take input on STDIN very soon (next!). |
583 | |
584 +--------------------------------------------------------+
585
586 And now I can find out how big a "meow" printing
587 function is in machine code (minus the "Meow." string
588 itself, because that's stored elsewhere. And also the
589 combined five meows:
590
591 "meow" find inspect
592 "meow5" find inspect
593
594 meow: 38 bytes
595 meow5: 190 bytes
596
597 There we go. So even repeating the entire printing
598 routine five times is still under 200 bytes of machine
599 code. When's the last time you shed a tear over 200
600 bytes?
601
602 Okay, now I just need a loop to call inspect for every
603 defined word and I'm all set.
604
605 I'm not sure I want to use the term "dictionary" from
606 Forth (or "word", for that matter). Hmm, I guess I can
607 avoid the issue by just calling it 'inspect_all'...
608
609 Almost there on inspect_all. It seems something has
610 left the token str addr on the stack and it's getting in
611 the way of popping the link addr and ruining everything.
612 At least, that's what it looks like.
613
614 Next night: So what I woke up thinking in the morning is
615 that if I'm not maintaining my stack correctly between
616 words, there are three ways I could debug this:
617
618 1. Painfully, with GDB stepping through...a lot.
619
620 2. Write a DEBUG_STACK macro that non-destructively
621 prints the values on the stack from anywhere within
622 the assembly.
623
624 3. Write a script (perhaps in awk?) that checks the
625 push/pop balance by following all of the called or
626 inlined code and incrementing a counter for each push
627 and decrementing for each pop.
628
629 Obviously I'm trying to avoid Option 1.
630
631 Option 3 sounds neat, but I'm not sure how to *display*
632 that information so that it helps me track down the
633 problem - some words push more than they pop and
634 vice-versa. It'd still be a lot of manual checking.
635
636 Option 2 seems generally useful anyway, so I'm going to
637 give that a shot and hope that peppering it about the
638 program will help me figure out what's going on.
639
640 Hmmm...well, printing the stack at the Meow5 language
641 level is no problem, but printing it at the assembly
642 level is a big problem. I've got a chicken-and-egg
643 problem where I need to preserve the registers it uses
644 AND preserve the stack.
645
646 I could preserve the registers in a purpose-built bit of
647 memory.
648
649 Well, I think I did that right. Here's the whole ugly
650 thing:
651
652 section .bss
653 ds_eax: resb 4
654 ds_ebx: resb 4
655 ds_ecx: resb 4
656 ds_edx: resb 4
657 ds_esi: resb 4
658 ds_edi: resb 4
659 section .text
660 %macro DEBUG_STACK 0
661 mov [ds_eax], eax
662 mov [ds_ebx], ebx
663 mov [ds_ecx], ecx
664 mov [ds_edx], edx
665 mov [ds_esi], esi
666 mov [ds_edi], edi
667 PRINTSTR "Stack: "
668 PRINTSTACK_CODE
669 NEWLINE_CODE
670 mov eax, [ds_eax]
671 mov ebx, [ds_ebx]
672 mov ecx, [ds_ecx]
673 mov edx, [ds_edx]
674 mov esi, [ds_esi]
675 mov edi, [ds_edi]
676 %endmacro
677
678 But talk about chicken-and-egg problem. Or is it more
679 of a "who watches the watchmen?" thing? Or maybe there's
680 an even better expression for it, but my problem is that
681 I think I'm acutally calling whatever is doing bad stack
682 things WHILE trying to print the stack... :-(
683
684 Okay, it's worse than even that. I don't know how I've
685 gotten away with it this long, but I've been using a
686 bunch of words mid-assembly to debug stuff...and a lot
687 of them aren't safe for that (like 'newline') because
688 they don't preserve the registers.
689
690 So it's possible that by trying to observe what's going
691 on for the 'inspect_all' feature, I've been ruining it!
692
693 I have to remember that most of my words are only safe
694 if I'm treating them as if they're being called from the
695 language - transfering values on the stack and not
696 giving a hoot what happens to the registers in between!
697
698 Okay, I think I got it. No idea how this got here, but
699 I had an extra pop for no reason I could understand in
700 'printnum'! I have no idea how it got there, but that
701 would definitely mess things up:
702
703 pop esi ; get preserved <-----??????
704
705 I think the lesson is to test all new words more
706 thoroughly before moving on!
707
708 Okay, can we _finally_ see the output of 'inspect_all'?
709
710 $ mr
711 Meow. Meow. Meow. Meow. Meow.
712 meow5: 190 bytes IMMEDIATE COMPILE
713 meow: 38 bytes IMMEDIATE COMPILE
714 inspect_all: 359 bytes IMMEDIATE
715 inspect: 340 bytes IMMEDIATE
716 ps: 177 bytes IMMEDIATE COMPILE
717 printmode: 100 bytes IMMEDIATE COMPILE
718 printnum: 116 bytes IMMEDIATE COMPILE
719 number: 306 bytes IMMEDIATE COMPILE
720 decimal: 10 bytes IMMEDIATE COMPILE
721 bin: 10 bytes IMMEDIATE COMPILE
722 oct: 10 bytes IMMEDIATE COMPILE
723 hex: 10 bytes IMMEDIATE COMPILE
724 radix: 6 bytes IMMEDIATE COMPILE
725 str2num: 95 bytes IMMEDIATE COMPILE
726 quote: 247 bytes IMMEDIATE COMPILE
727 num2str: 58 bytes IMMEDIATE COMPILE
728 ;: 150 bytes COMPILE RUNCOMP
729 return: 7 bytes IMMEDIATE
730 :: 137 bytes IMMEDIATE
731 copystr: 18 bytes IMMEDIATE COMPILE
732 get_token: 55 bytes IMMEDIATE
733 eat_spaces: 38 bytes IMMEDIATE COMPILE
734 find: 58 bytes IMMEDIATE
735 is_runcomp: 5 bytes IMMEDIATE COMPILE
736 get_flags: 7 bytes IMMEDIATE COMPILE
737 inline: 25 bytes IMMEDIATE
738 print: 33 bytes IMMEDIATE COMPILE
739 newline: 26 bytes IMMEDIATE COMPILE
740 strlen: 16 bytes IMMEDIATE COMPILE
741 exit: 8 bytes IMMEDIATE COMPILE
742 Goodbye.
743 Exit status: 0
744
745 Yay! At last! Every word in the system with the number
746 of bytes of machine code and mode(s).
747
748 And with the help of the shell, we can sort by size.
749
750 $ ./meow5 | awk '
751 /^.*: [0-9]+/ {t=t+$2; print $2, $1}
752 END{print "Total bytes:", t}
753 ' | sort -n
754
755 5 is_runcomp:
756 6 radix:
757 7 get_flags:
758 7 return:
759 8 exit:
760 10 bin:
761 10 decimal:
762 10 hex:
763 10 oct:
764 16 strlen:
765 18 copystr:
766 25 inline:
767 26 newline:
768 33 print:
769 38 eat_spaces:
770 38 meow:
771 55 get_token:
772 58 find:
773 58 num2str:
774 95 str2num:
775 100 printmode:
776 116 printnum:
777 137 ::
778 150 ;:
779 177 ps:
780 190 meow5:
781 247 quote:
782 306 number:
783 340 inspect:
784 359 inspect_all:
785
786 Total bytes: 2655
787
788 (Command line formatted for semi-readability and total
789 bytes separated out for clarity.)
790
791 So the smallest word is 'is_runcomp', which tests for
792 the RUNCOMP flag in a mode and pushes the result.
793
794 The largest word is not a surprise to me, 'inspect_all',
795 which contains 'inspect' and puts a loop around it. In
796 turn, 'inspect' includes a bunch of string and number
797 printing code.
798
799 So let's see how I did:
800
801 [x] Pretty-print meta info about word!
802 [ ] Loop through dictionary, list all names
803 [x] Loop through dictionary, pretty-print all
804 [x] New word: str2num (ascii to integer)
805 [x] New word: num2str (integer to ascii)
806 [x] Add "$" placeholders so the 'quote' word so it
807 can interpolate numbers from the stack into the
808 string.
809 [x] Break out 'number' into separate word that does
810 pre-check like 'quote'.
811
812 I forgot about a loop that just prints all the word
813 names. And 'number' works a little differently now than
814 I'd envisioned. But I'm gonna call this part done for
815 now and close out this log file.