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

176 lines
9.5 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,
)
# --- 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 [(0,"NVLLVS"),(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)
# --- 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)),
# NVLLVS coerces to 0 in modulo and division
("NVLLVS RELIQVVM V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_RELIQVVM"))]), ValInt(0)),
("NVLLVS / V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)),
# floored division and modulo with negative operands (Python semantics)
("CVM SVBNVLLA\n- VII RELIQVVM III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(2)),
("CVM SVBNVLLA\nVII RELIQVVM - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("VII"), UnaryMinus(Numeral("III")), "KEYWORD_RELIQVVM"))]), ValInt(-2)),
("CVM SVBNVLLA\n- VII RELIQVVM - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), UnaryMinus(Numeral("III")), "KEYWORD_RELIQVVM"))]), ValInt(-1)),
("CVM SVBNVLLA\n- VII / III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(-3)),
("CVM SVBNVLLA\nVII / - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("VII"), UnaryMinus(Numeral("III")), "SYMBOL_DIVIDE"))]), ValInt(-3)),
("CVM SVBNVLLA\n- VII / - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), UnaryMinus(Numeral("III")), "SYMBOL_DIVIDE"))]), ValInt(2)),
("CVM SVBNVLLA\n- L RELIQVVM C", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("L")), Numeral("C"), "KEYWORD_RELIQVVM"))]), ValInt(50)),
]
class TestArithmeticEdge(unittest.TestCase):
@parameterized.expand(arithmetic_edge_tests)
def test_arithmetic_edge(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- 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\nDIC(M + M + M + M)",
Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DIC", [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\nDIC(I_ + I_)",
Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DIC", [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)
# --- SVBNVLLA module (display of negatives) ---
svbnvlla_display_tests = [
# DIC prints a negative numeral
("CVM SVBNVLLA\nDIC(- III)",
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Numeral("III"))]))]),
ValStr("-III"), "-III\n"),
# Concat operator & with a negative numeral
('CVM SVBNVLLA\nDIC("x: " & - V)',
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(String("x: "), UnaryMinus(Numeral("V")), "SYMBOL_AMPERSAND")]))]),
ValStr("x: -V"), "x: -V\n"),
# String interpolation of a negative numeral
('CVM SVBNVLLA\nDIC("val: {- X}")',
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [InterpolatedString([String("val: "), UnaryMinus(Numeral("X"))])]))]),
ValStr("val: -X"), "val: -X\n"),
# DIC of LITTERA(negative numeral)
("CVM SVBNVLLA\nDIC(LITTERA(- III))",
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("LITTERA", [UnaryMinus(Numeral("III"))])]))]),
ValStr("-III"), "-III\n"),
# Combined with MAGNVM: negative of a number > 3999
("CVM MAGNVM\nCVM SVBNVLLA\nDIC(- (M + M + M + M))",
Program([ModuleCall("MAGNVM"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(BinOp(BinOp(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"))]))]),
ValStr("-MV_"), "-MV_\n"),
# Negative fraction via int / -int
("CVM FRACTIO\nCVM SVBNVLLA\nDIC(V / - II)",
Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("V"), UnaryMinus(Numeral("II")), "SYMBOL_DIVIDE")]))]),
ValStr("-IIS"), "-IIS\n"),
# Negative pure fraction (no integer part)
("CVM FRACTIO\nCVM SVBNVLLA\nDIC(I / - II)",
Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I"), UnaryMinus(Numeral("II")), "SYMBOL_DIVIDE")]))]),
ValStr("-S"), "-S\n"),
("CVM FRACTIO\nCVM SVBNVLLA\nDIC(- S)",
Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Fractio("S"))]))]),
ValStr("-S"), "-S\n"),
("CVM FRACTIO\nCVM SVBNVLLA\nDIC(- IIS)",
Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Fractio("IIS"))]))]),
ValStr("-IIS"), "-IIS\n"),
]
class TestSVBNVLLADisplay(unittest.TestCase):
@parameterized.expand(svbnvlla_display_tests)
def test_svbnvlla_display(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)