Files
centvrion/tests/05_test_literals.py
2026-04-24 19:13:48 +02:00

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)