From e61009b6efc60812389b15b67b090ae32c78ad60 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Tue, 21 Apr 2026 21:48:56 +0200 Subject: [PATCH] :goat: ORDINA --- README.md | 5 ++++ centvrion/ast_nodes.py | 11 ++++++++ centvrion/compiler/emit_expr.py | 3 +++ centvrion/compiler/runtime/cent_runtime.c | 30 ++++++++++++++++++++++ centvrion/compiler/runtime/cent_runtime.h | 1 + centvrion/lexer.py | 1 + snippets/syntaxes/centvrion.sublime-syntax | 2 +- tests.py | 23 +++++++++++++++++ 8 files changed, 75 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9122943..c744c7e 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,11 @@ Returns the length of `array` (element count), `string` (character count), or `d Returns the keys of `dict` as an array. +### ORDINA +`ORDINA(array)` + +Sorts an array in ascending order. Returns a new sorted array. All elements must be the same type — integers, fractions, or strings. Integers and fractions sort numerically; strings sort lexicographically. + ### SENATVS `SENATVS(bool, ...)` or `SENATVS([bool])` diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index 2bd341b..8244a54 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -1122,6 +1122,17 @@ class BuiltIn(Node): case "EVERRO": print("\033[2J\033[H", end="", flush=True) return vtable, ValNul() + case "ORDINA": + if not isinstance(params[0], ValList): + raise CentvrionError("ORDINA requires an array") + items = list(params[0].value()) + if not items: + return vtable, ValList([]) + all_numeric = all(isinstance(i, (ValInt, ValFrac)) for i in items) + all_string = all(isinstance(i, ValStr) for i in items) + if not (all_numeric or all_string): + raise CentvrionError("ORDINA requires all elements to be numbers or all strings") + return vtable, ValList(sorted(items, key=lambda v: v.value())) case "TYPVS": type_map = { ValInt: "NVMERVS", ValStr: "LITTERA", ValBool: "VERAX", diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index 105e2b6..dbe847b 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -251,6 +251,9 @@ def _emit_builtin(node, ctx): case "CLAVES": lines.append(f"CentValue {tmp} = cent_dict_keys({param_vars[0]});") + case "ORDINA": + lines.append(f"CentValue {tmp} = cent_ordina({param_vars[0]});") + case "EVERRO": lines.append("cent_everro();") lines.append(f"CentValue {tmp} = cent_null();") diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index 2e44ca0..6002bec 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -633,6 +633,36 @@ void cent_semen(CentValue seed) { srand((unsigned)seed.ival); } +static int _ordina_comparator(const void *a, const void *b) { + const CentValue *va = (const CentValue *)a; + const CentValue *vb = (const CentValue *)b; + if ((va->type == CENT_INT || va->type == CENT_FRAC) && + (vb->type == CENT_INT || vb->type == CENT_FRAC)) { + long an, ad, bn, bd; + to_frac(*va, &an, &ad); + to_frac(*vb, &bn, &bd); + long lhs = an * bd; + long rhs = bn * ad; + return (lhs > rhs) - (lhs < rhs); + } + if (va->type == CENT_STR && vb->type == CENT_STR) + return strcmp(va->sval, vb->sval); + cent_type_error("'ORDINA' requires all elements to be the same type"); + return 0; +} + +CentValue cent_ordina(CentValue lst) { + if (lst.type != CENT_LIST) + cent_type_error("'ORDINA' requires a list"); + int len = lst.lval.len; + CentValue result = cent_list_new(len); + for (int i = 0; i < len; i++) + cent_list_push(&result, lst.lval.items[i]); + if (len > 1) + qsort(result.lval.items, len, sizeof(CentValue), _ordina_comparator); + return result; +} + /* ------------------------------------------------------------------ */ /* Array helpers */ /* ------------------------------------------------------------------ */ diff --git a/centvrion/compiler/runtime/cent_runtime.h b/centvrion/compiler/runtime/cent_runtime.h index b0d9453..7145b28 100644 --- a/centvrion/compiler/runtime/cent_runtime.h +++ b/centvrion/compiler/runtime/cent_runtime.h @@ -222,6 +222,7 @@ void cent_everro(void); /* EVERRO */ CentValue cent_senatus(CentValue *args, int n); /* SENATVS */ CentValue cent_typvs(CentValue v); /* TYPVS */ void cent_dormi(CentValue n); /* DORMI */ +CentValue cent_ordina(CentValue lst); /* ORDINA */ /* ------------------------------------------------------------------ */ /* Array helpers */ diff --git a/centvrion/lexer.py b/centvrion/lexer.py index 73b6fec..4d96e77 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -49,6 +49,7 @@ builtin_tokens = [("BUILTIN", i) for i in [ "FORTIS_NVMERVS", "FORTIS_ELECTIONIS", "LONGITVDO", + "ORDINA", "SEMEN", "SENATVS", "TYPVS" diff --git a/snippets/syntaxes/centvrion.sublime-syntax b/snippets/syntaxes/centvrion.sublime-syntax index c794c8b..ebce99e 100644 --- a/snippets/syntaxes/centvrion.sublime-syntax +++ b/snippets/syntaxes/centvrion.sublime-syntax @@ -70,7 +70,7 @@ contexts: scope: constant.language.centvrion builtins: - - match: '\b(AVDI_NVMERVS|AVDI|CLAVES|DECIMATIO|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN|SENATVS)\b' + - match: '\b(AVDI_NVMERVS|AVDI|CLAVES|DECIMATIO|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|ORDINA|SEMEN|SENATVS)\b' scope: support.function.builtin.centvrion modules: diff --git a/tests.py b/tests.py index bfa245b..1c3bf1a 100644 --- a/tests.py +++ b/tests.py @@ -568,6 +568,26 @@ builtin_tests = [ ("SENATVS([FALSITAS, FALSITAS, VERITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(False), Bool(False), Bool(True)])]))]), ValBool(False)), # SENATVS: array input, empty → FALSITAS ("SENATVS([])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([])]))]), ValBool(False)), + # ORDINA: sort integers + ("ORDINA([III, I, II])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Numeral("I"), Numeral("II")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), + # ORDINA: sort strings + ('ORDINA(["c", "a", "b"])', Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([String("c"), String("a"), String("b")])]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])), + # ORDINA: empty list + ("ORDINA([])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([])]))]), ValList([])), + # ORDINA: single element + ("ORDINA([V])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("V")])]))]), ValList([ValInt(5)])), + # ORDINA: already sorted + ("ORDINA([I, II, III])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), + # ORDINA: duplicates + ("ORDINA([II, I, II])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("II"), Numeral("I"), Numeral("II")])]))]), ValList([ValInt(1), ValInt(2), ValInt(2)])), + # ORDINA: negative numbers + ("CVM SVBNVLLA\nORDINA([-II, III, -I])", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([UnaryMinus(Numeral("II")), Numeral("III"), UnaryMinus(Numeral("I"))])]))]), ValList([ValInt(-2), ValInt(-1), ValInt(3)])), + # ORDINA: fractions only + ("CVM FRACTIO\nORDINA([IIIS, S, IIS])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Fractio("IIIS"), Fractio("S"), Fractio("IIS")])]))]), ValList([ValFrac(Fraction(1, 2)), ValFrac(Fraction(5, 2)), ValFrac(Fraction(7, 2))])), + # ORDINA: mixed integers and fractions + ("CVM FRACTIO\nORDINA([III, S, II])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Fractio("S"), Numeral("II")])]))]), ValList([ValFrac(Fraction(1, 2)), ValInt(2), ValInt(3)])), + # ORDINA: array passed via variable + ("DESIGNA x VT [III, I, II]\nORDINA(x)", Program([], [Designa(ID("x"), DataArray([Numeral("III"), Numeral("I"), Numeral("II")])), ExpressionStatement(BuiltIn("ORDINA", [ID("x")]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])), # TYPVS: integer ("TYPVS(V)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Numeral("V")]))]), ValStr("NVMERVS")), # TYPVS: string @@ -629,6 +649,9 @@ error_tests = [ ("DECIMATIO([I, II, III])", CentvrionError), # FORS required for DECIMATIO ("CVM FORS\nDECIMATIO(I)", CentvrionError), # DECIMATIO requires an array ("LONGITVDO(I)", CentvrionError), # LONGITVDO on non-array + ("ORDINA(I)", CentvrionError), # ORDINA on non-array + ('ORDINA([I, "a"])', CentvrionError), # ORDINA mixed types + ("DESIGNA x VT I\nORDINA(x)", CentvrionError), # ORDINA on id (non-array) ("SENATVS(I)", CentvrionError), # SENATVS requires booleans ("SENATVS(VERITAS, I)", CentvrionError), # SENATVS mixed types ("SENATVS([I, II, III])", CentvrionError), # SENATVS array of non-bools