403 lines
17 KiB
Python
403 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,
|
|
)
|
|
|
|
|
|
# --- 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)
|