1     Well, now that I can actually run jonesforth.f with this
     
2     interpreter port, it "only" remains to test the rest of
     
3     the words defined in Forth. Then I've got some cleaning
     
4     up to do in the assembly.
     
5 
     6     It looks like I'm roughly halfway through testing what
     
7     jonesforth.f provides. So there's quite a bit left.
     
8 
     9     Next up is CASE OF...ENDOF... ENDCASE which lets you
    
10     do different things based on comparison with a value on
    
11     the stack:
    
12 
    13 JONESFORTH VERSION 1
    
14 20643 CELLS REMAINING
    
15 OK
    
16 
    17 : foo CASE
    
18     1 OF ." One" ENDOF
    
19     2 OF ." Two" ENDOF
    
20     3 OF ." Three" ENDOF
    
21 ENDCASE ;
    
22 
    23 1 foo
    
24 One
    
25 2 foo
    
26 Two
    
27 3 foo
    
28 Three
    
29 
    30     That is, dare I say, quite readable compared to a lot of
    
31     the Forth we've seen so far.
    
32 
    33     Next is CFA>, which is "the opposite of >CFA." Let's see
    
34     about that. I'll use >CFA to get the code word pointer
    
35     address from the LATEST word address, then get back to
    
36     the original word address with CFA> and see if it's right:
    
37 
    38 HEX
    
39 LATEST @ DUP . ID.
    
40 957ED74 foo
    
41 LATEST @ >CFA DUP .
    
42 957ED7C
    
43 CFA> DUP . ID.
    
44 957ED74 foo
    
45 
    46     Of course, we can do this all in one go as well:
    
47 
    48 LATEST @ >CFA CFA> ID.
    
49 foo
    
50 
    51     What's wild is that >CFA can just do a little address
    
52     math and skip forward to the codeword. But CFA> has to
    
53     search the entire dictionary for the correct word.
    
54  
    55     I feel like you should be able to scan backward to the
    
56     word start. But I can see how it would be hard to be
    
57     entirely sure it's accurate (you could use the length
    
58     field as a kind of "checksum" for the name field, but
    
59     that wouldn't be a *guarantee*).
    
60 
    61     Oooooh, the next one is bound to be a new favorite: SEE
    
62     decompiles a word!
    
63 
    64 SEE foo
    
65 : foo 1 OVER = 0BRANCH ( 20 ) DROP S" One" TELL BRANCH ( 74 ) 2 OVER = 0BRANCH
    
66 ( 20 ) DROP S" Two" TELL BRANCH ( 40 ) 3 OVER = 0BRANCH ( 24 ) DROP S" Three"
    
67 TELL BRANCH ( 8 ) DROP ;
    
68 
    69     That's awesome! I love how it prints the strings and
    
70     everthing.
    
71 
    72     The next thing are anonymous "no name" words. When you
    
73     define them with :NONAME, they leave the address of
    
74     their codeword on the stack. In Forth terminology, the
    
75     address of a codeword is an "execution token". In the
    
76     21st Century, we'd probably call them pointers.
    
77 
    78     Anyway, you can store an execution token like any other
    
79     value. And EXECUTE calls one:
    
80 
    81 :NONAME ." I have no name: " TELL CR ;
    
82 VALUE noname
    
83 noname .
    
84 160980384
    
85 ." Hello" noname EXECUTE
    
86 HelloI have no name: .
    
87 S" Hello" noname EXECUTE
    
88 I have no name: Hello
    
89 
    90     Next we have THROW and CATCH. I was not expecting
    
91     exception handling!
    
92 
    93     The THROW statement is easy to use - just give it a
    
94     number to throw as the exception.
    
95 
    96     But CATCH needs some very specific stuff:
    
