Dave's nasmjf Dev Log 20
Created: 2022-07-22
This is an entry in my developer’s log series written between December 2021 and August 2022 (started project in September). I wrote these as I completed my port of the JONESFORTH assembly language Forth interpreter.
So log19.txt had a major breakthrough with my understanding of Forth variables. And it solved a previous mystery of the need for FETCH after the variable LATEST. Unfortunately, I'm still crashing with a segfault and getting PARSE ERROR on some jonesforth.f source. So I'll set my lines to read to just before that error and continue reading and testing the Forth source: %assign __lines_of_jf_to_read 704 After RECURSE comes IF ... THEN statements! Oooh, exiciting stuff. With this, I can *finally* write programs with branching and logic! So the format is: <condition on stack> IF true-statements THEN Oh, and the Jones implementation of these only works in compiled words. These are crazy enough as it is (THEN supplies the address to branch to in a way that reminds me of INTERCAL's "come from" statement. Ha ha.) but apparently you can make some that work in immediate mode too. The gymnastics in the definition are amazing. And I can finally see 0BRANCH used properly. No wonder I wasn't having much luck on my own with that. Okay, let's see if it works: : foo IF 1 . THEN 2 . ; 1 foo 1 2 0 foo 2 That worked. Oh, and Forth is one of those languages which use numeric 1 for true, 0 for false, Now an ELSE: : bar IF 1 . ELSE 2. THEN 3 . ; 1 foo 1 3 0 foo 2 3 Excellent. Next night: This is what I'm *most* excited about: looping! BEGIN <put condition on stack> WHILE <still true, do this> REPEAT Here goes nothing: : foo BEGIN DUP 0> WHILE DUP . 1- REPEAT ; 10 foo 10 9 8 7 6 5 4 3 2 1 (By the way, I had a bit of a challenge remembering how to use 0> and 1- because they _look_ like, "zero is greater than..." and, "backwards negative one." But the Forth way to read them is: "push 0 onto the stack and check if first item is greater than that," and, "push 1 onto the stack and subtract.") Ooh, and I wonder if I can use IF to make a RECURSEive word that actually works correctly? : foo DUP . DUP 0> IF 1- RECURSE THEN ; 5 foo 5 4 3 2 1 0 Yes! Now I'm getting somewhere. UNLESS is just NOT IF...but it uses that wild [COMPILE] word to include the immediate IF word as part of its definition. Usage is simple. And I just remembered that we have TRUE and FALSE defined, so I can use those instead of 1 and 0 (take that, Perl!): : foo UNLESS 'A' EMIT ELSE 'A' 1+ EMIT THEN ; TRUE foo B FALSE foo A And yeah, I just realized I can get 'B' by incrementing 'A'. It seems my mind is in top form tonight, ha ha. Next is comments with ( parens ). And since '(' is an immediate word, it can be programmed to be smart and handle nested parethesis correcty: : foo ( this is foo ) 42 . ; ( now some foo ) foo 42 ( can we nest as much ( as we want? ( of course! ) ) ) foo 42 Amazing. Not too many languages let you change the comment syntax mid-stream. :-) Now that we have comments, Jones demonstrates some stack effect comments (which are a standard documentation format for words in Forth): : NIP ( x y -- y ) SWAP DROP ; : TUCK ( x y -- y x y ) SWAP OVER ; : PICK ( x_u ... x_1 x_0 u -- x_u ... x_1 x_0 x_u ) 1+ ( add one because of 'u' on the stack ) 4 * ( multiply by the word size ) DSP@ + ( add to the stack pointer ) @ ( and fetch ) ; Let's try them out. NIP shows that it takes two items from the stack and returns the first: 1 2 3 NIP . . 3 1 Tuck takes two items and puts a copy of the first after the second: 1 2 3 TUCK . . . . 3 2 3 1 The notation for PICK looks insane until you stare at it for a while. We get the element in the 'u'th position on the stack where 'u' is on top of the stack. 50 40 30 20 10 4 PICK . . . . . . 50 10 20 30 40 50 Clever notation and did you see the definition above for PICK? It had not occurred to me to fetch items on the stack via the stack pointer! Makes me want to try: 1 2 3 4 DSP@ @ . 4 DSP@ 4+ @ . 3 DSP@ 8 + @ . 2 Of course that works. You can do anything in Forth. Which reminds me: long ago, I remember hearing about this legendary programming language called Forth where you could even redefine _numbers_ to have different meanings. I can't believe it's taken me this long to try it: : 4 12 ; 4 1 + . 13 LOL. Of course. But, wow. That's really something to see, isn't it? Then a word that takes advantage of the ability to loop: SPACES. 0 SPACES 20 SPACES 20 SPACES And then HEX and DECIMAL, which simply set BASE: 15 HEX . F FF00 DECIMAL . 65280 Next up is number printing. If you've been reading this far, you know that I added '.' DOT (prints number from the top of the stack) in assembly as a debugging tool. The one defined in jonesforth.f will, of course "overwrite" mine. Before '.' itself, Jones defines these: U.R ( u width -- ) U. ( u -- ) .R ( n width -- ) Where U stands for "unsigned" number and R indicates that the word will print the number padded to a width. Let's try it out. Here's normal unsigned number printing: 50 U. CR 5000 U. CR 50 5000 Now with a width of 10 characters: 50 10 U.R CR 500 10 U.R CR 5000000 10 U.R CR 50 500 5000000 Compare signed and unsigned printing: -50 U. 4294967246 -50 10 .R -50 And now one that I'm very excited about: '.S' which prints everything on the stack without removing anything on the stack. 1 2 3 4 5 .S 5 4 3 2 1 1 3215633123 0 3215633132 3215633148 3215633162 3215634118 3215634127 3215634178 3215634191 3215634203 3215634213 3215634256 3215634264 3215634275 3215634288 ...6649441 791559519 1836278126 771778154 1935765039 6711917 0 Segmentation fault Oh no! Let's take a look at the definition: : .S ( -- ) DSP@ ( get current stack pointer ) BEGIN DUP S0 @ < WHILE DUP @ U. ( print the stack element ) SPACE 4+ ( move up ) REPEAT DROP ; I'd say my S0 variable isn't set correctly. A couple nights later: Yes, that's exactly what was wrong. But it took me a while to figure out why. Long story, but in my DEFVAR macro, if I try to set the labels by "name" macro parameter like JonesForth does, NASM won't like it: var_"S0": (Since a quote isn't a legal label character, it just sees the first part, 'var_' and complains about redefining 'var_' over and over.) And maybe I figured that out before, because I was setting them to the unquoted "label" macro parameter instead: var_SZ Which is great, except where we initially store the stack pointer in S0, it was by name (as in JonesForth). The fix was easy. Just change: mov [var_S0], esp ; save stack pointer to: mov [var_SZ], esp ; save stack pointer In case you're wondering why it didn't just crash before, since the macro wasn't defining a 'var_S0' label, I had apparently made one. Maybe I meant it as a temporary measure. I don't know, but it certainly was confusing to piece together later. With the fix in, let's test DSP@ and S0. They should start off the same: HEX DSP@ U. BFFFF990 S0 @ U. BFFFF990 Then let's put something on the stack: 1 DSP@ U. BFFFF98C And another thing: 2 DSP@ U. BFFFF988 Then pop the stack: . 2 DSP@ U. BFFFF98C Again, and we should be back where we started: . 1 DSP@ U. BFFFF990 Hooray! Now .S should work, right? 1 2 3 .S 3 2 1 .S 3 2 1 Excellent! It non-destructively prints the stack! (Now that I've made another fix to my port, I wonder if _now_ I can run jonesforth.f without errors? Darn it, nope. Same parse error on the the same spot as before. Oh well, I'll keep going until I hit that spot...) Next word is UWIDTH. It prints the width of a printed number: 5 UWIDTH . 1 42 UWIDTH . 2 777 UWIDTH . 3 That works, but it's no surprise since U.R and .R depend on it. Oh, and I thought this was fun. To add a space to U., Jones redefines it by calling the previous definition and adding a space: ( The real U., note the trailing space. ) : U. U. SPACE ; I'll close out this log with another tiny one: ( ? fetches the integer at an address and prints it. ) : ? ( addr -- ) @ . ; Which will be easy to test: 555 DSP@ ? 555 Looks like there's a real mix of stuff coming up next. See you in the next log!