From 8d06407527021459dc9e967570324312e13f4620 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 25 Apr 2026 18:34:47 +0200 Subject: [PATCH] :goat: Zipping --- README.md | 10 ++++++ centvrion/ast_nodes.py | 23 +++++++++++++ centvrion/compiler/emit_expr.py | 6 ++++ centvrion/compiler/runtime/cent_runtime.c | 32 +++++++++++++++++++ centvrion/compiler/runtime/cent_runtime.h | 2 ++ centvrion/lexer.py | 2 ++ snippets/syntaxes/centvrion.sublime-syntax | 2 +- tests/03_test_builtins.py | 24 ++++++++++++++ tests/12_test_failures.py | 8 +++++ .../syntaxes/cent.tmLanguage.json | 2 +- 10 files changed, 109 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c6b710..6e69900 100644 --- a/README.md +++ b/README.md @@ -376,6 +376,16 @@ Returns a new array with the element at 1-based position `idx` removed. The inde Returns a new array with `value` inserted at 1-based position `idx`, shifting later elements one position to the right. The index must be an integer in the range `[I, LONGITVDO(array) + I]`; passing `LONGITVDO(array) + I` is equivalent to `ADDE`. +### NECTE +`NECTE(array1, array2)` + +Weaves two arrays together into a new array of two-element pair arrays. The two inputs must have equal length; mismatched lengths raise an error. + +### IVNGE +`IVNGE(keys, values)` + +Builds a dict by yoking two parallel arrays — the i-th element of `keys` becomes the key for the i-th element of `values`. The two arrays must have equal length. Keys must be strings or integers. If `keys` contains duplicates, the later value wins. + ### SENATVS `SENATVS(bool, ...)` or `SENATVS([bool])` diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index a98403d..95e8ded 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -1522,6 +1522,29 @@ class BuiltIn(Node): if idx < 1 or idx > len(items) + 1: raise CentvrionError(f"Index {idx} out of range for array of length {len(items)}") return vtable, ValList(items[:idx - 1] + [params[2]] + items[idx - 1:]) + case "NECTE": + if len(params) != 2: + raise CentvrionError("NECTE takes exactly II arguments") + if not isinstance(params[0], ValList) or not isinstance(params[1], ValList): + raise CentvrionError("NECTE requires two arrays") + a, b = list(params[0].value()), list(params[1].value()) + if len(a) != len(b): + raise CentvrionError("NECTE requires arrays of equal length") + return vtable, ValList([ValList([x, y]) for x, y in zip(a, b)]) + case "IVNGE": + if len(params) != 2: + raise CentvrionError("IVNGE takes exactly II arguments") + if not isinstance(params[0], ValList) or not isinstance(params[1], ValList): + raise CentvrionError("IVNGE requires two arrays") + keys, vals = list(params[0].value()), list(params[1].value()) + if len(keys) != len(vals): + raise CentvrionError("IVNGE requires arrays of equal length") + d = {} + for k, v in zip(keys, vals): + if not isinstance(k, (ValStr, ValInt)): + raise CentvrionError("Dict keys must be strings or integers") + d[k.value()] = v + return vtable, ValDict(d) case "ORDINA": if not isinstance(params[0], ValList): raise CentvrionError("ORDINA requires an array") diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index b53d3ed..e7c03af 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -322,6 +322,12 @@ def _emit_builtin(node, ctx): case "INSERE": lines.append(f"CentValue {tmp} = cent_insere({param_vars[0]}, {param_vars[1]}, {param_vars[2]});") + case "NECTE": + lines.append(f"CentValue {tmp} = cent_necte({param_vars[0]}, {param_vars[1]});") + + case "IVNGE": + lines.append(f"CentValue {tmp} = cent_ivnge({param_vars[0]}, {param_vars[1]});") + case "EVERRE": lines.append("cent_everre();") lines.append(f"CentValue {tmp} = cent_null();") diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index ff1eb9b..5bfc918 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -898,6 +898,38 @@ CentValue cent_insere(CentValue lst, CentValue idx, CentValue v) { return result; } +CentValue cent_necte(CentValue a, CentValue b) { + if (a.type != CENT_LIST || b.type != CENT_LIST) + cent_type_error("'NECTE' requires two arrays"); + if (a.lval.len != b.lval.len) + cent_runtime_error("'NECTE' requires arrays of equal length"); + int len = a.lval.len; + CentValue result = cent_list_new(len); + for (int i = 0; i < len; i++) { + CentValue pair = cent_list_new(2); + cent_list_push(&pair, a.lval.items[i]); + cent_list_push(&pair, b.lval.items[i]); + cent_list_push(&result, pair); + } + return result; +} + +CentValue cent_ivnge(CentValue keys, CentValue vals) { + if (keys.type != CENT_LIST || vals.type != CENT_LIST) + cent_type_error("'IVNGE' requires two arrays"); + if (keys.lval.len != vals.lval.len) + cent_runtime_error("'IVNGE' requires arrays of equal length"); + int len = keys.lval.len; + CentValue result = cent_dict_new(len); + for (int i = 0; i < len; i++) { + CentValue k = keys.lval.items[i]; + if (k.type != CENT_INT && k.type != CENT_STR) + cent_runtime_error("Dict keys must be strings or integers"); + cent_dict_set(&result, k, vals.lval.items[i]); + } + return result; +} + /* ------------------------------------------------------------------ */ /* Array helpers */ /* ------------------------------------------------------------------ */ diff --git a/centvrion/compiler/runtime/cent_runtime.h b/centvrion/compiler/runtime/cent_runtime.h index 58ed0b8..0bdc7f2 100644 --- a/centvrion/compiler/runtime/cent_runtime.h +++ b/centvrion/compiler/runtime/cent_runtime.h @@ -242,6 +242,8 @@ CentValue cent_ordina(CentValue lst); /* ORDINA */ CentValue cent_adde(CentValue lst, CentValue v); /* ADDE */ CentValue cent_tolle(CentValue lst, CentValue idx); /* TOLLE */ CentValue cent_insere(CentValue lst, CentValue idx, CentValue v); /* INSERE */ +CentValue cent_necte(CentValue a, CentValue b); /* NECTE */ +CentValue cent_ivnge(CentValue keys, CentValue vals); /* IVNGE */ CentValue cent_lege(CentValue path); /* LEGE */ void cent_scribe(CentValue path, CentValue content); /* SCRIBE */ void cent_adivnge(CentValue path, CentValue content); /* ADIVNGE */ diff --git a/centvrion/lexer.py b/centvrion/lexer.py index d87ade1..66c5dee 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -57,10 +57,12 @@ builtin_tokens = [("BUILTIN", i) for i in [ "FORTVITVS_NVMERVS", "FORTVITA_ELECTIO", "INSERE", + "IVNGE", "LITTERA", "LONGITVDO", "MAIVSCVLA", "MINVSCVLA", + "NECTE", "NVMERVS", "ORDINA", "SEMEN", diff --git a/snippets/syntaxes/centvrion.sublime-syntax b/snippets/syntaxes/centvrion.sublime-syntax index fd8b6fc..5cdb1d2 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(ADDE|ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|INSERE|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TOLLE|TYPVS)\b' + - match: '\b(ADDE|ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|INSERE|IVNGE|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|NECTE|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TOLLE|TYPVS)\b' scope: support.function.builtin.centvrion modules: diff --git a/tests/03_test_builtins.py b/tests/03_test_builtins.py index 1ed272c..13b9ebf 100644 --- a/tests/03_test_builtins.py +++ b/tests/03_test_builtins.py @@ -242,6 +242,30 @@ builtin_tests = [ ('MINVSCVLA("A,B!1")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("A,B!1")]))]), ValStr("a,b!1")), # MINVSCVLA round-trips MAIVSCVLA on lowercase input ('MINVSCVLA(MAIVSCVLA("hi"))', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [BuiltIn("MAIVSCVLA", [String("hi")])]))]), ValStr("hi")), + # NECTE: zip two integer arrays + ("NECTE([I, II, III], [IV, V, VI])", Program([], [ExpressionStatement(BuiltIn("NECTE", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), DataArray([Numeral("IV"), Numeral("V"), Numeral("VI")])]))]), ValList([ValList([ValInt(1), ValInt(4)]), ValList([ValInt(2), ValInt(5)]), ValList([ValInt(3), ValInt(6)])])), + # NECTE: empty + empty + ("NECTE([], [])", Program([], [ExpressionStatement(BuiltIn("NECTE", [DataArray([]), DataArray([])]))]), ValList([])), + # NECTE: single element + ("NECTE([I], [II])", Program([], [ExpressionStatement(BuiltIn("NECTE", [DataArray([Numeral("I")]), DataArray([Numeral("II")])]))]), ValList([ValList([ValInt(1), ValInt(2)])])), + # NECTE: mixed types (numerals paired with strings) + ('NECTE([I, II], ["a", "b"])', Program([], [ExpressionStatement(BuiltIn("NECTE", [DataArray([Numeral("I"), Numeral("II")]), DataArray([String("a"), String("b")])]))]), ValList([ValList([ValInt(1), ValStr("a")]), ValList([ValInt(2), ValStr("b")])])), + # NECTE: print form + ('DIC(NECTE([I, II], [III, IV]))', Program([], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("NECTE", [DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])]))]), ValStr("[[I III] [II IV]]"), "[[I III] [II IV]]\n"), + # NECTE: via variables + ("DESIGNA a VT [I, II]\nDESIGNA b VT [III, IV]\nNECTE(a, b)", Program([], [Designa(ID("a"), DataArray([Numeral("I"), Numeral("II")])), Designa(ID("b"), DataArray([Numeral("III"), Numeral("IV")])), ExpressionStatement(BuiltIn("NECTE", [ID("a"), ID("b")]))]), ValList([ValList([ValInt(1), ValInt(3)]), ValList([ValInt(2), ValInt(4)])])), + # IVNGE: string keys + ('IVNGE(["a", "b"], [I, II])', Program([], [ExpressionStatement(BuiltIn("IVNGE", [DataArray([String("a"), String("b")]), DataArray([Numeral("I"), Numeral("II")])]))]), ValDict({"a": ValInt(1), "b": ValInt(2)})), + # IVNGE: integer keys + ('IVNGE([I, II], ["one", "two"])', Program([], [ExpressionStatement(BuiltIn("IVNGE", [DataArray([Numeral("I"), Numeral("II")]), DataArray([String("one"), String("two")])]))]), ValDict({1: ValStr("one"), 2: ValStr("two")})), + # IVNGE: empty + empty + ("IVNGE([], [])", Program([], [ExpressionStatement(BuiltIn("IVNGE", [DataArray([]), DataArray([])]))]), ValDict({})), + # IVNGE: duplicate keys → last wins + ('IVNGE(["a", "a"], [I, II])', Program([], [ExpressionStatement(BuiltIn("IVNGE", [DataArray([String("a"), String("a")]), DataArray([Numeral("I"), Numeral("II")])]))]), ValDict({"a": ValInt(2)})), + # IVNGE: print form + ('DIC(IVNGE(["a", "b"], [I, II]))', Program([], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("IVNGE", [DataArray([String("a"), String("b")]), DataArray([Numeral("I"), Numeral("II")])])]))]), ValStr("{a VT I, b VT II}"), "{a VT I, b VT II}\n"), + # IVNGE composes with CLAVES (round-trip keys) + ('CLAVES(IVNGE(["a", "b"], [I, II]))', Program([], [ExpressionStatement(BuiltIn("CLAVES", [BuiltIn("IVNGE", [DataArray([String("a"), String("b")]), DataArray([Numeral("I"), Numeral("II")])])]))]), ValList([ValStr("a"), ValStr("b")])), ] class TestBuiltins(unittest.TestCase): diff --git a/tests/12_test_failures.py b/tests/12_test_failures.py index 2e84382..695087b 100644 --- a/tests/12_test_failures.py +++ b/tests/12_test_failures.py @@ -132,6 +132,14 @@ error_tests = [ ('CVM RETE\nPETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})\nAVSCVLTA("text")', CentvrionError), # AVSCVLTA port must be integer ("DONICVM i VT I VSQVE X GRADV I - I FAC { DIC(i) }", CentvrionError), # GRADV zero step ('DONICVM i VT I VSQVE X GRADV "foo" FAC { DIC(i) }', CentvrionError), # GRADV non-integer step + ("NECTE([I, II], [III])", CentvrionError), # NECTE length mismatch + ('NECTE(I, [II])', CentvrionError), # NECTE first arg not a list + ('NECTE([I], II)', CentvrionError), # NECTE second arg not a list + ("IVNGE([I, II], [III])", CentvrionError), # IVNGE length mismatch + ('IVNGE(I, [II])', CentvrionError), # IVNGE first arg not a list + ('IVNGE(["a"], II)', CentvrionError), # IVNGE second arg not a list + ("IVNGE([VERITAS], [I])", CentvrionError), # IVNGE invalid key type (bool) + ("IVNGE([[I]], [II])", CentvrionError), # IVNGE invalid key type (list) ] class TestErrors(unittest.TestCase): diff --git a/vscode-extension/syntaxes/cent.tmLanguage.json b/vscode-extension/syntaxes/cent.tmLanguage.json index 3806be8..6f820ba 100644 --- a/vscode-extension/syntaxes/cent.tmLanguage.json +++ b/vscode-extension/syntaxes/cent.tmLanguage.json @@ -65,7 +65,7 @@ "patterns": [ { "name": "support.function.builtin.cent", - "match": "\\b(ADDE|ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|INSERE|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TOLLE|TYPVS)\\b" + "match": "\\b(ADDE|ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|INSERE|IVNGE|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|NECTE|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TOLLE|TYPVS)\\b" } ] },