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_RELIQVVM": "RELIQVVM", "KEYWORD_EST": "EST", "KEYWORD_DISPAR": "DISPAR", "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) -> 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 ]) 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) -> 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}]" 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) -> 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) -> 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, 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 return vtable, ValList([ValInt(i) for i in range(from_int, to_int)]) 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: 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() 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: 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 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): 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_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_EST": return vtable, ValBool(lv == rv) case "KEYWORD_DISPAR": 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: 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 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] 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" ALVID {{\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()} FACE {{\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 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"] 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 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: 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 "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 isinstance(params[0], (ValList, ValStr)): return vtable, ValInt(len(params[0].value())) raise CentvrionError("LONGITVDO requires an array or string") case "EVERRO": print("\033[2J\033[H", end="", flush=True) 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], } 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