diff --git a/brainfuck/__init__.py b/brainfuck/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/brainfuck/admin.py b/brainfuck/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/brainfuck/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/brainfuck/apps.py b/brainfuck/apps.py new file mode 100644 index 00000000..197e5069 --- /dev/null +++ b/brainfuck/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BrainfuckConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'brainfuck' diff --git a/brainfuck/interpreter.py b/brainfuck/interpreter.py new file mode 100644 index 00000000..804d833e --- /dev/null +++ b/brainfuck/interpreter.py @@ -0,0 +1,218 @@ +import sys +import re +import time + +class Interpreter: + def __init__(self, commands_per_second=1_000_000, debug=False, comma_standart_input=True): + self.debug = debug + self.commands = ['+', '-', '>', '<', '.', ',', '[', ']'] + self.mp = 0 # memory pointer + self.pc = 0 # program counter + self.data = [0] * 2 + ''' + chceme mít k dispozici i konec datového pole, (abychom mohli zavolat '<' na 0) + -> konec pole budeme mít jako self.data_end a kde jsme, poznáme podle self.is_at_end... + pak můžeme podle potřeby zvětšovat pole ze začátku nebo z konce + + !poznámka autora: dalo by se také udělat s jedním polem, tak že bychom 'posouvali' index 0... + to mě ale nenapadlo, když jsem to psal, tak jsem to udělal takhle... + ''' + self.data_end = [0] * 2 # tail is reversed + self.is_at_end = False + self.code = "" + self.commands_per_second = commands_per_second + self.MAX = 2**32 - 1 + + self.user_input = "" + self.user_input_index = 0 + self.saved_output = "" + self.comma_standart_input = comma_standart_input + self.last_step = 0 + + def our_i32(self, value): + if value == -1: + return self.MAX + if value == self.MAX + 1: + return 0 + return value + + def load_code_from_input(self): + def expand_numbers(match): + count = int(match.group(1)) + char = match.group(2) + return char * count + + self.code = "" + code_filename = sys.argv[1] + with open(code_filename) as code_file: + for line in code_file: + line = line.strip() + line = line.split('//')[0].strip() + line = re.sub(r'(\d+)(.)', expand_numbers, line) + line = ''.join(filter(lambda c: c in self.commands, line)) + self.code += line + + def load_code_from_string(self, code): + def expand_numbers(match): + count = int(match.group(1)) + char = match.group(2) + return char * count + + self.code = "" + code = code.strip() + code = re.sub(r'(\d+)(.)', expand_numbers, code) + code = ''.join(filter(lambda c: c in self.commands, code)) + self.code += code + + def print_overwrite(self, message, prev_len=[0]): + sys.stdout.write('\r' + message + ' ' * max(prev_len[0] - len(message), 0)) + sys.stdout.flush() + prev_len[0] = len(message) + + def print_data(self, steps): + to_print = "step: " + str(steps) + " " + to_print += "mp: " + str(self.mp) + " | " + for i in range(len(self.data)): + to_print += str(self.data[i]) + " " + to_print += "|" + to_print += " end: | " + for i in range(len(self.data_end)).__reversed__(): + to_print += str(self.data_end[i]) + " " + to_print += "|" + self.print_overwrite(to_print) + return to_print + + def get_value_at_mp(self): + if not self.is_at_end: + return self.data[self.mp] + else: + return self.data_end[self.MAX - self.mp] + + def set_value_at_mp(self, value): + if not self.is_at_end: + self.data[self.mp] = value + else: + self.data_end[self.MAX - self.mp] = value + + def handle_plus(self): + self.set_value_at_mp(self.our_i32(self.get_value_at_mp() + 1)) + + def handle_minus(self): + self.set_value_at_mp(self.our_i32(self.get_value_at_mp() - 1)) + + def handle_greater(self): + temp = self.mp + self.mp = self.our_i32(self.mp + 1) + # kontrola, jesstli jsem ne přeskočil konec + if self.mp > self.MAX - len(self.data_end): + self.is_at_end = True + return + if temp > self.mp: + self.is_at_end = False + # rozšíření datového pole + if self.mp >= len(self.data): + self.data += [0] * (len(self.data)) + + def handle_less(self): + temp = self.mp + self.mp = self.our_i32(self.mp - 1) + # kontrola, jestli jsem ne přeskočil začátek + if self.mp < len(self.data): + self.is_at_end = False + return + if temp < self.mp: + self.is_at_end = True + if self.MAX - self.mp >= len(self.data_end): + self.data_end += [0] * (len(self.data_end)) + + def handle_dot(self): + sys.stdout.write(chr(self.get_value_at_mp())) + sys.stdout.flush() + self.saved_output += chr(self.get_value_at_mp()) + + def handle_comma(self): + if self.comma_standart_input: + input_char = sys.stdin.read(1) + else: + if self.user_input_index < len(self.user_input): + input_char = self.user_input[self.user_input_index] + self.user_input_index += 1 + else: + input_char = None + if not input_char: + self.set_value_at_mp(0) + else: + self.set_value_at_mp(ord(input_char)) + + def handle_left_bracket(self): + if self.data[self.mp] == 0: # jump to the matching ] + open_brackets = 1 + while open_brackets > 0: + self.pc += 1 + if self.pc >= len(self.code): + raise SyntaxError("Unmatched \"[\"") + if self.code[self.pc] == "[": + open_brackets += 1 + elif self.code[self.pc] == "]": + open_brackets -= 1 + + def handle_right_bracket(self): + if self.data[self.mp] != 0: + close_brackets = 1 + while close_brackets > 0: + self.pc -= 1 + if self.pc < 0: + raise SyntaxError("Unmatched \"]\"") + if self.code[self.pc] == "]": + close_brackets += 1 + elif self.code[self.pc] == "[": + close_brackets -= 1 + + def execute_loaded_code(self): + steps = 0 + print("|Executing code|") + while True: + if self.pc >= len(self.code): + break + com = self.code[self.pc] + if com == "+": + self.handle_plus() + elif com == "-": + self.handle_minus() + elif com == ">": + self.handle_greater() + elif com == "<": + self.handle_less() + elif com == ".": + self.handle_dot() + elif com == ",": + self.handle_comma() + elif com == "[": + self.handle_left_bracket() + elif com == "]": + self.handle_right_bracket() + # debug print + if self.debug: + self.print_data(steps) + time.sleep(1 / self.commands_per_second) + self.pc += 1 + steps += 1 + self.last_step = steps + print("\n|Execution done|") + +''' +Usage: +python interpreter.py < + +nastavte si DEBUG na True, pokud chcete vidět debugovací výstup, pak ale nebude správně fungovat "."... +nastavte si commands_per_second na 2, pokud chcete dva kroky za sekundu +''' + +DEBUG = True +COMMANDS_PER_SECOND = 1000 + +if __name__ == "__main__": + interpreter = Interpreter(commands_per_second=COMMANDS_PER_SECOND, debug=DEBUG) + interpreter.load_code_from_input() + print("Loaded code:", interpreter.code, "length:", len(interpreter.code)) + interpreter.execute_loaded_code() diff --git a/brainfuck/migrations/__init__.py b/brainfuck/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/brainfuck/models.py b/brainfuck/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/brainfuck/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/brainfuck/templates/brainfuck/index.html b/brainfuck/templates/brainfuck/index.html new file mode 100644 index 00000000..72ea84e9 --- /dev/null +++ b/brainfuck/templates/brainfuck/index.html @@ -0,0 +1,153 @@ + + + + + + Brainfuck Compiler + + + + +
+

