🐐 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

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
.vscode/ .vscode/
__pycache__/ __pycache__/
.claude/
CLAUDE.md

View File

@@ -7,7 +7,7 @@
### Hello World ### Hello World
``` ```
DESIGNA x VT "Hello World!" DESIGNA x VT "Hello World!"
DICE x DICE(x)
``` ```
### Recursive Fibonacci number function ### Recursive Fibonacci number function
@@ -99,7 +99,7 @@ Booleans are denoted with the keywords `VERITAS` for true and `FALSITAS` for fal
Arrays are defined using square brackets (`[]`) and commas (`,`). An array of integers can also be initialized with the `VSQVE` keyword: Arrays are defined using square brackets (`[]`) and commas (`,`). An array of integers can also be initialized with the `VSQVE` keyword:
``` ```
DESIGNA x VT [1 VSQVE 10] DESIGNA x VT [I VSQVE X]
``` ```
## Conditionals ## Conditionals
@@ -110,10 +110,10 @@ If-then statements are denoted with the keywords `SI` (if) and `TVNC` (then). Th
DESIGNA x VT VERITAS DESIGNA x VT VERITAS
SI x TVNC { SI x TVNC {
DICE(I) DICE(I)
REDI(NVLLLVS) REDI(NVLLVS)
} }
DICE NVLLVS DICE(NVLLVS)
> I > I
``` ```
@@ -209,12 +209,12 @@ PER y IN x FACE {
``` ```
## Functions ## Functions
Functions are defined with the `DEFINI` and `VT` keywords. The `REDI` keyword is used to return. `REDI` must have exactly one parameter. `REDI` can also be used to end the program, if used outside of a function. Functions are defined with the `DEFINI` and `VT` keywords. The `REDI` keyword is used to return. `REDI` can also be used to end the program, if used outside of a function.
Calling a function is done with the `INVOCA` keyword. Calling a function is done with the `INVOCA` keyword.
``` ```
DEFINI square x VT { DEFINI square(x) VT {
REDI(x*x) REDI(x*x)
} }

View File

@@ -3,6 +3,8 @@ import random
from rply.token import BaseBox from rply.token import BaseBox
from centvrion.values import Val, ValInt, ValStr, ValBool, ValList, ValNul, ValFunc
NUMERALS = { NUMERALS = {
"I": 1, "I": 1,
"IV": 4, "IV": 4,
@@ -32,12 +34,12 @@ def rep_join(l):
def single_num_to_int(i, m): def single_num_to_int(i, m):
if i[-1] == "_": if i[-1] == "_":
if not m: if not m:
raise Exception( raise ValueError(
"Cannot calculate numbers above 3999 without 'MAGNVM' module" "Cannot calculate numbers above 3999 without 'MAGNVM' module"
) )
if i[0] != "I": if i[0] != "I":
raise Exception( raise ValueError(
"Cannot use 'I' with thousands operator, use 'M' instead" "Cannot use 'I' with thousands operator, use 'M' instead"
) )
@@ -48,12 +50,12 @@ def single_num_to_int(i, m):
def num_to_int(n, m): def num_to_int(n, m):
chars = re.findall(r"[IVXLCDM]_*", n) chars = re.findall(r"[IVXLCDM]_*", n)
if ''.join(chars) != n: if ''.join(chars) != n:
raise Exception("Invalid numeral", n) raise ValueError("Invalid numeral", n)
nums = [single_num_to_int(i, m) for i in chars] nums = [single_num_to_int(i, m) for i in chars]
new_nums = nums.copy() new_nums = nums.copy()
for x, num in enumerate(nums[:-3]): for x, num in enumerate(nums[:-3]):
if all(num == nums[x+i] for i in range(0,4)): if all(num == nums[x+i] for i in range(0,4)):
raise Exception(n, "is not a valid roman numeral") raise ValueError(n, "is not a valid roman numeral")
while True: while True:
for x, num in enumerate(nums[:-1]): for x, num in enumerate(nums[:-1]):
@@ -63,11 +65,11 @@ def num_to_int(n, m):
new_nums[x+1] = 0 new_nums[x+1] = 0
break break
else: else:
raise Exception(n, "is not a valid roman numeral") raise ValueError(n, "is not a valid roman numeral")
if new_nums != nums: if new_nums != nums:
nums = [i for i in new_nums if i != 0] nums = [i for i in new_nums if i != 0]
new_nums = nums new_nums = nums.copy()
else: else:
break break
@@ -79,7 +81,7 @@ def num_to_int(n, m):
def int_to_num(n, m): def int_to_num(n, m):
if n > 3999: if n > 3999:
if not m: if not m:
raise Exception( raise ValueError(
"Cannot display numbers above 3999 without 'MAGNVM' module" "Cannot display numbers above 3999 without 'MAGNVM' module"
) )
thousands_chars = re.findall(r"[IVXLCDM]_*", int_to_num(n//1000, m)) thousands_chars = re.findall(r"[IVXLCDM]_*", int_to_num(n//1000, m))
@@ -100,28 +102,42 @@ def int_to_num(n, m):
return ''.join(nums) return ''.join(nums)
def make_string(n, m): def make_string(val, magnvm=False):
if isinstance(n, str): if isinstance(val, ValStr):
return n return val.value()
elif isinstance(n, int): elif isinstance(val, ValInt):
return int_to_num(n, m) return int_to_num(val.value(), magnvm)
elif isinstance(n, list): elif isinstance(val, ValBool):
return f"[{' '.join([make_string(i, m) for i in n])}]" 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: else:
raise Exception(n) raise TypeError(f"Cannot display {val!r}")
class ExpressionStatement(BaseBox):
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: def __init__(self, expression) -> None:
self.expression = expression self.expression = expression
def __repr__(self) -> str: def __repr__(self) -> str:
return self.expression.__repr__() return self.expression.__repr__()
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
self.expression.eval(vtable.copy(), ftable.copy(), modules) return self.expression.eval(vtable)
return vtable, ftable
class DataArray(BaseBox):
class DataArray(Node):
def __init__(self, content) -> None: def __init__(self, content) -> None:
self.content = content self.content = content
@@ -129,11 +145,15 @@ class DataArray(BaseBox):
content_string = rep_join(self.content) content_string = rep_join(self.content)
return f"Array([{content_string}])" return f"Array([{content_string}])"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
content = [i.eval(vtable, ftable, modules) for i in self.content] vals = []
return content for item in self.content:
vtable, val = item.eval(vtable)
vals.append(val)
return vtable, ValList(vals)
class DataRangeArray(BaseBox):
class DataRangeArray(Node):
def __init__(self, from_value, to_value) -> None: def __init__(self, from_value, to_value) -> None:
self.from_value = from_value self.from_value = from_value
self.to_value = to_value self.to_value = to_value
@@ -142,39 +162,44 @@ class DataRangeArray(BaseBox):
content_string = rep_join([self.from_value, self.to_value]) content_string = rep_join([self.from_value, self.to_value])
return f"RangeArray([{content_string}])" return f"RangeArray([{content_string}])"
def eval(self, *_): def _eval(self, vtable):
content = list(range(self.from_value.eval(), self.to_value.eval())) vtable, from_val = self.from_value.eval(vtable)
return content vtable, to_val = self.to_value.eval(vtable)
return vtable, ValList([ValInt(i) for i in range(from_val.value(), to_val.value())])
class String(BaseBox):
class String(Node):
def __init__(self, value) -> None: def __init__(self, value) -> None:
self.value = value self.value = value
def __repr__(self): def __repr__(self):
return f"String({self.value})" return f"String({self.value})"
def eval(self, *_): def _eval(self, vtable):
return self.value return vtable, ValStr(self.value)
class Numeral(BaseBox):
class Numeral(Node):
def __init__(self, value) -> None: def __init__(self, value) -> None:
self.value = value self.value = value
def __repr__(self): def __repr__(self):
return f"Numeral({self.value})" return f"Numeral({self.value})"
def eval(self, *args): def _eval(self, vtable):
return num_to_int(self.value, "MAGNVM" in args[2]) return vtable, ValInt(num_to_int(self.value, "MAGNVM" in vtable["#modules"]))
class Bool(BaseBox):
class Bool(Node):
def __init__(self, value) -> None: def __init__(self, value) -> None:
self.value = value self.value = value
def __repr__(self): def __repr__(self):
return f"Bool({self.value})" return f"Bool({self.value})"
def eval(self, *_): def _eval(self, vtable):
return self.value return vtable, ValBool(self.value)
class ModuleCall(BaseBox): class ModuleCall(BaseBox):
def __init__(self, module_name) -> None: def __init__(self, module_name) -> None:
@@ -183,17 +208,19 @@ class ModuleCall(BaseBox):
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{self.module_name}" return f"{self.module_name}"
class ID(BaseBox):
class ID(Node):
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
self.name = name self.name = name
def __repr__(self) -> str: def __repr__(self) -> str:
return f"ID({self.name})" return f"ID({self.name})"
def eval(self, vtable, *_): def _eval(self, vtable):
return vtable[self.name] return vtable, vtable[self.name]
class Designa(BaseBox):
class Designa(Node):
def __init__(self, variable: ID, value) -> None: def __init__(self, variable: ID, value) -> None:
self.id = variable self.id = variable
self.value = value self.value = value
@@ -203,13 +230,13 @@ class Designa(BaseBox):
value_string = repr(self.value).replace('\n', '\n ') value_string = repr(self.value).replace('\n', '\n ')
return f"Designa(\n {id_string},\n {value_string}\n)" return f"Designa(\n {id_string},\n {value_string}\n)"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
vtable[self.id.name] = self.value.eval( vtable, val = self.value.eval(vtable)
vtable.copy(), ftable.copy(), modules vtable[self.id.name] = val
) return vtable, ValNul()
return vtable, ftable
class Defini(BaseBox):
class Defini(Node):
def __init__(self, name, parameters, statements) -> None: def __init__(self, name, parameters, statements) -> None:
self.name = name self.name = name
self.parameters = parameters self.parameters = parameters
@@ -223,13 +250,12 @@ class Defini(BaseBox):
) )
return f"Defini({def_string})" return f"Defini({def_string})"
def eval(self, vtable, ftable, _): def _eval(self, vtable):
ftable[self.name.name] = ( vtable[self.name.name] = ValFunc(self.parameters, self.statements)
self.parameters, self.statements return vtable, ValNul()
)
return vtable, ftable
class Redi(BaseBox):
class Redi(Node):
def __init__(self, values) -> None: def __init__(self, values) -> None:
self.values = values self.values = values
@@ -237,33 +263,36 @@ class Redi(BaseBox):
values_string = f"[{rep_join(self.values)}]" values_string = f"[{rep_join(self.values)}]"
return f"Redi({values_string})" return f"Redi({values_string})"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
values = [ vals = []
i.eval(vtable.copy(), ftable.copy(), modules) for v in self.values:
for i in self.values vtable, val = v.eval(vtable)
] vals.append(val)
if len(values) == 1: if len(vals) == 1:
vtable["REDI"] = values[0] vtable["#return"] = vals[0]
else: else:
vtable["REDI"] = values vtable["#return"] = ValList(vals)
return vtable, ftable return vtable, ValNul()
class Erumpe(BaseBox):
class Erumpe(Node):
def __repr__(self) -> str: def __repr__(self) -> str:
return "Erumpe()" return "Erumpe()"
def eval(self, vtable, ftable, _): def _eval(self, vtable):
vtable["ERVMPE"] = True vtable["#break"] = True
return vtable, ftable return vtable, ValNul()
class Nullus(BaseBox):
class Nullus(Node):
def __repr__(self) -> str: def __repr__(self) -> str:
return "Nullus()" return "Nullus()"
def eval(self, *_): def _eval(self, vtable):
return 0 return vtable, ValNul()
class BinOp(BaseBox):
class BinOp(Node):
def __init__(self, left, right, op) -> None: def __init__(self, left, right, op) -> None:
self.left = left self.left = left
self.right = right self.right = right
@@ -273,29 +302,35 @@ class BinOp(BaseBox):
binop_string = rep_join([self.left, self.right, self.op]) binop_string = rep_join([self.left, self.right, self.op])
return f"BinOp({binop_string})" return f"BinOp({binop_string})"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
left = self.left.eval(vtable.copy(), ftable.copy(), modules) vtable, left = self.left.eval(vtable)
right = self.right.eval(vtable.copy(), ftable.copy(), modules) vtable, right = self.right.eval(vtable)
lv, rv = left.value(), right.value()
match self.op: match self.op:
case "SYMBOL_PLUS": case "SYMBOL_PLUS":
return left + right return vtable, ValInt(lv + rv)
case "SYMBOL_MINUS": case "SYMBOL_MINUS":
return left - right return vtable, ValInt(lv - rv)
case "SYMBOL_TIMES": case "SYMBOL_TIMES":
return left * right return vtable, ValInt(lv * rv)
case "SYMBOL_DIVIDE": case "SYMBOL_DIVIDE":
# TODO: Fractio # TODO: Fractio
return left // right return vtable, ValInt(lv // rv)
case "KEYWORD_MINVS": case "KEYWORD_MINVS":
return left < right return vtable, ValBool(lv < rv)
case "KEYWORD_PLVS": case "KEYWORD_PLVS":
return left > right return vtable, ValBool(lv > rv)
case "KEYWORD_EST": case "KEYWORD_EST":
return left == right 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 _: case _:
raise Exception(self.op) raise Exception(self.op)
class SiStatement(BaseBox):
class SiStatement(Node):
def __init__(self, test, statements, else_part) -> None: def __init__(self, test, statements, else_part) -> None:
self.test = test self.test = test
self.statements = statements self.statements = statements
@@ -304,25 +339,23 @@ class SiStatement(BaseBox):
def __repr__(self) -> str: def __repr__(self) -> str:
test = repr(self.test) test = repr(self.test)
statements = f"statements([{rep_join(self.statements)}])" statements = f"statements([{rep_join(self.statements)}])"
else_part = f"statements([{rep_join(self.else_part)}])" else_part = f"statements([{rep_join(self.else_part) if self.else_part else ''}])"
si_string = rep_join([test, statements, else_part]) si_string = rep_join([test, statements, else_part])
return f"Si({si_string})" return f"Si({si_string})"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
if self.test.eval(vtable, ftable, modules): vtable, cond = self.test.eval(vtable)
last_val = ValNul()
if cond:
for statement in self.statements: for statement in self.statements:
vtable, ftable = statement.eval( vtable, last_val = statement.eval(vtable)
vtable, ftable, modules elif self.else_part:
)
else:
for statement in self.else_part: for statement in self.else_part:
vtable, ftable = statement.eval( vtable, last_val = statement.eval(vtable)
vtable, ftable, modules return vtable, last_val
)
return vtable, ftable
class DumStatement(BaseBox): class DumStatement(Node):
def __init__(self, test, statements) -> None: def __init__(self, test, statements) -> None:
self.test = test self.test = test
self.statements = statements self.statements = statements
@@ -333,22 +366,23 @@ class DumStatement(BaseBox):
dum_string = rep_join([test, statements]) dum_string = rep_join([test, statements])
return f"Dum({dum_string})" return f"Dum({dum_string})"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
while not self.test.eval(vtable, ftable, modules): last_val = ValNul()
vtable, cond = self.test.eval(vtable)
while not cond:
for statement in self.statements: for statement in self.statements:
vtable, ftable = statement.eval( vtable, val = statement.eval(vtable)
vtable, ftable, modules if vtable["#break"]:
)
if vtable["ERVMPE"]:
break break
last_val = val
if vtable["ERVMPE"]: if vtable["#break"]:
vtable["ERVMPE"] = False vtable["#break"] = False
break break
vtable, cond = self.test.eval(vtable)
return vtable, last_val
return vtable, ftable
class PerStatement(BaseBox): class PerStatement(Node):
def __init__(self, data_list, variable_name, statements) -> None: def __init__(self, data_list, variable_name, statements) -> None:
self.data_list = data_list self.data_list = data_list
self.variable_name = variable_name self.variable_name = variable_name
@@ -361,25 +395,24 @@ class PerStatement(BaseBox):
dum_string = rep_join([test, variable_name, statements]) dum_string = rep_join([test, variable_name, statements])
return f"Per({dum_string})" return f"Per({dum_string})"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
data_array = self.data_list.eval(vtable, ftable, modules) vtable, array = self.data_list.eval(vtable)
variable_name = self.variable_name.name variable_name = self.variable_name.name
for i in data_array: last_val = ValNul()
vtable[variable_name] = i for item in array:
vtable[variable_name] = item
for statement in self.statements: for statement in self.statements:
vtable, ftable = statement.eval( vtable, val = statement.eval(vtable)
vtable, ftable, modules if vtable["#break"]:
)
if vtable["ERVMPE"]:
break break
last_val = val
if vtable["ERVMPE"]: if vtable["#break"]:
vtable["ERVMPE"] = False vtable["#break"] = False
break break
return vtable, last_val
return vtable, ftable
class Invoca(BaseBox): class Invoca(Node):
def __init__(self, name, parameters) -> None: def __init__(self, name, parameters) -> None:
self.name = name self.name = name
self.parameters = parameters self.parameters = parameters
@@ -389,23 +422,25 @@ class Invoca(BaseBox):
invoca_string = rep_join([self.name, parameters_string]) invoca_string = rep_join([self.name, parameters_string])
return f"Invoca({invoca_string})" return f"Invoca({invoca_string})"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
parameters = [ params = [p.eval(vtable)[1] for p in self.parameters]
i.eval(vtable.copy(), ftable.copy(), modules) func = vtable[self.name.name]
for i in self.parameters if len(params) != len(func.params):
] raise TypeError(
vtable_copy = vtable.copy() f"{self.name.name} expects {len(func.params)} argument(s), got {len(params)}"
function = ftable[self.name.name] )
for i, parameter in enumerate(function[0]): func_vtable = vtable.copy()
vtable_copy[parameter.name] = parameters[i] 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()
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): class BuiltIn(Node):
def __init__(self, builtin, parameters) -> None: def __init__(self, builtin, parameters) -> None:
self.builtin = builtin self.builtin = builtin
self.parameters = parameters self.parameters = parameters
@@ -415,31 +450,41 @@ class BuiltIn(BaseBox):
builtin_string = rep_join([self.builtin, parameter_string]) builtin_string = rep_join([self.builtin, parameter_string])
return f"Builtin({builtin_string})" return f"Builtin({builtin_string})"
def eval(self, vtable, ftable, modules): def _eval(self, vtable):
parameters = [ params = [p.eval(vtable)[1] for p in self.parameters]
i.eval(vtable.copy(), ftable.copy(), modules) magnvm = "MAGNVM" in vtable["#modules"]
for i in self.parameters
]
match self.builtin: match self.builtin:
case "AVDI_NVMERVS": case "AVDI_NVMERVS":
return num_to_int(input(), "MAGNVM" in modules) return vtable, ValInt(num_to_int(input(), magnvm))
case "AVDI":
return vtable, ValStr(input())
case "DICE": case "DICE":
print(' '.join( print_string = ' '.join(
make_string(i, "MAGNVM" in modules) for i in parameters) make_string(i, magnvm) for i in params
) )
return None print(print_string)
return vtable, ValStr(print_string)
case "ERVMPE": case "ERVMPE":
vtable["ERVMPE"] = True vtable["#break"] = True
return None return vtable, ValNul()
case "FORTIS_NVMERVS": case "FORTIS_NVMERVS":
if "FORS" not in modules: if "FORS" not in vtable["#modules"]:
raise Exception( raise ValueError(
"Cannot use 'FORTIS_NVMERVS' without module 'FORS'" "Cannot use 'FORTIS_NVMERVS' without module 'FORS'"
) )
return random.randint(parameters[0], parameters[1]) 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 _: case _:
raise Exception(self.builtin) raise NotImplementedError(self.builtin)
class Program(BaseBox): class Program(BaseBox):
def __init__(self, module_calls: list[ModuleCall], statements) -> None: def __init__(self, module_calls: list[ModuleCall], statements) -> None:
@@ -452,9 +497,12 @@ class Program(BaseBox):
return f"{modules_string},\n{statements_string}" return f"{modules_string},\n{statements_string}"
def eval(self, *_): def eval(self, *_):
vtable = {"ERVMPE": False} vtable = {
ftable = {} "#break": False,
modules = [module.module_name for module in self.modules] "#return": None,
"#modules": [m.module_name for m in self.modules],
}
last_val = ValNul()
for statement in self.statements: for statement in self.statements:
vtable, ftable = statement.eval(vtable, ftable, modules) vtable, last_val = statement.eval(vtable)
return last_val

View File

@@ -4,12 +4,14 @@ valid_characters = '|'.join(list("abcdefghiklmnopqrstvxyz_"))
keyword_tokens = [("KEYWORD_"+i, i) for i in [ keyword_tokens = [("KEYWORD_"+i, i) for i in [
"ALVID", "ALVID",
"AVT",
"DEFINI", "DEFINI",
"DESIGNA", "DESIGNA",
"DONICVM", "DONICVM",
"DVM", "DVM",
"ERVMPE", "ERVMPE",
"EST", "EST",
"ET",
"FACE", "FACE",
"FALSITAS", "FALSITAS",
"INVOCA", "INVOCA",
@@ -37,8 +39,8 @@ builtin_tokens = [("BUILTIN", i) for i in [
]] ]]
data_tokens = [ data_tokens = [
("DATA_STRING", r"\".*?\""), ("DATA_STRING", r"(\".*?\"|'.*?')"),
("DATA_NUMERAL", r"[IVXLCDM]+") ("DATA_NUMERAL", r"[IVXLCDM][IVXLCDM_]*")
] ]
module_tokens = [("MODULE", i) for i in [ module_tokens = [("MODULE", i) for i in [
@@ -84,6 +86,8 @@ class Lexer():
for token in all_tokens: for token in all_tokens:
self.lexer.add(*token) self.lexer.add(*token)
self.lexer.ignore(r" +") self.lexer.ignore(r" +")
self.lexer.ignore(r'//[^\n]*')
self.lexer.ignore(r'/\*[\s\S]*?\*/')
def get_lexer(self): def get_lexer(self):
self._add_tokens() self._add_tokens()

View File

@@ -10,27 +10,34 @@ class Parser():
self.pg = ParserGenerator( self.pg = ParserGenerator(
ALL_TOKENS, ALL_TOKENS,
precedence=[ precedence=[
('left', ["KEYWORD_AVT"]),
('left', ["KEYWORD_ET"]),
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]), ('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]),
('left', ["SYMBOL_PLUS", "SYMBOL_MINUS"]), ('left', ["SYMBOL_PLUS", "SYMBOL_MINUS"]),
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]) ('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 # Top-level program stuff
@self.pg.production('program : opt_newline module_calls statement_list') @self.pg.production('program : opt_newline module_calls statement_list')
def program(tokens): def program(tokens):
return ast_nodes.Program(tokens[1], tokens[2]) return ast_nodes.Program(tokens[1], tokens[2])
@self.pg.production('newlines : NEWLINE')
@self.pg.production('newlines : NEWLINE newlines')
def newlines(_):
return None
@self.pg.production('opt_newline : ') @self.pg.production('opt_newline : ')
@self.pg.production('opt_newline : NEWLINE') @self.pg.production('opt_newline : newlines')
def opt_newline(_): def opt_newline(_):
return None return None
# Module calls # Module calls
@self.pg.production('module_calls : ') @self.pg.production('module_calls : ')
@self.pg.production('module_calls : module_call NEWLINE module_calls') @self.pg.production('module_calls : module_call newlines module_calls')
def module_calls(calls): def module_calls(calls):
if len(calls) == 0: if len(calls) == 0:
return [] return []
@@ -50,7 +57,7 @@ class Parser():
return tokens[1] return tokens[1]
@self.pg.production('statement_list : statement opt_newline') @self.pg.production('statement_list : statement opt_newline')
@self.pg.production('statement_list : statement NEWLINE statement_list') @self.pg.production('statement_list : statement newlines statement_list')
def statement_list(calls): def statement_list(calls):
if len(calls) == 2: if len(calls) == 2:
return [calls[0]] return [calls[0]]
@@ -161,6 +168,8 @@ class Parser():
@self.pg.production('expression : expression KEYWORD_EST expression') @self.pg.production('expression : expression KEYWORD_EST expression')
@self.pg.production('expression : expression KEYWORD_MINVS expression') @self.pg.production('expression : expression KEYWORD_MINVS expression')
@self.pg.production('expression : expression KEYWORD_PLVS 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): def binop(tokens):
return ast_nodes.BinOp(tokens[0], tokens[2], tokens[1].name) return ast_nodes.BinOp(tokens[0], tokens[2], tokens[1].name)
@@ -202,7 +211,7 @@ class Parser():
@self.pg.error @self.pg.error
def error_handle(token): def error_handle(token):
raise Exception(token.name, token.value, token.source_pos) raise SyntaxError(token.name, token.value, token.source_pos)
parser = self.pg.build() parser = self.pg.build()
return parser.parse(tokens_input) 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

View File

@@ -1,2 +0,0 @@
\relax
\gdef \@abspage@last{2}

View File

@@ -1,30 +0,0 @@
# Fdb version 3
["xdvipdfmx"] 1666044971 "main.xdv" "main.pdf" "main" 1666044971
"main.xdv" 1666044971 18856 0aa2d80a726d535daf28e1e7b8969553 "xelatex"
(generated)
"main.pdf"
["xelatex"] 1666044971 "/home/nikolaj/Code/python/centvrion/language/main.tex" "main.xdv" "main" 1666044971
"/home/nikolaj/Code/python/centvrion/language/main.tex" 1666044970 4376 88792a4bab5d145c10208dea722cbecb ""
"/usr/share/texmf-dist/tex/generic/iftex/iftex.sty" 1650183167 7237 bdd120a32c8fdb4b433cf9ca2e7cd98a ""
"/usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty" 1650183167 1057 525c2192b5febbd8c1f662c9468335bb ""
"/usr/share/texmf-dist/tex/latex/base/article.cls" 1650183167 20144 8a7de377ae7a11ee924a7499611f5a9d ""
"/usr/share/texmf-dist/tex/latex/base/fontenc.sty" 1650183167 4946 461cc78f6f26901410d9f1d725079cc6 ""
"/usr/share/texmf-dist/tex/latex/base/size10.clo" 1650183167 8448 96f18c76bf608a36ee6fbf021ac1dd32 ""
"/usr/share/texmf-dist/tex/latex/base/ts1cmr.fd" 1650183167 2430 06a89bcded389391906798ea7a3f3aaa ""
"/usr/share/texmf-dist/tex/latex/base/tuenc.def" 1650183167 29098 3e2b8f2cbae539bde3ed8f63503c0954 ""
"/usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty" 1650183167 162076 2f6d31c4632f2730c57b9c0fda038e15 ""
"/usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg" 1650183167 549 c4adac819276241fea8eb79c5ab7b99e ""
"/usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty" 1650183167 1656 7e824878bad4df5a3e8bba4e463d9126 ""
"/usr/share/texmf-dist/tex/latex/geometry/geometry.sty" 1650183167 41601 9cf6c5257b1bc7af01a58859749dd37a ""
"/usr/share/texmf-dist/tex/latex/graphics/keyval.sty" 1650183167 2671 4de6781a30211fe0ea4c672e4a2a8166 ""
"/usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def" 1650183167 35763 558b6bb076dfa9b058fe5c58dc6e0434 ""
"/usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty" 1650183167 6107 e4124ef96db512db87780f95c2a6b136 ""
"/usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty" 1650183167 6758 7d9d899cbbfc962fbc4bb93f4c69eec2 ""
"/usr/share/texmf-dist/web2c/texmf.cnf" 1650183167 39911 2da6c67557ec033436fe5418a70a8a61 ""
"/var/lib/texmf/web2c/xetex/xelatex.fmt" 1656162080 5825168 1cdc03dd54937efcb6a623296105d1a8 ""
"main.aux" 1666044971 32 044b7f8fc9779af7531264e0c5c84b6d "xelatex"
"main.tex" 1666044970 4376 88792a4bab5d145c10208dea722cbecb ""
(generated)
"main.aux"
"main.log"
"main.xdv"

View File

@@ -1,146 +0,0 @@
PWD /home/nikolaj/Code/python/centvrion/language
INPUT /usr/share/texmf-dist/web2c/texmf.cnf
INPUT /var/lib/texmf/web2c/xetex/xelatex.fmt
INPUT /home/nikolaj/Code/python/centvrion/language/main.tex
OUTPUT main.log
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/size10.clo
INPUT /usr/share/texmf-dist/tex/latex/base/size10.clo
INPUT /usr/share/texmf-dist/tex/latex/base/size10.clo
INPUT /usr/share/texmf-dist/tex/latex/base/size10.clo
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/base/tuenc.def
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg
INPUT /usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg
INPUT ./main.aux
INPUT main.aux
INPUT main.aux
OUTPUT main.aux
INPUT /usr/share/texmf-dist/tex/latex/base/ts1cmr.fd
INPUT /usr/share/texmf-dist/tex/latex/base/ts1cmr.fd
INPUT /usr/share/texmf-dist/tex/latex/base/ts1cmr.fd
INPUT /usr/share/texmf-dist/tex/latex/base/ts1cmr.fd
OUTPUT main.xdv
INPUT main.aux

View File

@@ -1,251 +0,0 @@
This is XeTeX, Version 3.141592653-2.6-0.999994 (TeX Live 2022/Arch Linux) (preloaded format=xelatex 2022.6.25) 18 OCT 2022 00:16
entering extended mode
restricted \write18 enabled.
file:line:error style messages enabled.
%&-line parsing enabled.
**/home/nikolaj/Code/python/centvrion/language/main.tex
(/home/nikolaj/Code/python/centvrion/language/main.tex
LaTeX2e <2021-11-15> patch level 1
L3 programming layer <2022-04-10> (/usr/share/texmf-dist/tex/latex/base/article.cls
Document Class: article 2021/10/04 v1.4n Standard LaTeX document class
(/usr/share/texmf-dist/tex/latex/base/size10.clo
File: size10.clo 2021/10/04 v1.4n Standard LaTeX file (size option)
)
\c@part=\count181
\c@section=\count182
\c@subsection=\count183
\c@subsubsection=\count184
\c@paragraph=\count185
\c@subparagraph=\count186
\c@figure=\count187
\c@table=\count188
\abovecaptionskip=\skip47
\belowcaptionskip=\skip48
\bibindent=\dimen138
) (/usr/share/texmf-dist/tex/latex/geometry/geometry.sty
Package: geometry 2020/01/02 v5.9 Page Geometry
(/usr/share/texmf-dist/tex/latex/graphics/keyval.sty
Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
\KV@toks@=\toks16
) (/usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead.
(/usr/share/texmf-dist/tex/generic/iftex/iftex.sty
Package: iftex 2022/02/03 v1.0f TeX engine tests
))
\Gm@cnth=\count189
\Gm@cntv=\count190
\c@Gm@tempcnt=\count191
\Gm@bindingoffset=\dimen139
\Gm@wd@mp=\dimen140
\Gm@odd@mp=\dimen141
\Gm@even@mp=\dimen142
\Gm@layoutwidth=\dimen143
\Gm@layoutheight=\dimen144
\Gm@layouthoffset=\dimen145
\Gm@layoutvoffset=\dimen146
\Gm@dimlist=\toks17
) (/usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty (/usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty (/usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
Package: expl3 2022-04-10 L3 programming layer (loader)
(/usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
File: l3backend-xetex.def 2022-04-14 L3 backend support: XeTeX
\l__color_backend_stack_int=\count192
\g__color_backend_stack_int=\count193
\g__graphics_track_int=\count194
\l__pdf_internal_box=\box50
\g__pdf_backend_object_int=\count195
\g__pdf_backend_annotation_int=\count196
\g__pdf_backend_link_int=\count197
))
Package: xparse 2022-01-12 L3 Experimental document command parser
)
Package: fontspec 2022/01/15 v2.8a Font selection for XeLaTeX and LuaLaTeX
(/usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
Package: fontspec-xetex 2022/01/15 v2.8a Font selection for XeLaTeX and LuaLaTeX
\l__fontspec_script_int=\count198
\l__fontspec_language_int=\count199
\l__fontspec_strnum_int=\count266
\l__fontspec_tmp_int=\count267
\l__fontspec_tmpa_int=\count268
\l__fontspec_tmpb_int=\count269
\l__fontspec_tmpc_int=\count270
\l__fontspec_em_int=\count271
\l__fontspec_emdef_int=\count272
\l__fontspec_strong_int=\count273
\l__fontspec_strongdef_int=\count274
\l__fontspec_tmpa_dim=\dimen147
\l__fontspec_tmpb_dim=\dimen148
\l__fontspec_tmpc_dim=\dimen149
(/usr/share/texmf-dist/tex/latex/base/fontenc.sty
Package: fontenc 2021/04/29 v2.0v Standard LaTeX package
) (/usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg)))
Package fontspec Info: Could not resolve font "Hurmit Nerd Font Mono/I" (it
(fontspec) probably doesn't exist).
Package fontspec Info: Font family 'HurmitNerdFontMono(0)' created for font
(fontspec) 'Hurmit Nerd Font Mono' with options
(fontspec) [WordSpace={1,0,0},HyphenChar=None,PunctuationSpace=WordSpace,Scale=0.7].
(fontspec)
(fontspec) This font family consists of the following NFSS
(fontspec) series/shapes:
(fontspec)
(fontspec) - 'normal' (m/n) with NFSS spec.: <->s*[0.7]"Hurmit
(fontspec) Nerd Font Mono/OT:language=dflt;"
(fontspec) - 'small caps' (m/sc) with NFSS spec.:
(fontspec) and font adjustment code:
(fontspec) \fontdimen 2\font =1\fontdimen 2\font \fontdimen 3\font
(fontspec) =0\fontdimen 3\font \fontdimen 4\font =0\fontdimen
(fontspec) 4\font \fontdimen 7\font =0\fontdimen 2\font
(fontspec) \tex_hyphenchar:D \font =-1\scan_stop:
(fontspec) - 'bold' (b/n) with NFSS spec.: <->s*[0.7]"Hurmit Nerd
(fontspec) Font Mono/B/OT:language=dflt;"
(fontspec) - 'bold small caps' (b/sc) with NFSS spec.:
(fontspec) and font adjustment code:
(fontspec) \fontdimen 2\font =1\fontdimen 2\font \fontdimen 3\font
(fontspec) =0\fontdimen 3\font \fontdimen 4\font =0\fontdimen
(fontspec) 4\font \fontdimen 7\font =0\fontdimen 2\font
(fontspec) \tex_hyphenchar:D \font =-1\scan_stop:
(fontspec) - 'bold italic' (b/it) with NFSS spec.:
(fontspec) <->s*[0.7]"Hurmit Nerd Font Mono/BI/OT:language=dflt;"
(fontspec) - 'bold italic small caps' (b/scit) with NFSS spec.:
(fontspec) and font adjustment code:
(fontspec) \fontdimen 2\font =1\fontdimen 2\font \fontdimen 3\font
(fontspec) =0\fontdimen 3\font \fontdimen 4\font =0\fontdimen
(fontspec) 4\font \fontdimen 7\font =0\fontdimen 2\font
(fontspec) \tex_hyphenchar:D \font =-1\scan_stop:
(./main.aux)
\openout1 = `main.aux'.
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 11.
LaTeX Font Info: Trying to load font information for TS1+cmr on input line 11.
(/usr/share/texmf-dist/tex/latex/base/ts1cmr.fd
File: ts1cmr.fd 2019/12/16 v2.5j Standard LaTeX font definitions
)
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for TU/lmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
*geometry* driver: auto-detecting
*geometry* detected driver: xetex
*geometry* verbose mode - [ preamble ] result:
* driver: xetex
* paper: a4paper
* layout: <same size as paper>
* layoutoffset:(h,v)=(0.0pt,0.0pt)
* modes:
* h-part:(L,W,R)=(72.26999pt, 452.9679pt, 72.26999pt)
* v-part:(T,H,B)=(72.26999pt, 700.50687pt, 72.26999pt)
* \paperwidth=597.50787pt
* \paperheight=845.04684pt
* \textwidth=452.9679pt
* \textheight=700.50687pt
* \oddsidemargin=0.0pt
* \evensidemargin=0.0pt
* \topmargin=-37.0pt
* \headheight=12.0pt
* \headsep=25.0pt
* \topskip=10.0pt
* \footskip=30.0pt
* \marginparwidth=57.0pt
* \marginparsep=11.0pt
* \columnsep=10.0pt
* \skip\footins=9.0pt plus 4.0pt minus 2.0pt
* \hoffset=0.0pt
* \voffset=0.0pt
* \mag=1000
* \@twocolumnfalse
* \@twosidefalse
* \@mparswitchfalse
* \@reversemarginfalse
* (1in=72.27pt=25.4mm, 1cm=28.453pt)
Package fontspec Info: Adjusting the maths setup (use [no-math] to avoid
(fontspec) this).
\symlegacymaths=\mathgroup4
LaTeX Font Info: Overwriting symbol font `legacymaths' in version `bold'
(Font) OT1/cmr/m/n --> OT1/cmr/bx/n on input line 11.
LaTeX Font Info: Redeclaring math accent \acute on input line 11.
LaTeX Font Info: Redeclaring math accent \grave on input line 11.
LaTeX Font Info: Redeclaring math accent \ddot on input line 11.
LaTeX Font Info: Redeclaring math accent \tilde on input line 11.
LaTeX Font Info: Redeclaring math accent \bar on input line 11.
LaTeX Font Info: Redeclaring math accent \breve on input line 11.
LaTeX Font Info: Redeclaring math accent \check on input line 11.
LaTeX Font Info: Redeclaring math accent \hat on input line 11.
LaTeX Font Info: Redeclaring math accent \dot on input line 11.
LaTeX Font Info: Redeclaring math accent \mathring on input line 11.
LaTeX Font Info: Redeclaring math symbol \colon on input line 11.
LaTeX Font Info: Redeclaring math symbol \Gamma on input line 11.
LaTeX Font Info: Redeclaring math symbol \Delta on input line 11.
LaTeX Font Info: Redeclaring math symbol \Theta on input line 11.
LaTeX Font Info: Redeclaring math symbol \Lambda on input line 11.
LaTeX Font Info: Redeclaring math symbol \Xi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Pi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Sigma on input line 11.
LaTeX Font Info: Redeclaring math symbol \Upsilon on input line 11.
LaTeX Font Info: Redeclaring math symbol \Phi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Psi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Omega on input line 11.
LaTeX Font Info: Redeclaring math symbol \mathdollar on input line 11.
LaTeX Font Info: Redeclaring symbol font `operators' on input line 11.
LaTeX Font Info: Encoding `OT1' has changed to `TU' for symbol font
(Font) `operators' in the math version `normal' on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `normal'
(Font) OT1/cmr/m/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Encoding `OT1' has changed to `TU' for symbol font
(Font) `operators' in the math version `bold' on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `bold'
(Font) OT1/cmr/bx/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `normal'
(Font) TU/lmr/m/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathit' in version `normal'
(Font) OT1/cmr/m/it --> TU/lmr/m/it on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathbf' in version `normal'
(Font) OT1/cmr/bx/n --> TU/lmr/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathsf' in version `normal'
(Font) OT1/cmss/m/n --> TU/lmss/m/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathtt' in version `normal'
(Font) OT1/cmtt/m/n --> TU/HurmitNerdFontMono(0)/m/n on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `bold'
(Font) TU/lmr/m/n --> TU/lmr/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathit' in version `bold'
(Font) OT1/cmr/bx/it --> TU/lmr/b/it on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathsf' in version `bold'
(Font) OT1/cmss/bx/n --> TU/lmss/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathtt' in version `bold'
(Font) OT1/cmtt/m/n --> TU/HurmitNerdFontMono(0)/b/n on input line 11.
LaTeX Font Info: External font `cmex10' loaded for size
(Font) <7> on input line 14.
LaTeX Font Info: External font `cmex10' loaded for size
(Font) <5> on input line 14.
LaTeX Font Info: Font shape `TU/HurmitNerdFontMono(0)/m/n' will be
(Font) scaled to size 6.99997pt on input line 22.
[1
] [2] (./main.aux) )
Here is how much of TeX's memory you used:
3535 strings out of 476156
107058 string characters out of 5814963
475344 words of memory out of 5000000
24333 multiletter control sequences out of 15000+600000
469355 words of font info for 40 fonts, out of 8000000 for 9000
1348 hyphenation exceptions out of 8191
72i,9n,77p,271b,304s stack positions out of 5000i,500n,10000p,200000b,80000s
Output written on main.xdv (2 pages, 18856 bytes).

Binary file not shown.

Binary file not shown.

View File

@@ -9,7 +9,7 @@
} }
\begin{document} \begin{document}
\begin{table}[ht] \begin{table}[ht!]
\begin{center} \begin{center}
\begin{tabular}{|lcl|} \begin{tabular}{|lcl|}
\hline \hline
@@ -77,14 +77,15 @@
\end{center} \end{center}
\end{table} \end{table}
\newpage
\begin{itemize} \begin{itemize}
\item \textbf{newline}: \\ Newlines are combined, so a single newline is the same as multiple. \item \textbf{newline}: \\ Newlines are combined, so a single newline is the same as multiple.
\item \textbf{module-name}: \item \textbf{module-name}: \\ Modules are flags given to the interpreter/compiler, to let it know you want to be using certain rules, functions, or features.
\item \textbf{id}: \item \textbf{id}: \\ Variable. Can only consist of lowercase characters and underscores, but not the letters j, u, or w.
\item \textbf{builtin}: \item \textbf{builtin}: \\ Builtin functions are uppercase latin words.
\item \textbf{string}: \item \textbf{string}: \\ Any text encased in " characters.
\item \textbf{numeral}: \item \textbf{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM.
\item \textbf{bool}: \item \textbf{bool}: \\ VERITAS or FALSITAS.
\end{itemize} \end{itemize}
\end{document} \end{document}

Binary file not shown.

701
tests.py Normal file
View File

@@ -0,0 +1,701 @@
import random
import unittest
from io import StringIO
from unittest.mock import patch
from parameterized import parameterized
from centvrion.ast_nodes import (
Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini,
Designa, DumStatement, Erumpe, ExpressionStatement, ID,
Invoca, ModuleCall, Nullus, Numeral, PerStatement,
Program, Redi, SiStatement, String,
num_to_int, int_to_num, make_string,
)
from centvrion.lexer import Lexer
from centvrion.parser import Parser
from centvrion.values import ValInt, ValStr, ValBool, ValList, ValNul, ValFunc
def run_test(self, source, target_nodes, target_value, target_output="", input_lines=[]):
random.seed(1)
lexer = Lexer().get_lexer()
tokens = lexer.lex(source + "\n")
program = Parser().parse(tokens)
##########################
####### Parser Test ###### (commented out — no __eq__ on AST nodes yet)
##########################
# self.assertEqual(
# program,
# target_nodes,
# f"Parser test:\n{program}\n{target_nodes}"
# )
##########################
#### Interpreter Test ####
##########################
captured = StringIO()
try:
if input_lines:
inputs = iter(input_lines)
with patch("builtins.input", lambda: next(inputs)), patch("sys.stdout", captured):
result = program.eval()
else:
with patch("sys.stdout", captured):
result = program.eval()
except Exception as e:
raise e
self.assertEqual(result, target_value, "Return value test")
self.assertEqual(captured.getvalue(), target_output, "Output test")
##########################
###### Printer Test ###### (commented out — no print() on AST nodes yet)
##########################
# try:
# new_text = program.print()
# new_tokens = Lexer().get_lexer().lex(new_text + "\n")
# new_nodes = Parser().parse(new_tokens)
# except Exception as e:
# raise Exception(f"###Printer test###\n{new_text}") from e
# self.assertEqual(
# program,
# new_nodes,
# f"Printer test\n{source}\n{new_text}"
# )
##########################
###### Compiler Test #####
##########################
# try:
# bytecode = program.compile()
# ...
# except Exception as e:
# raise Exception("###Compiler test###") from e
# --- Output ---
output_tests = [
("DICE(\"hello\")", None, ValStr("hello"), "hello\n"),
("DICE(\"world\")", None, ValStr("world"), "world\n"),
("DICE(III)", None, ValStr("III"), "III\n"),
("DICE(X)", None, ValStr("X"), "X\n"),
("DICE(MMXXV)", None, ValStr("MMXXV"), "MMXXV\n"),
("DICE('hello')", None, ValStr("hello"), "hello\n"),
("DICE('world')", None, ValStr("world"), "world\n"),
("DICE(\"a\", \"b\")", None, ValStr("a b"), "a b\n"),
("DICE(\"line one\")\nDICE(\"line two\")", None, ValStr("line two"), "line one\nline two\n"),
("DICE(DICE(II))", None, ValStr("II"), "II\nII\n"),
]
class TestOutput(unittest.TestCase):
@parameterized.expand(output_tests)
def test_output(self, source, nodes, value, output):
run_test(self, source, nodes, value, output)
# --- Arithmetic ---
arithmetic_tests = [
("I + I", None, ValInt(2)),
("X - III", None, ValInt(7)),
("III * IV", None, ValInt(12)),
("X / II", None, ValInt(5)),
("X / III", None, ValInt(3)), # integer division: 10 // 3 = 3
("II + III * IV", None, ValInt(14)), # precedence: 2 + (3*4) = 14
("(II + III) * IV", None, ValInt(20)), # parens: (2+3)*4 = 20
]
class TestArithmetic(unittest.TestCase):
@parameterized.expand(arithmetic_tests)
def test_arithmetic(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Assignment ---
assignment_tests = [
("DESIGNA x VT III\nx", None, ValInt(3)),
("DESIGNA msg VT \"hello\"\nmsg", None, ValStr("hello")),
("DESIGNA msg VT 'hello'\nmsg", None, ValStr("hello")),
("DESIGNA a VT V\nDESIGNA b VT X\na + b", None, ValInt(15)),
("DESIGNA x VT II\nDESIGNA x VT x + I\nx", None, ValInt(3)),
]
class TestAssignment(unittest.TestCase):
@parameterized.expand(assignment_tests)
def test_assignment(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Control flow ---
control_tests = [
# SI without ALVID — true branch
("SI VERITAS TVNC { DESIGNA r VT I }\nr", None, ValInt(1)),
# SI without ALVID — false branch
("SI FALSITAS TVNC { DESIGNA r VT I }", None, ValNul()),
# SI with ALVID — true branch
("SI VERITAS TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(1)),
# SI with ALVID — false branch
("SI FALSITAS TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(2)),
# SI with comparison — equal
("SI I EST I TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(1)),
# SI with comparison — unequal
("SI I EST II TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(2)),
# SI MINVS
("SI I MINVS II TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(1)),
# SI PLVS
("SI II PLVS I TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(1)),
# ALVID SI chain
(
"SI I EST II TVNC { DESIGNA r VT I } ALVID SI I EST I TVNC { DESIGNA r VT II } ALVID { DESIGNA r VT III }\nr",
None, ValInt(2),
),
# DVM (while not): loops until condition is true
(
"DESIGNA x VT I\nDVM x EST III FACE {\nDESIGNA x VT x + I\n}\nx",
None, ValInt(3),
),
# DVM with ERVMPE — loop body prints (testing DICE + ERVMPE together)
("DESIGNA x VT I\nDVM FALSITAS FACE {\nDICE(x)\nERVMPE\n}", None, ValStr("I"), "I\n"),
# PER foreach
("PER i IN [(I, II, III)] FACE { DICE(i) }", None, ValStr("III"), "I\nII\nIII\n"),
# DONICVM range loop
("DONICVM i VT I VSQVE V FACE { DICE(i) }", None, ValStr("IV"), "I\nII\nIII\nIV\n"),
]
class TestControl(unittest.TestCase):
@parameterized.expand(control_tests)
def test_control(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
# --- Functions ---
function_tests = [
(
"DEFINI bis (n) VT { REDI (n * II) }\nINVOCA bis (III)",
None, ValInt(6),
),
(
"DEFINI add (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)",
None, ValInt(7),
),
# Fibonacci: fib(n<3)=1, fib(n)=fib(n-1)+fib(n-2)
(
"DEFINI fib (n) VT {\nSI n MINVS III TVNC { REDI (I) } ALVID { REDI (INVOCA fib (n - I) + INVOCA fib (n - II)) }\n}\nINVOCA fib (VII)",
None, ValInt(13),
),
]
class TestFunctions(unittest.TestCase):
@parameterized.expand(function_tests)
def test_functions(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Builtins ---
builtin_tests = [
("AVDI_NVMERVS()", None, ValInt(3), "", ["III"]),
("AVDI_NVMERVS()", None, ValInt(10), "", ["X"]),
("CVM FORS\nFORTIS_NVMERVS(I, X)", None, ValInt(3)),
("AVDI()", None, ValStr("hello"), "", ["hello"]),
("LONGITVDO([(I, II, III)])", None, ValInt(3)),
("CVM FORS\nFORTIS_ELECTIONIS([(I, II, III)])", None, ValInt(1)),
]
class TestBuiltins(unittest.TestCase):
@parameterized.expand(builtin_tests)
def test_builtins(self, source, nodes, value, output="", input_lines=[]):
run_test(self, source, nodes, value, output, input_lines)
# --- Errors ---
error_tests = [
("x", KeyError), # undefined variable
("INVOCA f ()", KeyError), # undefined function
("DESIGNA VT III", SyntaxError), # parse error: missing id after DESIGNA
("DESIGNA x III", SyntaxError), # parse error: missing VT
("DICE(M + M + M + M)", ValueError), # output > 3999 without MAGNVM
("IIII", ValueError), # invalid Roman numeral in source
("FORTIS_NVMERVS(I, X)", ValueError), # requires FORS module
("DEFINI f (x) VT { REDI(x) }\nINVOCA f (I, II)", TypeError), # too many args
("DEFINI f (x, y) VT { REDI(x) }\nINVOCA f (I)", TypeError), # too few args
("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", TypeError), # args to zero-param function
("SI NVLLVS TVNC { DESIGNA r VT I }", TypeError), # NVLLVS cannot be used as boolean
]
class TestErrors(unittest.TestCase):
@parameterized.expand(error_tests)
def test_errors(self, source, error_type):
with self.assertRaises(error_type):
run_test(self, source, None, None)
# --- Repr ---
repr_tests = [
("string", String("hello"), "String(hello)"),
("numeral", Numeral("III"), "Numeral(III)"),
("bool_true", Bool(True), "Bool(True)"),
("bool_false", Bool(False), "Bool(False)"),
("nullus", Nullus(), "Nullus()"),
("erumpe", Erumpe(), "Erumpe()"),
("module_call", ModuleCall("FORS"), "FORS"),
("id", ID("x"), "ID(x)"),
("expression_stmt", ExpressionStatement(String("hi")), "String(hi)"),
("data_array", DataArray([Numeral("I"), Numeral("II")]), "Array([\n Numeral(I),\n Numeral(II)\n])"),
("data_range_array", DataRangeArray(Numeral("I"), Numeral("X")), "RangeArray([\n Numeral(I),\n Numeral(X)\n])"),
("designa", Designa(ID("x"), Numeral("III")), "Designa(\n ID(x),\n Numeral(III)\n)"),
("binop", BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"), "BinOp(\n Numeral(I),\n Numeral(II),\n SYMBOL_PLUS\n)"),
("redi", Redi([Numeral("I")]), "Redi([\n Numeral(I)\n])"),
("si_no_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], None), "Si(\n Bool(True),\n statements([\n Erumpe()\n ]),\n statements([])\n)"),
("si_with_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], [ExpressionStatement(Erumpe())]), "Si(\n Bool(True),\n statements([\n Erumpe()\n ]),\n statements([\n Erumpe()\n ])\n)"),
("dum", DumStatement(Bool(False), [ExpressionStatement(Erumpe())]), "Dum(\n Bool(False),\n statements([\n Erumpe()\n ])\n)"),
("per", PerStatement(DataArray([Numeral("I")]), ID("i"), [ExpressionStatement(Erumpe())]), "Per(\n Array([\n Numeral(I)\n ]),\n ID(i),\n statements([\n Erumpe()\n ])\n)"),
("invoca", Invoca(ID("f"), [Numeral("I")]), "Invoca(\n ID(f),\n parameters([\n Numeral(I)\n ])\n)"),
("builtin", BuiltIn("DICE", [String("hi")]), "Builtin(\n DICE,\n parameters([\n String(hi)\n ])\n)"),
("defini", Defini(ID("f"), [ID("n")], [ExpressionStatement(Redi([Numeral("I")]))]), "Defini(\n ID(f),\n parameters([\n ID(n)\n ]),\n statements([\n Redi([\n Numeral(I)\n ])\n ])\n)"),
("program_no_modules", Program([], [ExpressionStatement(Numeral("I"))]), "modules([]),\nstatements([\n Numeral(I)\n])"),
("program_with_module", Program([ModuleCall("FORS")], [ExpressionStatement(Numeral("I"))]), "modules([\n FORS\n]),\nstatements([\n Numeral(I)\n])"),
]
class TestRepr(unittest.TestCase):
@parameterized.expand(repr_tests)
def test_repr(self, _, node, expected):
self.assertEqual(repr(node), expected)
# --- Roman numeral utilities ---
class TestNumerals(unittest.TestCase):
# num_to_int: valid cases
def test_simple_numerals(self):
for s, n in [("I",1),("V",5),("X",10),("L",50),("C",100),("D",500),("M",1000)]:
self.assertEqual(num_to_int(s, False), n)
def test_subtractive_forms(self):
for s, n in [("IV",4),("IX",9),("XL",40),("XC",90),("CD",400),("CM",900)]:
self.assertEqual(num_to_int(s, False), n)
def test_complex_numerals(self):
for s, n in [("XLII",42),("XCIX",99),("MCMXCIX",1999),("MMMCMXCIX",3999)]:
self.assertEqual(num_to_int(s, False), n)
# num_to_int: invalid cases
def test_four_in_a_row_raises(self):
with self.assertRaises(Exception):
num_to_int("IIII", False)
def test_four_x_in_a_row_raises(self):
with self.assertRaises(Exception):
num_to_int("XXXX", False)
def test_invalid_subtractive_iix_raises(self):
# IIX is non-standard — I can't appear twice before X
with self.assertRaises(Exception):
num_to_int("IIX", False)
def test_invalid_subtractive_im_raises(self):
# I can only subtract from V and X, not M
with self.assertRaises(Exception):
num_to_int("IM", False)
# int_to_num: valid cases
def test_int_to_num(self):
for n, s in [(1,"I"),(4,"IV"),(9,"IX"),(40,"XL"),(42,"XLII"),(3999,"MMMCMXCIX")]:
self.assertEqual(int_to_num(n, False), s)
def test_int_to_num_above_3999_raises(self):
with self.assertRaises(Exception):
int_to_num(4000, False)
def test_int_to_num_magnvm(self):
# 4000 with MAGNVM enabled
self.assertEqual(int_to_num(4000, True), "MV_")
def test_num_to_int_magnvm_required(self):
# Numbers parsed from strings with _ require MAGNVM
with self.assertRaises(Exception):
num_to_int("V_", False)
# --- make_string ---
class TestMakeString(unittest.TestCase):
def test_str(self):
self.assertEqual(make_string(ValStr("hello")), "hello")
def test_int(self):
self.assertEqual(make_string(ValInt(3)), "III")
def test_bool_true(self):
self.assertEqual(make_string(ValBool(True)), "VERVS")
def test_bool_false(self):
self.assertEqual(make_string(ValBool(False)), "FALSVS")
def test_nul(self):
self.assertEqual(make_string(ValNul()), "NVLLVS")
def test_list(self):
self.assertEqual(make_string(ValList([ValInt(1), ValInt(2)])), "[I II]")
def test_empty_list(self):
self.assertEqual(make_string(ValList([])), "[]")
def test_nested_list(self):
self.assertEqual(
make_string(ValList([ValStr("a"), ValBool(True)])),
"[a VERVS]"
)
# --- DICE with non-integer types ---
dice_type_tests = [
("DICE(VERITAS)", None, ValStr("VERVS"), "VERVS\n"),
("DICE(FALSITAS)", None, ValStr("FALSVS"), "FALSVS\n"),
("DICE(NVLLVS)", None, ValStr("NVLLVS"), "NVLLVS\n"),
('DICE([(I, II)])', None, ValStr("[I II]"), "[I II]\n"),
('DICE("")', None, ValStr(""), "\n"),
# arithmetic result printed as numeral
("DICE(II + III)", None, ValStr("V"), "V\n"),
# multiple args of mixed types
('DICE("x", VERITAS)', None, ValStr("x VERVS"), "x VERVS\n"),
]
class TestDiceTypes(unittest.TestCase):
@parameterized.expand(dice_type_tests)
def test_dice_types(self, source, nodes, value, output):
run_test(self, source, nodes, value, output)
# --- SI/DVM: truthiness of non-bool conditions ---
truthiness_tests = [
# nonzero int is truthy
("SI I TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(1)),
# zero int is falsy
("DESIGNA z VT I - I\nSI z TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(2)),
# non-empty list is truthy
("SI [(I)] TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(1)),
# empty list is falsy
("SI [()] TVNC { DESIGNA r VT II } ALVID { DESIGNA r VT I }\nr", None, ValInt(1)),
# DVM exits when condition becomes truthy
(
"DESIGNA x VT I\nDVM x PLVS III FACE {\nDESIGNA x VT x + I\n}\nx",
None, ValInt(4),
),
]
class TestTruthiness(unittest.TestCase):
@parameterized.expand(truthiness_tests)
def test_truthiness(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Arithmetic: edge cases ---
arithmetic_edge_tests = [
("I - I", None, ValInt(0)), # result zero
("I - V", None, ValInt(-4)), # negative result
("I / V", None, ValInt(0)), # integer division → 0
("M * M", None, ValInt(1000000)), # large intermediate (not displayed)
("(I + II) * (IV - I)", None, ValInt(9)), # nested parens
]
class TestArithmeticEdge(unittest.TestCase):
@parameterized.expand(arithmetic_edge_tests)
def test_arithmetic_edge(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Comparison operators ---
comparison_tests = [
# EST on strings
('\"hello\" EST \"hello\"', None, ValBool(True)),
('\"hello\" EST \"world\"', None, ValBool(False)),
# chain comparisons as conditions
("SI III PLVS II TVNC { DESIGNA r VT I }\nr", None, ValInt(1)),
("SI II PLVS III TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(2)),
# result of comparison is ValBool
("I EST I", None, ValBool(True)),
("I EST II", None, ValBool(False)),
("I MINVS II", None, ValBool(True)),
("II MINVS I", None, ValBool(False)),
("II PLVS I", None, ValBool(True)),
("I PLVS II", None, ValBool(False)),
]
class TestComparisons(unittest.TestCase):
@parameterized.expand(comparison_tests)
def test_comparisons(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Function edge cases ---
function_edge_tests = [
# no explicit REDI → returns ValNul
("DEFINI f () VT { I }\nINVOCA f ()", None, ValNul()),
# REDI multiple values → ValList
(
"DEFINI pair (a, b) VT { REDI (a, b) }\nINVOCA pair (I, II)",
None, ValList([ValInt(1), ValInt(2)]),
),
# function doesn't mutate outer vtable
(
"DESIGNA x VT I\nDEFINI f () VT { DESIGNA x VT V\nREDI (x) }\nINVOCA f ()\nx",
None, ValInt(1),
),
# function can read outer vtable (closure-like)
(
"DESIGNA x VT VII\nDEFINI f () VT { REDI (x) }\nINVOCA f ()",
None, ValInt(7),
),
# function defined after use is still a parse error (definition must precede call at runtime)
# (skipped — ftable is populated at eval time, so definition order matters)
# parameter shadows outer variable inside function
(
"DESIGNA n VT I\nDEFINI f (n) VT { REDI (n * II) }\nINVOCA f (X)\nn",
None, ValInt(1),
),
# function aliasing: assign f to g, invoke via g
(
"DEFINI f (n) VT { REDI (n * II) }\nDESIGNA g VT f\nINVOCA g (V)",
None, ValInt(10),
),
# alias is independent: redefining f doesn't affect g
(
"DEFINI f (n) VT { REDI (n * II) }\nDESIGNA g VT f\nDEFINI f (n) VT { REDI (n * III) }\nINVOCA g (V)",
None, ValInt(10),
),
]
class TestFunctionEdge(unittest.TestCase):
@parameterized.expand(function_edge_tests)
def test_function_edge(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Loop edge cases ---
loop_edge_tests = [
# range(3, 3) is empty — body never runs, program returns ValNul
("DONICVM i VT III VSQVE III FACE { DICE(i) }", None, ValNul(), ""),
# empty array — body never runs
("PER i IN [()] FACE { DICE(i) }", None, ValNul(), ""),
# PER breaks on element 2 — last assigned i is 2
("PER i IN [(I, II, III)] FACE { SI i EST II TVNC { ERVMPE } }\ni", None, ValInt(2), ""),
# nested DVM: inner always breaks; outer runs until btr==3
("DESIGNA btr VT I\nDVM btr EST III FACE {\nDVM FALSITAS FACE {\nERVMPE\n}\nDESIGNA btr VT btr + I\n}\nbtr", None, ValInt(3), ""),
# nested PER: inner always breaks on first element; outer completes both iterations
# cnt starts at 1, increments twice → 3
("DESIGNA cnt VT I\nPER i IN [(I, II)] FACE {\nPER k IN [(I, II)] FACE {\nERVMPE\n}\nDESIGNA cnt VT cnt + I\n}\ncnt", None, ValInt(3), ""),
# DVM condition true from start — body never runs
("DESIGNA x VT I\nDVM VERITAS FACE {\nDESIGNA x VT x + I\n}\nx", None, ValInt(1), ""),
# single iteration: [I VSQVE II] = [1]
("DONICVM i VT I VSQVE II FACE { DICE(i) }", None, ValStr("I"), "I\n"),
]
class TestLoopEdge(unittest.TestCase):
@parameterized.expand(loop_edge_tests)
def test_loop_edge(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
# --- Values: equality and truthiness ---
class TestValues(unittest.TestCase):
def test_valint_equality(self):
self.assertEqual(ValInt(3), ValInt(3))
self.assertNotEqual(ValInt(3), ValInt(4))
def test_valstr_equality(self):
self.assertEqual(ValStr("hi"), ValStr("hi"))
self.assertNotEqual(ValStr("hi"), ValStr("bye"))
def test_valbool_equality(self):
self.assertEqual(ValBool(True), ValBool(True))
self.assertNotEqual(ValBool(True), ValBool(False))
def test_valnul_equality(self):
self.assertEqual(ValNul(), ValNul())
def test_vallist_equality(self):
self.assertEqual(ValList([ValInt(1)]), ValList([ValInt(1)]))
self.assertNotEqual(ValList([ValInt(1)]), ValList([ValInt(2)]))
self.assertNotEqual(ValList([ValInt(1)]), ValList([]))
def test_valint_truthiness(self):
self.assertTrue(bool(ValInt(1)))
self.assertTrue(bool(ValInt(-1)))
self.assertFalse(bool(ValInt(0)))
def test_valstr_truthiness(self):
self.assertTrue(bool(ValStr("x")))
self.assertFalse(bool(ValStr("")))
def test_valbool_truthiness(self):
self.assertTrue(bool(ValBool(True)))
self.assertFalse(bool(ValBool(False)))
def test_vallist_truthiness(self):
self.assertTrue(bool(ValList([ValInt(1)])))
self.assertFalse(bool(ValList([])))
def test_cross_type_inequality(self):
self.assertNotEqual(ValInt(1), ValBool(True))
self.assertNotEqual(ValInt(0), ValNul())
self.assertNotEqual(ValStr(""), ValNul())
# --- MAGNVM module ---
# (ValueError for 4000 without MAGNVM is already in error_tests)
magnvm_tests = [
# M+M+M+M = 4000; MAGNVM allows display as "MV_"
("CVM MAGNVM\nDICE(M + M + M + M)", None, ValStr("MV_"), "MV_\n"),
# I_ = 1000 with MAGNVM (same value as M, but written with thousands operator)
("CVM MAGNVM\nI_", None, ValInt(1000), ""),
# I_ + I_ = 2000; displayed as MM with MAGNVM
("CVM MAGNVM\nDICE(I_ + I_)", None, ValStr("MM"), "MM\n"),
]
class TestMAGNVM(unittest.TestCase):
@parameterized.expand(magnvm_tests)
def test_magnvm(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
# --- ET and AVT (boolean and/or) ---
et_avt_tests = [
("VERITAS ET VERITAS", None, ValBool(True)),
("VERITAS ET FALSITAS", None, ValBool(False)),
("FALSITAS ET VERITAS", None, ValBool(False)),
("FALSITAS ET FALSITAS", None, ValBool(False)),
("VERITAS AVT VERITAS", None, ValBool(True)),
("VERITAS AVT FALSITAS", None, ValBool(True)),
("FALSITAS AVT VERITAS", None, ValBool(True)),
("FALSITAS AVT FALSITAS", None, ValBool(False)),
# short-circuit behaviour: combined with comparisons
("(I EST I) ET (II EST II)", None, ValBool(True)),
("(I EST II) AVT (II EST II)", None, ValBool(True)),
# used as SI condition
("SI VERITAS ET VERITAS TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(1)),
("SI FALSITAS AVT FALSITAS TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr", None, ValInt(2)),
]
class TestEtAvt(unittest.TestCase):
@parameterized.expand(et_avt_tests)
def test_et_avt(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Array indexing ---
# Indexing is 0-based; NVLLVS serves as index 0
array_index_tests = [
# basic indexing
("[(I, II, III)][NVLLVS]", None, ValInt(1)), # index 0 → first element
("[(I, II, III)][I]", None, ValInt(2)), # index 1 → second element
("[(I, II, III)][II]", None, ValInt(3)), # index 2 → third element
# index into a variable
("DESIGNA a VT [(X, XX, XXX)]\na[I]", None, ValInt(20)),
# index into range array
("[I VSQVE V][II]", None, ValInt(3)),
]
class TestArrayIndex(unittest.TestCase):
@parameterized.expand(array_index_tests)
def test_array_index(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Comments ---
comment_tests = [
# trailing line comment
('DICE("hello") // this is ignored', None, ValStr("hello"), "hello\n"),
# line comment on its own line before code
('// ignored\nDICE("hi")', None, ValStr("hi"), "hi\n"),
# inline block comment
('DICE(/* ignored */ "hi")', None, ValStr("hi"), "hi\n"),
# block comment spanning multiple lines
('/* line one\nline two */\nDICE("hi")', None, ValStr("hi"), "hi\n"),
# block comment mid-expression
("II /* ignored */ + III", None, ValInt(5)),
# line comment after expression (no output)
("II + III // ignored", None, ValInt(5)),
# division still works (/ token not confused with //)
("X / II", None, ValInt(5)),
# multiple line comments
('// first\n// second\nDICE("ok")', None, ValStr("ok"), "ok\n"),
# comment-only line between two statements
('DESIGNA x VT I\n// set y\nDESIGNA y VT II\nx + y', None, ValInt(3)),
# blank line between two statements (double newline)
('DESIGNA x VT I\n\nDESIGNA y VT II\nx + y', None, ValInt(3)),
# multiple comment-only lines between statements
('DESIGNA x VT I\n// one\n// two\nDESIGNA y VT III\nx + y', None, ValInt(4)),
]
class TestComments(unittest.TestCase):
@parameterized.expand(comment_tests)
def test_comments(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
# --- Scope ---
scope_tests = [
# SI: variable assigned in true branch persists in outer scope
("SI VERITAS TVNC { DESIGNA r VT X }\nr", None, ValInt(10)),
# SI: variable assigned in ALVID branch persists in outer scope
("SI FALSITAS TVNC { DESIGNA r VT X } ALVID { DESIGNA r VT V }\nr", None, ValInt(5)),
# DVM: variable assigned in body persists after loop exits
# x goes 1→2→3→4→5; r tracks x each iteration; loop exits when x==5
("DESIGNA x VT I\nDVM x EST V FACE { DESIGNA x VT x + I\nDESIGNA r VT x }\nr", None, ValInt(5)),
# PER: loop variable holds last array element after loop (no ERVMPE)
("PER i IN [(I, II, III)] FACE { DESIGNA nop VT I }\ni", None, ValInt(3)),
# PER: reassigning loop var in body doesn't prevent remaining iterations from running
# cnt increments once per iteration (all 3); C=100 doesn't replace next element assignment
("DESIGNA cnt VT I\nPER i IN [(I, II, III)] FACE { DESIGNA i VT C\nDESIGNA cnt VT cnt + I }\ncnt", None, ValInt(4)),
# PER: loop var after loop reflects the last body assignment, not the last array element
# body sets i=C=100 on every iteration; after loop ends, i stays at 100
("PER i IN [(I, II, III)] FACE { DESIGNA i VT C }\ni", None, ValInt(100)),
# DONICVM: counter holds last range value after loop ends
# [I VSQVE IV] = [1,2,3]; last value assigned by loop is III=3
("DONICVM i VT I VSQVE IV FACE { DESIGNA nop VT I }\ni", None, ValInt(3)),
# DONICVM: reassigning counter inside body doesn't reduce the number of iterations
# range [I VSQVE IV] evaluated once; i reset each time; cnt still increments 3 times → 4
("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FACE { DESIGNA cnt VT cnt + I\nDESIGNA i VT C }\ncnt", None, ValInt(4)),
# DONICVM: ERVMPE exits loop early; counter persists at break value
("DONICVM i VT I VSQVE X FACE {\nSI i EST III TVNC { ERVMPE }\n}\ni", None, ValInt(3)),
# Function: modifying parameter inside function does not affect outer variable of same name
# outer n=1; f receives n=5 and modifies its local copy; outer n unchanged
("DESIGNA n VT I\nDEFINI f (n) VT { DESIGNA n VT n + X\nREDI (n) }\nINVOCA f (V)\nn", None, ValInt(1)),
# Function: mutating outer variable inside function (via DESIGNA) is not visible outside
# Invoca creates func_vtable = vtable.copy(); mutations to func_vtable don't propagate back
("DESIGNA x VT I\nDEFINI f () VT { DESIGNA x VT C\nREDI (x) }\nINVOCA f ()\nx", None, ValInt(1)),
# Function: two successive calls with same parameter name don't share state
("DEFINI f (n) VT { REDI (n * II) }\nINVOCA f (III) + INVOCA f (IV)", None, ValInt(14)),
# Function: calling f(I) with param named n does not overwrite outer n=II
# f is defined before n is designated; INVOCA creates a local copy, outer vtable unchanged
("DEFINI f (n) VT { REDI (n * II) }\nDESIGNA n VT II\nINVOCA f (I)\nn", None, ValInt(2)),
]
class TestScope(unittest.TestCase):
@parameterized.expand(scope_tests)
def test_scope(self, source, nodes, value):
run_test(self, source, nodes, value)
if __name__ == "__main__":
unittest.main()

View File

@@ -1,11 +0,0 @@
DEFINI fib(x) VT {
SI x EST NVLLVS TVNC {
REDI(NVLLVS)
} ALVID SI x EST I TVNC {
REDI(I)
} ALVID {
REDI(INVOCA fib(x-II) + INVOCA fib(x-I))
}
}
DICE(INVOCA fib(AVDI_NVMERVS()))

View File

@@ -1 +0,0 @@
X

View File

@@ -1,7 +1,7 @@
{ {
"name": "centvrion", "name": "centvrion",
"displayName": "centvrion", "displayName": "centvrion",
"description": "", "description": "Latin-inspired esoteric programming language with Roman numeral literals",
"version": "0.0.1", "version": "0.0.1",
"engines": { "engines": {
"vscode": "^1.68.0" "vscode": "^1.68.0"

View File

@@ -23,7 +23,7 @@
"patterns": [ "patterns": [
{ {
"name": "keyword.control.cent", "name": "keyword.control.cent",
"match": "\\b(ALVID|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|FACE|INVOCA|IN|PER|SI|TVNC|VSQVE|VT|CVM)\\b" "match": "\\b(ALVID|AVT|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|ET|FACE|INVOCA|IN|PER|SI|TVNC|VSQVE|VT|CVM)\\b"
}, },
{ {
"name": "keyword.operator.comparison.cent", "name": "keyword.operator.comparison.cent",