version one works, dont look at the, code, only output

This commit is contained in:
ticvac 2025-05-07 15:04:57 +02:00
parent 8bde5c7e4b
commit ac29780e6b
12 changed files with 447 additions and 0 deletions

0
brainfuck/__init__.py Normal file
View file

3
brainfuck/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
brainfuck/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class BrainfuckConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'brainfuck'

218
brainfuck/interpreter.py Normal file
View file

@ -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 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 <code_file> < <user_input>
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()

View file

3
brainfuck/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View file

@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brainfuck Compiler</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #f5f7fa;
--card: #ffffff;
--primary: #4f46e5;
--primary-light: #6366f1;
--text: #374151;
--border: #e5e7eb;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); }
.container { max-width: 1100px; margin: 2rem auto; padding: 1rem; }
h1 { text-align: center; margin-bottom: 1.5rem; font-size: 2rem; font-weight: 700; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 0.5rem; padding: 1.5rem; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 1.5rem; }
label { display: block; margin-bottom: 0.5rem; font-weight: 600; }
textarea, input[type="text"], input[type="file"] {
width: 100%; padding: 0.75rem; border: 1px solid var(--border); border-radius: 0.375rem; font-family: monospace; font-size: 0.9rem; margin-bottom: 1rem;
}
textarea { resize: vertical; height: 200px; }
.btn {
display: inline-block; padding: 0.75rem 1.5rem; background: var(--primary); color: #fff; text-decoration: none;
border: none; border-radius: 0.375rem; font-weight: 600; cursor: pointer;
transition: background 0.2s ease;
}
.btn:hover { background: var(--primary-light); }
.flex { display: flex; gap: 1rem; }
.flex .card { flex: 1; }
#output, #last_state { background: #1e293b; color: #d1d5db; height: 150px; overflow-y: auto; }
</style>
</head>
<body>
<div class="container">
<h1>Brainfuck Compiler</h1>
<form id="bf-form" class="card">
<div class="flex">
<div>
<label for="code">Code</label>
<textarea id="code" placeholder="Enter Brainfuck code, upload .bf, .in, or .txt file..."></textarea>
<input type="file" id="code-file" accept=".bf, .in, .txt">
</div>
<div>
<label for="input">Input</label>
<textarea id="input" placeholder="Program input, upload .in or .txt..."></textarea>
<input type="file" id="input-file" accept=".txt, .in">
</div>
</div>
<button type="button" id="run-btn" class="btn">Run</button>
</form>
<div class="card">
<label for="output">Output</label>
<div id="output"></div>
</div>
<div class="card">
<label for="last_state">Stav</label>
<div id="last_state"></div>
</div>
</div>
<script>
// Load file into textarea
document.getElementById('code-file').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => document.getElementById('code').value = reader.result;
reader.readAsText(file);
});
document.getElementById('input-file').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => document.getElementById('input').value = reader.result;
reader.readAsText(file);
});
// Brainfuck Interpreter
function runBrainfuck(code, input) {
const tape = Array(30000).fill(0);
let ptr = 0, inputPtr = 0, output = '';
const loopStack = [];
for (let i = 0; i < code.length; i++) {
switch(code[i]) {
case '>': ptr++; break;
case '<': ptr--; break;
case '+': tape[ptr] = (tape[ptr] + 1) % 256; break;
case '-': tape[ptr] = (tape[ptr] + 255) % 256; break;
case '.': output += String.fromCharCode(tape[ptr]); break;
case ',': tape[ptr] = inputPtr < input.length ? input.charCodeAt(inputPtr++) : 0; break;
case '[':
if (tape[ptr] === 0) {
let open = 1;
while (open) {
i++;
if (code[i] === '[') open++;
if (code[i] === ']') open--;
}
} else {
loopStack.push(i);
}
break;
case ']':
if (tape[ptr] !== 0) {
i = loopStack[loopStack.length - 1];
} else {
loopStack.pop();
}
break;
default: break;
}
}
return output;
}
document.getElementById('run-btn').addEventListener('click', () => {
const code = document.getElementById('code').value;
const input = document.getElementById('input').value;
// const output = runBrainfuck(code, input);
// here compilation...
url = '/brainfuck/process_code?code=' + encodeURI(code) + "&user_input=" + encodeURI(input);
console.log(url);
console.log(encodeURI(code));
fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}).then(Response => Response.json())
.then(data => {
console.log(data);
document.getElementById('output').textContent = data.output;
document.getElementById('last_state').textContent = data.end_state;
})
// fetch('/brainfuck/', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: body
// }).then(Response => Response.json())
// .then(data => {
// console.log(data);
// })
});
</script>
</body>
</html>

3
brainfuck/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

8
brainfuck/urls.py Normal file
View file

@ -0,0 +1,8 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.index),
path('process_code', views.process_code),
]

49
brainfuck/views.py Normal file
View file

@ -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,
})

View file

@ -152,6 +152,7 @@ INSTALLED_APPS = (
'vyroci',
'sifrovacka',
'novinky',
'brainfuck',
# Admin upravy:

View file

@ -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.