208 lines
17 KiB
Python
208 lines
17 KiB
Python
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)
|
|
("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)
|
|
]
|
|
|
|
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, "-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)
|
|
self.assertIn("at line 2", proc.stderr)
|
|
finally:
|
|
os.unlink(tmp_c_path)
|
|
os.unlink(tmp_bin_path)
|