Files
centvrion/centvrion/ast_nodes.py
2026-04-01 14:45:48 +02:00

898 lines
27 KiB
Python

import re
import random
from fractions import Fraction
from rply.token import BaseBox
from centvrion.errors import CentvrionError
from centvrion.values import Val, ValInt, ValStr, ValBool, ValList, ValNul, ValFunc, ValFrac
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": "&",
"KEYWORD_EST": "EST", "KEYWORD_MINVS": "MINVS",
"KEYWORD_PLVS": "PLVS", "KEYWORD_ET": "ET", "KEYWORD_AVT": "AVT",
}
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):
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
])
return thousands + int_to_num(n % 1000, m, s)
else:
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):
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 "VERVS" if val.value() else "FALSVS"
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}]"
else:
raise CentvrionError(f"Cannot display {val!r}")
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):
if f < 0:
if not svbnvlla:
raise CentvrionError("Cannot display negative fractions 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
class ExpressionStatement(Node):
def __init__(self, expression) -> None:
self.expression = expression
def __eq__(self, other):
return type(self) == type(other) and self.expression == other.expression
def __repr__(self) -> str:
return self.expression.__repr__()
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) -> None:
self.from_value = from_value
self.to_value = to_value
def __eq__(self, other):
return type(self) == type(other) and self.from_value == other.from_value and self.to_value == other.to_value
def __repr__(self) -> str:
content_string = rep_join([self.from_value, self.to_value])
return f"RangeArray([{content_string}])"
def print(self):
return f"[{self.from_value.print()} VSQVE {self.to_value.print()}]"
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) or not isinstance(to_val, ValInt):
raise CentvrionError("Range bounds must be numbers")
return vtable, ValList([ValInt(i) for i in range(from_val.value(), to_val.value())])
class String(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"String({self.value})"
def print(self):
return f'"{self.value}"'
def _eval(self, vtable):
return vtable, ValStr(self.value)
class Numeral(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"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) -> 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) -> 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()
class DesignaIndex(Node):
def __init__(self, variable: ID, index, value) -> None:
self.id = variable
self.index = index
self.value = value
def __eq__(self, other):
return type(self) == type(other) and self.id == other.id and self.index == other.index and self.value == other.value
def __repr__(self) -> str:
return f"DesignaIndex({self.id!r}, {self.index!r}, {self.value!r})"
def print(self):
return f"DESIGNA {self.id.print()}[{self.index.print()}] VT {self.value.print()}"
def _eval(self, vtable):
vtable, index = self.index.eval(vtable)
vtable, val = self.value.eval(vtable)
if self.id.name not in vtable:
raise CentvrionError(f"Undefined variable: {self.id.name}")
target = vtable[self.id.name]
if not isinstance(target, ValList):
raise CentvrionError(f"{self.id.name} is not an array")
i = index.value()
lst = list(target.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] = val
vtable[self.id.name] = ValList(lst)
return vtable, ValNul()
class Defini(Node):
def __init__(self, name, parameters, statements) -> 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 Redi(Node):
def __init__(self, values) -> 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 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, right, op) -> 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):
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_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_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_EST":
return vtable, ValBool(lv == rv)
case "KEYWORD_ET":
return vtable, ValBool(bool(left) and bool(right))
case "KEYWORD_AVT":
return vtable, ValBool(bool(left) or bool(right))
case _:
raise Exception(self.op)
class UnaryMinus(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"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 not isinstance(val, ValInt):
raise CentvrionError("Unary minus requires a number")
return vtable, ValInt(-val.value())
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)
return vtable, ValBool(not bool(val))
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 not isinstance(array, ValList):
raise CentvrionError("Cannot index a non-array value")
if not isinstance(index, ValInt):
raise CentvrionError("Array index must be a number")
i = index.value()
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]
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)}])"
else_part = f"statements([{rep_join(self.else_part) if self.else_part else ''}])"
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" ALVID {{\n{else_body}\n}}"
return result
def _eval(self, vtable):
vtable, cond = self.test.eval(vtable)
last_val = ValNul()
if cond:
for statement in self.statements:
vtable, last_val = statement.eval(vtable)
elif self.else_part:
for statement in self.else_part:
vtable, last_val = statement.eval(vtable)
return vtable, last_val
class DumStatement(Node):
def __init__(self, test, statements) -> 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()} FACE {{\n{body}\n}}"
def _eval(self, vtable):
last_val = ValNul()
vtable, cond = self.test.eval(vtable)
while not cond:
for statement in self.statements:
vtable, val = statement.eval(vtable)
if vtable["#break"]:
break
last_val = val
if vtable["#break"]:
vtable["#break"] = False
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
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)
return f"PER {self.variable_name.print()} IN {self.data_list.print()} FACE {{\n{body}\n}}"
def _eval(self, vtable):
vtable, array = self.data_list.eval(vtable)
if not isinstance(array, ValList):
raise CentvrionError("PER requires an array")
variable_name = self.variable_name.name
last_val = ValNul()
for item in array:
vtable[variable_name] = item
for statement in self.statements:
vtable, val = statement.eval(vtable)
if vtable["#break"]:
break
last_val = val
if vtable["#break"]:
vtable["#break"] = False
break
return vtable, last_val
class Invoca(Node):
def __init__(self, name, parameters) -> None:
self.name = name
self.parameters = parameters
def __eq__(self, other):
return type(self) == type(other) and self.name == other.name and self.parameters == other.parameters
def __repr__(self) -> str:
parameters_string = f"parameters([{rep_join(self.parameters)}])"
invoca_string = rep_join([self.name, parameters_string])
return f"Invoca({invoca_string})"
def print(self):
args = ", ".join(p.print() for p in self.parameters)
return f"INVOCA {self.name.print()} ({args})"
def _eval(self, vtable):
params = [p.eval(vtable)[1] for p in self.parameters]
if self.name.name not in vtable:
raise CentvrionError(f"Undefined function: {self.name.name}")
func = vtable[self.name.name]
if not isinstance(func, ValFunc):
raise CentvrionError(f"{self.name.name} is not a function")
if len(params) != len(func.params):
raise CentvrionError(
f"{self.name.name} 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, parameters) -> 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 "DICE":
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 "FORTIS_NVMERVS":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'FORTIS_NVMERVS' without module 'FORS'")
a, b = params[0].value(), params[1].value()
if not isinstance(a, int) or not isinstance(b, int):
raise CentvrionError("FORTIS_NVMERVS requires two numbers")
if a > b:
raise CentvrionError(f"FORTIS_NVMERVS: first argument ({a}) must be ≤ second ({b})")
return vtable, ValInt(random.randint(a, b))
case "FORTIS_ELECTIONIS":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'FORTIS_ELECTIONIS' without module 'FORS'")
if not isinstance(params[0], ValList):
raise CentvrionError("FORTIS_ELECTIONIS requires an array")
lst = params[0].value()
if len(lst) == 0:
raise CentvrionError("FORTIS_ELECTIONIS: cannot select from an empty array")
return vtable, lst[random.randint(0, len(lst) - 1)]
case "LONGITVDO":
if not isinstance(params[0], ValList):
raise CentvrionError("LONGITVDO requires an array")
return vtable, ValInt(len(params[0].value()))
case _:
raise NotImplementedError(self.builtin)
class Program(BaseBox):
def __init__(self, module_calls: list[ModuleCall], statements) -> 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,
"#return": None,
"#modules": [m.module_name for m in self.modules],
}
last_val = ValNul()
for statement in self.statements:
vtable, last_val = statement.eval(vtable)
return last_val