HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

NotBF - A Brainfuck-ish like "language"

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
notbflikelanguageishbrainfuck

Problem

I've made an interpreted language that's like Brainfuck except it has keywords instead of characters. Here's an explanation of the commands, and how to run it.

  • add_ostream - Add the ASCII value of the current cell to the output stream.



  • chg_size & [position]; - Change the current position on the stack.



  • reset_stack; - Reset the stack and all its cells to the defaults.



  • chg_size & [size]; - Change the size of the stack. This resets all cell values.



  • reset_pos; - Reset the current position on the stack.



  • chg_cell & [value]; - Change the value of the current cell.



  • out_stream; - Output the output stream.



  • reset_ostream; - Reset the output stream.



To run the program simply type this into your command prompt:


python NotBF.py /path/to/notbffile.txt

```
"""
NotBF v0.1.0

---------------------------------------

NotBF is an interpreted Brainfuck-like
language. NotBF is has many similarites
to regular Brainfuck, except without
all the confusing characters.

---------------------------------------
"""
from sys import exit, argv

class NotBFError(object):
"""
This is the base NotBF error class from which
all NotBF errors are derived from.
"""
def __init__(self, message, name, code):
self.message = message
self.name = name
self.code = code

def raise_error(self):
"""
Raise an error if something goes wrong.
"""
print "{0}::{1} -> {2}".format(self.code, self.name, self.message)
exit(0)

class Environment(object):
"""
This class provides the data and functions
for managing a NotBF environment during runtime.
"""
def __init__(self, stack_size, output_stream,):
self.stack_size = stack_size
self.output_stream = output_stream
self.stack = [0 for _ in range(self.stack_size)]
self.stack_position = 0
self.max_stack_pos = len(self.stack)-1
self.min_stack_pos = 0
self.max_cell_value = 255

Solution

As the classic "Stop Writing Classes" puts it:


the signature of "this shouldn't be a class" is that it has two methods, one of which is __init__

Virtually all of your classes fall foul of this; just because you can use OOP, doesn't mean you always should. Looking at the use of the classes in the code, this was a big red flag:

code_input = GetCodeInput(argv[1]).return_file()
tokenized_code = Tokenizer(code_input).tokenize()
interpreter = Interpreter(tokenized_code).execute_input()


You're creating the instance and immediately calling a method on it; you don't actually need the instance (or keep it around), you've just split the logic (pretty much arbitrarily) over two methods. The last line assigns interpreter = None, which doesn't even make any sense! Much neater would be simple functions:

code_input = get_code_input(argv[1])
tokenized_code = tokenize(code_input)
interpret(tokenized_code)


Only the Environment is using state in a meaningful way. All of your NotBFCommand subclasses rely on the existence of some global runtime_env; every single one has a single method that just aliases a method on the Environment, so why not just use those methods?

A neater implementation would spot that basically all of your code is about performing operations on the stack. You can therefore have a single class, which encapsulates the Environment (the state; stack and output stream), the NotBFCommands (methods) and NotBFErrors (a method plus some data). For example:

class Interpreter(object):
    """Interpreter for a BrainFuck-ish language."""

    # Cell sizes for the stack
    CELL_MAX = 255
    CELL_MIN = 0

    # Parsing rules
    LINE_SPLIT = ";"
    ARG_SPLIT = "&"

    # Errors
    ERRORS = {
        "integer_error": ("Invalid integer.", "e01"),
        "no_cell_error": ("Cell doesn't exist.", "e02"),
        "bad_value_error": ("Invalid ASCII code.", "e03"),
        "command_error": ("Invalid command.", "e04"),
    }

    def __init__(self, stack_size=256):
        self.stack_size = stack_size
        self.reset_output()
        self.reset_position()
        self.reset_stack()

    def add_output_stream(self):
        """Add the current stack cell to the output."""
        self.output_stream += chr(self.stack[self.pos])

    def change_cell(self, new_val):
        """Change the value of the current cell."""
        new_val = self.convert_int(new_val)
        if self.CELL_MIN  {msg}".format(code=code, name=name, msg=msg)
        exit(0)

    @staticmethod
    def remove_whitespace(string):
        """Remove unwanted whitespace from the string."""
        for whitespace in '\t\n ':
            string = string.replace(whitespace, '')
        return string

    def reset_output(self):
        """Reset the output stream."""
        self.output_stream = ""

    def reset_position(self):
        """Reset the stack pointer position to zero."""
        self.pos = 0

    def reset_stack(self):
        """Reset the stack to empty."""
        self.stack = [self.CELL_MIN for _ in range(self.stack_size)]

    def resize_stack(self, new_size):
        """Resize the stack."""
        self.stack_size = self.convert_int(new_size)
        self.reset_stack()

    def run_commands(self, commands):
        """Execute a series of commands."""
        for command in commands:
            self.execute_command(*command)

    def run_file(self, filename, verbose=False):
        """Execute commands from a code file."""
        with open(self.filename, "r") as code_file:
            data = self.remove_whitespace(code_file.read())
        self.run_commands(self.tokenize(data, verbose))

    @classmethod
    def tokenize(cls, command_string, verbose=False):
        """Split the command string into tokens."""
        tokens = []
        for line in command_string.split(cls.LINE_SPLIT):
            if line:
                tokens.append(line.split(cls.ARG_SPLIT))
        if verbose:
            print tokens
        return tokens     

    COMMANDS = {
        "add_ostream": add_output_stream,
        "chg_pos": change_position,
        "reset_stack": reset_stack,
        "chg_size": resize_stack,
        "reset_pos": reset_position,
        "chg_cell": change_cell,
        "out_stream": print_output,
        "reset_ostream": reset_output,
    }


Note that I've added docstrings and followed the style guide. Running the program is now:

if __name__ == "__main__":
    interpreter = Interpreter()
    interpreter.run_file(argv[1], True)


I have also made the stack resizing permanent - if that's not appropriate, you may need to refactor slightly.

Code Snippets

code_input = GetCodeInput(argv[1]).return_file()
tokenized_code = Tokenizer(code_input).tokenize()
interpreter = Interpreter(tokenized_code).execute_input()
code_input = get_code_input(argv[1])
tokenized_code = tokenize(code_input)
interpret(tokenized_code)
class Interpreter(object):
    """Interpreter for a BrainFuck-ish language."""

    # Cell sizes for the stack
    CELL_MAX = 255
    CELL_MIN = 0

    # Parsing rules
    LINE_SPLIT = ";"
    ARG_SPLIT = "&"

    # Errors
    ERRORS = {
        "integer_error": ("Invalid integer.", "e01"),
        "no_cell_error": ("Cell doesn't exist.", "e02"),
        "bad_value_error": ("Invalid ASCII code.", "e03"),
        "command_error": ("Invalid command.", "e04"),
    }

    def __init__(self, stack_size=256):
        self.stack_size = stack_size
        self.reset_output()
        self.reset_position()
        self.reset_stack()

    def add_output_stream(self):
        """Add the current stack cell to the output."""
        self.output_stream += chr(self.stack[self.pos])

    def change_cell(self, new_val):
        """Change the value of the current cell."""
        new_val = self.convert_int(new_val)
        if self.CELL_MIN <= new_val <= self.CELL_MAX:
            self.stack[self.pos] = new_val
        else:
            self.raise_error("bad_value_error")

    def change_position(self, new_pos):
        """Change stack pointer position, if new position is valid."""
        new_pos = self.convert_int(new_pos)
        if new_pos not in range(len(self.stack)):
            self.raise_error("no_cell_error")
        self.pos = new_pos

    @classmethod
    def convert_int(cls, val):
        """Convert the value to integer or raise an error."""
        try:
            return int(val)
        except ValueError:
            cls.raise_error("integer_error")

    def execute_command(self, command, *data):
        """Execute the command or raise an error."""
        if command not in self.COMMANDS:
            self.raise_error("command_error")
        self.COMMANDS[command](self, *data)

    def print_output(self):
        """Print the current output stream."""
        print self.output_stream

    @classmethod
    def raise_error(cls, name):
        """Report the error and exit."""
        msg, code = cls.ERRORS[name]
        print "{code}::{name} -> {msg}".format(code=code, name=name, msg=msg)
        exit(0)

    @staticmethod
    def remove_whitespace(string):
        """Remove unwanted whitespace from the string."""
        for whitespace in '\t\n ':
            string = string.replace(whitespace, '')
        return string

    def reset_output(self):
        """Reset the output stream."""
        self.output_stream = ""

    def reset_position(self):
        """Reset the stack pointer position to zero."""
        self.pos = 0

    def reset_stack(self):
        """Reset the stack to empty."""
        self.stack = [self.CELL_MIN for _ in range(self.stack_size)]

    def resize_stack(self, new_size):
        """Resize the stack."""
        self.stack_size = self.convert_int(new_size)
        self.reset_stack()

    def run_commands(self, commands):
        """Execute a series of commands."""
        for command in commands:
            self.execute_command
if __name__ == "__main__":
    interpreter = Interpreter()
    interpreter.run_file(argv[1], True)

Context

StackExchange Code Review Q#86642, answer score: 9

Revisions (0)

No revisions yet.