176 lines
9.5 KiB
Python
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)
|