97 
    98         : <word2> ['] <word1> CATCH IF ... THEN ;
    
99 
   100     The ['] primitive gets the execution token of a word
   
101     (:NONAME uses it).
   
102 
   103 : foo ." Foo! " 13 = IF ." Bah!" 5 THROW THEN ." Done." ;
   
104 12 foo
   
105 Foo! Done.
   
106 13 foo
   
107 Foo! Bah!UNCAUGHT THROW 5
   
108 
   109 : test-foo ['] foo CATCH ?DUP IF ."  Foo threw " . DROP THEN ."  Test done." ;
   
110 12 test-foo
   
111 Foo! Done. Test done.
   
112 13 test-foo
   
113 Foo! Bah! Foo threw 5 Test done.
   
114 
   115     i spent another night reading through the implementation
   
116     of THROW. It's funny how *using* CATCH and THROW is
   
117     inversely proportional to the complexity of those two
   
118     words.
   
119 
   120     This is the first time something really interesting is
   
121     done with the return stack.
   
122 
   123     This is also an excellent example of a language feature
   
124     that seems like more trouble than it's worth in a small
   
125     demonstration. But when you actually need a feature like
   
126     this in a decent-sized codebase, it will seem trivial
   
127     compared to the alternatives.
   
128 
   129     Having said that, reading an implementation like THROW
   
130     is just *nuts*. Cool how it works, though.
   
131 
   132     Next is another one that takes advantage of the return
   
133     stack: PRINT-STACK-TRACE.
   
134 
   135 : foo PRINT-STACK-TRACE ;
   
136 foo
   
137 foo+0
   
138 : bar foo ;
   
139 : baz bar ;
   
140 baz
   
141 foo+0 bar+0 baz+0
   
142 
   143     Beautiful.
   
144 
   145     Then JONESFORTH has support for reading the commandline
   
146     arguments, which is very cool.
   
147 
   148     It's easy enough to use ARGC and ARGV, but I'm getting
   
149     to the point where the usability of bare Linux input for
   
150     the Forth "REPL" is pretty painful. I must have re-typed
   
151     my BEGIN WHILE REPEAT loop a dozen times.
   
152 
   153     So I'm doing two things:
   
154 
   155     1. Using CAPSLOCK while typing Forth commands.
   
156     2. Installed "rlwrap" (Readline wrapper).
   
157 
   158     rlwrap is super awesome and easy to use. I'll be darned
   
159     if I can figure out why `apk search rlwrap` doesn't work
   
160     in Alpine Linux. It shows up as a community package when
   
161     I do a web search.
   
162 
   163     But whatever, that's what source is for:
   
164 
   165         $ doas apk add readline-dev
   
166         $ lynx github.com...
   
167         $ tar -xf rlwrap...tar.gz
   
168         $ cd rlwrap...
   
169         $ ./configure
   
170         $ make
   
171         $ doas make install
   
172 
   173     Now I've added this alias:
   
174 
   175         alias f='rlwrap /home/dave/nasmjf/nasmjf'
   
176 
   177     And what a relief! I can use arrow keys for editing and
   
178     history, etc.
   
179 
   180 eeepc:~/nasmjf$ f
   
181 JONESFORTH VERSION 1
   
182 20643 CELLS REMAINING
   
183 OK
   
184 ARGC .
   
185 1
   
186 0 ARGV TELL
   
187 /home/dave/nasmjf/nasmjf
   
188 
   189     Again, that's my alias 'f' providing the full path to
   
190     the nasmjf executable we're seeing there as the 0th
   
191     argument.
   
192 
   193     Now for the simple loop it took at least a dozen tries
   
194     to get right (often due to simple typos and some of them
   
195     causing nasmjf to segfault!) and now I am *so* grateful
   
196     that rlwrap gives me command history.
   
197 
   198 eeepc:~/nasmjf$ f foo bar
   
199 JONESFORTH VERSION 1
   
200 20643 CELLS REMAINING
   
201 OK
   
202 
   203 ARGC .
   
204 3
   
205 
   206 : PRINT-ARGS 0 BEGIN DUP ARGC < WHILE DUP DUP . ARGV TELL CR 1+ REPEAT ;
   
207 PRINT-ARGS
   
208 0 /home/dave/nasmjf/nasmjf
   
209 1 foo
   
210 2 bar
   
211 
   212     Yes!
   
213 
   214     And by the way, I have no doubt I'll get better at it,
   
215     but writing that loop felt way harder than it should
   
216     have been. The way you have to keep DUPing because
   
217     even comparisons eat the values on the stack...
   
218 
   219     I get it. But I'm not sure I like it.
   
220 
   221     Also, having capslock on makes my GNU screen shortcuts
   
222     and vim both go crazy when i forget to toggle it off
   
223     when i'm done typing Forth. :-(
   
224 
   225     But rlwrap is the best. I *highly* recommend it for use
   
226     with this or any other application that doesn't have
   
227     command history, etc.
   
228 
   229     Related to arguments (because Linux leaves it all in our
   
230     application's memory in the stack memory space,
   
231     evidently) are the environment variables.
   
232 
   233     JONESFORTH defines ENVIRON to get the address. Also the
   
234     utility STRLEN to get the length of null-terminated
   
235     strings, a.k.a. "C strings".
   
236 
   237 ENVIRON @ DUP STRLEN TELL
   
238 SHELL=/bin/bash
   
239 
   240     Unlike the result from ARGV, ENVIRON just gives us the
   
241     address of the first item. So I had to DUP and STRLEN to
   
242     get the string ready for printing with TELL.
   
243 
   244     The next item is 4 bytes after that and so on.
   
245 
   246     I guess you could write a word that gets the number of
   
247     environment variables available and another that gets
   
248     one from a particular position.
   
249 
   250     I started writing those words, but it was late and I was
   
251     tired. Eventually, I just wanted to see more, so I
   
252     printed out a big chunk of memory where args and
   
253     environment are stored:
   
254 
   255 0 ARGV DROP 128 DUMP
   
256 BFECBA8E 2E 2F 6E 61 73 6D 6A 66  0 66 6F 6F  0 62 61 72 ./nasmjf.foo.bar
   
257 BFECBA9E  0 53 48 45 4C 4C 3D 2F 62 69 6E 2F 62 61 73 68 .SHELL=/bin/bash
   
258 BFECBAAE  0 43 48 41 52 53 45 54 3D 55 54 46 2D 38  0 54 .CHARSET=UTF-8.T
   
259 BFECBABE 45 52 4D 43 41 50 3D 53 43 7C 73 63 72 65 65 6E ERMCAP=SC|screen
   
260 BFECBACE 7C 56 54 20 31 30 30 2F 41 4E 53 49 20 58 33 2E |VT 100/ANSI X3.
   
261 BFECBADE 36 34 20 76 69 72 74 75 61 6C 20 74 65 72 6D 69 64 virtual termi
   
262 BFECBAEE 6E 61 6C 3A 44 4F 3D 5C 45 5B 25 64 42 3A 4C 45 nal:DO=\E[%dB:LE
   
263 BFECBAFE 3D 5C 45 5B 25 64 44 3A 52 49 3D 5C 45 5B 25 64 =\E[%dD:RI=\E[%d
   
264 
   265     I'll end this particular log file with an appropriate
   
266     sign-off with another new word definition:
   
267 
   268 JONESFORTH VERSION 1
   
269 20643 CELLS REMAINING
   
270 OK
   
271 BYE