Dave's nasmjf Dev Log 23

Created: 2022-07-22

nasmjf home

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.

← Previous 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Next →
    Well, now that I can actually run jonesforth.f with this
    interpreter port, it "only" remains to test the rest of
    the words defined in Forth. Then I've got some cleaning
    up to do in the assembly.

    It looks like I'm roughly halfway through testing what
    jonesforth.f provides. So there's quite a bit left.

    Next up is CASE OF...ENDOF... ENDCASE which lets you
    do different things based on comparison with a value on
    the stack:

JONESFORTH VERSION 1
20643 CELLS REMAINING
OK

: foo CASE
    1 OF ." One" ENDOF
    2 OF ." Two" ENDOF
    3 OF ." Three" ENDOF
ENDCASE ;

1 foo
One
2 foo
Two
3 foo
Three

    That is, dare I say, quite readable compared to a lot of
    the Forth we've seen so far.

    Next is CFA>, which is "the opposite of >CFA." Let's see
    about that. I'll use >CFA to get the code word pointer
    address from the LATEST word address, then get back to
    the original word address with CFA> and see if it's right:

HEX
LATEST @ DUP . ID.
957ED74 foo
LATEST @ >CFA DUP .
957ED7C
CFA> DUP . ID.
957ED74 foo

    Of course, we can do this all in one go as well:

LATEST @ >CFA CFA> ID.
foo

    What's wild is that >CFA can just do a little address
    math and skip forward to the codeword. But CFA> has to
    search the entire dictionary for the correct word.

    I feel like you should be able to scan backward to the
    word start. But I can see how it would be hard to be
    entirely sure it's accurate (you could use the length
    field as a kind of "checksum" for the name field, but
    that wouldn't be a *guarantee*).

    Oooooh, the next one is bound to be a new favorite: SEE
    decompiles a word!

SEE foo
: foo 1 OVER = 0BRANCH ( 20 ) DROP S" One" TELL BRANCH ( 74 ) 2 OVER = 0BRANCH
( 20 ) DROP S" Two" TELL BRANCH ( 40 ) 3 OVER = 0BRANCH ( 24 ) DROP S" Three"
TELL BRANCH ( 8 ) DROP ;

    That's awesome! I love how it prints the strings and
    everthing.

    The next thing are anonymous "no name" words. When you
    define them with :NONAME, they leave the address of
    their codeword on the stack. In Forth terminology, the
    address of a codeword is an "execution token". In the
    21st Century, we'd probably call them pointers.

    Anyway, you can store an execution token like any other
    value. And EXECUTE calls one:

