version one works, dont look at the, code, only output
This commit is contained in:
parent
8bde5c7e4b
commit
ac29780e6b
12 changed files with 447 additions and 0 deletions
0
brainfuck/__init__.py
Normal file
0
brainfuck/__init__.py
Normal file
3
brainfuck/admin.py
Normal file
3
brainfuck/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
brainfuck/apps.py
Normal file
6
brainfuck/apps.py
Normal 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
218
brainfuck/interpreter.py
Normal 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 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 <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()
|
0
brainfuck/migrations/__init__.py
Normal file
0
brainfuck/migrations/__init__.py
Normal file
3
brainfuck/models.py
Normal file
3
brainfuck/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
153
brainfuck/templates/brainfuck/index.html
Normal file
153
brainfuck/templates/brainfuck/index.html
Normal 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
3
brainfuck/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
8
brainfuck/urls.py
Normal file
8
brainfuck/urls.py
Normal 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
49
brainfuck/views.py
Normal 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,
|
||||
})
|
|
@ -152,6 +152,7 @@ INSTALLED_APPS = (
|
|||
'vyroci',
|
||||
'sifrovacka',
|
||||
'novinky',
|
||||
'brainfuck',
|
||||
|
||||
# Admin upravy:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue