🐐 Reviving this old project. Mainly adding tests and fixing bugs.

This commit is contained in:
2026-03-31 18:25:20 +02:00
parent 88d7f0ed69
commit e845cb62c1
20 changed files with 1502 additions and 1090 deletions

View File

@@ -3,458 +3,506 @@ import random
from rply.token import BaseBox
from centvrion.values import Val, ValInt, ValStr, ValBool, ValList, ValNul, ValFunc
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
"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 ')
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"
if format_string != "":
format_string = f"\n {format_string}\n"
return format_string
return format_string
def single_num_to_int(i, m):
if i[-1] == "_":
if not m:
raise Exception(
"Cannot calculate numbers above 3999 without 'MAGNVM' module"
)
if i[-1] == "_":
if not m:
raise ValueError(
"Cannot calculate numbers above 3999 without 'MAGNVM' module"
)
if i[0] != "I":
raise Exception(
"Cannot use 'I' with thousands operator, use 'M' instead"
)
if i[0] != "I":
raise ValueError(
"Cannot use 'I' with thousands operator, use 'M' instead"
)
return 1000 * single_num_to_int(i[:-1], m)
else:
return NUMERALS[i]
return 1000 * single_num_to_int(i[:-1], m)
else:
return NUMERALS[i]
def num_to_int(n, m):
chars = re.findall(r"[IVXLCDM]_*", n)
if ''.join(chars) != n:
raise Exception("Invalid numeral", n)
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 Exception(n, "is not a valid roman numeral")
chars = re.findall(r"[IVXLCDM]_*", n)
if ''.join(chars) != n:
raise ValueError("Invalid numeral", n)
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 ValueError(n, "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 Exception(n, "is not a valid roman numeral")
if new_nums != nums:
nums = [i for i in new_nums if i != 0]
new_nums = nums
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:
break
raise ValueError(n, "is not a valid roman numeral")
if nums != sorted(nums)[::-1]:
raise Exception(n, "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
return sum(nums)
if nums != sorted(nums)[::-1]:
raise Exception(n, "is not a valid roman numeral")
return sum(nums)
def int_to_num(n, m):
if n > 3999:
if not m:
raise Exception(
"Cannot display numbers above 3999 without 'MAGNVM' module"
)
thousands_chars = re.findall(r"[IVXLCDM]_*", int_to_num(n//1000, m))
thousands = ''.join([
"M" if i == "I" else i+"_"
for i in thousands_chars
])
if n > 3999:
if not m:
raise ValueError(
"Cannot display numbers above 3999 without 'MAGNVM' module"
)
thousands_chars = re.findall(r"[IVXLCDM]_*", int_to_num(n//1000, m))
thousands = ''.join([
"M" if i == "I" else i+"_"
for i in thousands_chars
])
return thousands + int_to_num(n % 1000, m)
else:
nums = []
while n > 0:
for num, i in list(NUMERALS.items())[::-1]:
if n >= i:
nums.append(num)
n -= i
break
return thousands + int_to_num(n % 1000, m)
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)
return ''.join(nums)
def make_string(n, m):
if isinstance(n, str):
return n
elif isinstance(n, int):
return int_to_num(n, m)
elif isinstance(n, list):
return f"[{' '.join([make_string(i, m) for i in n])}]"
else:
raise Exception(n)
def make_string(val, magnvm=False):
if isinstance(val, ValStr):
return val.value()
elif isinstance(val, ValInt):
return int_to_num(val.value(), magnvm)
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) for i in val.value())
return f"[{inner}]"
else:
raise TypeError(f"Cannot display {val!r}")
class ExpressionStatement(BaseBox):
def __init__(self, expression) -> None:
self.expression = expression
def __repr__(self) -> str:
return self.expression.__repr__()
class Node(BaseBox):
def eval(self, vtable):
return self._eval(vtable.copy())
def eval(self, vtable, ftable, modules):
self.expression.eval(vtable.copy(), ftable.copy(), modules)
return vtable, ftable
def _eval(self, vtable):
raise NotImplementedError
class DataArray(BaseBox):
def __init__(self, content) -> None:
self.content = content
def __repr__(self) -> str:
content_string = rep_join(self.content)
return f"Array([{content_string}])"
class ExpressionStatement(Node):
def __init__(self, expression) -> None:
self.expression = expression
def eval(self, vtable, ftable, modules):
content = [i.eval(vtable, ftable, modules) for i in self.content]
return content
def __repr__(self) -> str:
return self.expression.__repr__()
class DataRangeArray(BaseBox):
def __init__(self, from_value, to_value) -> None:
self.from_value = from_value
self.to_value = to_value
def _eval(self, vtable):
return self.expression.eval(vtable)
def __repr__(self) -> str:
content_string = rep_join([self.from_value, self.to_value])
return f"RangeArray([{content_string}])"
def eval(self, *_):
content = list(range(self.from_value.eval(), self.to_value.eval()))
return content
class DataArray(Node):
def __init__(self, content) -> None:
self.content = content
class String(BaseBox):
def __init__(self, value) -> None:
self.value = value
def __repr__(self) -> str:
content_string = rep_join(self.content)
return f"Array([{content_string}])"
def __repr__(self):
return f"String({self.value})"
def _eval(self, vtable):
vals = []
for item in self.content:
vtable, val = item.eval(vtable)
vals.append(val)
return vtable, ValList(vals)
def eval(self, *_):
return self.value
class Numeral(BaseBox):
def __init__(self, value) -> None:
self.value = value
class DataRangeArray(Node):
def __init__(self, from_value, to_value) -> None:
self.from_value = from_value
self.to_value = to_value
def __repr__(self):
return f"Numeral({self.value})"
def __repr__(self) -> str:
content_string = rep_join([self.from_value, self.to_value])
return f"RangeArray([{content_string}])"
def eval(self, *args):
return num_to_int(self.value, "MAGNVM" in args[2])
def _eval(self, vtable):
vtable, from_val = self.from_value.eval(vtable)
vtable, to_val = self.to_value.eval(vtable)
return vtable, ValList([ValInt(i) for i in range(from_val.value(), to_val.value())])
class Bool(BaseBox):
def __init__(self, value) -> None:
self.value = value
def __repr__(self):
return f"Bool({self.value})"
class String(Node):
def __init__(self, value) -> None:
self.value = value
def __repr__(self):
return f"String({self.value})"
def _eval(self, vtable):
return vtable, ValStr(self.value)
class Numeral(Node):
def __init__(self, value) -> None:
self.value = value
def __repr__(self):
return f"Numeral({self.value})"
def _eval(self, vtable):
return vtable, ValInt(num_to_int(self.value, "MAGNVM" in vtable["#modules"]))
class Bool(Node):
def __init__(self, value) -> None:
self.value = value
def __repr__(self):
return f"Bool({self.value})"
def _eval(self, vtable):
return vtable, ValBool(self.value)
def eval(self, *_):
return self.value
class ModuleCall(BaseBox):
def __init__(self, module_name) -> None:
self.module_name = module_name
def __init__(self, module_name) -> None:
self.module_name = module_name
def __repr__(self) -> str:
return f"{self.module_name}"
def __repr__(self) -> str:
return f"{self.module_name}"
class ID(BaseBox):
def __init__(self, name: str) -> None:
self.name = name
def __repr__(self) -> str:
return f"ID({self.name})"
class ID(Node):
def __init__(self, name: str) -> None:
self.name = name
def eval(self, vtable, *_):
return vtable[self.name]
def __repr__(self) -> str:
return f"ID({self.name})"
class Designa(BaseBox):
def __init__(self, variable: ID, value) -> None:
self.id = variable
self.value = value
def _eval(self, vtable):
return vtable, vtable[self.name]
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 eval(self, vtable, ftable, modules):
vtable[self.id.name] = self.value.eval(
vtable.copy(), ftable.copy(), modules
class Designa(Node):
def __init__(self, variable: ID, value) -> None:
self.id = variable
self.value = 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 _eval(self, vtable):
vtable, val = self.value.eval(vtable)
vtable[self.id.name] = val
return vtable, ValNul()
class Defini(Node):
def __init__(self, name, parameters, statements) -> None:
self.name = name
self.parameters = parameters
self.statements = 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 _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 __repr__(self) -> str:
values_string = f"[{rep_join(self.values)}]"
return f"Redi({values_string})"
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 __repr__(self) -> str:
return "Erumpe()"
def _eval(self, vtable):
vtable["#break"] = True
return vtable, ValNul()
class Nullus(Node):
def __repr__(self) -> str:
return "Nullus()"
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 __repr__(self) -> str:
binop_string = rep_join([self.left, self.right, self.op])
return f"BinOp({binop_string})"
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":
return vtable, ValInt(lv + rv)
case "SYMBOL_MINUS":
return vtable, ValInt(lv - rv)
case "SYMBOL_TIMES":
return vtable, ValInt(lv * rv)
case "SYMBOL_DIVIDE":
# TODO: Fractio
return vtable, ValInt(lv // rv)
case "KEYWORD_MINVS":
return vtable, ValBool(lv < rv)
case "KEYWORD_PLVS":
return vtable, ValBool(lv > rv)
case "KEYWORD_EST":
return vtable, ValBool(lv == rv)
case "KEYWORD_ET":
return vtable, ValBool(bool(lv) and bool(rv))
case "KEYWORD_AVT":
return vtable, ValBool(bool(lv) or bool(rv))
case _:
raise Exception(self.op)
class SiStatement(Node):
def __init__(self, test, statements, else_part) -> None:
self.test = test
self.statements = statements
self.else_part = 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 _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 __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 _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 __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 _eval(self, vtable):
vtable, array = self.data_list.eval(vtable)
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 __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 _eval(self, vtable):
params = [p.eval(vtable)[1] for p in self.parameters]
func = vtable[self.name.name]
if len(params) != len(func.params):
raise TypeError(
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 __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 _eval(self, vtable):
params = [p.eval(vtable)[1] for p in self.parameters]
magnvm = "MAGNVM" in vtable["#modules"]
match self.builtin:
case "AVDI_NVMERVS":
return vtable, ValInt(num_to_int(input(), magnvm))
case "AVDI":
return vtable, ValStr(input())
case "DICE":
print_string = ' '.join(
make_string(i, magnvm) for i in params
)
return vtable, ftable
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 ValueError(
"Cannot use 'FORTIS_NVMERVS' without module 'FORS'"
)
return vtable, ValInt(random.randint(params[0].value(), params[1].value()))
case "FORTIS_ELECTIONIS":
if "FORS" not in vtable["#modules"]:
raise ValueError(
"Cannot use 'FORTIS_ELECTIONIS' without module 'FORS'"
)
return vtable, params[0].value()[random.randint(0, len(params[0].value()) - 1)]
case "LONGITVDO":
return vtable, ValInt(len(params[0].value()))
case _:
raise NotImplementedError(self.builtin)
class Defini(BaseBox):
def __init__(self, name, parameters, statements) -> None:
self.name = name
self.parameters = parameters
self.statements = 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 eval(self, vtable, ftable, _):
ftable[self.name.name] = (
self.parameters, self.statements
)
return vtable, ftable
class Redi(BaseBox):
def __init__(self, values) -> None:
self.values = values
def __repr__(self) -> str:
values_string = f"[{rep_join(self.values)}]"
return f"Redi({values_string})"
def eval(self, vtable, ftable, modules):
values = [
i.eval(vtable.copy(), ftable.copy(), modules)
for i in self.values
]
if len(values) == 1:
vtable["REDI"] = values[0]
else:
vtable["REDI"] = values
return vtable, ftable
class Erumpe(BaseBox):
def __repr__(self) -> str:
return "Erumpe()"
def eval(self, vtable, ftable, _):
vtable["ERVMPE"] = True
return vtable, ftable
class Nullus(BaseBox):
def __repr__(self) -> str:
return "Nullus()"
def eval(self, *_):
return 0
class BinOp(BaseBox):
def __init__(self, left, right, op) -> None:
self.left = left
self.right = right
self.op = op
def __repr__(self) -> str:
binop_string = rep_join([self.left, self.right, self.op])
return f"BinOp({binop_string})"
def eval(self, vtable, ftable, modules):
left = self.left.eval(vtable.copy(), ftable.copy(), modules)
right = self.right.eval(vtable.copy(), ftable.copy(), modules)
match self.op:
case "SYMBOL_PLUS":
return left + right
case "SYMBOL_MINUS":
return left - right
case "SYMBOL_TIMES":
return left * right
case "SYMBOL_DIVIDE":
# TODO: Fractio
return left // right
case "KEYWORD_MINVS":
return left < right
case "KEYWORD_PLVS":
return left > right
case "KEYWORD_EST":
return left == right
case _:
raise Exception(self.op)
class SiStatement(BaseBox):
def __init__(self, test, statements, else_part) -> None:
self.test = test
self.statements = statements
self.else_part = 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)}])"
si_string = rep_join([test, statements, else_part])
return f"Si({si_string})"
def eval(self, vtable, ftable, modules):
if self.test.eval(vtable, ftable, modules):
for statement in self.statements:
vtable, ftable = statement.eval(
vtable, ftable, modules
)
else:
for statement in self.else_part:
vtable, ftable = statement.eval(
vtable, ftable, modules
)
return vtable, ftable
class DumStatement(BaseBox):
def __init__(self, test, statements) -> None:
self.test = test
self.statements = 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 eval(self, vtable, ftable, modules):
while not self.test.eval(vtable, ftable, modules):
for statement in self.statements:
vtable, ftable = statement.eval(
vtable, ftable, modules
)
if vtable["ERVMPE"]:
break
if vtable["ERVMPE"]:
vtable["ERVMPE"] = False
break
return vtable, ftable
class PerStatement(BaseBox):
def __init__(self, data_list, variable_name, statements) -> None:
self.data_list = data_list
self.variable_name = variable_name
self.statements = 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 eval(self, vtable, ftable, modules):
data_array = self.data_list.eval(vtable, ftable, modules)
variable_name = self.variable_name.name
for i in data_array:
vtable[variable_name] = i
for statement in self.statements:
vtable, ftable = statement.eval(
vtable, ftable, modules
)
if vtable["ERVMPE"]:
break
if vtable["ERVMPE"]:
vtable["ERVMPE"] = False
break
return vtable, ftable
class Invoca(BaseBox):
def __init__(self, name, parameters) -> None:
self.name = name
self.parameters = 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 eval(self, vtable, ftable, modules):
parameters = [
i.eval(vtable.copy(), ftable.copy(), modules)
for i in self.parameters
]
vtable_copy = vtable.copy()
function = ftable[self.name.name]
for i, parameter in enumerate(function[0]):
vtable_copy[parameter.name] = parameters[i]
vtable_copy["REDI"] = None
for statement in function[1]:
statement.eval(vtable_copy, ftable, modules)
if vtable_copy["REDI"] is not None:
return vtable_copy["REDI"]
class BuiltIn(BaseBox):
def __init__(self, builtin, parameters) -> None:
self.builtin = builtin
self.parameters = 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 eval(self, vtable, ftable, modules):
parameters = [
i.eval(vtable.copy(), ftable.copy(), modules)
for i in self.parameters
]
match self.builtin:
case "AVDI_NVMERVS":
return num_to_int(input(), "MAGNVM" in modules)
case "DICE":
print(' '.join(
make_string(i, "MAGNVM" in modules) for i in parameters)
)
return None
case "ERVMPE":
vtable["ERVMPE"] = True
return None
case "FORTIS_NVMERVS":
if "FORS" not in modules:
raise Exception(
"Cannot use 'FORTIS_NVMERVS' without module 'FORS'"
)
return random.randint(parameters[0], parameters[1])
case _:
raise Exception(self.builtin)
class Program(BaseBox):
def __init__(self, module_calls: list[ModuleCall], statements) -> None:
self.modules = module_calls
self.statements = statements
def __init__(self, module_calls: list[ModuleCall], statements) -> None:
self.modules = module_calls
self.statements = 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 __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 eval(self, *_):
vtable = {"ERVMPE": False}
ftable = {}
modules = [module.module_name for module in self.modules]
for statement in self.statements:
vtable, ftable = statement.eval(vtable, ftable, modules)
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

View File

@@ -3,88 +3,92 @@ from rply import LexerGenerator
valid_characters = '|'.join(list("abcdefghiklmnopqrstvxyz_"))
keyword_tokens = [("KEYWORD_"+i, i) for i in [
"ALVID",
"DEFINI",
"DESIGNA",
"DONICVM",
"DVM",
"ERVMPE",
"EST",
"FACE",
"FALSITAS",
"INVOCA",
"IN",
"MINVS",
"NVLLVS",
"PER",
"PLVS",
"REDI",
"SI",
"TVNC",
"VSQVE",
"VT",
"VERITAS",
"CVM"
"ALVID",
"AVT",
"DEFINI",
"DESIGNA",
"DONICVM",
"DVM",
"ERVMPE",
"EST",
"ET",
"FACE",
"FALSITAS",
"INVOCA",
"IN",
"MINVS",
"NVLLVS",
"PER",
"PLVS",
"REDI",
"SI",
"TVNC",
"VSQVE",
"VT",
"VERITAS",
"CVM"
]]
builtin_tokens = [("BUILTIN", i) for i in [
"AVDI_NVMERVS",
"AVDI",
"DICE",
"FORTIS_NVMERVS",
"FORTIS_ELECTIONIS",
"LONGITVDO"
"AVDI_NVMERVS",
"AVDI",
"DICE",
"FORTIS_NVMERVS",
"FORTIS_ELECTIONIS",
"LONGITVDO"
]]
data_tokens = [
("DATA_STRING", r"\".*?\""),
("DATA_NUMERAL", r"[IVXLCDM]+")
("DATA_STRING", r"(\".*?\"|'.*?')"),
("DATA_NUMERAL", r"[IVXLCDM][IVXLCDM_]*")
]
module_tokens = [("MODULE", i) for i in [
"FORS",
"FRACTIO",
"MAGNVM",
"SVBNVLLA"
"FORS",
"FRACTIO",
"MAGNVM",
"SVBNVLLA"
]]
symbol_tokens = [
("SYMBOL_LPARENS", r"\("),
("SYMBOL_RPARENS", r"\)"),
("SYMBOL_LBRACKET", r"\["),
("SYMBOL_RBRACKET", r"\]"),
("SYMBOL_LCURL", r"\{"),
("SYMBOL_RCURL", r"\}"),
("SYMBOL_PLUS", r"\+"),
("SYMBOL_MINUS", r"\-"),
("SYMBOL_TIMES", r"\*"),
("SYMBOL_DIVIDE", r"\/"),
("SYMBOL_COMMA", r",")
("SYMBOL_LPARENS", r"\("),
("SYMBOL_RPARENS", r"\)"),
("SYMBOL_LBRACKET", r"\["),
("SYMBOL_RBRACKET", r"\]"),
("SYMBOL_LCURL", r"\{"),
("SYMBOL_RCURL", r"\}"),
("SYMBOL_PLUS", r"\+"),
("SYMBOL_MINUS", r"\-"),
("SYMBOL_TIMES", r"\*"),
("SYMBOL_DIVIDE", r"\/"),
("SYMBOL_COMMA", r",")
]
whitespace_tokens = [
("NEWLINE", r"\n+")
("NEWLINE", r"\n+")
]
all_tokens = (
keyword_tokens +
builtin_tokens +
module_tokens +
symbol_tokens +
data_tokens +
whitespace_tokens +
[("ID", f"({valid_characters})+")]
keyword_tokens +
builtin_tokens +
module_tokens +
symbol_tokens +
data_tokens +
whitespace_tokens +
[("ID", f"({valid_characters})+")]
)
class Lexer():
def __init__(self):
self.lexer = LexerGenerator()
def __init__(self):
self.lexer = LexerGenerator()
def _add_tokens(self):
for token in all_tokens:
self.lexer.add(*token)
self.lexer.ignore(r" +")
def _add_tokens(self):
for token in all_tokens:
self.lexer.add(*token)
self.lexer.ignore(r" +")
self.lexer.ignore(r'//[^\n]*')
self.lexer.ignore(r'/\*[\s\S]*?\*/')
def get_lexer(self):
self._add_tokens()
return self.lexer.build()
def get_lexer(self):
self._add_tokens()
return self.lexer.build()

View File

@@ -6,203 +6,212 @@ from . import ast_nodes
ALL_TOKENS = list(set([i[0] for i in all_tokens]))
class Parser():
def __init__(self):
self.pg = ParserGenerator(
ALL_TOKENS,
precedence=[
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]),
('left', ["SYMBOL_PLUS", "SYMBOL_MINUS"]),
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"])
]
)
def __init__(self):
self.pg = ParserGenerator(
ALL_TOKENS,
precedence=[
('left', ["KEYWORD_AVT"]),
('left', ["KEYWORD_ET"]),
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]),
('left', ["SYMBOL_PLUS", "SYMBOL_MINUS"]),
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"])
]
)
def parse(self, tokens_input) -> ast_nodes.BaseBox:
def parse(self, tokens_input) -> ast_nodes.Program:
# Top-level program stuff
@self.pg.production('program : opt_newline module_calls statement_list')
def program(tokens):
return ast_nodes.Program(tokens[1], tokens[2])
# Top-level program stuff
@self.pg.production('program : opt_newline module_calls statement_list')
def program(tokens):
return ast_nodes.Program(tokens[1], tokens[2])
@self.pg.production('opt_newline : ')
@self.pg.production('opt_newline : NEWLINE')
def opt_newline(_):
return None
@self.pg.production('newlines : NEWLINE')
@self.pg.production('newlines : NEWLINE newlines')
def newlines(_):
return None
# Module calls
@self.pg.production('module_calls : ')
@self.pg.production('module_calls : module_call NEWLINE module_calls')
def module_calls(calls):
if len(calls) == 0:
return []
elif len(calls) == 1:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production('opt_newline : ')
@self.pg.production('opt_newline : newlines')
def opt_newline(_):
return None
@self.pg.production('module_call : KEYWORD_CVM MODULE')
def module_call(tokens):
return ast_nodes.ModuleCall(tokens[1].value)
# Module calls
@self.pg.production('module_calls : ')
@self.pg.production('module_calls : module_call newlines module_calls')
def module_calls(calls):
if len(calls) == 0:
return []
elif len(calls) == 1:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production('module_call : KEYWORD_CVM MODULE')
def module_call(tokens):
return ast_nodes.ModuleCall(tokens[1].value)
# Statements
@self.pg.production('statements : opt_newline statement_list')
def statements(tokens):
return tokens[1]
# Statements
@self.pg.production('statements : opt_newline statement_list')
def statements(tokens):
return tokens[1]
@self.pg.production('statement_list : statement opt_newline')
@self.pg.production('statement_list : statement NEWLINE statement_list')
def statement_list(calls):
if len(calls) == 2:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production('statement_list : statement opt_newline')
@self.pg.production('statement_list : statement newlines statement_list')
def statement_list(calls):
if len(calls) == 2:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production('statement : KEYWORD_DESIGNA id KEYWORD_VT expression')
def statement_designa(tokens):
return ast_nodes.Designa(tokens[1], tokens[3])
@self.pg.production('statement : KEYWORD_DESIGNA id KEYWORD_VT expression')
def statement_designa(tokens):
return ast_nodes.Designa(tokens[1], tokens[3])
@self.pg.production('statement : expression')
def statement_expression(tokens):
return ast_nodes.ExpressionStatement(tokens[0])
@self.pg.production('statement : expression')
def statement_expression(tokens):
return ast_nodes.ExpressionStatement(tokens[0])
@self.pg.production('statement : KEYWORD_DEFINI id ids KEYWORD_VT SYMBOL_LCURL statements SYMBOL_RCURL')
def defini(tokens):
return ast_nodes.Defini(tokens[1], tokens[2], tokens[5])
@self.pg.production('statement : KEYWORD_DEFINI id ids KEYWORD_VT SYMBOL_LCURL statements SYMBOL_RCURL')
def defini(tokens):
return ast_nodes.Defini(tokens[1], tokens[2], tokens[5])
@self.pg.production('statement : KEYWORD_REDI expressions')
def redi(tokens):
return ast_nodes.Redi(tokens[1])
@self.pg.production('statement : KEYWORD_REDI expressions')
def redi(tokens):
return ast_nodes.Redi(tokens[1])
@self.pg.production('statement : per_statement')
@self.pg.production('statement : dum_statement')
@self.pg.production('statement : donicum_statement')
@self.pg.production('statement : si_statement')
def nested_statements(tokens):
return tokens[0]
@self.pg.production('statement : per_statement')
@self.pg.production('statement : dum_statement')
@self.pg.production('statement : donicum_statement')
@self.pg.production('statement : si_statement')
def nested_statements(tokens):
return tokens[0]
@self.pg.production('statement : KEYWORD_ERVMPE')
def erumpe(_):
return ast_nodes.Erumpe()
@self.pg.production('statement : KEYWORD_ERVMPE')
def erumpe(_):
return ast_nodes.Erumpe()
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL')
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL aluid_statement')
def si_statement(tokens):
if len(tokens) == 7:
return ast_nodes.SiStatement(tokens[1], tokens[4], tokens[6])
else:
return ast_nodes.SiStatement(tokens[1], tokens[4], None)
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL')
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL aluid_statement')
def si_statement(tokens):
if len(tokens) == 7:
return ast_nodes.SiStatement(tokens[1], tokens[4], tokens[6])
else:
return ast_nodes.SiStatement(tokens[1], tokens[4], None)
@self.pg.production('aluid_statement : KEYWORD_ALVID si_statement')
def aluid_si(tokens):
return [tokens[1]]
@self.pg.production('aluid_statement : KEYWORD_ALVID si_statement')
def aluid_si(tokens):
return [tokens[1]]
@self.pg.production('aluid_statement : KEYWORD_ALVID SYMBOL_LCURL statements SYMBOL_RCURL')
def aluid(tokens):
return tokens[2]
@self.pg.production('aluid_statement : KEYWORD_ALVID SYMBOL_LCURL statements SYMBOL_RCURL')
def aluid(tokens):
return tokens[2]
@self.pg.production('dum_statement : KEYWORD_DVM expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
def dum(tokens):
return ast_nodes.DumStatement(tokens[1], tokens[4])
@self.pg.production('dum_statement : KEYWORD_DVM expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
def dum(tokens):
return ast_nodes.DumStatement(tokens[1], tokens[4])
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
def per(tokens):
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
def per(tokens):
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
def donicum(tokens):
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
return ast_nodes.PerStatement(range_array, tokens[1], tokens[8])
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
def donicum(tokens):
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
return ast_nodes.PerStatement(range_array, tokens[1], tokens[8])
# expressions
@self.pg.production('expressions : SYMBOL_LPARENS expression_list')
def expressions(tokens):
return tokens[1]
# expressions
@self.pg.production('expressions : SYMBOL_LPARENS expression_list')
def expressions(tokens):
return tokens[1]
@self.pg.production('expression_list : SYMBOL_RPARENS')
@self.pg.production('expression_list : expression SYMBOL_RPARENS')
@self.pg.production('expression_list : expression SYMBOL_COMMA expression_list')
def expression_list(calls):
if len(calls) == 1:
return []
elif len(calls) == 2:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production('expression_list : SYMBOL_RPARENS')
@self.pg.production('expression_list : expression SYMBOL_RPARENS')
@self.pg.production('expression_list : expression SYMBOL_COMMA expression_list')
def expression_list(calls):
if len(calls) == 1:
return []
elif len(calls) == 2:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production('expression : id')
def expression_id(tokens):
return tokens[0]
@self.pg.production('expression : id')
def expression_id(tokens):
return tokens[0]
@self.pg.production('expression : BUILTIN expressions')
def expression_builtin(tokens):
return ast_nodes.BuiltIn(tokens[0].value, tokens[1])
@self.pg.production('expression : BUILTIN expressions')
def expression_builtin(tokens):
return ast_nodes.BuiltIn(tokens[0].value, tokens[1])
@self.pg.production('expression : DATA_STRING')
def expression_string(tokens):
return ast_nodes.String(tokens[0].value[1:-1])
@self.pg.production('expression : DATA_STRING')
def expression_string(tokens):
return ast_nodes.String(tokens[0].value[1:-1])
@self.pg.production('expression : DATA_NUMERAL')
def expression_numeral(tokens):
return ast_nodes.Numeral(tokens[0].value)
@self.pg.production('expression : DATA_NUMERAL')
def expression_numeral(tokens):
return ast_nodes.Numeral(tokens[0].value)
@self.pg.production('expression : KEYWORD_FALSITAS')
@self.pg.production('expression : KEYWORD_VERITAS')
def expression_bool(tokens):
return ast_nodes.Bool(tokens[0].name == "KEYWORD_VERITAS")
@self.pg.production('expression : KEYWORD_FALSITAS')
@self.pg.production('expression : KEYWORD_VERITAS')
def expression_bool(tokens):
return ast_nodes.Bool(tokens[0].name == "KEYWORD_VERITAS")
@self.pg.production('expression : KEYWORD_NVLLVS')
def expression_nullus(_):
return ast_nodes.Nullus()
@self.pg.production('expression : KEYWORD_NVLLVS')
def expression_nullus(_):
return ast_nodes.Nullus()
@self.pg.production('expression : expression SYMBOL_MINUS expression')
@self.pg.production('expression : expression SYMBOL_PLUS expression')
@self.pg.production('expression : expression SYMBOL_TIMES expression')
@self.pg.production('expression : expression SYMBOL_DIVIDE expression')
@self.pg.production('expression : expression KEYWORD_EST expression')
@self.pg.production('expression : expression KEYWORD_MINVS expression')
@self.pg.production('expression : expression KEYWORD_PLVS expression')
def binop(tokens):
return ast_nodes.BinOp(tokens[0], tokens[2], tokens[1].name)
@self.pg.production('expression : expression SYMBOL_MINUS expression')
@self.pg.production('expression : expression SYMBOL_PLUS expression')
@self.pg.production('expression : expression SYMBOL_TIMES expression')
@self.pg.production('expression : expression SYMBOL_DIVIDE expression')
@self.pg.production('expression : expression KEYWORD_EST expression')
@self.pg.production('expression : expression KEYWORD_MINVS expression')
@self.pg.production('expression : expression KEYWORD_PLVS expression')
@self.pg.production('expression : expression KEYWORD_ET expression')
@self.pg.production('expression : expression KEYWORD_AVT expression')
def binop(tokens):
return ast_nodes.BinOp(tokens[0], tokens[2], tokens[1].name)
@self.pg.production('expression : KEYWORD_INVOCA id expressions')
def invoca(tokens):
return ast_nodes.Invoca(tokens[1], tokens[2])
@self.pg.production('expression : KEYWORD_INVOCA id expressions')
def invoca(tokens):
return ast_nodes.Invoca(tokens[1], tokens[2])
@self.pg.production('expression : SYMBOL_LPARENS expression SYMBOL_RPARENS')
def parens(tokens):
return tokens[1]
@self.pg.production('expression : SYMBOL_LPARENS expression SYMBOL_RPARENS')
def parens(tokens):
return tokens[1]
@self.pg.production('expression : SYMBOL_LBRACKET expressions SYMBOL_RBRACKET')
def array(tokens):
return ast_nodes.DataArray(tokens[1])
@self.pg.production('expression : SYMBOL_LBRACKET expressions SYMBOL_RBRACKET')
def array(tokens):
return ast_nodes.DataArray(tokens[1])
@self.pg.production('expression : SYMBOL_LBRACKET expression KEYWORD_VSQVE expression SYMBOL_RBRACKET')
def range_array(tokens):
return ast_nodes.DataRangeArray(tokens[1], tokens[3])
@self.pg.production('expression : SYMBOL_LBRACKET expression KEYWORD_VSQVE expression SYMBOL_RBRACKET')
def range_array(tokens):
return ast_nodes.DataRangeArray(tokens[1], tokens[3])
# ids
@self.pg.production('ids : SYMBOL_LPARENS id_list')
def ids(tokens):
return tokens[1]
# ids
@self.pg.production('ids : SYMBOL_LPARENS id_list')
def ids(tokens):
return tokens[1]
@self.pg.production('id_list : SYMBOL_RPARENS')
@self.pg.production('id_list : id SYMBOL_RPARENS')
@self.pg.production('id_list : id SYMBOL_COMMA id_list')
def id_list(calls):
if len(calls) == 1:
return []
elif len(calls) == 2:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production('id_list : SYMBOL_RPARENS')
@self.pg.production('id_list : id SYMBOL_RPARENS')
@self.pg.production('id_list : id SYMBOL_COMMA id_list')
def id_list(calls):
if len(calls) == 1:
return []
elif len(calls) == 2:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production("id : ID")
def id_expression(tokens):
return ast_nodes.ID(tokens[0].value)
@self.pg.production("id : ID")
def id_expression(tokens):
return ast_nodes.ID(tokens[0].value)
@self.pg.error
def error_handle(token):
raise Exception(token.name, token.value, token.source_pos)
@self.pg.error
def error_handle(token):
raise SyntaxError(token.name, token.value, token.source_pos)
parser = self.pg.build()
return parser.parse(tokens_input)
parser = self.pg.build()
return parser.parse(tokens_input) # type: ignore

87
centvrion/values.py Normal file
View File

@@ -0,0 +1,87 @@
from abc import ABC, abstractmethod
class Val(ABC):
@abstractmethod
def value(self): ...
def __repr__(self):
return f"{self.__class__.__name__}({self.value()!r})"
def __eq__(self, other):
return isinstance(other, type(self)) and self.value() == other.value()
class ValInt(Val):
def __init__(self, v: int):
assert isinstance(v, int) and not isinstance(v, bool)
self._v = v
def value(self):
return self._v
def __bool__(self):
return self._v != 0
def __lt__(self, other):
return self._v < other.value()
def __gt__(self, other):
return self._v > other.value()
class ValStr(Val):
def __init__(self, v: str):
assert isinstance(v, str)
self._v = v
def value(self):
return self._v
def __bool__(self):
return self._v != ""
class ValBool(Val):
def __init__(self, v: bool):
assert isinstance(v, bool)
self._v = v
def value(self):
return self._v
def __bool__(self):
return self._v
class ValList(Val):
def __init__(self, v: list):
assert isinstance(v, list)
self._v = v
def value(self):
return self._v
def __bool__(self):
return len(self._v) > 0
def __iter__(self):
return iter(self._v)
class ValNul(Val):
def value(self):
return None
def __bool__(self):
raise TypeError("NVLLVS cannot be evaluated as a boolean")
class ValFunc(Val):
def __init__(self, params: list, body: list):
self._params = params
self._body = body
def value(self):
return (self._params, self._body)
@property
def params(self):
return self._params
@property
def body(self):
return self._body