:NONAME ." I have no name: " TELL CR ;
VALUE noname
noname .
160980384
." Hello" noname EXECUTE
HelloI have no name: .
S" Hello" noname EXECUTE
I have no name: Hello

    Next we have THROW and CATCH. I was not expecting
    exception handling!

    The THROW statement is easy to use - just give it a
    number to throw as the exception.

    But CATCH needs some very specific stuff:

        : <word2> ['] <word1> CATCH IF ... THEN ;

    The ['] primitive gets the execution token of a word
    (:NONAME uses it).

: foo ." Foo! " 13 = IF ." Bah!" 5 THROW THEN ." Done." ;
12 foo
Foo! Done.
13 foo
Foo! Bah!UNCAUGHT THROW 5

: test-foo ['] foo CATCH ?DUP IF ."  Foo threw " . DROP THEN ."  Test done." ;
12 test-foo
Foo! Done. Test done.
13 test-foo
Foo! Bah! Foo threw 5 Test done.

    i spent another night reading through the implementation
    of THROW. It's funny how *using* CATCH and THROW is
    inversely proportional to the complexity of those two
    words.

    This is the first time something really interesting is
    done with the return stack.

    This is also an excellent example of a language feature
    that seems like more trouble than it's worth in a small
    demonstration. But when you actually need a feature like
    this in a decent-sized codebase, it will seem trivial
    compared to the alternatives.

    Having said that, reading an implementation like THROW
    is just *nuts*. Cool how it works, though.

    Next is another one that takes advantage of the return
    stack: PRINT-STACK-TRACE.

: foo PRINT-STACK-TRACE ;
foo
foo+0
: bar foo ;
: baz bar ;
baz
foo+0 bar+0 baz+0

    Beautiful.

    Then JONESFORTH has support for reading the commandline
    arguments, which is very cool.

    It's easy enough to use ARGC and ARGV, but I'm getting
    to the point where the usability of bare Linux input for
    the Forth "REPL" is pretty painful. I must have re-typed
    my BEGIN WHILE REPEAT loop a dozen times.

    So I'm doing two things:

    1. Using CAPSLOCK while typing Forth commands.
    2. Installed "rlwrap" (Readline wrapper).

    rlwrap is super awesome and easy to use. I'll be darned
    if I can figure out why `apk search rlwrap` doesn't work
    in Alpine Linux. It shows up as a community package when
    I do a web search.

    But whatever, that's what source is for:

        $ doas apk add readline-dev
        $ lynx github.com...
        $ tar -xf rlwrap...tar.gz
        $ cd rlwrap...
        $ ./configure
        $ make
        $ doas make install

    Now I've added this alias:

        alias f='rlwrap /home/dave/nasmjf/nasmjf'

    And what a relief! I can use arrow keys for editing and
    history, etc.

eeepc:~/nasmjf$ f
JONESFORTH VERSION 1
20643 CELLS REMAINING
OK
ARGC .
1
0 ARGV TELL
/home/dave/nasmjf/nasmjf

    Again, that's my alias 'f' providing the full path to
    the nasmjf executable we're seeing there as the 0th
    argument.

    Now for the simple loop it took at least a dozen tries
    to get right (often due to simple typos and some of them
    causing nasmjf to segfault!) and now I am *so* grateful
    that rlwrap gives me command history.

eeepc:~/nasmjf$ f foo bar
JONESFORTH VERSION 1
20643 CELLS REMAINING
OK

ARGC .
3

: PRINT-ARGS 0 BEGIN DUP ARGC < WHILE DUP DUP . ARGV TELL CR 1+ REPEAT ;
PRINT-ARGS
0 /home/dave/nasmjf/nasmjf
1 foo
2 bar

    Yes!

    And by the way, I have no doubt I'll get better at it,
    but writing that loop felt way harder than it should
    have been. The way you have to keep DUPing because
    even comparisons eat the values on the stack...

    I get it. But I'm not sure I like it.

    Also, having capslock on makes my GNU screen shortcuts
    and vim both go crazy when i forget to toggle it off
    when i'm done typing Forth. :-(

    But rlwrap is the best. I *highly* recommend it for use
    with this or any other application that doesn't have
    command history, etc.

    Related to arguments (because Linux leaves it all in our
    application's memory in the stack memory space,
    evidently) are the environment variables.

    JONESFORTH defines ENVIRON to get the address. Also the
    utility STRLEN to get the length of null-terminated
    strings, a.k.a. "C strings".

ENVIRON @ DUP STRLEN TELL
SHELL=/bin/bash

    Unlike the result from ARGV, ENVIRON just gives us the
    address of the first item. So I had to DUP and STRLEN to
    get the string ready for printing with TELL.

    The next item is 4 bytes after that and so on.

    I guess you could write a word that gets the number of
    environment variables available and another that gets
    one from a particular position.

    I started writing those words, but it was late and I was
    tired. Eventually, I just wanted to see more, so I
    printed out a big chunk of memory where args and
    environment are stored:

0 ARGV DROP 128 DUMP
BFECBA8E 2E 2F 6E 61 73 6D 6A 66  0 66 6F 6F  0 62 61 72 ./nasmjf.foo.bar
BFECBA9E  0 53 48 45 4C 4C 3D 2F 62 69 6E 2F 62 61 73 68 .SHELL=/bin/bash
BFECBAAE  0 43 48 41 52 53 45 54 3D 55 54 46 2D 38  0 54 .CHARSET=UTF-8.T
BFECBABE 45 52 4D 43 41 50 3D 53 43 7C 73 63 72 65 65 6E ERMCAP=SC|screen
BFECBACE 7C 56 54 20 31 30 30 2F 41 4E 53 49 20 58 33 2E |VT 100/ANSI X3.
BFECBADE 36 34 20 76 69 72 74 75 61 6C 20 74 65 72 6D 69 64 virtual termi
BFECBAEE 6E 61 6C 3A 44 4F 3D 5C 45 5B 25 64 42 3A 4C 45 nal:DO=\E[%dB:LE
BFECBAFE 3D 5C 45 5B 25 64 44 3A 52 49 3D 5C 45 5B 25 64 =\E[%dD:RI=\E[%d

    I'll end this particular log file with an appropriate
    sign-off with another new word definition:

JONESFORTH VERSION 1
20643 CELLS REMAINING
OK
BYE
← Previous 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Next →