898 lines
27 KiB
Python
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
|