The Repo on GitHub

I fully expect to fail this morning: I don’t see how to do what I need to do. I’ll try to fail in a way that sets me up for a win sometime soon.

Yesterday’s debacle led me to some reading in Loeliger and in the py4fun Forth article. That reading, and some reasoning that I could have done days ago, led me to understand that my implementation of CREATE-DOES> is, how can I put this delicately, just plain unconditionally flat wrong.

It is also not what we need. I borrowed the idea from py4fun, thinking that I understood it, although I did not, and still do not fully get it. I was trying to come up with some words that would let us define things like VARIABLE and CONSTANT and other new defining words, but the CREATE-DOES> idea is too specific to the py4fun version to fit into our design.

So I’ve removed the tests and code for it.

Musing

I suspect that we should remove the heap. We can certainly store constants in the words that define them, and there’s no reason why we couldn’t store multiple words if more were allotted. Of course we’d have to skip over them when we execute the word. We do not have a marker in the word to signify the end of the code, though we do have the ability to skip / jump the program counter.

One thing that is not helping with my confusion is that Loeliger and py4fun have made different decisions about the heap. Loeliger does not use one: py4fun does. So Loeliger and py4fun have different definitions for words like ‘,’, which in Loeliger stores the contents of the stack in the next cell in the word being defined, and py4fun stores the contents of the stack into a newly-allocated cell in the heap.

We’ll belay that concern for now, and try to focus on what we need and how to do it.

Constants and Variables

Presently, CONSTANT and VARIABLE are defined in code, like this:

    def _define_constant_variable(self):
        def _constant(forth):
            name = forth.next_token()
            value = forth.stack.pop()
            literal = forth.find_word('*#')
            word = SecondaryWord(name, [literal, value])
            forth.lexicon.append(word)

        def _variable(forth):
            name = forth.next_token()
            value = len(forth.heap)
            literal = forth.find_word('*#')
            word = SecondaryWord(name, [literal, value])
            forth.lexicon.append(word)

        self.pw('VARIABLE', _variable)
        self.pw('CONSTANT', _constant)

CONSTANT simply defines a word that returns the literal value found on stack top. VARIABLE, in contrast, defines a word that returns the literal value of the next word on the heap. We expect an allot statement after VARIABLE, so to allocate an array of 12 months of data we might say this:

VARIABLE MONTHLY_RENT 12 ALLOT

Clarify Your Goals, Ron!

OK, set aside how CREATE-DOES> does or doesn’t serve us. What are we trying to do?

We are trying to come up with primitive words that will allow us to define words like CONSTANT and VARIABLE using colon definitions. At a larger scale, we are trying to allow our colon definitions, with the aid of some other primitive words, to define essentially any word we might want without recourse to coding in Python.

Let’s imagine a somewhat simple thing we might want to do. We want to be able to define CONST, which is like CONSTANT, with the following characteristics.

If we say:

2025 CONST YEAR
  1. The constant value, 2025 is allocated into a new cell on the heap, say at cell 5;
  2. The word YEAR henceforth fetches the value of the constant from cell 5 on the heap.

So YEAR might be compiled this way:

*# 5 @

Where *# pushes the next value, 5, onto the stack, and @ pops the stack and fetches the corresponding word from the heap, providing the 2025.

So what does our colon definition for CONST have to do?

  1. Create a word whose name is the next token in the input (YEAR);
  2. Pop the stack and push it into the heap,
    allocating a word for it, say 5
    and then into YEAR, compile *#, and 5;
  3. Into YEAR, compile @

Suppose there is a word STACK_TO_HEAP! that immediately pushes the stack top to the heap, pushing the cell number allocated back onto the stack. Then this much:

: CONST CREATE ( name ) STACK_TO_HEAP! ...

Would have a word YEAR being compiled, and the index of the heap cell on the stack. So if there was a word STACK_TO_LITERAL!, that compiled *# 5 into the word we’re building … could we say something like this?

: CONST CREATE ( name ) STACK_TO_HEAP! COMPILE> STACK_TO_LITERAL! @ ;

I mean by the COMPILE> word that what precedes it was to be executed immediately, and what follows is to be compiled into the word (YEAR). Except that STACK_TO_LITERAL! has to be executed immediately.

Or maybe it’s like this:

: CONST CREATE ( name ) STACK_TO_HEAP! COMPILE> *# S>W! @ ;

Where S>W! just pops the stack and stores the actual value in the current word.

Fail Again?

I want to give up. I can’t keep all these balls in the air at the same time. I think that what we need is better control over whether the compile is executing or accumulating words. Possibly this particular case can be managed with a judicious use of immediate words versus regular words, but I don’t think that will hold water for long. Maybe the thing is just to make one test work and learn something.

OK a test:

    def test_const(self):
        f = Forth()
        s = (': CONST CREATE ( name ) '
             ' STACK_TO_HEAP! '
             ' *# '
             ' STACK_TO_WORD! '
             ' @ '
             ' ; ')
        f.compile(s)
        f.compile('2025 CONST YEAR')
        assert f.stack.is_empty()
        f.compile('YEAR')
        assert f.stack.stack == [2025]

I’m positing those two new words as immediates.

Fail Again!

I also need to define CREATE and I just don’t see it.

It’s no good. It keeps slipping between my fingers. I have to step away from the machine and think. Or something.

Summary

Bah!