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

444 lines
16 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,
)
# --- Comments ---
comment_tests = [
# trailing line comment
('DIC("hello") // this is ignored', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"),
# line comment on its own line before code
('// ignored\nDIC("hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"),
# inline block comment
('DIC(/* ignored */ "hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"),
# block comment spanning multiple lines
('/* line one\nline two */\nDIC("hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"),
# block comment mid-expression
("II /* ignored */ + III", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"))]), ValInt(5)),
# line comment after expression (no output)
("II + III // ignored", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"))]), ValInt(5)),
# division still works (/ token not confused with //)
("X / II", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))]), ValInt(5)),
# multiple line comments
('// first\n// second\nDIC("ok")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("ok")]))]), ValStr("ok"), "ok\n"),
# comment-only line between two statements
('DESIGNA x VT I\n// set y\nDESIGNA y VT II\nx + y',
Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("II")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]),
ValInt(3)),
# blank line between two statements (double newline)
('DESIGNA x VT I\n\nDESIGNA y VT II\nx + y',
Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("II")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]),
ValInt(3)),
# multiple comment-only lines between statements
('DESIGNA x VT I\n// one\n// two\nDESIGNA y VT III\nx + y',
Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("III")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]),
ValInt(4)),
]
class TestComments(unittest.TestCase):
@parameterized.expand(comment_tests)
def test_comments(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
# --- Scope ---
scope_tests = [
# SI: variable assigned in true branch persists in outer scope
("SI VERITAS TVNC { DESIGNA r VT X }\nr",
Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("X"))], None), ExpressionStatement(ID("r"))]),
ValInt(10)),
# SI: variable assigned in ALIVD branch persists in outer scope
("SI FALSITAS TVNC { DESIGNA r VT X } ALIVD { DESIGNA r VT V }\nr",
Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("X"))], [Designa(ID("r"), Numeral("V"))]), ExpressionStatement(ID("r"))]),
ValInt(5)),
# DVM: variable assigned in body persists after loop exits
# x goes 1→2→3→4→5; r tracks x each iteration; loop exits when x==5
("DESIGNA x VT I\nDVM x EST V FAC { DESIGNA x VT x + I\nDESIGNA r VT x }\nr",
Program([], [
Designa(ID("x"), Numeral("I")),
DumStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_EST"), [
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
Designa(ID("r"), ID("x")),
]),
ExpressionStatement(ID("r")),
]),
ValInt(5)),
# PER: loop variable holds last array element after loop (no ERVMPE)
("PER i IN [I, II, III] FAC { DESIGNA nop VT I }\ni",
Program([], [
PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [Designa(ID("nop"), Numeral("I"))]),
ExpressionStatement(ID("i")),
]),
ValInt(3)),
# PER: reassigning loop var in body doesn't prevent remaining iterations from running
# cnt increments once per iteration (all 3); C=100 doesn't replace next element assignment
("DESIGNA cnt VT I\nPER i IN [I, II, III] FAC { DESIGNA i VT C\nDESIGNA cnt VT cnt + I }\ncnt",
Program([], [
Designa(ID("cnt"), Numeral("I")),
PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [
Designa(ID("i"), Numeral("C")),
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS")),
]),
ExpressionStatement(ID("cnt")),
]),
ValInt(4)),
# PER: loop var after loop reflects the last body assignment, not the last array element
# body sets i=C=100 on every iteration; after loop ends, i stays at 100
("PER i IN [I, II, III] FAC { DESIGNA i VT C }\ni",
Program([], [
PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [Designa(ID("i"), Numeral("C"))]),
ExpressionStatement(ID("i")),
]),
ValInt(100)),
# DONICVM: counter holds last range value after loop ends
# [I VSQVE IV] = [1,2,3,4]; last value assigned by loop is IV=4
("DONICVM i VT I VSQVE IV FAC { DESIGNA nop VT I }\ni",
Program([], [
PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [Designa(ID("nop"), Numeral("I"))]),
ExpressionStatement(ID("i")),
]),
ValInt(4)),
# DONICVM: reassigning counter inside body doesn't reduce the number of iterations
# range [I VSQVE IV] evaluated once; i reset each time; cnt still increments 4 times → 5
("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FAC { DESIGNA cnt VT cnt + I\nDESIGNA i VT C }\ncnt",
Program([], [
Designa(ID("cnt"), Numeral("I")),
PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS")),
Designa(ID("i"), Numeral("C")),
]),
ExpressionStatement(ID("cnt")),
]),
ValInt(5)),
# DONICVM: ERVMPE exits loop early; counter persists at break value
("DONICVM i VT I VSQVE X FAC {\nSI i EST III TVNC { ERVMPE }\n}\ni",
Program([], [
PerStatement(DataRangeArray(Numeral("I"), Numeral("X")), ID("i"), [
SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None),
]),
ExpressionStatement(ID("i")),
]),
ValInt(3)),
# Function: modifying parameter inside function does not affect outer variable of same name
# outer n=1; f receives n=5 and modifies its local copy; outer n unchanged
("DESIGNA n VT I\nDEFINI f (n) VT { DESIGNA n VT n + X\nREDI (n) }\nINVOCA f (V)\nn",
Program([], [
Designa(ID("n"), Numeral("I")),
Defini(ID("f"), [ID("n")], [Designa(ID("n"), BinOp(ID("n"), Numeral("X"), "SYMBOL_PLUS")), Redi([ID("n")])]),
ExpressionStatement(Invoca(ID("f"), [Numeral("V")])),
ExpressionStatement(ID("n")),
]),
ValInt(1)),
# Function: mutating outer variable inside function (via DESIGNA) is not visible outside
# Invoca creates func_vtable = vtable.copy(); mutations to func_vtable don't propagate back
("DESIGNA x VT I\nDEFINI f () VT { DESIGNA x VT C\nREDI (x) }\nINVOCA f ()\nx",
Program([], [
Designa(ID("x"), Numeral("I")),
Defini(ID("f"), [], [Designa(ID("x"), Numeral("C")), Redi([ID("x")])]),
ExpressionStatement(Invoca(ID("f"), [])),
ExpressionStatement(ID("x")),
]),
ValInt(1)),
# Function: two successive calls with same parameter name don't share state
("DEFINI f (n) VT { REDI (n * II) }\nINVOCA f (III) + INVOCA f (IV)",
Program([], [
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
ExpressionStatement(BinOp(Invoca(ID("f"), [Numeral("III")]), Invoca(ID("f"), [Numeral("IV")]), "SYMBOL_PLUS")),
]),
ValInt(14)),
# Function: calling f(I) with param named n does not overwrite outer n=II
# f is defined before n is designated; INVOCA creates a local copy, outer vtable unchanged
("DEFINI f (n) VT { REDI (n * II) }\nDESIGNA n VT II\nINVOCA f (I)\nn",
Program([], [
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
Designa(ID("n"), Numeral("II")),
ExpressionStatement(Invoca(ID("f"), [Numeral("I")])),
ExpressionStatement(ID("n")),
]),
ValInt(2)),
]
class TestScope(unittest.TestCase):
@parameterized.expand(scope_tests)
def test_scope(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- First-class functions / FVNCTIO ---
fvnctio_tests = [
# Lambda assigned to variable, then called
(
"DESIGNA f VT FVNCTIO (x) VT { REDI (x + I) }\nINVOCA f (V)",
Program([], [
Designa(ID("f"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])),
ExpressionStatement(Invoca(ID("f"), [Numeral("V")])),
]),
ValInt(6),
),
# IIFE: immediately invoked lambda
(
"INVOCA FVNCTIO (x) VT { REDI (x * II) } (III)",
Program([], [
ExpressionStatement(Invoca(
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]),
[Numeral("III")],
)),
]),
ValInt(6),
),
# Zero-arg lambda
(
"INVOCA FVNCTIO () VT { REDI (XLII) } ()",
Program([], [
ExpressionStatement(Invoca(
Fvnctio([], [Redi([Numeral("XLII")])]),
[],
)),
]),
ValInt(42),
),
# Function passed as argument
(
"DEFINI apply (f, x) VT { REDI (INVOCA f (x)) }\n"
"DESIGNA dbl VT FVNCTIO (n) VT { REDI (n * II) }\n"
"INVOCA apply (dbl, V)",
Program([], [
Defini(ID("apply"), [ID("f"), ID("x")], [
Redi([Invoca(ID("f"), [ID("x")])])
]),
Designa(ID("dbl"), Fvnctio([ID("n")], [
Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])
])),
ExpressionStatement(Invoca(ID("apply"), [ID("dbl"), Numeral("V")])),
]),
ValInt(10),
),
# Lambda uses caller-scope variable (copy-caller semantics)
(
"DESIGNA n VT III\n"
"DESIGNA f VT FVNCTIO (x) VT { REDI (x + n) }\n"
"INVOCA f (V)",
Program([], [
Designa(ID("n"), Numeral("III")),
Designa(ID("f"), Fvnctio([ID("x")], [
Redi([BinOp(ID("x"), ID("n"), "SYMBOL_PLUS")])
])),
ExpressionStatement(Invoca(ID("f"), [Numeral("V")])),
]),
ValInt(8),
),
# Named function passed as value
(
"DEFINI sqr (x) VT { REDI (x * x) }\n"
"DESIGNA f VT sqr\n"
"INVOCA f (IV)",
Program([], [
Defini(ID("sqr"), [ID("x")], [Redi([BinOp(ID("x"), ID("x"), "SYMBOL_TIMES")])]),
Designa(ID("f"), ID("sqr")),
ExpressionStatement(Invoca(ID("f"), [Numeral("IV")])),
]),
ValInt(16),
),
# Nested lambdas
(
"INVOCA FVNCTIO (x) VT { REDI (INVOCA FVNCTIO (y) VT { REDI (y + I) } (x)) } (V)",
Program([], [
ExpressionStatement(Invoca(
Fvnctio([ID("x")], [
Redi([Invoca(
Fvnctio([ID("y")], [Redi([BinOp(ID("y"), Numeral("I"), "SYMBOL_PLUS")])]),
[ID("x")],
)])
]),
[Numeral("V")],
)),
]),
ValInt(6),
),
# DIC on a function value
(
"DESIGNA f VT FVNCTIO (x) VT { REDI (x) }\nDIC(f)",
Program([], [
Designa(ID("f"), Fvnctio([ID("x")], [Redi([ID("x")])])),
ExpressionStatement(BuiltIn("DIC", [ID("f")])),
]),
ValStr("FVNCTIO"),
"FVNCTIO\n",
),
# Lambda stored in array, called via index
(
"DESIGNA fns VT [FVNCTIO (x) VT { REDI (x + I) }, FVNCTIO (x) VT { REDI (x * II) }]\n"
"INVOCA fns[I] (V)",
Program([], [
Designa(ID("fns"), DataArray([
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])]),
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]),
])),
ExpressionStatement(Invoca(
ArrayIndex(ID("fns"), Numeral("I")),
[Numeral("V")],
)),
]),
ValInt(6),
),
# Lambda stored in dict, called via key
(
'DESIGNA d VT TABVLA {"add" VT FVNCTIO (x) VT { REDI (x + I) }}\n'
'INVOCA d["add"] (V)',
Program([], [
Designa(ID("d"), DataDict([
(String("add"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])),
])),
ExpressionStatement(Invoca(
ArrayIndex(ID("d"), String("add")),
[Numeral("V")],
)),
]),
ValInt(6),
),
# Multi-param lambda
(
"DESIGNA add VT FVNCTIO (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)",
Program([], [
Designa(ID("add"), Fvnctio([ID("a"), ID("b")], [
Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")])
])),
ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])),
]),
ValInt(7),
),
]
class TestFvnctio(unittest.TestCase):
@parameterized.expand(fvnctio_tests)
def test_fvnctio(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
# --- Tempta/Cape (try/catch) ---
tempta_tests = [
# Try block succeeds — catch not entered
(
"TEMPTA {\nDESIGNA r VT I\n} CAPE e {\nDESIGNA r VT II\n}\nr",
Program([], [
TemptaStatement(
[Designa(ID("r"), Numeral("I"))],
ID("e"),
[Designa(ID("r"), Numeral("II"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(1),
),
# Try block errors — caught by catch
(
"TEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT II\n}\nr",
Program([], [
TemptaStatement(
[Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[Designa(ID("r"), Numeral("II"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(2),
),
# Error variable contains the error message
(
'DESIGNA e VT NVLLVS\nTEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nNVLLVS\n}\ne',
Program([], [
Designa(ID("e"), Nullus()),
TemptaStatement(
[Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[ExpressionStatement(Nullus())],
),
ExpressionStatement(ID("e")),
]),
ValStr("Division by zero"),
),
# Nested tempta — inner catches, outer unaffected
(
"DESIGNA r VT NVLLVS\nTEMPTA {\nTEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT I\n}\n} CAPE e {\nDESIGNA r VT II\n}\nr",
Program([], [
Designa(ID("r"), Nullus()),
TemptaStatement(
[TemptaStatement(
[Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[Designa(ID("r"), Numeral("I"))],
)],
ID("e"),
[Designa(ID("r"), Numeral("II"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(1),
),
# REDI inside catch block
(
"DEFINI f () VT {\nTEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nREDI (III)\n}\nREDI (IV)\n}\nINVOCA f ()",
Program([], [
Defini(ID("f"), [], [
TemptaStatement(
[Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[Redi([Numeral("III")])],
),
Redi([Numeral("IV")]),
]),
ExpressionStatement(Invoca(ID("f"), [])),
]),
ValInt(3),
),
# ERVMPE inside catch block (inside a loop)
(
"DESIGNA r VT NVLLVS\nDVM r EST I FAC {\nTEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT I\nERVMPE\n}\n}\nr",
Program([], [
Designa(ID("r"), Nullus()),
DumStatement(
BinOp(ID("r"), Numeral("I"), "KEYWORD_EST"),
[TemptaStatement(
[Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[Designa(ID("r"), Numeral("I")), Erumpe()],
)],
),
ExpressionStatement(ID("r")),
]),
ValInt(1),
),
# Statement after error in try block is not executed
(
"DESIGNA r VT NVLLVS\nTEMPTA {\nDESIGNA x VT I / NVLLVS\nDESIGNA r VT III\n} CAPE e {\nDESIGNA r VT II\n}\nr",
Program([], [
Designa(ID("r"), Nullus()),
TemptaStatement(
[Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE")),
Designa(ID("r"), Numeral("III"))],
ID("e"),
[Designa(ID("r"), Numeral("II"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(2),
),
]
class TestTempta(unittest.TestCase):
@parameterized.expand(tempta_tests)
def test_tempta(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)