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, _IASON_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) ("ORDINA([I, II], V)", CentvrionError), # ORDINA comparator not a function ("DEFINI bad (a) VT { REDI (VERITAS) }\nORDINA([I, II], bad)", CentvrionError), # ORDINA comparator wrong arity ("DEFINI bad (a, b) VT { REDI (V) }\nORDINA([I, II], bad)", CentvrionError), # ORDINA comparator returns non-bool ("ORDINA([I], V, V)", CentvrionError), # ORDINA too many args ("MVTA([I, II])", CentvrionError), # MVTA too few args ("MVTA([I, II], FVNCTIO (x) VT { REDI (x) }, V)", CentvrionError), # MVTA too many args ("MVTA(I, FVNCTIO (x) VT { REDI (x) })", CentvrionError), # MVTA on non-array ("MVTA([I, II], V)", CentvrionError), # MVTA function arg not a function ("DEFINI bad (a, b) VT { REDI (a) }\nMVTA([I, II], bad)", CentvrionError), # MVTA function wrong arity ("CRIBRA([I, II])", CentvrionError), # CRIBRA too few args ("CRIBRA([I, II], FVNCTIO (x) VT { REDI (VERITAS) }, V)", CentvrionError), # CRIBRA too many args ("CRIBRA(I, FVNCTIO (x) VT { REDI (VERITAS) })", CentvrionError), # CRIBRA on non-array ("CRIBRA([I, II], V)", CentvrionError), # CRIBRA predicate not a function ("DEFINI bad (a, b) VT { REDI (VERITAS) }\nCRIBRA([I, II], bad)", CentvrionError), # CRIBRA predicate wrong arity ("DEFINI bad (x) VT { REDI (V) }\nCRIBRA([I, II], bad)", CentvrionError), # CRIBRA predicate returns non-bool ("CONFLA([I, II], I)", CentvrionError), # CONFLA too few args ("CONFLA([I, II], I, FVNCTIO (a, b) VT { REDI (a + b) }, V)", CentvrionError), # CONFLA too many args ("CONFLA(I, I, FVNCTIO (a, b) VT { REDI (a + b) })", CentvrionError), # CONFLA on non-array ("CONFLA([I, II], I, V)", CentvrionError), # CONFLA function arg not a function ("DEFINI bad (a) VT { REDI (a) }\nCONFLA([I, II], I, bad)", CentvrionError), # CONFLA function wrong arity ("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 ("NECTE([I, II], [III])", CentvrionError), # NECTE length mismatch ('NECTE(I, [II])', CentvrionError), # NECTE first arg not a list ('NECTE([I], II)', CentvrionError), # NECTE second arg not a list ("IVNGE([I, II], [III])", CentvrionError), # IVNGE length mismatch ('IVNGE(I, [II])', CentvrionError), # IVNGE first arg not a list ('IVNGE(["a"], II)', CentvrionError), # IVNGE second arg not a list ("IVNGE([VERITAS], [I])", CentvrionError), # IVNGE invalid key type (bool) ("IVNGE([[I]], [II])", CentvrionError), # IVNGE invalid key type (list) ("IASON_LEGE('null')", CentvrionError), # IASON module required for IASON_LEGE ("IASON_SCRIBE(NVLLVS)", CentvrionError), # IASON module required for IASON_SCRIBE ("CVM IASON\nIASON_LEGE(I)", CentvrionError), # IASON_LEGE non-string arg ("CVM IASON\nIASON_LEGE()", CentvrionError), # IASON_LEGE no args ("CVM IASON\nIASON_LEGE('null', 'null')", CentvrionError), # IASON_LEGE too many args ("CVM IASON\nIASON_LEGE('not json')", CentvrionError), # invalid JSON ("CVM IASON\nIASON_LEGE('[1,]')", CentvrionError), # trailing comma in array ("CVM IASON\nIASON_LEGE('{\"a\":}')", CentvrionError), # missing value in object ("CVM IASON\nIASON_LEGE('{\"a\" 1}')", CentvrionError), # missing colon in object ("CVM IASON\nIASON_LEGE('[1, 2')", CentvrionError), # unterminated array ("CVM IASON\nIASON_LEGE('{')", CentvrionError), # unterminated object ("CVM IASON\nIASON_LEGE('\"abc')", CentvrionError), # unterminated string ("CVM IASON\nIASON_LEGE('[1] junk')", CentvrionError), # trailing data ("CVM IASON\nIASON_LEGE('[\"a\\\\x\"]')", CentvrionError), # invalid escape ("CVM IASON\nIASON_SCRIBE()", CentvrionError), # IASON_SCRIBE no args ("CVM IASON\nIASON_SCRIBE(I, II)", CentvrionError), # IASON_SCRIBE too many args ('CVM IASON\nIASON_SCRIBE(IVNGE([I], ["v"]))', CentvrionError), # IASON_SCRIBE int dict keys ("CVM IASON\nIASON_SCRIBE(FVNCTIO (x) VT { REDI(x) })", CentvrionError), # IASON_SCRIBE function ] 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) class TestErrorLineNumbers(unittest.TestCase): def test_interpreter_error_includes_line(self): source = "DESIGNA x VT III\nDIC(y)\n" tokens = Lexer().get_lexer().lex(source) program = Parser().parse(tokens) with self.assertRaisesRegex(CentvrionError, r"at line 2"): program.eval() def test_compiled_error_includes_line(self): source = "DESIGNA x VT III\nDIC(y)\n" tokens = Lexer().get_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, _IASON_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd", "-lm"], check=True, capture_output=True, ) proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) self.assertNotEqual(proc.returncode, 0) self.assertIn("at line 2", proc.stderr) finally: os.unlink(tmp_c_path) os.unlink(tmp_bin_path)