🐐 Web server

This commit is contained in:
2026-04-22 09:43:58 +02:00
parent 39218485c7
commit 8c69a300a5
13 changed files with 541 additions and 34 deletions

View File

@@ -1,6 +1,8 @@
import http.server
import re
import random
import time
import urllib.parse
import urllib.request
from fractions import Fraction
from rply.token import BaseBox
@@ -8,6 +10,26 @@ from rply.token import BaseBox
from centvrion.errors import CentvrionError
from centvrion.values import Val, ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac
class _CentRng:
"""Xorshift32 RNG — identical algorithm in Python and C runtime."""
def __init__(self, seed=None):
if seed is None:
seed = int(time.time())
self.state = seed & 0xFFFFFFFF or 1
def seed(self, s):
self.state = s & 0xFFFFFFFF or 1
def next(self):
x = self.state
x ^= (x << 13) & 0xFFFFFFFF
x ^= x >> 17
x ^= (x << 5) & 0xFFFFFFFF
self.state = x
return x
def randint(self, a, b):
return a + self.next() % (b - a + 1)
_cent_rng = _CentRng()
NUMERALS = {
"I": 1,
"IV": 4,
@@ -1175,7 +1197,7 @@ class BuiltIn(Node):
raise CentvrionError("FORTVITVS_NVMERVS requires two numbers")
if a > b:
raise CentvrionError(f"FORTVITVS_NVMERVS: first argument ({a}) must be ≤ second ({b})")
return vtable, ValInt(random.randint(a, b))
return vtable, ValInt(_cent_rng.randint(a, b))
case "FORTVITA_ELECTIO":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'FORTVITA_ELECTIO' without module 'FORS'")
@@ -1184,14 +1206,14 @@ class BuiltIn(Node):
lst = params[0].value()
if len(lst) == 0:
raise CentvrionError("FORTVITA_ELECTIO: cannot select from an empty array")
return vtable, lst[random.randint(0, len(lst) - 1)]
return vtable, lst[_cent_rng.randint(0, len(lst) - 1)]
case "SEMEN":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'SEMEN' without module 'FORS'")
seed = params[0].value()
if not isinstance(seed, int):
raise CentvrionError("SEMEN requires an integer seed")
random.seed(seed)
_cent_rng.seed(seed)
return vtable, ValNul()
case "DECIMATIO":
if "FORS" not in vtable["#modules"]:
@@ -1201,7 +1223,7 @@ class BuiltIn(Node):
arr = list(params[0].value())
to_remove = len(arr) // 10
for _ in range(to_remove):
arr.pop(random.randint(0, len(arr) - 1))
arr.pop(_cent_rng.randint(0, len(arr) - 1))
return vtable, ValList(arr)
case "SENATVS":
if len(params) == 1 and isinstance(params[0], ValList):
@@ -1301,6 +1323,75 @@ class BuiltIn(Node):
except re.error as e:
raise CentvrionError(f"Invalid regex: {e}")
return vtable, ValStr(result)
case "PETE":
if "RETE" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'PETE' without module 'RETE'")
url = params[0]
if not isinstance(url, ValStr):
raise CentvrionError("PETE requires a string URL")
try:
with urllib.request.urlopen(url.value()) as resp:
return vtable, ValStr(resp.read().decode("utf-8"))
except Exception as e:
raise CentvrionError(f"PETE: {e}")
case "PETITVR":
if "RETE" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'PETITVR' without module 'RETE'")
path, handler = params[0], params[1]
if not isinstance(path, ValStr):
raise CentvrionError("PETITVR requires a string path")
if not isinstance(handler, ValFunc):
raise CentvrionError("PETITVR requires a function handler")
vtable["#routes"].append((path.value(), handler))
return vtable, ValNul()
case "AVSCVLTA":
if "RETE" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'AVSCVLTA' without module 'RETE'")
port = params[0]
if not isinstance(port, ValInt):
raise CentvrionError("AVSCVLTA requires an integer port")
routes = vtable["#routes"]
if not routes:
raise CentvrionError("AVSCVLTA: no routes registered")
captured_vtable = vtable.copy()
route_map = {p: h for p, h in routes}
class _CentHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
parsed = urllib.parse.urlparse(self.path)
handler = route_map.get(parsed.path)
if handler is None:
self.send_response(404)
self.end_headers()
return
request_dict = ValDict({
"via": ValStr(parsed.path),
"quaestio": ValStr(parsed.query),
"methodus": ValStr("GET"),
})
func_vtable = captured_vtable.copy()
if len(handler.params) == 1:
func_vtable[handler.params[0].name] = request_dict
func_vtable["#return"] = None
for statement in handler.body:
func_vtable, _ = statement.eval(func_vtable)
if func_vtable["#return"] is not None:
break
result = func_vtable["#return"]
if isinstance(result, ValStr):
body = result.value().encode("utf-8")
elif result is not None:
body = make_string(result, magnvm, svbnvlla).encode("utf-8")
else:
body = b""
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
server = http.server.HTTPServer(("0.0.0.0", port.value()), _CentHandler)
server.serve_forever()
return vtable, ValNul()
case _:
raise NotImplementedError(self.builtin)
@@ -1331,6 +1422,7 @@ class Program(BaseBox):
"#continue": False,
"#return": None,
"#modules": [m.module_name for m in self.modules],
"#routes": [],
}
last_val = ValNul()
for statement in self.statements: