1891 lines
87 KiB
Python
1891 lines
87 KiB
Python
import os
|
||
import random
|
||
import subprocess
|
||
import tempfile
|
||
import unittest
|
||
from io import StringIO
|
||
from unittest.mock import patch
|
||
from parameterized import parameterized
|
||
|
||
from fractions import Fraction
|
||
|
||
from centvrion.ast_nodes import (
|
||
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini,
|
||
Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, Erumpe,
|
||
ExpressionStatement, ID, InterpolatedString, Invoca, ModuleCall, Nullus,
|
||
Numeral, PerStatement, Program, Redi, SiStatement, String, UnaryMinus,
|
||
UnaryNot, Fractio, frac_to_fraction, fraction_to_frac,
|
||
num_to_int, int_to_num, make_string,
|
||
)
|
||
from centvrion.compiler.emitter import compile_program
|
||
from centvrion.errors import CentvrionError
|
||
from centvrion.lexer import Lexer
|
||
from centvrion.parser import Parser
|
||
from centvrion.values import ValInt, ValStr, ValBool, ValList, ValNul, ValFunc, ValFrac
|
||
|
||
_RUNTIME_C = os.path.join(
|
||
os.path.dirname(__file__),
|
||
"centvrion", "compiler", "runtime", "cent_runtime.c"
|
||
)
|
||
|
||
def run_test(self, source, target_nodes, target_value, target_output="", input_lines=[]):
|
||
random.seed(1)
|
||
|
||
lexer = Lexer().get_lexer()
|
||
tokens = lexer.lex(source + "\n")
|
||
program = Parser().parse(tokens)
|
||
|
||
##########################
|
||
####### Parser Test ######
|
||
##########################
|
||
if target_nodes is not None:
|
||
self.assertEqual(
|
||
program,
|
||
target_nodes,
|
||
f"Parser test:\n{program}\n{target_nodes}"
|
||
)
|
||
|
||
##########################
|
||
#### Interpreter Test ####
|
||
##########################
|
||
captured = StringIO()
|
||
try:
|
||
if input_lines:
|
||
inputs = iter(input_lines)
|
||
with patch("builtins.input", lambda: next(inputs)), patch("sys.stdout", captured):
|
||
result = program.eval()
|
||
else:
|
||
with patch("sys.stdout", captured):
|
||
result = program.eval()
|
||
except Exception as e:
|
||
raise e
|
||
|
||
self.assertEqual(result, target_value, "Return value test")
|
||
self.assertEqual(captured.getvalue(), target_output, "Output test")
|
||
|
||
##########################
|
||
###### Printer Test ######
|
||
##########################
|
||
try:
|
||
new_text = program.print()
|
||
new_tokens = Lexer().get_lexer().lex(new_text + "\n")
|
||
new_nodes = Parser().parse(new_tokens)
|
||
except Exception as e:
|
||
raise Exception(f"###Printer test###\n{new_text}") from e
|
||
self.assertEqual(
|
||
program,
|
||
new_nodes,
|
||
f"Printer test\n{source}\n{new_text}"
|
||
)
|
||
|
||
##########################
|
||
###### Compiler Test #####
|
||
##########################
|
||
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],
|
||
check=True, capture_output=True,
|
||
)
|
||
stdin_data = "".join(f"{l}\n" for l in input_lines)
|
||
proc = subprocess.run(
|
||
[tmp_bin_path],
|
||
input=stdin_data, capture_output=True, text=True,
|
||
)
|
||
self.assertEqual(proc.returncode, 0, f"Compiler binary exited non-zero:\n{proc.stderr}")
|
||
self.assertEqual(proc.stdout, target_output, "Compiler output test")
|
||
finally:
|
||
os.unlink(tmp_c_path)
|
||
os.unlink(tmp_bin_path)
|
||
|
||
assert target_nodes is not None, "All tests must have target nodes"
|
||
|
||
|
||
# --- Output ---
|
||
|
||
output_tests = [
|
||
("DICE(\"hello\")", Program([], [ExpressionStatement(BuiltIn("DICE", [String("hello")]))]), ValStr("hello"), "hello\n"),
|
||
("DICE(\"world\")", Program([], [ExpressionStatement(BuiltIn("DICE", [String("world")]))]), ValStr("world"), "world\n"),
|
||
("DICE(III)", Program([], [ExpressionStatement(BuiltIn("DICE", [Numeral("III")]))]), ValStr("III"), "III\n"),
|
||
("DICE(X)", Program([], [ExpressionStatement(BuiltIn("DICE", [Numeral("X")]))]), ValStr("X"), "X\n"),
|
||
("DICE(MMXXV)", Program([], [ExpressionStatement(BuiltIn("DICE", [Numeral("MMXXV")]))]), ValStr("MMXXV"), "MMXXV\n"),
|
||
("DICE('hello')", Program([], [ExpressionStatement(BuiltIn("DICE", [String("hello")]))]), ValStr("hello"), "hello\n"),
|
||
("DICE('world')", Program([], [ExpressionStatement(BuiltIn("DICE", [String("world")]))]), ValStr("world"), "world\n"),
|
||
("DICE(\"a\", \"b\")", Program([], [ExpressionStatement(BuiltIn("DICE", [String("a"), String("b")]))]), ValStr("a b"), "a b\n"),
|
||
("DICE(\"line one\")\nDICE(\"line two\")", Program([], [ExpressionStatement(BuiltIn("DICE", [String("line one")])), ExpressionStatement(BuiltIn("DICE", [String("line two")]))]), ValStr("line two"), "line one\nline two\n"),
|
||
("DICE(DICE(II))", Program([], [ExpressionStatement(BuiltIn("DICE", [BuiltIn("DICE", [Numeral("II")])]))]), ValStr("II"), "II\nII\n"),
|
||
("EVERRO()", Program([], [ExpressionStatement(BuiltIn("EVERRO", []))]), ValNul(), "\033[2J\033[H"),
|
||
]
|
||
|
||
class TestOutput(unittest.TestCase):
|
||
@parameterized.expand(output_tests)
|
||
def test_output(self, source, nodes, value, output):
|
||
run_test(self, source, nodes, value, output)
|
||
|
||
|
||
# --- Arithmetic ---
|
||
|
||
arithmetic_tests = [
|
||
("I + I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"))]), ValInt(2)),
|
||
("X - III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_MINUS"))]), ValInt(7)),
|
||
("III * IV", Program([], [ExpressionStatement(BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(12)),
|
||
("X / II", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))]), ValInt(5)),
|
||
("X / III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(3)), # integer division: 10 // 3 = 3
|
||
("X RELIQVVM III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(1)), # 10 % 3 = 1
|
||
("IX RELIQVVM III", Program([], [ExpressionStatement(BinOp(Numeral("IX"), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(0)), # exact divisor
|
||
("VII RELIQVVM X", Program([], [ExpressionStatement(BinOp(Numeral("VII"), Numeral("X"), "KEYWORD_RELIQVVM"))]), ValInt(7)), # dividend < divisor
|
||
("II + III * IV", Program([], [ExpressionStatement(BinOp(Numeral("II"), BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"), "SYMBOL_PLUS"))]), ValInt(14)), # precedence: 2 + (3*4) = 14
|
||
("(II + III) * IV", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(20)), # parens: (2+3)*4 = 20
|
||
("CVM SVBNVLLA\n- III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(Numeral("III")))]), ValInt(-3)), # unary negation
|
||
("CVM SVBNVLLA\n- (II + III)", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")))]), ValInt(-5)), # unary negation of expression
|
||
("CVM SVBNVLLA\n- - II", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(UnaryMinus(Numeral("II"))))]), ValInt(2)), # double negation
|
||
("CVM SVBNVLLA\nIII + - II", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("III"), UnaryMinus(Numeral("II")), "SYMBOL_PLUS"))]), ValInt(1)), # unary in binary context
|
||
]
|
||
|
||
class TestArithmetic(unittest.TestCase):
|
||
@parameterized.expand(arithmetic_tests)
|
||
def test_arithmetic(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Precedence and associativity ---
|
||
#
|
||
# Precedence (lowest → highest):
|
||
# AVT < ET < (EST, DISPAR, PLVS, MINVS) < (+ -) < (* / RELIQVVM) < UMINUS < INDEX
|
||
|
||
precedence_tests = [
|
||
# * binds tighter than -: 10 - (2*3) = 4, not (10-2)*3 = 24
|
||
("X - II * III",
|
||
Program([], [ExpressionStatement(BinOp(Numeral("X"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), "SYMBOL_MINUS"))]),
|
||
ValInt(4)),
|
||
# / binds tighter than +: (10/2) + 3 = 8, not 10/(2+3) = 2
|
||
("X / II + III",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_PLUS"))]),
|
||
ValInt(8)),
|
||
# + binds tighter than EST: (2+3)==5 = True, not 2+(3==5) = type error
|
||
("II + III EST V",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("V"), "KEYWORD_EST"))]),
|
||
ValBool(True)),
|
||
# * binds tighter than PLVS: (2*3)>4 = True
|
||
("II * III PLVS IV",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), Numeral("IV"), "KEYWORD_PLVS"))]),
|
||
ValBool(True)),
|
||
# comparison binds tighter than ET: (1==2) AND (2==2) = False AND True = False
|
||
("I EST II ET II EST II",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]),
|
||
ValBool(False)),
|
||
# + binds tighter than DISPAR: (2+3)!=5 = False, not 2+(3!=5) = type error
|
||
("II + III DISPAR V",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("V"), "KEYWORD_DISPAR"))]),
|
||
ValBool(False)),
|
||
# DISPAR binds tighter than ET: (1!=2) AND (2!=2) = True AND False = False
|
||
("I DISPAR II ET II DISPAR II",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_DISPAR"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_DISPAR"), "KEYWORD_ET"))]),
|
||
ValBool(False)),
|
||
# ET binds tighter than AVT: True OR (False AND False) = True
|
||
("VERITAS AVT FALSITAS ET FALSITAS",
|
||
Program([], [ExpressionStatement(BinOp(Bool(True), BinOp(Bool(False), Bool(False), "KEYWORD_ET"), "KEYWORD_AVT"))]),
|
||
ValBool(True)),
|
||
# UMINUS binds tighter than *: (-2)*3 = -6, not -(2*3) = -6 (same value, different tree)
|
||
("CVM SVBNVLLA\n- II * III",
|
||
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_TIMES"))]),
|
||
ValInt(-6)),
|
||
# UMINUS binds tighter than +: (-2)+3 = 1, not -(2+3) = -5
|
||
("CVM SVBNVLLA\n- II + III",
|
||
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_PLUS"))]),
|
||
ValInt(1)),
|
||
# INDEX binds tighter than UMINUS: -(arr[I]) = -1
|
||
("CVM SVBNVLLA\n- [I, II, III][I]",
|
||
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I"))))]),
|
||
ValInt(-1)),
|
||
# INDEX binds tighter than NON: NON (arr[I]) = NON VERITAS = False
|
||
("NON [VERITAS, FALSITAS][I]",
|
||
Program([], [ExpressionStatement(UnaryNot(ArrayIndex(DataArray([Bool(True), Bool(False)]), Numeral("I"))))]),
|
||
ValBool(False)),
|
||
# INDEX binds tighter than +: (arr[II]) + X = 2 + 10 = 12
|
||
("[I, II, III][II] + X",
|
||
Program([], [ExpressionStatement(BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")), Numeral("X"), "SYMBOL_PLUS"))]),
|
||
ValInt(12)),
|
||
# left-associativity of -: (10-3)-2 = 5, not 10-(3-2) = 9
|
||
("X - III - II",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_MINUS"), Numeral("II"), "SYMBOL_MINUS"))]),
|
||
ValInt(5)),
|
||
# left-associativity of /: (12/2)/3 = 2, not 12/(2/3) = 18
|
||
("XII / II / III",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("XII"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_DIVIDE"))]),
|
||
ValInt(2)),
|
||
# RELIQVVM same precedence as *, /; left-associative: (17 % 5) % 2 = 0
|
||
("XVII RELIQVVM V RELIQVVM II",
|
||
Program([], [ExpressionStatement(
|
||
BinOp(BinOp(Numeral("XVII"), Numeral("V"), "KEYWORD_RELIQVVM"),
|
||
Numeral("II"), "KEYWORD_RELIQVVM"))]),
|
||
ValInt(0)),
|
||
# RELIQVVM binds tighter than +: 2 + (7 % 3) = 3, not (2+7) % 3 = 0
|
||
("II + VII RELIQVVM III",
|
||
Program([], [ExpressionStatement(
|
||
BinOp(Numeral("II"),
|
||
BinOp(Numeral("VII"), Numeral("III"), "KEYWORD_RELIQVVM"),
|
||
"SYMBOL_PLUS"))]),
|
||
ValInt(3)),
|
||
# left-associativity of AVT: (False OR True) OR False = True
|
||
("FALSITAS AVT VERITAS AVT FALSITAS",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"), Bool(False), "KEYWORD_AVT"))]),
|
||
ValBool(True)),
|
||
]
|
||
|
||
class TestPrecedence(unittest.TestCase):
|
||
@parameterized.expand(precedence_tests)
|
||
def test_precedence(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Assignment ---
|
||
|
||
assignment_tests = [
|
||
("DESIGNA x VT III\nx",
|
||
Program([], [Designa(ID("x"), Numeral("III")), ExpressionStatement(ID("x"))]),
|
||
ValInt(3)),
|
||
("DESIGNA msg VT \"hello\"\nmsg",
|
||
Program([], [Designa(ID("msg"), String("hello")), ExpressionStatement(ID("msg"))]),
|
||
ValStr("hello")),
|
||
("DESIGNA msg VT 'hello'\nmsg",
|
||
Program([], [Designa(ID("msg"), String("hello")), ExpressionStatement(ID("msg"))]),
|
||
ValStr("hello")),
|
||
("DESIGNA a VT V\nDESIGNA b VT X\na + b",
|
||
Program([], [Designa(ID("a"), Numeral("V")), Designa(ID("b"), Numeral("X")),
|
||
ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"))]),
|
||
ValInt(15)),
|
||
("DESIGNA x VT II\nDESIGNA x VT x + I\nx",
|
||
Program([], [Designa(ID("x"), Numeral("II")),
|
||
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
|
||
ExpressionStatement(ID("x"))]),
|
||
ValInt(3)),
|
||
# Compound assignment — AVGE (+=)
|
||
("DESIGNA x VT V\nx AVGE III\nx",
|
||
Program([], [Designa(ID("x"), Numeral("V")),
|
||
Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_PLUS")),
|
||
ExpressionStatement(ID("x"))]),
|
||
ValInt(8)),
|
||
# Compound assignment — MINVE (-=)
|
||
("DESIGNA x VT X\nx MINVE III\nx",
|
||
Program([], [Designa(ID("x"), Numeral("X")),
|
||
Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_MINUS")),
|
||
ExpressionStatement(ID("x"))]),
|
||
ValInt(7)),
|
||
# AVGE with complex expression
|
||
("DESIGNA x VT I\nx AVGE II + III\nx",
|
||
Program([], [Designa(ID("x"), Numeral("I")),
|
||
Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), "SYMBOL_PLUS")),
|
||
ExpressionStatement(ID("x"))]),
|
||
ValInt(6)),
|
||
# AVGE inside a loop (DONICVM range is exclusive of upper bound: I VSQVE III = [1, 2])
|
||
("DESIGNA s VT NVLLVS\nDONICVM i VT I VSQVE III FACE {\ns AVGE i\n}\ns",
|
||
Program([], [Designa(ID("s"), Nullus()),
|
||
PerStatement(DataRangeArray(Numeral("I"), Numeral("III")), ID("i"),
|
||
[Designa(ID("s"), BinOp(ID("s"), ID("i"), "SYMBOL_PLUS"))]),
|
||
ExpressionStatement(ID("s"))]),
|
||
ValInt(3)),
|
||
]
|
||
|
||
class TestAssignment(unittest.TestCase):
|
||
@parameterized.expand(assignment_tests)
|
||
def test_assignment(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Destructuring ---
|
||
|
||
destructuring_tests = [
|
||
# basic: unpack multi-return function
|
||
(
|
||
"DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (III, VII)\nx + y",
|
||
Program([], [
|
||
Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]),
|
||
DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("III"), Numeral("VII")])),
|
||
ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS")),
|
||
]),
|
||
ValInt(10),
|
||
),
|
||
# unpack array literal
|
||
(
|
||
"DESIGNA a, b VT [I, II]\na + b",
|
||
Program([], [
|
||
DesignaDestructure([ID("a"), ID("b")], DataArray([Numeral("I"), Numeral("II")])),
|
||
ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")),
|
||
]),
|
||
ValInt(3),
|
||
),
|
||
# three variables
|
||
(
|
||
"DESIGNA a, b, c VT [X, XX, XXX]\na + b + c",
|
||
Program([], [
|
||
DesignaDestructure([ID("a"), ID("b"), ID("c")], DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])),
|
||
ExpressionStatement(BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")),
|
||
]),
|
||
ValInt(60),
|
||
),
|
||
# destructure into individual use
|
||
(
|
||
"DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (V, II)\nDICE(x)\nDICE(y)",
|
||
Program([], [
|
||
Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]),
|
||
DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("V"), Numeral("II")])),
|
||
ExpressionStatement(BuiltIn("DICE", [ID("x")])),
|
||
ExpressionStatement(BuiltIn("DICE", [ID("y")])),
|
||
]),
|
||
ValStr("II"),
|
||
"V\nII\n",
|
||
),
|
||
]
|
||
|
||
class TestDestructuring(unittest.TestCase):
|
||
@parameterized.expand(destructuring_tests)
|
||
def test_destructuring(self, source, nodes, value, output=""):
|
||
run_test(self, source, nodes, value, output)
|
||
|
||
|
||
# --- Control flow ---
|
||
|
||
control_tests = [
|
||
# SI without ALVID — true branch
|
||
("SI VERITAS TVNC { DESIGNA r VT I }\nr",
|
||
Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("I"))], None), ExpressionStatement(ID("r"))]),
|
||
ValInt(1)),
|
||
# SI without ALVID — false branch
|
||
("SI FALSITAS TVNC { DESIGNA r VT I }",
|
||
Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("I"))], None)]),
|
||
ValNul()),
|
||
# SI with ALVID — true branch
|
||
("SI VERITAS TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(1)),
|
||
# SI with ALVID — false branch
|
||
("SI FALSITAS TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(2)),
|
||
# SI with comparison — equal
|
||
("SI I EST I TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(1)),
|
||
# SI with comparison — unequal
|
||
("SI I EST II TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(2)),
|
||
# SI MINVS
|
||
("SI I MINVS II TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_MINVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(1)),
|
||
# SI PLVS
|
||
("SI II PLVS I TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(1)),
|
||
# ALVID SI chain
|
||
(
|
||
"SI I EST II TVNC { DESIGNA r VT I } ALVID SI I EST I TVNC { DESIGNA r VT II } ALVID { DESIGNA r VT III }\nr",
|
||
Program([], [
|
||
SiStatement(
|
||
BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"),
|
||
[Designa(ID("r"), Numeral("I"))],
|
||
[SiStatement(
|
||
BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"),
|
||
[Designa(ID("r"), Numeral("II"))],
|
||
[Designa(ID("r"), Numeral("III"))],
|
||
)],
|
||
),
|
||
ExpressionStatement(ID("r")),
|
||
]),
|
||
ValInt(2),
|
||
),
|
||
# DVM (while not): loops until condition is true
|
||
(
|
||
"DESIGNA x VT I\nDVM x EST III FACE {\nDESIGNA x VT x + I\n}\nx",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]),
|
||
ExpressionStatement(ID("x")),
|
||
]),
|
||
ValInt(3),
|
||
),
|
||
# DVM with ERVMPE — loop body prints (testing DICE + ERVMPE together)
|
||
("DESIGNA x VT I\nDVM FALSITAS FACE {\nDICE(x)\nERVMPE\n}",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DICE", [ID("x")])), Erumpe()]),
|
||
]),
|
||
ValStr("I"), "I\n"),
|
||
# AETERNVM is sugar for DVM FALSITAS — must produce the same AST.
|
||
("DESIGNA x VT I\nAETERNVM FACE {\nDICE(x)\nERVMPE\n}",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DICE", [ID("x")])), Erumpe()]),
|
||
]),
|
||
ValStr("I"), "I\n"),
|
||
# AETERNVM with counter + ERVMPE on condition
|
||
("DESIGNA x VT I\nAETERNVM FACE {\nSI x EST III TVNC { ERVMPE }\nDESIGNA x VT x + I\n}\nx",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(Bool(False), [
|
||
SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None),
|
||
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
|
||
]),
|
||
ExpressionStatement(ID("x")),
|
||
]),
|
||
ValInt(3)),
|
||
# AETERNVM with CONTINVA — skip printing III; ERVMPE after V.
|
||
# Return value is ValNul because the iteration that triggers ERVMPE runs
|
||
# Designa first (resetting last_val); we test on output, which is the point.
|
||
("DESIGNA x VT NVLLVS\nAETERNVM FACE {\nDESIGNA x VT x + I\nSI x PLVS V TVNC { ERVMPE }\nSI x EST III TVNC { CONTINVA }\nDICE(x)\n}",
|
||
Program([], [
|
||
Designa(ID("x"), Nullus()),
|
||
DumStatement(Bool(False), [
|
||
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
|
||
SiStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_PLVS"), [Erumpe()], None),
|
||
SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Continva()], None),
|
||
ExpressionStatement(BuiltIn("DICE", [ID("x")])),
|
||
]),
|
||
]),
|
||
ValNul(), "I\nII\nIV\nV\n"),
|
||
# REDI inside AETERNVM (inside DEFINI) — exits both loop and function
|
||
(
|
||
"DEFINI f () VT {\nDESIGNA x VT I\nAETERNVM FACE {\nREDI (x)\n}\n}\nINVOCA f ()",
|
||
Program([], [
|
||
Defini(ID("f"), [], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(Bool(False), [Redi([ID("x")])]),
|
||
]),
|
||
ExpressionStatement(Invoca(ID("f"), [])),
|
||
]),
|
||
ValInt(1),
|
||
),
|
||
# PER foreach
|
||
("PER i IN [I, II, III] FACE { DICE(i) }",
|
||
Program([], [PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
||
ValStr("III"), "I\nII\nIII\n"),
|
||
# DONICVM range loop
|
||
("DONICVM i VT I VSQVE V FACE { DICE(i) }",
|
||
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
||
ValStr("IV"), "I\nII\nIII\nIV\n"),
|
||
]
|
||
|
||
class TestControl(unittest.TestCase):
|
||
@parameterized.expand(control_tests)
|
||
def test_control(self, source, nodes, value, output=""):
|
||
run_test(self, source, nodes, value, output)
|
||
|
||
|
||
# --- Functions ---
|
||
|
||
function_tests = [
|
||
(
|
||
"DEFINI bis (n) VT { REDI (n * II) }\nINVOCA bis (III)",
|
||
Program([], [
|
||
Defini(ID("bis"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||
ExpressionStatement(Invoca(ID("bis"), [Numeral("III")])),
|
||
]),
|
||
ValInt(6),
|
||
),
|
||
(
|
||
"DEFINI add (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)",
|
||
Program([], [
|
||
Defini(ID("add"), [ID("a"), ID("b")], [Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")])]),
|
||
ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])),
|
||
]),
|
||
ValInt(7),
|
||
),
|
||
# Fibonacci: fib(n<3)=1, fib(n)=fib(n-1)+fib(n-2)
|
||
(
|
||
"DEFINI fib (n) VT {\nSI n MINVS III TVNC { REDI (I) } ALVID { REDI (INVOCA fib (n - I) + INVOCA fib (n - II)) }\n}\nINVOCA fib (VII)",
|
||
Program([], [
|
||
Defini(ID("fib"), [ID("n")], [
|
||
SiStatement(
|
||
BinOp(ID("n"), Numeral("III"), "KEYWORD_MINVS"),
|
||
[Redi([Numeral("I")])],
|
||
[Redi([BinOp(
|
||
Invoca(ID("fib"), [BinOp(ID("n"), Numeral("I"), "SYMBOL_MINUS")]),
|
||
Invoca(ID("fib"), [BinOp(ID("n"), Numeral("II"), "SYMBOL_MINUS")]),
|
||
"SYMBOL_PLUS",
|
||
)])],
|
||
),
|
||
]),
|
||
ExpressionStatement(Invoca(ID("fib"), [Numeral("VII")])),
|
||
]),
|
||
ValInt(13),
|
||
),
|
||
]
|
||
|
||
class TestFunctions(unittest.TestCase):
|
||
@parameterized.expand(function_tests)
|
||
def test_functions(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Builtins ---
|
||
|
||
builtin_tests = [
|
||
("AVDI_NVMERVS()", Program([], [ExpressionStatement(BuiltIn("AVDI_NVMERVS", []))]), ValInt(3), "", ["III"]),
|
||
("AVDI_NVMERVS()", Program([], [ExpressionStatement(BuiltIn("AVDI_NVMERVS", []))]), ValInt(10), "", ["X"]),
|
||
("CVM FORS\nFORTIS_NVMERVS(I, X)", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("FORTIS_NVMERVS", [Numeral("I"), Numeral("X")]))]), ValInt(3)),
|
||
("AVDI()", Program([], [ExpressionStatement(BuiltIn("AVDI", []))]), ValStr("hello"), "", ["hello"]),
|
||
("LONGITVDO([I, II, III])", Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValInt(3)),
|
||
("LONGITVDO([])", Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataArray([])]))]), ValInt(0)),
|
||
('LONGITVDO("salve")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("salve")]))]), ValInt(5)),
|
||
('LONGITVDO("")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("")]))]), ValInt(0)),
|
||
("CVM FORS\nFORTIS_ELECTIONIS([I, II, III])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("FORTIS_ELECTIONIS", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValInt(1)),
|
||
("CVM FORS\nSEMEN(XLII)\nFORTIS_NVMERVS(I, C)", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("FORTIS_NVMERVS", [Numeral("I"), Numeral("C")]))]), ValInt(82)),
|
||
# DECIMATIO: seed 42, 10 elements → removes 1 (element II)
|
||
("CVM FORS\nSEMEN(XLII)\nDECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X")])]))]), ValList([ValInt(1), ValInt(3), ValInt(4), ValInt(5), ValInt(6), ValInt(7), ValInt(8), ValInt(9), ValInt(10)])),
|
||
# DECIMATIO: seed 1, 3 elements → 3//10=0, nothing removed
|
||
("CVM FORS\nSEMEN(I)\nDECIMATIO([I, II, III])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("I")])), ExpressionStatement(BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])),
|
||
# DECIMATIO: empty array → empty array
|
||
("CVM FORS\nDECIMATIO([])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("DECIMATIO", [DataArray([])]))]), ValList([])),
|
||
# DECIMATIO: seed 42, 20 elements → removes 2 (elements I and IV)
|
||
("CVM FORS\nSEMEN(XLII)\nDECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X"), Numeral("XI"), Numeral("XII"), Numeral("XIII"), Numeral("XIV"), Numeral("XV"), Numeral("XVI"), Numeral("XVII"), Numeral("XVIII"), Numeral("XIX"), Numeral("XX")])]))]), ValList([ValInt(2), ValInt(3), ValInt(5), ValInt(6), ValInt(7), ValInt(8), ValInt(9), ValInt(10), ValInt(11), ValInt(12), ValInt(13), ValInt(14), ValInt(15), ValInt(16), ValInt(17), ValInt(18), ValInt(19), ValInt(20)])),
|
||
# SENATVS: majority true → VERITAS
|
||
("SENATVS(VERITAS, VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False)]))]), ValBool(True)),
|
||
# SENATVS: majority false → FALSITAS
|
||
("SENATVS(FALSITAS, FALSITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False), Bool(False), Bool(True)]))]), ValBool(False)),
|
||
# SENATVS: tie → FALSITAS
|
||
("SENATVS(VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(False)]))]), ValBool(False)),
|
||
# SENATVS: 4-arg tie → FALSITAS
|
||
("SENATVS(VERITAS, VERITAS, FALSITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False), Bool(False)]))]), ValBool(False)),
|
||
# SENATVS: single true → VERITAS
|
||
("SENATVS(VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True)]))]), ValBool(True)),
|
||
# SENATVS: single false → FALSITAS
|
||
("SENATVS(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False)]))]), ValBool(False)),
|
||
# SENATVS: empty → FALSITAS (vacuous)
|
||
("SENATVS()", Program([], [ExpressionStatement(BuiltIn("SENATVS", []))]), ValBool(False)),
|
||
# SENATVS: all true <20><> VERITAS
|
||
("SENATVS(VERITAS, VERITAS, VERITAS, VERITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(True), Bool(True), Bool(True)]))]), ValBool(True)),
|
||
# SENATVS: array input, majority true → VERITAS
|
||
("SENATVS([VERITAS, VERITAS, FALSITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(True), Bool(True), Bool(False)])]))]), ValBool(True)),
|
||
# SENATVS: array input, majority false → FALSITAS
|
||
("SENATVS([FALSITAS, FALSITAS, VERITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(False), Bool(False), Bool(True)])]))]), ValBool(False)),
|
||
# SENATVS: array input, empty → FALSITAS
|
||
("SENATVS([])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([])]))]), ValBool(False)),
|
||
]
|
||
|
||
class TestBuiltins(unittest.TestCase):
|
||
@parameterized.expand(builtin_tests)
|
||
def test_builtins(self, source, nodes, value, output="", input_lines=[]):
|
||
run_test(self, source, nodes, value, output, input_lines)
|
||
|
||
|
||
# --- 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
|
||
("DICE(M + M + M + M)", CentvrionError), # output > 3999 without MAGNVM
|
||
("IIII", CentvrionError), # invalid Roman numeral in source
|
||
("FORTIS_NVMERVS(I, X)", CentvrionError), # requires FORS module
|
||
("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
|
||
('"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)
|
||
("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
|
||
("I[I]", CentvrionError), # indexing a non-array
|
||
("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
|
||
("FORTIS_ELECTIONIS([])", CentvrionError), # FORS required for FORTIS_ELECTIONIS
|
||
("CVM FORS\nFORTIS_ELECTIONIS([])", CentvrionError), # FORTIS_ELECTIONIS on empty array
|
||
("CVM FORS\nFORTIS_NVMERVS(X, I)", CentvrionError), # FORTIS_NVMERVS a > b
|
||
("PER i IN I FACE { DICE(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
|
||
("SENATVS(I)", CentvrionError), # SENATVS requires booleans
|
||
("SENATVS(VERITAS, I)", CentvrionError), # SENATVS mixed types
|
||
("SENATVS([I, II, III])", CentvrionError), # SENATVS array of non-bools
|
||
("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 FACE {\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
|
||
]
|
||
|
||
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)
|
||
|
||
|
||
def run_compiler_error_test(self, source):
|
||
lexer = Lexer().get_lexer()
|
||
tokens = lexer.lex(source + "\n")
|
||
program = Parser().parse(tokens)
|
||
try:
|
||
c_source = compile_program(program)
|
||
except CentvrionError:
|
||
return # compile-time detection is valid
|
||
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],
|
||
check=True, capture_output=True,
|
||
)
|
||
proc = subprocess.run([tmp_bin_path], capture_output=True, text=True)
|
||
self.assertNotEqual(proc.returncode, 0, "Expected non-zero exit for error program")
|
||
self.assertTrue(proc.stderr.strip(), "Expected error message on stderr")
|
||
finally:
|
||
os.unlink(tmp_c_path)
|
||
os.unlink(tmp_bin_path)
|
||
|
||
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)
|
||
|
||
|
||
# --- 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("DICE", [String("hi")]), "Builtin(\n DICE,\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)
|
||
|
||
|
||
# --- Roman numeral utilities ---
|
||
|
||
class TestNumerals(unittest.TestCase):
|
||
# num_to_int: valid cases
|
||
def test_simple_numerals(self):
|
||
for s, n in [("I",1),("V",5),("X",10),("L",50),("C",100),("D",500),("M",1000)]:
|
||
self.assertEqual(num_to_int(s, False), n)
|
||
|
||
def test_subtractive_forms(self):
|
||
for s, n in [("IV",4),("IX",9),("XL",40),("XC",90),("CD",400),("CM",900)]:
|
||
self.assertEqual(num_to_int(s, False), n)
|
||
|
||
def test_complex_numerals(self):
|
||
for s, n in [("XLII",42),("XCIX",99),("MCMXCIX",1999),("MMMCMXCIX",3999)]:
|
||
self.assertEqual(num_to_int(s, False), n)
|
||
|
||
# num_to_int: invalid cases
|
||
def test_four_in_a_row_raises(self):
|
||
with self.assertRaises(Exception):
|
||
num_to_int("IIII", False)
|
||
|
||
def test_four_x_in_a_row_raises(self):
|
||
with self.assertRaises(Exception):
|
||
num_to_int("XXXX", False)
|
||
|
||
def test_invalid_subtractive_iix_raises(self):
|
||
# IIX is non-standard — I can't appear twice before X
|
||
with self.assertRaises(Exception):
|
||
num_to_int("IIX", False)
|
||
|
||
def test_invalid_subtractive_im_raises(self):
|
||
# I can only subtract from V and X, not M
|
||
with self.assertRaises(Exception):
|
||
num_to_int("IM", False)
|
||
|
||
def test_negative_without_svbnvlla_raises(self):
|
||
with self.assertRaises(CentvrionError):
|
||
num_to_int("-IV", False)
|
||
|
||
def test_negative_with_svbnvlla(self):
|
||
self.assertEqual(num_to_int("-IV", False, True), -4)
|
||
self.assertEqual(num_to_int("-XLII", False, True), -42)
|
||
|
||
# int_to_num: valid cases
|
||
def test_int_to_num(self):
|
||
for n, s in [(1,"I"),(4,"IV"),(9,"IX"),(40,"XL"),(42,"XLII"),(3999,"MMMCMXCIX")]:
|
||
self.assertEqual(int_to_num(n, False), s)
|
||
|
||
def test_int_to_num_above_3999_raises(self):
|
||
with self.assertRaises(Exception):
|
||
int_to_num(4000, False)
|
||
|
||
def test_int_to_num_magnvm(self):
|
||
# 4000 with MAGNVM enabled
|
||
self.assertEqual(int_to_num(4000, True), "MV_")
|
||
|
||
def test_num_to_int_magnvm_required(self):
|
||
# Numbers parsed from strings with _ require MAGNVM
|
||
with self.assertRaises(Exception):
|
||
num_to_int("V_", False)
|
||
|
||
|
||
# --- 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_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]"
|
||
)
|
||
|
||
|
||
# --- DICE with non-integer types ---
|
||
|
||
dice_type_tests = [
|
||
("DICE(VERITAS)", Program([], [ExpressionStatement(BuiltIn("DICE", [Bool(True)]))]), ValStr("VERITAS"), "VERITAS\n"),
|
||
("DICE(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("DICE", [Bool(False)]))]), ValStr("FALSITAS"), "FALSITAS\n"),
|
||
("DICE(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("DICE", [Nullus()]))]), ValStr("NVLLVS"), "NVLLVS\n"),
|
||
('DICE([I, II])', Program([], [ExpressionStatement(BuiltIn("DICE", [DataArray([Numeral("I"), Numeral("II")])]))]), ValStr("[I II]"), "[I II]\n"),
|
||
('DICE("")', Program([], [ExpressionStatement(BuiltIn("DICE", [String("")]))]), ValStr(""), "\n"),
|
||
# arithmetic result printed as numeral
|
||
("DICE(II + III)", Program([], [ExpressionStatement(BuiltIn("DICE", [BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")]))]), ValStr("V"), "V\n"),
|
||
# multiple args of mixed types
|
||
('DICE("x", VERITAS)', Program([], [ExpressionStatement(BuiltIn("DICE", [String("x"), Bool(True)]))]), ValStr("x VERITAS"), "x VERITAS\n"),
|
||
]
|
||
|
||
class TestDiceTypes(unittest.TestCase):
|
||
@parameterized.expand(dice_type_tests)
|
||
def test_dice_types(self, source, nodes, value, output):
|
||
run_test(self, source, nodes, value, output)
|
||
|
||
|
||
# --- SI/DVM: boolean condition enforcement ---
|
||
|
||
dvm_bool_condition_tests = [
|
||
# DVM exits when condition becomes true (boolean comparison)
|
||
(
|
||
"DESIGNA x VT I\nDVM x PLVS III FACE {\nDESIGNA x VT x + I\n}\nx",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_PLVS"), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]),
|
||
ExpressionStatement(ID("x")),
|
||
]),
|
||
ValInt(4),
|
||
),
|
||
]
|
||
|
||
class TestDvmBoolCondition(unittest.TestCase):
|
||
@parameterized.expand(dvm_bool_condition_tests)
|
||
def test_dvm_bool_condition(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
|
||
# --- Arithmetic: edge cases ---
|
||
|
||
arithmetic_edge_tests = [
|
||
("I - I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"))]), ValInt(0)), # result zero
|
||
("I - V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-4)), # negative result
|
||
("I / V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)), # integer division → 0
|
||
("M * M", Program([], [ExpressionStatement(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_TIMES"))]), ValInt(1000000)), # large intermediate (not displayed)
|
||
("(I + II) * (IV - I)", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"), BinOp(Numeral("IV"), Numeral("I"), "SYMBOL_MINUS"), "SYMBOL_TIMES"))]), ValInt(9)), # nested parens
|
||
# NVLLVS coerces to 0 in integer arithmetic
|
||
("NVLLVS + V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_PLUS"))]), ValInt(5)),
|
||
("V + NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_PLUS"))]), ValInt(5)),
|
||
("NVLLVS + NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_PLUS"))]), ValNul()),
|
||
("NVLLVS - V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-5)),
|
||
("V - NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_MINUS"))]), ValInt(5)),
|
||
]
|
||
|
||
class TestArithmeticEdge(unittest.TestCase):
|
||
@parameterized.expand(arithmetic_edge_tests)
|
||
def test_arithmetic_edge(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- 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")),
|
||
# 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 DICE output
|
||
('DESIGNA name VT "Roma"\nDICE("Salve, {name}!")',
|
||
Program([], [
|
||
Designa(ID("name"), String("Roma")),
|
||
ExpressionStatement(BuiltIn("DICE", [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)
|
||
|
||
|
||
# --- Comparison operators ---
|
||
|
||
comparison_tests = [
|
||
# EST on strings
|
||
('\"hello\" EST \"hello\"', Program([], [ExpressionStatement(BinOp(String("hello"), String("hello"), "KEYWORD_EST"))]), ValBool(True)),
|
||
('\"hello\" EST \"world\"', Program([], [ExpressionStatement(BinOp(String("hello"), String("world"), "KEYWORD_EST"))]), ValBool(False)),
|
||
# chain comparisons as conditions
|
||
("SI III PLVS II TVNC { DESIGNA r VT I }\nr",
|
||
Program([], [SiStatement(BinOp(Numeral("III"), Numeral("II"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], None), ExpressionStatement(ID("r"))]),
|
||
ValInt(1)),
|
||
("SI II PLVS III TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(BinOp(Numeral("II"), Numeral("III"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(2)),
|
||
# result of comparison is ValBool
|
||
("I EST I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"))]), ValBool(True)),
|
||
("I EST II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"))]), ValBool(False)),
|
||
("I MINVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_MINVS"))]), ValBool(True)),
|
||
("II MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_MINVS"))]), ValBool(False)),
|
||
("II PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"))]), ValBool(True)),
|
||
("I PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_PLVS"))]), ValBool(False)),
|
||
# NVLLVS coerces to 0 in comparisons
|
||
("V PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_PLVS"))]), ValBool(True)),
|
||
("NVLLVS MINVS V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_MINVS"))]), ValBool(True)),
|
||
# DISPAR (not-equal): mirrors EST semantics, negated
|
||
("I DISPAR II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||
("I DISPAR I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||
('"hello" DISPAR "hello"', Program([], [ExpressionStatement(BinOp(String("hello"), String("hello"), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||
('"hello" DISPAR "world"', Program([], [ExpressionStatement(BinOp(String("hello"), String("world"), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||
("VERITAS DISPAR FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||
("NVLLVS DISPAR NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||
# cross-type: an int and a string are never equal
|
||
('I DISPAR "I"', Program([], [ExpressionStatement(BinOp(Numeral("I"), String("I"), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||
]
|
||
|
||
class TestComparisons(unittest.TestCase):
|
||
@parameterized.expand(comparison_tests)
|
||
def test_comparisons(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Function edge cases ---
|
||
|
||
function_edge_tests = [
|
||
# no explicit REDI → returns ValNul
|
||
("DEFINI f () VT { I }\nINVOCA f ()",
|
||
Program([], [Defini(ID("f"), [], [ExpressionStatement(Numeral("I"))]), ExpressionStatement(Invoca(ID("f"), []))]),
|
||
ValNul()),
|
||
# REDI multiple values → ValList
|
||
(
|
||
"DEFINI pair (a, b) VT { REDI (a, b) }\nINVOCA pair (I, II)",
|
||
Program([], [
|
||
Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]),
|
||
ExpressionStatement(Invoca(ID("pair"), [Numeral("I"), Numeral("II")])),
|
||
]),
|
||
ValList([ValInt(1), ValInt(2)]),
|
||
),
|
||
# function doesn't mutate outer vtable
|
||
(
|
||
"DESIGNA x VT I\nDEFINI f () VT { DESIGNA x VT V\nREDI (x) }\nINVOCA f ()\nx",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
Defini(ID("f"), [], [Designa(ID("x"), Numeral("V")), Redi([ID("x")])]),
|
||
ExpressionStatement(Invoca(ID("f"), [])),
|
||
ExpressionStatement(ID("x")),
|
||
]),
|
||
ValInt(1),
|
||
),
|
||
# function can read outer vtable (closure-like)
|
||
(
|
||
"DESIGNA x VT VII\nDEFINI f () VT { REDI (x) }\nINVOCA f ()",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("VII")),
|
||
Defini(ID("f"), [], [Redi([ID("x")])]),
|
||
ExpressionStatement(Invoca(ID("f"), [])),
|
||
]),
|
||
ValInt(7),
|
||
),
|
||
# parameter shadows outer variable inside function
|
||
(
|
||
"DESIGNA n VT I\nDEFINI f (n) VT { REDI (n * II) }\nINVOCA f (X)\nn",
|
||
Program([], [
|
||
Designa(ID("n"), Numeral("I")),
|
||
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||
ExpressionStatement(Invoca(ID("f"), [Numeral("X")])),
|
||
ExpressionStatement(ID("n")),
|
||
]),
|
||
ValInt(1),
|
||
),
|
||
# function aliasing: assign f to g, invoke via g
|
||
(
|
||
"DEFINI f (n) VT { REDI (n * II) }\nDESIGNA g VT f\nINVOCA g (V)",
|
||
Program([], [
|
||
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||
Designa(ID("g"), ID("f")),
|
||
ExpressionStatement(Invoca(ID("g"), [Numeral("V")])),
|
||
]),
|
||
ValInt(10),
|
||
),
|
||
# alias is independent: redefining f doesn't affect g
|
||
(
|
||
"DEFINI f (n) VT { REDI (n * II) }\nDESIGNA g VT f\nDEFINI f (n) VT { REDI (n * III) }\nINVOCA g (V)",
|
||
Program([], [
|
||
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||
Designa(ID("g"), ID("f")),
|
||
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("III"), "SYMBOL_TIMES")])]),
|
||
ExpressionStatement(Invoca(ID("g"), [Numeral("V")])),
|
||
]),
|
||
ValInt(10),
|
||
),
|
||
# REDI inside SI exits function, skips remaining statements in block
|
||
(
|
||
"DEFINI f () VT {\nSI VERITAS TVNC {\nREDI (I)\nREDI (II)\n}\n}\nINVOCA f ()",
|
||
Program([],[
|
||
Defini(ID("f"), [], [SiStatement(Bool(True),[Redi([Numeral("I")]),Redi([Numeral("II")])],None)]),
|
||
ExpressionStatement(Invoca(ID("f"),[]))
|
||
]),
|
||
ValInt(1),
|
||
),
|
||
# REDI inside DVM exits loop and function
|
||
(
|
||
"DEFINI f () VT {\nDESIGNA x VT I\nDVM FALSITAS FACE {\nREDI (x)\n}\n}\nINVOCA f ()",
|
||
Program([],[
|
||
Defini(ID("f"), [], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(Bool(False), [Redi([ID("x")])])
|
||
]),
|
||
ExpressionStatement(Invoca(ID("f"),[]))
|
||
]),
|
||
ValInt(1),
|
||
),
|
||
# REDI inside PER exits loop and function
|
||
(
|
||
"DEFINI f () VT {\nPER x IN [I, II, III] FACE {\nSI x EST II TVNC {\nREDI (x)\n}\n}\n}\nINVOCA f ()",
|
||
Program([],[
|
||
Defini(ID("f"), [], [
|
||
PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("x"), [
|
||
SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"), [
|
||
Redi([ID("x")])
|
||
], None)
|
||
])
|
||
]),
|
||
ExpressionStatement(Invoca(ID("f"),[]))
|
||
]),
|
||
ValInt(2),
|
||
),
|
||
# REDI inside nested loops exits all loops and function
|
||
(
|
||
"DEFINI f () VT {\nDESIGNA x VT I\nDVM FALSITAS FACE {\nDVM FALSITAS FACE {\nREDI (x)\n}\n}\n}\nINVOCA f ()",
|
||
Program([],[
|
||
Defini(ID("f"), [], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(Bool(False), [
|
||
DumStatement(Bool(False), [
|
||
Redi([ID("x")])
|
||
])
|
||
])
|
||
]),
|
||
ExpressionStatement(Invoca(ID("f"),[]))
|
||
]),
|
||
ValInt(1),
|
||
),
|
||
]
|
||
|
||
class TestFunctionEdge(unittest.TestCase):
|
||
@parameterized.expand(function_edge_tests)
|
||
def test_function_edge(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Loop edge cases ---
|
||
|
||
loop_edge_tests = [
|
||
# range(3, 3) is empty — body never runs, program returns ValNul
|
||
("DONICVM i VT III VSQVE III FACE { DICE(i) }",
|
||
Program([], [PerStatement(DataRangeArray(Numeral("III"), Numeral("III")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
||
ValNul(), ""),
|
||
# empty array — body never runs
|
||
("PER i IN [] FACE { DICE(i) }",
|
||
Program([], [PerStatement(DataArray([]), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
||
ValNul(), ""),
|
||
# PER breaks on element 2 — last assigned i is 2
|
||
("PER i IN [I, II, III] FACE { SI i EST II TVNC { ERVMPE } }\ni",
|
||
Program([], [
|
||
PerStatement(
|
||
DataArray([Numeral("I"), Numeral("II"), Numeral("III")]),
|
||
ID("i"),
|
||
[SiStatement(BinOp(ID("i"), Numeral("II"), "KEYWORD_EST"), [Erumpe()], None)],
|
||
),
|
||
ExpressionStatement(ID("i")),
|
||
]),
|
||
ValInt(2), ""),
|
||
# nested DVM: inner always breaks; outer runs until btr==3
|
||
("DESIGNA btr VT I\nDVM btr EST III FACE {\nDVM FALSITAS FACE {\nERVMPE\n}\nDESIGNA btr VT btr + I\n}\nbtr",
|
||
Program([], [
|
||
Designa(ID("btr"), Numeral("I")),
|
||
DumStatement(
|
||
BinOp(ID("btr"), Numeral("III"), "KEYWORD_EST"),
|
||
[DumStatement(Bool(False), [Erumpe()]), Designa(ID("btr"), BinOp(ID("btr"), Numeral("I"), "SYMBOL_PLUS"))],
|
||
),
|
||
ExpressionStatement(ID("btr")),
|
||
]),
|
||
ValInt(3), ""),
|
||
# nested PER: inner always breaks on first element; outer completes both iterations
|
||
# cnt starts at 1, increments twice → 3
|
||
("DESIGNA cnt VT I\nPER i IN [I, II] FACE {\nPER k IN [I, II] FACE {\nERVMPE\n}\nDESIGNA cnt VT cnt + I\n}\ncnt",
|
||
Program([], [
|
||
Designa(ID("cnt"), Numeral("I")),
|
||
PerStatement(
|
||
DataArray([Numeral("I"), Numeral("II")]),
|
||
ID("i"),
|
||
[PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"), [Erumpe()]),
|
||
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))],
|
||
),
|
||
ExpressionStatement(ID("cnt")),
|
||
]),
|
||
ValInt(3), ""),
|
||
# PER with CONTINVA: skip odd numbers, sum evens
|
||
# [I,II,III,IV] → skip I and III; cnt increments on II and IV → cnt = III
|
||
("DESIGNA cnt VT I\nPER i IN [I, II, III, IV] FACE {\nSI i EST I AVT i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt",
|
||
Program([], [
|
||
Designa(ID("cnt"), Numeral("I")),
|
||
PerStatement(
|
||
DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]),
|
||
ID("i"),
|
||
[SiStatement(BinOp(BinOp(ID("i"), Numeral("I"), "KEYWORD_EST"), BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), "KEYWORD_AVT"), [Continva()], None),
|
||
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))],
|
||
),
|
||
ExpressionStatement(ID("cnt")),
|
||
]),
|
||
ValInt(3), ""),
|
||
# DVM with CONTINVA: skip body when x is II, increment regardless
|
||
# x goes 1→2→3; on x=2 we continue (no DICE); DICE fires for x=1 and x=3
|
||
("DESIGNA x VT I\nDVM x EST IV FACE {\nSI x EST II TVNC { DESIGNA x VT x + I\nCONTINVA }\nDICE(x)\nDESIGNA x VT x + I\n}\nx",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(
|
||
BinOp(ID("x"), Numeral("IV"), "KEYWORD_EST"),
|
||
[SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"),
|
||
[Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), Continva()], None),
|
||
ExpressionStatement(BuiltIn("DICE", [ID("x")])),
|
||
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))],
|
||
),
|
||
ExpressionStatement(ID("x")),
|
||
]),
|
||
ValInt(4), "I\nIII\n"),
|
||
# nested PER: CONTINVA in inner only skips rest of inner body; outer still increments
|
||
("DESIGNA cnt VT I\nPER i IN [I, II] FACE {\nPER k IN [I, II] FACE {\nCONTINVA\nDESIGNA cnt VT cnt + I\n}\nDESIGNA cnt VT cnt + I\n}\ncnt",
|
||
Program([], [
|
||
Designa(ID("cnt"), Numeral("I")),
|
||
PerStatement(
|
||
DataArray([Numeral("I"), Numeral("II")]),
|
||
ID("i"),
|
||
[PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"),
|
||
[Continva(), Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))]),
|
||
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))],
|
||
),
|
||
ExpressionStatement(ID("cnt")),
|
||
]),
|
||
ValInt(3), ""),
|
||
# DONICVM with CONTINVA: skip value III, count remaining
|
||
("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FACE {\nSI i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt",
|
||
Program([], [
|
||
Designa(ID("cnt"), Numeral("I")),
|
||
PerStatement(
|
||
DataRangeArray(Numeral("I"), Numeral("IV")),
|
||
ID("i"),
|
||
[SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Continva()], None),
|
||
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))],
|
||
),
|
||
ExpressionStatement(ID("cnt")),
|
||
]),
|
||
ValInt(3)),
|
||
# DVM condition true from start — body never runs
|
||
("DESIGNA x VT I\nDVM VERITAS FACE {\nDESIGNA x VT x + I\n}\nx",
|
||
Program([], [
|
||
Designa(ID("x"), Numeral("I")),
|
||
DumStatement(Bool(True), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]),
|
||
ExpressionStatement(ID("x")),
|
||
]),
|
||
ValInt(1), ""),
|
||
# single iteration: [I VSQVE II] = [1]
|
||
("DONICVM i VT I VSQVE II FACE { DICE(i) }",
|
||
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("II")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
||
ValStr("I"), "I\n"),
|
||
]
|
||
|
||
class TestLoopEdge(unittest.TestCase):
|
||
@parameterized.expand(loop_edge_tests)
|
||
def test_loop_edge(self, source, nodes, value, output=""):
|
||
run_test(self, source, nodes, value, output)
|
||
|
||
|
||
# --- Values: equality and truthiness ---
|
||
|
||
class TestValues(unittest.TestCase):
|
||
def test_valint_equality(self):
|
||
self.assertEqual(ValInt(3), ValInt(3))
|
||
self.assertNotEqual(ValInt(3), ValInt(4))
|
||
|
||
def test_valstr_equality(self):
|
||
self.assertEqual(ValStr("hi"), ValStr("hi"))
|
||
self.assertNotEqual(ValStr("hi"), ValStr("bye"))
|
||
|
||
def test_valbool_equality(self):
|
||
self.assertEqual(ValBool(True), ValBool(True))
|
||
self.assertNotEqual(ValBool(True), ValBool(False))
|
||
|
||
def test_valnul_equality(self):
|
||
self.assertEqual(ValNul(), ValNul())
|
||
|
||
def test_vallist_equality(self):
|
||
self.assertEqual(ValList([ValInt(1)]), ValList([ValInt(1)]))
|
||
self.assertNotEqual(ValList([ValInt(1)]), ValList([ValInt(2)]))
|
||
self.assertNotEqual(ValList([ValInt(1)]), ValList([]))
|
||
|
||
def test_valint_truthiness(self):
|
||
self.assertTrue(bool(ValInt(1)))
|
||
self.assertTrue(bool(ValInt(-1)))
|
||
self.assertFalse(bool(ValInt(0)))
|
||
|
||
def test_valstr_truthiness(self):
|
||
self.assertTrue(bool(ValStr("x")))
|
||
self.assertFalse(bool(ValStr("")))
|
||
|
||
def test_valbool_truthiness(self):
|
||
self.assertTrue(bool(ValBool(True)))
|
||
self.assertFalse(bool(ValBool(False)))
|
||
|
||
def test_vallist_truthiness(self):
|
||
self.assertTrue(bool(ValList([ValInt(1)])))
|
||
self.assertFalse(bool(ValList([])))
|
||
|
||
def test_cross_type_inequality(self):
|
||
self.assertNotEqual(ValInt(1), ValBool(True))
|
||
self.assertNotEqual(ValInt(0), ValNul())
|
||
self.assertNotEqual(ValStr(""), ValNul())
|
||
|
||
|
||
# --- MAGNVM module ---
|
||
# (ValueError for 4000 without MAGNVM is already in error_tests)
|
||
|
||
magnvm_tests = [
|
||
# M+M+M+M = 4000; MAGNVM allows display as "MV_"
|
||
("CVM MAGNVM\nDICE(M + M + M + M)",
|
||
Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DICE", [BinOp(BinOp(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS")]))]),
|
||
ValStr("MV_"), "MV_\n"),
|
||
# I_ = 1000 with MAGNVM (same value as M, but written with thousands operator)
|
||
("CVM MAGNVM\nI_",
|
||
Program([ModuleCall("MAGNVM")], [ExpressionStatement(Numeral("I_"))]),
|
||
ValInt(1000), ""),
|
||
# I_ + I_ = 2000; displayed as MM with MAGNVM
|
||
("CVM MAGNVM\nDICE(I_ + I_)",
|
||
Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DICE", [BinOp(Numeral("I_"), Numeral("I_"), "SYMBOL_PLUS")]))]),
|
||
ValStr("MM"), "MM\n"),
|
||
]
|
||
|
||
class TestMAGNVM(unittest.TestCase):
|
||
@parameterized.expand(magnvm_tests)
|
||
def test_magnvm(self, source, nodes, value, output=""):
|
||
run_test(self, source, nodes, value, output)
|
||
|
||
|
||
# --- ET and AVT (boolean and/or) ---
|
||
|
||
et_avt_tests = [
|
||
("VERITAS ET VERITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(True), "KEYWORD_ET"))]), ValBool(True)),
|
||
("VERITAS ET FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_ET"))]), ValBool(False)),
|
||
("FALSITAS ET VERITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(True), "KEYWORD_ET"))]), ValBool(False)),
|
||
("FALSITAS ET FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(False), "KEYWORD_ET"))]), ValBool(False)),
|
||
("VERITAS AVT VERITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(True), "KEYWORD_AVT"))]), ValBool(True)),
|
||
("VERITAS AVT FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_AVT"))]), ValBool(True)),
|
||
("FALSITAS AVT VERITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"))]), ValBool(True)),
|
||
("FALSITAS AVT FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"))]), ValBool(False)),
|
||
# short-circuit behavior: combined with comparisons
|
||
("(I EST I) ET (II EST II)",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]),
|
||
ValBool(True)),
|
||
("(I EST II) AVT (II EST II)",
|
||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_AVT"))]),
|
||
ValBool(True)),
|
||
# used as SI condition
|
||
("SI VERITAS ET VERITAS TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(BinOp(Bool(True), Bool(True), "KEYWORD_ET"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(1)),
|
||
("SI FALSITAS AVT FALSITAS TVNC { DESIGNA r VT I } ALVID { DESIGNA r VT II }\nr",
|
||
Program([], [SiStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||
ValInt(2)),
|
||
]
|
||
|
||
class TestEtAvt(unittest.TestCase):
|
||
@parameterized.expand(et_avt_tests)
|
||
def test_et_avt(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Array indexing ---
|
||
# Indexing is 1-based; I is the first element
|
||
|
||
array_index_tests = [
|
||
# basic indexing
|
||
("[I, II, III][I]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I")))]), ValInt(1)), # first element
|
||
("[I, II, III][II]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")))]), ValInt(2)), # second element
|
||
("[I, II, III][III]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("III")))]), ValInt(3)), # third element
|
||
# index into a variable
|
||
("DESIGNA a VT [X, XX, XXX]\na[II]",
|
||
Program([], [Designa(ID("a"), DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])), ExpressionStatement(ArrayIndex(ID("a"), Numeral("II")))]),
|
||
ValInt(20)), # second element
|
||
# index into range array
|
||
("[I VSQVE V][II]", Program([], [ExpressionStatement(ArrayIndex(DataRangeArray(Numeral("I"), Numeral("V")), Numeral("II")))]), ValInt(2)), # second element of [1,2,3,4]
|
||
# expression as index
|
||
("[I, II, III][I + I]",
|
||
Program([], [ExpressionStatement(ArrayIndex(
|
||
DataArray([Numeral("I"), Numeral("II"), Numeral("III")]),
|
||
BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS")))]),
|
||
ValInt(2)),
|
||
# division result as index (no FRACTIO): IV / II = 2
|
||
("[X, XX, XXX][IV / II]",
|
||
Program([], [ExpressionStatement(ArrayIndex(
|
||
DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")]),
|
||
BinOp(Numeral("IV"), Numeral("II"), "SYMBOL_DIVIDE")))]),
|
||
ValInt(20)),
|
||
# whole-number fraction (from division) as index, with FRACTIO imported
|
||
("CVM FRACTIO\n[X, XX, XXX][IV / II]",
|
||
Program([ModuleCall("FRACTIO")], [ExpressionStatement(ArrayIndex(
|
||
DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")]),
|
||
BinOp(Numeral("IV"), Numeral("II"), "SYMBOL_DIVIDE")))]),
|
||
ValInt(20)),
|
||
]
|
||
|
||
class TestArrayIndex(unittest.TestCase):
|
||
@parameterized.expand(array_index_tests)
|
||
def test_array_index(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Array index assignment ---
|
||
|
||
array_index_assign_tests = [
|
||
# assign to middle element
|
||
("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[II]",
|
||
Program([], [
|
||
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||
DesignaIndex(ID("a"), Numeral("II"), Numeral("X")),
|
||
ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))),
|
||
]),
|
||
ValInt(10)),
|
||
# assign to first element
|
||
("DESIGNA a VT [I, II, III]\nDESIGNA a[I] VT V\na[I]",
|
||
Program([], [
|
||
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||
DesignaIndex(ID("a"), Numeral("I"), Numeral("V")),
|
||
ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))),
|
||
]),
|
||
ValInt(5)),
|
||
# assign to last element
|
||
("DESIGNA a VT [I, II, III]\nDESIGNA a[III] VT L\na[III]",
|
||
Program([], [
|
||
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||
DesignaIndex(ID("a"), Numeral("III"), Numeral("L")),
|
||
ExpressionStatement(ArrayIndex(ID("a"), Numeral("III"))),
|
||
]),
|
||
ValInt(50)),
|
||
# other elements unaffected
|
||
("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[I]",
|
||
Program([], [
|
||
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||
DesignaIndex(ID("a"), Numeral("II"), Numeral("X")),
|
||
ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))),
|
||
]),
|
||
ValInt(1)),
|
||
# expression as index
|
||
("DESIGNA a VT [I, II, III]\nDESIGNA i VT II\nDESIGNA a[i] VT X\na[II]",
|
||
Program([], [
|
||
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||
Designa(ID("i"), Numeral("II")),
|
||
DesignaIndex(ID("a"), ID("i"), Numeral("X")),
|
||
ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))),
|
||
]),
|
||
ValInt(10)),
|
||
]
|
||
|
||
class TestArrayIndexAssign(unittest.TestCase):
|
||
@parameterized.expand(array_index_assign_tests)
|
||
def test_array_index_assign(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- Comments ---
|
||
|
||
comment_tests = [
|
||
# trailing line comment
|
||
('DICE("hello") // this is ignored', Program([], [ExpressionStatement(BuiltIn("DICE", [String("hello")]))]), ValStr("hello"), "hello\n"),
|
||
# line comment on its own line before code
|
||
('// ignored\nDICE("hi")', Program([], [ExpressionStatement(BuiltIn("DICE", [String("hi")]))]), ValStr("hi"), "hi\n"),
|
||
# inline block comment
|
||
('DICE(/* ignored */ "hi")', Program([], [ExpressionStatement(BuiltIn("DICE", [String("hi")]))]), ValStr("hi"), "hi\n"),
|
||
# block comment spanning multiple lines
|
||
('/* line one\nline two */\nDICE("hi")', Program([], [ExpressionStatement(BuiltIn("DICE", [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\nDICE("ok")', Program([], [ExpressionStatement(BuiltIn("DICE", [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 ALVID branch persists in outer scope
|
||
("SI FALSITAS TVNC { DESIGNA r VT X } ALVID { 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 FACE { 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] FACE { 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] FACE { 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] FACE { 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]; last value assigned by loop is III=3
|
||
("DONICVM i VT I VSQVE IV FACE { DESIGNA nop VT I }\ni",
|
||
Program([], [
|
||
PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [Designa(ID("nop"), Numeral("I"))]),
|
||
ExpressionStatement(ID("i")),
|
||
]),
|
||
ValInt(3)),
|
||
# 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 3 times → 4
|
||
("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FACE { 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(4)),
|
||
# DONICVM: ERVMPE exits loop early; counter persists at break value
|
||
("DONICVM i VT I VSQVE X FACE {\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)
|
||
|
||
|
||
# --- NON (boolean not) ---
|
||
|
||
non_tests = [
|
||
("NON VERITAS",
|
||
Program([], [ExpressionStatement(UnaryNot(Bool(True)))]),
|
||
ValBool(False)),
|
||
("NON FALSITAS",
|
||
Program([], [ExpressionStatement(UnaryNot(Bool(False)))]),
|
||
ValBool(True)),
|
||
("NON NON VERITAS",
|
||
Program([], [ExpressionStatement(UnaryNot(UnaryNot(Bool(True))))]),
|
||
ValBool(True)),
|
||
("DESIGNA b VT I EST II\nNON b",
|
||
Program([], [Designa(ID("b"), BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("b")))]),
|
||
ValBool(True)),
|
||
("DESIGNA z VT I EST I\nNON z",
|
||
Program([], [Designa(ID("z"), BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("z")))]),
|
||
ValBool(False)),
|
||
("NON VERITAS AVT FALSITAS",
|
||
Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_AVT"))]),
|
||
ValBool(False)),
|
||
("NON VERITAS EST FALSITAS",
|
||
Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_EST"))]),
|
||
ValBool(True)),
|
||
]
|
||
|
||
class TestNon(unittest.TestCase):
|
||
@parameterized.expand(non_tests)
|
||
def test_non(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
# --- FRACTIO module ---
|
||
|
||
fractio_tests = [
|
||
# Basic fraction literals
|
||
("CVM FRACTIO\nIIIS",
|
||
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("IIIS"))]),
|
||
ValFrac(Fraction(7, 2))),
|
||
("CVM FRACTIO\nS",
|
||
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("S"))]),
|
||
ValFrac(Fraction(1, 2))),
|
||
("CVM FRACTIO\nS:.",
|
||
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("S:."))]),
|
||
ValFrac(Fraction(3, 4))),
|
||
("CVM FRACTIO\n.",
|
||
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("."))]),
|
||
ValFrac(Fraction(1, 12))),
|
||
("CVM FRACTIO\n:.",
|
||
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio(":."))]),
|
||
ValFrac(Fraction(1, 4))),
|
||
# Integer part with fraction
|
||
("CVM FRACTIO\nVIIS:|::",
|
||
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("VIIS:|::"))]),
|
||
ValFrac(Fraction(7) + Fraction(100, 144))),
|
||
# Arithmetic
|
||
("CVM FRACTIO\nIIIS + S",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_PLUS"))
|
||
]),
|
||
ValFrac(Fraction(4))
|
||
),
|
||
("CVM FRACTIO\nIIIS - S",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_MINUS"))
|
||
]),
|
||
ValFrac(Fraction(3))
|
||
),
|
||
("CVM FRACTIO\nS * IV",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("S"), Numeral("IV"), "SYMBOL_TIMES"))
|
||
]),
|
||
ValFrac(Fraction(2))
|
||
),
|
||
# Division returns fraction
|
||
("CVM FRACTIO\nI / IV",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Numeral("I"), Numeral("IV"), "SYMBOL_DIVIDE"))
|
||
]),
|
||
ValFrac(Fraction(1, 4))
|
||
),
|
||
("CVM FRACTIO\nI / III",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Numeral("I"), Numeral("III"), "SYMBOL_DIVIDE"))
|
||
]),
|
||
ValFrac(Fraction(1, 3))
|
||
),
|
||
# Integer division still works without fractions in operands... but with FRACTIO returns ValFrac
|
||
("CVM FRACTIO\nX / II",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))
|
||
]),
|
||
ValFrac(Fraction(5))
|
||
),
|
||
# Modulo on fractions: 7/2 RELIQVVM 3/2 = 1/2 (7/2 / 3/2 = 7/3, floor=2, 7/2 - 3 = 1/2)
|
||
("CVM FRACTIO\nIIIS RELIQVVM IS",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IS"), "KEYWORD_RELIQVVM"))
|
||
]),
|
||
ValFrac(Fraction(1, 2))
|
||
),
|
||
# Modulo with mixed operand types: 5/2 RELIQVVM 1 = 1/2
|
||
("CVM FRACTIO\nIIS RELIQVVM I",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIS"), Numeral("I"), "KEYWORD_RELIQVVM"))
|
||
]),
|
||
ValFrac(Fraction(1, 2))
|
||
),
|
||
# Int operands under FRACTIO still return a fraction: 10 RELIQVVM 3 = 1 (as Fraction)
|
||
("CVM FRACTIO\nX RELIQVVM III",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "KEYWORD_RELIQVVM"))
|
||
]),
|
||
ValFrac(Fraction(1))
|
||
),
|
||
# Exact multiple under FRACTIO: 3 RELIQVVM 3/2 = 0
|
||
("CVM FRACTIO\nIII RELIQVVM IS",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Numeral("III"), Fractio("IS"), "KEYWORD_RELIQVVM"))
|
||
]),
|
||
ValFrac(Fraction(0))
|
||
),
|
||
# String concatenation with fraction
|
||
("CVM FRACTIO\nDICE(IIIS & \"!\")",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BuiltIn("DICE", [BinOp(Fractio("IIIS"), String("!"), "SYMBOL_AMPERSAND")]))
|
||
]),
|
||
ValStr("IIIS!"), "IIIS!\n"
|
||
),
|
||
# Negative fractions
|
||
("CVM FRACTIO\nCVM SVBNVLLA\n-IIS",
|
||
Program([ModuleCall("FRACTIO"),ModuleCall("SVBNVLLA")],[
|
||
ExpressionStatement(UnaryMinus(Fractio("IIS")))
|
||
]),
|
||
ValFrac(Fraction(-5,2))
|
||
)
|
||
]
|
||
|
||
class TestFractio(unittest.TestCase):
|
||
@parameterized.expand(fractio_tests)
|
||
def test_fractio(self, source, nodes, value, output=""):
|
||
run_test(self, source, nodes, value, output)
|
||
|
||
|
||
fractio_comparison_tests = [
|
||
# fraction vs fraction
|
||
("CVM FRACTIO\nIIIS PLVS III",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("III"), "KEYWORD_PLVS"))
|
||
]),
|
||
ValBool(True)
|
||
),
|
||
("CVM FRACTIO\nIII MINVS IIIS",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Numeral("III"), Fractio("IIIS"), "KEYWORD_MINVS"))
|
||
]),
|
||
ValBool(True)
|
||
),
|
||
("CVM FRACTIO\nIIIS MINVS IV",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("IV"), "KEYWORD_MINVS"))
|
||
]),
|
||
ValBool(True)
|
||
),
|
||
("CVM FRACTIO\nIV PLVS IIIS",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Numeral("IV"), Fractio("IIIS"), "KEYWORD_PLVS"))
|
||
]),
|
||
ValBool(True)
|
||
),
|
||
("CVM FRACTIO\nIIIS PLVS IIIS",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_PLVS"))
|
||
]),
|
||
ValBool(False)
|
||
),
|
||
("CVM FRACTIO\nIIIS MINVS IIIS",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_MINVS"))
|
||
]),
|
||
ValBool(False)
|
||
),
|
||
# equality: fraction == fraction
|
||
("CVM FRACTIO\nIIIS EST IIIS",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_EST"))
|
||
]),
|
||
ValBool(True)
|
||
),
|
||
("CVM FRACTIO\nIIIS EST IV",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("IV"), "KEYWORD_EST"))
|
||
]),
|
||
ValBool(False)
|
||
),
|
||
# equality: fraction == whole number (ValFrac(4) vs ValInt(4))
|
||
("CVM FRACTIO\nIIIS + S EST IV",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(
|
||
BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_PLUS"),
|
||
Numeral("IV"), "KEYWORD_EST"))
|
||
]),
|
||
ValBool(True)
|
||
),
|
||
("CVM FRACTIO\nS + S EST I",
|
||
Program([ModuleCall("FRACTIO")], [
|
||
ExpressionStatement(BinOp(
|
||
BinOp(Fractio("S"), Fractio("S"), "SYMBOL_PLUS"),
|
||
Numeral("I"), "KEYWORD_EST"))
|
||
]),
|
||
ValBool(True)
|
||
),
|
||
]
|
||
|
||
class TestFractioComparisons(unittest.TestCase):
|
||
@parameterized.expand(fractio_comparison_tests)
|
||
def test_fractio_comparison(self, source, nodes, value):
|
||
run_test(self, source, nodes, value)
|
||
|
||
|
||
class TestFractioHelpers(unittest.TestCase):
|
||
def test_frac_to_fraction_ordering(self):
|
||
with self.assertRaises(CentvrionError):
|
||
frac_to_fraction(".S") # . before S violates highest-to-lowest
|
||
|
||
def test_frac_to_fraction_level_overflow(self):
|
||
with self.assertRaises(CentvrionError):
|
||
frac_to_fraction("SSSSSS") # SS means S twice = 12/12 = 1, violating < 12/12 constraint
|
||
|
||
def test_frac_to_fraction_iiis(self):
|
||
self.assertEqual(frac_to_fraction("IIIS"), Fraction(7, 2))
|
||
|
||
def test_frac_to_fraction_s_colon_dot(self):
|
||
self.assertEqual(frac_to_fraction("S:."), Fraction(3, 4))
|
||
|
||
def test_frac_to_fraction_dot(self):
|
||
self.assertEqual(frac_to_fraction("."), Fraction(1, 12))
|
||
|
||
def test_frac_to_fraction_multilevel(self):
|
||
self.assertEqual(frac_to_fraction("VIIS:|::"), Fraction(7) + Fraction(100, 144))
|
||
|
||
def test_fraction_to_frac_iiis(self):
|
||
self.assertEqual(fraction_to_frac(Fraction(7, 2)), "IIIS")
|
||
|
||
def test_fraction_to_frac_s_colon_dot(self):
|
||
self.assertEqual(fraction_to_frac(Fraction(3, 4)), "S:.")
|
||
|
||
def test_fraction_to_frac_dot(self):
|
||
self.assertEqual(fraction_to_frac(Fraction(1, 12)), ".")
|
||
|
||
def test_fraction_to_frac_multilevel(self):
|
||
self.assertEqual(
|
||
fraction_to_frac(Fraction(7) + Fraction(100, 144)),
|
||
"VIIS:|::"
|
||
)
|
||
|
||
def test_roundtrip(self):
|
||
# Only canonical forms roundtrip — fraction_to_frac always uses max colons before dots
|
||
for s in ["IIIS", "S:.", ".", "::", "VIIS:|::", "S"]:
|
||
self.assertEqual(fraction_to_frac(frac_to_fraction(s)), s)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|