From 37fdff2db5f7a1fda18c619c3f1f5a4715714125 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Fri, 24 Apr 2026 19:13:48 +0200 Subject: [PATCH] :goat: Tests --- pyproject.toml | 3 + tests.py | 3626 ------------------------------------- tests/01_test_core____.py | 450 +++++ tests/02_test_con_flow.py | 418 +++++ tests/03_test_builtins.py | 226 +++ tests/04_test_numerals.py | 175 ++ tests/05_test_literals.py | 402 ++++ tests/06_test_booleans.py | 223 +++ tests/07_test_arrays__.py | 273 +++ tests/08_test_tabulas_.py | 191 ++ tests/09_test_fraction.py | 263 +++ tests/10_test_external.py | 406 +++++ tests/11_test_assorted.py | 443 +++++ tests/12_test_failures.py | 148 ++ tests/__init__.py | 0 tests/_helpers.py | 135 ++ 16 files changed, 3756 insertions(+), 3626 deletions(-) create mode 100644 pyproject.toml delete mode 100644 tests.py create mode 100644 tests/01_test_core____.py create mode 100644 tests/02_test_con_flow.py create mode 100644 tests/03_test_builtins.py create mode 100644 tests/04_test_numerals.py create mode 100644 tests/05_test_literals.py create mode 100644 tests/06_test_booleans.py create mode 100644 tests/07_test_arrays__.py create mode 100644 tests/08_test_tabulas_.py create mode 100644 tests/09_test_fraction.py create mode 100644 tests/10_test_external.py create mode 100644 tests/11_test_assorted.py create mode 100644 tests/12_test_failures.py create mode 100644 tests/__init__.py create mode 100644 tests/_helpers.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2fd691e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.pytest.ini_options] +python_files = ["[0-9][0-9]_test_*.py", "test_*.py"] +testpaths = ["tests"] diff --git a/tests.py b/tests.py deleted file mode 100644 index 7a541da..0000000 --- a/tests.py +++ /dev/null @@ -1,3626 +0,0 @@ -import os -import subprocess -import tempfile -import time -import unittest -from io import StringIO -from unittest.mock import patch -from parameterized import parameterized - -from fractions import Fraction - -from centvrion.ast_nodes import ( - ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, - Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, - Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, - ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, - String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, - fraction_to_frac, num_to_int, int_to_num, make_string, - _cent_rng, -) -from centvrion.compiler.emitter import compile_program -from centvrion.errors import CentvrionError -from centvrion.lexer import Lexer -from centvrion.parser import Parser -from centvrion.values import ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac - -_RUNTIME_C = os.path.join( - os.path.dirname(__file__), - "centvrion", "compiler", "runtime", "cent_runtime.c" -) - -def run_test(self, source, target_nodes, target_value, target_output="", input_lines=[]): - _cent_rng.seed(1) - - lexer = Lexer().get_lexer() - tokens = lexer.lex(source + "\n") - program = Parser().parse(tokens) - - ########################## - ####### Parser Test ###### - ########################## - if target_nodes is not None: - 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 ###### - ########################## - 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 ##### - ########################## - c_source = compile_program(program) - # Force deterministic RNG seed=1 for test reproducibility - c_source = c_source.replace("cent_init();", "cent_init(); cent_semen((CentValue){.type=CENT_INT, .ival=1});", 1) - with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: - tmp_c.write(c_source) - tmp_c_path = tmp_c.name - with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: - tmp_bin_path = tmp_bin.name - try: - subprocess.run( - ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], - check=True, capture_output=True, - ) - stdin_data = "".join(f"{l}\n" for l in input_lines) - proc = subprocess.run( - [tmp_bin_path], - input=stdin_data, capture_output=True, text=True, - ) - self.assertEqual(proc.returncode, 0, f"Compiler binary exited non-zero:\n{proc.stderr}") - self.assertEqual(proc.stdout, target_output, "Compiler output test") - finally: - os.unlink(tmp_c_path) - os.unlink(tmp_bin_path) - - assert target_nodes is not None, "All tests must have target nodes" - - -# --- Output --- - -output_tests = [ - ("DIC(\"hello\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"), - ("DIC(\"world\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("world")]))]), ValStr("world"), "world\n"), - ("DIC(III)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("III")]))]), ValStr("III"), "III\n"), - ("DIC(X)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("X")]))]), ValStr("X"), "X\n"), - ("DIC(MMXXV)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("MMXXV")]))]), ValStr("MMXXV"), "MMXXV\n"), - ("DIC('hello')", Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"), - ("DIC('world')", Program([], [ExpressionStatement(BuiltIn("DIC", [String("world")]))]), ValStr("world"), "world\n"), - ("DIC(\"a\", \"b\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("a"), String("b")]))]), ValStr("a b"), "a b\n"), - ("DIC(\"line one\")\nDIC(\"line two\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("line one")])), ExpressionStatement(BuiltIn("DIC", [String("line two")]))]), ValStr("line two"), "line one\nline two\n"), - ("DIC(DIC(II))", Program([], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("DIC", [Numeral("II")])]))]), ValStr("II"), "II\nII\n"), - ("EVERRE()", Program([], [ExpressionStatement(BuiltIn("EVERRE", []))]), ValNul(), "\033[2J\033[H"), -] - -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", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"))]), ValInt(2)), - ("X - III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_MINUS"))]), ValInt(7)), - ("III * IV", Program([], [ExpressionStatement(BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(12)), - ("X / II", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))]), ValInt(5)), - ("X / III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(3)), # integer division: 10 // 3 = 3 - ("X RELIQVVM III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(1)), # 10 % 3 = 1 - ("IX RELIQVVM III", Program([], [ExpressionStatement(BinOp(Numeral("IX"), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(0)), # exact divisor - ("VII RELIQVVM X", Program([], [ExpressionStatement(BinOp(Numeral("VII"), Numeral("X"), "KEYWORD_RELIQVVM"))]), ValInt(7)), # dividend < divisor - ("II + III * IV", Program([], [ExpressionStatement(BinOp(Numeral("II"), BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"), "SYMBOL_PLUS"))]), ValInt(14)), # precedence: 2 + (3*4) = 14 - ("(II + III) * IV", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(20)), # parens: (2+3)*4 = 20 - ("CVM SVBNVLLA\n- III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(Numeral("III")))]), ValInt(-3)), # unary negation - ("CVM SVBNVLLA\n- (II + III)", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")))]), ValInt(-5)), # unary negation of expression - ("CVM SVBNVLLA\n- - II", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(UnaryMinus(Numeral("II"))))]), ValInt(2)), # double negation - ("CVM SVBNVLLA\nIII + - II", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("III"), UnaryMinus(Numeral("II")), "SYMBOL_PLUS"))]), ValInt(1)), # unary in binary context -] - -class TestArithmetic(unittest.TestCase): - @parameterized.expand(arithmetic_tests) - def test_arithmetic(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Precedence and associativity --- -# -# Precedence (lowest → highest): -# AVT < ET < (EST, DISPAR, PLVS, MINVS) < (+ -) < (* / RELIQVVM) < UMINUS < INDEX - -precedence_tests = [ - # * binds tighter than -: 10 - (2*3) = 4, not (10-2)*3 = 24 - ("X - II * III", - Program([], [ExpressionStatement(BinOp(Numeral("X"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), "SYMBOL_MINUS"))]), - ValInt(4)), - # / binds tighter than +: (10/2) + 3 = 8, not 10/(2+3) = 2 - ("X / II + III", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_PLUS"))]), - ValInt(8)), - # + binds tighter than EST: (2+3)==5 = True, not 2+(3==5) = type error - ("II + III EST V", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("V"), "KEYWORD_EST"))]), - ValBool(True)), - # * binds tighter than PLVS: (2*3)>4 = True - ("II * III PLVS IV", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), Numeral("IV"), "KEYWORD_PLVS"))]), - ValBool(True)), - # comparison binds tighter than ET: (1==2) AND (2==2) = False AND True = False - ("I EST II ET II EST II", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]), - ValBool(False)), - # + binds tighter than DISPAR: (2+3)!=5 = False, not 2+(3!=5) = type error - ("II + III DISPAR V", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("V"), "KEYWORD_DISPAR"))]), - ValBool(False)), - # DISPAR binds tighter than ET: (1!=2) AND (2!=2) = True AND False = False - ("I DISPAR II ET II DISPAR II", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_DISPAR"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_DISPAR"), "KEYWORD_ET"))]), - ValBool(False)), - # ET binds tighter than AVT: True OR (False AND False) = True - ("VERITAS AVT FALSITAS ET FALSITAS", - Program([], [ExpressionStatement(BinOp(Bool(True), BinOp(Bool(False), Bool(False), "KEYWORD_ET"), "KEYWORD_AVT"))]), - ValBool(True)), - # UMINUS binds tighter than *: (-2)*3 = -6, not -(2*3) = -6 (same value, different tree) - ("CVM SVBNVLLA\n- II * III", - Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_TIMES"))]), - ValInt(-6)), - # UMINUS binds tighter than +: (-2)+3 = 1, not -(2+3) = -5 - ("CVM SVBNVLLA\n- II + III", - Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_PLUS"))]), - ValInt(1)), - # INDEX binds tighter than UMINUS: -(arr[I]) = -1 - ("CVM SVBNVLLA\n- [I, II, III][I]", - Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I"))))]), - ValInt(-1)), - # INDEX binds tighter than NON: NON (arr[I]) = NON VERITAS = False - ("NON [VERITAS, FALSITAS][I]", - Program([], [ExpressionStatement(UnaryNot(ArrayIndex(DataArray([Bool(True), Bool(False)]), Numeral("I"))))]), - ValBool(False)), - # INDEX binds tighter than +: (arr[II]) + X = 2 + 10 = 12 - ("[I, II, III][II] + X", - Program([], [ExpressionStatement(BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")), Numeral("X"), "SYMBOL_PLUS"))]), - ValInt(12)), - # left-associativity of -: (10-3)-2 = 5, not 10-(3-2) = 9 - ("X - III - II", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_MINUS"), Numeral("II"), "SYMBOL_MINUS"))]), - ValInt(5)), - # left-associativity of /: (12/2)/3 = 2, not 12/(2/3) = 18 - ("XII / II / III", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("XII"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_DIVIDE"))]), - ValInt(2)), - # RELIQVVM same precedence as *, /; left-associative: (17 % 5) % 2 = 0 - ("XVII RELIQVVM V RELIQVVM II", - Program([], [ExpressionStatement( - BinOp(BinOp(Numeral("XVII"), Numeral("V"), "KEYWORD_RELIQVVM"), - Numeral("II"), "KEYWORD_RELIQVVM"))]), - ValInt(0)), - # RELIQVVM binds tighter than +: 2 + (7 % 3) = 3, not (2+7) % 3 = 0 - ("II + VII RELIQVVM III", - Program([], [ExpressionStatement( - BinOp(Numeral("II"), - BinOp(Numeral("VII"), Numeral("III"), "KEYWORD_RELIQVVM"), - "SYMBOL_PLUS"))]), - ValInt(3)), - # left-associativity of AVT: (False OR True) OR False = True - ("FALSITAS AVT VERITAS AVT FALSITAS", - Program([], [ExpressionStatement(BinOp(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"), Bool(False), "KEYWORD_AVT"))]), - ValBool(True)), -] - -class TestPrecedence(unittest.TestCase): - @parameterized.expand(precedence_tests) - def test_precedence(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Assignment --- - -assignment_tests = [ - ("DESIGNA x VT III\nx", - Program([], [Designa(ID("x"), Numeral("III")), ExpressionStatement(ID("x"))]), - ValInt(3)), - ("DESIGNA msg VT \"hello\"\nmsg", - Program([], [Designa(ID("msg"), String("hello")), ExpressionStatement(ID("msg"))]), - ValStr("hello")), - ("DESIGNA msg VT 'hello'\nmsg", - Program([], [Designa(ID("msg"), String("hello")), ExpressionStatement(ID("msg"))]), - ValStr("hello")), - ("DESIGNA a VT V\nDESIGNA b VT X\na + b", - Program([], [Designa(ID("a"), Numeral("V")), Designa(ID("b"), Numeral("X")), - ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"))]), - ValInt(15)), - ("DESIGNA x VT II\nDESIGNA x VT x + I\nx", - Program([], [Designa(ID("x"), Numeral("II")), - Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), - ExpressionStatement(ID("x"))]), - ValInt(3)), - # Compound assignment — AVGE (+=) - ("DESIGNA x VT V\nx AVGE III\nx", - Program([], [Designa(ID("x"), Numeral("V")), - Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_PLUS")), - ExpressionStatement(ID("x"))]), - ValInt(8)), - # Compound assignment — MINVE (-=) - ("DESIGNA x VT X\nx MINVE III\nx", - Program([], [Designa(ID("x"), Numeral("X")), - Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_MINUS")), - ExpressionStatement(ID("x"))]), - ValInt(7)), - # AVGE with complex expression - ("DESIGNA x VT I\nx AVGE II + III\nx", - Program([], [Designa(ID("x"), Numeral("I")), - Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), "SYMBOL_PLUS")), - ExpressionStatement(ID("x"))]), - ValInt(6)), - # AVGE inside a loop (DONICVM range is inclusive: I VSQVE III = [1, 2, 3]) - ("DESIGNA s VT NVLLVS\nDONICVM i VT I VSQVE III FAC {\ns AVGE i\n}\ns", - Program([], [Designa(ID("s"), Nullus()), - PerStatement(DataRangeArray(Numeral("I"), Numeral("III")), ID("i"), - [Designa(ID("s"), BinOp(ID("s"), ID("i"), "SYMBOL_PLUS"))]), - ExpressionStatement(ID("s"))]), - ValInt(6)), - # Compound assignment — MVLTIPLICA (*=) - ("DESIGNA x VT III\nx MVLTIPLICA II\nx", - Program([], [Designa(ID("x"), Numeral("III")), - Designa(ID("x"), BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")), - ExpressionStatement(ID("x"))]), - ValInt(6)), - # Compound assignment — DIVIDE (/=) - ("DESIGNA x VT XII\nx DIVIDE III\nx", - Program([], [Designa(ID("x"), Numeral("XII")), - Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_DIVIDE")), - ExpressionStatement(ID("x"))]), - ValInt(4)), - # MVLTIPLICA with complex RHS — whole expression is captured before the op - ("DESIGNA x VT II\nx MVLTIPLICA II + I\nx", - Program([], [Designa(ID("x"), Numeral("II")), - Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("I"), "SYMBOL_PLUS"), "SYMBOL_TIMES")), - ExpressionStatement(ID("x"))]), - ValInt(6)), - # DIVIDE with complex RHS - ("DESIGNA x VT XX\nx DIVIDE II + II\nx", - Program([], [Designa(ID("x"), Numeral("XX")), - Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS"), "SYMBOL_DIVIDE")), - ExpressionStatement(ID("x"))]), - ValInt(5)), -] - -class TestAssignment(unittest.TestCase): - @parameterized.expand(assignment_tests) - def test_assignment(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Destructuring --- - -destructuring_tests = [ - # basic: unpack multi-return function - ( - "DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (III, VII)\nx + y", - Program([], [ - Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]), - DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("III"), Numeral("VII")])), - ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS")), - ]), - ValInt(10), - ), - # unpack array literal - ( - "DESIGNA a, b VT [I, II]\na + b", - Program([], [ - DesignaDestructure([ID("a"), ID("b")], DataArray([Numeral("I"), Numeral("II")])), - ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")), - ]), - ValInt(3), - ), - # three variables - ( - "DESIGNA a, b, c VT [X, XX, XXX]\na + b + c", - Program([], [ - DesignaDestructure([ID("a"), ID("b"), ID("c")], DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])), - ExpressionStatement(BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")), - ]), - ValInt(60), - ), - # destructure into individual use - ( - "DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (V, II)\nDIC(x)\nDIC(y)", - Program([], [ - Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]), - DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("V"), Numeral("II")])), - ExpressionStatement(BuiltIn("DIC", [ID("x")])), - ExpressionStatement(BuiltIn("DIC", [ID("y")])), - ]), - ValStr("II"), - "V\nII\n", - ), -] - -class TestDestructuring(unittest.TestCase): - @parameterized.expand(destructuring_tests) - def test_destructuring(self, source, nodes, value, output=""): - run_test(self, source, nodes, value, output) - - -# --- Control flow --- - -control_tests = [ - # SI without ALIVD — true branch - ("SI VERITAS TVNC { DESIGNA r VT I }\nr", - Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("I"))], None), ExpressionStatement(ID("r"))]), - ValInt(1)), - # SI without ALIVD — false branch - ("SI FALSITAS TVNC { DESIGNA r VT I }", - Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("I"))], None)]), - ValNul()), - # SI with ALIVD — true branch - ("SI VERITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(1)), - # SI with ALIVD — false branch - ("SI FALSITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(2)), - # SI with comparison — equal - ("SI I EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(1)), - # SI with comparison — unequal - ("SI I EST II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(2)), - # SI MINVS - ("SI I MINVS II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_MINVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(1)), - # SI PLVS - ("SI II PLVS I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(1)), - # ALIVD SI chain - ( - "SI I EST II TVNC { DESIGNA r VT I } ALIVD SI I EST I TVNC { DESIGNA r VT II } ALIVD { DESIGNA r VT III }\nr", - Program([], [ - SiStatement( - BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), - [Designa(ID("r"), Numeral("I"))], - [SiStatement( - BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), - [Designa(ID("r"), Numeral("II"))], - [Designa(ID("r"), Numeral("III"))], - )], - ), - ExpressionStatement(ID("r")), - ]), - ValInt(2), - ), - # DVM (while not): loops until condition is true - ( - "DESIGNA x VT I\nDVM x EST III FAC {\nDESIGNA x VT x + I\n}\nx", - Program([], [ - Designa(ID("x"), Numeral("I")), - DumStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]), - ExpressionStatement(ID("x")), - ]), - ValInt(3), - ), - # DVM with ERVMPE — loop body prints (testing DIC + ERVMPE together) - ("DESIGNA x VT I\nDVM FALSITAS FAC {\nDIC(x)\nERVMPE\n}", - Program([], [ - Designa(ID("x"), Numeral("I")), - DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DIC", [ID("x")])), Erumpe()]), - ]), - ValStr("I"), "I\n"), - # AETERNVM is sugar for DVM FALSITAS — must produce the same AST. - ("DESIGNA x VT I\nAETERNVM FAC {\nDIC(x)\nERVMPE\n}", - Program([], [ - Designa(ID("x"), Numeral("I")), - DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DIC", [ID("x")])), Erumpe()]), - ]), - ValStr("I"), "I\n"), - # AETERNVM with counter + ERVMPE on condition - ("DESIGNA x VT I\nAETERNVM FAC {\nSI x EST III TVNC { ERVMPE }\nDESIGNA x VT x + I\n}\nx", - Program([], [ - Designa(ID("x"), Numeral("I")), - DumStatement(Bool(False), [ - SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None), - Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), - ]), - ExpressionStatement(ID("x")), - ]), - ValInt(3)), - # AETERNVM with CONTINVA — skip printing III; ERVMPE after V. - # Return value is ValNul because the iteration that triggers ERVMPE runs - # Designa first (resetting last_val); we test on output, which is the point. - ("DESIGNA x VT NVLLVS\nAETERNVM FAC {\nDESIGNA x VT x + I\nSI x PLVS V TVNC { ERVMPE }\nSI x EST III TVNC { CONTINVA }\nDIC(x)\n}", - Program([], [ - Designa(ID("x"), Nullus()), - DumStatement(Bool(False), [ - Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), - SiStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_PLVS"), [Erumpe()], None), - SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Continva()], None), - ExpressionStatement(BuiltIn("DIC", [ID("x")])), - ]), - ]), - ValNul(), "I\nII\nIV\nV\n"), - # REDI inside AETERNVM (inside DEFINI) — exits both loop and function - ( - "DEFINI f () VT {\nDESIGNA x VT I\nAETERNVM FAC {\nREDI (x)\n}\n}\nINVOCA f ()", - Program([], [ - Defini(ID("f"), [], [ - Designa(ID("x"), Numeral("I")), - DumStatement(Bool(False), [Redi([ID("x")])]), - ]), - ExpressionStatement(Invoca(ID("f"), [])), - ]), - ValInt(1), - ), - # PER foreach - ("PER i IN [I, II, III] FAC { DIC(i) }", - Program([], [PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("III"), "I\nII\nIII\n"), - # DONICVM range loop - ("DONICVM i VT I VSQVE V FAC { DIC(i) }", - Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("V"), "I\nII\nIII\nIV\nV\n"), - # PER destructuring - ("PER a, b IN [[I, II], [III, IV]] FAC { DIC(a + b) }", - Program([], [PerStatement( - DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]), - [ID("a"), ID("b")], - [ExpressionStatement(BuiltIn("DIC", [BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")]))])]), - ValStr("VII"), "III\nVII\n"), - # PER destructuring: three variables - ("PER a, b, c IN [[I, II, III]] FAC { DIC(a + b + c) }", - Program([], [PerStatement( - DataArray([DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]), - [ID("a"), ID("b"), ID("c")], - [ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")]))])]), - ValStr("VI"), "VI\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)", - Program([], [ - Defini(ID("bis"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), - ExpressionStatement(Invoca(ID("bis"), [Numeral("III")])), - ]), - ValInt(6), - ), - ( - "DEFINI add (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)", - Program([], [ - Defini(ID("add"), [ID("a"), ID("b")], [Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")])]), - ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])), - ]), - 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) } ALIVD { REDI (INVOCA fib (n - I) + INVOCA fib (n - II)) }\n}\nINVOCA fib (VII)", - Program([], [ - Defini(ID("fib"), [ID("n")], [ - SiStatement( - BinOp(ID("n"), Numeral("III"), "KEYWORD_MINVS"), - [Redi([Numeral("I")])], - [Redi([BinOp( - Invoca(ID("fib"), [BinOp(ID("n"), Numeral("I"), "SYMBOL_MINUS")]), - Invoca(ID("fib"), [BinOp(ID("n"), Numeral("II"), "SYMBOL_MINUS")]), - "SYMBOL_PLUS", - )])], - ), - ]), - ExpressionStatement(Invoca(ID("fib"), [Numeral("VII")])), - ]), - 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()", Program([], [ExpressionStatement(BuiltIn("AVDI_NVMERVS", []))]), ValInt(3), "", ["III"]), - ("AVDI_NVMERVS()", Program([], [ExpressionStatement(BuiltIn("AVDI_NVMERVS", []))]), ValInt(10), "", ["X"]), - ("CVM FORS\nDESIGNA a VT [I, II, III]\nDIC(a[FORTVITVS_NVMERVS(I, LONGITVDO(a))])", Program([ModuleCall("FORS")], [Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), ExpressionStatement(BuiltIn("DIC", [ArrayIndex(ID("a"), BuiltIn("FORTVITVS_NVMERVS", [Numeral("I"), BuiltIn("LONGITVDO", [ID("a")])]))]))]), ValStr("I"), "I\n"), - ("AVDI()", Program([], [ExpressionStatement(BuiltIn("AVDI", []))]), ValStr("hello"), "", ["hello"]), - ("LONGITVDO([I, II, III])", Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValInt(3)), - ("LONGITVDO([])", Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataArray([])]))]), ValInt(0)), - ('LONGITVDO("salve")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("salve")]))]), ValInt(5)), - ('LONGITVDO("")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("")]))]), ValInt(0)), - ("CVM FORS\nDIC(FORTVITA_ELECTIO([I, II, III]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("FORTVITA_ELECTIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])])]))]), ValStr("I"), "I\n"), - ("CVM FORS\nSEMEN(XLII)\nDIC(FORTVITVS_NVMERVS(I, C))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("FORTVITVS_NVMERVS", [Numeral("I"), Numeral("C")])]))]), ValStr("XXXIII"), "XXXIII\n"), - # DECIMATIO: seed 42, 10 elements → removes 1 (element III) - ("CVM FORS\nSEMEN(XLII)\nDIC(DECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X")])])]))]), ValStr("[I II IV V VI VII VIII IX X]"), "[I II IV V VI VII VIII IX X]\n"), - # DECIMATIO: seed 1, 3 elements → 3//10=0, nothing removed - ("CVM FORS\nSEMEN(I)\nDIC(DECIMATIO([I, II, III]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("I")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])])]))]), ValStr("[I II III]"), "[I II III]\n"), - # DECIMATIO: empty array → empty array - ("CVM FORS\nDIC(DECIMATIO([]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([])])]))]), ValStr("[]"), "[]\n"), - # DECIMATIO: seed 42, 20 elements → removes 2 (elements XIII and XII) - ("CVM FORS\nSEMEN(XLII)\nDIC(DECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X"), Numeral("XI"), Numeral("XII"), Numeral("XIII"), Numeral("XIV"), Numeral("XV"), Numeral("XVI"), Numeral("XVII"), Numeral("XVIII"), Numeral("XIX"), Numeral("XX")])])]))]), ValStr("[I II III IV V VI VII VIII IX X XI XIV XV XVI XVII XVIII XIX XX]"), "[I II III IV V VI VII VIII IX X XI XIV XV XVI XVII XVIII XIX XX]\n"), - # SENATVS: majority true → VERITAS - ("SENATVS(VERITAS, VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False)]))]), ValBool(True)), - # SENATVS: majority false → FALSITAS - ("SENATVS(FALSITAS, FALSITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False), Bool(False), Bool(True)]))]), ValBool(False)), - # SENATVS: tie → FALSITAS - ("SENATVS(VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(False)]))]), ValBool(False)), - # SENATVS: 4-arg tie → FALSITAS - ("SENATVS(VERITAS, VERITAS, FALSITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False), Bool(False)]))]), ValBool(False)), - # SENATVS: single true → VERITAS - ("SENATVS(VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True)]))]), ValBool(True)), - # SENATVS: single false → FALSITAS - ("SENATVS(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False)]))]), ValBool(False)), - # SENATVS: empty → FALSITAS (vacuous) - ("SENATVS()", Program([], [ExpressionStatement(BuiltIn("SENATVS", []))]), ValBool(False)), - # SENATVS: all true �� VERITAS - ("SENATVS(VERITAS, VERITAS, VERITAS, VERITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(True), Bool(True), Bool(True)]))]), ValBool(True)), - # SENATVS: array input, majority true → VERITAS - ("SENATVS([VERITAS, VERITAS, FALSITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(True), Bool(True), Bool(False)])]))]), ValBool(True)), - # SENATVS: array input, majority false → FALSITAS - ("SENATVS([FALSITAS, FALSITAS, VERITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(False), Bool(False), Bool(True)])]))]), ValBool(False)), - # SENATVS: array input, empty → FALSITAS - ("SENATVS([])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([])]))]), ValBool(False)), - # ORDINA: sort integers - ("ORDINA([III, I, II])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Numeral("I"), Numeral("II")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), - # ORDINA: sort strings - ('ORDINA(["c", "a", "b"])', Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([String("c"), String("a"), String("b")])]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), - # ORDINA: empty list - ("ORDINA([])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([])]))]), ValList([])), - # ORDINA: single element - ("ORDINA([V])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("V")])]))]), ValList([ValInt(5)])), - # ORDINA: already sorted - ("ORDINA([I, II, III])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), - # ORDINA: duplicates - ("ORDINA([II, I, II])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("II"), Numeral("I"), Numeral("II")])]))]), ValList([ValInt(1), ValInt(2), ValInt(2)])), - # ORDINA: negative numbers - ("CVM SVBNVLLA\nORDINA([-II, III, -I])", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([UnaryMinus(Numeral("II")), Numeral("III"), UnaryMinus(Numeral("I"))])]))]), ValList([ValInt(-2), ValInt(-1), ValInt(3)])), - # ORDINA: fractions only - ("CVM FRACTIO\nORDINA([IIIS, S, IIS])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Fractio("IIIS"), Fractio("S"), Fractio("IIS")])]))]), ValList([ValFrac(Fraction(1, 2)), ValFrac(Fraction(5, 2)), ValFrac(Fraction(7, 2))])), - # ORDINA: mixed integers and fractions - ("CVM FRACTIO\nORDINA([III, S, II])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Fractio("S"), Numeral("II")])]))]), ValList([ValFrac(Fraction(1, 2)), ValInt(2), ValInt(3)])), - # ORDINA: array passed via variable - ("DESIGNA x VT [III, I, II]\nORDINA(x)", Program([], [Designa(ID("x"), DataArray([Numeral("III"), Numeral("I"), Numeral("II")])), ExpressionStatement(BuiltIn("ORDINA", [ID("x")]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), - # TYPVS: integer - ("TYPVS(V)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Numeral("V")]))]), ValStr("NVMERVS")), - # TYPVS: string - ('TYPVS("hello")', Program([], [ExpressionStatement(BuiltIn("TYPVS", [String("hello")]))]), ValStr("LITTERA")), - # TYPVS: boolean - ("TYPVS(VERITAS)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Bool(True)]))]), ValStr("VERAX")), - # TYPVS: list - ("TYPVS([I, II])", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataArray([Numeral("I"), Numeral("II")])]))]), ValStr("CATALOGVS")), - # TYPVS: empty list - ("TYPVS([])", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataArray([])]))]), ValStr("CATALOGVS")), - # TYPVS: fraction - ("CVM FRACTIO\nTYPVS(S)", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("TYPVS", [Fractio("S")]))]), ValStr("FRACTIO")), - # TYPVS: dict - ("TYPVS(TABVLA {})", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataDict([])]))]), ValStr("TABVLA")), - # TYPVS: function - ("TYPVS(FVNCTIO () VT { REDI(I) })", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Fvnctio([], [Redi([Numeral("I")])])]))]), ValStr("FVNCTIO")), - # TYPVS: null - ("TYPVS(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Nullus()]))]), ValStr("NVLLVS")), - # LITTERA: integer → Roman numeral - ("LITTERA(V)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Numeral("V")]))]), ValStr("V")), - # LITTERA: larger integer - ("LITTERA(MCMLXXXIV)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Numeral("MCMLXXXIV")]))]), ValStr("MCMLXXXIV")), - # LITTERA: zero → NVLLVS - ("LITTERA(I - I)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("NVLLVS")), - # LITTERA: string passthrough - ('LITTERA("salve")', Program([], [ExpressionStatement(BuiltIn("LITTERA", [String("salve")]))]), ValStr("salve")), - # LITTERA: empty string - ('LITTERA("")', Program([], [ExpressionStatement(BuiltIn("LITTERA", [String("")]))]), ValStr("")), - # LITTERA: VERITAS - ("LITTERA(VERITAS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Bool(True)]))]), ValStr("VERITAS")), - # LITTERA: FALSITAS - ("LITTERA(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Bool(False)]))]), ValStr("FALSITAS")), - # LITTERA: NVLLVS - ("LITTERA(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Nullus()]))]), ValStr("NVLLVS")), - # LITTERA: array of integers - ("LITTERA([I, II, III])", Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValStr("[I II III]")), - # LITTERA: empty array - ("LITTERA([])", Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataArray([])]))]), ValStr("[]")), - # LITTERA: dict with string keys (make_string emits bare keys, not quoted — matches DIC's output) - ('LITTERA(TABVLA {"a" VT I})', Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataDict([(String("a"), Numeral("I"))])]))]), ValStr('{a VT I}')), - # LITTERA: fraction (requires FRACTIO module) - ("CVM FRACTIO\nLITTERA(S)", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("LITTERA", [Fractio("S")]))]), ValStr("S")), - # LITTERA: negative integer - ("CVM SVBNVLLA\nLITTERA(-III)", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("LITTERA", [UnaryMinus(Numeral("III"))]))]), ValStr("-III")), - # LITTERA: round-trips through NVMERVS - ('NVMERVS(LITTERA(VII))', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [BuiltIn("LITTERA", [Numeral("VII")])]))]), ValInt(7)), - # LITTERA: concatenated with a string via & - ('LITTERA(V) & " est quinque"', Program([], [ExpressionStatement(BinOp(BuiltIn("LITTERA", [Numeral("V")]), String(" est quinque"), "SYMBOL_AMPERSAND"))]), ValStr("V est quinque")), - # LITTERA: via variable - ("DESIGNA x VT IX\nLITTERA(x)", Program([], [Designa(ID("x"), Numeral("IX")), ExpressionStatement(BuiltIn("LITTERA", [ID("x")]))]), ValStr("IX")), - # QVAERE: basic literal match - ('QVAERE("ab", "abcabc")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("ab"), String("abcabc")]))]), ValList([ValStr("ab"), ValStr("ab")])), - # QVAERE: no match → empty list - ('QVAERE("xyz", "abc")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("xyz"), String("abc")]))]), ValList([])), - # QVAERE: regex character class - ('QVAERE("[a-z]+", "abc123def")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("[a-z]+"), String("abc123def")]))]), ValList([ValStr("abc"), ValStr("def")])), - # QVAERE: empty text → empty list - ('QVAERE("a", "")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a"), String("")]))]), ValList([])), - # QVAERE: capture groups still return full match - ('QVAERE("(a)(b)", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(a)(b)"), String("ab")]))]), ValList([ValStr("ab")])), - # QVAERE: empty pattern matches between every character - ('QVAERE("", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String(""), String("ab")]))]), ValList([ValStr(""), ValStr(""), ValStr("")])), - # QVAERE: dot matches any character - ('QVAERE(".", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("."), String("ab")]))]), ValList([ValStr("a"), ValStr("b")])), - # SVBSTITVE: basic literal replacement - ('SVBSTITVE("a", "b", "aaa")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String("b"), String("aaa")]))]), ValStr("bbb")), - # SVBSTITVE: regex character class - ('SVBSTITVE("[0-9]+", "N", "abc123def456")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("[0-9]+"), String("N"), String("abc123def456")]))]), ValStr("abcNdefN")), - # SVBSTITVE: no match → string unchanged - ('SVBSTITVE("x", "y", "abc")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("x"), String("y"), String("abc")]))]), ValStr("abc")), - # SVBSTITVE: empty replacement (deletion) - ('SVBSTITVE("a", "", "banana")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String(""), String("banana")]))]), ValStr("bnn")), - # SVBSTITVE: empty text → empty string - ('SVBSTITVE("a", "b", "")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String("b"), String("")]))]), ValStr("")), - # SVBSTITVE: dot matches any character - ('SVBSTITVE(".", "x", "ab")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("."), String("x"), String("ab")]))]), ValStr("xx")), - # SVBSTITVE: backreference swaps two groups (Roman numerals) - ('SVBSTITVE("(a)(b)", "\\II\\I", "ab")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)"), String("\\II\\I"), String("ab")]))]), ValStr("ba")), - # SVBSTITVE: backreference with unmatched group (ignored) - ('SVBSTITVE("(a)(b)?", "\\I\\II", "a")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)?"), String("\\I\\II"), String("a")]))]), ValStr("a")), - # SVBSTITVE: Roman numeral quantifier in pattern - ("SVBSTITVE('a{III}', 'x', 'aaa')", Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a{III}"), String("x"), String("aaa")]))]), ValStr("x")), - # QVAERE: Roman numeral quantifier — exact repetition - ("QVAERE('a{III}', 'aaaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{III}"), String("aaaa")]))]), ValList([ValStr("aaa")])), - # QVAERE: Roman numeral quantifier — range - ("QVAERE('a{II,III}', 'aaaaaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{II,III}"), String("aaaaaa")]))]), ValList([ValStr("aaa"), ValStr("aaa")])), - # QVAERE: Roman numeral quantifier — at-least - ("QVAERE('a{II,}', 'a aa aaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{II,}"), String("a aa aaa")]))]), ValList([ValStr("aa"), ValStr("aaa")])), - # QVAERE: pattern backreference — repeated character - ("QVAERE('(.)\\I', 'aabcdd')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(.)\\I"), String("aabcdd")]))]), ValList([ValStr("aa"), ValStr("dd")])), - # QVAERE: pattern backreference — repeated group - ("QVAERE('(..)\\I', 'ababcc')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(..)\\I"), String("ababcc")]))]), ValList([ValStr("abab")])), - # NVMERVS: basic conversion - ('NVMERVS("XIV")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("XIV")]))]), ValInt(14)), - # NVMERVS: simple single numeral - ('NVMERVS("I")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("I")]))]), ValInt(1)), - # NVMERVS: large numeral - ('NVMERVS("MMMCMXCIX")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("MMMCMXCIX")]))]), ValInt(3999)), - # NVMERVS: subtractive form - ('NVMERVS("IX")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("IX")]))]), ValInt(9)), - # SCINDE: basic split - ('SCINDE("a,b,c", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("a,b,c"), String(",")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), - # SCINDE: no match (delimiter not found) - ('SCINDE("abc", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("abc"), String(",")]))]), ValList([ValStr("abc")])), - # SCINDE: empty string - ('SCINDE("", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String(""), String(",")]))]), ValList([ValStr("")])), - # SCINDE: multi-char delimiter - ('SCINDE("a::b::c", "::")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("a::b::c"), String("::")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), - # SCINDE: delimiter at edges - ('SCINDE(",a,", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String(",a,"), String(",")]))]), ValList([ValStr(""), ValStr("a"), ValStr("")])), - # SCINDE: empty delimiter (split into chars) - ('SCINDE("abc", "")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("abc"), String("")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), - # MAIVSCVLA: basic lowercase→uppercase - ('MAIVSCVLA("hello")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("hello")]))]), ValStr("HELLO")), - # MAIVSCVLA: mixed case - ('MAIVSCVLA("HeLLo")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("HeLLo")]))]), ValStr("HELLO")), - # MAIVSCVLA: already uppercase (idempotence) - ('MAIVSCVLA("HELLO")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("HELLO")]))]), ValStr("HELLO")), - # MAIVSCVLA: empty string - ('MAIVSCVLA("")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("")]))]), ValStr("")), - # MAIVSCVLA: Roman-numeral-shaped ASCII (case-only, not numeral-aware) - ('MAIVSCVLA("xii")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("xii")]))]), ValStr("XII")), - # MAIVSCVLA: non-alphabetic chars unchanged - ('MAIVSCVLA("a,b!1")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("a,b!1")]))]), ValStr("A,B!1")), - # MAIVSCVLA: via variable - ('DESIGNA s VT "foo"\nDIC(MAIVSCVLA(s))', Program([], [Designa(ID("s"), String("foo")), ExpressionStatement(BuiltIn("DIC", [BuiltIn("MAIVSCVLA", [ID("s")])]))]), ValStr("FOO"), "FOO\n"), - # MAIVSCVLA: concatenated with & - ('MAIVSCVLA("hi") & "!"', Program([], [ExpressionStatement(BinOp(BuiltIn("MAIVSCVLA", [String("hi")]), String("!"), "SYMBOL_AMPERSAND"))]), ValStr("HI!")), - # MINVSCVLA: basic uppercase→lowercase - ('MINVSCVLA("HELLO")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("HELLO")]))]), ValStr("hello")), - # MINVSCVLA: mixed case - ('MINVSCVLA("HeLLo")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("HeLLo")]))]), ValStr("hello")), - # MINVSCVLA: already lowercase (idempotence) - ('MINVSCVLA("hello")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("hello")]))]), ValStr("hello")), - # MINVSCVLA: empty string - ('MINVSCVLA("")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("")]))]), ValStr("")), - # MINVSCVLA: Roman-numeral-shaped ASCII (case-only, not numeral-aware) - ('MINVSCVLA("XII")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("XII")]))]), ValStr("xii")), - # MINVSCVLA: non-alphabetic chars unchanged - ('MINVSCVLA("A,B!1")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("A,B!1")]))]), ValStr("a,b!1")), - # MINVSCVLA round-trips MAIVSCVLA on lowercase input - ('MINVSCVLA(MAIVSCVLA("hi"))', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [BuiltIn("MAIVSCVLA", [String("hi")])]))]), ValStr("hi")), -] - -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", CentvrionError), # undefined variable - ("INVOCA f ()", CentvrionError), # undefined function - ("DESIGNA VT III", SyntaxError), # parse error: missing id after DESIGNA - ("DESIGNA x III", SyntaxError), # parse error: missing VT - ("DEFINI f () VT { REDI(I) }\nf()", SyntaxError), # function call without INVOCA (no args) - ("DEFINI f (x) VT { REDI(x) }\nf(I)", SyntaxError), # function call without INVOCA (with args) - ("DIC(M + M + M + M)", CentvrionError), # output > 3999 without MAGNVM - ("IIII", CentvrionError), # invalid Roman numeral in source - ("FORTVITVS_NVMERVS(I, X)", CentvrionError), # requires FORS module - ('NVMERVS(I)', CentvrionError), # NVMERVS expects a string, not int - ('NVMERVS("ABC")', CentvrionError), # invalid Roman numeral string - ('NVMERVS("XIV", "IX")', CentvrionError), # too many args - ("DEFINI f (x) VT { REDI(x) }\nINVOCA f (I, II)", CentvrionError), # too many args - ("DEFINI f (x, y) VT { REDI(x) }\nINVOCA f (I)", CentvrionError), # too few args - ("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", CentvrionError), # args to zero-param function - ("SI NVLLVS TVNC { DESIGNA r VT I }", CentvrionError), # NVLLVS cannot be used as boolean - ("NVLLVS AVT VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in AVT - ("FALSITAS AVT NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean - ("VERITAS ET NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean - ("NVLLVS ET VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in ET - ('I @ [II]', CentvrionError), # @ requires two arrays (int @ array) - ('[I] @ "hello"', CentvrionError), # @ requires two arrays (array @ string) - ('"a" @ "b"', CentvrionError), # @ requires two arrays (string @ string) - ('"hello" + " world"', CentvrionError), # use & for string concatenation, not + - ("[I, II][III]", CentvrionError), # index too high - ("CVM SVBNVLLA\n[I, II][-I]", CentvrionError), # negative index - ("[I, II][-I]", CentvrionError), # negative value - ("I / NVLLVS", CentvrionError), # division by zero (NVLLVS coerces to 0) - ("V RELIQVVM NVLLVS", CentvrionError), # modulo by zero (NVLLVS coerces to 0) - ("NVLLVS RELIQVVM NVLLVS", CentvrionError), # modulo by zero (both NVLLVS) - ("I / [I, II]", CentvrionError), # division with array operand - ("I - \"hello\"", CentvrionError), # subtraction with string - ("I * \"hello\"", CentvrionError), # multiplication with string - ("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings - ('"a" HAVD_PLVS "b"', CentvrionError), # HAVD_PLVS on strings - ('[I] HAVD_MINVS [II]', CentvrionError), # HAVD_MINVS on arrays - ("I[I]", CentvrionError), # indexing a non-array - ('"SALVTE"[VII]', CentvrionError), # string index out of range - ('"SALVTE"[NVLLVS]', CentvrionError), # string index with non-integer - ('"SALVTE"[II VSQVE VII]', CentvrionError), # string slice out of range - ('"SALVTE"[III VSQVE II]', CentvrionError), # string slice from > to - ("DESIGNA x VT I\nDESIGNA x[I] VT II", CentvrionError), # index-assign to non-array - ("SEMEN(I)", CentvrionError), # requires FORS module - ('CVM FORS\nSEMEN("abc")', CentvrionError), # SEMEN requires integer seed - ("FORTVITA_ELECTIO([])", CentvrionError), # FORS required for FORTVITA_ELECTIO - ("CVM FORS\nFORTVITA_ELECTIO([])", CentvrionError), # FORTVITA_ELECTIO on empty array - ("CVM FORS\nFORTVITVS_NVMERVS(X, I)", CentvrionError), # FORTVITVS_NVMERVS a > b - ("PER i IN I FAC { DIC(i) }", CentvrionError), # PER over non-array - ("DECIMATIO([I, II, III])", CentvrionError), # FORS required for DECIMATIO - ("CVM FORS\nDECIMATIO(I)", CentvrionError), # DECIMATIO requires an array - ("LONGITVDO(I)", CentvrionError), # LONGITVDO on non-array - ("ORDINA(I)", CentvrionError), # ORDINA on non-array - ('ORDINA([I, "a"])', CentvrionError), # ORDINA mixed types - ("DESIGNA x VT I\nORDINA(x)", CentvrionError), # ORDINA on id (non-array) - ("SENATVS(I)", CentvrionError), # SENATVS requires booleans - ("SENATVS(VERITAS, I)", CentvrionError), # SENATVS mixed types - ("SENATVS([I, II, III])", CentvrionError), # SENATVS array of non-bools - ('LEGE("x.txt")', CentvrionError), # SCRIPTA required for LEGE - ('SCRIBE("x.txt", "hi")', CentvrionError), # SCRIPTA required for SCRIBE - ('ADIVNGE("x.txt", "hi")', CentvrionError), # SCRIPTA required for ADIVNGE - ("DESIGNA x VT I\nINVOCA x ()", CentvrionError), # invoking a non-function - ("SI I TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: int - ("IIIS", CentvrionError), # fraction without FRACTIO module - ("CVM FRACTIO\n[I, II, III][IIIS]", CentvrionError), # fractional index (IIIS = 7/2) - ("CVM FRACTIO\n[I, II, III][I / II]", CentvrionError), # fractional index from division (1/2) - ("DESIGNA z VT I - I\nSI z TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: zero int - ("SI [I] TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: non-empty list - ("SI [] TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: empty list - ("DESIGNA x VT I\nDVM x FAC {\nDESIGNA x VT x + I\n}", CentvrionError), # non-bool DVM condition: int - ("NON I", CentvrionError), # NON on integer - ("DESIGNA z VT I - I\nNON z", CentvrionError), # NON on zero integer - ('NON "hello"', CentvrionError), # NON on string - ("DESIGNA a, b VT III", CentvrionError), # destructure non-array - ("DESIGNA a, b VT [I]", CentvrionError), # destructure length mismatch: too many targets - ("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets - ("PER a, b IN [I, II, III] FAC { DIC(a) }", CentvrionError), # PER destructure: element is not an array - ("PER a, b IN [[I], [II]] FAC { DIC(a) }", CentvrionError), # PER destructure: wrong number of elements - ("[I, II, III][II VSQVE IV]", CentvrionError), # slice upper bound out of range - ("[I, II, III][NVLLVS VSQVE II]", CentvrionError), # slice with non-integer bound - ("I[I VSQVE II]", CentvrionError), # slice on non-array - ("[I, II, III][III VSQVE I]", CentvrionError), # slice from > to - ("CVM SVBNVLLA\n[I, II, III][-I VSQVE II]", CentvrionError), # slice with negative lower bound - ("CVM SVBNVLLA\n[I, II, III][I VSQVE -I]", CentvrionError), # slice with negative upper bound - ("CVM FRACTIO\n[I, II, III][IIIS VSQVE III]", CentvrionError), # slice with fractional lower bound - ("CVM FRACTIO\n[I, II, III][I VSQVE IIIS]", CentvrionError), # slice with fractional upper bound - ("CVM FRACTIO\n[I, II, III][I / II VSQVE III]", CentvrionError), # slice with division-fraction lower bound - ("TEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nDESIGNA y VT I / NVLLVS\n}", CentvrionError), # uncaught error in catch block propagates - ('QVAERE(I, "abc")', CentvrionError), # QVAERE requires strings, not int - ('QVAERE("abc", I)', CentvrionError), # QVAERE requires strings, not int - ('QVAERE("[", "abc")', CentvrionError), # QVAERE invalid regex - ('SVBSTITVE(I, "b", "c")', CentvrionError), # SVBSTITVE requires strings, not int pattern - ('SVBSTITVE("a", I, "c")', CentvrionError), # SVBSTITVE requires strings, not int replacement - ('SVBSTITVE("a", "b", I)', CentvrionError), # SVBSTITVE requires strings, not int text - ('SVBSTITVE("[", "b", "c")', CentvrionError), # SVBSTITVE invalid regex - ("SVBSTITVE('(a)', '\\1', 'a')", CentvrionError), # Arabic backref in replacement - ("QVAERE('(.)\\1', 'aa')", CentvrionError), # Arabic backref in pattern - ("QVAERE('a{3}', 'aaa')", CentvrionError), # Arabic quantifier in pattern - ('SCINDE(I, ",")', CentvrionError), # SCINDE requires strings, not int - ('SCINDE("a", I)', CentvrionError), # SCINDE requires strings, not int delimiter - ('MAIVSCVLA(I)', CentvrionError), # MAIVSCVLA requires a string, not int - ('MAIVSCVLA()', CentvrionError), # MAIVSCVLA requires exactly 1 arg - ('MAIVSCVLA("a", "b")', CentvrionError), # MAIVSCVLA too many args - ('MINVSCVLA(I)', CentvrionError), # MINVSCVLA requires a string, not int - ('MINVSCVLA()', CentvrionError), # MINVSCVLA requires exactly 1 arg - ('MINVSCVLA("a", "b")', CentvrionError), # MINVSCVLA too many args - ('PETE("http://example.com")', CentvrionError), # RETE required for PETE - ('CVM RETE\nPETE(I)', CentvrionError), # PETE requires a string URL - ('PETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})', CentvrionError), # RETE required for PETITVR - ('CVM RETE\nPETITVR(I, FVNCTIO (r) VT {\nREDI("hi")\n})', CentvrionError), # PETITVR path must be string - ('CVM RETE\nPETITVR("/", "not a func")', CentvrionError), # PETITVR handler must be function - ('CVM RETE\nAVSCVLTA(LXXX)', CentvrionError), # AVSCVLTA: no routes registered - ('AVSCVLTA(LXXX)', CentvrionError), # RETE required for AVSCVLTA - ('CVM RETE\nPETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})\nAVSCVLTA("text")', CentvrionError), # AVSCVLTA port must be integer - ("DONICVM i VT I VSQVE X GRADV I - I FAC { DIC(i) }", CentvrionError), # GRADV zero step - ('DONICVM i VT I VSQVE X GRADV "foo" FAC { DIC(i) }', CentvrionError), # GRADV non-integer step -] - -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) - - -def run_compiler_error_test(self, source): - lexer = Lexer().get_lexer() - tokens = lexer.lex(source + "\n") - program = Parser().parse(tokens) - try: - c_source = compile_program(program) - except CentvrionError: - return # compile-time detection is valid - with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: - tmp_c.write(c_source) - tmp_c_path = tmp_c.name - with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: - tmp_bin_path = tmp_bin.name - try: - subprocess.run( - ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], - check=True, capture_output=True, - ) - proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) - self.assertNotEqual(proc.returncode, 0, "Expected non-zero exit for error program") - self.assertTrue(proc.stderr.strip(), "Expected error message on stderr") - finally: - os.unlink(tmp_c_path) - os.unlink(tmp_bin_path) - -compiler_error_tests = [(s, e) for s, e in error_tests if e == CentvrionError] - -class TestCompilerErrors(unittest.TestCase): - @parameterized.expand(compiler_error_tests) - def test_compiler_errors(self, source, error_type): - run_compiler_error_test(self, source) - - -# --- 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")), "ExpressionStatement(\n String(hi)\n)"), - ("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 ExpressionStatement(\n Erumpe()\n )\n ]),\n None\n)"), - ("si_with_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], [ExpressionStatement(Erumpe())]), "Si(\n Bool(True),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ]),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"), - ("si_empty_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], []), "Si(\n Bool(True),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ]),\n statements([])\n)"), - ("dum", DumStatement(Bool(False), [ExpressionStatement(Erumpe())]), "Dum(\n Bool(False),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"), - ("per", PerStatement(DataArray([Numeral("I")]), ID("i"), [ExpressionStatement(Erumpe())]), "Per(\n Array([\n Numeral(I)\n ]),\n ID(i),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"), - ("invoca", Invoca(ID("f"), [Numeral("I")]), "Invoca(\n ID(f),\n parameters([\n Numeral(I)\n ])\n)"), - ("builtin", BuiltIn("DIC", [String("hi")]), "Builtin(\n DIC,\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 ExpressionStatement(\n Redi([\n Numeral(I)\n ])\n )\n ])\n)"), - ("program_no_modules", Program([], [ExpressionStatement(Numeral("I"))]), "modules([]),\nstatements([\n ExpressionStatement(\n Numeral(I)\n )\n])"), - ("program_with_module", Program([ModuleCall("FORS")], [ExpressionStatement(Numeral("I"))]), "modules([\n FORS\n]),\nstatements([\n ExpressionStatement(\n Numeral(I)\n )\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) - - def test_negative_without_svbnvlla_raises(self): - with self.assertRaises(CentvrionError): - num_to_int("-IV", False) - - def test_negative_with_svbnvlla(self): - self.assertEqual(num_to_int("-IV", False, True), -4) - self.assertEqual(num_to_int("-XLII", False, True), -42) - - # int_to_num: valid cases - def test_int_to_num(self): - for n, s in [(0,"NVLLVS"),(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_int_zero(self): - self.assertEqual(make_string(ValInt(0)), "NVLLVS") - - def test_bool_true(self): - self.assertEqual(make_string(ValBool(True)), "VERITAS") - - def test_bool_false(self): - self.assertEqual(make_string(ValBool(False)), "FALSITAS") - - 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 VERITAS]" - ) - - -# --- DIC with non-integer types --- - -dic_type_tests = [ - ("DIC(VERITAS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Bool(True)]))]), ValStr("VERITAS"), "VERITAS\n"), - ("DIC(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Bool(False)]))]), ValStr("FALSITAS"), "FALSITAS\n"), - ("DIC(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Nullus()]))]), ValStr("NVLLVS"), "NVLLVS\n"), - ('DIC([I, II])', Program([], [ExpressionStatement(BuiltIn("DIC", [DataArray([Numeral("I"), Numeral("II")])]))]), ValStr("[I II]"), "[I II]\n"), - ('DIC("")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("")]))]), ValStr(""), "\n"), - # arithmetic result printed as numeral - ("DIC(II + III)", Program([], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")]))]), ValStr("V"), "V\n"), - # integer 0 prints as NVLLVS - ("DIC(I - I)", Program([], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("NVLLVS"), "NVLLVS\n"), - # multiple args of mixed types - ('DIC("x", VERITAS)', Program([], [ExpressionStatement(BuiltIn("DIC", [String("x"), Bool(True)]))]), ValStr("x VERITAS"), "x VERITAS\n"), -] - -class TestDicTypes(unittest.TestCase): - @parameterized.expand(dic_type_tests) - def test_dic_types(self, source, nodes, value, output): - run_test(self, source, nodes, value, output) - - -# --- SI/DVM: boolean condition enforcement --- - -dvm_bool_condition_tests = [ - # DVM exits when condition becomes true (boolean comparison) - ( - "DESIGNA x VT I\nDVM x PLVS III FAC {\nDESIGNA x VT x + I\n}\nx", - Program([], [ - Designa(ID("x"), Numeral("I")), - DumStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_PLVS"), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]), - ExpressionStatement(ID("x")), - ]), - ValInt(4), - ), -] - -class TestDvmBoolCondition(unittest.TestCase): - @parameterized.expand(dvm_bool_condition_tests) - def test_dvm_bool_condition(self, source, nodes, value): - run_test(self, source, nodes, value) - - - -# --- Arithmetic: edge cases --- - -arithmetic_edge_tests = [ - ("I - I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"))]), ValInt(0)), # result zero - ("I - V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-4)), # negative result - ("I / V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)), # integer division → 0 - ("M * M", Program([], [ExpressionStatement(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_TIMES"))]), ValInt(1000000)), # large intermediate (not displayed) - ("(I + II) * (IV - I)", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"), BinOp(Numeral("IV"), Numeral("I"), "SYMBOL_MINUS"), "SYMBOL_TIMES"))]), ValInt(9)), # nested parens - # NVLLVS coerces to 0 in integer arithmetic - ("NVLLVS + V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_PLUS"))]), ValInt(5)), - ("V + NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_PLUS"))]), ValInt(5)), - ("NVLLVS + NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_PLUS"))]), ValNul()), - ("NVLLVS - V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-5)), - ("V - NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_MINUS"))]), ValInt(5)), - # NVLLVS coerces to 0 in modulo and division - ("NVLLVS RELIQVVM V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_RELIQVVM"))]), ValInt(0)), - ("NVLLVS / V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)), - # floored division and modulo with negative operands (Python semantics) - ("CVM SVBNVLLA\n- VII RELIQVVM III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(2)), - ("CVM SVBNVLLA\nVII RELIQVVM - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("VII"), UnaryMinus(Numeral("III")), "KEYWORD_RELIQVVM"))]), ValInt(-2)), - ("CVM SVBNVLLA\n- VII RELIQVVM - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), UnaryMinus(Numeral("III")), "KEYWORD_RELIQVVM"))]), ValInt(-1)), - ("CVM SVBNVLLA\n- VII / III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(-3)), - ("CVM SVBNVLLA\nVII / - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("VII"), UnaryMinus(Numeral("III")), "SYMBOL_DIVIDE"))]), ValInt(-3)), - ("CVM SVBNVLLA\n- VII / - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), UnaryMinus(Numeral("III")), "SYMBOL_DIVIDE"))]), ValInt(2)), - ("CVM SVBNVLLA\n- L RELIQVVM C", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("L")), Numeral("C"), "KEYWORD_RELIQVVM"))]), ValInt(50)), -] - -class TestArithmeticEdge(unittest.TestCase): - @parameterized.expand(arithmetic_edge_tests) - def test_arithmetic_edge(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Array concatenation --- - -array_concat_tests = [ - ("[I, II] @ [III, IV]", - Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), "SYMBOL_AT"))]), - ValList([ValInt(1), ValInt(2), ValInt(3), ValInt(4)])), - ("[] @ [I]", - Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([Numeral("I")]), "SYMBOL_AT"))]), - ValList([ValInt(1)])), - ("[I] @ []", - Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I")]), DataArray([]), "SYMBOL_AT"))]), - ValList([ValInt(1)])), - ("[] @ []", - Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([]), "SYMBOL_AT"))]), - ValList([])), - ('["a"] @ [I]', - Program([], [ExpressionStatement(BinOp(DataArray([String("a")]), DataArray([Numeral("I")]), "SYMBOL_AT"))]), - ValList([ValStr("a"), ValInt(1)])), - # left-associative chaining - ("[I] @ [II] @ [III]", - Program([], [ExpressionStatement(BinOp(BinOp(DataArray([Numeral("I")]), DataArray([Numeral("II")]), "SYMBOL_AT"), DataArray([Numeral("III")]), "SYMBOL_AT"))]), - ValList([ValInt(1), ValInt(2), ValInt(3)])), - # concat with variable - ("DESIGNA a VT [I, II]\nDESIGNA b VT [III]\na @ b", - Program([], [Designa(ID("a"), DataArray([Numeral("I"), Numeral("II")])), Designa(ID("b"), DataArray([Numeral("III")])), ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_AT"))]), - ValList([ValInt(1), ValInt(2), ValInt(3)])), -] - -class TestArrayConcat(unittest.TestCase): - @parameterized.expand(array_concat_tests) - def test_array_concat(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- String concatenation --- - -string_concat_tests = [ - ('"hello" & " world"', Program([], [ExpressionStatement(BinOp(String("hello"), String(" world"), "SYMBOL_AMPERSAND"))]), ValStr("hello world")), - # NVLLVS coerces to "" in string context - ('NVLLVS & "hello"', Program([], [ExpressionStatement(BinOp(Nullus(), String("hello"), "SYMBOL_AMPERSAND"))]), ValStr("hello")), - ('"hello" & NVLLVS', Program([], [ExpressionStatement(BinOp(String("hello"), Nullus(), "SYMBOL_AMPERSAND"))]), ValStr("hello")), - ('NVLLVS & NVLLVS', Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_AMPERSAND"))]), ValStr("")), - # integers coerce to Roman numerals in string context - ('"value: " & V', Program([], [ExpressionStatement(BinOp(String("value: "), Numeral("V"), "SYMBOL_AMPERSAND"))]), ValStr("value: V")), - ('X & " items"', Program([], [ExpressionStatement(BinOp(Numeral("X"), String(" items"), "SYMBOL_AMPERSAND"))]), ValStr("X items")), -] - -class TestStringConcat(unittest.TestCase): - @parameterized.expand(string_concat_tests) - def test_string_concat(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- String interpolation --- - -interpolation_tests = [ - # basic variable interpolation - ('DESIGNA nomen VT "Marcus"\n"Salve, {nomen}!"', - Program([], [ - Designa(ID("nomen"), String("Marcus")), - ExpressionStatement(InterpolatedString([String("Salve, "), ID("nomen"), String("!")])) - ]), ValStr("Salve, Marcus!")), - # arithmetic expression inside interpolation - ('DESIGNA x VT III\n"Sum: {x + II}"', - Program([], [ - Designa(ID("x"), Numeral("III")), - ExpressionStatement(InterpolatedString([String("Sum: "), BinOp(ID("x"), Numeral("II"), "SYMBOL_PLUS")])) - ]), ValStr("Sum: V")), - # multiple interpolations - ('DESIGNA a VT I\nDESIGNA b VT II\n"{a} + {b} = {a + b}"', - Program([], [ - Designa(ID("a"), Numeral("I")), - Designa(ID("b"), Numeral("II")), - ExpressionStatement(InterpolatedString([ - ID("a"), String(" + "), ID("b"), String(" = "), - BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), - ])) - ]), ValStr("I + II = III")), - # escaped braces become literal - ('"use {{braces}}"', - Program([], [ExpressionStatement(String("use {braces}"))]), - ValStr("use {braces}")), - # single-quoted strings ignore braces - ("'hello {world}'", - Program([], [ExpressionStatement(String("hello {world}"))]), - ValStr("hello {world}")), - # integer coercion - ('DESIGNA n VT V\n"n is {n}"', - Program([], [ - Designa(ID("n"), Numeral("V")), - ExpressionStatement(InterpolatedString([String("n is "), ID("n")])) - ]), ValStr("n is V")), - # boolean coercion - ('DESIGNA b VT VERITAS\n"value: {b}"', - Program([], [ - Designa(ID("b"), Bool(True)), - ExpressionStatement(InterpolatedString([String("value: "), ID("b")])) - ]), ValStr("value: VERITAS")), - # NVLLVS coercion - ('"value: {NVLLVS}"', - Program([], [ - ExpressionStatement(InterpolatedString([String("value: "), Nullus()])) - ]), ValStr("value: NVLLVS")), - # integer 0 interpolates as NVLLVS - ('"value: {I - I}"', Program([], [ExpressionStatement(InterpolatedString([String("value: "), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("value: NVLLVS")), - # expression-only string (no literal parts around it) - ('DESIGNA x VT "hi"\n"{x}"', - Program([], [ - Designa(ID("x"), String("hi")), - ExpressionStatement(InterpolatedString([ID("x")])) - ]), ValStr("hi")), - # adjacent interpolations - ('DESIGNA a VT "x"\nDESIGNA b VT "y"\n"{a}{b}"', - Program([], [ - Designa(ID("a"), String("x")), - Designa(ID("b"), String("y")), - ExpressionStatement(InterpolatedString([ID("a"), ID("b")])) - ]), ValStr("xy")), - # function call inside interpolation - ("DEFINI f () VT {\nREDI (V)\n}\n\"result: {INVOCA f()}\"", - Program([], [ - Defini(ID("f"), [], [Redi([Numeral("V")])]), - ExpressionStatement(InterpolatedString([String("result: "), Invoca(ID("f"), [])])) - ]), ValStr("result: V")), - # single-quoted string inside interpolation - ("DESIGNA x VT 'hello'\n\"{x & '!'}\"", - Program([], [ - Designa(ID("x"), String("hello")), - ExpressionStatement(InterpolatedString([BinOp(ID("x"), String("!"), "SYMBOL_AMPERSAND")])) - ]), ValStr("hello!")), - # plain double-quoted string (no braces) still works - ('"hello world"', - Program([], [ExpressionStatement(String("hello world"))]), - ValStr("hello world")), - # interpolation in DIC output - ('DESIGNA name VT "Roma"\nDIC("Salve, {name}!")', - Program([], [ - Designa(ID("name"), String("Roma")), - ExpressionStatement(BuiltIn("DIC", [InterpolatedString([String("Salve, "), ID("name"), String("!")])])) - ]), ValStr("Salve, Roma!"), "Salve, Roma!\n"), -] - -class TestInterpolation(unittest.TestCase): - @parameterized.expand(interpolation_tests) - def test_interpolation(self, source, nodes, value, output=""): - run_test(self, source, nodes, value, output) - - -# --- Escape sequences --- - -escape_tests = [ - # \n → newline - ('"hello\\nworld"', - Program([], [ExpressionStatement(String("hello\nworld"))]), - ValStr("hello\nworld")), - # \t → tab - ('"col\\tcol"', - Program([], [ExpressionStatement(String("col\tcol"))]), - ValStr("col\tcol")), - # \r → carriage return - ('"line\\rover"', - Program([], [ExpressionStatement(String("line\rover"))]), - ValStr("line\rover")), - # \\ → literal backslash - ('"back\\\\slash"', - Program([], [ExpressionStatement(String("back\\slash"))]), - ValStr("back\\slash")), - # \" → literal double quote - ('"say \\"salve\\""', - Program([], [ExpressionStatement(String('say "salve"'))]), - ValStr('say "salve"')), - # \' → literal single quote in single-quoted string - ("'it\\'s'", - Program([], [ExpressionStatement(String("it's"))]), - ValStr("it's")), - # \n in single-quoted string - ("'hello\\nworld'", - Program([], [ExpressionStatement(String("hello\nworld"))]), - ValStr("hello\nworld")), - # escape inside interpolated string - ('DESIGNA name VT "Roma"\n"salve\\n{name}"', - Program([], [ - Designa(ID("name"), String("Roma")), - ExpressionStatement(InterpolatedString([String("salve\n"), ID("name")])) - ]), ValStr("salve\nRoma")), - # DIC with newline escape - ('DIC("hello\\nworld")', - Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello\nworld")]))]), - ValStr("hello\nworld"), "hello\nworld\n"), - # multiple escapes in one string - ('"\\t\\n\\\\"', - Program([], [ExpressionStatement(String("\t\n\\"))]), - ValStr("\t\n\\")), - # unknown escapes pass through (regex backrefs) - ('"\\1\\2"', - Program([], [ExpressionStatement(String("\\1\\2"))]), - ValStr("\\1\\2")), -] - -class TestEscapeSequences(unittest.TestCase): - @parameterized.expand(escape_tests) - def test_escape(self, source, nodes, value, output=""): - run_test(self, source, nodes, value, output) - - -# --- Comparison operators --- - -comparison_tests = [ - # EST on strings - ('\"hello\" EST \"hello\"', Program([], [ExpressionStatement(BinOp(String("hello"), String("hello"), "KEYWORD_EST"))]), ValBool(True)), - ('\"hello\" EST \"world\"', Program([], [ExpressionStatement(BinOp(String("hello"), String("world"), "KEYWORD_EST"))]), ValBool(False)), - # chain comparisons as conditions - ("SI III PLVS II TVNC { DESIGNA r VT I }\nr", - Program([], [SiStatement(BinOp(Numeral("III"), Numeral("II"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], None), ExpressionStatement(ID("r"))]), - ValInt(1)), - ("SI II PLVS III TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(BinOp(Numeral("II"), Numeral("III"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(2)), - # result of comparison is ValBool - ("I EST I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"))]), ValBool(True)), - ("I EST II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"))]), ValBool(False)), - ("I MINVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_MINVS"))]), ValBool(True)), - ("II MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_MINVS"))]), ValBool(False)), - ("II PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"))]), ValBool(True)), - ("I PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_PLVS"))]), ValBool(False)), - # NVLLVS coerces to 0 in comparisons - ("V PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_PLVS"))]), ValBool(True)), - ("NVLLVS MINVS V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_MINVS"))]), ValBool(True)), - # DISPAR (not-equal): mirrors EST semantics, negated - ("I DISPAR II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_DISPAR"))]), ValBool(True)), - ("I DISPAR I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_DISPAR"))]), ValBool(False)), - ('"hello" DISPAR "hello"', Program([], [ExpressionStatement(BinOp(String("hello"), String("hello"), "KEYWORD_DISPAR"))]), ValBool(False)), - ('"hello" DISPAR "world"', Program([], [ExpressionStatement(BinOp(String("hello"), String("world"), "KEYWORD_DISPAR"))]), ValBool(True)), - ("VERITAS DISPAR FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_DISPAR"))]), ValBool(True)), - ("NVLLVS DISPAR NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "KEYWORD_DISPAR"))]), ValBool(False)), - # cross-type: an int and a string are never equal - ('I DISPAR "I"', Program([], [ExpressionStatement(BinOp(Numeral("I"), String("I"), "KEYWORD_DISPAR"))]), ValBool(True)), - # integer 0 equals NVLLVS - ("(I - I) EST NVLLVS", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), Nullus(), "KEYWORD_EST"))]), ValBool(True)), - ("NVLLVS EST (I - I)", Program([], [ExpressionStatement(BinOp(Nullus(), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), "KEYWORD_EST"))]), ValBool(True)), - ("(I - I) DISPAR NVLLVS", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), Nullus(), "KEYWORD_DISPAR"))]), ValBool(False)), - ("NVLLVS DISPAR (I - I)", Program([], [ExpressionStatement(BinOp(Nullus(), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), "KEYWORD_DISPAR"))]), ValBool(False)), - # non-zero integer does not equal NVLLVS - ("I EST NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("I"), Nullus(), "KEYWORD_EST"))]), ValBool(False)), - ("NVLLVS DISPAR I", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("I"), "KEYWORD_DISPAR"))]), ValBool(True)), - # EST / DISPAR on arrays - ("[I, II] EST [I, II]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II")]), "KEYWORD_EST"))]), ValBool(True)), - ("[I, II] EST [I, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("III")]), "KEYWORD_EST"))]), ValBool(False)), - ("[I, II] EST [I, II, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), "KEYWORD_EST"))]), ValBool(False)), - ("[] EST []", Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([]), "KEYWORD_EST"))]), ValBool(True)), - ("[I, II] DISPAR [I, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("III")]), "KEYWORD_DISPAR"))]), ValBool(True)), - ("[I, II] DISPAR [I, II]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II")]), "KEYWORD_DISPAR"))]), ValBool(False)), - # HAVD_PLVS (<=) and HAVD_MINVS (>=) - ("I HAVD_PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)), - ("II HAVD_PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_HAVD_PLVS"))]), ValBool(False)), - # equality boundary — the only case that distinguishes <= from < - ("II HAVD_PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)), - ("II HAVD_MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_HAVD_MINVS"))]), ValBool(True)), - ("I HAVD_MINVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_HAVD_MINVS"))]), ValBool(False)), - # equality boundary — the only case that distinguishes >= from > - ("II HAVD_MINVS II",Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_MINVS"))]), ValBool(True)), - # NVLLVS coerces to 0 - ("V HAVD_MINVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_HAVD_MINVS"))]), ValBool(True)), - ("NVLLVS HAVD_PLVS V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)), - ("NVLLVS HAVD_PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "KEYWORD_HAVD_PLVS"))]), ValBool(True)), - # precedence: * binds tighter, so II*III HAVD_PLVS VI parses as (II*III) HAVD_PLVS VI = 6 <= 6 = True - ("II * III HAVD_PLVS VI", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), Numeral("VI"), "KEYWORD_HAVD_PLVS"))]), - ValBool(True)), - # control flow: SI ... HAVD_MINVS - ("SI II HAVD_MINVS II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_MINVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(1)), -] - -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 ()", - Program([], [Defini(ID("f"), [], [ExpressionStatement(Numeral("I"))]), ExpressionStatement(Invoca(ID("f"), []))]), - ValNul()), - # REDI multiple values → ValList - ( - "DEFINI pair (a, b) VT { REDI (a, b) }\nINVOCA pair (I, II)", - Program([], [ - Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]), - ExpressionStatement(Invoca(ID("pair"), [Numeral("I"), Numeral("II")])), - ]), - 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", - Program([], [ - Designa(ID("x"), Numeral("I")), - Defini(ID("f"), [], [Designa(ID("x"), Numeral("V")), Redi([ID("x")])]), - ExpressionStatement(Invoca(ID("f"), [])), - ExpressionStatement(ID("x")), - ]), - ValInt(1), - ), - # function can read outer vtable (closure-like) - ( - "DESIGNA x VT VII\nDEFINI f () VT { REDI (x) }\nINVOCA f ()", - Program([], [ - Designa(ID("x"), Numeral("VII")), - Defini(ID("f"), [], [Redi([ID("x")])]), - ExpressionStatement(Invoca(ID("f"), [])), - ]), - ValInt(7), - ), - # parameter shadows outer variable inside function - ( - "DESIGNA n VT I\nDEFINI f (n) VT { REDI (n * II) }\nINVOCA f (X)\nn", - Program([], [ - Designa(ID("n"), Numeral("I")), - Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), - ExpressionStatement(Invoca(ID("f"), [Numeral("X")])), - ExpressionStatement(ID("n")), - ]), - 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)", - Program([], [ - Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), - Designa(ID("g"), ID("f")), - ExpressionStatement(Invoca(ID("g"), [Numeral("V")])), - ]), - 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)", - Program([], [ - Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), - Designa(ID("g"), ID("f")), - Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("III"), "SYMBOL_TIMES")])]), - ExpressionStatement(Invoca(ID("g"), [Numeral("V")])), - ]), - ValInt(10), - ), - # REDI inside SI exits function, skips remaining statements in block - ( - "DEFINI f () VT {\nSI VERITAS TVNC {\nREDI (I)\nREDI (II)\n}\n}\nINVOCA f ()", - Program([],[ - Defini(ID("f"), [], [SiStatement(Bool(True),[Redi([Numeral("I")]),Redi([Numeral("II")])],None)]), - ExpressionStatement(Invoca(ID("f"),[])) - ]), - ValInt(1), - ), - # REDI inside DVM exits loop and function - ( - "DEFINI f () VT {\nDESIGNA x VT I\nDVM FALSITAS FAC {\nREDI (x)\n}\n}\nINVOCA f ()", - Program([],[ - Defini(ID("f"), [], [ - Designa(ID("x"), Numeral("I")), - DumStatement(Bool(False), [Redi([ID("x")])]) - ]), - ExpressionStatement(Invoca(ID("f"),[])) - ]), - ValInt(1), - ), - # REDI inside PER exits loop and function - ( - "DEFINI f () VT {\nPER x IN [I, II, III] FAC {\nSI x EST II TVNC {\nREDI (x)\n}\n}\n}\nINVOCA f ()", - Program([],[ - Defini(ID("f"), [], [ - PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("x"), [ - SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"), [ - Redi([ID("x")]) - ], None) - ]) - ]), - ExpressionStatement(Invoca(ID("f"),[])) - ]), - ValInt(2), - ), - # REDI inside nested loops exits all loops and function - ( - "DEFINI f () VT {\nDESIGNA x VT I\nDVM FALSITAS FAC {\nDVM FALSITAS FAC {\nREDI (x)\n}\n}\n}\nINVOCA f ()", - Program([],[ - Defini(ID("f"), [], [ - Designa(ID("x"), Numeral("I")), - DumStatement(Bool(False), [ - DumStatement(Bool(False), [ - Redi([ID("x")]) - ]) - ]) - ]), - ExpressionStatement(Invoca(ID("f"),[])) - ]), - ValInt(1), - ), -] - -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 = [ - # [III VSQVE III] = [3] — single iteration - ("DONICVM i VT III VSQVE III FAC { DIC(i) }", - Program([], [PerStatement(DataRangeArray(Numeral("III"), Numeral("III")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("III"), "III\n"), - # empty array — body never runs - ("PER i IN [] FAC { DIC(i) }", - Program([], [PerStatement(DataArray([]), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValNul(), ""), - # PER breaks on element 2 — last assigned i is 2 - ("PER i IN [I, II, III] FAC { SI i EST II TVNC { ERVMPE } }\ni", - Program([], [ - PerStatement( - DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), - ID("i"), - [SiStatement(BinOp(ID("i"), Numeral("II"), "KEYWORD_EST"), [Erumpe()], None)], - ), - ExpressionStatement(ID("i")), - ]), - ValInt(2), ""), - # nested DVM: inner always breaks; outer runs until btr==3 - ("DESIGNA btr VT I\nDVM btr EST III FAC {\nDVM FALSITAS FAC {\nERVMPE\n}\nDESIGNA btr VT btr + I\n}\nbtr", - Program([], [ - Designa(ID("btr"), Numeral("I")), - DumStatement( - BinOp(ID("btr"), Numeral("III"), "KEYWORD_EST"), - [DumStatement(Bool(False), [Erumpe()]), Designa(ID("btr"), BinOp(ID("btr"), Numeral("I"), "SYMBOL_PLUS"))], - ), - ExpressionStatement(ID("btr")), - ]), - 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] FAC {\nPER k IN [I, II] FAC {\nERVMPE\n}\nDESIGNA cnt VT cnt + I\n}\ncnt", - Program([], [ - Designa(ID("cnt"), Numeral("I")), - PerStatement( - DataArray([Numeral("I"), Numeral("II")]), - ID("i"), - [PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"), [Erumpe()]), - Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], - ), - ExpressionStatement(ID("cnt")), - ]), - ValInt(3), ""), - # PER with CONTINVA: skip odd numbers, sum evens - # [I,II,III,IV] → skip I and III; cnt increments on II and IV → cnt = III - ("DESIGNA cnt VT I\nPER i IN [I, II, III, IV] FAC {\nSI i EST I AVT i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt", - Program([], [ - Designa(ID("cnt"), Numeral("I")), - PerStatement( - DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]), - ID("i"), - [SiStatement(BinOp(BinOp(ID("i"), Numeral("I"), "KEYWORD_EST"), BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), "KEYWORD_AVT"), [Continva()], None), - Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], - ), - ExpressionStatement(ID("cnt")), - ]), - ValInt(3), ""), - # DVM with CONTINVA: skip body when x is II, increment regardless - # x goes 1→2→3; on x=2 we continue (no DIC); DIC fires for x=1 and x=3 - ("DESIGNA x VT I\nDVM x EST IV FAC {\nSI x EST II TVNC { DESIGNA x VT x + I\nCONTINVA }\nDIC(x)\nDESIGNA x VT x + I\n}\nx", - Program([], [ - Designa(ID("x"), Numeral("I")), - DumStatement( - BinOp(ID("x"), Numeral("IV"), "KEYWORD_EST"), - [SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"), - [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), Continva()], None), - ExpressionStatement(BuiltIn("DIC", [ID("x")])), - Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))], - ), - ExpressionStatement(ID("x")), - ]), - ValInt(4), "I\nIII\n"), - # nested PER: CONTINVA in inner only skips rest of inner body; outer still increments - ("DESIGNA cnt VT I\nPER i IN [I, II] FAC {\nPER k IN [I, II] FAC {\nCONTINVA\nDESIGNA cnt VT cnt + I\n}\nDESIGNA cnt VT cnt + I\n}\ncnt", - Program([], [ - Designa(ID("cnt"), Numeral("I")), - PerStatement( - DataArray([Numeral("I"), Numeral("II")]), - ID("i"), - [PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"), - [Continva(), Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))]), - Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], - ), - ExpressionStatement(ID("cnt")), - ]), - ValInt(3), ""), - # DONICVM with CONTINVA: skip value III, count remaining (I VSQVE IV = [1,2,3,4], skip 3 → 3 increments) - ("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FAC {\nSI i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt", - Program([], [ - Designa(ID("cnt"), Numeral("I")), - PerStatement( - DataRangeArray(Numeral("I"), Numeral("IV")), - ID("i"), - [SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Continva()], None), - Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], - ), - ExpressionStatement(ID("cnt")), - ]), - ValInt(4)), - # DVM condition true from start — body never runs - ("DESIGNA x VT I\nDVM VERITAS FAC {\nDESIGNA x VT x + I\n}\nx", - Program([], [ - Designa(ID("x"), Numeral("I")), - DumStatement(Bool(True), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]), - ExpressionStatement(ID("x")), - ]), - ValInt(1), ""), - # two iterations: [I VSQVE II] = [1, 2] - ("DONICVM i VT I VSQVE II FAC { DIC(i) }", - Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("II")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("II"), "I\nII\n"), - # single iteration: [I VSQVE I] = [1] - ("DONICVM i VT I VSQVE I FAC { DIC(i) }", - Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("I")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("I"), "I\n"), - # empty range: [V VSQVE I] = [] - ("DESIGNA x VT NVLLVS\nDONICVM i VT V VSQVE I FAC { DESIGNA x VT x + i }\nx", - Program([], [Designa(ID("x"), Nullus()), - PerStatement(DataRangeArray(Numeral("V"), Numeral("I")), ID("i"), - [Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]), - ExpressionStatement(ID("x"))]), - ValNul(), ""), - # PER destructuring with ERVMPE - ("DESIGNA r VT I\nPER a, b IN [[I, II], [III, IV], [V, VI]] FAC {\nSI a EST III TVNC { ERVMPE }\nDESIGNA r VT r + a + b\n}\nr", - Program([], [ - Designa(ID("r"), Numeral("I")), - PerStatement( - DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), DataArray([Numeral("V"), Numeral("VI")])]), - [ID("a"), ID("b")], - [SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None), - Designa(ID("r"), BinOp(BinOp(ID("r"), ID("a"), "SYMBOL_PLUS"), ID("b"), "SYMBOL_PLUS"))], - ), - ExpressionStatement(ID("r")), - ]), - ValInt(4)), # 1 + 1 + 2 = 4, breaks before [III, IV] - # PER destructuring with REDI - ("DEFINI f () VT {\nPER a, b IN [[I, II], [III, IV]] FAC {\nSI a EST III TVNC { REDI (b) }\n}\n}\nINVOCA f ()", - Program([], [ - Defini(ID("f"), [], - [PerStatement( - DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]), - [ID("a"), ID("b")], - [SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Redi([ID("b")])], None)], - )]), - ExpressionStatement(Invoca(ID("f"), [])), - ]), - ValInt(4)), # returns b=IV when a=III - # DONICVM GRADV II, endpoint hit exactly: [I, III, V] - ("DONICVM i VT I VSQVE V GRADV II FAC { DIC(i) }", - Program([], [PerStatement( - DataRangeArray(Numeral("I"), Numeral("V"), Numeral("II")), - ID("i"), - [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("V"), "I\nIII\nV\n"), - # DONICVM GRADV II, endpoint overshot: VI excluded, stops at V - ("DONICVM i VT I VSQVE VI GRADV II FAC { DIC(i) }", - Program([], [PerStatement( - DataRangeArray(Numeral("I"), Numeral("VI"), Numeral("II")), - ID("i"), - [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("V"), "I\nIII\nV\n"), - # DONICVM GRADV I is equivalent to default step - ("DONICVM i VT I VSQVE III GRADV I FAC { DIC(i) }", - Program([], [PerStatement( - DataRangeArray(Numeral("I"), Numeral("III"), Numeral("I")), - ID("i"), - [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("III"), "I\nII\nIII\n"), - # DONICVM descending by I - ("CVM SVBNVLLA\nDONICVM i VT V VSQVE I GRADV - I FAC { DIC(i) }", - Program([ModuleCall("SVBNVLLA")], [PerStatement( - DataRangeArray(Numeral("V"), Numeral("I"), UnaryMinus(Numeral("I"))), - ID("i"), - [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("I"), "V\nIV\nIII\nII\nI\n"), - # DONICVM descending by II, endpoint overshot - ("CVM SVBNVLLA\nDONICVM i VT X VSQVE I GRADV - II FAC { DIC(i) }", - Program([ModuleCall("SVBNVLLA")], [PerStatement( - DataRangeArray(Numeral("X"), Numeral("I"), UnaryMinus(Numeral("II"))), - ID("i"), - [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("II"), "X\nVIII\nVI\nIV\nII\n"), - # DONICVM with step bound to a variable - ("DESIGNA s VT II\nDONICVM i VT I VSQVE V GRADV s FAC { DIC(i) }", - Program([], [ - Designa(ID("s"), Numeral("II")), - PerStatement( - DataRangeArray(Numeral("I"), Numeral("V"), ID("s")), - ID("i"), - [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("V"), "I\nIII\nV\n"), - # DONICVM from == to with nonzero step: single iteration - ("DONICVM i VT III VSQVE III GRADV II FAC { DIC(i) }", - Program([], [PerStatement( - DataRangeArray(Numeral("III"), Numeral("III"), Numeral("II")), - ID("i"), - [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("III"), "III\n"), - # DONICVM direction mismatch — negative step with ascending bounds: body never runs - ("CVM SVBNVLLA\nDESIGNA x VT NVLLVS\nDONICVM i VT I VSQVE X GRADV - I FAC { DESIGNA x VT x + i }\nx", - Program([ModuleCall("SVBNVLLA")], [ - Designa(ID("x"), Nullus()), - PerStatement( - DataRangeArray(Numeral("I"), Numeral("X"), UnaryMinus(Numeral("I"))), - ID("i"), - [Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]), - ExpressionStatement(ID("x"))]), - ValNul(), ""), - # Range-array literal with GRADV used via PER - ("PER i IN [I VSQVE V GRADV II] FAC { DIC(i) }", - Program([], [PerStatement( - DataRangeArray(Numeral("I"), Numeral("V"), Numeral("II")), - ID("i"), - [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("V"), "I\nIII\nV\n"), - # DONICVM GRADV II with CONTINVA: skip value V, print the others - ("DONICVM i VT I VSQVE IX GRADV II FAC {\nSI i EST V TVNC { CONTINVA }\nDIC(i)\n}", - Program([], [PerStatement( - DataRangeArray(Numeral("I"), Numeral("IX"), Numeral("II")), - ID("i"), - [SiStatement(BinOp(ID("i"), Numeral("V"), "KEYWORD_EST"), [Continva()], None), - ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("IX"), "I\nIII\nVII\nIX\n"), - # DONICVM GRADV II with ERVMPE: stop at V (last successful DIC was III) - ("DONICVM i VT I VSQVE IX GRADV II FAC {\nSI i EST V TVNC { ERVMPE }\nDIC(i)\n}", - Program([], [PerStatement( - DataRangeArray(Numeral("I"), Numeral("IX"), Numeral("II")), - ID("i"), - [SiStatement(BinOp(ID("i"), Numeral("V"), "KEYWORD_EST"), [Erumpe()], None), - ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), - ValStr("III"), "I\nIII\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\nDIC(M + M + M + M)", - Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS")]))]), - ValStr("MV_"), "MV_\n"), - # I_ = 1000 with MAGNVM (same value as M, but written with thousands operator) - ("CVM MAGNVM\nI_", - Program([ModuleCall("MAGNVM")], [ExpressionStatement(Numeral("I_"))]), - ValInt(1000), ""), - # I_ + I_ = 2000; displayed as MM with MAGNVM - ("CVM MAGNVM\nDIC(I_ + I_)", - Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I_"), Numeral("I_"), "SYMBOL_PLUS")]))]), - 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) - - -# --- SVBNVLLA module (display of negatives) --- - -svbnvlla_display_tests = [ - # DIC prints a negative numeral - ("CVM SVBNVLLA\nDIC(- III)", - Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Numeral("III"))]))]), - ValStr("-III"), "-III\n"), - # Concat operator & with a negative numeral - ('CVM SVBNVLLA\nDIC("x: " & - V)', - Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(String("x: "), UnaryMinus(Numeral("V")), "SYMBOL_AMPERSAND")]))]), - ValStr("x: -V"), "x: -V\n"), - # String interpolation of a negative numeral - ('CVM SVBNVLLA\nDIC("val: {- X}")', - Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [InterpolatedString([String("val: "), UnaryMinus(Numeral("X"))])]))]), - ValStr("val: -X"), "val: -X\n"), - # DIC of LITTERA(negative numeral) - ("CVM SVBNVLLA\nDIC(LITTERA(- III))", - Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("LITTERA", [UnaryMinus(Numeral("III"))])]))]), - ValStr("-III"), "-III\n"), - # Combined with MAGNVM: negative of a number > 3999 - ("CVM MAGNVM\nCVM SVBNVLLA\nDIC(- (M + M + M + M))", - Program([ModuleCall("MAGNVM"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(BinOp(BinOp(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"))]))]), - ValStr("-MV_"), "-MV_\n"), - # Negative fraction via int / -int - ("CVM FRACTIO\nCVM SVBNVLLA\nDIC(V / - II)", - Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("V"), UnaryMinus(Numeral("II")), "SYMBOL_DIVIDE")]))]), - ValStr("-IIS"), "-IIS\n"), - # Negative pure fraction (no integer part) - ("CVM FRACTIO\nCVM SVBNVLLA\nDIC(I / - II)", - Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I"), UnaryMinus(Numeral("II")), "SYMBOL_DIVIDE")]))]), - ValStr("-S"), "-S\n"), - ("CVM FRACTIO\nCVM SVBNVLLA\nDIC(- S)", - Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Fractio("S"))]))]), - ValStr("-S"), "-S\n"), - ("CVM FRACTIO\nCVM SVBNVLLA\nDIC(- IIS)", - Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Fractio("IIS"))]))]), - ValStr("-IIS"), "-IIS\n"), -] - -class TestSVBNVLLADisplay(unittest.TestCase): - @parameterized.expand(svbnvlla_display_tests) - def test_svbnvlla_display(self, source, nodes, value, output=""): - run_test(self, source, nodes, value, output) - - -# --- ET and AVT (boolean and/or) --- - -et_avt_tests = [ - ("VERITAS ET VERITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(True), "KEYWORD_ET"))]), ValBool(True)), - ("VERITAS ET FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_ET"))]), ValBool(False)), - ("FALSITAS ET VERITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(True), "KEYWORD_ET"))]), ValBool(False)), - ("FALSITAS ET FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(False), "KEYWORD_ET"))]), ValBool(False)), - ("VERITAS AVT VERITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(True), "KEYWORD_AVT"))]), ValBool(True)), - ("VERITAS AVT FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_AVT"))]), ValBool(True)), - ("FALSITAS AVT VERITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"))]), ValBool(True)), - ("FALSITAS AVT FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"))]), ValBool(False)), - # short-circuit behavior: combined with comparisons - ("(I EST I) ET (II EST II)", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]), - ValBool(True)), - ("(I EST II) AVT (II EST II)", - Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_AVT"))]), - ValBool(True)), - # used as SI condition - ("SI VERITAS ET VERITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(BinOp(Bool(True), Bool(True), "KEYWORD_ET"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(1)), - ("SI FALSITAS AVT FALSITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [SiStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), - ValInt(2)), - # short-circuit: right side not evaluated when result is determined - ("VERITAS AVT NVLLVS", - Program([], [ExpressionStatement(BinOp(Bool(True), Nullus(), "KEYWORD_AVT"))]), - ValBool(True)), - ("FALSITAS ET NVLLVS", - Program([], [ExpressionStatement(BinOp(Bool(False), Nullus(), "KEYWORD_ET"))]), - ValBool(False)), - # short-circuit with side-effect-prone expressions - ("DESIGNA x VT NVLLVS\nSI x EST NVLLVS AVT [I, II][x] EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [ - Designa(ID("x"), Nullus()), - SiStatement( - BinOp(BinOp(ID("x"), Nullus(), "KEYWORD_EST"), BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II")]), ID("x")), Numeral("I"), "KEYWORD_EST"), "KEYWORD_AVT"), - [Designa(ID("r"), Numeral("I"))], - [Designa(ID("r"), Numeral("II"))]), - ExpressionStatement(ID("r"))]), - ValInt(1)), - ("DESIGNA x VT NVLLVS\nSI x DISPAR NVLLVS ET [I, II][x] EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", - Program([], [ - Designa(ID("x"), Nullus()), - SiStatement( - BinOp(BinOp(ID("x"), Nullus(), "KEYWORD_DISPAR"), BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II")]), ID("x")), Numeral("I"), "KEYWORD_EST"), "KEYWORD_ET"), - [Designa(ID("r"), Numeral("I"))], - [Designa(ID("r"), Numeral("II"))]), - ExpressionStatement(ID("r"))]), - 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 1-based; I is the first element - -array_index_tests = [ - # basic indexing - ("[I, II, III][I]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I")))]), ValInt(1)), # first element - ("[I, II, III][II]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")))]), ValInt(2)), # second element - ("[I, II, III][III]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("III")))]), ValInt(3)), # third element - # index into a variable - ("DESIGNA a VT [X, XX, XXX]\na[II]", - Program([], [Designa(ID("a"), DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])), ExpressionStatement(ArrayIndex(ID("a"), Numeral("II")))]), - ValInt(20)), # second element - # index into range array - ("[I VSQVE V][II]", Program([], [ExpressionStatement(ArrayIndex(DataRangeArray(Numeral("I"), Numeral("V")), Numeral("II")))]), ValInt(2)), # second element of [1,2,3,4,5] - # expression as index - ("[I, II, III][I + I]", - Program([], [ExpressionStatement(ArrayIndex( - DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), - BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS")))]), - ValInt(2)), - # division result as index (no FRACTIO): IV / II = 2 - ("[X, XX, XXX][IV / II]", - Program([], [ExpressionStatement(ArrayIndex( - DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")]), - BinOp(Numeral("IV"), Numeral("II"), "SYMBOL_DIVIDE")))]), - ValInt(20)), - # whole-number fraction (from division) as index, with FRACTIO imported - ("CVM FRACTIO\n[X, XX, XXX][IV / II]", - Program([ModuleCall("FRACTIO")], [ExpressionStatement(ArrayIndex( - DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")]), - BinOp(Numeral("IV"), Numeral("II"), "SYMBOL_DIVIDE")))]), - ValInt(20)), -] - -class TestArrayIndex(unittest.TestCase): - @parameterized.expand(array_index_tests) - def test_array_index(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Array index assignment --- - -array_index_assign_tests = [ - # assign to middle element - ("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[II]", - Program([], [ - Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), - DesignaIndex(ID("a"), [Numeral("II")], Numeral("X")), - ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))), - ]), - ValInt(10)), - # assign to first element - ("DESIGNA a VT [I, II, III]\nDESIGNA a[I] VT V\na[I]", - Program([], [ - Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), - DesignaIndex(ID("a"), [Numeral("I")], Numeral("V")), - ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))), - ]), - ValInt(5)), - # assign to last element - ("DESIGNA a VT [I, II, III]\nDESIGNA a[III] VT L\na[III]", - Program([], [ - Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), - DesignaIndex(ID("a"), [Numeral("III")], Numeral("L")), - ExpressionStatement(ArrayIndex(ID("a"), Numeral("III"))), - ]), - ValInt(50)), - # other elements unaffected - ("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[I]", - Program([], [ - Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), - DesignaIndex(ID("a"), [Numeral("II")], Numeral("X")), - ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))), - ]), - ValInt(1)), - # expression as index - ("DESIGNA a VT [I, II, III]\nDESIGNA i VT II\nDESIGNA a[i] VT X\na[II]", - Program([], [ - Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), - Designa(ID("i"), Numeral("II")), - DesignaIndex(ID("a"), [ID("i")], Numeral("X")), - ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))), - ]), - ValInt(10)), -] - -class TestArrayIndexAssign(unittest.TestCase): - @parameterized.expand(array_index_assign_tests) - def test_array_index_assign(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Multi-dimensional array index assignment --- - -multidim_assign_tests = [ - # 2D array assignment - ("DESIGNA a VT [[I, II], [III, IV]]\nDESIGNA a[I][II] VT X\na[I][II]", - Program([], [ - Designa(ID("a"), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])), - DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], Numeral("X")), - ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), Numeral("II"))), - ]), - ValInt(10)), - # other elements unaffected - ("DESIGNA a VT [[I, II], [III, IV]]\nDESIGNA a[I][II] VT X\na[II][I]", - Program([], [ - Designa(ID("a"), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])), - DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], Numeral("X")), - ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("II")), Numeral("I"))), - ]), - ValInt(3)), - # dict inside array - ('DESIGNA a VT [TABVLA {"x" VT I}]\nDESIGNA a[I]["x"] VT X\na[I]["x"]', - Program([], [ - Designa(ID("a"), DataArray([DataDict([(String("x"), Numeral("I"))])])), - DesignaIndex(ID("a"), [Numeral("I"), String("x")], Numeral("X")), - ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), String("x"))), - ]), - ValInt(10)), - # array inside dict - ('DESIGNA d VT TABVLA {"a" VT [I, II]}\nDESIGNA d["a"][I] VT X\nd["a"][I]', - Program([], [ - Designa(ID("d"), DataDict([(String("a"), DataArray([Numeral("I"), Numeral("II")]))])), - DesignaIndex(ID("d"), [String("a"), Numeral("I")], Numeral("X")), - ExpressionStatement(ArrayIndex(ArrayIndex(ID("d"), String("a")), Numeral("I"))), - ]), - ValInt(10)), - # 3 levels deep - ("DESIGNA a VT [[[I]]]\nDESIGNA a[I][I][I] VT X\na[I][I][I]", - Program([], [ - Designa(ID("a"), DataArray([DataArray([DataArray([Numeral("I")])])])), - DesignaIndex(ID("a"), [Numeral("I"), Numeral("I"), Numeral("I")], Numeral("X")), - ExpressionStatement(ArrayIndex(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), Numeral("I")), Numeral("I"))), - ]), - ValInt(10)), -] - -class TestMultidimAssign(unittest.TestCase): - @parameterized.expand(multidim_assign_tests) - def test_multidim_assign(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- String index assignment --- - -string_index_assign_tests = [ - # assign to middle character - ('DESIGNA s VT "ABCDE"\nDESIGNA s[III] VT "X"\ns', - Program([], [ - Designa(ID("s"), String("ABCDE")), - DesignaIndex(ID("s"), [Numeral("III")], String("X")), - ExpressionStatement(ID("s")), - ]), - ValStr("ABXDE")), - # assign to first character - ('DESIGNA s VT "ABCDE"\nDESIGNA s[I] VT "Z"\ns', - Program([], [ - Designa(ID("s"), String("ABCDE")), - DesignaIndex(ID("s"), [Numeral("I")], String("Z")), - ExpressionStatement(ID("s")), - ]), - ValStr("ZBCDE")), - # assign to last character - ('DESIGNA s VT "ABCDE"\nDESIGNA s[V] VT "Z"\ns', - Program([], [ - Designa(ID("s"), String("ABCDE")), - DesignaIndex(ID("s"), [Numeral("V")], String("Z")), - ExpressionStatement(ID("s")), - ]), - ValStr("ABCDZ")), - # variable as index - ('DESIGNA s VT "ABCDE"\nDESIGNA i VT II\nDESIGNA s[i] VT "X"\ns', - Program([], [ - Designa(ID("s"), String("ABCDE")), - Designa(ID("i"), Numeral("II")), - DesignaIndex(ID("s"), [ID("i")], String("X")), - ExpressionStatement(ID("s")), - ]), - ValStr("AXCDE")), - # string inside array - ('DESIGNA a VT ["ABC", "DEF"]\nDESIGNA a[I][II] VT "X"\na[I]', - Program([], [ - Designa(ID("a"), DataArray([String("ABC"), String("DEF")])), - DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], String("X")), - ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))), - ]), - ValStr("AXC")), -] - -class TestStringIndexAssign(unittest.TestCase): - @parameterized.expand(string_index_assign_tests) - def test_string_index_assign(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Array slicing --- - -array_slice_tests = [ - # basic slice from middle - ("[X, XX, XXX, XL, L][II VSQVE IV]", - Program([], [ExpressionStatement(ArraySlice( - DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX"), Numeral("XL"), Numeral("L")]), - Numeral("II"), Numeral("IV")))]), - ValList([ValInt(20), ValInt(30), ValInt(40)])), - # slice of length 1 - ("[I, II, III][II VSQVE II]", - Program([], [ExpressionStatement(ArraySlice( - DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), - Numeral("II"), Numeral("II")))]), - ValList([ValInt(2)])), - # full array slice - ("[I, II, III][I VSQVE III]", - Program([], [ExpressionStatement(ArraySlice( - DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), - Numeral("I"), Numeral("III")))]), - ValList([ValInt(1), ValInt(2), ValInt(3)])), - # slice on variable - ("DESIGNA a VT [I, II, III, IV, V]\na[II VSQVE IV]", - Program([], [ - Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V")])), - ExpressionStatement(ArraySlice(ID("a"), Numeral("II"), Numeral("IV"))), - ]), - ValList([ValInt(2), ValInt(3), ValInt(4)])), - # slice then index (chained) - ("[I, II, III, IV][I VSQVE III][II]", - Program([], [ExpressionStatement(ArrayIndex( - ArraySlice( - DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]), - Numeral("I"), Numeral("III")), - Numeral("II")))]), - ValInt(2)), - # slice on range array - ("[I VSQVE X][III VSQVE VII]", - Program([], [ExpressionStatement(ArraySlice( - DataRangeArray(Numeral("I"), Numeral("X")), - Numeral("III"), Numeral("VII")))]), - ValList([ValInt(3), ValInt(4), ValInt(5), ValInt(6), ValInt(7)])), - # expression as slice bounds - ("[I, II, III, IV, V][I + I VSQVE II + II]", - Program([], [ExpressionStatement(ArraySlice( - DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V")]), - BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"), - BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS")))]), - ValList([ValInt(2), ValInt(3), ValInt(4)])), -] - -class TestArraySlice(unittest.TestCase): - @parameterized.expand(array_slice_tests) - def test_array_slice(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Multiline arrays --- - -multiline_array_tests = [ - # newlines after commas - ("[I,\nII,\nIII]", - Program([], [ExpressionStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]))]), - ValList([ValInt(1), ValInt(2), ValInt(3)])), - # single newline after comma - ("[I, II,\nIII]", - Program([], [ExpressionStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]))]), - ValList([ValInt(1), ValInt(2), ValInt(3)])), - # empty array still works - ("[]", - Program([], [ExpressionStatement(DataArray([]))]), - ValList([])), - # nested arrays with newlines - ("[I,\n[II, III],\nIV]", - Program([], [ExpressionStatement(DataArray([ - Numeral("I"), - DataArray([Numeral("II"), Numeral("III")]), - Numeral("IV")]))]), - ValList([ValInt(1), ValList([ValInt(2), ValInt(3)]), ValInt(4)])), -] - -class TestMultilineArray(unittest.TestCase): - @parameterized.expand(multiline_array_tests) - def test_multiline_array(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- String indexing --- - -string_index_tests = [ - # first character - ('"SALVTE"[I]', - Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("I")))]), - ValStr("S")), - # last character - ('"SALVTE"[VI]', - Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("VI")))]), - ValStr("E")), - # middle character - ('"SALVTE"[III]', - Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("III")))]), - ValStr("L")), - # string index via variable - ('DESIGNA s VT "SALVTE"\ns[II]', - Program([], [ - Designa(ID("s"), String("SALVTE")), - ExpressionStatement(ArrayIndex(ID("s"), Numeral("II"))), - ]), - ValStr("A")), - # expression as index - ('"SALVTE"[I + II]', - Program([], [ExpressionStatement(ArrayIndex( - String("SALVTE"), - BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS")))]), - ValStr("L")), -] - -class TestStringIndex(unittest.TestCase): - @parameterized.expand(string_index_tests) - def test_string_index(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- String slicing --- - -string_slice_tests = [ - # substring from middle - ('"SALVTE"[II VSQVE IV]', - Program([], [ExpressionStatement(ArraySlice( - String("SALVTE"), Numeral("II"), Numeral("IV")))]), - ValStr("ALV")), - # full string slice - ('"SALVTE"[I VSQVE VI]', - Program([], [ExpressionStatement(ArraySlice( - String("SALVTE"), Numeral("I"), Numeral("VI")))]), - ValStr("SALVTE")), - # single-char slice - ('"SALVTE"[III VSQVE III]', - Program([], [ExpressionStatement(ArraySlice( - String("SALVTE"), Numeral("III"), Numeral("III")))]), - ValStr("L")), - # slice on variable - ('DESIGNA s VT "SALVTE"\ns[II VSQVE IV]', - Program([], [ - Designa(ID("s"), String("SALVTE")), - ExpressionStatement(ArraySlice(ID("s"), Numeral("II"), Numeral("IV"))), - ]), - ValStr("ALV")), - # chaining: slice then index - ('"SALVTE"[I VSQVE III][II]', - Program([], [ExpressionStatement(ArrayIndex( - ArraySlice(String("SALVTE"), Numeral("I"), Numeral("III")), - Numeral("II")))]), - ValStr("A")), - # expression as slice bounds - ('"SALVTE"[I + I VSQVE II + II]', - Program([], [ExpressionStatement(ArraySlice( - String("SALVTE"), - BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"), - BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS")))]), - ValStr("ALV")), -] - -class TestStringSlice(unittest.TestCase): - @parameterized.expand(string_slice_tests) - def test_string_slice(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- Comments --- - -comment_tests = [ - # trailing line comment - ('DIC("hello") // this is ignored', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"), - # line comment on its own line before code - ('// ignored\nDIC("hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"), - # inline block comment - ('DIC(/* ignored */ "hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"), - # block comment spanning multiple lines - ('/* line one\nline two */\nDIC("hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"), - # block comment mid-expression - ("II /* ignored */ + III", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"))]), ValInt(5)), - # line comment after expression (no output) - ("II + III // ignored", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"))]), ValInt(5)), - # division still works (/ token not confused with //) - ("X / II", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))]), ValInt(5)), - # multiple line comments - ('// first\n// second\nDIC("ok")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("ok")]))]), ValStr("ok"), "ok\n"), - # comment-only line between two statements - ('DESIGNA x VT I\n// set y\nDESIGNA y VT II\nx + y', - Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("II")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]), - ValInt(3)), - # blank line between two statements (double newline) - ('DESIGNA x VT I\n\nDESIGNA y VT II\nx + y', - Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("II")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]), - ValInt(3)), - # multiple comment-only lines between statements - ('DESIGNA x VT I\n// one\n// two\nDESIGNA y VT III\nx + y', - Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("III")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]), - 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", - Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("X"))], None), ExpressionStatement(ID("r"))]), - ValInt(10)), - # SI: variable assigned in ALIVD branch persists in outer scope - ("SI FALSITAS TVNC { DESIGNA r VT X } ALIVD { DESIGNA r VT V }\nr", - Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("X"))], [Designa(ID("r"), Numeral("V"))]), ExpressionStatement(ID("r"))]), - 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 FAC { DESIGNA x VT x + I\nDESIGNA r VT x }\nr", - Program([], [ - Designa(ID("x"), Numeral("I")), - DumStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_EST"), [ - Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), - Designa(ID("r"), ID("x")), - ]), - ExpressionStatement(ID("r")), - ]), - ValInt(5)), - # PER: loop variable holds last array element after loop (no ERVMPE) - ("PER i IN [I, II, III] FAC { DESIGNA nop VT I }\ni", - Program([], [ - PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [Designa(ID("nop"), Numeral("I"))]), - ExpressionStatement(ID("i")), - ]), - 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] FAC { DESIGNA i VT C\nDESIGNA cnt VT cnt + I }\ncnt", - Program([], [ - Designa(ID("cnt"), Numeral("I")), - PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ - Designa(ID("i"), Numeral("C")), - Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS")), - ]), - ExpressionStatement(ID("cnt")), - ]), - 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] FAC { DESIGNA i VT C }\ni", - Program([], [ - PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [Designa(ID("i"), Numeral("C"))]), - ExpressionStatement(ID("i")), - ]), - ValInt(100)), - # DONICVM: counter holds last range value after loop ends - # [I VSQVE IV] = [1,2,3,4]; last value assigned by loop is IV=4 - ("DONICVM i VT I VSQVE IV FAC { DESIGNA nop VT I }\ni", - Program([], [ - PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [Designa(ID("nop"), Numeral("I"))]), - ExpressionStatement(ID("i")), - ]), - ValInt(4)), - # 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 4 times → 5 - ("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FAC { DESIGNA cnt VT cnt + I\nDESIGNA i VT C }\ncnt", - Program([], [ - Designa(ID("cnt"), Numeral("I")), - PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [ - Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS")), - Designa(ID("i"), Numeral("C")), - ]), - ExpressionStatement(ID("cnt")), - ]), - ValInt(5)), - # DONICVM: ERVMPE exits loop early; counter persists at break value - ("DONICVM i VT I VSQVE X FAC {\nSI i EST III TVNC { ERVMPE }\n}\ni", - Program([], [ - PerStatement(DataRangeArray(Numeral("I"), Numeral("X")), ID("i"), [ - SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None), - ]), - ExpressionStatement(ID("i")), - ]), - 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", - Program([], [ - Designa(ID("n"), Numeral("I")), - Defini(ID("f"), [ID("n")], [Designa(ID("n"), BinOp(ID("n"), Numeral("X"), "SYMBOL_PLUS")), Redi([ID("n")])]), - ExpressionStatement(Invoca(ID("f"), [Numeral("V")])), - ExpressionStatement(ID("n")), - ]), - 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", - Program([], [ - Designa(ID("x"), Numeral("I")), - Defini(ID("f"), [], [Designa(ID("x"), Numeral("C")), Redi([ID("x")])]), - ExpressionStatement(Invoca(ID("f"), [])), - ExpressionStatement(ID("x")), - ]), - 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)", - Program([], [ - Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), - ExpressionStatement(BinOp(Invoca(ID("f"), [Numeral("III")]), Invoca(ID("f"), [Numeral("IV")]), "SYMBOL_PLUS")), - ]), - 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", - Program([], [ - Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), - Designa(ID("n"), Numeral("II")), - ExpressionStatement(Invoca(ID("f"), [Numeral("I")])), - ExpressionStatement(ID("n")), - ]), - ValInt(2)), -] - -class TestScope(unittest.TestCase): - @parameterized.expand(scope_tests) - def test_scope(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- NON (boolean not) --- - -non_tests = [ - ("NON VERITAS", - Program([], [ExpressionStatement(UnaryNot(Bool(True)))]), - ValBool(False)), - ("NON FALSITAS", - Program([], [ExpressionStatement(UnaryNot(Bool(False)))]), - ValBool(True)), - ("NON NON VERITAS", - Program([], [ExpressionStatement(UnaryNot(UnaryNot(Bool(True))))]), - ValBool(True)), - ("DESIGNA b VT I EST II\nNON b", - Program([], [Designa(ID("b"), BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("b")))]), - ValBool(True)), - ("DESIGNA z VT I EST I\nNON z", - Program([], [Designa(ID("z"), BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("z")))]), - ValBool(False)), - ("NON VERITAS AVT FALSITAS", - Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_AVT"))]), - ValBool(False)), - ("NON VERITAS EST FALSITAS", - Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_EST"))]), - ValBool(True)), -] - -class TestNon(unittest.TestCase): - @parameterized.expand(non_tests) - def test_non(self, source, nodes, value): - run_test(self, source, nodes, value) - - -# --- FRACTIO module --- - -fractio_tests = [ - # Basic fraction literals - ("CVM FRACTIO\nIIIS", - Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("IIIS"))]), - ValFrac(Fraction(7, 2))), - ("CVM FRACTIO\nS", - Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("S"))]), - ValFrac(Fraction(1, 2))), - ("CVM FRACTIO\nS:.", - Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("S:."))]), - ValFrac(Fraction(3, 4))), - ("CVM FRACTIO\n.", - Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("."))]), - ValFrac(Fraction(1, 12))), - ("CVM FRACTIO\n:.", - Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio(":."))]), - ValFrac(Fraction(1, 4))), - # Integer part with fraction - ("CVM FRACTIO\nVIIS:|::", - Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("VIIS:|::"))]), - ValFrac(Fraction(7) + Fraction(100, 144))), - # Arithmetic - ("CVM FRACTIO\nIIIS + S", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_PLUS")) - ]), - ValFrac(Fraction(4)) - ), - ("CVM FRACTIO\nIIIS - S", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_MINUS")) - ]), - ValFrac(Fraction(3)) - ), - ("CVM FRACTIO\nS * IV", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("S"), Numeral("IV"), "SYMBOL_TIMES")) - ]), - ValFrac(Fraction(2)) - ), - # Division returns fraction - ("CVM FRACTIO\nI / IV", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Numeral("I"), Numeral("IV"), "SYMBOL_DIVIDE")) - ]), - ValFrac(Fraction(1, 4)) - ), - ("CVM FRACTIO\nI / III", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Numeral("I"), Numeral("III"), "SYMBOL_DIVIDE")) - ]), - ValFrac(Fraction(1, 3)) - ), - # Integer division still works without fractions in operands... but with FRACTIO returns ValFrac - ("CVM FRACTIO\nX / II", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE")) - ]), - ValFrac(Fraction(5)) - ), - # Modulo on fractions: 7/2 RELIQVVM 3/2 = 1/2 (7/2 / 3/2 = 7/3, floor=2, 7/2 - 3 = 1/2) - ("CVM FRACTIO\nIIIS RELIQVVM IS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IS"), "KEYWORD_RELIQVVM")) - ]), - ValFrac(Fraction(1, 2)) - ), - # Modulo with mixed operand types: 5/2 RELIQVVM 1 = 1/2 - ("CVM FRACTIO\nIIS RELIQVVM I", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIS"), Numeral("I"), "KEYWORD_RELIQVVM")) - ]), - ValFrac(Fraction(1, 2)) - ), - # Int operands under FRACTIO still return a fraction: 10 RELIQVVM 3 = 1 (as Fraction) - ("CVM FRACTIO\nX RELIQVVM III", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "KEYWORD_RELIQVVM")) - ]), - ValFrac(Fraction(1)) - ), - # Exact multiple under FRACTIO: 3 RELIQVVM 3/2 = 0 - ("CVM FRACTIO\nIII RELIQVVM IS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Numeral("III"), Fractio("IS"), "KEYWORD_RELIQVVM")) - ]), - ValFrac(Fraction(0)) - ), - # String concatenation with fraction - ("CVM FRACTIO\nDIC(IIIS & \"!\")", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BuiltIn("DIC", [BinOp(Fractio("IIIS"), String("!"), "SYMBOL_AMPERSAND")])) - ]), - ValStr("IIIS!"), "IIIS!\n" - ), - # Negative fractions - ("CVM FRACTIO\nCVM SVBNVLLA\n-IIS", - Program([ModuleCall("FRACTIO"),ModuleCall("SVBNVLLA")],[ - ExpressionStatement(UnaryMinus(Fractio("IIS"))) - ]), - ValFrac(Fraction(-5,2)) - ) -] - -class TestFractio(unittest.TestCase): - @parameterized.expand(fractio_tests) - def test_fractio(self, source, nodes, value, output=""): - run_test(self, source, nodes, value, output) - - -fractio_comparison_tests = [ - # fraction vs fraction - ("CVM FRACTIO\nIIIS PLVS III", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("III"), "KEYWORD_PLVS")) - ]), - ValBool(True) - ), - ("CVM FRACTIO\nIII MINVS IIIS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Numeral("III"), Fractio("IIIS"), "KEYWORD_MINVS")) - ]), - ValBool(True) - ), - ("CVM FRACTIO\nIIIS MINVS IV", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("IV"), "KEYWORD_MINVS")) - ]), - ValBool(True) - ), - ("CVM FRACTIO\nIV PLVS IIIS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Numeral("IV"), Fractio("IIIS"), "KEYWORD_PLVS")) - ]), - ValBool(True) - ), - ("CVM FRACTIO\nIIIS PLVS IIIS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_PLVS")) - ]), - ValBool(False) - ), - ("CVM FRACTIO\nIIIS MINVS IIIS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_MINVS")) - ]), - ValBool(False) - ), - # HAVD_PLVS / HAVD_MINVS on fractions — equality boundary distinguishes from MINVS / PLVS - ("CVM FRACTIO\nIIIS HAVD_PLVS III", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("III"), "KEYWORD_HAVD_PLVS")) - ]), - ValBool(False) # 3.5 <= 3 is false - ), - ("CVM FRACTIO\nIIIS HAVD_MINVS IIIS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_HAVD_MINVS")) - ]), - ValBool(True) # 3.5 >= 3.5 is true (equality boundary) - ), - ("CVM FRACTIO\nIIIS HAVD_PLVS IIIS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_HAVD_PLVS")) - ]), - ValBool(True) # 3.5 <= 3.5 is true (equality boundary) - ), - # equality: fraction == fraction - ("CVM FRACTIO\nIIIS EST IIIS", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_EST")) - ]), - ValBool(True) - ), - ("CVM FRACTIO\nIIIS EST IV", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("IV"), "KEYWORD_EST")) - ]), - ValBool(False) - ), - # equality: fraction == whole number (ValFrac(4) vs ValInt(4)) - ("CVM FRACTIO\nIIIS + S EST IV", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp( - BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_PLUS"), - Numeral("IV"), "KEYWORD_EST")) - ]), - ValBool(True) - ), - ("CVM FRACTIO\nS + S EST I", - Program([ModuleCall("FRACTIO")], [ - ExpressionStatement(BinOp( - BinOp(Fractio("S"), Fractio("S"), "SYMBOL_PLUS"), - Numeral("I"), "KEYWORD_EST")) - ]), - ValBool(True) - ), -] - -class TestFractioComparisons(unittest.TestCase): - @parameterized.expand(fractio_comparison_tests) - def test_fractio_comparison(self, source, nodes, value): - run_test(self, source, nodes, value) - - -class TestFractioHelpers(unittest.TestCase): - def test_frac_to_fraction_ordering(self): - with self.assertRaises(CentvrionError): - frac_to_fraction(".S") # . before S violates highest-to-lowest - - def test_frac_to_fraction_level_overflow(self): - with self.assertRaises(CentvrionError): - frac_to_fraction("SSSSSS") # SS means S twice = 12/12 = 1, violating < 12/12 constraint - - def test_frac_to_fraction_iiis(self): - self.assertEqual(frac_to_fraction("IIIS"), Fraction(7, 2)) - - def test_frac_to_fraction_s_colon_dot(self): - self.assertEqual(frac_to_fraction("S:."), Fraction(3, 4)) - - def test_frac_to_fraction_dot(self): - self.assertEqual(frac_to_fraction("."), Fraction(1, 12)) - - def test_frac_to_fraction_multilevel(self): - self.assertEqual(frac_to_fraction("VIIS:|::"), Fraction(7) + Fraction(100, 144)) - - def test_fraction_to_frac_iiis(self): - self.assertEqual(fraction_to_frac(Fraction(7, 2)), "IIIS") - - def test_fraction_to_frac_s_colon_dot(self): - self.assertEqual(fraction_to_frac(Fraction(3, 4)), "S:.") - - def test_fraction_to_frac_dot(self): - self.assertEqual(fraction_to_frac(Fraction(1, 12)), ".") - - def test_fraction_to_frac_multilevel(self): - self.assertEqual( - fraction_to_frac(Fraction(7) + Fraction(100, 144)), - "VIIS:|::" - ) - - def test_roundtrip(self): - # Only canonical forms roundtrip — fraction_to_frac always uses max colons before dots - for s in ["IIIS", "S:.", ".", "::", "VIIS:|::", "S"]: - self.assertEqual(fraction_to_frac(frac_to_fraction(s)), s) - - -# --- Dict (TABVLA) --- - -dict_tests = [ - # empty dict - ("TABVLA {}", - Program([], [ExpressionStatement(DataDict([]))]), - ValDict({})), - # single string key - ('TABVLA {"a" VT I}', - Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I"))]))]), - ValDict({"a": ValInt(1)})), - # multiple entries - ('TABVLA {"a" VT I, "b" VT II}', - Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]))]), - ValDict({"a": ValInt(1), "b": ValInt(2)})), - # integer keys - ('TABVLA {I VT "one", II VT "two"}', - Program([], [ExpressionStatement(DataDict([(Numeral("I"), String("one")), (Numeral("II"), String("two"))]))]), - ValDict({1: ValStr("one"), 2: ValStr("two")})), - # expression values - ('TABVLA {"x" VT I + II}', - Program([], [ExpressionStatement(DataDict([(String("x"), BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"))]))]), - ValDict({"x": ValInt(3)})), - # empty dict with newline - ('TABVLA {\n}', - Program([], [ExpressionStatement(DataDict([]))]), - ValDict({})), - # single entry with surrounding newlines - ('TABVLA {\n"a" VT I\n}', - Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I"))]))]), - ValDict({"a": ValInt(1)})), - # multiple entries with newlines after commas - ('TABVLA {\n"a" VT I,\n"b" VT II\n}', - Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]))]), - ValDict({"a": ValInt(1), "b": ValInt(2)})), - # newlines around every delimiter - ('TABVLA {\n"a" VT I,\n"b" VT II,\n"c" VT III\n}', - Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II")), (String("c"), Numeral("III"))]))]), - ValDict({"a": ValInt(1), "b": ValInt(2), "c": ValInt(3)})), -] - -class TestDict(unittest.TestCase): - @parameterized.expand(dict_tests) - def test_dict(self, source, nodes, value): - run_test(self, source, nodes, value) - - -dict_index_tests = [ - # string key access - ('TABVLA {"a" VT X}["a"]', - Program([], [ExpressionStatement(ArrayIndex(DataDict([(String("a"), Numeral("X"))]), String("a")))]), - ValInt(10)), - # integer key access - ('TABVLA {I VT "one"}[I]', - Program([], [ExpressionStatement(ArrayIndex(DataDict([(Numeral("I"), String("one"))]), Numeral("I")))]), - ValStr("one")), - # access via variable - ('DESIGNA d VT TABVLA {"x" VT V}\nd["x"]', - Program([], [ - Designa(ID("d"), DataDict([(String("x"), Numeral("V"))])), - ExpressionStatement(ArrayIndex(ID("d"), String("x"))), - ]), - ValInt(5)), - # nested dict access - ('TABVLA {"a" VT TABVLA {"b" VT X}}["a"]["b"]', - Program([], [ExpressionStatement( - ArrayIndex(ArrayIndex(DataDict([(String("a"), DataDict([(String("b"), Numeral("X"))]))]), String("a")), String("b")) - )]), - ValInt(10)), -] - -class TestDictIndex(unittest.TestCase): - @parameterized.expand(dict_index_tests) - def test_dict_index(self, source, nodes, value): - run_test(self, source, nodes, value) - - -dict_assign_tests = [ - # update existing key - ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["a"] VT X\nd["a"]', - Program([], [ - Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), - DesignaIndex(ID("d"), [String("a")], Numeral("X")), - ExpressionStatement(ArrayIndex(ID("d"), String("a"))), - ]), - ValInt(10)), - # insert new key - ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["b"]', - Program([], [ - Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), - DesignaIndex(ID("d"), [String("b")], Numeral("II")), - ExpressionStatement(ArrayIndex(ID("d"), String("b"))), - ]), - ValInt(2)), - # original key unaffected after insert - ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["a"]', - Program([], [ - Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), - DesignaIndex(ID("d"), [String("b")], Numeral("II")), - ExpressionStatement(ArrayIndex(ID("d"), String("a"))), - ]), - ValInt(1)), -] - -class TestDictAssign(unittest.TestCase): - @parameterized.expand(dict_assign_tests) - def test_dict_assign(self, source, nodes, value): - run_test(self, source, nodes, value) - - -dict_builtin_tests = [ - # LONGITVDO on dict - ('LONGITVDO(TABVLA {"a" VT I, "b" VT II})', - Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]), - ValInt(2)), - # LONGITVDO on empty dict - ('LONGITVDO(TABVLA {})', - Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([])]))]), - ValInt(0)), - # CLAVES - ('CLAVES(TABVLA {"a" VT I, "b" VT II})', - Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]), - ValList([ValStr("a"), ValStr("b")])), - # CLAVES with int keys - ('CLAVES(TABVLA {I VT "x", II VT "y"})', - Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(Numeral("I"), String("x")), (Numeral("II"), String("y"))])]))]), - ValList([ValInt(1), ValInt(2)])), -] - -class TestDictBuiltins(unittest.TestCase): - @parameterized.expand(dict_builtin_tests) - def test_dict_builtin(self, source, nodes, value): - run_test(self, source, nodes, value) - - -dict_iteration_tests = [ - # PER iterates over keys - ('DESIGNA r VT ""\nPER k IN TABVLA {"a" VT I, "b" VT II} FAC {\nDESIGNA r VT r & k\n}\nr', - Program([], [ - Designa(ID("r"), String("")), - PerStatement( - DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]), - ID("k"), - [Designa(ID("r"), BinOp(ID("r"), ID("k"), "SYMBOL_AMPERSAND"))], - ), - ExpressionStatement(ID("r")), - ]), - ValStr("ab")), -] - -class TestDictIteration(unittest.TestCase): - @parameterized.expand(dict_iteration_tests) - def test_dict_iteration(self, source, nodes, value): - run_test(self, source, nodes, value) - - -dict_display_tests = [ - # DIC on dict - ('DIC(TABVLA {"a" VT I})', - Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([(String("a"), Numeral("I"))])]))]), - ValStr("{a VT I}"), "{a VT I}\n"), - # DIC on multi-entry dict - ('DIC(TABVLA {"a" VT I, "b" VT II})', - Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]), - ValStr("{a VT I, b VT II}"), "{a VT I, b VT II}\n"), - # DIC on empty dict - ('DIC(TABVLA {})', - Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([])]))]), - ValStr("{}"), "{}\n"), -] - -class TestDictDisplay(unittest.TestCase): - @parameterized.expand(dict_display_tests) - def test_dict_display(self, source, nodes, value, output): - run_test(self, source, nodes, value, output) - - -# --- First-class functions / FVNCTIO --- - -fvnctio_tests = [ - # Lambda assigned to variable, then called - ( - "DESIGNA f VT FVNCTIO (x) VT { REDI (x + I) }\nINVOCA f (V)", - Program([], [ - Designa(ID("f"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])), - ExpressionStatement(Invoca(ID("f"), [Numeral("V")])), - ]), - ValInt(6), - ), - # IIFE: immediately invoked lambda - ( - "INVOCA FVNCTIO (x) VT { REDI (x * II) } (III)", - Program([], [ - ExpressionStatement(Invoca( - Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]), - [Numeral("III")], - )), - ]), - ValInt(6), - ), - # Zero-arg lambda - ( - "INVOCA FVNCTIO () VT { REDI (XLII) } ()", - Program([], [ - ExpressionStatement(Invoca( - Fvnctio([], [Redi([Numeral("XLII")])]), - [], - )), - ]), - ValInt(42), - ), - # Function passed as argument - ( - "DEFINI apply (f, x) VT { REDI (INVOCA f (x)) }\n" - "DESIGNA dbl VT FVNCTIO (n) VT { REDI (n * II) }\n" - "INVOCA apply (dbl, V)", - Program([], [ - Defini(ID("apply"), [ID("f"), ID("x")], [ - Redi([Invoca(ID("f"), [ID("x")])]) - ]), - Designa(ID("dbl"), Fvnctio([ID("n")], [ - Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")]) - ])), - ExpressionStatement(Invoca(ID("apply"), [ID("dbl"), Numeral("V")])), - ]), - ValInt(10), - ), - # Lambda uses caller-scope variable (copy-caller semantics) - ( - "DESIGNA n VT III\n" - "DESIGNA f VT FVNCTIO (x) VT { REDI (x + n) }\n" - "INVOCA f (V)", - Program([], [ - Designa(ID("n"), Numeral("III")), - Designa(ID("f"), Fvnctio([ID("x")], [ - Redi([BinOp(ID("x"), ID("n"), "SYMBOL_PLUS")]) - ])), - ExpressionStatement(Invoca(ID("f"), [Numeral("V")])), - ]), - ValInt(8), - ), - # Named function passed as value - ( - "DEFINI sqr (x) VT { REDI (x * x) }\n" - "DESIGNA f VT sqr\n" - "INVOCA f (IV)", - Program([], [ - Defini(ID("sqr"), [ID("x")], [Redi([BinOp(ID("x"), ID("x"), "SYMBOL_TIMES")])]), - Designa(ID("f"), ID("sqr")), - ExpressionStatement(Invoca(ID("f"), [Numeral("IV")])), - ]), - ValInt(16), - ), - # Nested lambdas - ( - "INVOCA FVNCTIO (x) VT { REDI (INVOCA FVNCTIO (y) VT { REDI (y + I) } (x)) } (V)", - Program([], [ - ExpressionStatement(Invoca( - Fvnctio([ID("x")], [ - Redi([Invoca( - Fvnctio([ID("y")], [Redi([BinOp(ID("y"), Numeral("I"), "SYMBOL_PLUS")])]), - [ID("x")], - )]) - ]), - [Numeral("V")], - )), - ]), - ValInt(6), - ), - # DIC on a function value - ( - "DESIGNA f VT FVNCTIO (x) VT { REDI (x) }\nDIC(f)", - Program([], [ - Designa(ID("f"), Fvnctio([ID("x")], [Redi([ID("x")])])), - ExpressionStatement(BuiltIn("DIC", [ID("f")])), - ]), - ValStr("FVNCTIO"), - "FVNCTIO\n", - ), - # Lambda stored in array, called via index - ( - "DESIGNA fns VT [FVNCTIO (x) VT { REDI (x + I) }, FVNCTIO (x) VT { REDI (x * II) }]\n" - "INVOCA fns[I] (V)", - Program([], [ - Designa(ID("fns"), DataArray([ - Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])]), - Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]), - ])), - ExpressionStatement(Invoca( - ArrayIndex(ID("fns"), Numeral("I")), - [Numeral("V")], - )), - ]), - ValInt(6), - ), - # Lambda stored in dict, called via key - ( - 'DESIGNA d VT TABVLA {"add" VT FVNCTIO (x) VT { REDI (x + I) }}\n' - 'INVOCA d["add"] (V)', - Program([], [ - Designa(ID("d"), DataDict([ - (String("add"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])), - ])), - ExpressionStatement(Invoca( - ArrayIndex(ID("d"), String("add")), - [Numeral("V")], - )), - ]), - ValInt(6), - ), - # Multi-param lambda - ( - "DESIGNA add VT FVNCTIO (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)", - Program([], [ - Designa(ID("add"), Fvnctio([ID("a"), ID("b")], [ - Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")]) - ])), - ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])), - ]), - ValInt(7), - ), -] - -class TestFvnctio(unittest.TestCase): - @parameterized.expand(fvnctio_tests) - def test_fvnctio(self, source, nodes, value, output=""): - run_test(self, source, nodes, value, output) - - -# --- DORMI --- - -dormi_tests = [ - ("DORMI(NVLLVS)", - Program([], [ExpressionStatement(BuiltIn("DORMI", [Nullus()]))]), - ValNul()), -] - -class TestDormi(unittest.TestCase): - @parameterized.expand(dormi_tests) - def test_dormi(self, source, nodes, value, output=""): - run_test(self, source, nodes, value, output) - - def test_dormi_timing_int(self): - source = "DORMI(I)\n" - lexer = Lexer().get_lexer() - tokens = lexer.lex(source) - program = Parser().parse(tokens) - - start = time.time() - program.eval() - elapsed = time.time() - start - self.assertAlmostEqual(elapsed, 1.0, delta=0.5) - - def test_dormi_timing_int_compiled(self): - source = "DORMI(I)\n" - lexer = Lexer().get_lexer() - tokens = lexer.lex(source) - program = Parser().parse(tokens) - - c_source = compile_program(program) - with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: - tmp_c.write(c_source) - tmp_c_path = tmp_c.name - with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: - tmp_bin_path = tmp_bin.name - try: - subprocess.run( - ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], - check=True, capture_output=True, - ) - start = time.time() - proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) - elapsed = time.time() - start - self.assertEqual(proc.returncode, 0) - self.assertAlmostEqual(elapsed, 1.0, delta=0.5) - finally: - os.unlink(tmp_c_path) - os.unlink(tmp_bin_path) - - def test_dormi_timing_frac(self): - source = "CVM FRACTIO\nDORMI(S)\n" - lexer = Lexer().get_lexer() - tokens = lexer.lex(source) - program = Parser().parse(tokens) - - start = time.time() - program.eval() - elapsed = time.time() - start - self.assertAlmostEqual(elapsed, 0.5, delta=0.5) - - def test_dormi_timing_frac_compiled(self): - source = "CVM FRACTIO\nDORMI(S)\n" - lexer = Lexer().get_lexer() - tokens = lexer.lex(source) - program = Parser().parse(tokens) - - c_source = compile_program(program) - with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: - tmp_c.write(c_source) - tmp_c_path = tmp_c.name - with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: - tmp_bin_path = tmp_bin.name - try: - subprocess.run( - ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], - check=True, capture_output=True, - ) - start = time.time() - proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) - elapsed = time.time() - start - self.assertEqual(proc.returncode, 0) - self.assertAlmostEqual(elapsed, 0.5, delta=0.5) - finally: - os.unlink(tmp_c_path) - os.unlink(tmp_bin_path) - - -class TestScripta(unittest.TestCase): - def _run_scripta(self, source): - lexer = Lexer().get_lexer() - tokens = lexer.lex(source + "\n") - program = Parser().parse(tokens) - # printer round-trip - new_text = program.print() - new_tokens = Lexer().get_lexer().lex(new_text + "\n") - new_nodes = Parser().parse(new_tokens) - self.assertEqual(program, new_nodes, f"Printer test\n{source}\n{new_text}") - return program - - def _compile_and_run(self, program): - c_source = compile_program(program) - with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: - tmp_c.write(c_source) - tmp_c_path = tmp_c.name - with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: - tmp_bin_path = tmp_bin.name - try: - subprocess.run( - ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], - check=True, capture_output=True, - ) - proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) - self.assertEqual(proc.returncode, 0, f"Compiler binary exited non-zero:\n{proc.stderr}") - return proc.stdout - finally: - os.unlink(tmp_c_path) - os.unlink(tmp_bin_path) - - def test_scribe_and_lege(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: - path = f.name - try: - source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE MVNDE")\nDIC(LEGE("{path}"))' - program = self._run_scripta(source) - captured = StringIO() - with patch("sys.stdout", captured): - program.eval() - self.assertEqual(captured.getvalue(), "SALVE MVNDE\n") - with open(path) as f: - self.assertEqual(f.read(), "SALVE MVNDE") - finally: - if os.path.exists(path): - os.unlink(path) - - def test_scribe_and_lege_compiled(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: - path = f.name - try: - source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE MVNDE")\nDIC(LEGE("{path}"))' - program = self._run_scripta(source) - output = self._compile_and_run(program) - self.assertEqual(output, "SALVE MVNDE\n") - with open(path) as f: - self.assertEqual(f.read(), "SALVE MVNDE") - finally: - if os.path.exists(path): - os.unlink(path) - - def test_adivnge(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: - path = f.name - try: - source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE")\nADIVNGE("{path}", " MVNDE")\nDIC(LEGE("{path}"))' - program = self._run_scripta(source) - captured = StringIO() - with patch("sys.stdout", captured): - program.eval() - self.assertEqual(captured.getvalue(), "SALVE MVNDE\n") - finally: - if os.path.exists(path): - os.unlink(path) - - def test_adivnge_compiled(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: - path = f.name - try: - source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE")\nADIVNGE("{path}", " MVNDE")\nDIC(LEGE("{path}"))' - program = self._run_scripta(source) - output = self._compile_and_run(program) - self.assertEqual(output, "SALVE MVNDE\n") - finally: - if os.path.exists(path): - os.unlink(path) - - def test_scribe_overwrites(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: - path = f.name - try: - source = f'CVM SCRIPTA\nSCRIBE("{path}", "first")\nSCRIBE("{path}", "second")\nLEGE("{path}")' - program = self._run_scripta(source) - result = program.eval() - self.assertEqual(result, ValStr("second")) - finally: - if os.path.exists(path): - os.unlink(path) - - def test_lege_empty_file(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False, mode="w") as f: - path = f.name - try: - source = f'CVM SCRIPTA\nLEGE("{path}")' - program = self._run_scripta(source) - result = program.eval() - self.assertEqual(result, ValStr("")) - finally: - os.unlink(path) - - def test_lege_preexisting_content(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False, mode="w") as f: - f.write("hello from python") - path = f.name - try: - source = f'CVM SCRIPTA\nLEGE("{path}")' - program = self._run_scripta(source) - result = program.eval() - self.assertEqual(result, ValStr("hello from python")) - finally: - os.unlink(path) - - def test_scribe_returns_nullus(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: - path = f.name - try: - source = f'CVM SCRIPTA\nSCRIBE("{path}", "x")' - program = self._run_scripta(source) - result = program.eval() - self.assertEqual(result, ValNul()) - finally: - if os.path.exists(path): - os.unlink(path) - - def test_adivnge_returns_nullus(self): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: - path = f.name - try: - source = f'CVM SCRIPTA\nADIVNGE("{path}", "x")' - program = self._run_scripta(source) - result = program.eval() - self.assertEqual(result, ValNul()) - finally: - if os.path.exists(path): - os.unlink(path) - - -# --- Tempta/Cape (try/catch) --- - -tempta_tests = [ - # Try block succeeds — catch not entered - ( - "TEMPTA {\nDESIGNA r VT I\n} CAPE e {\nDESIGNA r VT II\n}\nr", - Program([], [ - TemptaStatement( - [Designa(ID("r"), Numeral("I"))], - ID("e"), - [Designa(ID("r"), Numeral("II"))], - ), - ExpressionStatement(ID("r")), - ]), - ValInt(1), - ), - # Try block errors — caught by catch - ( - "TEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT II\n}\nr", - Program([], [ - TemptaStatement( - [Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], - ID("e"), - [Designa(ID("r"), Numeral("II"))], - ), - ExpressionStatement(ID("r")), - ]), - ValInt(2), - ), - # Error variable contains the error message - ( - 'DESIGNA e VT NVLLVS\nTEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nNVLLVS\n}\ne', - Program([], [ - Designa(ID("e"), Nullus()), - TemptaStatement( - [Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], - ID("e"), - [ExpressionStatement(Nullus())], - ), - ExpressionStatement(ID("e")), - ]), - ValStr("Division by zero"), - ), - # Nested tempta — inner catches, outer unaffected - ( - "DESIGNA r VT NVLLVS\nTEMPTA {\nTEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT I\n}\n} CAPE e {\nDESIGNA r VT II\n}\nr", - Program([], [ - Designa(ID("r"), Nullus()), - TemptaStatement( - [TemptaStatement( - [Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], - ID("e"), - [Designa(ID("r"), Numeral("I"))], - )], - ID("e"), - [Designa(ID("r"), Numeral("II"))], - ), - ExpressionStatement(ID("r")), - ]), - ValInt(1), - ), - # REDI inside catch block - ( - "DEFINI f () VT {\nTEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nREDI (III)\n}\nREDI (IV)\n}\nINVOCA f ()", - Program([], [ - Defini(ID("f"), [], [ - TemptaStatement( - [Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], - ID("e"), - [Redi([Numeral("III")])], - ), - Redi([Numeral("IV")]), - ]), - ExpressionStatement(Invoca(ID("f"), [])), - ]), - ValInt(3), - ), - # ERVMPE inside catch block (inside a loop) - ( - "DESIGNA r VT NVLLVS\nDVM r EST I FAC {\nTEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT I\nERVMPE\n}\n}\nr", - Program([], [ - Designa(ID("r"), Nullus()), - DumStatement( - BinOp(ID("r"), Numeral("I"), "KEYWORD_EST"), - [TemptaStatement( - [Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], - ID("e"), - [Designa(ID("r"), Numeral("I")), Erumpe()], - )], - ), - ExpressionStatement(ID("r")), - ]), - ValInt(1), - ), - # Statement after error in try block is not executed - ( - "DESIGNA r VT NVLLVS\nTEMPTA {\nDESIGNA x VT I / NVLLVS\nDESIGNA r VT III\n} CAPE e {\nDESIGNA r VT II\n}\nr", - Program([], [ - Designa(ID("r"), Nullus()), - TemptaStatement( - [Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE")), - Designa(ID("r"), Numeral("III"))], - ID("e"), - [Designa(ID("r"), Numeral("II"))], - ), - ExpressionStatement(ID("r")), - ]), - ValInt(2), - ), -] - -class TestTempta(unittest.TestCase): - @parameterized.expand(tempta_tests) - def test_tempta(self, source, nodes, value, output=""): - run_test(self, source, nodes, value, output) - - -class TestRete(unittest.TestCase): - @classmethod - def setUpClass(cls): - import http.server, threading - class Handler(http.server.BaseHTTPRequestHandler): - def do_GET(self): - self.send_response(200) - self.send_header("Content-Type", "text/plain") - self.end_headers() - self.wfile.write(b"SALVE MVNDE") - def log_message(self, *args): - pass - cls.server = http.server.HTTPServer(("127.0.0.1", 0), Handler) - cls.port = cls.server.server_address[1] - cls.thread = threading.Thread(target=cls.server.serve_forever) - cls.thread.daemon = True - cls.thread.start() - - @classmethod - def tearDownClass(cls): - cls.server.shutdown() - - def test_pete(self): - url = f"http://127.0.0.1:{self.port}/" - source = f'CVM RETE\nPETE("{url}")' - run_test(self, source, - Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("PETE", [String(url)]))]), - ValStr("SALVE MVNDE")) - - def test_pete_dic(self): - url = f"http://127.0.0.1:{self.port}/" - source = f'CVM RETE\nDIC(PETE("{url}"))' - run_test(self, source, - Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("PETE", [String(url)])]))]), - ValStr("SALVE MVNDE"), "SALVE MVNDE\n") - - -class TestReteServer(unittest.TestCase): - """Integration tests for PETITVR + AVSCVLTA server functionality.""" - - def _wait_for_server(self, port, timeout=2.0): - """Poll until the server is accepting connections.""" - import socket - deadline = time.time() + timeout - while time.time() < deadline: - try: - with socket.create_connection(("127.0.0.1", port), timeout=0.1): - return - except OSError: - time.sleep(0.05) - self.fail(f"Server on port {port} did not start within {timeout}s") - - def _free_port(self): - """Find a free port in range 1024-3999 (representable without MAGNVM).""" - import socket, random - for _ in range(100): - port = random.randint(1024, 3999) - try: - with socket.socket() as s: - s.bind(("127.0.0.1", port)) - return port - except OSError: - continue - raise RuntimeError("Could not find a free port in range 1024-3999") - - def _run_server(self, source): - """Parse and eval source in a daemon thread. Returns when server is ready.""" - import threading - lexer = Lexer().get_lexer() - tokens = lexer.lex(source + "\n") - program = Parser().parse(tokens) - t = threading.Thread(target=program.eval, daemon=True) - t.start() - - def test_basic_handler(self): - port = self._free_port() - source = ( - f'CVM RETE\n' - f'PETITVR("/", FVNCTIO (petitio) VT {{\n' - f'REDI("SALVE MVNDE")\n' - f'}})\n' - f'AVSCVLTA({int_to_num(port, False)})' - ) - self._run_server(source) - self._wait_for_server(port) - import urllib.request - resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/") - self.assertEqual(resp.read().decode(), "SALVE MVNDE") - - def test_multiple_routes(self): - port = self._free_port() - source = ( - f'CVM RETE\n' - f'PETITVR("/", FVNCTIO (p) VT {{\nREDI("RADIX")\n}})\n' - f'PETITVR("/nomen", FVNCTIO (p) VT {{\nREDI("MARCVS")\n}})\n' - f'AVSCVLTA({int_to_num(port, False)})' - ) - self._run_server(source) - self._wait_for_server(port) - import urllib.request - resp1 = urllib.request.urlopen(f"http://127.0.0.1:{port}/") - self.assertEqual(resp1.read().decode(), "RADIX") - resp2 = urllib.request.urlopen(f"http://127.0.0.1:{port}/nomen") - self.assertEqual(resp2.read().decode(), "MARCVS") - - def test_404_unmatched(self): - port = self._free_port() - source = ( - f'CVM RETE\n' - f'PETITVR("/", FVNCTIO (p) VT {{\nREDI("ok")\n}})\n' - f'AVSCVLTA({int_to_num(port, False)})' - ) - self._run_server(source) - self._wait_for_server(port) - import urllib.request, urllib.error - with self.assertRaises(urllib.error.HTTPError) as ctx: - urllib.request.urlopen(f"http://127.0.0.1:{port}/nonexistent") - self.assertEqual(ctx.exception.code, 404) - - def test_request_dict_via(self): - port = self._free_port() - source = ( - f'CVM RETE\n' - f'PETITVR("/echo", FVNCTIO (petitio) VT {{\n' - f'REDI(petitio["via"])\n' - f'}})\n' - f'AVSCVLTA({int_to_num(port, False)})' - ) - self._run_server(source) - self._wait_for_server(port) - import urllib.request - resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/echo") - self.assertEqual(resp.read().decode(), "/echo") - - def test_request_dict_quaestio(self): - port = self._free_port() - source = ( - f'CVM RETE\n' - f'PETITVR("/q", FVNCTIO (petitio) VT {{\n' - f'REDI(petitio["quaestio"])\n' - f'}})\n' - f'AVSCVLTA({int_to_num(port, False)})' - ) - self._run_server(source) - self._wait_for_server(port) - import urllib.request - resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/q?nomen=Marcus") - self.assertEqual(resp.read().decode(), "nomen=Marcus") - - def test_petitvr_stores_route(self): - """PETITVR alone (without AVSCVLTA) just stores a route and returns NVLLVS.""" - source = 'CVM RETE\nPETITVR("/", FVNCTIO (p) VT {\nREDI("hi")\n})' - run_test(self, source, - Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("PETITVR", [ - String("/"), - Fvnctio([ID("p")], [Redi([String("hi")])]) - ]))]), - ValNul()) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/01_test_core____.py b/tests/01_test_core____.py new file mode 100644 index 0000000..026700b --- /dev/null +++ b/tests/01_test_core____.py @@ -0,0 +1,450 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- Output --- + +output_tests = [ + ("DIC(\"hello\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"), + ("DIC(\"world\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("world")]))]), ValStr("world"), "world\n"), + ("DIC(III)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("III")]))]), ValStr("III"), "III\n"), + ("DIC(X)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("X")]))]), ValStr("X"), "X\n"), + ("DIC(MMXXV)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("MMXXV")]))]), ValStr("MMXXV"), "MMXXV\n"), + ("DIC('hello')", Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"), + ("DIC('world')", Program([], [ExpressionStatement(BuiltIn("DIC", [String("world")]))]), ValStr("world"), "world\n"), + ("DIC(\"a\", \"b\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("a"), String("b")]))]), ValStr("a b"), "a b\n"), + ("DIC(\"line one\")\nDIC(\"line two\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("line one")])), ExpressionStatement(BuiltIn("DIC", [String("line two")]))]), ValStr("line two"), "line one\nline two\n"), + ("DIC(DIC(II))", Program([], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("DIC", [Numeral("II")])]))]), ValStr("II"), "II\nII\n"), + ("EVERRE()", Program([], [ExpressionStatement(BuiltIn("EVERRE", []))]), ValNul(), "\033[2J\033[H"), +] + +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", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"))]), ValInt(2)), + ("X - III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_MINUS"))]), ValInt(7)), + ("III * IV", Program([], [ExpressionStatement(BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(12)), + ("X / II", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))]), ValInt(5)), + ("X / III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(3)), # integer division: 10 // 3 = 3 + ("X RELIQVVM III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(1)), # 10 % 3 = 1 + ("IX RELIQVVM III", Program([], [ExpressionStatement(BinOp(Numeral("IX"), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(0)), # exact divisor + ("VII RELIQVVM X", Program([], [ExpressionStatement(BinOp(Numeral("VII"), Numeral("X"), "KEYWORD_RELIQVVM"))]), ValInt(7)), # dividend < divisor + ("II + III * IV", Program([], [ExpressionStatement(BinOp(Numeral("II"), BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"), "SYMBOL_PLUS"))]), ValInt(14)), # precedence: 2 + (3*4) = 14 + ("(II + III) * IV", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(20)), # parens: (2+3)*4 = 20 + ("CVM SVBNVLLA\n- III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(Numeral("III")))]), ValInt(-3)), # unary negation + ("CVM SVBNVLLA\n- (II + III)", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")))]), ValInt(-5)), # unary negation of expression + ("CVM SVBNVLLA\n- - II", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(UnaryMinus(Numeral("II"))))]), ValInt(2)), # double negation + ("CVM SVBNVLLA\nIII + - II", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("III"), UnaryMinus(Numeral("II")), "SYMBOL_PLUS"))]), ValInt(1)), # unary in binary context +] + +class TestArithmetic(unittest.TestCase): + @parameterized.expand(arithmetic_tests) + def test_arithmetic(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- Precedence and associativity --- +# +# Precedence (lowest → highest): +# AVT < ET < (EST, DISPAR, PLVS, MINVS) < (+ -) < (* / RELIQVVM) < UMINUS < INDEX + +precedence_tests = [ + # * binds tighter than -: 10 - (2*3) = 4, not (10-2)*3 = 24 + ("X - II * III", + Program([], [ExpressionStatement(BinOp(Numeral("X"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), "SYMBOL_MINUS"))]), + ValInt(4)), + # / binds tighter than +: (10/2) + 3 = 8, not 10/(2+3) = 2 + ("X / II + III", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_PLUS"))]), + ValInt(8)), + # + binds tighter than EST: (2+3)==5 = True, not 2+(3==5) = type error + ("II + III EST V", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("V"), "KEYWORD_EST"))]), + ValBool(True)), + # * binds tighter than PLVS: (2*3)>4 = True + ("II * III PLVS IV", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), Numeral("IV"), "KEYWORD_PLVS"))]), + ValBool(True)), + # comparison binds tighter than ET: (1==2) AND (2==2) = False AND True = False + ("I EST II ET II EST II", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]), + ValBool(False)), + # + binds tighter than DISPAR: (2+3)!=5 = False, not 2+(3!=5) = type error + ("II + III DISPAR V", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("V"), "KEYWORD_DISPAR"))]), + ValBool(False)), + # DISPAR binds tighter than ET: (1!=2) AND (2!=2) = True AND False = False + ("I DISPAR II ET II DISPAR II", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_DISPAR"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_DISPAR"), "KEYWORD_ET"))]), + ValBool(False)), + # ET binds tighter than AVT: True OR (False AND False) = True + ("VERITAS AVT FALSITAS ET FALSITAS", + Program([], [ExpressionStatement(BinOp(Bool(True), BinOp(Bool(False), Bool(False), "KEYWORD_ET"), "KEYWORD_AVT"))]), + ValBool(True)), + # UMINUS binds tighter than *: (-2)*3 = -6, not -(2*3) = -6 (same value, different tree) + ("CVM SVBNVLLA\n- II * III", + Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_TIMES"))]), + ValInt(-6)), + # UMINUS binds tighter than +: (-2)+3 = 1, not -(2+3) = -5 + ("CVM SVBNVLLA\n- II + III", + Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_PLUS"))]), + ValInt(1)), + # INDEX binds tighter than UMINUS: -(arr[I]) = -1 + ("CVM SVBNVLLA\n- [I, II, III][I]", + Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I"))))]), + ValInt(-1)), + # INDEX binds tighter than NON: NON (arr[I]) = NON VERITAS = False + ("NON [VERITAS, FALSITAS][I]", + Program([], [ExpressionStatement(UnaryNot(ArrayIndex(DataArray([Bool(True), Bool(False)]), Numeral("I"))))]), + ValBool(False)), + # INDEX binds tighter than +: (arr[II]) + X = 2 + 10 = 12 + ("[I, II, III][II] + X", + Program([], [ExpressionStatement(BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")), Numeral("X"), "SYMBOL_PLUS"))]), + ValInt(12)), + # left-associativity of -: (10-3)-2 = 5, not 10-(3-2) = 9 + ("X - III - II", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_MINUS"), Numeral("II"), "SYMBOL_MINUS"))]), + ValInt(5)), + # left-associativity of /: (12/2)/3 = 2, not 12/(2/3) = 18 + ("XII / II / III", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("XII"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_DIVIDE"))]), + ValInt(2)), + # RELIQVVM same precedence as *, /; left-associative: (17 % 5) % 2 = 0 + ("XVII RELIQVVM V RELIQVVM II", + Program([], [ExpressionStatement( + BinOp(BinOp(Numeral("XVII"), Numeral("V"), "KEYWORD_RELIQVVM"), + Numeral("II"), "KEYWORD_RELIQVVM"))]), + ValInt(0)), + # RELIQVVM binds tighter than +: 2 + (7 % 3) = 3, not (2+7) % 3 = 0 + ("II + VII RELIQVVM III", + Program([], [ExpressionStatement( + BinOp(Numeral("II"), + BinOp(Numeral("VII"), Numeral("III"), "KEYWORD_RELIQVVM"), + "SYMBOL_PLUS"))]), + ValInt(3)), + # left-associativity of AVT: (False OR True) OR False = True + ("FALSITAS AVT VERITAS AVT FALSITAS", + Program([], [ExpressionStatement(BinOp(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"), Bool(False), "KEYWORD_AVT"))]), + ValBool(True)), +] + +class TestPrecedence(unittest.TestCase): + @parameterized.expand(precedence_tests) + def test_precedence(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- Assignment --- + +assignment_tests = [ + ("DESIGNA x VT III\nx", + Program([], [Designa(ID("x"), Numeral("III")), ExpressionStatement(ID("x"))]), + ValInt(3)), + ("DESIGNA msg VT \"hello\"\nmsg", + Program([], [Designa(ID("msg"), String("hello")), ExpressionStatement(ID("msg"))]), + ValStr("hello")), + ("DESIGNA msg VT 'hello'\nmsg", + Program([], [Designa(ID("msg"), String("hello")), ExpressionStatement(ID("msg"))]), + ValStr("hello")), + ("DESIGNA a VT V\nDESIGNA b VT X\na + b", + Program([], [Designa(ID("a"), Numeral("V")), Designa(ID("b"), Numeral("X")), + ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"))]), + ValInt(15)), + ("DESIGNA x VT II\nDESIGNA x VT x + I\nx", + Program([], [Designa(ID("x"), Numeral("II")), + Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), + ExpressionStatement(ID("x"))]), + ValInt(3)), + # Compound assignment — AVGE (+=) + ("DESIGNA x VT V\nx AVGE III\nx", + Program([], [Designa(ID("x"), Numeral("V")), + Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_PLUS")), + ExpressionStatement(ID("x"))]), + ValInt(8)), + # Compound assignment — MINVE (-=) + ("DESIGNA x VT X\nx MINVE III\nx", + Program([], [Designa(ID("x"), Numeral("X")), + Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_MINUS")), + ExpressionStatement(ID("x"))]), + ValInt(7)), + # AVGE with complex expression + ("DESIGNA x VT I\nx AVGE II + III\nx", + Program([], [Designa(ID("x"), Numeral("I")), + Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), "SYMBOL_PLUS")), + ExpressionStatement(ID("x"))]), + ValInt(6)), + # AVGE inside a loop (DONICVM range is inclusive: I VSQVE III = [1, 2, 3]) + ("DESIGNA s VT NVLLVS\nDONICVM i VT I VSQVE III FAC {\ns AVGE i\n}\ns", + Program([], [Designa(ID("s"), Nullus()), + PerStatement(DataRangeArray(Numeral("I"), Numeral("III")), ID("i"), + [Designa(ID("s"), BinOp(ID("s"), ID("i"), "SYMBOL_PLUS"))]), + ExpressionStatement(ID("s"))]), + ValInt(6)), + # Compound assignment — MVLTIPLICA (*=) + ("DESIGNA x VT III\nx MVLTIPLICA II\nx", + Program([], [Designa(ID("x"), Numeral("III")), + Designa(ID("x"), BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")), + ExpressionStatement(ID("x"))]), + ValInt(6)), + # Compound assignment — DIVIDE (/=) + ("DESIGNA x VT XII\nx DIVIDE III\nx", + Program([], [Designa(ID("x"), Numeral("XII")), + Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_DIVIDE")), + ExpressionStatement(ID("x"))]), + ValInt(4)), + # MVLTIPLICA with complex RHS — whole expression is captured before the op + ("DESIGNA x VT II\nx MVLTIPLICA II + I\nx", + Program([], [Designa(ID("x"), Numeral("II")), + Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("I"), "SYMBOL_PLUS"), "SYMBOL_TIMES")), + ExpressionStatement(ID("x"))]), + ValInt(6)), + # DIVIDE with complex RHS + ("DESIGNA x VT XX\nx DIVIDE II + II\nx", + Program([], [Designa(ID("x"), Numeral("XX")), + Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS"), "SYMBOL_DIVIDE")), + ExpressionStatement(ID("x"))]), + ValInt(5)), +] + +class TestAssignment(unittest.TestCase): + @parameterized.expand(assignment_tests) + def test_assignment(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- Destructuring --- + +destructuring_tests = [ + # basic: unpack multi-return function + ( + "DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (III, VII)\nx + y", + Program([], [ + Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]), + DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("III"), Numeral("VII")])), + ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS")), + ]), + ValInt(10), + ), + # unpack array literal + ( + "DESIGNA a, b VT [I, II]\na + b", + Program([], [ + DesignaDestructure([ID("a"), ID("b")], DataArray([Numeral("I"), Numeral("II")])), + ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")), + ]), + ValInt(3), + ), + # three variables + ( + "DESIGNA a, b, c VT [X, XX, XXX]\na + b + c", + Program([], [ + DesignaDestructure([ID("a"), ID("b"), ID("c")], DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])), + ExpressionStatement(BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")), + ]), + ValInt(60), + ), + # destructure into individual use + ( + "DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (V, II)\nDIC(x)\nDIC(y)", + Program([], [ + Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]), + DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("V"), Numeral("II")])), + ExpressionStatement(BuiltIn("DIC", [ID("x")])), + ExpressionStatement(BuiltIn("DIC", [ID("y")])), + ]), + ValStr("II"), + "V\nII\n", + ), +] + +class TestDestructuring(unittest.TestCase): + @parameterized.expand(destructuring_tests) + def test_destructuring(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)", + Program([], [ + Defini(ID("bis"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), + ExpressionStatement(Invoca(ID("bis"), [Numeral("III")])), + ]), + ValInt(6), + ), + ( + "DEFINI add (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)", + Program([], [ + Defini(ID("add"), [ID("a"), ID("b")], [Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")])]), + ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])), + ]), + 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) } ALIVD { REDI (INVOCA fib (n - I) + INVOCA fib (n - II)) }\n}\nINVOCA fib (VII)", + Program([], [ + Defini(ID("fib"), [ID("n")], [ + SiStatement( + BinOp(ID("n"), Numeral("III"), "KEYWORD_MINVS"), + [Redi([Numeral("I")])], + [Redi([BinOp( + Invoca(ID("fib"), [BinOp(ID("n"), Numeral("I"), "SYMBOL_MINUS")]), + Invoca(ID("fib"), [BinOp(ID("n"), Numeral("II"), "SYMBOL_MINUS")]), + "SYMBOL_PLUS", + )])], + ), + ]), + ExpressionStatement(Invoca(ID("fib"), [Numeral("VII")])), + ]), + ValInt(13), + ), +] + +class TestFunctions(unittest.TestCase): + @parameterized.expand(function_tests) + def test_functions(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 ()", + Program([], [Defini(ID("f"), [], [ExpressionStatement(Numeral("I"))]), ExpressionStatement(Invoca(ID("f"), []))]), + ValNul()), + # REDI multiple values → ValList + ( + "DEFINI pair (a, b) VT { REDI (a, b) }\nINVOCA pair (I, II)", + Program([], [ + Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]), + ExpressionStatement(Invoca(ID("pair"), [Numeral("I"), Numeral("II")])), + ]), + 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", + Program([], [ + Designa(ID("x"), Numeral("I")), + Defini(ID("f"), [], [Designa(ID("x"), Numeral("V")), Redi([ID("x")])]), + ExpressionStatement(Invoca(ID("f"), [])), + ExpressionStatement(ID("x")), + ]), + ValInt(1), + ), + # function can read outer vtable (closure-like) + ( + "DESIGNA x VT VII\nDEFINI f () VT { REDI (x) }\nINVOCA f ()", + Program([], [ + Designa(ID("x"), Numeral("VII")), + Defini(ID("f"), [], [Redi([ID("x")])]), + ExpressionStatement(Invoca(ID("f"), [])), + ]), + ValInt(7), + ), + # parameter shadows outer variable inside function + ( + "DESIGNA n VT I\nDEFINI f (n) VT { REDI (n * II) }\nINVOCA f (X)\nn", + Program([], [ + Designa(ID("n"), Numeral("I")), + Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), + ExpressionStatement(Invoca(ID("f"), [Numeral("X")])), + ExpressionStatement(ID("n")), + ]), + 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)", + Program([], [ + Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), + Designa(ID("g"), ID("f")), + ExpressionStatement(Invoca(ID("g"), [Numeral("V")])), + ]), + 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)", + Program([], [ + Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), + Designa(ID("g"), ID("f")), + Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("III"), "SYMBOL_TIMES")])]), + ExpressionStatement(Invoca(ID("g"), [Numeral("V")])), + ]), + ValInt(10), + ), + # REDI inside SI exits function, skips remaining statements in block + ( + "DEFINI f () VT {\nSI VERITAS TVNC {\nREDI (I)\nREDI (II)\n}\n}\nINVOCA f ()", + Program([],[ + Defini(ID("f"), [], [SiStatement(Bool(True),[Redi([Numeral("I")]),Redi([Numeral("II")])],None)]), + ExpressionStatement(Invoca(ID("f"),[])) + ]), + ValInt(1), + ), + # REDI inside DVM exits loop and function + ( + "DEFINI f () VT {\nDESIGNA x VT I\nDVM FALSITAS FAC {\nREDI (x)\n}\n}\nINVOCA f ()", + Program([],[ + Defini(ID("f"), [], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [Redi([ID("x")])]) + ]), + ExpressionStatement(Invoca(ID("f"),[])) + ]), + ValInt(1), + ), + # REDI inside PER exits loop and function + ( + "DEFINI f () VT {\nPER x IN [I, II, III] FAC {\nSI x EST II TVNC {\nREDI (x)\n}\n}\n}\nINVOCA f ()", + Program([],[ + Defini(ID("f"), [], [ + PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("x"), [ + SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"), [ + Redi([ID("x")]) + ], None) + ]) + ]), + ExpressionStatement(Invoca(ID("f"),[])) + ]), + ValInt(2), + ), + # REDI inside nested loops exits all loops and function + ( + "DEFINI f () VT {\nDESIGNA x VT I\nDVM FALSITAS FAC {\nDVM FALSITAS FAC {\nREDI (x)\n}\n}\n}\nINVOCA f ()", + Program([],[ + Defini(ID("f"), [], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [ + DumStatement(Bool(False), [ + Redi([ID("x")]) + ]) + ]) + ]), + ExpressionStatement(Invoca(ID("f"),[])) + ]), + ValInt(1), + ), +] + +class TestFunctionEdge(unittest.TestCase): + @parameterized.expand(function_edge_tests) + def test_function_edge(self, source, nodes, value): + run_test(self, source, nodes, value) diff --git a/tests/02_test_con_flow.py b/tests/02_test_con_flow.py new file mode 100644 index 0000000..84b890f --- /dev/null +++ b/tests/02_test_con_flow.py @@ -0,0 +1,418 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + +# --- Control flow --- + +control_tests = [ + # SI without ALIVD — true branch + ("SI VERITAS TVNC { DESIGNA r VT I }\nr", + Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("I"))], None), ExpressionStatement(ID("r"))]), + ValInt(1)), + # SI without ALIVD — false branch + ("SI FALSITAS TVNC { DESIGNA r VT I }", + Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("I"))], None)]), + ValNul()), + # SI with ALIVD — true branch + ("SI VERITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(1)), + # SI with ALIVD — false branch + ("SI FALSITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(2)), + # SI with comparison — equal + ("SI I EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(1)), + # SI with comparison — unequal + ("SI I EST II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(2)), + # SI MINVS + ("SI I MINVS II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_MINVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(1)), + # SI PLVS + ("SI II PLVS I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(1)), + # ALIVD SI chain + ( + "SI I EST II TVNC { DESIGNA r VT I } ALIVD SI I EST I TVNC { DESIGNA r VT II } ALIVD { DESIGNA r VT III }\nr", + Program([], [ + SiStatement( + BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), + [Designa(ID("r"), Numeral("I"))], + [SiStatement( + BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), + [Designa(ID("r"), Numeral("II"))], + [Designa(ID("r"), Numeral("III"))], + )], + ), + ExpressionStatement(ID("r")), + ]), + ValInt(2), + ), + # DVM (while not): loops until condition is true + ( + "DESIGNA x VT I\nDVM x EST III FAC {\nDESIGNA x VT x + I\n}\nx", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]), + ExpressionStatement(ID("x")), + ]), + ValInt(3), + ), + # DVM with ERVMPE — loop body prints (testing DIC + ERVMPE together) + ("DESIGNA x VT I\nDVM FALSITAS FAC {\nDIC(x)\nERVMPE\n}", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DIC", [ID("x")])), Erumpe()]), + ]), + ValStr("I"), "I\n"), + # AETERNVM is sugar for DVM FALSITAS — must produce the same AST. + ("DESIGNA x VT I\nAETERNVM FAC {\nDIC(x)\nERVMPE\n}", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DIC", [ID("x")])), Erumpe()]), + ]), + ValStr("I"), "I\n"), + # AETERNVM with counter + ERVMPE on condition + ("DESIGNA x VT I\nAETERNVM FAC {\nSI x EST III TVNC { ERVMPE }\nDESIGNA x VT x + I\n}\nx", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [ + SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None), + Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), + ]), + ExpressionStatement(ID("x")), + ]), + ValInt(3)), + # AETERNVM with CONTINVA — skip printing III; ERVMPE after V. + # Return value is ValNul because the iteration that triggers ERVMPE runs + # Designa first (resetting last_val); we test on output, which is the point. + ("DESIGNA x VT NVLLVS\nAETERNVM FAC {\nDESIGNA x VT x + I\nSI x PLVS V TVNC { ERVMPE }\nSI x EST III TVNC { CONTINVA }\nDIC(x)\n}", + Program([], [ + Designa(ID("x"), Nullus()), + DumStatement(Bool(False), [ + Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), + SiStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_PLVS"), [Erumpe()], None), + SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Continva()], None), + ExpressionStatement(BuiltIn("DIC", [ID("x")])), + ]), + ]), + ValNul(), "I\nII\nIV\nV\n"), + # REDI inside AETERNVM (inside DEFINI) — exits both loop and function + ( + "DEFINI f () VT {\nDESIGNA x VT I\nAETERNVM FAC {\nREDI (x)\n}\n}\nINVOCA f ()", + Program([], [ + Defini(ID("f"), [], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [Redi([ID("x")])]), + ]), + ExpressionStatement(Invoca(ID("f"), [])), + ]), + ValInt(1), + ), + # PER foreach + ("PER i IN [I, II, III] FAC { DIC(i) }", + Program([], [PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("III"), "I\nII\nIII\n"), + # DONICVM range loop + ("DONICVM i VT I VSQVE V FAC { DIC(i) }", + Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("V"), "I\nII\nIII\nIV\nV\n"), + # PER destructuring + ("PER a, b IN [[I, II], [III, IV]] FAC { DIC(a + b) }", + Program([], [PerStatement( + DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]), + [ID("a"), ID("b")], + [ExpressionStatement(BuiltIn("DIC", [BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")]))])]), + ValStr("VII"), "III\nVII\n"), + # PER destructuring: three variables + ("PER a, b, c IN [[I, II, III]] FAC { DIC(a + b + c) }", + Program([], [PerStatement( + DataArray([DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]), + [ID("a"), ID("b"), ID("c")], + [ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")]))])]), + ValStr("VI"), "VI\n"), +] + +class TestControl(unittest.TestCase): + @parameterized.expand(control_tests) + def test_control(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) + +# --- Loop edge cases --- + +loop_edge_tests = [ + # [III VSQVE III] = [3] — single iteration + ("DONICVM i VT III VSQVE III FAC { DIC(i) }", + Program([], [PerStatement(DataRangeArray(Numeral("III"), Numeral("III")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("III"), "III\n"), + # empty array — body never runs + ("PER i IN [] FAC { DIC(i) }", + Program([], [PerStatement(DataArray([]), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValNul(), ""), + # PER breaks on element 2 — last assigned i is 2 + ("PER i IN [I, II, III] FAC { SI i EST II TVNC { ERVMPE } }\ni", + Program([], [ + PerStatement( + DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), + ID("i"), + [SiStatement(BinOp(ID("i"), Numeral("II"), "KEYWORD_EST"), [Erumpe()], None)], + ), + ExpressionStatement(ID("i")), + ]), + ValInt(2), ""), + # nested DVM: inner always breaks; outer runs until btr==3 + ("DESIGNA btr VT I\nDVM btr EST III FAC {\nDVM FALSITAS FAC {\nERVMPE\n}\nDESIGNA btr VT btr + I\n}\nbtr", + Program([], [ + Designa(ID("btr"), Numeral("I")), + DumStatement( + BinOp(ID("btr"), Numeral("III"), "KEYWORD_EST"), + [DumStatement(Bool(False), [Erumpe()]), Designa(ID("btr"), BinOp(ID("btr"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("btr")), + ]), + 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] FAC {\nPER k IN [I, II] FAC {\nERVMPE\n}\nDESIGNA cnt VT cnt + I\n}\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement( + DataArray([Numeral("I"), Numeral("II")]), + ID("i"), + [PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"), [Erumpe()]), + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("cnt")), + ]), + ValInt(3), ""), + # PER with CONTINVA: skip odd numbers, sum evens + # [I,II,III,IV] → skip I and III; cnt increments on II and IV → cnt = III + ("DESIGNA cnt VT I\nPER i IN [I, II, III, IV] FAC {\nSI i EST I AVT i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement( + DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]), + ID("i"), + [SiStatement(BinOp(BinOp(ID("i"), Numeral("I"), "KEYWORD_EST"), BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), "KEYWORD_AVT"), [Continva()], None), + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("cnt")), + ]), + ValInt(3), ""), + # DVM with CONTINVA: skip body when x is II, increment regardless + # x goes 1→2→3; on x=2 we continue (no DIC); DIC fires for x=1 and x=3 + ("DESIGNA x VT I\nDVM x EST IV FAC {\nSI x EST II TVNC { DESIGNA x VT x + I\nCONTINVA }\nDIC(x)\nDESIGNA x VT x + I\n}\nx", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement( + BinOp(ID("x"), Numeral("IV"), "KEYWORD_EST"), + [SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"), + [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), Continva()], None), + ExpressionStatement(BuiltIn("DIC", [ID("x")])), + Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("x")), + ]), + ValInt(4), "I\nIII\n"), + # nested PER: CONTINVA in inner only skips rest of inner body; outer still increments + ("DESIGNA cnt VT I\nPER i IN [I, II] FAC {\nPER k IN [I, II] FAC {\nCONTINVA\nDESIGNA cnt VT cnt + I\n}\nDESIGNA cnt VT cnt + I\n}\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement( + DataArray([Numeral("I"), Numeral("II")]), + ID("i"), + [PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"), + [Continva(), Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))]), + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("cnt")), + ]), + ValInt(3), ""), + # DONICVM with CONTINVA: skip value III, count remaining (I VSQVE IV = [1,2,3,4], skip 3 → 3 increments) + ("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FAC {\nSI i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement( + DataRangeArray(Numeral("I"), Numeral("IV")), + ID("i"), + [SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Continva()], None), + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("cnt")), + ]), + ValInt(4)), + # DVM condition true from start — body never runs + ("DESIGNA x VT I\nDVM VERITAS FAC {\nDESIGNA x VT x + I\n}\nx", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(True), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]), + ExpressionStatement(ID("x")), + ]), + ValInt(1), ""), + # two iterations: [I VSQVE II] = [1, 2] + ("DONICVM i VT I VSQVE II FAC { DIC(i) }", + Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("II")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("II"), "I\nII\n"), + # single iteration: [I VSQVE I] = [1] + ("DONICVM i VT I VSQVE I FAC { DIC(i) }", + Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("I")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("I"), "I\n"), + # empty range: [V VSQVE I] = [] + ("DESIGNA x VT NVLLVS\nDONICVM i VT V VSQVE I FAC { DESIGNA x VT x + i }\nx", + Program([], [Designa(ID("x"), Nullus()), + PerStatement(DataRangeArray(Numeral("V"), Numeral("I")), ID("i"), + [Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]), + ExpressionStatement(ID("x"))]), + ValNul(), ""), + # PER destructuring with ERVMPE + ("DESIGNA r VT I\nPER a, b IN [[I, II], [III, IV], [V, VI]] FAC {\nSI a EST III TVNC { ERVMPE }\nDESIGNA r VT r + a + b\n}\nr", + Program([], [ + Designa(ID("r"), Numeral("I")), + PerStatement( + DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), DataArray([Numeral("V"), Numeral("VI")])]), + [ID("a"), ID("b")], + [SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None), + Designa(ID("r"), BinOp(BinOp(ID("r"), ID("a"), "SYMBOL_PLUS"), ID("b"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("r")), + ]), + ValInt(4)), # 1 + 1 + 2 = 4, breaks before [III, IV] + # PER destructuring with REDI + ("DEFINI f () VT {\nPER a, b IN [[I, II], [III, IV]] FAC {\nSI a EST III TVNC { REDI (b) }\n}\n}\nINVOCA f ()", + Program([], [ + Defini(ID("f"), [], + [PerStatement( + DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]), + [ID("a"), ID("b")], + [SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Redi([ID("b")])], None)], + )]), + ExpressionStatement(Invoca(ID("f"), [])), + ]), + ValInt(4)), # returns b=IV when a=III + # DONICVM GRADV II, endpoint hit exactly: [I, III, V] + ("DONICVM i VT I VSQVE V GRADV II FAC { DIC(i) }", + Program([], [PerStatement( + DataRangeArray(Numeral("I"), Numeral("V"), Numeral("II")), + ID("i"), + [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("V"), "I\nIII\nV\n"), + # DONICVM GRADV II, endpoint overshot: VI excluded, stops at V + ("DONICVM i VT I VSQVE VI GRADV II FAC { DIC(i) }", + Program([], [PerStatement( + DataRangeArray(Numeral("I"), Numeral("VI"), Numeral("II")), + ID("i"), + [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("V"), "I\nIII\nV\n"), + # DONICVM GRADV I is equivalent to default step + ("DONICVM i VT I VSQVE III GRADV I FAC { DIC(i) }", + Program([], [PerStatement( + DataRangeArray(Numeral("I"), Numeral("III"), Numeral("I")), + ID("i"), + [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("III"), "I\nII\nIII\n"), + # DONICVM descending by I + ("CVM SVBNVLLA\nDONICVM i VT V VSQVE I GRADV - I FAC { DIC(i) }", + Program([ModuleCall("SVBNVLLA")], [PerStatement( + DataRangeArray(Numeral("V"), Numeral("I"), UnaryMinus(Numeral("I"))), + ID("i"), + [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("I"), "V\nIV\nIII\nII\nI\n"), + # DONICVM descending by II, endpoint overshot + ("CVM SVBNVLLA\nDONICVM i VT X VSQVE I GRADV - II FAC { DIC(i) }", + Program([ModuleCall("SVBNVLLA")], [PerStatement( + DataRangeArray(Numeral("X"), Numeral("I"), UnaryMinus(Numeral("II"))), + ID("i"), + [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("II"), "X\nVIII\nVI\nIV\nII\n"), + # DONICVM with step bound to a variable + ("DESIGNA s VT II\nDONICVM i VT I VSQVE V GRADV s FAC { DIC(i) }", + Program([], [ + Designa(ID("s"), Numeral("II")), + PerStatement( + DataRangeArray(Numeral("I"), Numeral("V"), ID("s")), + ID("i"), + [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("V"), "I\nIII\nV\n"), + # DONICVM from == to with nonzero step: single iteration + ("DONICVM i VT III VSQVE III GRADV II FAC { DIC(i) }", + Program([], [PerStatement( + DataRangeArray(Numeral("III"), Numeral("III"), Numeral("II")), + ID("i"), + [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("III"), "III\n"), + # DONICVM direction mismatch — negative step with ascending bounds: body never runs + ("CVM SVBNVLLA\nDESIGNA x VT NVLLVS\nDONICVM i VT I VSQVE X GRADV - I FAC { DESIGNA x VT x + i }\nx", + Program([ModuleCall("SVBNVLLA")], [ + Designa(ID("x"), Nullus()), + PerStatement( + DataRangeArray(Numeral("I"), Numeral("X"), UnaryMinus(Numeral("I"))), + ID("i"), + [Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]), + ExpressionStatement(ID("x"))]), + ValNul(), ""), + # Range-array literal with GRADV used via PER + ("PER i IN [I VSQVE V GRADV II] FAC { DIC(i) }", + Program([], [PerStatement( + DataRangeArray(Numeral("I"), Numeral("V"), Numeral("II")), + ID("i"), + [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("V"), "I\nIII\nV\n"), + # DONICVM GRADV II with CONTINVA: skip value V, print the others + ("DONICVM i VT I VSQVE IX GRADV II FAC {\nSI i EST V TVNC { CONTINVA }\nDIC(i)\n}", + Program([], [PerStatement( + DataRangeArray(Numeral("I"), Numeral("IX"), Numeral("II")), + ID("i"), + [SiStatement(BinOp(ID("i"), Numeral("V"), "KEYWORD_EST"), [Continva()], None), + ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("IX"), "I\nIII\nVII\nIX\n"), + # DONICVM GRADV II with ERVMPE: stop at V (last successful DIC was III) + ("DONICVM i VT I VSQVE IX GRADV II FAC {\nSI i EST V TVNC { ERVMPE }\nDIC(i)\n}", + Program([], [PerStatement( + DataRangeArray(Numeral("I"), Numeral("IX"), Numeral("II")), + ID("i"), + [SiStatement(BinOp(ID("i"), Numeral("V"), "KEYWORD_EST"), [Erumpe()], None), + ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), + ValStr("III"), "I\nIII\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) + +# --- SI/DVM: boolean condition enforcement --- + +dvm_bool_condition_tests = [ + # DVM exits when condition becomes true (boolean comparison) + ( + "DESIGNA x VT I\nDVM x PLVS III FAC {\nDESIGNA x VT x + I\n}\nx", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_PLVS"), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]), + ExpressionStatement(ID("x")), + ]), + ValInt(4), + ), +] + +class TestDvmBoolCondition(unittest.TestCase): + @parameterized.expand(dvm_bool_condition_tests) + def test_dvm_bool_condition(self, source, nodes, value): + run_test(self, source, nodes, value) diff --git a/tests/03_test_builtins.py b/tests/03_test_builtins.py new file mode 100644 index 0000000..cf327ff --- /dev/null +++ b/tests/03_test_builtins.py @@ -0,0 +1,226 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + +# --- Builtins --- + +builtin_tests = [ + ("AVDI_NVMERVS()", Program([], [ExpressionStatement(BuiltIn("AVDI_NVMERVS", []))]), ValInt(3), "", ["III"]), + ("AVDI_NVMERVS()", Program([], [ExpressionStatement(BuiltIn("AVDI_NVMERVS", []))]), ValInt(10), "", ["X"]), + ("CVM FORS\nDESIGNA a VT [I, II, III]\nDIC(a[FORTVITVS_NVMERVS(I, LONGITVDO(a))])", Program([ModuleCall("FORS")], [Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), ExpressionStatement(BuiltIn("DIC", [ArrayIndex(ID("a"), BuiltIn("FORTVITVS_NVMERVS", [Numeral("I"), BuiltIn("LONGITVDO", [ID("a")])]))]))]), ValStr("I"), "I\n"), + ("AVDI()", Program([], [ExpressionStatement(BuiltIn("AVDI", []))]), ValStr("hello"), "", ["hello"]), + ("LONGITVDO([I, II, III])", Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValInt(3)), + ("LONGITVDO([])", Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataArray([])]))]), ValInt(0)), + ('LONGITVDO("salve")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("salve")]))]), ValInt(5)), + ('LONGITVDO("")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("")]))]), ValInt(0)), + ("CVM FORS\nDIC(FORTVITA_ELECTIO([I, II, III]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("FORTVITA_ELECTIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])])]))]), ValStr("I"), "I\n"), + ("CVM FORS\nSEMEN(XLII)\nDIC(FORTVITVS_NVMERVS(I, C))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("FORTVITVS_NVMERVS", [Numeral("I"), Numeral("C")])]))]), ValStr("XXXIII"), "XXXIII\n"), + # DECIMATIO: seed 42, 10 elements → removes 1 (element III) + ("CVM FORS\nSEMEN(XLII)\nDIC(DECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X")])])]))]), ValStr("[I II IV V VI VII VIII IX X]"), "[I II IV V VI VII VIII IX X]\n"), + # DECIMATIO: seed 1, 3 elements → 3//10=0, nothing removed + ("CVM FORS\nSEMEN(I)\nDIC(DECIMATIO([I, II, III]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("I")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])])]))]), ValStr("[I II III]"), "[I II III]\n"), + # DECIMATIO: empty array → empty array + ("CVM FORS\nDIC(DECIMATIO([]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([])])]))]), ValStr("[]"), "[]\n"), + # DECIMATIO: seed 42, 20 elements → removes 2 (elements XIII and XII) + ("CVM FORS\nSEMEN(XLII)\nDIC(DECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X"), Numeral("XI"), Numeral("XII"), Numeral("XIII"), Numeral("XIV"), Numeral("XV"), Numeral("XVI"), Numeral("XVII"), Numeral("XVIII"), Numeral("XIX"), Numeral("XX")])])]))]), ValStr("[I II III IV V VI VII VIII IX X XI XIV XV XVI XVII XVIII XIX XX]"), "[I II III IV V VI VII VIII IX X XI XIV XV XVI XVII XVIII XIX XX]\n"), + # SENATVS: majority true → VERITAS + ("SENATVS(VERITAS, VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False)]))]), ValBool(True)), + # SENATVS: majority false → FALSITAS + ("SENATVS(FALSITAS, FALSITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False), Bool(False), Bool(True)]))]), ValBool(False)), + # SENATVS: tie → FALSITAS + ("SENATVS(VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(False)]))]), ValBool(False)), + # SENATVS: 4-arg tie → FALSITAS + ("SENATVS(VERITAS, VERITAS, FALSITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False), Bool(False)]))]), ValBool(False)), + # SENATVS: single true → VERITAS + ("SENATVS(VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True)]))]), ValBool(True)), + # SENATVS: single false → FALSITAS + ("SENATVS(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False)]))]), ValBool(False)), + # SENATVS: empty → FALSITAS (vacuous) + ("SENATVS()", Program([], [ExpressionStatement(BuiltIn("SENATVS", []))]), ValBool(False)), + # SENATVS: all true �� VERITAS + ("SENATVS(VERITAS, VERITAS, VERITAS, VERITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(True), Bool(True), Bool(True)]))]), ValBool(True)), + # SENATVS: array input, majority true → VERITAS + ("SENATVS([VERITAS, VERITAS, FALSITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(True), Bool(True), Bool(False)])]))]), ValBool(True)), + # SENATVS: array input, majority false → FALSITAS + ("SENATVS([FALSITAS, FALSITAS, VERITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(False), Bool(False), Bool(True)])]))]), ValBool(False)), + # SENATVS: array input, empty → FALSITAS + ("SENATVS([])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([])]))]), ValBool(False)), + # ORDINA: sort integers + ("ORDINA([III, I, II])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Numeral("I"), Numeral("II")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), + # ORDINA: sort strings + ('ORDINA(["c", "a", "b"])', Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([String("c"), String("a"), String("b")])]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), + # ORDINA: empty list + ("ORDINA([])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([])]))]), ValList([])), + # ORDINA: single element + ("ORDINA([V])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("V")])]))]), ValList([ValInt(5)])), + # ORDINA: already sorted + ("ORDINA([I, II, III])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), + # ORDINA: duplicates + ("ORDINA([II, I, II])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("II"), Numeral("I"), Numeral("II")])]))]), ValList([ValInt(1), ValInt(2), ValInt(2)])), + # ORDINA: negative numbers + ("CVM SVBNVLLA\nORDINA([-II, III, -I])", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([UnaryMinus(Numeral("II")), Numeral("III"), UnaryMinus(Numeral("I"))])]))]), ValList([ValInt(-2), ValInt(-1), ValInt(3)])), + # ORDINA: fractions only + ("CVM FRACTIO\nORDINA([IIIS, S, IIS])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Fractio("IIIS"), Fractio("S"), Fractio("IIS")])]))]), ValList([ValFrac(Fraction(1, 2)), ValFrac(Fraction(5, 2)), ValFrac(Fraction(7, 2))])), + # ORDINA: mixed integers and fractions + ("CVM FRACTIO\nORDINA([III, S, II])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Fractio("S"), Numeral("II")])]))]), ValList([ValFrac(Fraction(1, 2)), ValInt(2), ValInt(3)])), + # ORDINA: array passed via variable + ("DESIGNA x VT [III, I, II]\nORDINA(x)", Program([], [Designa(ID("x"), DataArray([Numeral("III"), Numeral("I"), Numeral("II")])), ExpressionStatement(BuiltIn("ORDINA", [ID("x")]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), + # TYPVS: integer + ("TYPVS(V)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Numeral("V")]))]), ValStr("NVMERVS")), + # TYPVS: string + ('TYPVS("hello")', Program([], [ExpressionStatement(BuiltIn("TYPVS", [String("hello")]))]), ValStr("LITTERA")), + # TYPVS: boolean + ("TYPVS(VERITAS)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Bool(True)]))]), ValStr("VERAX")), + # TYPVS: list + ("TYPVS([I, II])", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataArray([Numeral("I"), Numeral("II")])]))]), ValStr("CATALOGVS")), + # TYPVS: empty list + ("TYPVS([])", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataArray([])]))]), ValStr("CATALOGVS")), + # TYPVS: fraction + ("CVM FRACTIO\nTYPVS(S)", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("TYPVS", [Fractio("S")]))]), ValStr("FRACTIO")), + # TYPVS: dict + ("TYPVS(TABVLA {})", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataDict([])]))]), ValStr("TABVLA")), + # TYPVS: function + ("TYPVS(FVNCTIO () VT { REDI(I) })", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Fvnctio([], [Redi([Numeral("I")])])]))]), ValStr("FVNCTIO")), + # TYPVS: null + ("TYPVS(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Nullus()]))]), ValStr("NVLLVS")), + # LITTERA: integer → Roman numeral + ("LITTERA(V)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Numeral("V")]))]), ValStr("V")), + # LITTERA: larger integer + ("LITTERA(MCMLXXXIV)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Numeral("MCMLXXXIV")]))]), ValStr("MCMLXXXIV")), + # LITTERA: zero → NVLLVS + ("LITTERA(I - I)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("NVLLVS")), + # LITTERA: string passthrough + ('LITTERA("salve")', Program([], [ExpressionStatement(BuiltIn("LITTERA", [String("salve")]))]), ValStr("salve")), + # LITTERA: empty string + ('LITTERA("")', Program([], [ExpressionStatement(BuiltIn("LITTERA", [String("")]))]), ValStr("")), + # LITTERA: VERITAS + ("LITTERA(VERITAS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Bool(True)]))]), ValStr("VERITAS")), + # LITTERA: FALSITAS + ("LITTERA(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Bool(False)]))]), ValStr("FALSITAS")), + # LITTERA: NVLLVS + ("LITTERA(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Nullus()]))]), ValStr("NVLLVS")), + # LITTERA: array of integers + ("LITTERA([I, II, III])", Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValStr("[I II III]")), + # LITTERA: empty array + ("LITTERA([])", Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataArray([])]))]), ValStr("[]")), + # LITTERA: dict with string keys (make_string emits bare keys, not quoted — matches DIC's output) + ('LITTERA(TABVLA {"a" VT I})', Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataDict([(String("a"), Numeral("I"))])]))]), ValStr('{a VT I}')), + # LITTERA: fraction (requires FRACTIO module) + ("CVM FRACTIO\nLITTERA(S)", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("LITTERA", [Fractio("S")]))]), ValStr("S")), + # LITTERA: negative integer + ("CVM SVBNVLLA\nLITTERA(-III)", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("LITTERA", [UnaryMinus(Numeral("III"))]))]), ValStr("-III")), + # LITTERA: round-trips through NVMERVS + ('NVMERVS(LITTERA(VII))', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [BuiltIn("LITTERA", [Numeral("VII")])]))]), ValInt(7)), + # LITTERA: concatenated with a string via & + ('LITTERA(V) & " est quinque"', Program([], [ExpressionStatement(BinOp(BuiltIn("LITTERA", [Numeral("V")]), String(" est quinque"), "SYMBOL_AMPERSAND"))]), ValStr("V est quinque")), + # LITTERA: via variable + ("DESIGNA x VT IX\nLITTERA(x)", Program([], [Designa(ID("x"), Numeral("IX")), ExpressionStatement(BuiltIn("LITTERA", [ID("x")]))]), ValStr("IX")), + # QVAERE: basic literal match + ('QVAERE("ab", "abcabc")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("ab"), String("abcabc")]))]), ValList([ValStr("ab"), ValStr("ab")])), + # QVAERE: no match → empty list + ('QVAERE("xyz", "abc")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("xyz"), String("abc")]))]), ValList([])), + # QVAERE: regex character class + ('QVAERE("[a-z]+", "abc123def")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("[a-z]+"), String("abc123def")]))]), ValList([ValStr("abc"), ValStr("def")])), + # QVAERE: empty text → empty list + ('QVAERE("a", "")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a"), String("")]))]), ValList([])), + # QVAERE: capture groups still return full match + ('QVAERE("(a)(b)", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(a)(b)"), String("ab")]))]), ValList([ValStr("ab")])), + # QVAERE: empty pattern matches between every character + ('QVAERE("", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String(""), String("ab")]))]), ValList([ValStr(""), ValStr(""), ValStr("")])), + # QVAERE: dot matches any character + ('QVAERE(".", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("."), String("ab")]))]), ValList([ValStr("a"), ValStr("b")])), + # SVBSTITVE: basic literal replacement + ('SVBSTITVE("a", "b", "aaa")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String("b"), String("aaa")]))]), ValStr("bbb")), + # SVBSTITVE: regex character class + ('SVBSTITVE("[0-9]+", "N", "abc123def456")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("[0-9]+"), String("N"), String("abc123def456")]))]), ValStr("abcNdefN")), + # SVBSTITVE: no match → string unchanged + ('SVBSTITVE("x", "y", "abc")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("x"), String("y"), String("abc")]))]), ValStr("abc")), + # SVBSTITVE: empty replacement (deletion) + ('SVBSTITVE("a", "", "banana")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String(""), String("banana")]))]), ValStr("bnn")), + # SVBSTITVE: empty text → empty string + ('SVBSTITVE("a", "b", "")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String("b"), String("")]))]), ValStr("")), + # SVBSTITVE: dot matches any character + ('SVBSTITVE(".", "x", "ab")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("."), String("x"), String("ab")]))]), ValStr("xx")), + # SVBSTITVE: backreference swaps two groups (Roman numerals) + ('SVBSTITVE("(a)(b)", "\\II\\I", "ab")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)"), String("\\II\\I"), String("ab")]))]), ValStr("ba")), + # SVBSTITVE: backreference with unmatched group (ignored) + ('SVBSTITVE("(a)(b)?", "\\I\\II", "a")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)?"), String("\\I\\II"), String("a")]))]), ValStr("a")), + # SVBSTITVE: Roman numeral quantifier in pattern + ("SVBSTITVE('a{III}', 'x', 'aaa')", Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a{III}"), String("x"), String("aaa")]))]), ValStr("x")), + # QVAERE: Roman numeral quantifier — exact repetition + ("QVAERE('a{III}', 'aaaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{III}"), String("aaaa")]))]), ValList([ValStr("aaa")])), + # QVAERE: Roman numeral quantifier — range + ("QVAERE('a{II,III}', 'aaaaaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{II,III}"), String("aaaaaa")]))]), ValList([ValStr("aaa"), ValStr("aaa")])), + # QVAERE: Roman numeral quantifier — at-least + ("QVAERE('a{II,}', 'a aa aaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{II,}"), String("a aa aaa")]))]), ValList([ValStr("aa"), ValStr("aaa")])), + # QVAERE: pattern backreference — repeated character + ("QVAERE('(.)\\I', 'aabcdd')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(.)\\I"), String("aabcdd")]))]), ValList([ValStr("aa"), ValStr("dd")])), + # QVAERE: pattern backreference — repeated group + ("QVAERE('(..)\\I', 'ababcc')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(..)\\I"), String("ababcc")]))]), ValList([ValStr("abab")])), + # NVMERVS: basic conversion + ('NVMERVS("XIV")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("XIV")]))]), ValInt(14)), + # NVMERVS: simple single numeral + ('NVMERVS("I")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("I")]))]), ValInt(1)), + # NVMERVS: large numeral + ('NVMERVS("MMMCMXCIX")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("MMMCMXCIX")]))]), ValInt(3999)), + # NVMERVS: subtractive form + ('NVMERVS("IX")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("IX")]))]), ValInt(9)), + # SCINDE: basic split + ('SCINDE("a,b,c", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("a,b,c"), String(",")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), + # SCINDE: no match (delimiter not found) + ('SCINDE("abc", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("abc"), String(",")]))]), ValList([ValStr("abc")])), + # SCINDE: empty string + ('SCINDE("", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String(""), String(",")]))]), ValList([ValStr("")])), + # SCINDE: multi-char delimiter + ('SCINDE("a::b::c", "::")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("a::b::c"), String("::")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), + # SCINDE: delimiter at edges + ('SCINDE(",a,", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String(",a,"), String(",")]))]), ValList([ValStr(""), ValStr("a"), ValStr("")])), + # SCINDE: empty delimiter (split into chars) + ('SCINDE("abc", "")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("abc"), String("")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), + # MAIVSCVLA: basic lowercase→uppercase + ('MAIVSCVLA("hello")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("hello")]))]), ValStr("HELLO")), + # MAIVSCVLA: mixed case + ('MAIVSCVLA("HeLLo")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("HeLLo")]))]), ValStr("HELLO")), + # MAIVSCVLA: already uppercase (idempotence) + ('MAIVSCVLA("HELLO")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("HELLO")]))]), ValStr("HELLO")), + # MAIVSCVLA: empty string + ('MAIVSCVLA("")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("")]))]), ValStr("")), + # MAIVSCVLA: Roman-numeral-shaped ASCII (case-only, not numeral-aware) + ('MAIVSCVLA("xii")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("xii")]))]), ValStr("XII")), + # MAIVSCVLA: non-alphabetic chars unchanged + ('MAIVSCVLA("a,b!1")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("a,b!1")]))]), ValStr("A,B!1")), + # MAIVSCVLA: via variable + ('DESIGNA s VT "foo"\nDIC(MAIVSCVLA(s))', Program([], [Designa(ID("s"), String("foo")), ExpressionStatement(BuiltIn("DIC", [BuiltIn("MAIVSCVLA", [ID("s")])]))]), ValStr("FOO"), "FOO\n"), + # MAIVSCVLA: concatenated with & + ('MAIVSCVLA("hi") & "!"', Program([], [ExpressionStatement(BinOp(BuiltIn("MAIVSCVLA", [String("hi")]), String("!"), "SYMBOL_AMPERSAND"))]), ValStr("HI!")), + # MINVSCVLA: basic uppercase→lowercase + ('MINVSCVLA("HELLO")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("HELLO")]))]), ValStr("hello")), + # MINVSCVLA: mixed case + ('MINVSCVLA("HeLLo")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("HeLLo")]))]), ValStr("hello")), + # MINVSCVLA: already lowercase (idempotence) + ('MINVSCVLA("hello")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("hello")]))]), ValStr("hello")), + # MINVSCVLA: empty string + ('MINVSCVLA("")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("")]))]), ValStr("")), + # MINVSCVLA: Roman-numeral-shaped ASCII (case-only, not numeral-aware) + ('MINVSCVLA("XII")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("XII")]))]), ValStr("xii")), + # MINVSCVLA: non-alphabetic chars unchanged + ('MINVSCVLA("A,B!1")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("A,B!1")]))]), ValStr("a,b!1")), + # MINVSCVLA round-trips MAIVSCVLA on lowercase input + ('MINVSCVLA(MAIVSCVLA("hi"))', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [BuiltIn("MAIVSCVLA", [String("hi")])]))]), ValStr("hi")), +] + +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) diff --git a/tests/04_test_numerals.py b/tests/04_test_numerals.py new file mode 100644 index 0000000..0926605 --- /dev/null +++ b/tests/04_test_numerals.py @@ -0,0 +1,175 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- 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) + + def test_negative_without_svbnvlla_raises(self): + with self.assertRaises(CentvrionError): + num_to_int("-IV", False) + + def test_negative_with_svbnvlla(self): + self.assertEqual(num_to_int("-IV", False, True), -4) + self.assertEqual(num_to_int("-XLII", False, True), -42) + + # int_to_num: valid cases + def test_int_to_num(self): + for n, s in [(0,"NVLLVS"),(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) + +# --- Arithmetic: edge cases --- + +arithmetic_edge_tests = [ + ("I - I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"))]), ValInt(0)), # result zero + ("I - V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-4)), # negative result + ("I / V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)), # integer division → 0 + ("M * M", Program([], [ExpressionStatement(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_TIMES"))]), ValInt(1000000)), # large intermediate (not displayed) + ("(I + II) * (IV - I)", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"), BinOp(Numeral("IV"), Numeral("I"), "SYMBOL_MINUS"), "SYMBOL_TIMES"))]), ValInt(9)), # nested parens + # NVLLVS coerces to 0 in integer arithmetic + ("NVLLVS + V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_PLUS"))]), ValInt(5)), + ("V + NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_PLUS"))]), ValInt(5)), + ("NVLLVS + NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_PLUS"))]), ValNul()), + ("NVLLVS - V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-5)), + ("V - NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_MINUS"))]), ValInt(5)), + # NVLLVS coerces to 0 in modulo and division + ("NVLLVS RELIQVVM V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_RELIQVVM"))]), ValInt(0)), + ("NVLLVS / V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)), + # floored division and modulo with negative operands (Python semantics) + ("CVM SVBNVLLA\n- VII RELIQVVM III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(2)), + ("CVM SVBNVLLA\nVII RELIQVVM - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("VII"), UnaryMinus(Numeral("III")), "KEYWORD_RELIQVVM"))]), ValInt(-2)), + ("CVM SVBNVLLA\n- VII RELIQVVM - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), UnaryMinus(Numeral("III")), "KEYWORD_RELIQVVM"))]), ValInt(-1)), + ("CVM SVBNVLLA\n- VII / III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(-3)), + ("CVM SVBNVLLA\nVII / - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("VII"), UnaryMinus(Numeral("III")), "SYMBOL_DIVIDE"))]), ValInt(-3)), + ("CVM SVBNVLLA\n- VII / - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), UnaryMinus(Numeral("III")), "SYMBOL_DIVIDE"))]), ValInt(2)), + ("CVM SVBNVLLA\n- L RELIQVVM C", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("L")), Numeral("C"), "KEYWORD_RELIQVVM"))]), ValInt(50)), +] + +class TestArithmeticEdge(unittest.TestCase): + @parameterized.expand(arithmetic_edge_tests) + def test_arithmetic_edge(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- 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\nDIC(M + M + M + M)", + Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS")]))]), + ValStr("MV_"), "MV_\n"), + # I_ = 1000 with MAGNVM (same value as M, but written with thousands operator) + ("CVM MAGNVM\nI_", + Program([ModuleCall("MAGNVM")], [ExpressionStatement(Numeral("I_"))]), + ValInt(1000), ""), + # I_ + I_ = 2000; displayed as MM with MAGNVM + ("CVM MAGNVM\nDIC(I_ + I_)", + Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I_"), Numeral("I_"), "SYMBOL_PLUS")]))]), + 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) + +# --- SVBNVLLA module (display of negatives) --- + +svbnvlla_display_tests = [ + # DIC prints a negative numeral + ("CVM SVBNVLLA\nDIC(- III)", + Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Numeral("III"))]))]), + ValStr("-III"), "-III\n"), + # Concat operator & with a negative numeral + ('CVM SVBNVLLA\nDIC("x: " & - V)', + Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(String("x: "), UnaryMinus(Numeral("V")), "SYMBOL_AMPERSAND")]))]), + ValStr("x: -V"), "x: -V\n"), + # String interpolation of a negative numeral + ('CVM SVBNVLLA\nDIC("val: {- X}")', + Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [InterpolatedString([String("val: "), UnaryMinus(Numeral("X"))])]))]), + ValStr("val: -X"), "val: -X\n"), + # DIC of LITTERA(negative numeral) + ("CVM SVBNVLLA\nDIC(LITTERA(- III))", + Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("LITTERA", [UnaryMinus(Numeral("III"))])]))]), + ValStr("-III"), "-III\n"), + # Combined with MAGNVM: negative of a number > 3999 + ("CVM MAGNVM\nCVM SVBNVLLA\nDIC(- (M + M + M + M))", + Program([ModuleCall("MAGNVM"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(BinOp(BinOp(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"))]))]), + ValStr("-MV_"), "-MV_\n"), + # Negative fraction via int / -int + ("CVM FRACTIO\nCVM SVBNVLLA\nDIC(V / - II)", + Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("V"), UnaryMinus(Numeral("II")), "SYMBOL_DIVIDE")]))]), + ValStr("-IIS"), "-IIS\n"), + # Negative pure fraction (no integer part) + ("CVM FRACTIO\nCVM SVBNVLLA\nDIC(I / - II)", + Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I"), UnaryMinus(Numeral("II")), "SYMBOL_DIVIDE")]))]), + ValStr("-S"), "-S\n"), + ("CVM FRACTIO\nCVM SVBNVLLA\nDIC(- S)", + Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Fractio("S"))]))]), + ValStr("-S"), "-S\n"), + ("CVM FRACTIO\nCVM SVBNVLLA\nDIC(- IIS)", + Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Fractio("IIS"))]))]), + ValStr("-IIS"), "-IIS\n"), +] + +class TestSVBNVLLADisplay(unittest.TestCase): + @parameterized.expand(svbnvlla_display_tests) + def test_svbnvlla_display(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) diff --git a/tests/05_test_literals.py b/tests/05_test_literals.py new file mode 100644 index 0000000..58a36eb --- /dev/null +++ b/tests/05_test_literals.py @@ -0,0 +1,402 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- 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")), "ExpressionStatement(\n String(hi)\n)"), + ("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 ExpressionStatement(\n Erumpe()\n )\n ]),\n None\n)"), + ("si_with_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], [ExpressionStatement(Erumpe())]), "Si(\n Bool(True),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ]),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"), + ("si_empty_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], []), "Si(\n Bool(True),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ]),\n statements([])\n)"), + ("dum", DumStatement(Bool(False), [ExpressionStatement(Erumpe())]), "Dum(\n Bool(False),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"), + ("per", PerStatement(DataArray([Numeral("I")]), ID("i"), [ExpressionStatement(Erumpe())]), "Per(\n Array([\n Numeral(I)\n ]),\n ID(i),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"), + ("invoca", Invoca(ID("f"), [Numeral("I")]), "Invoca(\n ID(f),\n parameters([\n Numeral(I)\n ])\n)"), + ("builtin", BuiltIn("DIC", [String("hi")]), "Builtin(\n DIC,\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 ExpressionStatement(\n Redi([\n Numeral(I)\n ])\n )\n ])\n)"), + ("program_no_modules", Program([], [ExpressionStatement(Numeral("I"))]), "modules([]),\nstatements([\n ExpressionStatement(\n Numeral(I)\n )\n])"), + ("program_with_module", Program([ModuleCall("FORS")], [ExpressionStatement(Numeral("I"))]), "modules([\n FORS\n]),\nstatements([\n ExpressionStatement(\n Numeral(I)\n )\n])"), +] + +class TestRepr(unittest.TestCase): + @parameterized.expand(repr_tests) + def test_repr(self, _, node, expected): + self.assertEqual(repr(node), expected) + +# --- 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_int_zero(self): + self.assertEqual(make_string(ValInt(0)), "NVLLVS") + + def test_bool_true(self): + self.assertEqual(make_string(ValBool(True)), "VERITAS") + + def test_bool_false(self): + self.assertEqual(make_string(ValBool(False)), "FALSITAS") + + 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 VERITAS]" + ) + +# --- DIC with non-integer types --- + +dic_type_tests = [ + ("DIC(VERITAS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Bool(True)]))]), ValStr("VERITAS"), "VERITAS\n"), + ("DIC(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Bool(False)]))]), ValStr("FALSITAS"), "FALSITAS\n"), + ("DIC(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Nullus()]))]), ValStr("NVLLVS"), "NVLLVS\n"), + ('DIC([I, II])', Program([], [ExpressionStatement(BuiltIn("DIC", [DataArray([Numeral("I"), Numeral("II")])]))]), ValStr("[I II]"), "[I II]\n"), + ('DIC("")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("")]))]), ValStr(""), "\n"), + # arithmetic result printed as numeral + ("DIC(II + III)", Program([], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")]))]), ValStr("V"), "V\n"), + # integer 0 prints as NVLLVS + ("DIC(I - I)", Program([], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("NVLLVS"), "NVLLVS\n"), + # multiple args of mixed types + ('DIC("x", VERITAS)', Program([], [ExpressionStatement(BuiltIn("DIC", [String("x"), Bool(True)]))]), ValStr("x VERITAS"), "x VERITAS\n"), +] + +class TestDicTypes(unittest.TestCase): + @parameterized.expand(dic_type_tests) + def test_dic_types(self, source, nodes, value, output): + run_test(self, source, nodes, value, output) + +# --- String concatenation --- + +string_concat_tests = [ + ('"hello" & " world"', Program([], [ExpressionStatement(BinOp(String("hello"), String(" world"), "SYMBOL_AMPERSAND"))]), ValStr("hello world")), + # NVLLVS coerces to "" in string context + ('NVLLVS & "hello"', Program([], [ExpressionStatement(BinOp(Nullus(), String("hello"), "SYMBOL_AMPERSAND"))]), ValStr("hello")), + ('"hello" & NVLLVS', Program([], [ExpressionStatement(BinOp(String("hello"), Nullus(), "SYMBOL_AMPERSAND"))]), ValStr("hello")), + ('NVLLVS & NVLLVS', Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_AMPERSAND"))]), ValStr("")), + # integers coerce to Roman numerals in string context + ('"value: " & V', Program([], [ExpressionStatement(BinOp(String("value: "), Numeral("V"), "SYMBOL_AMPERSAND"))]), ValStr("value: V")), + ('X & " items"', Program([], [ExpressionStatement(BinOp(Numeral("X"), String(" items"), "SYMBOL_AMPERSAND"))]), ValStr("X items")), +] + +class TestStringConcat(unittest.TestCase): + @parameterized.expand(string_concat_tests) + def test_string_concat(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- String interpolation --- + +interpolation_tests = [ + # basic variable interpolation + ('DESIGNA nomen VT "Marcus"\n"Salve, {nomen}!"', + Program([], [ + Designa(ID("nomen"), String("Marcus")), + ExpressionStatement(InterpolatedString([String("Salve, "), ID("nomen"), String("!")])) + ]), ValStr("Salve, Marcus!")), + # arithmetic expression inside interpolation + ('DESIGNA x VT III\n"Sum: {x + II}"', + Program([], [ + Designa(ID("x"), Numeral("III")), + ExpressionStatement(InterpolatedString([String("Sum: "), BinOp(ID("x"), Numeral("II"), "SYMBOL_PLUS")])) + ]), ValStr("Sum: V")), + # multiple interpolations + ('DESIGNA a VT I\nDESIGNA b VT II\n"{a} + {b} = {a + b}"', + Program([], [ + Designa(ID("a"), Numeral("I")), + Designa(ID("b"), Numeral("II")), + ExpressionStatement(InterpolatedString([ + ID("a"), String(" + "), ID("b"), String(" = "), + BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), + ])) + ]), ValStr("I + II = III")), + # escaped braces become literal + ('"use {{braces}}"', + Program([], [ExpressionStatement(String("use {braces}"))]), + ValStr("use {braces}")), + # single-quoted strings ignore braces + ("'hello {world}'", + Program([], [ExpressionStatement(String("hello {world}"))]), + ValStr("hello {world}")), + # integer coercion + ('DESIGNA n VT V\n"n is {n}"', + Program([], [ + Designa(ID("n"), Numeral("V")), + ExpressionStatement(InterpolatedString([String("n is "), ID("n")])) + ]), ValStr("n is V")), + # boolean coercion + ('DESIGNA b VT VERITAS\n"value: {b}"', + Program([], [ + Designa(ID("b"), Bool(True)), + ExpressionStatement(InterpolatedString([String("value: "), ID("b")])) + ]), ValStr("value: VERITAS")), + # NVLLVS coercion + ('"value: {NVLLVS}"', + Program([], [ + ExpressionStatement(InterpolatedString([String("value: "), Nullus()])) + ]), ValStr("value: NVLLVS")), + # integer 0 interpolates as NVLLVS + ('"value: {I - I}"', Program([], [ExpressionStatement(InterpolatedString([String("value: "), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("value: NVLLVS")), + # expression-only string (no literal parts around it) + ('DESIGNA x VT "hi"\n"{x}"', + Program([], [ + Designa(ID("x"), String("hi")), + ExpressionStatement(InterpolatedString([ID("x")])) + ]), ValStr("hi")), + # adjacent interpolations + ('DESIGNA a VT "x"\nDESIGNA b VT "y"\n"{a}{b}"', + Program([], [ + Designa(ID("a"), String("x")), + Designa(ID("b"), String("y")), + ExpressionStatement(InterpolatedString([ID("a"), ID("b")])) + ]), ValStr("xy")), + # function call inside interpolation + ("DEFINI f () VT {\nREDI (V)\n}\n\"result: {INVOCA f()}\"", + Program([], [ + Defini(ID("f"), [], [Redi([Numeral("V")])]), + ExpressionStatement(InterpolatedString([String("result: "), Invoca(ID("f"), [])])) + ]), ValStr("result: V")), + # single-quoted string inside interpolation + ("DESIGNA x VT 'hello'\n\"{x & '!'}\"", + Program([], [ + Designa(ID("x"), String("hello")), + ExpressionStatement(InterpolatedString([BinOp(ID("x"), String("!"), "SYMBOL_AMPERSAND")])) + ]), ValStr("hello!")), + # plain double-quoted string (no braces) still works + ('"hello world"', + Program([], [ExpressionStatement(String("hello world"))]), + ValStr("hello world")), + # interpolation in DIC output + ('DESIGNA name VT "Roma"\nDIC("Salve, {name}!")', + Program([], [ + Designa(ID("name"), String("Roma")), + ExpressionStatement(BuiltIn("DIC", [InterpolatedString([String("Salve, "), ID("name"), String("!")])])) + ]), ValStr("Salve, Roma!"), "Salve, Roma!\n"), +] + +class TestInterpolation(unittest.TestCase): + @parameterized.expand(interpolation_tests) + def test_interpolation(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) + +# --- Escape sequences --- + +escape_tests = [ + # \n → newline + ('"hello\\nworld"', + Program([], [ExpressionStatement(String("hello\nworld"))]), + ValStr("hello\nworld")), + # \t → tab + ('"col\\tcol"', + Program([], [ExpressionStatement(String("col\tcol"))]), + ValStr("col\tcol")), + # \r → carriage return + ('"line\\rover"', + Program([], [ExpressionStatement(String("line\rover"))]), + ValStr("line\rover")), + # \\ → literal backslash + ('"back\\\\slash"', + Program([], [ExpressionStatement(String("back\\slash"))]), + ValStr("back\\slash")), + # \" → literal double quote + ('"say \\"salve\\""', + Program([], [ExpressionStatement(String('say "salve"'))]), + ValStr('say "salve"')), + # \' → literal single quote in single-quoted string + ("'it\\'s'", + Program([], [ExpressionStatement(String("it's"))]), + ValStr("it's")), + # \n in single-quoted string + ("'hello\\nworld'", + Program([], [ExpressionStatement(String("hello\nworld"))]), + ValStr("hello\nworld")), + # escape inside interpolated string + ('DESIGNA name VT "Roma"\n"salve\\n{name}"', + Program([], [ + Designa(ID("name"), String("Roma")), + ExpressionStatement(InterpolatedString([String("salve\n"), ID("name")])) + ]), ValStr("salve\nRoma")), + # DIC with newline escape + ('DIC("hello\\nworld")', + Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello\nworld")]))]), + ValStr("hello\nworld"), "hello\nworld\n"), + # multiple escapes in one string + ('"\\t\\n\\\\"', + Program([], [ExpressionStatement(String("\t\n\\"))]), + ValStr("\t\n\\")), + # unknown escapes pass through (regex backrefs) + ('"\\1\\2"', + Program([], [ExpressionStatement(String("\\1\\2"))]), + ValStr("\\1\\2")), +] + +class TestEscapeSequences(unittest.TestCase): + @parameterized.expand(escape_tests) + def test_escape(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) + +# --- String index assignment --- + +string_index_assign_tests = [ + # assign to middle character + ('DESIGNA s VT "ABCDE"\nDESIGNA s[III] VT "X"\ns', + Program([], [ + Designa(ID("s"), String("ABCDE")), + DesignaIndex(ID("s"), [Numeral("III")], String("X")), + ExpressionStatement(ID("s")), + ]), + ValStr("ABXDE")), + # assign to first character + ('DESIGNA s VT "ABCDE"\nDESIGNA s[I] VT "Z"\ns', + Program([], [ + Designa(ID("s"), String("ABCDE")), + DesignaIndex(ID("s"), [Numeral("I")], String("Z")), + ExpressionStatement(ID("s")), + ]), + ValStr("ZBCDE")), + # assign to last character + ('DESIGNA s VT "ABCDE"\nDESIGNA s[V] VT "Z"\ns', + Program([], [ + Designa(ID("s"), String("ABCDE")), + DesignaIndex(ID("s"), [Numeral("V")], String("Z")), + ExpressionStatement(ID("s")), + ]), + ValStr("ABCDZ")), + # variable as index + ('DESIGNA s VT "ABCDE"\nDESIGNA i VT II\nDESIGNA s[i] VT "X"\ns', + Program([], [ + Designa(ID("s"), String("ABCDE")), + Designa(ID("i"), Numeral("II")), + DesignaIndex(ID("s"), [ID("i")], String("X")), + ExpressionStatement(ID("s")), + ]), + ValStr("AXCDE")), + # string inside array + ('DESIGNA a VT ["ABC", "DEF"]\nDESIGNA a[I][II] VT "X"\na[I]', + Program([], [ + Designa(ID("a"), DataArray([String("ABC"), String("DEF")])), + DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], String("X")), + ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))), + ]), + ValStr("AXC")), +] + +class TestStringIndexAssign(unittest.TestCase): + @parameterized.expand(string_index_assign_tests) + def test_string_index_assign(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- String indexing --- + +string_index_tests = [ + # first character + ('"SALVTE"[I]', + Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("I")))]), + ValStr("S")), + # last character + ('"SALVTE"[VI]', + Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("VI")))]), + ValStr("E")), + # middle character + ('"SALVTE"[III]', + Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("III")))]), + ValStr("L")), + # string index via variable + ('DESIGNA s VT "SALVTE"\ns[II]', + Program([], [ + Designa(ID("s"), String("SALVTE")), + ExpressionStatement(ArrayIndex(ID("s"), Numeral("II"))), + ]), + ValStr("A")), + # expression as index + ('"SALVTE"[I + II]', + Program([], [ExpressionStatement(ArrayIndex( + String("SALVTE"), + BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS")))]), + ValStr("L")), +] + +class TestStringIndex(unittest.TestCase): + @parameterized.expand(string_index_tests) + def test_string_index(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- String slicing --- + +string_slice_tests = [ + # substring from middle + ('"SALVTE"[II VSQVE IV]', + Program([], [ExpressionStatement(ArraySlice( + String("SALVTE"), Numeral("II"), Numeral("IV")))]), + ValStr("ALV")), + # full string slice + ('"SALVTE"[I VSQVE VI]', + Program([], [ExpressionStatement(ArraySlice( + String("SALVTE"), Numeral("I"), Numeral("VI")))]), + ValStr("SALVTE")), + # single-char slice + ('"SALVTE"[III VSQVE III]', + Program([], [ExpressionStatement(ArraySlice( + String("SALVTE"), Numeral("III"), Numeral("III")))]), + ValStr("L")), + # slice on variable + ('DESIGNA s VT "SALVTE"\ns[II VSQVE IV]', + Program([], [ + Designa(ID("s"), String("SALVTE")), + ExpressionStatement(ArraySlice(ID("s"), Numeral("II"), Numeral("IV"))), + ]), + ValStr("ALV")), + # chaining: slice then index + ('"SALVTE"[I VSQVE III][II]', + Program([], [ExpressionStatement(ArrayIndex( + ArraySlice(String("SALVTE"), Numeral("I"), Numeral("III")), + Numeral("II")))]), + ValStr("A")), + # expression as slice bounds + ('"SALVTE"[I + I VSQVE II + II]', + Program([], [ExpressionStatement(ArraySlice( + String("SALVTE"), + BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"), + BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS")))]), + ValStr("ALV")), +] + +class TestStringSlice(unittest.TestCase): + @parameterized.expand(string_slice_tests) + def test_string_slice(self, source, nodes, value): + run_test(self, source, nodes, value) diff --git a/tests/06_test_booleans.py b/tests/06_test_booleans.py new file mode 100644 index 0000000..9f9fab4 --- /dev/null +++ b/tests/06_test_booleans.py @@ -0,0 +1,223 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + +# --- Comparison operators --- + +comparison_tests = [ + # EST on strings + ('\"hello\" EST \"hello\"', Program([], [ExpressionStatement(BinOp(String("hello"), String("hello"), "KEYWORD_EST"))]), ValBool(True)), + ('\"hello\" EST \"world\"', Program([], [ExpressionStatement(BinOp(String("hello"), String("world"), "KEYWORD_EST"))]), ValBool(False)), + # chain comparisons as conditions + ("SI III PLVS II TVNC { DESIGNA r VT I }\nr", + Program([], [SiStatement(BinOp(Numeral("III"), Numeral("II"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], None), ExpressionStatement(ID("r"))]), + ValInt(1)), + ("SI II PLVS III TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(BinOp(Numeral("II"), Numeral("III"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(2)), + # result of comparison is ValBool + ("I EST I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"))]), ValBool(True)), + ("I EST II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"))]), ValBool(False)), + ("I MINVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_MINVS"))]), ValBool(True)), + ("II MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_MINVS"))]), ValBool(False)), + ("II PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"))]), ValBool(True)), + ("I PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_PLVS"))]), ValBool(False)), + # NVLLVS coerces to 0 in comparisons + ("V PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_PLVS"))]), ValBool(True)), + ("NVLLVS MINVS V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_MINVS"))]), ValBool(True)), + # DISPAR (not-equal): mirrors EST semantics, negated + ("I DISPAR II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_DISPAR"))]), ValBool(True)), + ("I DISPAR I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_DISPAR"))]), ValBool(False)), + ('"hello" DISPAR "hello"', Program([], [ExpressionStatement(BinOp(String("hello"), String("hello"), "KEYWORD_DISPAR"))]), ValBool(False)), + ('"hello" DISPAR "world"', Program([], [ExpressionStatement(BinOp(String("hello"), String("world"), "KEYWORD_DISPAR"))]), ValBool(True)), + ("VERITAS DISPAR FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_DISPAR"))]), ValBool(True)), + ("NVLLVS DISPAR NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "KEYWORD_DISPAR"))]), ValBool(False)), + # cross-type: an int and a string are never equal + ('I DISPAR "I"', Program([], [ExpressionStatement(BinOp(Numeral("I"), String("I"), "KEYWORD_DISPAR"))]), ValBool(True)), + # integer 0 equals NVLLVS + ("(I - I) EST NVLLVS", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), Nullus(), "KEYWORD_EST"))]), ValBool(True)), + ("NVLLVS EST (I - I)", Program([], [ExpressionStatement(BinOp(Nullus(), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), "KEYWORD_EST"))]), ValBool(True)), + ("(I - I) DISPAR NVLLVS", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), Nullus(), "KEYWORD_DISPAR"))]), ValBool(False)), + ("NVLLVS DISPAR (I - I)", Program([], [ExpressionStatement(BinOp(Nullus(), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), "KEYWORD_DISPAR"))]), ValBool(False)), + # non-zero integer does not equal NVLLVS + ("I EST NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("I"), Nullus(), "KEYWORD_EST"))]), ValBool(False)), + ("NVLLVS DISPAR I", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("I"), "KEYWORD_DISPAR"))]), ValBool(True)), + # EST / DISPAR on arrays + ("[I, II] EST [I, II]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II")]), "KEYWORD_EST"))]), ValBool(True)), + ("[I, II] EST [I, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("III")]), "KEYWORD_EST"))]), ValBool(False)), + ("[I, II] EST [I, II, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), "KEYWORD_EST"))]), ValBool(False)), + ("[] EST []", Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([]), "KEYWORD_EST"))]), ValBool(True)), + ("[I, II] DISPAR [I, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("III")]), "KEYWORD_DISPAR"))]), ValBool(True)), + ("[I, II] DISPAR [I, II]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II")]), "KEYWORD_DISPAR"))]), ValBool(False)), + # HAVD_PLVS (<=) and HAVD_MINVS (>=) + ("I HAVD_PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)), + ("II HAVD_PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_HAVD_PLVS"))]), ValBool(False)), + # equality boundary — the only case that distinguishes <= from < + ("II HAVD_PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)), + ("II HAVD_MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_HAVD_MINVS"))]), ValBool(True)), + ("I HAVD_MINVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_HAVD_MINVS"))]), ValBool(False)), + # equality boundary — the only case that distinguishes >= from > + ("II HAVD_MINVS II",Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_MINVS"))]), ValBool(True)), + # NVLLVS coerces to 0 + ("V HAVD_MINVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_HAVD_MINVS"))]), ValBool(True)), + ("NVLLVS HAVD_PLVS V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)), + ("NVLLVS HAVD_PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "KEYWORD_HAVD_PLVS"))]), ValBool(True)), + # precedence: * binds tighter, so II*III HAVD_PLVS VI parses as (II*III) HAVD_PLVS VI = 6 <= 6 = True + ("II * III HAVD_PLVS VI", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), Numeral("VI"), "KEYWORD_HAVD_PLVS"))]), + ValBool(True)), + # control flow: SI ... HAVD_MINVS + ("SI II HAVD_MINVS II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_MINVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(1)), +] + +class TestComparisons(unittest.TestCase): + @parameterized.expand(comparison_tests) + def test_comparisons(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- ET and AVT (boolean and/or) --- + +et_avt_tests = [ + ("VERITAS ET VERITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(True), "KEYWORD_ET"))]), ValBool(True)), + ("VERITAS ET FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_ET"))]), ValBool(False)), + ("FALSITAS ET VERITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(True), "KEYWORD_ET"))]), ValBool(False)), + ("FALSITAS ET FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(False), "KEYWORD_ET"))]), ValBool(False)), + ("VERITAS AVT VERITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(True), "KEYWORD_AVT"))]), ValBool(True)), + ("VERITAS AVT FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_AVT"))]), ValBool(True)), + ("FALSITAS AVT VERITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"))]), ValBool(True)), + ("FALSITAS AVT FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"))]), ValBool(False)), + # short-circuit behavior: combined with comparisons + ("(I EST I) ET (II EST II)", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]), + ValBool(True)), + ("(I EST II) AVT (II EST II)", + Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_AVT"))]), + ValBool(True)), + # used as SI condition + ("SI VERITAS ET VERITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(BinOp(Bool(True), Bool(True), "KEYWORD_ET"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(1)), + ("SI FALSITAS AVT FALSITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [SiStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), + ValInt(2)), + # short-circuit: right side not evaluated when result is determined + ("VERITAS AVT NVLLVS", + Program([], [ExpressionStatement(BinOp(Bool(True), Nullus(), "KEYWORD_AVT"))]), + ValBool(True)), + ("FALSITAS ET NVLLVS", + Program([], [ExpressionStatement(BinOp(Bool(False), Nullus(), "KEYWORD_ET"))]), + ValBool(False)), + # short-circuit with side-effect-prone expressions + ("DESIGNA x VT NVLLVS\nSI x EST NVLLVS AVT [I, II][x] EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [ + Designa(ID("x"), Nullus()), + SiStatement( + BinOp(BinOp(ID("x"), Nullus(), "KEYWORD_EST"), BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II")]), ID("x")), Numeral("I"), "KEYWORD_EST"), "KEYWORD_AVT"), + [Designa(ID("r"), Numeral("I"))], + [Designa(ID("r"), Numeral("II"))]), + ExpressionStatement(ID("r"))]), + ValInt(1)), + ("DESIGNA x VT NVLLVS\nSI x DISPAR NVLLVS ET [I, II][x] EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [ + Designa(ID("x"), Nullus()), + SiStatement( + BinOp(BinOp(ID("x"), Nullus(), "KEYWORD_DISPAR"), BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II")]), ID("x")), Numeral("I"), "KEYWORD_EST"), "KEYWORD_ET"), + [Designa(ID("r"), Numeral("I"))], + [Designa(ID("r"), Numeral("II"))]), + ExpressionStatement(ID("r"))]), + 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) + +# --- NON (boolean not) --- + +non_tests = [ + ("NON VERITAS", + Program([], [ExpressionStatement(UnaryNot(Bool(True)))]), + ValBool(False)), + ("NON FALSITAS", + Program([], [ExpressionStatement(UnaryNot(Bool(False)))]), + ValBool(True)), + ("NON NON VERITAS", + Program([], [ExpressionStatement(UnaryNot(UnaryNot(Bool(True))))]), + ValBool(True)), + ("DESIGNA b VT I EST II\nNON b", + Program([], [Designa(ID("b"), BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("b")))]), + ValBool(True)), + ("DESIGNA z VT I EST I\nNON z", + Program([], [Designa(ID("z"), BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("z")))]), + ValBool(False)), + ("NON VERITAS AVT FALSITAS", + Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_AVT"))]), + ValBool(False)), + ("NON VERITAS EST FALSITAS", + Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_EST"))]), + ValBool(True)), +] + +class TestNon(unittest.TestCase): + @parameterized.expand(non_tests) + def test_non(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- 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()) diff --git a/tests/07_test_arrays__.py b/tests/07_test_arrays__.py new file mode 100644 index 0000000..724c83c --- /dev/null +++ b/tests/07_test_arrays__.py @@ -0,0 +1,273 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- Array concatenation --- + +array_concat_tests = [ + ("[I, II] @ [III, IV]", + Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), "SYMBOL_AT"))]), + ValList([ValInt(1), ValInt(2), ValInt(3), ValInt(4)])), + ("[] @ [I]", + Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([Numeral("I")]), "SYMBOL_AT"))]), + ValList([ValInt(1)])), + ("[I] @ []", + Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I")]), DataArray([]), "SYMBOL_AT"))]), + ValList([ValInt(1)])), + ("[] @ []", + Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([]), "SYMBOL_AT"))]), + ValList([])), + ('["a"] @ [I]', + Program([], [ExpressionStatement(BinOp(DataArray([String("a")]), DataArray([Numeral("I")]), "SYMBOL_AT"))]), + ValList([ValStr("a"), ValInt(1)])), + # left-associative chaining + ("[I] @ [II] @ [III]", + Program([], [ExpressionStatement(BinOp(BinOp(DataArray([Numeral("I")]), DataArray([Numeral("II")]), "SYMBOL_AT"), DataArray([Numeral("III")]), "SYMBOL_AT"))]), + ValList([ValInt(1), ValInt(2), ValInt(3)])), + # concat with variable + ("DESIGNA a VT [I, II]\nDESIGNA b VT [III]\na @ b", + Program([], [Designa(ID("a"), DataArray([Numeral("I"), Numeral("II")])), Designa(ID("b"), DataArray([Numeral("III")])), ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_AT"))]), + ValList([ValInt(1), ValInt(2), ValInt(3)])), +] + +class TestArrayConcat(unittest.TestCase): + @parameterized.expand(array_concat_tests) + def test_array_concat(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- Array indexing --- +# Indexing is 1-based; I is the first element + +array_index_tests = [ + # basic indexing + ("[I, II, III][I]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I")))]), ValInt(1)), # first element + ("[I, II, III][II]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")))]), ValInt(2)), # second element + ("[I, II, III][III]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("III")))]), ValInt(3)), # third element + # index into a variable + ("DESIGNA a VT [X, XX, XXX]\na[II]", + Program([], [Designa(ID("a"), DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])), ExpressionStatement(ArrayIndex(ID("a"), Numeral("II")))]), + ValInt(20)), # second element + # index into range array + ("[I VSQVE V][II]", Program([], [ExpressionStatement(ArrayIndex(DataRangeArray(Numeral("I"), Numeral("V")), Numeral("II")))]), ValInt(2)), # second element of [1,2,3,4,5] + # expression as index + ("[I, II, III][I + I]", + Program([], [ExpressionStatement(ArrayIndex( + DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), + BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS")))]), + ValInt(2)), + # division result as index (no FRACTIO): IV / II = 2 + ("[X, XX, XXX][IV / II]", + Program([], [ExpressionStatement(ArrayIndex( + DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")]), + BinOp(Numeral("IV"), Numeral("II"), "SYMBOL_DIVIDE")))]), + ValInt(20)), + # whole-number fraction (from division) as index, with FRACTIO imported + ("CVM FRACTIO\n[X, XX, XXX][IV / II]", + Program([ModuleCall("FRACTIO")], [ExpressionStatement(ArrayIndex( + DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")]), + BinOp(Numeral("IV"), Numeral("II"), "SYMBOL_DIVIDE")))]), + ValInt(20)), +] + +class TestArrayIndex(unittest.TestCase): + @parameterized.expand(array_index_tests) + def test_array_index(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- Array index assignment --- + +array_index_assign_tests = [ + # assign to middle element + ("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[II]", + Program([], [ + Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), + DesignaIndex(ID("a"), [Numeral("II")], Numeral("X")), + ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))), + ]), + ValInt(10)), + # assign to first element + ("DESIGNA a VT [I, II, III]\nDESIGNA a[I] VT V\na[I]", + Program([], [ + Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), + DesignaIndex(ID("a"), [Numeral("I")], Numeral("V")), + ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))), + ]), + ValInt(5)), + # assign to last element + ("DESIGNA a VT [I, II, III]\nDESIGNA a[III] VT L\na[III]", + Program([], [ + Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), + DesignaIndex(ID("a"), [Numeral("III")], Numeral("L")), + ExpressionStatement(ArrayIndex(ID("a"), Numeral("III"))), + ]), + ValInt(50)), + # other elements unaffected + ("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[I]", + Program([], [ + Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), + DesignaIndex(ID("a"), [Numeral("II")], Numeral("X")), + ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))), + ]), + ValInt(1)), + # expression as index + ("DESIGNA a VT [I, II, III]\nDESIGNA i VT II\nDESIGNA a[i] VT X\na[II]", + Program([], [ + Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), + Designa(ID("i"), Numeral("II")), + DesignaIndex(ID("a"), [ID("i")], Numeral("X")), + ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))), + ]), + ValInt(10)), +] + +class TestArrayIndexAssign(unittest.TestCase): + @parameterized.expand(array_index_assign_tests) + def test_array_index_assign(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- Multi-dimensional array index assignment --- + +multidim_assign_tests = [ + # 2D array assignment + ("DESIGNA a VT [[I, II], [III, IV]]\nDESIGNA a[I][II] VT X\na[I][II]", + Program([], [ + Designa(ID("a"), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])), + DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], Numeral("X")), + ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), Numeral("II"))), + ]), + ValInt(10)), + # other elements unaffected + ("DESIGNA a VT [[I, II], [III, IV]]\nDESIGNA a[I][II] VT X\na[II][I]", + Program([], [ + Designa(ID("a"), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])), + DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], Numeral("X")), + ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("II")), Numeral("I"))), + ]), + ValInt(3)), + # dict inside array + ('DESIGNA a VT [TABVLA {"x" VT I}]\nDESIGNA a[I]["x"] VT X\na[I]["x"]', + Program([], [ + Designa(ID("a"), DataArray([DataDict([(String("x"), Numeral("I"))])])), + DesignaIndex(ID("a"), [Numeral("I"), String("x")], Numeral("X")), + ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), String("x"))), + ]), + ValInt(10)), + # array inside dict + ('DESIGNA d VT TABVLA {"a" VT [I, II]}\nDESIGNA d["a"][I] VT X\nd["a"][I]', + Program([], [ + Designa(ID("d"), DataDict([(String("a"), DataArray([Numeral("I"), Numeral("II")]))])), + DesignaIndex(ID("d"), [String("a"), Numeral("I")], Numeral("X")), + ExpressionStatement(ArrayIndex(ArrayIndex(ID("d"), String("a")), Numeral("I"))), + ]), + ValInt(10)), + # 3 levels deep + ("DESIGNA a VT [[[I]]]\nDESIGNA a[I][I][I] VT X\na[I][I][I]", + Program([], [ + Designa(ID("a"), DataArray([DataArray([DataArray([Numeral("I")])])])), + DesignaIndex(ID("a"), [Numeral("I"), Numeral("I"), Numeral("I")], Numeral("X")), + ExpressionStatement(ArrayIndex(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), Numeral("I")), Numeral("I"))), + ]), + ValInt(10)), +] + +class TestMultidimAssign(unittest.TestCase): + @parameterized.expand(multidim_assign_tests) + def test_multidim_assign(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- Array slicing --- + +array_slice_tests = [ + # basic slice from middle + ("[X, XX, XXX, XL, L][II VSQVE IV]", + Program([], [ExpressionStatement(ArraySlice( + DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX"), Numeral("XL"), Numeral("L")]), + Numeral("II"), Numeral("IV")))]), + ValList([ValInt(20), ValInt(30), ValInt(40)])), + # slice of length 1 + ("[I, II, III][II VSQVE II]", + Program([], [ExpressionStatement(ArraySlice( + DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), + Numeral("II"), Numeral("II")))]), + ValList([ValInt(2)])), + # full array slice + ("[I, II, III][I VSQVE III]", + Program([], [ExpressionStatement(ArraySlice( + DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), + Numeral("I"), Numeral("III")))]), + ValList([ValInt(1), ValInt(2), ValInt(3)])), + # slice on variable + ("DESIGNA a VT [I, II, III, IV, V]\na[II VSQVE IV]", + Program([], [ + Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V")])), + ExpressionStatement(ArraySlice(ID("a"), Numeral("II"), Numeral("IV"))), + ]), + ValList([ValInt(2), ValInt(3), ValInt(4)])), + # slice then index (chained) + ("[I, II, III, IV][I VSQVE III][II]", + Program([], [ExpressionStatement(ArrayIndex( + ArraySlice( + DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]), + Numeral("I"), Numeral("III")), + Numeral("II")))]), + ValInt(2)), + # slice on range array + ("[I VSQVE X][III VSQVE VII]", + Program([], [ExpressionStatement(ArraySlice( + DataRangeArray(Numeral("I"), Numeral("X")), + Numeral("III"), Numeral("VII")))]), + ValList([ValInt(3), ValInt(4), ValInt(5), ValInt(6), ValInt(7)])), + # expression as slice bounds + ("[I, II, III, IV, V][I + I VSQVE II + II]", + Program([], [ExpressionStatement(ArraySlice( + DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V")]), + BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"), + BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS")))]), + ValList([ValInt(2), ValInt(3), ValInt(4)])), +] + +class TestArraySlice(unittest.TestCase): + @parameterized.expand(array_slice_tests) + def test_array_slice(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- Multiline arrays --- + +multiline_array_tests = [ + # newlines after commas + ("[I,\nII,\nIII]", + Program([], [ExpressionStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]))]), + ValList([ValInt(1), ValInt(2), ValInt(3)])), + # single newline after comma + ("[I, II,\nIII]", + Program([], [ExpressionStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]))]), + ValList([ValInt(1), ValInt(2), ValInt(3)])), + # empty array still works + ("[]", + Program([], [ExpressionStatement(DataArray([]))]), + ValList([])), + # nested arrays with newlines + ("[I,\n[II, III],\nIV]", + Program([], [ExpressionStatement(DataArray([ + Numeral("I"), + DataArray([Numeral("II"), Numeral("III")]), + Numeral("IV")]))]), + ValList([ValInt(1), ValList([ValInt(2), ValInt(3)]), ValInt(4)])), +] + +class TestMultilineArray(unittest.TestCase): + @parameterized.expand(multiline_array_tests) + def test_multiline_array(self, source, nodes, value): + run_test(self, source, nodes, value) diff --git a/tests/08_test_tabulas_.py b/tests/08_test_tabulas_.py new file mode 100644 index 0000000..146ad49 --- /dev/null +++ b/tests/08_test_tabulas_.py @@ -0,0 +1,191 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- Dict (TABVLA) --- + +dict_tests = [ + # empty dict + ("TABVLA {}", + Program([], [ExpressionStatement(DataDict([]))]), + ValDict({})), + # single string key + ('TABVLA {"a" VT I}', + Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I"))]))]), + ValDict({"a": ValInt(1)})), + # multiple entries + ('TABVLA {"a" VT I, "b" VT II}', + Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]))]), + ValDict({"a": ValInt(1), "b": ValInt(2)})), + # integer keys + ('TABVLA {I VT "one", II VT "two"}', + Program([], [ExpressionStatement(DataDict([(Numeral("I"), String("one")), (Numeral("II"), String("two"))]))]), + ValDict({1: ValStr("one"), 2: ValStr("two")})), + # expression values + ('TABVLA {"x" VT I + II}', + Program([], [ExpressionStatement(DataDict([(String("x"), BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"))]))]), + ValDict({"x": ValInt(3)})), + # empty dict with newline + ('TABVLA {\n}', + Program([], [ExpressionStatement(DataDict([]))]), + ValDict({})), + # single entry with surrounding newlines + ('TABVLA {\n"a" VT I\n}', + Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I"))]))]), + ValDict({"a": ValInt(1)})), + # multiple entries with newlines after commas + ('TABVLA {\n"a" VT I,\n"b" VT II\n}', + Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]))]), + ValDict({"a": ValInt(1), "b": ValInt(2)})), + # newlines around every delimiter + ('TABVLA {\n"a" VT I,\n"b" VT II,\n"c" VT III\n}', + Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II")), (String("c"), Numeral("III"))]))]), + ValDict({"a": ValInt(1), "b": ValInt(2), "c": ValInt(3)})), +] + +class TestDict(unittest.TestCase): + @parameterized.expand(dict_tests) + def test_dict(self, source, nodes, value): + run_test(self, source, nodes, value) + + +dict_index_tests = [ + # string key access + ('TABVLA {"a" VT X}["a"]', + Program([], [ExpressionStatement(ArrayIndex(DataDict([(String("a"), Numeral("X"))]), String("a")))]), + ValInt(10)), + # integer key access + ('TABVLA {I VT "one"}[I]', + Program([], [ExpressionStatement(ArrayIndex(DataDict([(Numeral("I"), String("one"))]), Numeral("I")))]), + ValStr("one")), + # access via variable + ('DESIGNA d VT TABVLA {"x" VT V}\nd["x"]', + Program([], [ + Designa(ID("d"), DataDict([(String("x"), Numeral("V"))])), + ExpressionStatement(ArrayIndex(ID("d"), String("x"))), + ]), + ValInt(5)), + # nested dict access + ('TABVLA {"a" VT TABVLA {"b" VT X}}["a"]["b"]', + Program([], [ExpressionStatement( + ArrayIndex(ArrayIndex(DataDict([(String("a"), DataDict([(String("b"), Numeral("X"))]))]), String("a")), String("b")) + )]), + ValInt(10)), +] + +class TestDictIndex(unittest.TestCase): + @parameterized.expand(dict_index_tests) + def test_dict_index(self, source, nodes, value): + run_test(self, source, nodes, value) + + +dict_assign_tests = [ + # update existing key + ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["a"] VT X\nd["a"]', + Program([], [ + Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), + DesignaIndex(ID("d"), [String("a")], Numeral("X")), + ExpressionStatement(ArrayIndex(ID("d"), String("a"))), + ]), + ValInt(10)), + # insert new key + ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["b"]', + Program([], [ + Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), + DesignaIndex(ID("d"), [String("b")], Numeral("II")), + ExpressionStatement(ArrayIndex(ID("d"), String("b"))), + ]), + ValInt(2)), + # original key unaffected after insert + ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["a"]', + Program([], [ + Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), + DesignaIndex(ID("d"), [String("b")], Numeral("II")), + ExpressionStatement(ArrayIndex(ID("d"), String("a"))), + ]), + ValInt(1)), +] + +class TestDictAssign(unittest.TestCase): + @parameterized.expand(dict_assign_tests) + def test_dict_assign(self, source, nodes, value): + run_test(self, source, nodes, value) + + +dict_builtin_tests = [ + # LONGITVDO on dict + ('LONGITVDO(TABVLA {"a" VT I, "b" VT II})', + Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]), + ValInt(2)), + # LONGITVDO on empty dict + ('LONGITVDO(TABVLA {})', + Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([])]))]), + ValInt(0)), + # CLAVES + ('CLAVES(TABVLA {"a" VT I, "b" VT II})', + Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]), + ValList([ValStr("a"), ValStr("b")])), + # CLAVES with int keys + ('CLAVES(TABVLA {I VT "x", II VT "y"})', + Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(Numeral("I"), String("x")), (Numeral("II"), String("y"))])]))]), + ValList([ValInt(1), ValInt(2)])), +] + +class TestDictBuiltins(unittest.TestCase): + @parameterized.expand(dict_builtin_tests) + def test_dict_builtin(self, source, nodes, value): + run_test(self, source, nodes, value) + + +dict_iteration_tests = [ + # PER iterates over keys + ('DESIGNA r VT ""\nPER k IN TABVLA {"a" VT I, "b" VT II} FAC {\nDESIGNA r VT r & k\n}\nr', + Program([], [ + Designa(ID("r"), String("")), + PerStatement( + DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]), + ID("k"), + [Designa(ID("r"), BinOp(ID("r"), ID("k"), "SYMBOL_AMPERSAND"))], + ), + ExpressionStatement(ID("r")), + ]), + ValStr("ab")), +] + +class TestDictIteration(unittest.TestCase): + @parameterized.expand(dict_iteration_tests) + def test_dict_iteration(self, source, nodes, value): + run_test(self, source, nodes, value) + + +dict_display_tests = [ + # DIC on dict + ('DIC(TABVLA {"a" VT I})', + Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([(String("a"), Numeral("I"))])]))]), + ValStr("{a VT I}"), "{a VT I}\n"), + # DIC on multi-entry dict + ('DIC(TABVLA {"a" VT I, "b" VT II})', + Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]), + ValStr("{a VT I, b VT II}"), "{a VT I, b VT II}\n"), + # DIC on empty dict + ('DIC(TABVLA {})', + Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([])]))]), + ValStr("{}"), "{}\n"), +] + +class TestDictDisplay(unittest.TestCase): + @parameterized.expand(dict_display_tests) + def test_dict_display(self, source, nodes, value, output): + run_test(self, source, nodes, value, output) diff --git a/tests/09_test_fraction.py b/tests/09_test_fraction.py new file mode 100644 index 0000000..8d85f57 --- /dev/null +++ b/tests/09_test_fraction.py @@ -0,0 +1,263 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- FRACTIO module --- + +fractio_tests = [ + # Basic fraction literals + ("CVM FRACTIO\nIIIS", + Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("IIIS"))]), + ValFrac(Fraction(7, 2))), + ("CVM FRACTIO\nS", + Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("S"))]), + ValFrac(Fraction(1, 2))), + ("CVM FRACTIO\nS:.", + Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("S:."))]), + ValFrac(Fraction(3, 4))), + ("CVM FRACTIO\n.", + Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("."))]), + ValFrac(Fraction(1, 12))), + ("CVM FRACTIO\n:.", + Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio(":."))]), + ValFrac(Fraction(1, 4))), + # Integer part with fraction + ("CVM FRACTIO\nVIIS:|::", + Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("VIIS:|::"))]), + ValFrac(Fraction(7) + Fraction(100, 144))), + # Arithmetic + ("CVM FRACTIO\nIIIS + S", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_PLUS")) + ]), + ValFrac(Fraction(4)) + ), + ("CVM FRACTIO\nIIIS - S", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_MINUS")) + ]), + ValFrac(Fraction(3)) + ), + ("CVM FRACTIO\nS * IV", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("S"), Numeral("IV"), "SYMBOL_TIMES")) + ]), + ValFrac(Fraction(2)) + ), + # Division returns fraction + ("CVM FRACTIO\nI / IV", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Numeral("I"), Numeral("IV"), "SYMBOL_DIVIDE")) + ]), + ValFrac(Fraction(1, 4)) + ), + ("CVM FRACTIO\nI / III", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Numeral("I"), Numeral("III"), "SYMBOL_DIVIDE")) + ]), + ValFrac(Fraction(1, 3)) + ), + # Integer division still works without fractions in operands... but with FRACTIO returns ValFrac + ("CVM FRACTIO\nX / II", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE")) + ]), + ValFrac(Fraction(5)) + ), + # Modulo on fractions: 7/2 RELIQVVM 3/2 = 1/2 (7/2 / 3/2 = 7/3, floor=2, 7/2 - 3 = 1/2) + ("CVM FRACTIO\nIIIS RELIQVVM IS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IS"), "KEYWORD_RELIQVVM")) + ]), + ValFrac(Fraction(1, 2)) + ), + # Modulo with mixed operand types: 5/2 RELIQVVM 1 = 1/2 + ("CVM FRACTIO\nIIS RELIQVVM I", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIS"), Numeral("I"), "KEYWORD_RELIQVVM")) + ]), + ValFrac(Fraction(1, 2)) + ), + # Int operands under FRACTIO still return a fraction: 10 RELIQVVM 3 = 1 (as Fraction) + ("CVM FRACTIO\nX RELIQVVM III", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "KEYWORD_RELIQVVM")) + ]), + ValFrac(Fraction(1)) + ), + # Exact multiple under FRACTIO: 3 RELIQVVM 3/2 = 0 + ("CVM FRACTIO\nIII RELIQVVM IS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Numeral("III"), Fractio("IS"), "KEYWORD_RELIQVVM")) + ]), + ValFrac(Fraction(0)) + ), + # String concatenation with fraction + ("CVM FRACTIO\nDIC(IIIS & \"!\")", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BuiltIn("DIC", [BinOp(Fractio("IIIS"), String("!"), "SYMBOL_AMPERSAND")])) + ]), + ValStr("IIIS!"), "IIIS!\n" + ), + # Negative fractions + ("CVM FRACTIO\nCVM SVBNVLLA\n-IIS", + Program([ModuleCall("FRACTIO"),ModuleCall("SVBNVLLA")],[ + ExpressionStatement(UnaryMinus(Fractio("IIS"))) + ]), + ValFrac(Fraction(-5,2)) + ) +] + +class TestFractio(unittest.TestCase): + @parameterized.expand(fractio_tests) + def test_fractio(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) + + +fractio_comparison_tests = [ + # fraction vs fraction + ("CVM FRACTIO\nIIIS PLVS III", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("III"), "KEYWORD_PLVS")) + ]), + ValBool(True) + ), + ("CVM FRACTIO\nIII MINVS IIIS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Numeral("III"), Fractio("IIIS"), "KEYWORD_MINVS")) + ]), + ValBool(True) + ), + ("CVM FRACTIO\nIIIS MINVS IV", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("IV"), "KEYWORD_MINVS")) + ]), + ValBool(True) + ), + ("CVM FRACTIO\nIV PLVS IIIS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Numeral("IV"), Fractio("IIIS"), "KEYWORD_PLVS")) + ]), + ValBool(True) + ), + ("CVM FRACTIO\nIIIS PLVS IIIS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_PLVS")) + ]), + ValBool(False) + ), + ("CVM FRACTIO\nIIIS MINVS IIIS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_MINVS")) + ]), + ValBool(False) + ), + # HAVD_PLVS / HAVD_MINVS on fractions — equality boundary distinguishes from MINVS / PLVS + ("CVM FRACTIO\nIIIS HAVD_PLVS III", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("III"), "KEYWORD_HAVD_PLVS")) + ]), + ValBool(False) # 3.5 <= 3 is false + ), + ("CVM FRACTIO\nIIIS HAVD_MINVS IIIS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_HAVD_MINVS")) + ]), + ValBool(True) # 3.5 >= 3.5 is true (equality boundary) + ), + ("CVM FRACTIO\nIIIS HAVD_PLVS IIIS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_HAVD_PLVS")) + ]), + ValBool(True) # 3.5 <= 3.5 is true (equality boundary) + ), + # equality: fraction == fraction + ("CVM FRACTIO\nIIIS EST IIIS", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_EST")) + ]), + ValBool(True) + ), + ("CVM FRACTIO\nIIIS EST IV", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("IV"), "KEYWORD_EST")) + ]), + ValBool(False) + ), + # equality: fraction == whole number (ValFrac(4) vs ValInt(4)) + ("CVM FRACTIO\nIIIS + S EST IV", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp( + BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_PLUS"), + Numeral("IV"), "KEYWORD_EST")) + ]), + ValBool(True) + ), + ("CVM FRACTIO\nS + S EST I", + Program([ModuleCall("FRACTIO")], [ + ExpressionStatement(BinOp( + BinOp(Fractio("S"), Fractio("S"), "SYMBOL_PLUS"), + Numeral("I"), "KEYWORD_EST")) + ]), + ValBool(True) + ), +] + +class TestFractioComparisons(unittest.TestCase): + @parameterized.expand(fractio_comparison_tests) + def test_fractio_comparison(self, source, nodes, value): + run_test(self, source, nodes, value) + + +class TestFractioHelpers(unittest.TestCase): + def test_frac_to_fraction_ordering(self): + with self.assertRaises(CentvrionError): + frac_to_fraction(".S") # . before S violates highest-to-lowest + + def test_frac_to_fraction_level_overflow(self): + with self.assertRaises(CentvrionError): + frac_to_fraction("SSSSSS") # SS means S twice = 12/12 = 1, violating < 12/12 constraint + + def test_frac_to_fraction_iiis(self): + self.assertEqual(frac_to_fraction("IIIS"), Fraction(7, 2)) + + def test_frac_to_fraction_s_colon_dot(self): + self.assertEqual(frac_to_fraction("S:."), Fraction(3, 4)) + + def test_frac_to_fraction_dot(self): + self.assertEqual(frac_to_fraction("."), Fraction(1, 12)) + + def test_frac_to_fraction_multilevel(self): + self.assertEqual(frac_to_fraction("VIIS:|::"), Fraction(7) + Fraction(100, 144)) + + def test_fraction_to_frac_iiis(self): + self.assertEqual(fraction_to_frac(Fraction(7, 2)), "IIIS") + + def test_fraction_to_frac_s_colon_dot(self): + self.assertEqual(fraction_to_frac(Fraction(3, 4)), "S:.") + + def test_fraction_to_frac_dot(self): + self.assertEqual(fraction_to_frac(Fraction(1, 12)), ".") + + def test_fraction_to_frac_multilevel(self): + self.assertEqual( + fraction_to_frac(Fraction(7) + Fraction(100, 144)), + "VIIS:|::" + ) + + def test_roundtrip(self): + # Only canonical forms roundtrip — fraction_to_frac always uses max colons before dots + for s in ["IIIS", "S:.", ".", "::", "VIIS:|::", "S"]: + self.assertEqual(fraction_to_frac(frac_to_fraction(s)), s) diff --git a/tests/10_test_external.py b/tests/10_test_external.py new file mode 100644 index 0000000..1c5703a --- /dev/null +++ b/tests/10_test_external.py @@ -0,0 +1,406 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- DORMI --- + +dormi_tests = [ + ("DORMI(NVLLVS)", + Program([], [ExpressionStatement(BuiltIn("DORMI", [Nullus()]))]), + ValNul()), +] + +class TestDormi(unittest.TestCase): + @parameterized.expand(dormi_tests) + def test_dormi(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) + + def test_dormi_timing_int(self): + source = "DORMI(I)\n" + lexer = Lexer().get_lexer() + tokens = lexer.lex(source) + program = Parser().parse(tokens) + + start = time.time() + program.eval() + elapsed = time.time() - start + self.assertAlmostEqual(elapsed, 1.0, delta=0.5) + + def test_dormi_timing_int_compiled(self): + source = "DORMI(I)\n" + lexer = Lexer().get_lexer() + tokens = lexer.lex(source) + program = Parser().parse(tokens) + + c_source = compile_program(program) + with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: + tmp_c.write(c_source) + tmp_c_path = tmp_c.name + with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: + tmp_bin_path = tmp_bin.name + try: + subprocess.run( + ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], + check=True, capture_output=True, + ) + start = time.time() + proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) + elapsed = time.time() - start + self.assertEqual(proc.returncode, 0) + self.assertAlmostEqual(elapsed, 1.0, delta=0.5) + finally: + os.unlink(tmp_c_path) + os.unlink(tmp_bin_path) + + def test_dormi_timing_frac(self): + source = "CVM FRACTIO\nDORMI(S)\n" + lexer = Lexer().get_lexer() + tokens = lexer.lex(source) + program = Parser().parse(tokens) + + start = time.time() + program.eval() + elapsed = time.time() - start + self.assertAlmostEqual(elapsed, 0.5, delta=0.5) + + def test_dormi_timing_frac_compiled(self): + source = "CVM FRACTIO\nDORMI(S)\n" + lexer = Lexer().get_lexer() + tokens = lexer.lex(source) + program = Parser().parse(tokens) + + c_source = compile_program(program) + with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: + tmp_c.write(c_source) + tmp_c_path = tmp_c.name + with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: + tmp_bin_path = tmp_bin.name + try: + subprocess.run( + ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], + check=True, capture_output=True, + ) + start = time.time() + proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) + elapsed = time.time() - start + self.assertEqual(proc.returncode, 0) + self.assertAlmostEqual(elapsed, 0.5, delta=0.5) + finally: + os.unlink(tmp_c_path) + os.unlink(tmp_bin_path) + +class TestScripta(unittest.TestCase): + def _run_scripta(self, source): + lexer = Lexer().get_lexer() + tokens = lexer.lex(source + "\n") + program = Parser().parse(tokens) + # printer round-trip + new_text = program.print() + new_tokens = Lexer().get_lexer().lex(new_text + "\n") + new_nodes = Parser().parse(new_tokens) + self.assertEqual(program, new_nodes, f"Printer test\n{source}\n{new_text}") + return program + + def _compile_and_run(self, program): + c_source = compile_program(program) + with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: + tmp_c.write(c_source) + tmp_c_path = tmp_c.name + with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: + tmp_bin_path = tmp_bin.name + try: + subprocess.run( + ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], + check=True, capture_output=True, + ) + proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) + self.assertEqual(proc.returncode, 0, f"Compiler binary exited non-zero:\n{proc.stderr}") + return proc.stdout + finally: + os.unlink(tmp_c_path) + os.unlink(tmp_bin_path) + + def test_scribe_and_lege(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: + path = f.name + try: + source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE MVNDE")\nDIC(LEGE("{path}"))' + program = self._run_scripta(source) + captured = StringIO() + with patch("sys.stdout", captured): + program.eval() + self.assertEqual(captured.getvalue(), "SALVE MVNDE\n") + with open(path) as f: + self.assertEqual(f.read(), "SALVE MVNDE") + finally: + if os.path.exists(path): + os.unlink(path) + + def test_scribe_and_lege_compiled(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: + path = f.name + try: + source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE MVNDE")\nDIC(LEGE("{path}"))' + program = self._run_scripta(source) + output = self._compile_and_run(program) + self.assertEqual(output, "SALVE MVNDE\n") + with open(path) as f: + self.assertEqual(f.read(), "SALVE MVNDE") + finally: + if os.path.exists(path): + os.unlink(path) + + def test_adivnge(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: + path = f.name + try: + source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE")\nADIVNGE("{path}", " MVNDE")\nDIC(LEGE("{path}"))' + program = self._run_scripta(source) + captured = StringIO() + with patch("sys.stdout", captured): + program.eval() + self.assertEqual(captured.getvalue(), "SALVE MVNDE\n") + finally: + if os.path.exists(path): + os.unlink(path) + + def test_adivnge_compiled(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: + path = f.name + try: + source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE")\nADIVNGE("{path}", " MVNDE")\nDIC(LEGE("{path}"))' + program = self._run_scripta(source) + output = self._compile_and_run(program) + self.assertEqual(output, "SALVE MVNDE\n") + finally: + if os.path.exists(path): + os.unlink(path) + + def test_scribe_overwrites(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: + path = f.name + try: + source = f'CVM SCRIPTA\nSCRIBE("{path}", "first")\nSCRIBE("{path}", "second")\nLEGE("{path}")' + program = self._run_scripta(source) + result = program.eval() + self.assertEqual(result, ValStr("second")) + finally: + if os.path.exists(path): + os.unlink(path) + + def test_lege_empty_file(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False, mode="w") as f: + path = f.name + try: + source = f'CVM SCRIPTA\nLEGE("{path}")' + program = self._run_scripta(source) + result = program.eval() + self.assertEqual(result, ValStr("")) + finally: + os.unlink(path) + + def test_lege_preexisting_content(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False, mode="w") as f: + f.write("hello from python") + path = f.name + try: + source = f'CVM SCRIPTA\nLEGE("{path}")' + program = self._run_scripta(source) + result = program.eval() + self.assertEqual(result, ValStr("hello from python")) + finally: + os.unlink(path) + + def test_scribe_returns_nullus(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: + path = f.name + try: + source = f'CVM SCRIPTA\nSCRIBE("{path}", "x")' + program = self._run_scripta(source) + result = program.eval() + self.assertEqual(result, ValNul()) + finally: + if os.path.exists(path): + os.unlink(path) + + def test_adivnge_returns_nullus(self): + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: + path = f.name + try: + source = f'CVM SCRIPTA\nADIVNGE("{path}", "x")' + program = self._run_scripta(source) + result = program.eval() + self.assertEqual(result, ValNul()) + finally: + if os.path.exists(path): + os.unlink(path) + +class TestRete(unittest.TestCase): + @classmethod + def setUpClass(cls): + import http.server, threading + class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.end_headers() + self.wfile.write(b"SALVE MVNDE") + def log_message(self, *args): + pass + cls.server = http.server.HTTPServer(("127.0.0.1", 0), Handler) + cls.port = cls.server.server_address[1] + cls.thread = threading.Thread(target=cls.server.serve_forever) + cls.thread.daemon = True + cls.thread.start() + + @classmethod + def tearDownClass(cls): + cls.server.shutdown() + + def test_pete(self): + url = f"http://127.0.0.1:{self.port}/" + source = f'CVM RETE\nPETE("{url}")' + run_test(self, source, + Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("PETE", [String(url)]))]), + ValStr("SALVE MVNDE")) + + def test_pete_dic(self): + url = f"http://127.0.0.1:{self.port}/" + source = f'CVM RETE\nDIC(PETE("{url}"))' + run_test(self, source, + Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("PETE", [String(url)])]))]), + ValStr("SALVE MVNDE"), "SALVE MVNDE\n") + +class TestReteServer(unittest.TestCase): + """Integration tests for PETITVR + AVSCVLTA server functionality.""" + + def _wait_for_server(self, port, timeout=2.0): + """Poll until the server is accepting connections.""" + import socket + deadline = time.time() + timeout + while time.time() < deadline: + try: + with socket.create_connection(("127.0.0.1", port), timeout=0.1): + return + except OSError: + time.sleep(0.05) + self.fail(f"Server on port {port} did not start within {timeout}s") + + def _free_port(self): + """Find a free port in range 1024-3999 (representable without MAGNVM).""" + import socket, random + for _ in range(100): + port = random.randint(1024, 3999) + try: + with socket.socket() as s: + s.bind(("127.0.0.1", port)) + return port + except OSError: + continue + raise RuntimeError("Could not find a free port in range 1024-3999") + + def _run_server(self, source): + """Parse and eval source in a daemon thread. Returns when server is ready.""" + import threading + lexer = Lexer().get_lexer() + tokens = lexer.lex(source + "\n") + program = Parser().parse(tokens) + t = threading.Thread(target=program.eval, daemon=True) + t.start() + + def test_basic_handler(self): + port = self._free_port() + source = ( + f'CVM RETE\n' + f'PETITVR("/", FVNCTIO (petitio) VT {{\n' + f'REDI("SALVE MVNDE")\n' + f'}})\n' + f'AVSCVLTA({int_to_num(port, False)})' + ) + self._run_server(source) + self._wait_for_server(port) + import urllib.request + resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/") + self.assertEqual(resp.read().decode(), "SALVE MVNDE") + + def test_multiple_routes(self): + port = self._free_port() + source = ( + f'CVM RETE\n' + f'PETITVR("/", FVNCTIO (p) VT {{\nREDI("RADIX")\n}})\n' + f'PETITVR("/nomen", FVNCTIO (p) VT {{\nREDI("MARCVS")\n}})\n' + f'AVSCVLTA({int_to_num(port, False)})' + ) + self._run_server(source) + self._wait_for_server(port) + import urllib.request + resp1 = urllib.request.urlopen(f"http://127.0.0.1:{port}/") + self.assertEqual(resp1.read().decode(), "RADIX") + resp2 = urllib.request.urlopen(f"http://127.0.0.1:{port}/nomen") + self.assertEqual(resp2.read().decode(), "MARCVS") + + def test_404_unmatched(self): + port = self._free_port() + source = ( + f'CVM RETE\n' + f'PETITVR("/", FVNCTIO (p) VT {{\nREDI("ok")\n}})\n' + f'AVSCVLTA({int_to_num(port, False)})' + ) + self._run_server(source) + self._wait_for_server(port) + import urllib.request, urllib.error + with self.assertRaises(urllib.error.HTTPError) as ctx: + urllib.request.urlopen(f"http://127.0.0.1:{port}/nonexistent") + self.assertEqual(ctx.exception.code, 404) + + def test_request_dict_via(self): + port = self._free_port() + source = ( + f'CVM RETE\n' + f'PETITVR("/echo", FVNCTIO (petitio) VT {{\n' + f'REDI(petitio["via"])\n' + f'}})\n' + f'AVSCVLTA({int_to_num(port, False)})' + ) + self._run_server(source) + self._wait_for_server(port) + import urllib.request + resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/echo") + self.assertEqual(resp.read().decode(), "/echo") + + def test_request_dict_quaestio(self): + port = self._free_port() + source = ( + f'CVM RETE\n' + f'PETITVR("/q", FVNCTIO (petitio) VT {{\n' + f'REDI(petitio["quaestio"])\n' + f'}})\n' + f'AVSCVLTA({int_to_num(port, False)})' + ) + self._run_server(source) + self._wait_for_server(port) + import urllib.request + resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/q?nomen=Marcus") + self.assertEqual(resp.read().decode(), "nomen=Marcus") + + def test_petitvr_stores_route(self): + """PETITVR alone (without AVSCVLTA) just stores a route and returns NVLLVS.""" + source = 'CVM RETE\nPETITVR("/", FVNCTIO (p) VT {\nREDI("hi")\n})' + run_test(self, source, + Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("PETITVR", [ + String("/"), + Fvnctio([ID("p")], [Redi([String("hi")])]) + ]))]), + ValNul()) diff --git a/tests/11_test_assorted.py b/tests/11_test_assorted.py new file mode 100644 index 0000000..d69359d --- /dev/null +++ b/tests/11_test_assorted.py @@ -0,0 +1,443 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- Comments --- + +comment_tests = [ + # trailing line comment + ('DIC("hello") // this is ignored', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"), + # line comment on its own line before code + ('// ignored\nDIC("hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"), + # inline block comment + ('DIC(/* ignored */ "hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"), + # block comment spanning multiple lines + ('/* line one\nline two */\nDIC("hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"), + # block comment mid-expression + ("II /* ignored */ + III", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"))]), ValInt(5)), + # line comment after expression (no output) + ("II + III // ignored", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"))]), ValInt(5)), + # division still works (/ token not confused with //) + ("X / II", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))]), ValInt(5)), + # multiple line comments + ('// first\n// second\nDIC("ok")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("ok")]))]), ValStr("ok"), "ok\n"), + # comment-only line between two statements + ('DESIGNA x VT I\n// set y\nDESIGNA y VT II\nx + y', + Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("II")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]), + ValInt(3)), + # blank line between two statements (double newline) + ('DESIGNA x VT I\n\nDESIGNA y VT II\nx + y', + Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("II")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]), + ValInt(3)), + # multiple comment-only lines between statements + ('DESIGNA x VT I\n// one\n// two\nDESIGNA y VT III\nx + y', + Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("III")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]), + 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", + Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("X"))], None), ExpressionStatement(ID("r"))]), + ValInt(10)), + # SI: variable assigned in ALIVD branch persists in outer scope + ("SI FALSITAS TVNC { DESIGNA r VT X } ALIVD { DESIGNA r VT V }\nr", + Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("X"))], [Designa(ID("r"), Numeral("V"))]), ExpressionStatement(ID("r"))]), + 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 FAC { DESIGNA x VT x + I\nDESIGNA r VT x }\nr", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_EST"), [ + Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), + Designa(ID("r"), ID("x")), + ]), + ExpressionStatement(ID("r")), + ]), + ValInt(5)), + # PER: loop variable holds last array element after loop (no ERVMPE) + ("PER i IN [I, II, III] FAC { DESIGNA nop VT I }\ni", + Program([], [ + PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [Designa(ID("nop"), Numeral("I"))]), + ExpressionStatement(ID("i")), + ]), + 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] FAC { DESIGNA i VT C\nDESIGNA cnt VT cnt + I }\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ + Designa(ID("i"), Numeral("C")), + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS")), + ]), + ExpressionStatement(ID("cnt")), + ]), + 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] FAC { DESIGNA i VT C }\ni", + Program([], [ + PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [Designa(ID("i"), Numeral("C"))]), + ExpressionStatement(ID("i")), + ]), + ValInt(100)), + # DONICVM: counter holds last range value after loop ends + # [I VSQVE IV] = [1,2,3,4]; last value assigned by loop is IV=4 + ("DONICVM i VT I VSQVE IV FAC { DESIGNA nop VT I }\ni", + Program([], [ + PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [Designa(ID("nop"), Numeral("I"))]), + ExpressionStatement(ID("i")), + ]), + ValInt(4)), + # 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 4 times → 5 + ("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FAC { DESIGNA cnt VT cnt + I\nDESIGNA i VT C }\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [ + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS")), + Designa(ID("i"), Numeral("C")), + ]), + ExpressionStatement(ID("cnt")), + ]), + ValInt(5)), + # DONICVM: ERVMPE exits loop early; counter persists at break value + ("DONICVM i VT I VSQVE X FAC {\nSI i EST III TVNC { ERVMPE }\n}\ni", + Program([], [ + PerStatement(DataRangeArray(Numeral("I"), Numeral("X")), ID("i"), [ + SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None), + ]), + ExpressionStatement(ID("i")), + ]), + 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", + Program([], [ + Designa(ID("n"), Numeral("I")), + Defini(ID("f"), [ID("n")], [Designa(ID("n"), BinOp(ID("n"), Numeral("X"), "SYMBOL_PLUS")), Redi([ID("n")])]), + ExpressionStatement(Invoca(ID("f"), [Numeral("V")])), + ExpressionStatement(ID("n")), + ]), + 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", + Program([], [ + Designa(ID("x"), Numeral("I")), + Defini(ID("f"), [], [Designa(ID("x"), Numeral("C")), Redi([ID("x")])]), + ExpressionStatement(Invoca(ID("f"), [])), + ExpressionStatement(ID("x")), + ]), + 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)", + Program([], [ + Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), + ExpressionStatement(BinOp(Invoca(ID("f"), [Numeral("III")]), Invoca(ID("f"), [Numeral("IV")]), "SYMBOL_PLUS")), + ]), + 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", + Program([], [ + Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]), + Designa(ID("n"), Numeral("II")), + ExpressionStatement(Invoca(ID("f"), [Numeral("I")])), + ExpressionStatement(ID("n")), + ]), + ValInt(2)), +] + +class TestScope(unittest.TestCase): + @parameterized.expand(scope_tests) + def test_scope(self, source, nodes, value): + run_test(self, source, nodes, value) + +# --- First-class functions / FVNCTIO --- + +fvnctio_tests = [ + # Lambda assigned to variable, then called + ( + "DESIGNA f VT FVNCTIO (x) VT { REDI (x + I) }\nINVOCA f (V)", + Program([], [ + Designa(ID("f"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])), + ExpressionStatement(Invoca(ID("f"), [Numeral("V")])), + ]), + ValInt(6), + ), + # IIFE: immediately invoked lambda + ( + "INVOCA FVNCTIO (x) VT { REDI (x * II) } (III)", + Program([], [ + ExpressionStatement(Invoca( + Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]), + [Numeral("III")], + )), + ]), + ValInt(6), + ), + # Zero-arg lambda + ( + "INVOCA FVNCTIO () VT { REDI (XLII) } ()", + Program([], [ + ExpressionStatement(Invoca( + Fvnctio([], [Redi([Numeral("XLII")])]), + [], + )), + ]), + ValInt(42), + ), + # Function passed as argument + ( + "DEFINI apply (f, x) VT { REDI (INVOCA f (x)) }\n" + "DESIGNA dbl VT FVNCTIO (n) VT { REDI (n * II) }\n" + "INVOCA apply (dbl, V)", + Program([], [ + Defini(ID("apply"), [ID("f"), ID("x")], [ + Redi([Invoca(ID("f"), [ID("x")])]) + ]), + Designa(ID("dbl"), Fvnctio([ID("n")], [ + Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")]) + ])), + ExpressionStatement(Invoca(ID("apply"), [ID("dbl"), Numeral("V")])), + ]), + ValInt(10), + ), + # Lambda uses caller-scope variable (copy-caller semantics) + ( + "DESIGNA n VT III\n" + "DESIGNA f VT FVNCTIO (x) VT { REDI (x + n) }\n" + "INVOCA f (V)", + Program([], [ + Designa(ID("n"), Numeral("III")), + Designa(ID("f"), Fvnctio([ID("x")], [ + Redi([BinOp(ID("x"), ID("n"), "SYMBOL_PLUS")]) + ])), + ExpressionStatement(Invoca(ID("f"), [Numeral("V")])), + ]), + ValInt(8), + ), + # Named function passed as value + ( + "DEFINI sqr (x) VT { REDI (x * x) }\n" + "DESIGNA f VT sqr\n" + "INVOCA f (IV)", + Program([], [ + Defini(ID("sqr"), [ID("x")], [Redi([BinOp(ID("x"), ID("x"), "SYMBOL_TIMES")])]), + Designa(ID("f"), ID("sqr")), + ExpressionStatement(Invoca(ID("f"), [Numeral("IV")])), + ]), + ValInt(16), + ), + # Nested lambdas + ( + "INVOCA FVNCTIO (x) VT { REDI (INVOCA FVNCTIO (y) VT { REDI (y + I) } (x)) } (V)", + Program([], [ + ExpressionStatement(Invoca( + Fvnctio([ID("x")], [ + Redi([Invoca( + Fvnctio([ID("y")], [Redi([BinOp(ID("y"), Numeral("I"), "SYMBOL_PLUS")])]), + [ID("x")], + )]) + ]), + [Numeral("V")], + )), + ]), + ValInt(6), + ), + # DIC on a function value + ( + "DESIGNA f VT FVNCTIO (x) VT { REDI (x) }\nDIC(f)", + Program([], [ + Designa(ID("f"), Fvnctio([ID("x")], [Redi([ID("x")])])), + ExpressionStatement(BuiltIn("DIC", [ID("f")])), + ]), + ValStr("FVNCTIO"), + "FVNCTIO\n", + ), + # Lambda stored in array, called via index + ( + "DESIGNA fns VT [FVNCTIO (x) VT { REDI (x + I) }, FVNCTIO (x) VT { REDI (x * II) }]\n" + "INVOCA fns[I] (V)", + Program([], [ + Designa(ID("fns"), DataArray([ + Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])]), + Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]), + ])), + ExpressionStatement(Invoca( + ArrayIndex(ID("fns"), Numeral("I")), + [Numeral("V")], + )), + ]), + ValInt(6), + ), + # Lambda stored in dict, called via key + ( + 'DESIGNA d VT TABVLA {"add" VT FVNCTIO (x) VT { REDI (x + I) }}\n' + 'INVOCA d["add"] (V)', + Program([], [ + Designa(ID("d"), DataDict([ + (String("add"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])), + ])), + ExpressionStatement(Invoca( + ArrayIndex(ID("d"), String("add")), + [Numeral("V")], + )), + ]), + ValInt(6), + ), + # Multi-param lambda + ( + "DESIGNA add VT FVNCTIO (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)", + Program([], [ + Designa(ID("add"), Fvnctio([ID("a"), ID("b")], [ + Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")]) + ])), + ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])), + ]), + ValInt(7), + ), +] + +class TestFvnctio(unittest.TestCase): + @parameterized.expand(fvnctio_tests) + def test_fvnctio(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) + +# --- Tempta/Cape (try/catch) --- + +tempta_tests = [ + # Try block succeeds — catch not entered + ( + "TEMPTA {\nDESIGNA r VT I\n} CAPE e {\nDESIGNA r VT II\n}\nr", + Program([], [ + TemptaStatement( + [Designa(ID("r"), Numeral("I"))], + ID("e"), + [Designa(ID("r"), Numeral("II"))], + ), + ExpressionStatement(ID("r")), + ]), + ValInt(1), + ), + # Try block errors — caught by catch + ( + "TEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT II\n}\nr", + Program([], [ + TemptaStatement( + [Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], + ID("e"), + [Designa(ID("r"), Numeral("II"))], + ), + ExpressionStatement(ID("r")), + ]), + ValInt(2), + ), + # Error variable contains the error message + ( + 'DESIGNA e VT NVLLVS\nTEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nNVLLVS\n}\ne', + Program([], [ + Designa(ID("e"), Nullus()), + TemptaStatement( + [Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], + ID("e"), + [ExpressionStatement(Nullus())], + ), + ExpressionStatement(ID("e")), + ]), + ValStr("Division by zero"), + ), + # Nested tempta — inner catches, outer unaffected + ( + "DESIGNA r VT NVLLVS\nTEMPTA {\nTEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT I\n}\n} CAPE e {\nDESIGNA r VT II\n}\nr", + Program([], [ + Designa(ID("r"), Nullus()), + TemptaStatement( + [TemptaStatement( + [Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], + ID("e"), + [Designa(ID("r"), Numeral("I"))], + )], + ID("e"), + [Designa(ID("r"), Numeral("II"))], + ), + ExpressionStatement(ID("r")), + ]), + ValInt(1), + ), + # REDI inside catch block + ( + "DEFINI f () VT {\nTEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nREDI (III)\n}\nREDI (IV)\n}\nINVOCA f ()", + Program([], [ + Defini(ID("f"), [], [ + TemptaStatement( + [Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], + ID("e"), + [Redi([Numeral("III")])], + ), + Redi([Numeral("IV")]), + ]), + ExpressionStatement(Invoca(ID("f"), [])), + ]), + ValInt(3), + ), + # ERVMPE inside catch block (inside a loop) + ( + "DESIGNA r VT NVLLVS\nDVM r EST I FAC {\nTEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT I\nERVMPE\n}\n}\nr", + Program([], [ + Designa(ID("r"), Nullus()), + DumStatement( + BinOp(ID("r"), Numeral("I"), "KEYWORD_EST"), + [TemptaStatement( + [Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))], + ID("e"), + [Designa(ID("r"), Numeral("I")), Erumpe()], + )], + ), + ExpressionStatement(ID("r")), + ]), + ValInt(1), + ), + # Statement after error in try block is not executed + ( + "DESIGNA r VT NVLLVS\nTEMPTA {\nDESIGNA x VT I / NVLLVS\nDESIGNA r VT III\n} CAPE e {\nDESIGNA r VT II\n}\nr", + Program([], [ + Designa(ID("r"), Nullus()), + TemptaStatement( + [Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE")), + Designa(ID("r"), Numeral("III"))], + ID("e"), + [Designa(ID("r"), Numeral("II"))], + ), + ExpressionStatement(ID("r")), + ]), + ValInt(2), + ), +] + +class TestTempta(unittest.TestCase): + @parameterized.expand(tempta_tests) + def test_tempta(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) diff --git a/tests/12_test_failures.py b/tests/12_test_failures.py new file mode 100644 index 0000000..2e84382 --- /dev/null +++ b/tests/12_test_failures.py @@ -0,0 +1,148 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, time, + run_test, run_compiler_error_test, + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, + CentvrionError, _RUNTIME_C, _cent_rng, + Lexer, Parser, compile_program, + os, subprocess, tempfile, StringIO, patch, +) + + +# --- Errors --- + +error_tests = [ + ("x", CentvrionError), # undefined variable + ("INVOCA f ()", CentvrionError), # undefined function + ("DESIGNA VT III", SyntaxError), # parse error: missing id after DESIGNA + ("DESIGNA x III", SyntaxError), # parse error: missing VT + ("DEFINI f () VT { REDI(I) }\nf()", SyntaxError), # function call without INVOCA (no args) + ("DEFINI f (x) VT { REDI(x) }\nf(I)", SyntaxError), # function call without INVOCA (with args) + ("DIC(M + M + M + M)", CentvrionError), # output > 3999 without MAGNVM + ("IIII", CentvrionError), # invalid Roman numeral in source + ("FORTVITVS_NVMERVS(I, X)", CentvrionError), # requires FORS module + ('NVMERVS(I)', CentvrionError), # NVMERVS expects a string, not int + ('NVMERVS("ABC")', CentvrionError), # invalid Roman numeral string + ('NVMERVS("XIV", "IX")', CentvrionError), # too many args + ("DEFINI f (x) VT { REDI(x) }\nINVOCA f (I, II)", CentvrionError), # too many args + ("DEFINI f (x, y) VT { REDI(x) }\nINVOCA f (I)", CentvrionError), # too few args + ("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", CentvrionError), # args to zero-param function + ("SI NVLLVS TVNC { DESIGNA r VT I }", CentvrionError), # NVLLVS cannot be used as boolean + ("NVLLVS AVT VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in AVT + ("FALSITAS AVT NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean + ("VERITAS ET NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean + ("NVLLVS ET VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in ET + ('I @ [II]', CentvrionError), # @ requires two arrays (int @ array) + ('[I] @ "hello"', CentvrionError), # @ requires two arrays (array @ string) + ('"a" @ "b"', CentvrionError), # @ requires two arrays (string @ string) + ('"hello" + " world"', CentvrionError), # use & for string concatenation, not + + ("[I, II][III]", CentvrionError), # index too high + ("CVM SVBNVLLA\n[I, II][-I]", CentvrionError), # negative index + ("[I, II][-I]", CentvrionError), # negative value + ("I / NVLLVS", CentvrionError), # division by zero (NVLLVS coerces to 0) + ("V RELIQVVM NVLLVS", CentvrionError), # modulo by zero (NVLLVS coerces to 0) + ("NVLLVS RELIQVVM NVLLVS", CentvrionError), # modulo by zero (both NVLLVS) + ("I / [I, II]", CentvrionError), # division with array operand + ("I - \"hello\"", CentvrionError), # subtraction with string + ("I * \"hello\"", CentvrionError), # multiplication with string + ("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings + ('"a" HAVD_PLVS "b"', CentvrionError), # HAVD_PLVS on strings + ('[I] HAVD_MINVS [II]', CentvrionError), # HAVD_MINVS on arrays + ("I[I]", CentvrionError), # indexing a non-array + ('"SALVTE"[VII]', CentvrionError), # string index out of range + ('"SALVTE"[NVLLVS]', CentvrionError), # string index with non-integer + ('"SALVTE"[II VSQVE VII]', CentvrionError), # string slice out of range + ('"SALVTE"[III VSQVE II]', CentvrionError), # string slice from > to + ("DESIGNA x VT I\nDESIGNA x[I] VT II", CentvrionError), # index-assign to non-array + ("SEMEN(I)", CentvrionError), # requires FORS module + ('CVM FORS\nSEMEN("abc")', CentvrionError), # SEMEN requires integer seed + ("FORTVITA_ELECTIO([])", CentvrionError), # FORS required for FORTVITA_ELECTIO + ("CVM FORS\nFORTVITA_ELECTIO([])", CentvrionError), # FORTVITA_ELECTIO on empty array + ("CVM FORS\nFORTVITVS_NVMERVS(X, I)", CentvrionError), # FORTVITVS_NVMERVS a > b + ("PER i IN I FAC { DIC(i) }", CentvrionError), # PER over non-array + ("DECIMATIO([I, II, III])", CentvrionError), # FORS required for DECIMATIO + ("CVM FORS\nDECIMATIO(I)", CentvrionError), # DECIMATIO requires an array + ("LONGITVDO(I)", CentvrionError), # LONGITVDO on non-array + ("ORDINA(I)", CentvrionError), # ORDINA on non-array + ('ORDINA([I, "a"])', CentvrionError), # ORDINA mixed types + ("DESIGNA x VT I\nORDINA(x)", CentvrionError), # ORDINA on id (non-array) + ("SENATVS(I)", CentvrionError), # SENATVS requires booleans + ("SENATVS(VERITAS, I)", CentvrionError), # SENATVS mixed types + ("SENATVS([I, II, III])", CentvrionError), # SENATVS array of non-bools + ('LEGE("x.txt")', CentvrionError), # SCRIPTA required for LEGE + ('SCRIBE("x.txt", "hi")', CentvrionError), # SCRIPTA required for SCRIBE + ('ADIVNGE("x.txt", "hi")', CentvrionError), # SCRIPTA required for ADIVNGE + ("DESIGNA x VT I\nINVOCA x ()", CentvrionError), # invoking a non-function + ("SI I TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: int + ("IIIS", CentvrionError), # fraction without FRACTIO module + ("CVM FRACTIO\n[I, II, III][IIIS]", CentvrionError), # fractional index (IIIS = 7/2) + ("CVM FRACTIO\n[I, II, III][I / II]", CentvrionError), # fractional index from division (1/2) + ("DESIGNA z VT I - I\nSI z TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: zero int + ("SI [I] TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: non-empty list + ("SI [] TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: empty list + ("DESIGNA x VT I\nDVM x FAC {\nDESIGNA x VT x + I\n}", CentvrionError), # non-bool DVM condition: int + ("NON I", CentvrionError), # NON on integer + ("DESIGNA z VT I - I\nNON z", CentvrionError), # NON on zero integer + ('NON "hello"', CentvrionError), # NON on string + ("DESIGNA a, b VT III", CentvrionError), # destructure non-array + ("DESIGNA a, b VT [I]", CentvrionError), # destructure length mismatch: too many targets + ("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets + ("PER a, b IN [I, II, III] FAC { DIC(a) }", CentvrionError), # PER destructure: element is not an array + ("PER a, b IN [[I], [II]] FAC { DIC(a) }", CentvrionError), # PER destructure: wrong number of elements + ("[I, II, III][II VSQVE IV]", CentvrionError), # slice upper bound out of range + ("[I, II, III][NVLLVS VSQVE II]", CentvrionError), # slice with non-integer bound + ("I[I VSQVE II]", CentvrionError), # slice on non-array + ("[I, II, III][III VSQVE I]", CentvrionError), # slice from > to + ("CVM SVBNVLLA\n[I, II, III][-I VSQVE II]", CentvrionError), # slice with negative lower bound + ("CVM SVBNVLLA\n[I, II, III][I VSQVE -I]", CentvrionError), # slice with negative upper bound + ("CVM FRACTIO\n[I, II, III][IIIS VSQVE III]", CentvrionError), # slice with fractional lower bound + ("CVM FRACTIO\n[I, II, III][I VSQVE IIIS]", CentvrionError), # slice with fractional upper bound + ("CVM FRACTIO\n[I, II, III][I / II VSQVE III]", CentvrionError), # slice with division-fraction lower bound + ("TEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nDESIGNA y VT I / NVLLVS\n}", CentvrionError), # uncaught error in catch block propagates + ('QVAERE(I, "abc")', CentvrionError), # QVAERE requires strings, not int + ('QVAERE("abc", I)', CentvrionError), # QVAERE requires strings, not int + ('QVAERE("[", "abc")', CentvrionError), # QVAERE invalid regex + ('SVBSTITVE(I, "b", "c")', CentvrionError), # SVBSTITVE requires strings, not int pattern + ('SVBSTITVE("a", I, "c")', CentvrionError), # SVBSTITVE requires strings, not int replacement + ('SVBSTITVE("a", "b", I)', CentvrionError), # SVBSTITVE requires strings, not int text + ('SVBSTITVE("[", "b", "c")', CentvrionError), # SVBSTITVE invalid regex + ("SVBSTITVE('(a)', '\\1', 'a')", CentvrionError), # Arabic backref in replacement + ("QVAERE('(.)\\1', 'aa')", CentvrionError), # Arabic backref in pattern + ("QVAERE('a{3}', 'aaa')", CentvrionError), # Arabic quantifier in pattern + ('SCINDE(I, ",")', CentvrionError), # SCINDE requires strings, not int + ('SCINDE("a", I)', CentvrionError), # SCINDE requires strings, not int delimiter + ('MAIVSCVLA(I)', CentvrionError), # MAIVSCVLA requires a string, not int + ('MAIVSCVLA()', CentvrionError), # MAIVSCVLA requires exactly 1 arg + ('MAIVSCVLA("a", "b")', CentvrionError), # MAIVSCVLA too many args + ('MINVSCVLA(I)', CentvrionError), # MINVSCVLA requires a string, not int + ('MINVSCVLA()', CentvrionError), # MINVSCVLA requires exactly 1 arg + ('MINVSCVLA("a", "b")', CentvrionError), # MINVSCVLA too many args + ('PETE("http://example.com")', CentvrionError), # RETE required for PETE + ('CVM RETE\nPETE(I)', CentvrionError), # PETE requires a string URL + ('PETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})', CentvrionError), # RETE required for PETITVR + ('CVM RETE\nPETITVR(I, FVNCTIO (r) VT {\nREDI("hi")\n})', CentvrionError), # PETITVR path must be string + ('CVM RETE\nPETITVR("/", "not a func")', CentvrionError), # PETITVR handler must be function + ('CVM RETE\nAVSCVLTA(LXXX)', CentvrionError), # AVSCVLTA: no routes registered + ('AVSCVLTA(LXXX)', CentvrionError), # RETE required for AVSCVLTA + ('CVM RETE\nPETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})\nAVSCVLTA("text")', CentvrionError), # AVSCVLTA port must be integer + ("DONICVM i VT I VSQVE X GRADV I - I FAC { DIC(i) }", CentvrionError), # GRADV zero step + ('DONICVM i VT I VSQVE X GRADV "foo" FAC { DIC(i) }', CentvrionError), # GRADV non-integer step +] + +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) + +compiler_error_tests = [(s, e) for s, e in error_tests if e == CentvrionError] + +class TestCompilerErrors(unittest.TestCase): + @parameterized.expand(compiler_error_tests) + def test_compiler_errors(self, source, error_type): + run_compiler_error_test(self, source) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/_helpers.py b/tests/_helpers.py new file mode 100644 index 0000000..417f696 --- /dev/null +++ b/tests/_helpers.py @@ -0,0 +1,135 @@ +import os +import subprocess +import tempfile +import time +import unittest +from io import StringIO +from unittest.mock import patch +from parameterized import parameterized + +from fractions import Fraction + +from centvrion.ast_nodes import ( + ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, + Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, + Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, + ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, + String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, + fraction_to_frac, num_to_int, int_to_num, make_string, + _cent_rng, +) +from centvrion.compiler.emitter import compile_program +from centvrion.errors import CentvrionError +from centvrion.lexer import Lexer +from centvrion.parser import Parser +from centvrion.values import ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac + +_RUNTIME_C = os.path.join( + os.path.dirname(__file__), "..", + "centvrion", "compiler", "runtime", "cent_runtime.c" +) + +def run_test(self, source, target_nodes, target_value, target_output="", input_lines=[]): + _cent_rng.seed(1) + + lexer = Lexer().get_lexer() + tokens = lexer.lex(source + "\n") + program = Parser().parse(tokens) + + ########################## + ####### Parser Test ###### + ########################## + if target_nodes is not None: + 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 ###### + ########################## + 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 ##### + ########################## + c_source = compile_program(program) + # Force deterministic RNG seed=1 for test reproducibility + c_source = c_source.replace("cent_init();", "cent_init(); cent_semen((CentValue){.type=CENT_INT, .ival=1});", 1) + with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: + tmp_c.write(c_source) + tmp_c_path = tmp_c.name + with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: + tmp_bin_path = tmp_bin.name + try: + subprocess.run( + ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], + check=True, capture_output=True, + ) + stdin_data = "".join(f"{l}\n" for l in input_lines) + proc = subprocess.run( + [tmp_bin_path], + input=stdin_data, capture_output=True, text=True, + ) + self.assertEqual(proc.returncode, 0, f"Compiler binary exited non-zero:\n{proc.stderr}") + self.assertEqual(proc.stdout, target_output, "Compiler output test") + finally: + os.unlink(tmp_c_path) + os.unlink(tmp_bin_path) + + assert target_nodes is not None, "All tests must have target nodes" + + +def run_compiler_error_test(self, source): + lexer = Lexer().get_lexer() + tokens = lexer.lex(source + "\n") + program = Parser().parse(tokens) + try: + c_source = compile_program(program) + except CentvrionError: + return # compile-time detection is valid + with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c: + tmp_c.write(c_source) + tmp_c_path = tmp_c.name + with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin: + tmp_bin_path = tmp_bin.name + try: + subprocess.run( + ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], + check=True, capture_output=True, + ) + proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) + self.assertNotEqual(proc.returncode, 0, "Expected non-zero exit for error program") + self.assertTrue(proc.stderr.strip(), "Expected error message on stderr") + finally: + os.unlink(tmp_c_path) + os.unlink(tmp_bin_path)