Home Brainmess: Extract Tape Class
Post
Cancel

Brainmess: Extract Tape Class

Last time I worked on extracting out the methods related to fetching instructions and jump instructions into a Program class. I’m going to use a similar pattern today to extract out methods related to the tape. Currently the tape is implemented as an array of integers and a tape counter. I want to replace all of that with a Tape class.

Extract out tape methods

First I will extract out all the tape related methods and a property. (See commit 746f4e3 for extract method details.)

int MoveForward()
{
    return tc++;
}

int MoveBackward()
{
    return tc--;
}

int Increment()
{
return tape[tc]++;
}

int Decrement()
{
    return tape[tc]--;
}

int Current
{
    get
    {
        return tape[tc];
    }
    set
    {
        tape[tc] = value;
    }
}

This leaves our Run method looking like this:

public void Run()
{
    while(!_program.EndOfProgram)
    {
        char instruction = _program.Fetch();
        switch(instruction)
        {
            case '>':
                MoveForward();
                break;
            case '<':
                MoveBackward();
                break;
            case '+':
                Increment();
                break;
            case '-':
                Decrement();
                break;
            case '.':
                Console.Write((char)Current);
                break;
            case ',':
                Current = Console.Read();
                break;
            case '[':
                if (Current == 0)
                {
                    _program.JumpForward();
                }
                break;
            case ']':
                if (Current != 0)
                {
                    _program.JumpBackward();
                }
                break;
        }
    }
}

I rerun all my tests and see that they still all pass.

Extract Tape Class

These methods along with the tape and tc variables can be moved into a new Tape class. (See commit c75cc6 for the details of this refactoring.)

Here is the Tape class:

public class Tape
{
    private readonly int[] tape = new int[5000];
    private int tc = 2500;

    public int MoveForward()
    {
        return tc++;
    }

    public int MoveBackward()
    {
        return tc--;
    }

    public int Increment()
    {
        return tape[tc]++;
    }

    public int Decrement()
    {
        return tape[tc]--;
    }

    public int Current
    {
        get
        {
            return tape[tc];
        }
        set
        {
            tape[tc] = value;
        }
    }
}

Leaving the Run method looking like this.

public void Run()
{
    while(!_program.EndOfProgram)
    {
        char instruction = _program.Fetch();
        switch(instruction)
        {
            case '>':
                _tape.MoveForward();
                break;
            case '<':
                _tape.MoveBackward();
                break;
            case '+':
                _tape.Increment();
                break;
            case '-':
                _tape.Decrement();
                break;
            case '.':
                Console.Write((char)_tape.Current);
                break;
            case ',':
                _tape.Current = Console.Read();
                break;
            case '[':
                if (_tape.Current == 0)
                {
                    _program.JumpForward();
                }
                break;
            case ']':
                if (_tape.Current != 0)
                {
                    _program.JumpBackward();
                }
                break;
        }
    }
}

I think this change makes Run read a little better than the version at the top of this post. This is because now each access of the tape is explicit because of the use of the _tape variable.

Benefits

Like last time we get several benefits out of this refactoring: testability, encapsulation, readability.

All the access to the tape is now in one class and has no dependencies on anything else. We can add some methods (like “LoadState” and “GetState”) that allow us to create a tape in any state we want for a test setup. Then we can execute one of its methods and use GetState to check our results.

We may also now choose to change our tape implementation. I actually prefer the use of a linked list for the tape because it makes it easy to continue to add cells on either end of the tape. We can go ahead and make this sort of change and not have to worry about how it affects the rest of the program.

Finally, I like how the new Run method reads. Again, the variable and method names make the code “self documenting”. We know what each branch of the switch statement is doing without having to worry about the details. This makes it easier for us to read and it certainly makes it easier for a code reviewer or a future maintainer of the code.

Notice how easy it is to tell which instructions affect the program and which affect the tape. Also, notice how the different “components” are easier to identify.

This post is licensed under CC BY 4.0 by the author.

Brainmess: Extract Class Program

My First Greasemonkey Script