diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index bdd66a4..751c372 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -129,7 +129,7 @@ def emit_expr(node, ctx): if isinstance(node, UnaryMinus): inner_lines, inner_var = emit_expr(node.expr, ctx) tmp = ctx.fresh_tmp() - return inner_lines + [f"CentValue {tmp} = cent_int(-{inner_var}.ival);"], tmp + return inner_lines + [f"CentValue {tmp} = cent_neg({inner_var});"], tmp if isinstance(node, UnaryNot): inner_lines, inner_var = emit_expr(node.expr, ctx) diff --git a/centvrion/compiler/emitter.py b/centvrion/compiler/emitter.py index 5ce1bcd..4ca6768 100644 --- a/centvrion/compiler/emitter.py +++ b/centvrion/compiler/emitter.py @@ -92,6 +92,8 @@ def compile_program(program): lines.append(" cent_init();") if "MAGNVM" in ctx.modules: lines.append(" cent_magnvm = 1;") + if "SVBNVLLA" in ctx.modules: + lines.append(" cent_svbnvlla = 1;") lines.append(" CentScope _scope = {0};") lines.append(" CentValue _return_val = cent_null();") diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index 3248ff2..10c6232 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -15,6 +15,7 @@ CentArena *cent_arena; int cent_magnvm = 0; +int cent_svbnvlla = 0; /* ------------------------------------------------------------------ */ /* Portable xorshift32 RNG (matches Python _CentRng) */ @@ -181,7 +182,15 @@ void cent_int_to_roman(long n, char *buf, size_t bufsz) { if (bufsz > 6) { memcpy(buf, "NVLLVS", 6); buf[6] = '\0'; } return; } - if (n < 0 || (n > 3999 && !cent_magnvm)) + if (n < 0) { + if (!cent_svbnvlla) + cent_runtime_error("number out of range for Roman numerals"); + if (bufsz < 2) cent_runtime_error("Roman numeral buffer overflow"); + buf[0] = '-'; + cent_int_to_roman(-n, buf + 1, bufsz - 1); + return; + } + if (n > 3999 && !cent_magnvm) cent_runtime_error("number out of range for Roman numerals"); size_t pos = 0; if (n > 3999) { @@ -271,8 +280,13 @@ static int write_val(CentValue v, char *buf, int bufsz) { case CENT_FRAC: { long num = v.fval.num, den = v.fval.den; if (den < 0) { num = -num; den = -den; } - if (num < 0) - cent_runtime_error("cannot display negative numbers without SVBNVLLA"); + int negative = 0; + if (num < 0) { + if (!cent_svbnvlla) + cent_runtime_error("cannot display negative numbers without SVBNVLLA"); + negative = 1; + num = -num; + } long int_part = num / den; long rem_num = num % den; @@ -297,11 +311,13 @@ static int write_val(CentValue v, char *buf, int bufsz) { for (int i = 0; i < (int)((level_int % 6) % 2); i++) frac_buf[frac_pos++] = '.'; } - n = int_len + frac_pos; + n = int_len + frac_pos + (negative ? 1 : 0); if (buf && n < bufsz) { - memcpy(buf, int_buf, int_len); - memcpy(buf + int_len, frac_buf, frac_pos); - buf[n] = '\0'; + int pos = 0; + if (negative) buf[pos++] = '-'; + memcpy(buf + pos, int_buf, int_len); pos += int_len; + memcpy(buf + pos, frac_buf, frac_pos); pos += frac_pos; + buf[pos] = '\0'; } return n; } @@ -391,6 +407,13 @@ static void to_frac(CentValue v, long *num, long *den) { else { *num = v.fval.num; *den = v.fval.den; } } +CentValue cent_neg(CentValue v) { + if (v.type == CENT_INT) return cent_int(-v.ival); + if (v.type == CENT_FRAC) return frac_reduce(-v.fval.num, v.fval.den); + cent_type_error("Unary minus requires a number"); + return cent_null(); +} + CentValue cent_add(CentValue a, CentValue b) { if (a.type == CENT_INT && b.type == CENT_INT) return cent_int(a.ival + b.ival); diff --git a/centvrion/compiler/runtime/cent_runtime.h b/centvrion/compiler/runtime/cent_runtime.h index a6c8e3b..22ef77d 100644 --- a/centvrion/compiler/runtime/cent_runtime.h +++ b/centvrion/compiler/runtime/cent_runtime.h @@ -97,6 +97,9 @@ extern CentArena *cent_arena; /* Set to 1 when CVM MAGNVM is active; enables extended numeral display */ extern int cent_magnvm; +/* Set to 1 when CVM SVBNVLLA is active; enables negative number display */ +extern int cent_svbnvlla; + /* ------------------------------------------------------------------ */ /* Value constructors */ /* ------------------------------------------------------------------ */ @@ -197,6 +200,7 @@ char *cent_make_string(CentValue v); /* Arithmetic and comparison operators */ /* ------------------------------------------------------------------ */ +CentValue cent_neg(CentValue v); /* unary minus: INT or FRAC */ CentValue cent_add(CentValue a, CentValue b); /* INT+INT or FRAC+FRAC/INT */ CentValue cent_array_concat(CentValue a, CentValue b); /* @ operator: concatenate two arrays */ CentValue cent_concat(CentValue a, CentValue b); /* & operator: coerce all types to str */ diff --git a/tests.py b/tests.py index bd65d74..9143e4e 100644 --- a/tests.py +++ b/tests.py @@ -1747,6 +1747,51 @@ class TestMAGNVM(unittest.TestCase): 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) + + # --- ET and AVT (boolean and/or) --- et_avt_tests = [