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)