Brainfuck Compiler

+
+
+
+ + + +
+
+ + + +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ + + + diff --git a/brainfuck/tests.py b/brainfuck/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/brainfuck/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/brainfuck/urls.py b/brainfuck/urls.py new file mode 100644 index 00000000..3b4f273d --- /dev/null +++ b/brainfuck/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.index), + path('process_code', views.process_code), +] + diff --git a/brainfuck/views.py b/brainfuck/views.py new file mode 100644 index 00000000..0b8cf338 --- /dev/null +++ b/brainfuck/views.py @@ -0,0 +1,49 @@ +from django.template.response import TemplateResponse +from django.http import JsonResponse as JSONResponse +from .interpreter import Interpreter +from urllib.parse import parse_qsl, unquote_plus, unquote +from typing import Dict + + +def index(request): + args = {} + return TemplateResponse(request, 'brainfuck/index.html', args) + + +def process_code(request): + qs = request.META['QUERY_STRING'] + params: Dict[str, str] = {} + for pair in qs.split('&'): + if not pair: + continue + key, sep, val = pair.partition('=') + # decode key (still treat '+'→space in parameter names) + decoded_key = unquote_plus(key) + # decode value: unquote (leaves '+' intact), then percent-decode + decoded_val = unquote(val) + params[decoded_key] = decoded_val + + print('params:', params) + + code = params.get('code', '') + input_data = params.get('user_input', '') + # interpreter + interpreter = Interpreter(commands_per_second=1_000_000_000_000, debug=True, comma_standart_input=False) + interpreter.user_input = input_data + interpreter.load_code_from_string(code) + # should be try/except? + try: + interpreter.execute_loaded_code() + out = interpreter.saved_output + except Exception as e: + print('Error:', e) + out = 'Error: Code execution failed' + + end_state = interpreter.print_data(interpreter.last_step) + + return JSONResponse({ + 'code': code, + 'input_data': input_data, + 'output': out, + 'end_state': end_state, + }) diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index 9a87ed87..d4679a12 100644 --- a/mamweb/settings_common.py +++ b/mamweb/settings_common.py @@ -152,6 +152,7 @@ INSTALLED_APPS = ( 'vyroci', 'sifrovacka', 'novinky', + 'brainfuck', # Admin upravy: diff --git a/mamweb/urls.py b/mamweb/urls.py index eed460c2..495b750f 100644 --- a/mamweb/urls.py +++ b/mamweb/urls.py @@ -62,6 +62,9 @@ urlpatterns = [ # Miniapka na šifrovačku path('sifrovacka/', include('sifrovacka.urls')), + + # tematku brainfuck + path('brainfuck/', include('brainfuck.urls')), ] # This is only needed when using runserver.