Files
centvrion/centvrion/ast_nodes.py
2026-04-24 18:33:48 +02:00

1698 lines
56 KiB
Python

import http.server
import re
import time
import urllib.parse
import urllib.request
from fractions import Fraction
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,
"V": 5,
"IX": 9,
"X": 10,
"XL": 40,
"L": 50,
"XC": 90,
"C": 100,
"CD": 400,
"D": 500,
"CM": 900,
"M": 1000
}
def rep_join(l):
format_string = ',\n'.join(
[repr(i) if not isinstance(i, str) else i for i in l]
).replace('\n', '\n ')
if format_string != "":
format_string = f"\n {format_string}\n"
return format_string
OP_STR = {
"SYMBOL_PLUS": "+", "SYMBOL_MINUS": "-",
"SYMBOL_TIMES": "*", "SYMBOL_DIVIDE": "/",
"SYMBOL_AMPERSAND": "&", "SYMBOL_AT": "@",
"KEYWORD_RELIQVVM": "RELIQVVM",
"KEYWORD_EST": "EST", "KEYWORD_DISPAR": "DISPAR",
"KEYWORD_MINVS": "MINVS",
"KEYWORD_PLVS": "PLVS", "KEYWORD_ET": "ET", "KEYWORD_AVT": "AVT",
"KEYWORD_HAVD_PLVS": "HAVD_PLVS",
"KEYWORD_HAVD_MINVS": "HAVD_MINVS",
}
def single_num_to_int(i, m):
if i[-1] == "_":
if not m:
raise CentvrionError(
"Cannot calculate numbers above 3999 without 'MAGNVM' module"
)
if i[0] != "I":
raise CentvrionError(
"Cannot use 'I' with thousands operator, use 'M' instead"
)
return 1000 * single_num_to_int(i[:-1], m)
else:
return NUMERALS[i]
def num_to_int(n, m, s=False):
if n.startswith("-"):
if not s:
raise CentvrionError("Cannot use negative numbers without 'SVBNVLLA' module")
return -num_to_int(n[1:], m, s)
chars = re.findall(r"[IVXLCDM]_*", n)
if ''.join(chars) != n:
raise CentvrionError(f"Invalid numeral: {n!r}")
nums = [single_num_to_int(i, m) for i in chars]
new_nums = nums.copy()
for x, num in enumerate(nums[:-3]):
if all(num == nums[x+i] for i in range(0,4)):
raise CentvrionError(f"{n!r} is not a valid roman numeral")
while True:
for x, num in enumerate(nums[:-1]):
if num < nums[x+1]:
if (not nums[x+1] % 5) and (num in [nums[x+1]/5, nums[x+1]/10]):
new_nums[x] = nums[x+1] - num
new_nums[x+1] = 0
break
else:
raise CentvrionError(f"{n!r} is not a valid roman numeral")
if new_nums != nums:
nums = [i for i in new_nums if i != 0]
new_nums = nums.copy()
else:
break
if nums != sorted(nums)[::-1]:
raise CentvrionError(f"{n!r} is not a valid roman numeral")
return sum(nums)
def int_to_num(n, m, s=False) -> str:
if n < 0:
if not s:
raise CentvrionError("Cannot display negative numbers without 'SVBNVLLA' module")
return "-" + int_to_num(-n, m, s)
if n > 3999:
if not m:
raise CentvrionError(
"Cannot display numbers above 3999 without 'MAGNVM' module"
)
thousands_chars = re.findall(r"[IVXLCDM]_*", int_to_num(n//1000, m, s))
thousands = ''.join([
"M" if i == "I" else i+"_"
for i in thousands_chars
])
remainder = n % 1000
return thousands + (int_to_num(remainder, m, s) if remainder else "")
else:
if n == 0:
return "NVLLVS"
nums = []
while n > 0:
for num, i in list(NUMERALS.items())[::-1]:
if n >= i:
nums.append(num)
n -= i
break
return ''.join(nums)
def make_string(val, magnvm=False, svbnvlla=False) -> str:
if isinstance(val, ValStr):
return val.value()
elif isinstance(val, ValInt):
return int_to_num(val.value(), magnvm, svbnvlla)
elif isinstance(val, ValFrac):
return fraction_to_frac(val.value(), magnvm, svbnvlla)
elif isinstance(val, ValBool):
return "VERITAS" if val.value() else "FALSITAS"
elif isinstance(val, ValNul):
return "NVLLVS"
elif isinstance(val, ValList):
inner = ' '.join(make_string(i, magnvm, svbnvlla) for i in val.value())
return f"[{inner}]"
elif isinstance(val, ValDict):
def _key_val(k):
return ValStr(k) if isinstance(k, str) else ValInt(k)
inner = ', '.join(
f"{make_string(_key_val(k), magnvm, svbnvlla)} VT {make_string(v, magnvm, svbnvlla)}"
for k, v in val.value().items()
)
return "{" + inner + "}"
elif isinstance(val, ValFunc):
return "FVNCTIO"
else:
raise CentvrionError(f"Cannot display {val!r}")
def _roman_backref(m):
try:
n = num_to_int(m.group(1), False)
except CentvrionError:
return m.group(0)
return f"\\{n}"
def _check_arabic_backref(s):
for i in range(len(s) - 1):
if s[i] == '\\' and s[i+1].isdigit():
raise CentvrionError(f"Invalid escape sequence '\\{s[i+1]}' — use Roman numerals for backreferences")
def _romanize_replacement(s):
_check_arabic_backref(s)
return re.sub(r'\\([IVXLCDM]+)', _roman_backref, s)
def _convert_quantifier(inner):
parts = inner.split(',')
converted = []
for p in parts:
p = p.strip()
if p == '':
converted.append('')
else:
try:
converted.append(str(num_to_int(p, False)))
except CentvrionError:
return None
return '{' + ','.join(converted) + '}'
def _romanize_pattern(s):
result = []
i = 0
while i < len(s):
if s[i] == '\\' and i + 1 < len(s) and s[i+1] in 'IVXLCDM':
# backref: collect Roman numeral chars and convert
j = i + 1
while j < len(s) and s[j] in 'IVXLCDM':
j += 1
try:
n = num_to_int(s[i+1:j], False)
result.append(f'\\{n}')
except CentvrionError:
result.append(s[i:j])
i = j
elif s[i] == '\\' and i + 1 < len(s) and s[i+1].isdigit():
raise CentvrionError(f"Invalid escape sequence '\\{s[i+1]}' — use Roman numerals for backreferences")
elif s[i] == '\\' and i + 1 < len(s):
result.append(s[i:i+2])
i += 2
elif s[i] == '[':
# skip character class
j = i + 1
if j < len(s) and s[j] == '^':
j += 1
if j < len(s) and s[j] == ']':
j += 1
while j < len(s) and s[j] != ']':
if s[j] == '\\' and j + 1 < len(s):
j += 1
j += 1
result.append(s[i:j+1])
i = j + 1
elif s[i] == '{':
j = s.find('}', i)
if j == -1:
result.append(s[i])
i += 1
else:
inner = s[i+1:j]
if re.match(r'^[\d,\s]+$', inner) and re.search(r'\d', inner):
raise CentvrionError(f"Invalid quantifier '{{{inner}}}' — use Roman numerals")
converted = _convert_quantifier(inner)
if converted is not None:
result.append(converted)
else:
result.append(s[i:j+1])
i = j + 1
else:
result.append(s[i])
i += 1
return ''.join(result)
FRAC_SYMBOLS = [("S", 6), (":", 2), (".", 1)]
def frac_to_fraction(s, magnvm=False, svbnvlla=False):
match = re.match(r'^([IVXLCDM][IVXLCDM_]*)(.*)', s)
if match:
int_str, frac_str = match.group(1), match.group(2)
total = Fraction(num_to_int(int_str, magnvm, svbnvlla))
else:
total = Fraction(0)
frac_str = s
for level_idx, level in enumerate(frac_str.split('|')):
divisor = 12 ** (level_idx + 1)
level_val = 0
prev_weight = 12
for ch in level:
weight = next((w for sym, w in FRAC_SYMBOLS if sym == ch), None)
if weight is None:
raise CentvrionError(f"Invalid fraction symbol: {ch!r}")
if weight > prev_weight:
raise CentvrionError("Fraction symbols must be written highest to lowest")
prev_weight = weight
level_val += weight
if level_val >= 12:
raise CentvrionError("Fraction level value must be less than 1 (< 12/12)")
total += Fraction(level_val, divisor)
return total
def fraction_to_frac(f, magnvm=False, svbnvlla=False) -> str:
if f < 0:
if not svbnvlla:
raise CentvrionError("Cannot display negative numbers without 'SVBNVLLA' module")
return "-" + fraction_to_frac(-f, magnvm, svbnvlla)
integer_part = int(f)
remainder = f - integer_part
int_str = int_to_num(integer_part, magnvm, svbnvlla) if integer_part > 0 else ""
levels = []
current = remainder
for _ in range(6):
if current == 0:
break
current = current * 12
level_int = int(current)
current = current - level_int
s_count = level_int // 6
remaining = level_int % 6
colon_count = remaining // 2
dot_count = remaining % 2
levels.append("S" * s_count + ":" * colon_count + "." * dot_count)
return int_str + "|".join(levels)
class Node(BaseBox):
def eval(self, vtable):
return self._eval(vtable.copy())
def _eval(self, vtable):
raise NotImplementedError
def print(self):
return "Node()"
class ExpressionStatement(Node):
def __init__(self, expression: Node) -> None:
self.expression = expression
def __eq__(self, other):
return type(self) == type(other) and self.expression == other.expression
def __repr__(self) -> str:
expr = repr(self.expression).replace('\n', '\n ')
return f"ExpressionStatement(\n {expr}\n)"
def print(self):
return self.expression.print()
def _eval(self, vtable):
return self.expression.eval(vtable)
class DataArray(Node):
def __init__(self, content) -> None:
self.content = content
def __eq__(self, other):
return type(self) == type(other) and self.content == other.content
def __repr__(self) -> str:
content_string = rep_join(self.content)
return f"Array([{content_string}])"
def print(self):
items = ", ".join(i.print() for i in self.content)
return f"[{items}]"
def _eval(self, vtable):
vals = []
for item in self.content:
vtable, val = item.eval(vtable)
vals.append(val)
return vtable, ValList(vals)
class DataRangeArray(Node):
def __init__(self, from_value, to_value, step=None) -> None:
self.from_value = from_value
self.to_value = to_value
self.step = step
def __eq__(self, other):
return (type(self) == type(other)
and self.from_value == other.from_value
and self.to_value == other.to_value
and self.step == other.step)
def __repr__(self) -> str:
parts = [self.from_value, self.to_value]
if self.step is not None:
parts.append(self.step)
return f"RangeArray([{rep_join(parts)}])"
def print(self):
base = f"[{self.from_value.print()} VSQVE {self.to_value.print()}"
if self.step is not None:
base += f" GRADV {self.step.print()}"
return base + "]"
def _eval(self, vtable):
vtable, from_val = self.from_value.eval(vtable)
vtable, to_val = self.to_value.eval(vtable)
if not isinstance(from_val, (ValInt, ValNul)) or not isinstance(to_val, (ValInt, ValNul)):
raise CentvrionError("Range bounds must be numbers")
from_int = from_val.value() or 0
to_int = to_val.value() or 0
if self.step is None:
return vtable, ValList([ValInt(i) for i in range(from_int, to_int + 1)])
vtable, step_val = self.step.eval(vtable)
if not isinstance(step_val, ValInt):
raise CentvrionError("Range step must be a number")
step_int = step_val.value()
if step_int == 0:
raise CentvrionError("Range step cannot be zero")
items = []
i = from_int
if step_int > 0:
while i <= to_int:
items.append(ValInt(i))
i += step_int
else:
while i >= to_int:
items.append(ValInt(i))
i += step_int
return vtable, ValList(items)
class DataDict(Node):
def __init__(self, pairs) -> None:
self.pairs = pairs
def __eq__(self, other):
return type(self) == type(other) and self.pairs == other.pairs
def __repr__(self) -> str:
pair_strs = ', '.join(f"({k!r}, {v!r})" for k, v in self.pairs)
return f"Dict([{pair_strs}])"
def print(self):
items = ", ".join(f"{k.print()} VT {v.print()}" for k, v in self.pairs)
return "TABVLA {" + items + "}"
def _eval(self, vtable):
d = {}
for key_node, val_node in self.pairs:
vtable, key = key_node.eval(vtable)
vtable, val = val_node.eval(vtable)
if not isinstance(key, (ValStr, ValInt)):
raise CentvrionError("Dict keys must be strings or integers")
d[key.value()] = val
return vtable, ValDict(d)
class String(Node):
def __init__(self, value) -> None:
self.value = value
self.quote = '"'
def __eq__(self, other):
return type(self) == type(other) and self.value == other.value
def __repr__(self):
return f"String({self.value})"
def print(self):
v = (self.value
.replace('\\', '\\\\')
.replace('\n', '\\n')
.replace('\t', '\\t')
.replace('\r', '\\r'))
if self.quote == "'":
return f"'{v}'"
v = v.replace('"', '\\"').replace('{', '{{').replace('}', '}}')
return f'"{v}"'
def _eval(self, vtable):
return vtable, ValStr(self.value)
def _flip_quotes(node, quote):
"""Recursively set quote style on all String nodes in an expression tree."""
if isinstance(node, String):
node.quote = quote
for attr in vars(node).values():
if isinstance(attr, Node):
_flip_quotes(attr, quote)
elif isinstance(attr, list):
for item in attr:
if isinstance(item, Node):
_flip_quotes(item, quote)
class InterpolatedString(Node):
def __init__(self, parts) -> None:
self.parts = parts
def __eq__(self, other):
return type(self) == type(other) and self.parts == other.parts
def __repr__(self):
return f"InterpolatedString([{rep_join(self.parts)}])"
def print(self):
result = '"'
for part in self.parts:
if isinstance(part, String):
result += part.value.replace('{', '{{').replace('}', '}}')
else:
_flip_quotes(part, "'")
result += '{' + part.print() + '}'
_flip_quotes(part, '"')
result += '"'
return result
def _eval(self, vtable):
magnvm = "MAGNVM" in vtable["#modules"]
svbnvlla = "SVBNVLLA" in vtable["#modules"]
pieces = []
for part in self.parts:
vtable, val = part.eval(vtable)
pieces.append(make_string(val, magnvm, svbnvlla))
return vtable, ValStr(''.join(pieces))
class Numeral(Node):
def __init__(self, value: str) -> None:
self.value = value
def __eq__(self, other):
return type(self) == type(other) and self.value == other.value
def __repr__(self):
return f"Numeral({self.value})"
def print(self):
return self.value
def _eval(self, vtable):
return vtable, ValInt(num_to_int(self.value, "MAGNVM" in vtable["#modules"], "SVBNVLLA" in vtable["#modules"]))
class Fractio(Node):
def __init__(self, value: str) -> None:
self.value = value
def __eq__(self, other):
return type(self) == type(other) and self.value == other.value
def __repr__(self):
return f"Fractio({self.value!r})"
def print(self):
return self.value
def _eval(self, vtable):
if "FRACTIO" not in vtable["#modules"]:
raise CentvrionError("Cannot use fraction literals without 'FRACTIO' module")
m = "MAGNVM" in vtable["#modules"]
s = "SVBNVLLA" in vtable["#modules"]
return vtable, ValFrac(frac_to_fraction(self.value, m, s))
class Bool(Node):
def __init__(self, value) -> None:
self.value = value
def __eq__(self, other):
return type(self) == type(other) and self.value == other.value
def __repr__(self):
return f"Bool({self.value})"
def print(self):
return "VERITAS" if self.value else "FALSITAS"
def _eval(self, vtable):
return vtable, ValBool(self.value)
class ModuleCall(BaseBox):
def __init__(self, module_name) -> None:
self.module_name = module_name
def __eq__(self, other):
return type(self) == type(other) and self.module_name == other.module_name
def __repr__(self) -> str:
return f"{self.module_name}"
def print(self):
return f"CVM {self.module_name}"
class ID(Node):
def __init__(self, name: str) -> None:
self.name = name
def __eq__(self, other):
return type(self) == type(other) and self.name == other.name
def __repr__(self) -> str:
return f"ID({self.name})"
def print(self):
return self.name
def _eval(self, vtable):
if self.name not in vtable:
raise CentvrionError(f"Undefined variable: {self.name}")
return vtable, vtable[self.name]
class Designa(Node):
def __init__(self, variable: ID, value: Node) -> None:
self.id = variable
self.value = value
def __eq__(self, other):
return type(self) == type(other) and self.id == other.id and self.value == other.value
def __repr__(self) -> str:
id_string = repr(self.id).replace('\n', '\n ')
value_string = repr(self.value).replace('\n', '\n ')
return f"Designa(\n {id_string},\n {value_string}\n)"
def print(self):
return f"DESIGNA {self.id.print()} VT {self.value.print()}"
def _eval(self, vtable):
vtable, val = self.value.eval(vtable)
vtable[self.id.name] = val
return vtable, ValNul()
def _index_get(container, index):
if isinstance(container, ValDict):
if not isinstance(index, (ValStr, ValInt)):
raise CentvrionError("Dict key must be a string or integer")
d = container.value()
k = index.value()
if k not in d:
raise CentvrionError("Key not found in dict")
return d[k]
if isinstance(container, ValList):
i = index.value()
lst = container.value()
if i < 1 or i > len(lst):
raise CentvrionError(f"Index {i} out of range for array of length {len(lst)}")
return lst[i - 1]
if isinstance(container, ValStr):
if isinstance(index, ValInt):
i = index.value()
elif isinstance(index, ValFrac) and index.value().denominator == 1:
i = index.value().numerator
else:
raise CentvrionError("String index must be a number")
s = container.value()
if i < 1 or i > len(s):
raise CentvrionError(f"Index {i} out of range for string of length {len(s)}")
return ValStr(s[i - 1])
raise CentvrionError("Cannot index into a non-array, non-dict value")
def _index_set(container, index, value):
if isinstance(container, ValDict):
if not isinstance(index, (ValStr, ValInt)):
raise CentvrionError("Dict key must be a string or integer")
d = dict(container.value())
d[index.value()] = value
return ValDict(d)
if isinstance(container, ValList):
i = index.value()
lst = list(container.value())
if i < 1 or i > len(lst):
raise CentvrionError(f"Index {i} out of range for array of length {len(lst)}")
lst[i - 1] = value
return ValList(lst)
if isinstance(container, ValStr):
if isinstance(index, ValInt):
i = index.value()
elif isinstance(index, ValFrac) and index.value().denominator == 1:
i = index.value().numerator
else:
raise CentvrionError("String index must be a number")
if not isinstance(value, ValStr) or len(value.value()) != 1:
raise CentvrionError("String index assignment requires a single character")
s = container.value()
if i < 1 or i > len(s):
raise CentvrionError(f"Index {i} out of range for string of length {len(s)}")
return ValStr(s[:i - 1] + value.value() + s[i:])
raise CentvrionError("Cannot assign to index of a non-array, non-dict value")
class DesignaIndex(Node):
def __init__(self, variable: ID, indices, value) -> None:
self.id = variable
self.indices = indices if isinstance(indices, list) else [indices]
self.value = value
def __eq__(self, other):
return type(self) == type(other) and self.id == other.id and self.indices == other.indices and self.value == other.value
def __repr__(self) -> str:
return f"DesignaIndex({self.id!r}, {self.indices!r}, {self.value!r})"
def print(self):
idx_str = ''.join(f'[{idx.print()}]' for idx in self.indices)
return f"DESIGNA {self.id.print()}{idx_str} VT {self.value.print()}"
def _eval(self, vtable):
evaluated_indices = []
for idx_expr in self.indices:
vtable, idx_val = idx_expr.eval(vtable)
evaluated_indices.append(idx_val)
vtable, val = self.value.eval(vtable)
if self.id.name not in vtable:
raise CentvrionError(f"Undefined variable: {self.id.name}")
root = vtable[self.id.name]
containers = [root]
for idx in evaluated_indices[:-1]:
containers.append(_index_get(containers[-1], idx))
new_val = _index_set(containers[-1], evaluated_indices[-1], val)
for i in range(len(containers) - 2, -1, -1):
new_val = _index_set(containers[i], evaluated_indices[i], new_val)
vtable[self.id.name] = new_val
return vtable, ValNul()
class DesignaDestructure(Node):
def __init__(self, variables: list, value: Node) -> None:
self.ids = variables
self.value = value
def __eq__(self, other):
return type(self) == type(other) and self.ids == other.ids and self.value == other.value
def __repr__(self) -> str:
ids_string = ", ".join(repr(i) for i in self.ids)
value_string = repr(self.value).replace('\n', '\n ')
return f"DesignaDestructure(\n [{ids_string}],\n {value_string}\n)"
def print(self):
ids_str = ", ".join(i.print() for i in self.ids)
return f"DESIGNA {ids_str} VT {self.value.print()}"
def _eval(self, vtable):
vtable, val = self.value.eval(vtable)
if not isinstance(val, ValList):
raise CentvrionError("Cannot destructure non-array value")
if len(val.value()) != len(self.ids):
raise CentvrionError(
f"Destructuring mismatch: {len(self.ids)} targets, {len(val.value())} values")
for id_node, item in zip(self.ids, val.value()):
vtable[id_node.name] = item
return vtable, ValNul()
class Defini(Node):
def __init__(self, name: ID, parameters: list[ID], statements: list[Node]) -> None:
self.name = name
self.parameters = parameters
self.statements = statements
def __eq__(self, other):
return type(self) == type(other) and self.name == other.name and self.parameters == other.parameters and self.statements == other.statements
def __repr__(self) -> str:
parameter_string = f"parameters([{rep_join(self.parameters)}])"
statements_string = f"statements([{rep_join(self.statements)}])"
def_string = rep_join(
[f"{repr(self.name)}", parameter_string, statements_string]
)
return f"Defini({def_string})"
def print(self):
params = ", ".join(p.print() for p in self.parameters)
body = "\n".join(s.print() for s in self.statements)
return f"DEFINI {self.name.print()} ({params}) VT {{\n{body}\n}}"
def _eval(self, vtable):
vtable[self.name.name] = ValFunc(self.parameters, self.statements)
return vtable, ValNul()
class Fvnctio(Node):
def __init__(self, parameters: list[ID], statements: list[Node]) -> None:
self.parameters = parameters
self.statements = statements
def __eq__(self, other):
return (type(self) == type(other)
and self.parameters == other.parameters
and self.statements == other.statements)
def __repr__(self) -> str:
parameter_string = f"parameters([{rep_join(self.parameters)}])"
statements_string = f"statements([{rep_join(self.statements)}])"
fvn_string = rep_join([parameter_string, statements_string])
return f"Fvnctio({fvn_string})"
def print(self):
params = ", ".join(p.print() for p in self.parameters)
body = "\n".join(s.print() for s in self.statements)
return f"FVNCTIO ({params}) VT {{\n{body}\n}}"
def _eval(self, vtable):
return vtable, ValFunc(self.parameters, self.statements)
class Redi(Node):
def __init__(self, values: list[Node]) -> None:
self.values = values
def __eq__(self, other):
return type(self) == type(other) and self.values == other.values
def __repr__(self) -> str:
values_string = f"[{rep_join(self.values)}]"
return f"Redi({values_string})"
def print(self):
exprs = ", ".join(v.print() for v in self.values)
return f"REDI ({exprs})"
def _eval(self, vtable):
vals = []
for v in self.values:
vtable, val = v.eval(vtable)
vals.append(val)
if len(vals) == 1:
vtable["#return"] = vals[0]
else:
vtable["#return"] = ValList(vals)
return vtable, ValNul()
class Erumpe(Node):
def __eq__(self, other):
return type(self) == type(other)
def __repr__(self) -> str:
return "Erumpe()"
def print(self):
return "ERVMPE"
def _eval(self, vtable):
vtable["#break"] = True
return vtable, ValNul()
class Continva(Node):
def __eq__(self, other):
return type(self) == type(other)
def __repr__(self) -> str:
return "Continva()"
def print(self):
return "CONTINVA"
def _eval(self, vtable):
vtable["#continue"] = True
return vtable, ValNul()
class Nullus(Node):
def __eq__(self, other):
return type(self) == type(other)
def __repr__(self) -> str:
return "Nullus()"
def print(self):
return "NVLLVS"
def _eval(self, vtable):
return vtable, ValNul()
class BinOp(Node):
def __init__(self, left: Node, right: Node, op: str) -> None:
self.left = left
self.right = right
self.op = op
def __eq__(self, other):
return type(self) == type(other) and self.left == other.left and self.right == other.right and self.op == other.op
def __repr__(self) -> str:
binop_string = rep_join([self.left, self.right, self.op])
return f"BinOp({binop_string})"
def print(self):
return f"({self.left.print()} {OP_STR[self.op]} {self.right.print()})"
def _eval(self, vtable):
# Short-circuit for logical operators
if self.op == "KEYWORD_AVT":
vtable, left = self.left.eval(vtable)
if bool(left):
return vtable, ValBool(True)
vtable, right = self.right.eval(vtable)
return vtable, ValBool(bool(right))
if self.op == "KEYWORD_ET":
vtable, left = self.left.eval(vtable)
if not bool(left):
return vtable, ValBool(False)
vtable, right = self.right.eval(vtable)
return vtable, ValBool(bool(right))
vtable, left = self.left.eval(vtable)
vtable, right = self.right.eval(vtable)
lv, rv = left.value(), right.value()
match self.op:
case "SYMBOL_PLUS":
if isinstance(lv, str) or isinstance(rv, str):
raise CentvrionError("Use & for string concatenation, not +")
if isinstance(lv, list) or isinstance(rv, list):
raise CentvrionError("Cannot use + on arrays")
if lv is None and rv is None:
return vtable, ValNul()
result = (lv or 0) + (rv or 0)
return vtable, ValFrac(result) if isinstance(result, Fraction) else ValInt(result)
case "SYMBOL_AT":
if not isinstance(left, ValList) or not isinstance(right, ValList):
raise CentvrionError("@ requires two arrays")
return vtable, ValList(list(lv) + list(rv))
case "SYMBOL_AMPERSAND":
magnvm = "MAGNVM" in vtable["#modules"]
svbnvlla = "SVBNVLLA" in vtable["#modules"]
def _coerce(v):
if v is None: return ""
if isinstance(v, bool): return "VERITAS" if v else "FALSITAS"
if isinstance(v, int): return int_to_num(v, magnvm, svbnvlla)
if isinstance(v, Fraction): return fraction_to_frac(v, magnvm, svbnvlla)
if isinstance(v, list): return make_string(ValList(v), magnvm, svbnvlla)
return v
return vtable, ValStr(_coerce(lv) + _coerce(rv))
case "SYMBOL_MINUS":
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
raise CentvrionError("Cannot use - on strings or arrays")
result = (lv or 0) - (rv or 0)
return vtable, ValFrac(result) if isinstance(result, Fraction) else ValInt(result)
case "SYMBOL_TIMES":
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
raise CentvrionError("Cannot use * on strings or arrays")
result = (lv or 0) * (rv or 0)
return vtable, ValFrac(result) if isinstance(result, Fraction) else ValInt(result)
case "SYMBOL_DIVIDE":
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
raise CentvrionError("Cannot use / on strings or arrays")
if (rv or 0) == 0:
raise CentvrionError("Division by zero")
if isinstance(lv, Fraction) or isinstance(rv, Fraction) or "FRACTIO" in vtable["#modules"]:
return vtable, ValFrac(Fraction(lv or 0) / Fraction(rv or 0))
return vtable, ValInt((lv or 0) // (rv or 0))
case "KEYWORD_RELIQVVM":
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
raise CentvrionError("Cannot use RELIQVVM on strings or arrays")
if (rv or 0) == 0:
raise CentvrionError("Modulo by zero")
if isinstance(lv, Fraction) or isinstance(rv, Fraction) or "FRACTIO" in vtable["#modules"]:
return vtable, ValFrac(Fraction(lv or 0) % Fraction(rv or 0))
return vtable, ValInt((lv or 0) % (rv or 0))
case "KEYWORD_MINVS":
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
raise CentvrionError("Cannot compare strings or arrays with MINVS")
return vtable, ValBool((lv or 0) < (rv or 0))
case "KEYWORD_PLVS":
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
raise CentvrionError("Cannot compare strings or arrays with PLVS")
return vtable, ValBool((lv or 0) > (rv or 0))
case "KEYWORD_HAVD_PLVS":
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
raise CentvrionError("Cannot compare strings or arrays with HAVD_PLVS")
return vtable, ValBool((lv or 0) <= (rv or 0))
case "KEYWORD_HAVD_MINVS":
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
raise CentvrionError("Cannot compare strings or arrays with HAVD_MINVS")
return vtable, ValBool((lv or 0) >= (rv or 0))
case "KEYWORD_EST":
if ((isinstance(left, ValInt) and lv == 0 and isinstance(right, ValNul)) or
(isinstance(left, ValNul) and isinstance(right, ValInt) and rv == 0)):
return vtable, ValBool(True)
return vtable, ValBool(lv == rv)
case "KEYWORD_DISPAR":
if ((isinstance(left, ValInt) and lv == 0 and isinstance(right, ValNul)) or
(isinstance(left, ValNul) and isinstance(right, ValInt) and rv == 0)):
return vtable, ValBool(False)
return vtable, ValBool(lv != rv)
case _:
raise Exception(self.op)
class UnaryMinus(Node):
def __init__(self, expr: Node):
self.expr = expr
def __eq__(self, other):
return type(self) == type(other) and self.expr == other.expr
def __repr__(self):
return f"UnaryMinus({self.expr!r})"
def print(self):
return f"(- {self.expr.print()})"
def _eval(self, vtable):
if "SVBNVLLA" not in vtable["#modules"]:
raise CentvrionError("Cannot use unary minus without 'SVBNVLLA' module")
vtable, val = self.expr.eval(vtable)
if isinstance(val, ValInt):
return vtable, ValInt(-val.value())
elif isinstance(val, ValFrac):
return vtable, ValFrac(-val.value())
else:
raise CentvrionError("Unary minus requires a number")
class UnaryNot(Node):
def __init__(self, expr):
self.expr = expr
def __eq__(self, other):
return type(self) == type(other) and self.expr == other.expr
def __repr__(self):
return f"UnaryNot({self.expr!r})"
def print(self):
return f"(NON {self.expr.print()})"
def _eval(self, vtable):
vtable, val = self.expr.eval(vtable)
if not isinstance(val, ValBool):
raise CentvrionError("NON requires a boolean")
return vtable, ValBool(not val.value())
class ArrayIndex(Node):
def __init__(self, array, index) -> None:
self.array = array
self.index = index
def __eq__(self, other):
return type(self) == type(other) and self.array == other.array and self.index == other.index
def __repr__(self) -> str:
return f"ArrayIndex({self.array!r}, {self.index!r})"
def print(self):
return f"{self.array.print()}[{self.index.print()}]"
def _eval(self, vtable):
vtable, array = self.array.eval(vtable)
vtable, index = self.index.eval(vtable)
if isinstance(array, ValDict):
if not isinstance(index, (ValStr, ValInt)):
raise CentvrionError("Dict key must be a string or integer")
k = index.value()
d = array.value()
if k not in d:
raise CentvrionError(f"Key not found in dict")
return vtable, d[k]
if isinstance(array, ValStr):
if isinstance(index, ValInt):
i = index.value()
elif isinstance(index, ValFrac) and index.value().denominator == 1:
i = index.value().numerator
else:
raise CentvrionError("String index must be a number")
s = array.value()
if i < 1 or i > len(s):
raise CentvrionError(f"Index {i} out of range for string of length {len(s)}")
return vtable, ValStr(s[i - 1])
if not isinstance(array, ValList):
raise CentvrionError("Cannot index a non-array value")
if isinstance(index, ValInt):
i = index.value()
elif isinstance(index, ValFrac) and index.value().denominator == 1:
i = index.value().numerator
else:
raise CentvrionError("Array index must be a number")
lst = array.value()
if i < 1 or i > len(lst):
raise CentvrionError(f"Index {i} out of range for array of length {len(lst)}")
return vtable, lst[i - 1]
def _to_index_int(val):
if isinstance(val, ValInt):
return val.value()
if isinstance(val, ValFrac) and val.value().denominator == 1:
return val.value().numerator
raise CentvrionError("Array index must be a number")
class ArraySlice(Node):
def __init__(self, array, from_index, to_index) -> None:
self.array = array
self.from_index = from_index
self.to_index = to_index
def __eq__(self, other):
return (type(self) == type(other)
and self.array == other.array
and self.from_index == other.from_index
and self.to_index == other.to_index)
def __repr__(self) -> str:
return f"ArraySlice({self.array!r}, {self.from_index!r}, {self.to_index!r})"
def print(self):
return f"{self.array.print()}[{self.from_index.print()} VSQVE {self.to_index.print()}]"
def _eval(self, vtable):
vtable, array = self.array.eval(vtable)
vtable, from_val = self.from_index.eval(vtable)
vtable, to_val = self.to_index.eval(vtable)
if isinstance(array, ValStr):
from_int = _to_index_int(from_val)
to_int = _to_index_int(to_val)
s = array.value()
if from_int < 1 or to_int > len(s) or from_int > to_int:
raise CentvrionError(
f"Slice [{from_int} VSQVE {to_int}] out of range"
f" for string of length {len(s)}"
)
return vtable, ValStr(s[from_int - 1 : to_int])
if not isinstance(array, ValList):
raise CentvrionError("Cannot slice a non-array value")
from_int = _to_index_int(from_val)
to_int = _to_index_int(to_val)
lst = array.value()
if from_int < 1 or to_int > len(lst) or from_int > to_int:
raise CentvrionError(
f"Slice [{from_int} VSQVE {to_int}] out of range"
f" for array of length {len(lst)}"
)
return vtable, ValList(lst[from_int - 1 : to_int])
class SiStatement(Node):
def __init__(self, test, statements, else_part) -> None:
self.test = test
self.statements = statements
self.else_part = else_part
def __eq__(self, other):
return type(self) == type(other) and self.test == other.test and self.statements == other.statements and self.else_part == other.else_part
def __repr__(self) -> str:
test = repr(self.test)
statements = f"statements([{rep_join(self.statements)}])"
if self.else_part is None:
else_part = "None"
else:
else_part = f"statements([{rep_join(self.else_part)}])"
si_string = rep_join([test, statements, else_part])
return f"Si({si_string})"
def print(self):
body = "\n".join(s.print() for s in self.statements)
result = f"SI {self.test.print()} TVNC {{\n{body}\n}}"
if self.else_part:
else_body = "\n".join(s.print() for s in self.else_part)
result += f" ALIVD {{\n{else_body}\n}}"
return result
def _eval(self, vtable):
vtable, cond = self.test.eval(vtable)
if not isinstance(cond, ValBool):
raise CentvrionError("SI condition must be a boolean")
last_val = ValNul()
if cond:
for statement in self.statements:
vtable, last_val = statement.eval(vtable)
if vtable["#return"] is not None:
break
elif self.else_part:
for statement in self.else_part:
vtable, last_val = statement.eval(vtable)
if vtable["#return"] is not None:
break
return vtable, last_val
class DumStatement(Node):
def __init__(self, test: Node, statements: list[Node]) -> None:
self.test = test
self.statements = statements
def __eq__(self, other):
return type(self) == type(other) and self.test == other.test and self.statements == other.statements
def __repr__(self) -> str:
test = repr(self.test)
statements = f"statements([{rep_join(self.statements)}])"
dum_string = rep_join([test, statements])
return f"Dum({dum_string})"
def print(self):
body = "\n".join(s.print() for s in self.statements)
return f"DVM {self.test.print()} FAC {{\n{body}\n}}"
def _eval(self, vtable):
last_val = ValNul()
vtable, cond = self.test.eval(vtable)
if not isinstance(cond, ValBool):
raise CentvrionError("DVM condition must be a boolean")
while not cond:
for statement in self.statements:
vtable, val = statement.eval(vtable)
if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None:
break
last_val = val
if vtable["#break"]:
vtable["#break"] = False
break
if vtable["#continue"]:
vtable["#continue"] = False
vtable, cond = self.test.eval(vtable)
continue
if vtable["#return"] is not None:
break
vtable, cond = self.test.eval(vtable)
return vtable, last_val
class PerStatement(Node):
def __init__(self, data_list, variable_name, statements) -> None:
self.data_list = data_list
self.variable_name = variable_name
self.statements = statements
def __eq__(self, other):
return type(self) == type(other) and self.data_list == other.data_list and self.variable_name == other.variable_name and self.statements == other.statements
@property
def destructure(self):
return isinstance(self.variable_name, list)
def __repr__(self) -> str:
test = repr(self.data_list)
variable_name = repr(self.variable_name)
statements = f"statements([{rep_join(self.statements)}])"
dum_string = rep_join([test, variable_name, statements])
return f"Per({dum_string})"
def print(self):
body = "\n".join(s.print() for s in self.statements)
if self.destructure:
var_str = ", ".join(v.print() for v in self.variable_name)
else:
var_str = self.variable_name.print()
return f"PER {var_str} IN {self.data_list.print()} FAC {{\n{body}\n}}"
def _assign_loop_var(self, vtable, item):
if self.destructure:
if not isinstance(item, ValList):
raise CentvrionError("Cannot destructure non-array value in PER loop")
if len(item.value()) != len(self.variable_name):
raise CentvrionError(
f"Destructuring mismatch: {len(self.variable_name)} targets, {len(item.value())} values")
for id_node, val in zip(self.variable_name, item.value()):
vtable[id_node.name] = val
else:
vtable[self.variable_name.name] = item
def _eval(self, vtable):
vtable, array = self.data_list.eval(vtable)
if isinstance(array, ValDict):
keys = [ValStr(k) if isinstance(k, str) else ValInt(k) for k in array.value().keys()]
array = ValList(keys)
if not isinstance(array, ValList):
raise CentvrionError("PER requires an array or dict")
last_val = ValNul()
for item in array:
self._assign_loop_var(vtable, item)
for statement in self.statements:
vtable, val = statement.eval(vtable)
if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None:
break
last_val = val
if vtable["#break"]:
vtable["#break"] = False
break
if vtable["#continue"]:
vtable["#continue"] = False
continue
if vtable["#return"] is not None:
break
return vtable, last_val
class TemptaStatement(Node):
def __init__(self, try_statements, error_var, catch_statements) -> None:
self.try_statements = try_statements
self.error_var = error_var
self.catch_statements = catch_statements
def __eq__(self, other):
return (type(self) == type(other)
and self.try_statements == other.try_statements
and self.error_var == other.error_var
and self.catch_statements == other.catch_statements)
def __repr__(self) -> str:
try_stmts = f"try([{rep_join(self.try_statements)}])"
catch_stmts = f"catch([{rep_join(self.catch_statements)}])"
tempta_string = rep_join([try_stmts, repr(self.error_var), catch_stmts])
return f"Tempta({tempta_string})"
def print(self):
try_body = "\n".join(s.print() for s in self.try_statements)
catch_body = "\n".join(s.print() for s in self.catch_statements)
return f"TEMPTA {{\n{try_body}\n}} CAPE {self.error_var.print()} {{\n{catch_body}\n}}"
def _eval(self, vtable):
last_val = ValNul()
try:
for statement in self.try_statements:
vtable, last_val = statement.eval(vtable)
if vtable["#return"] is not None or vtable["#break"] or vtable["#continue"]:
return vtable, last_val
except CentvrionError as e:
vtable[self.error_var.name] = ValStr(str(e))
for statement in self.catch_statements:
vtable, last_val = statement.eval(vtable)
if vtable["#return"] is not None or vtable["#break"] or vtable["#continue"]:
return vtable, last_val
return vtable, last_val
class Invoca(Node):
def __init__(self, callee, parameters) -> None:
self.callee = callee
self.parameters = parameters
def __eq__(self, other):
return (type(self) == type(other)
and self.callee == other.callee
and self.parameters == other.parameters)
def __repr__(self) -> str:
parameters_string = f"parameters([{rep_join(self.parameters)}])"
invoca_string = rep_join([self.callee, parameters_string])
return f"Invoca({invoca_string})"
def print(self):
args = ", ".join(p.print() for p in self.parameters)
return f"INVOCA {self.callee.print()} ({args})"
def _eval(self, vtable):
params = [p.eval(vtable)[1] for p in self.parameters]
vtable, func = self.callee.eval(vtable)
if not isinstance(func, ValFunc):
callee_desc = (self.callee.name
if isinstance(self.callee, ID) else "expression")
raise CentvrionError(f"{callee_desc} is not a function")
if len(params) != len(func.params):
callee_desc = (self.callee.name
if isinstance(self.callee, ID) else "FVNCTIO")
raise CentvrionError(
f"{callee_desc} expects {len(func.params)} argument(s), got {len(params)}"
)
func_vtable = vtable.copy()
for i, param in enumerate(func.params):
func_vtable[param.name] = params[i]
func_vtable["#return"] = None
for statement in func.body:
func_vtable, _ = statement.eval(func_vtable)
if func_vtable["#return"] is not None:
return vtable, func_vtable["#return"]
return vtable, ValNul()
class BuiltIn(Node):
def __init__(self, builtin: str, parameters: list[Node]) -> None:
self.builtin = builtin
self.parameters = parameters
def __eq__(self, other):
return type(self) == type(other) and self.builtin == other.builtin and self.parameters == other.parameters
def __repr__(self) -> str:
parameter_string = f"parameters([{rep_join(self.parameters)}])"
builtin_string = rep_join([self.builtin, parameter_string])
return f"Builtin({builtin_string})"
def print(self):
args = ", ".join(p.print() for p in self.parameters)
return f"{self.builtin}({args})"
def _eval(self, vtable):
params = [p.eval(vtable)[1] for p in self.parameters]
magnvm = "MAGNVM" in vtable["#modules"]
svbnvlla = "SVBNVLLA" in vtable["#modules"]
match self.builtin:
case "AVDI_NVMERVS":
raw = input()
try:
return vtable, ValInt(num_to_int(raw, magnvm, svbnvlla))
except ValueError:
raise CentvrionError(f"Invalid numeral input: {raw!r}")
case "AVDI":
return vtable, ValStr(input())
case "NVMERVS":
if len(params) != 1:
raise CentvrionError("NVMERVS takes exactly I argument")
val = params[0]
if not isinstance(val, ValStr):
raise CentvrionError(f"NVMERVS expects a string, got {type(val).__name__}")
return vtable, ValInt(num_to_int(val.value(), magnvm, svbnvlla))
case "DIC":
print_string = ' '.join(
make_string(i, magnvm, svbnvlla) for i in params
)
print(print_string)
return vtable, ValStr(print_string)
case "ERVMPE":
vtable["#break"] = True
return vtable, ValNul()
case "FORTVITVS_NVMERVS":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'FORTVITVS_NVMERVS' without module 'FORS'")
a, b = params[0].value(), params[1].value()
if not isinstance(a, int) or not isinstance(b, int):
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(_cent_rng.randint(a, b))
case "FORTVITA_ELECTIO":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'FORTVITA_ELECTIO' without module 'FORS'")
if not isinstance(params[0], ValList):
raise CentvrionError("FORTVITA_ELECTIO requires an array")
lst = params[0].value()
if len(lst) == 0:
raise CentvrionError("FORTVITA_ELECTIO: cannot select from an empty array")
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")
_cent_rng.seed(seed)
return vtable, ValNul()
case "DECIMATIO":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'DECIMATIO' without module 'FORS'")
if not isinstance(params[0], ValList):
raise CentvrionError("DECIMATIO requires an array")
arr = list(params[0].value())
to_remove = len(arr) // 10
for _ in range(to_remove):
arr.pop(_cent_rng.randint(0, len(arr) - 1))
return vtable, ValList(arr)
case "SENATVS":
if len(params) == 1 and isinstance(params[0], ValList):
items = params[0].value()
else:
items = params
for p in items:
if not isinstance(p, ValBool):
raise CentvrionError("SENATVS requires boolean arguments")
true_count = sum(1 for p in items if p.value())
return vtable, ValBool(true_count > len(items) / 2)
case "LONGITVDO":
if isinstance(params[0], (ValList, ValStr, ValDict)):
return vtable, ValInt(len(params[0].value()))
raise CentvrionError("LONGITVDO requires an array, string, or dict")
case "LITTERA":
if len(params) != 1:
raise CentvrionError("LITTERA takes exactly I argument")
return vtable, ValStr(make_string(params[0], magnvm, svbnvlla))
case "MAIVSCVLA":
if len(params) != 1:
raise CentvrionError("MAIVSCVLA takes exactly I argument")
val = params[0]
if not isinstance(val, ValStr):
raise CentvrionError(f"MAIVSCVLA expects a string, got {type(val).__name__}")
s = val.value()
return vtable, ValStr("".join(chr(ord(c) - 32) if "a" <= c <= "z" else c for c in s))
case "MINVSCVLA":
if len(params) != 1:
raise CentvrionError("MINVSCVLA takes exactly I argument")
val = params[0]
if not isinstance(val, ValStr):
raise CentvrionError(f"MINVSCVLA expects a string, got {type(val).__name__}")
s = val.value()
return vtable, ValStr("".join(chr(ord(c) + 32) if "A" <= c <= "Z" else c for c in s))
case "CLAVES":
if not isinstance(params[0], ValDict):
raise CentvrionError("CLAVES requires a dict")
keys = [ValStr(k) if isinstance(k, str) else ValInt(k) for k in params[0].value().keys()]
return vtable, ValList(keys)
case "EVERRE":
print("\033[2J\033[H", end="", flush=True)
return vtable, ValNul()
case "ORDINA":
if not isinstance(params[0], ValList):
raise CentvrionError("ORDINA requires an array")
items = list(params[0].value())
if not items:
return vtable, ValList([])
all_numeric = all(isinstance(i, (ValInt, ValFrac)) for i in items)
all_string = all(isinstance(i, ValStr) for i in items)
if not (all_numeric or all_string):
raise CentvrionError("ORDINA requires all elements to be numbers or all strings")
return vtable, ValList(sorted(items, key=lambda v: v.value()))
case "TYPVS":
type_map = {
ValInt: "NVMERVS", ValStr: "LITTERA", ValBool: "VERAX",
ValList: "CATALOGVS", ValFrac: "FRACTIO", ValDict: "TABVLA",
ValFunc: "FVNCTIO", ValNul: "NVLLVS",
}
return vtable, ValStr(type_map[type(params[0])])
case "DORMI":
v = params[0]
if isinstance(v, ValNul):
seconds = 0
elif isinstance(v, ValInt):
seconds = v.value()
elif isinstance(v, ValFrac):
seconds = float(v.value())
else:
raise CentvrionError("DORMI requires a number or NVLLVS")
time.sleep(seconds)
return vtable, ValNul()
case "LEGE":
if "SCRIPTA" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'LEGE' without module 'SCRIPTA'")
path = make_string(params[0], magnvm, svbnvlla)
with open(path, "r") as f:
return vtable, ValStr(f.read())
case "SCRIBE":
if "SCRIPTA" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'SCRIBE' without module 'SCRIPTA'")
path = make_string(params[0], magnvm, svbnvlla)
content = make_string(params[1], magnvm, svbnvlla)
with open(path, "w") as f:
f.write(content)
return vtable, ValNul()
case "ADIVNGE":
if "SCRIPTA" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'ADIVNGE' without module 'SCRIPTA'")
path = make_string(params[0], magnvm, svbnvlla)
content = make_string(params[1], magnvm, svbnvlla)
with open(path, "a") as f:
f.write(content)
return vtable, ValNul()
case "QVAERE":
pattern = params[0]
text = params[1]
if not isinstance(pattern, ValStr) or not isinstance(text, ValStr):
raise CentvrionError("QVAERE requires two strings")
try:
matches = [
ValStr(m.group(0))
for m in re.finditer(_romanize_pattern(pattern.value()), text.value())
]
except re.error as e:
raise CentvrionError(f"Invalid regex: {e}")
return vtable, ValList(matches)
case "SVBSTITVE":
pattern = params[0]
replacement = params[1]
text = params[2]
if not isinstance(pattern, ValStr) or not isinstance(replacement, ValStr) or not isinstance(text, ValStr):
raise CentvrionError("SVBSTITVE requires three strings")
try:
result = re.sub(
_romanize_pattern(pattern.value()),
_romanize_replacement(replacement.value()),
text.value()
)
except re.error as e:
raise CentvrionError(f"Invalid regex: {e}")
return vtable, ValStr(result)
case "SCINDE":
string = params[0]
delimiter = params[1]
if not isinstance(string, ValStr) or not isinstance(delimiter, ValStr):
raise CentvrionError("SCINDE requires two strings")
s = string.value()
d = delimiter.value()
if d == "":
parts = [ValStr(c) for c in s]
else:
parts = [ValStr(p) for p in s.split(d)]
return vtable, ValList(parts)
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)
class Program(BaseBox):
def __init__(self, module_calls: list[ModuleCall], statements: list[Node]) -> None:
self.modules = module_calls
self.statements = statements
def __eq__(self, other):
return type(self) == type(other) and self.modules == other.modules and self.statements == other.statements
def __repr__(self) -> str:
modules_string = f"modules([{rep_join(self.modules)}])"
statements_string = f"statements([{rep_join(self.statements)}])"
return f"{modules_string},\n{statements_string}"
def print(self):
stmts = "\n".join(s.print() for s in self.statements)
if self.modules:
mods = "\n".join(m.print() for m in self.modules)
return f"{mods}\n\n{stmts}"
return stmts
def eval(self, *_):
vtable = {
"#break": False,
"#continue": False,
"#return": None,
"#modules": [m.module_name for m in self.modules],
"#routes": [],
}
last_val = ValNul()
for statement in self.statements:
vtable, last_val = statement.eval(vtable)
if vtable["#return"] is not None:
return vtable["#return"]
return last_val