From 16e785e8fa8599b09c1325618cfa1546e75eebd6 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Wed, 1 Apr 2026 12:13:00 +0200 Subject: [PATCH] :goat: SVBNVLLA implementation --- centvrion/ast_nodes.py | 31 +++++++++++++++++++++---------- tests.py | 31 ++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index f504a12..febc60d 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -54,7 +54,11 @@ def single_num_to_int(i, m): else: return NUMERALS[i] -def num_to_int(n, m): +def num_to_int(n, m, s=False): + if n.startswith("-"): + if not s: + raise ValueError("Cannot use negative numbers without 'SVBNVLLA' module") + return -num_to_int(n[1:], m, s) chars = re.findall(r"[IVXLCDM]_*", n) if ''.join(chars) != n: raise ValueError("Invalid numeral", n) @@ -85,19 +89,23 @@ def num_to_int(n, m): return sum(nums) -def int_to_num(n, m): +def int_to_num(n, m, s=False): + if n < 0: + if not s: + raise ValueError("Cannot display negative numbers without 'SVBNVLLA' module") + return "-" + int_to_num(-n, m, s) if n > 3999: if not m: raise ValueError( "Cannot display numbers above 3999 without 'MAGNVM' module" ) - thousands_chars = re.findall(r"[IVXLCDM]_*", int_to_num(n//1000, m)) + thousands_chars = re.findall(r"[IVXLCDM]_*", int_to_num(n//1000, m, s)) thousands = ''.join([ "M" if i == "I" else i+"_" for i in thousands_chars ]) - return thousands + int_to_num(n % 1000, m) + return thousands + int_to_num(n % 1000, m, s) else: nums = [] while n > 0: @@ -109,17 +117,17 @@ def int_to_num(n, m): return ''.join(nums) -def make_string(val, magnvm=False): +def make_string(val, magnvm=False, svbnvlla=False): if isinstance(val, ValStr): return val.value() elif isinstance(val, ValInt): - return int_to_num(val.value(), magnvm) + return int_to_num(val.value(), magnvm, svbnvlla) elif isinstance(val, ValBool): return "VERVS" if val.value() else "FALSVS" elif isinstance(val, ValNul): return "NVLLVS" elif isinstance(val, ValList): - inner = ' '.join(make_string(i, magnvm) for i in val.value()) + inner = ' '.join(make_string(i, magnvm, svbnvlla) for i in val.value()) return f"[{inner}]" else: raise TypeError(f"Cannot display {val!r}") @@ -225,7 +233,7 @@ class Numeral(Node): return self.value def _eval(self, vtable): - return vtable, ValInt(num_to_int(self.value, "MAGNVM" in vtable["#modules"])) + return vtable, ValInt(num_to_int(self.value, "MAGNVM" in vtable["#modules"], "SVBNVLLA" in vtable["#modules"])) class Bool(Node): @@ -439,6 +447,8 @@ class UnaryMinus(Node): return f"(- {self.expr.print()})" def _eval(self, vtable): + if "SVBNVLLA" not in vtable["#modules"]: + raise ValueError("Cannot use unary minus without 'SVBNVLLA' module") vtable, val = self.expr.eval(vtable) return vtable, ValInt(-val.value()) @@ -629,15 +639,16 @@ class BuiltIn(Node): def _eval(self, vtable): params = [p.eval(vtable)[1] for p in self.parameters] magnvm = "MAGNVM" in vtable["#modules"] + svbnvlla = "SVBNVLLA" in vtable["#modules"] match self.builtin: case "AVDI_NVMERVS": - return vtable, ValInt(num_to_int(input(), magnvm)) + return vtable, ValInt(num_to_int(input(), magnvm, svbnvlla)) case "AVDI": return vtable, ValStr(input()) case "DICE": print_string = ' '.join( - make_string(i, magnvm) for i in params + make_string(i, magnvm, svbnvlla) for i in params ) print(print_string) return vtable, ValStr(print_string) diff --git a/tests.py b/tests.py index 3f7c67a..41ecd72 100644 --- a/tests.py +++ b/tests.py @@ -106,10 +106,10 @@ arithmetic_tests = [ ("X / III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(3)), # integer division: 10 // 3 = 3 ("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 - ("- III", Program([], [ExpressionStatement(UnaryMinus(Numeral("III")))]), ValInt(-3)), # unary negation - ("- (II + III)", Program([], [ExpressionStatement(UnaryMinus(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")))]), ValInt(-5)), # unary negation of expression - ("- - II", Program([], [ExpressionStatement(UnaryMinus(UnaryMinus(Numeral("II"))))]), ValInt(2)), # double negation - ("III + - II", Program([], [ExpressionStatement(BinOp(Numeral("III"), UnaryMinus(Numeral("II")), "SYMBOL_PLUS"))]), ValInt(1)), # unary in binary context + ("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): @@ -149,16 +149,16 @@ precedence_tests = [ 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) - ("- II * III", - Program([], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_TIMES"))]), + ("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 - ("- II + III", - Program([], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_PLUS"))]), + ("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 - ("- [I, II, III][I]", - Program([], [ExpressionStatement(UnaryMinus(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I"))))]), + ("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 +: (arr[II]) + X = 2 + 10 = 12 ("[I, II, III][II] + X", @@ -376,7 +376,8 @@ error_tests = [ ("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", TypeError), # args to zero-param function ("SI NVLLVS TVNC { DESIGNA r VT I }", TypeError), # NVLLVS cannot be used as boolean ("[I, II][III]", IndexError), # index too high - ("[I, II][-I]", IndexError), # negative index + ("CVM SVBNVLLA\n[I, II][-I]", IndexError), # negative index + ("[I, II][-I]", ValueError), # negative value ] class TestErrors(unittest.TestCase): @@ -455,6 +456,14 @@ class TestNumerals(unittest.TestCase): with self.assertRaises(Exception): num_to_int("IM", False) + def test_negative_without_svbnvlla_raises(self): + with self.assertRaises(ValueError): + 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")]: