patternpythonMinor
NotBF - A Brainfuck-ish like "language"
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.
To run the program simply type this into your command prompt:
```
"""
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
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
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:
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
Only the
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
Note that I've added docstrings and followed the style guide. Running the program is now:
I have also made the stack resizing permanent - if that's not appropriate, you may need to refactor slightly.
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_commandif __name__ == "__main__":
interpreter = Interpreter()
interpreter.run_file(argv[1], True)Context
StackExchange Code Review Q#86642, answer score: 9
Revisions (0)
No revisions yet.