From e845cb62c10b773ba0caf120f0cd6508e865190a Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Tue, 31 Mar 2026 18:25:20 +0200 Subject: [PATCH] :goat: Reviving this old project. Mainly adding tests and fixing bugs. --- .gitignore | 5 +- README.md | 12 +- centvrion/ast_nodes.py | 850 +++++++++--------- centvrion/lexer.py | 128 +-- centvrion/parser.py | 333 +++---- centvrion/values.py | 87 ++ examples/guessing.cent | 16 +- language/main.aux | 2 - language/main.fdb_latexmk | 30 - language/main.fls | 146 --- language/main.log | 251 ------ language/main.pdf | Bin 17218 -> 20111 bytes language/main.synctex.gz | Bin 8175 -> 0 bytes language/main.tex | 15 +- language/main.xdv | Bin 18856 -> 0 bytes tests.py | 701 +++++++++++++++ tests/fib.cent | 11 - tests/fib.input | 1 - vscode-extension/package.json | 2 +- .../syntaxes/cent.tmLanguage.json | 2 +- 20 files changed, 1502 insertions(+), 1090 deletions(-) create mode 100644 centvrion/values.py delete mode 100644 language/main.aux delete mode 100644 language/main.fdb_latexmk delete mode 100644 language/main.fls delete mode 100644 language/main.log delete mode 100644 language/main.synctex.gz delete mode 100644 language/main.xdv create mode 100644 tests.py delete mode 100644 tests/fib.cent delete mode 100644 tests/fib.input diff --git a/.gitignore b/.gitignore index 40bb4e3..cceecf2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .vscode/ -__pycache__/ \ No newline at end of file +__pycache__/ + +.claude/ +CLAUDE.md \ No newline at end of file diff --git a/README.md b/README.md index 97c8fae..621b331 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ### Hello World ``` DESIGNA x VT "Hello World!" -DICE x +DICE(x) ``` ### 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: ``` -DESIGNA x VT [1 VSQVE 10] +DESIGNA x VT [I VSQVE X] ``` ## Conditionals @@ -110,10 +110,10 @@ If-then statements are denoted with the keywords `SI` (if) and `TVNC` (then). Th DESIGNA x VT VERITAS SI x TVNC { DICE(I) - REDI(NVLLLVS) + REDI(NVLLVS) } -DICE NVLLVS +DICE(NVLLVS) > I ``` @@ -209,12 +209,12 @@ PER y IN x FACE { ``` ## 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. ``` -DEFINI square x VT { +DEFINI square(x) VT { REDI(x*x) } diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index 3613566..b9e2ce5 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -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 diff --git a/centvrion/lexer.py b/centvrion/lexer.py index dbbba44..d782962 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -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() diff --git a/centvrion/parser.py b/centvrion/parser.py index 77e1f61..1a276f0 100644 --- a/centvrion/parser.py +++ b/centvrion/parser.py @@ -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 diff --git a/centvrion/values.py b/centvrion/values.py new file mode 100644 index 0000000..87f6655 --- /dev/null +++ b/centvrion/values.py @@ -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 diff --git a/examples/guessing.cent b/examples/guessing.cent index 4dc8dc1..ef4866b 100644 --- a/examples/guessing.cent +++ b/examples/guessing.cent @@ -4,14 +4,14 @@ DESIGNA correct VT FORTIS_NVMERVS(I,C) DESIGNA gvess VT NVLLVS DVM FALSITAS FACE { - DESIGNA gvess VT AVDI_NVMERVS() - SI gvess MINVS correct TVNC { - DICE("Too low!") - } ALVID SI gvess PLVS correct TVNC { - DICE("Too high!") - } ALVID { - ERVMPE - } + DESIGNA gvess VT AVDI_NVMERVS() + SI gvess MINVS correct TVNC { + DICE("Too low!") + } ALVID SI gvess PLVS correct TVNC { + DICE("Too high!") + } ALVID { + ERVMPE + } } DICE("You guessed correctly!") \ No newline at end of file diff --git a/language/main.aux b/language/main.aux deleted file mode 100644 index d490c35..0000000 --- a/language/main.aux +++ /dev/null @@ -1,2 +0,0 @@ -\relax -\gdef \@abspage@last{2} diff --git a/language/main.fdb_latexmk b/language/main.fdb_latexmk deleted file mode 100644 index 89efc33..0000000 --- a/language/main.fdb_latexmk +++ /dev/null @@ -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" diff --git a/language/main.fls b/language/main.fls deleted file mode 100644 index 9bd8324..0000000 --- a/language/main.fls +++ /dev/null @@ -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 diff --git a/language/main.log b/language/main.log deleted file mode 100644 index fda90cc..0000000 --- a/language/main.log +++ /dev/null @@ -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: -* 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). diff --git a/language/main.pdf b/language/main.pdf index 1934e226534ff83e58519d62f4fcb8eee846b7bb..445ef6e31c57c0e2e7c5ecfbda8f5eee9954a95b 100644 GIT binary patch delta 13402 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%0Adx>>02}yr>NQI6cM{+>f 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 ZKhDD*80?=G8vHi^{s&N%9dn(x{{v;qcP;<` diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..39e4c76 --- /dev/null +++ b/tests.py @@ -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() diff --git a/tests/fib.cent b/tests/fib.cent deleted file mode 100644 index c10cadd..0000000 --- a/tests/fib.cent +++ /dev/null @@ -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())) diff --git a/tests/fib.input b/tests/fib.input deleted file mode 100644 index 500c070..0000000 --- a/tests/fib.input +++ /dev/null @@ -1 +0,0 @@ -X \ No newline at end of file diff --git a/vscode-extension/package.json b/vscode-extension/package.json index be0d62b..863d117 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -1,7 +1,7 @@ { "name": "centvrion", "displayName": "centvrion", - "description": "", + "description": "Latin-inspired esoteric programming language with Roman numeral literals", "version": "0.0.1", "engines": { "vscode": "^1.68.0" diff --git a/vscode-extension/syntaxes/cent.tmLanguage.json b/vscode-extension/syntaxes/cent.tmLanguage.json index 9affe61..8a4918b 100644 --- a/vscode-extension/syntaxes/cent.tmLanguage.json +++ b/vscode-extension/syntaxes/cent.tmLanguage.json @@ -23,7 +23,7 @@ "patterns": [ { "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",