From c55a63f46c91382191c233d7babe831da291c3e4 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Wed, 22 Apr 2026 12:35:00 +0200 Subject: [PATCH] :goat: NVLLVUS fix --- centvrion/ast_nodes.py | 11 ++++++++++- centvrion/compiler/runtime/cent_runtime.c | 11 +++++++++-- tests.py | 17 ++++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index b508889..7539044 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -133,8 +133,11 @@ def int_to_num(n, m, s=False) -> str: for i in thousands_chars ]) - return thousands + int_to_num(n % 1000, m, s) + remainder = n % 1000 + return thousands + (int_to_num(remainder, m, s) if remainder else "") else: + if n == 0: + return "NVLLVS" nums = [] while n > 0: for num, i in list(NUMERALS.items())[::-1]: @@ -779,8 +782,14 @@ class BinOp(Node): raise CentvrionError("Cannot compare strings or arrays with PLVS") return vtable, ValBool((lv or 0) > (rv or 0)) case "KEYWORD_EST": + if ((isinstance(left, ValInt) and lv == 0 and isinstance(right, ValNul)) or + (isinstance(left, ValNul) and isinstance(right, ValInt) and rv == 0)): + return vtable, ValBool(True) return vtable, ValBool(lv == rv) case "KEYWORD_DISPAR": + if ((isinstance(left, ValInt) and lv == 0 and isinstance(right, ValNul)) or + (isinstance(left, ValNul) and isinstance(right, ValInt) and rv == 0)): + return vtable, ValBool(False) return vtable, ValBool(lv != rv) case "KEYWORD_ET": return vtable, ValBool(bool(left) and bool(right)) diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index bcb0622..afb9012 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -177,8 +177,12 @@ static void transform_thousands(const char *src, char *dst, size_t dstsz) { } void cent_int_to_roman(long n, char *buf, size_t bufsz) { - if (n <= 0 || (n > 3999 && !cent_magnvm)) - cent_runtime_error("number out of range for Roman numerals (1-3999)"); + if (n == 0) { + if (bufsz > 6) { memcpy(buf, "NVLLVS", 6); buf[6] = '\0'; } + return; + } + if (n < 0 || (n > 3999 && !cent_magnvm)) + cent_runtime_error("number out of range for Roman numerals"); size_t pos = 0; if (n > 3999) { char base[64]; @@ -480,6 +484,9 @@ CentValue cent_mod_frac(CentValue a, CentValue b) { } CentValue cent_eq(CentValue a, CentValue b) { + if ((a.type == CENT_INT && a.ival == 0 && b.type == CENT_NULL) || + (a.type == CENT_NULL && b.type == CENT_INT && b.ival == 0)) + return cent_bool(1); if ((a.type == CENT_INT || a.type == CENT_FRAC) && (b.type == CENT_INT || b.type == CENT_FRAC)) { long an, ad, bn, bd; diff --git a/tests.py b/tests.py index 1becd99..9e69873 100644 --- a/tests.py +++ b/tests.py @@ -881,7 +881,7 @@ class TestNumerals(unittest.TestCase): # 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")]: + 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): @@ -907,6 +907,9 @@ class TestMakeString(unittest.TestCase): def test_int(self): self.assertEqual(make_string(ValInt(3)), "III") + def test_int_zero(self): + self.assertEqual(make_string(ValInt(0)), "NVLLVS") + def test_bool_true(self): self.assertEqual(make_string(ValBool(True)), "VERITAS") @@ -939,6 +942,8 @@ dic_type_tests = [ ('DIC("")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("")]))]), ValStr(""), "\n"), # arithmetic result printed as numeral ("DIC(II + III)", Program([], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")]))]), ValStr("V"), "V\n"), + # integer 0 prints as NVLLVS + ("DIC(I - I)", Program([], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("NVLLVS"), "NVLLVS\n"), # multiple args of mixed types ('DIC("x", VERITAS)', Program([], [ExpressionStatement(BuiltIn("DIC", [String("x"), Bool(True)]))]), ValStr("x VERITAS"), "x VERITAS\n"), ] @@ -1062,6 +1067,8 @@ interpolation_tests = [ Program([], [ ExpressionStatement(InterpolatedString([String("value: "), Nullus()])) ]), ValStr("value: NVLLVS")), + # integer 0 interpolates as NVLLVS + ('"value: {I - I}"', Program([], [ExpressionStatement(InterpolatedString([String("value: "), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("value: NVLLVS")), # expression-only string (no literal parts around it) ('DESIGNA x VT "hi"\n"{x}"', Program([], [ @@ -1194,6 +1201,14 @@ comparison_tests = [ ("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)), + # integer 0 equals NVLLVS + ("(I - I) EST NVLLVS", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), Nullus(), "KEYWORD_EST"))]), ValBool(True)), + ("NVLLVS EST (I - I)", Program([], [ExpressionStatement(BinOp(Nullus(), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), "KEYWORD_EST"))]), ValBool(True)), + ("(I - I) DISPAR NVLLVS", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), Nullus(), "KEYWORD_DISPAR"))]), ValBool(False)), + ("NVLLVS DISPAR (I - I)", Program([], [ExpressionStatement(BinOp(Nullus(), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), "KEYWORD_DISPAR"))]), ValBool(False)), + # non-zero integer does not equal NVLLVS + ("I EST NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("I"), Nullus(), "KEYWORD_EST"))]), ValBool(False)), + ("NVLLVS DISPAR I", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("I"), "KEYWORD_DISPAR"))]), ValBool(True)), ] class TestComparisons(unittest.TestCase):