NotBF - Brainfuck-ish как "язык"

Я сделал интерпретируемый язык, похожий на Brainfuck, за исключением того, что у него есть ключевые слова вместо символов. Вот объяснение команд и их запуск.

  • add_ostream - Добавить значение ASCII текущей ячейки в выходной поток.
  • chg_size & [position]; - изменить текущую позицию в стеке.
  • reset_stack; - сбросить стек и все его ячейки до значений по умолчанию.
  • chg_size & [size]; - изменить размер стека. Это сбрасывает все значения ячеек.
  • reset_pos; - Сбросить текущую позицию в стеке.
  • chg_cell & [value]; - изменить значение текущей ячейки.
  • out_stream; - Вывести выходной поток.
  • reset_ostream; - Сбросить выходной поток.

Чтобы запустить программу, просто введите ее в свою командную строку:

  

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
        self.min_cell_value = 0

    def add_output_stream(self, character):
        """
        Add a character to the output stream.
        """
        self.output_stream += character

    def reset_stack(self):
        """
        Reset the stack to it's default length, 
        self.stack_size, and reset all cells.
        """
        self.stack = [0 for _ in range(self.stack_size)]

    def change_stack_size(self, new_size):
        """
        Change the size of the stack. WARNING,
        this operation resets all cell values.
        """     
        self.stack = [0 for _ in range(new_size)]

    def reset_stack_position(self):
        """
        Reset the stack_position to zero.
        """
        self.stack_position = 0

    def change_stack_position(self, new_position):
        """
        Move the stack_position to a new position.
        """
        self.stack_position = new_position

    def change_cell_value(self, new_value):
        """
        Change the value of a cell.
        """
        self.stack[self.stack_position] = new_value

    def output_output_stream(self):
        """
        Output the output stream.
        """
        print self.output_stream

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


class NotBFCommand(object):
    """
    A base command class where tokenized input
    is inputted into and then run. All NotBF
    command classes are derived from this base
    class.
    """
    def __init__(self, tokenized_string):
        self.tokenized_string = tokenized_string

    def debug_input(self):
        print self.tokenized_string


"""
Initalize various variables and other
items to make sure that command classes
work the way they should.
"""
runtime_env = Environment(256, "")
integer_error = NotBFError("Invalid integer.", "int_error", "e01")
no_cell_error = NotBFError("Cell doesn't exist.", "no_cell_error", "e02")
bad_value_error = NotBFError("Invalid ASCII code.", "bad_value_error", "e03")
command_error = NotBFError("Invalid command.", "cmd_error", "e04")


class AddOutputStream(NotBFCommand):
    def execute(self):
        character = chr(runtime_env.stack[runtime_env.stack_position])
        runtime_env.add_output_stream(character)


class ResetStack(NotBFCommand):
    def execute(self):
        runtime_env.reset_stack()


class ChangeStackSize(NotBFCommand):
    def execute(self):
        try:
            new_size = int(self.tokenized_string[1])
            runtime_env.change_stack_size(new_size)
        except ValueError:
            integer_error.raise_error()


class ResetStackPosition(NotBFCommand):
    def execute(self):
        runtime_env.reset_stack_position()


class ChangeStackPosition(NotBFCommand):
    def execute(self):
        try:
            new_position = int(self.tokenized_string[1])
            try:
                runtime_env.change_stack_position(new_position)
            except IndexError:
                no_cell_error.raise_error()
        except ValueError:
            integer_error.raise_error()


class ChangeCellValue(NotBFCommand):
    def execute(self):
        try:
            new_value = int(self.tokenized_string[1])
            if new_value <= runtime_env.max_cell_value and new_value >= runtime_env.min_cell_value:
                runtime_env.change_cell_value(new_value)
            else: 
                bad_value_error.raise_error()
        except ValueError:
            integer_error.raise_error()


class OutputOutputStream(NotBFCommand):
    def execute(self):
        runtime_env.output_output_stream()


class ResetOutputStream(NotBFCommand):
    def execute(self):
        runtime_env.reset_output_stream()


class GetCodeInput(object):
    """
    Get code file input from a path.
    """
    def __init__(self, code_file_path):
        self.code_file_path = code_file_path

    def return_file(self):
        """"""
        with open(self.code_file_path, "r") as code_file:
            return code_file.read().replace("\n", "").replace(" ", "").replace("\t", "")


