The Robot World Repo on GitHub
The Forth Repo on GitHub

#ElonaldDelendaEst! How do we add simplicity? By removing duplication and redundancy, and occasionally by generalizing specifics. Sometimes it takes a few tries.

Last time we added simplicity by switching to BR and 0BR. This time, we’ll smush some things around to see if we can’t add a bit more. We have these bits that we just did:

    def _define_if_else_then(self):
        def _if(forth):
            forth.compile_word('0BR')
            info = CompileInfo('IF', forth.word_list, len(forth.word_list))
            forth.compile_stack.push(info)
            forth.compile_word('BR_TARGET')

        def _else(forth):
            forth.compile_word('BR')
            new_info = CompileInfo('IF', forth.word_list, len(forth.word_list))
            forth.compile_stack.push(new_info)
            forth.compile_word('BR_TARGET')
            forth.compile_stack.swap_pop().patch('IF')

There is a common pattern here, where we compile a word then create and push a CompileInfo pointing at the next location, then compile a BR_TARGET. Rename for even more duplication:

    def _define_if_else_then(self):
        def _if(forth):
            forth.compile_word('0BR')
            info = CompileInfo('IF', forth.word_list, len(forth.word_list))
            forth.compile_stack.push(info)
            forth.compile_word('BR_TARGET')

        def _else(forth):
            forth.compile_word('BR')
            info = CompileInfo('IF', forth.word_list, len(forth.word_list))
            forth.compile_stack.push(info)
            forth.compile_word('BR_TARGET')
            forth.compile_stack.swap_pop().patch('IF')

There are two places in the CASE code that are similar:

        def _of(f):
            f.compile_word('OVER')
            f.compile_word('=')
            f.compile_word('0BR')
            f.compile_stack.push(CompileInfo('OF', f.word_list, len(f.word_list)))
            f.compile_word('BR_TARGET')
            f.compile_word('DROP')

        def _endof(f):
            f.compile_word('BR')
            f.compile_stack.peek_under().add_current_location('CASE')
            f.compile_word('BR_TARGET')
            f.compile_stack.pop().patch('OF')

The _of function clearly can use the same thing as _if and _else. _endof is a bit different and might or might not find a way to be folded in.

What if there was a method, on Forth, called compile_and_save_target(operation, name), or something like that.

Let’s extract a function up in the IF logic. No, let’s just replicate a method on Forth, there’s really nothing to extract.

class Forth:
    def compile_branch(self, branch_name, info_name):
            self.compile_word(branch_name)
            info = CompileInfo(info_name, self.word_list, len(self.word_list))
            self.compile_stack.push(info)
            self.compile_word('BR_TARGET')

I think we can just go ahead and call that:

    def _define_if_else_then(self):
        def _if(forth):
            forth.compile_branch('0BR', 'IF')

Green. Commit: forth compile_branch method. And here:

        def _else(forth):
            forth.compile_branch('BR', 'IF')
            forth.compile_stack.swap_pop().patch('IF')

Commit again. Let’s change this:

        def _of(f):
            f.compile_word('OVER')
            f.compile_word('=')
            f.compile_word('0BR')
            f.compile_stack.push(CompileInfo('OF', f.word_list, len(f.word_list)))
            f.compile_word('BR_TARGET')
            f.compile_word('DROP')

To this:

        def _of(f):
            f.compile_word('OVER')
            f.compile_word('=')
            f.compile_branch('0BR', 'OF')
            f.compile_word('DROP')

Commit again. Is there something good to do about this other case?

        def _of(f):
            f.compile_word('OVER')
            f.compile_word('=')
            f.compile_branch('0BR', 'OF')
            f.compile_word('DROP')

        def _endof(f):
            f.compile_word('BR')
            f.compile_stack.peek_under().add_current_location('CASE')
            f.compile_word('BR_TARGET')
            f.compile_stack.pop().patch('OF')

Let’s review the compile_branch again:

    def compile_branch(self, branch_name, info_name):
            self.compile_word(branch_name)
            info = CompileInfo(info_name, self.word_list, len(self.word_list))
            self.compile_stack.push(info)
            self.compile_word('BR_TARGET')

If we didn’t prime the CompileInfo and instead used add_current_location, like this:

    def compile_branch(self, branch_name, info_name):
        self.compile_word(branch_name)
        info = CompileInfo(info_name, self.word_list)
        info.add_current_location(info_name)
        self.compile_stack.push(info)
        self.compile_word('BR_TARGET')

The code would be more similar, between that and this:

        def _endof(f):
            f.compile_word('BR')
            f.compile_stack.peek_under().add_current_location('CASE')
            f.compile_word('BR_TARGET')
            f.compile_stack.pop().patch('OF')

What if we swap _endof’s’ up and then back down? Or this:

        def _endof(f):
            info = f.compile_stack.swap_pop()
            f.compile_word('BR')
            info.add_current_location('CASE')
            f.compile_word('BR_TARGET')
            f.compile_stack.pop().patch('OF')
            f.compile_stack.push(info)

Not quite the thing. But if we had a method into which we passed a CompileInfo with locations list possibly containing items … maybe update_branch

    def compile_branch(self, branch_name, info_name):
        self.compile_word(branch_name)
        info = CompileInfo(info_name, self.word_list)
        self.compile_stack.push(info)
        self.update_branch()
        self.compile_word('BR_TARGET')

    def update_branch(self):
        top = self.compile_stack.top()
        top.add_current_location(top.name)

Then can’t we use that in _endof?

        def _endof(f):
            f.compile_stack.swap()
            f.compile_word('BR')
            f.update_branch()
            f.compile_word('BR_TARGET')
            f.compile_stack.swap()
            f.compile_stack.pop().patch('OF')

Green. Commit: refactoring.

Still not quite what we want, but closer. Compare the Forth offerings with _end_of:

        def _endof(f):
            f.compile_stack.swap()
            f.compile_word('BR')
            f.update_branch()
            f.compile_word('BR_TARGET')
            f.compile_stack.swap()
            f.compile_stack.pop().patch('OF')

    def compile_branch(self, branch_name, info_name):
        self.compile_word(branch_name)
        info = CompileInfo(info_name, self.word_list)
        self.compile_stack.push(info)
        self.update_branch()
        self.compile_word('BR_TARGET')

    def update_branch(self):
        top = self.compile_stack.top()
        top.add_current_location(top.name)

Wait, what if we were to use compile_branch, and then add in the items from the old CompileInfo which we have saved, like this:

I have fallen into a sort of trial and error debugging. I should revert. Instead I find a tiny error, ‘OF’ where I needed ‘CASE’ and now we have:

        def _endof(f):
            existing_info = f.compile_stack.swap_pop()
            f.compile_branch('BR', 'CASE')
            f.add_locations_from(existing_info)
            f.compile_stack.swap_pop().patch('OF')

We are green. Commit: refactoring _endof,

I am tired, and this works. Let’s do a short summary and we’ll review the code in more detail later.

Summary

I believe that we have all the conditionals using compile_branch now, minimizing the code duplication down from several lines to four, to three, and finally to one. We have an additional method on Forth, forwarding to CompileInfo, for copying a list of locations from one info to another, which we use by scarfing the existing CASE compile info, creating a new one, and then loading the history into it. An alternative way of doing it might exist, somehow avoiding the extra creation, but I didn’t see it and still don’t. This way is rather nice, in that there are no conditionals, everyone just steps through doing what they want.

We have consolidated the code substantially, “adding” simplicity, through adding simpler methods and using them, removing the less simple code in favor of the more simple.

A good thing.

#ElonaldDelendaEst! See you next time!