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)