class Tokenizer(object):
    """
    Tokenize given input into a format that is readable
    by the interpreter class. Here's the format.
    tokenized_string = [ [ keyword, arg, ... ], [ ... ], ... ]
    """
    def __init__(self, input_string, line_split=";", arg_split="&"):
        self.input_string = input_string
        self.line_split = line_split
        self.arg_split = arg_split

    def tokenize(self):
        """
        Tokenize the string input into the
        correct format.
        """
        tokenized_string = self.input_string.split(self.line_split)
        tokenized_string = [string.split(self.arg_split) for string in tokenized_string]
        tokenized_string.remove([""])
        print tokenized_string
        return tokenized_string


class Interpreter(object):
    def __init__(self, tokenized_input):
        self.tokenized_input = tokenized_input
        self.COMMAND_KEYS = {
            "add_ostream": AddOutputStream,
            "chg_pos": ChangeStackPosition,
            "reset_stack": ResetStack,
            "chg_size": ChangeStackSize,
            "reset_pos": ResetStackPosition,
            "chg_cell": ChangeCellValue,
            "out_stream": OutputOutputStream,
            "reset_ostream": ResetOutputStream,
        }

    def execute_input(self):
        for line in self.tokenized_input:
            token = line[0]
            if token in self.COMMAND_KEYS:
                command_to_execute = self.COMMAND_KEYS[token](line)
                command_to_execute.execute()
            else: 
                command_error.raise_error()


if __name__ == "__main__":
    code_input = GetCodeInput(argv[1]).return_file()
    tokenized_code = Tokenizer(code_input).tokenize()
    interpreter = Interpreter(tokenized_code).execute_input()

Вот пример кода и его вывод:

 chg_cell & 48;
add_ostream;
chg_pos & 1;

chg_cell & 49;
add_ostream;
chg_pos & 1;

reset_stack;
chg_size & 512;

chg_cell & 65;
add_ostream;
chg_pos & 1;

chg_cell & 66;
add_ostream;
chg_pos & 1;

out_stream;
reset_stack;

reset_ostream;

Вот результат этой программы:

  

[['chg_cell', '48'], ['add_ostream'], ['chg_pos', '1'], ['chg_cell', '49'], ['add_ostream'], ['chg_pos', '1'], ['reset_stack'], ['chg_size', '512'], ['chg_cell', '65'], ['add_ostream'], ['chg_pos', '1'], ['chg_cell', '66'], ['add_ostream'], ['chg_pos', '1'], ['out_stream'], ['reset_stack'],['reset_ostream']] 01AB

11 голосов | спросил Ethan Bierlein 12 AMpSun, 12 Apr 2015 05:03:19 +030003Sunday 2015, 05:03:19

1 ответ


8

Как классический «Остановить написание классов» ставит это:

  

подпись "это не должен быть класс" заключается в том, что он имеет два метода, один из которых __init__

Практически все ваши классы оскверняют это; просто потому, что вы можете использовать ООП, не означает, что вы всегда должны . Рассматривая использование классов в коде, это был большой красный флаг:

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

Вы создаете экземпляр и сразу вызываете на нем метод; вам на самом деле не нужен экземпляр (или сохранить его), вы просто разделили логику (практически произвольно) на два метода. Последняя строка присваивает interpreter = None, что даже не имеет смысла! Многое neater были бы простыми функциями:

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

Только Environment использует состояние значимым образом. Все ваши подклассы NotBFCommand полагаются на существование некоторого глобального runtime_env; каждый из них имеет единственный метод, который просто псевдонизирует метод в Environment, поэтому почему бы просто не использовать эти методы ?

Реализация более аккуратной будет означать, что в основном весь ваш код связан с выполнением операций в стеке. Таким образом, вы можете иметь одиночный класс , который инкапсулирует Environment (состояние, стек и выходной поток), NotBFCommand s (методы) и NotBFError s (метод плюс некоторые данные). Например:

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(*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,
    }

Заметьте, что я добавил docstrings и последовал за руководство по стилю . Запуск программы теперь:

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

Я также сделал изменение размера стека постоянным - если это не подходит, вам может потребоваться рефакторинг.

ответил jonrsharpe 12 PMpSun, 12 Apr 2015 18:53:04 +030053Sunday 2015, 18:53:04

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132