🐐 Web server
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user