1317 lines
42 KiB
Python
1317 lines
42 KiB
Python
import re
|
|
import random
|
|
import time
|
|
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
|
|
|
|
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}]"
|
|
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}")
|
|
|
|
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 + 1)])
|
|
|
|
|
|
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):
|
|
if self.quote == "'":
|
|
return f"'{self.value}'"
|
|
escaped = self.value.replace('{', '{{').replace('}', '}}')
|
|
return f'"{escaped}"'
|
|
|
|
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()
|
|
|
|
|
|
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 isinstance(target, ValDict):
|
|
if not isinstance(index, (ValStr, ValInt)):
|
|
raise CentvrionError("Dict key must be a string or integer")
|
|
d = dict(target.value())
|
|
d[index.value()] = val
|
|
vtable[self.id.name] = ValDict(d)
|
|
return vtable, ValNul()
|
|
if not isinstance(target, ValList):
|
|
raise CentvrionError(f"{self.id.name} is not an array or dict")
|
|
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 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):
|
|
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 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
|
|
|
|
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()} FAC {{\n{body}\n}}"
|
|
|
|
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")
|
|
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 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 "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(random.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[random.randint(0, len(lst) - 1)]
|
|
case "SEMEN":
|
|
if "FORS" not in vtable["#modules"]:
|
|
raise CentvrionError("Cannot use 'SEMEN' without module 'FORS'")
|
|
seed = params[0].value()
|
|
if not isinstance(seed, int):
|
|
raise CentvrionError("SEMEN requires an integer seed")
|
|
random.seed(seed)
|
|
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(random.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 "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 _:
|
|
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
|