Compare commits

...

6 Commits

Author SHA1 Message Date
6413d9fd1c 🐐 SENATVS 2026-04-21 15:52:58 +02:00
f62a7dda1c 🐐 SENATVS 2026-04-21 15:49:53 +02:00
861448cb04 🐐 DECIMATIO 2026-04-21 15:18:39 +02:00
63c35605a2 🐐 Unpacking arrays 2026-04-21 14:54:36 +02:00
c28ffbbf45 🐐 Compound assignment 2026-04-21 14:39:42 +02:00
ad46f189c0 🐐 SEMEN 2026-04-21 14:24:48 +02:00
15 changed files with 328 additions and 9 deletions

View File

@@ -22,6 +22,33 @@ Variables are set with the `DESIGNA` and `VT` keywords. Type is inferred.
Variable can consist of lower-case letters, numbers, as well as `_`. Variable can consist of lower-case letters, numbers, as well as `_`.
### Compound assignment
`AVGE` (+=) and `MINVE` (-=) are shorthand for incrementing or decrementing a variable:
![Compound assignment](snippets/compound.png)
```
> VIII
```
`x AVGE III` is equivalent to `DESIGNA x VT x + III`.
### Destructuring
Multiple variables can be assigned at once by unpacking an array or multi-return function:
```
DEFINI pair (a, b) VT { REDI (a, b) }
DESIGNA x, y VT INVOCA pair (III, VII)
```
The number of targets must match the length of the array. This also works with array literals:
```
DESIGNA a, b, c VT [I, II, III]
```
## Data types ## Data types
### NVLLVS ### NVLLVS
`NVLLVS` is a special kind of data type in `CENTVRION`, similar to the `null` value in many other languages. `NVLLVS` can be 0 if evaluated as an int or float, or an empty string if evaluated as a string. `NVLLVS` cannot be evaluated as a boolean. `NVLLVS` is a special kind of data type in `CENTVRION`, similar to the `null` value in many other languages. `NVLLVS` can be 0 if evaluated as an int or float, or an empty string if evaluated as a string. `NVLLVS` cannot be evaluated as a boolean.
@@ -209,6 +236,11 @@ Breaks out of the current loop (`DVM` or `PER`). Has no meaningful return value.
Returns the length of `array` (element count) or `string` (character count) as an integer. Returns the length of `array` (element count) or `string` (character count) as an integer.
### SENATVS
`SENATVS(bool, ...)` or `SENATVS([bool])`
Returns VERITAS if a strict majority of the arguments are VERITAS, FALSITAS otherwise. Also accepts a single array of booleans. All values must be booleans. Ties return FALSITAS.
## Modules ## Modules
Modules are additions to the base `CENTVRION` syntax. They add or change certain features. Modules are included in your code by having Modules are additions to the base `CENTVRION` syntax. They add or change certain features. Modules are included in your code by having
@@ -221,12 +253,16 @@ Vnlike many other programming languages with modules, the modules in `CENTVRION`
### FORS ### FORS
![CVM FORS](snippets/fors.png) ![CVM FORS](snippets/fors.png)
The `FORS` module allows you to add randomness to your `CENTVRION` program. It adds 2 new built-in functions: `FORTIS_NVMERVS int int` and `FORTIS_ELECTIONIS ['a]`. The `FORS` module allows you to add randomness to your `CENTVRION` program. It adds 4 new built-in functions: `FORTIS_NVMERVS int int`, `FORTIS_ELECTIONIS ['a]`, `DECIMATIO ['a]`, and `SEMEN int`.
`FORTIS_NVMERVS int int` picks a random int in the (inclusive) range of the two given ints. `FORTIS_NVMERVS int int` picks a random int in the (inclusive) range of the two given ints.
`FORTIS_ELECTIONIS ['a]` picks a random element from the given array. `FORTIS_ELECTIONIS array` is identical to ```array[FORTIS_NVMERVS NVLLVS ((LONGITVDO array)-I)]```. `FORTIS_ELECTIONIS ['a]` picks a random element from the given array. `FORTIS_ELECTIONIS array` is identical to ```array[FORTIS_NVMERVS NVLLVS ((LONGITVDO array)-I)]```.
`DECIMATIO ['a]` returns a copy of the given array with a random tenth of its elements removed. Arrays with fewer than 10 elements are returned unchanged.
`SEMEN int` seeds the random number generator for reproducibility.
### FRACTIO ### FRACTIO
![CVM FRACTIO](snippets/fractio.png) ![CVM FRACTIO](snippets/fractio.png)

View File

@@ -431,6 +431,35 @@ class DesignaIndex(Node):
return vtable, ValNul() return vtable, ValNul()
class DesignaDestructure(Node):
def __init__(self, variables: list, value: Node) -> None:
self.ids = variables
self.value = value
def __eq__(self, other):
return type(self) == type(other) and self.ids == other.ids and self.value == other.value
def __repr__(self) -> str:
ids_string = ", ".join(repr(i) for i in self.ids)
value_string = repr(self.value).replace('\n', '\n ')
return f"DesignaDestructure(\n [{ids_string}],\n {value_string}\n)"
def print(self):
ids_str = ", ".join(i.print() for i in self.ids)
return f"DESIGNA {ids_str} VT {self.value.print()}"
def _eval(self, vtable):
vtable, val = self.value.eval(vtable)
if not isinstance(val, ValList):
raise CentvrionError("Cannot destructure non-array value")
if len(val.value()) != len(self.ids):
raise CentvrionError(
f"Destructuring mismatch: {len(self.ids)} targets, {len(val.value())} values")
for id_node, item in zip(self.ids, val.value()):
vtable[id_node.name] = item
return vtable, ValNul()
class Defini(Node): class Defini(Node):
def __init__(self, name: ID, parameters: list[ID], statements: list[Node]) -> None: def __init__(self, name: ID, parameters: list[ID], statements: list[Node]) -> None:
self.name = name self.name = name
@@ -920,6 +949,34 @@ class BuiltIn(Node):
if len(lst) == 0: if len(lst) == 0:
raise CentvrionError("FORTIS_ELECTIONIS: cannot select from an empty array") raise CentvrionError("FORTIS_ELECTIONIS: cannot select from an empty array")
return vtable, lst[random.randint(0, len(lst) - 1)] return vtable, lst[random.randint(0, len(lst) - 1)]
case "SEMEN":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'SEMEN' without module 'FORS'")
seed = params[0].value()
if not isinstance(seed, int):
raise CentvrionError("SEMEN requires an integer seed")
random.seed(seed)
return vtable, ValNul()
case "DECIMATIO":
if "FORS" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'DECIMATIO' without module 'FORS'")
if not isinstance(params[0], ValList):
raise CentvrionError("DECIMATIO requires an array")
arr = list(params[0].value())
to_remove = len(arr) // 10
for _ in range(to_remove):
arr.pop(random.randint(0, len(arr) - 1))
return vtable, ValList(arr)
case "SENATVS":
if len(params) == 1 and isinstance(params[0], ValList):
items = params[0].value()
else:
items = params
for p in items:
if not isinstance(p, ValBool):
raise CentvrionError("SENATVS requires boolean arguments")
true_count = sum(1 for p in items if p.value())
return vtable, ValBool(true_count > len(items) / 2)
case "LONGITVDO": case "LONGITVDO":
if isinstance(params[0], (ValList, ValStr)): if isinstance(params[0], (ValList, ValStr)):
return vtable, ValInt(len(params[0].value())) return vtable, ValInt(len(params[0].value()))

View File

@@ -186,6 +186,29 @@ def _emit_builtin(node, ctx):
else: else:
lines.append(f"CentValue {tmp} = cent_fortis_electionis({param_vars[0]});") lines.append(f"CentValue {tmp} = cent_fortis_electionis({param_vars[0]});")
case "DECIMATIO":
if not ctx.has_module("FORS"):
lines.append('cent_runtime_error("FORS module required for DECIMATIO");')
lines.append(f"CentValue {tmp} = cent_null();")
else:
lines.append(f"CentValue {tmp} = cent_decimatio({param_vars[0]});")
case "SEMEN":
if not ctx.has_module("FORS"):
lines.append('cent_runtime_error("FORS module required for SEMEN");')
lines.append(f"CentValue {tmp} = cent_null();")
else:
lines.append(f"cent_semen({param_vars[0]});")
lines.append(f"CentValue {tmp} = cent_null();")
case "SENATVS":
if param_vars:
arr_tmp = ctx.fresh_tmp() + "_arr"
lines.append(f"CentValue {arr_tmp}[] = {{{', '.join(param_vars)}}};")
lines.append(f"CentValue {tmp} = cent_senatus({arr_tmp}, {len(param_vars)});")
else:
lines.append(f"CentValue {tmp} = cent_senatus(NULL, 0);")
case "ERVMPE": case "ERVMPE":
# break as expression (side-effecting; result is unused) # break as expression (side-effecting; result is unused)
lines.append("break;") lines.append("break;")

View File

@@ -1,6 +1,6 @@
from centvrion.ast_nodes import ( from centvrion.ast_nodes import (
Designa, DesignaIndex, SiStatement, DumStatement, PerStatement, Designa, DesignaIndex, DesignaDestructure, SiStatement, DumStatement,
Defini, Redi, Erumpe, Continva, ExpressionStatement, ID, PerStatement, Defini, Redi, Erumpe, Continva, ExpressionStatement, ID,
) )
from centvrion.compiler.emit_expr import emit_expr from centvrion.compiler.emit_expr import emit_expr
@@ -29,6 +29,18 @@ def emit_stmt(node, ctx):
] ]
) )
if isinstance(node, DesignaDestructure):
n = len(node.ids)
val_lines, val_var = emit_expr(node.value, ctx)
lines = val_lines[:]
lines.append(f'if ({val_var}.type != CENT_LIST) cent_type_error("Cannot destructure non-array value");')
lines.append(f'if ({val_var}.lval.len != {n}) cent_runtime_error("Destructuring mismatch");')
for i, id_node in enumerate(node.ids):
tmp = ctx.fresh_tmp()
lines.append(f"CentValue {tmp} = cent_list_index({val_var}, cent_int({i + 1}));")
lines.append(f'cent_scope_set(&_scope, "{id_node.name}", {tmp});')
return lines
if isinstance(node, SiStatement): if isinstance(node, SiStatement):
cond_lines, cond_var = emit_expr(node.test, ctx) cond_lines, cond_var = emit_expr(node.test, ctx)
then_lines = _emit_body(node.statements, ctx) then_lines = _emit_body(node.statements, ctx)

View File

@@ -533,6 +533,46 @@ CentValue cent_fortis_electionis(CentValue lst) {
return lst.lval.items[rand() % lst.lval.len]; return lst.lval.items[rand() % lst.lval.len];
} }
CentValue cent_senatus(CentValue *args, int n) {
/* Single array argument: unpack it */
if (n == 1 && args[0].type == CENT_LIST) {
n = args[0].lval.len;
args = args[0].lval.items;
}
int true_count = 0;
for (int i = 0; i < n; i++) {
if (args[i].type != CENT_BOOL)
cent_type_error("'SENATVS' requires boolean arguments");
if (args[i].bval) true_count++;
}
return cent_bool(true_count * 2 > n);
}
CentValue cent_decimatio(CentValue lst) {
if (lst.type != CENT_LIST)
cent_type_error("'DECIMATIO' requires a list");
int len = lst.lval.len;
/* Copy the list so we can remove in-place */
CentValue result = cent_list_new(len);
for (int i = 0; i < len; i++)
cent_list_push(&result, lst.lval.items[i]);
int to_remove = result.lval.len / 10;
for (int i = 0; i < to_remove; i++) {
int idx = rand() % result.lval.len;
/* Shift remaining elements left */
for (int j = idx; j < result.lval.len - 1; j++)
result.lval.items[j] = result.lval.items[j + 1];
result.lval.len--;
}
return result;
}
void cent_semen(CentValue seed) {
if (seed.type != CENT_INT)
cent_type_error("'SEMEN' requires an integer seed");
srand((unsigned)seed.ival);
}
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Array helpers */ /* Array helpers */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */

View File

@@ -177,7 +177,10 @@ CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */
CentValue cent_longitudo(CentValue v); /* LONGITVDO */ CentValue cent_longitudo(CentValue v); /* LONGITVDO */
CentValue cent_fortis_numerus(CentValue lo, CentValue hi); /* FORTIS_NVMERVS */ CentValue cent_fortis_numerus(CentValue lo, CentValue hi); /* FORTIS_NVMERVS */
CentValue cent_fortis_electionis(CentValue lst); /* FORTIS_ELECTIONIS */ CentValue cent_fortis_electionis(CentValue lst); /* FORTIS_ELECTIONIS */
CentValue cent_decimatio(CentValue lst); /* DECIMATIO */
void cent_semen(CentValue seed); /* SEMEN */
void cent_everro(void); /* EVERRO */ void cent_everro(void); /* EVERRO */
CentValue cent_senatus(CentValue *args, int n); /* SENATVS */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Array helpers */ /* Array helpers */

View File

@@ -5,6 +5,7 @@ valid_characters = '|'.join(list("abcdefghiklmnopqrstvxyz_"))
keyword_tokens = [("KEYWORD_"+i, i) for i in [ keyword_tokens = [("KEYWORD_"+i, i) for i in [
"AETERNVM", "AETERNVM",
"ALVID", "ALVID",
"AVGE",
"AVT", "AVT",
"DEFINI", "DEFINI",
"DESIGNA", "DESIGNA",
@@ -19,6 +20,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
"FALSITAS", "FALSITAS",
"INVOCA", "INVOCA",
"IN", "IN",
"MINVE",
"MINVS", "MINVS",
"NON", "NON",
"NVLLVS", "NVLLVS",
@@ -37,11 +39,14 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
builtin_tokens = [("BUILTIN", i) for i in [ builtin_tokens = [("BUILTIN", i) for i in [
"AVDI_NVMERVS", "AVDI_NVMERVS",
"AVDI", "AVDI",
"DECIMATIO",
"DICE", "DICE",
"EVERRO", "EVERRO",
"FORTIS_NVMERVS", "FORTIS_NVMERVS",
"FORTIS_ELECTIONIS", "FORTIS_ELECTIONIS",
"LONGITVDO" "LONGITVDO",
"SEMEN",
"SENATVS"
]] ]]
data_tokens = [ data_tokens = [

View File

@@ -74,6 +74,18 @@ class Parser():
def statement_designa_index(tokens): def statement_designa_index(tokens):
return ast_nodes.DesignaIndex(tokens[1], tokens[3], tokens[6]) return ast_nodes.DesignaIndex(tokens[1], tokens[3], tokens[6])
@self.pg.production('statement : KEYWORD_DESIGNA id SYMBOL_COMMA id_list_rest KEYWORD_VT expression')
def statement_designa_destructure(tokens):
return ast_nodes.DesignaDestructure([tokens[1]] + tokens[3], tokens[5])
@self.pg.production('statement : id KEYWORD_AVGE expression')
def statement_avge(tokens):
return ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_PLUS"))
@self.pg.production('statement : id KEYWORD_MINVE expression')
def statement_minve(tokens):
return ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_MINUS"))
@self.pg.production('statement : expression') @self.pg.production('statement : expression')
def statement_expression(tokens): def statement_expression(tokens):
return ast_nodes.ExpressionStatement(tokens[0]) return ast_nodes.ExpressionStatement(tokens[0])
@@ -250,6 +262,14 @@ class Parser():
else: else:
return [calls[0]] + calls[2] return [calls[0]] + calls[2]
@self.pg.production('id_list_rest : id')
@self.pg.production('id_list_rest : id SYMBOL_COMMA id_list_rest')
def id_list_rest(calls):
if len(calls) == 1:
return [calls[0]]
else:
return [calls[0]] + calls[2]
@self.pg.production("id : ID") @self.pg.production("id : ID")
def id_expression(tokens): def id_expression(tokens):
return ast_nodes.ID(tokens[0].value) return ast_nodes.ID(tokens[0].value)

Binary file not shown.

View File

@@ -30,6 +30,9 @@
\multicolumn{3}{|c|}{\textbf{Statements}} \\ \hline \multicolumn{3}{|c|}{\textbf{Statements}} \\ \hline
\languageline{statement}{\textit{expression}} \\ \languageline{statement}{\textit{expression}} \\
\languageline{statement}{\texttt{DESIGNA} \textbf{id} \texttt{VT} \textit{expression}} \\ \languageline{statement}{\texttt{DESIGNA} \textbf{id} \texttt{VT} \textit{expression}} \\
\languageline{statement}{\texttt{DESIGNA} \textbf{id} \texttt{,} \textit{ids} \texttt{VT} \textit{expression}} \\
\languageline{statement}{\textbf{id} \texttt{AVGE} \textit{expression}} \\
\languageline{statement}{\textbf{id} \texttt{MINVE} \textit{expression}} \\
\languageline{statement}{\texttt{DEFINI} \textbf{id} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\ \languageline{statement}{\texttt{DEFINI} \textbf{id} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
\languageline{statement}{\textit{if-statement}} \\ \languageline{statement}{\textit{if-statement}} \\
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FACE} \textit{scope}} \\ \languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FACE} \textit{scope}} \\

3
snippets/compound.cent Normal file
View File

@@ -0,0 +1,3 @@
DESIGNA x VT V
x AVGE III
DICE(x)

BIN
snippets/compound.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -57,7 +57,7 @@ contexts:
scope: constant.language.centvrion scope: constant.language.centvrion
builtins: builtins:
- match: '\b(AVDI_NVMERVS|AVDI|DICE|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO)\b' - match: '\b(AVDI_NVMERVS|AVDI|DECIMATIO|DICE|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN)\b'
scope: support.function.builtin.centvrion scope: support.function.builtin.centvrion
modules: modules:
@@ -65,7 +65,7 @@ contexts:
scope: support.class.module.centvrion scope: support.class.module.centvrion
keywords: keywords:
- match: '\b(AETERNVM|ALVID|AVT|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TVNC|VSQVE|VT|CVM)\b' - match: '\b(AETERNVM|ALVID|AVGE|AVT|CONTINVA|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVE|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TVNC|VSQVE|VT|CVM)\b'
scope: keyword.control.centvrion scope: keyword.control.centvrion
operators: operators:

121
tests.py
View File

@@ -11,8 +11,8 @@ from fractions import Fraction
from centvrion.ast_nodes import ( from centvrion.ast_nodes import (
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini, ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini,
Continva, Designa, DesignaIndex, DumStatement, Erumpe, ExpressionStatement, ID, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, Erumpe,
Invoca, ModuleCall, Nullus, Numeral, PerStatement, ExpressionStatement, ID, Invoca, ModuleCall, Nullus, Numeral, PerStatement,
Program, Redi, SiStatement, String, UnaryMinus, UnaryNot, Program, Redi, SiStatement, String, UnaryMinus, UnaryNot,
Fractio, frac_to_fraction, fraction_to_frac, Fractio, frac_to_fraction, fraction_to_frac,
num_to_int, int_to_num, make_string, num_to_int, int_to_num, make_string,
@@ -265,6 +265,31 @@ assignment_tests = [
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
ExpressionStatement(ID("x"))]), ExpressionStatement(ID("x"))]),
ValInt(3)), ValInt(3)),
# Compound assignment — AVGE (+=)
("DESIGNA x VT V\nx AVGE III\nx",
Program([], [Designa(ID("x"), Numeral("V")),
Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_PLUS")),
ExpressionStatement(ID("x"))]),
ValInt(8)),
# Compound assignment — MINVE (-=)
("DESIGNA x VT X\nx MINVE III\nx",
Program([], [Designa(ID("x"), Numeral("X")),
Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_MINUS")),
ExpressionStatement(ID("x"))]),
ValInt(7)),
# AVGE with complex expression
("DESIGNA x VT I\nx AVGE II + III\nx",
Program([], [Designa(ID("x"), Numeral("I")),
Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), "SYMBOL_PLUS")),
ExpressionStatement(ID("x"))]),
ValInt(6)),
# AVGE inside a loop (DONICVM range is exclusive of upper bound: I VSQVE III = [1, 2])
("DESIGNA s VT NVLLVS\nDONICVM i VT I VSQVE III FACE {\ns AVGE i\n}\ns",
Program([], [Designa(ID("s"), Nullus()),
PerStatement(DataRangeArray(Numeral("I"), Numeral("III")), ID("i"),
[Designa(ID("s"), BinOp(ID("s"), ID("i"), "SYMBOL_PLUS"))]),
ExpressionStatement(ID("s"))]),
ValInt(3)),
] ]
class TestAssignment(unittest.TestCase): class TestAssignment(unittest.TestCase):
@@ -273,6 +298,57 @@ class TestAssignment(unittest.TestCase):
run_test(self, source, nodes, value) run_test(self, source, nodes, value)
# --- Destructuring ---
destructuring_tests = [
# basic: unpack multi-return function
(
"DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (III, VII)\nx + y",
Program([], [
Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]),
DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("III"), Numeral("VII")])),
ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS")),
]),
ValInt(10),
),
# unpack array literal
(
"DESIGNA a, b VT [I, II]\na + b",
Program([], [
DesignaDestructure([ID("a"), ID("b")], DataArray([Numeral("I"), Numeral("II")])),
ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")),
]),
ValInt(3),
),
# three variables
(
"DESIGNA a, b, c VT [X, XX, XXX]\na + b + c",
Program([], [
DesignaDestructure([ID("a"), ID("b"), ID("c")], DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])),
ExpressionStatement(BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")),
]),
ValInt(60),
),
# destructure into individual use
(
"DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (V, II)\nDICE(x)\nDICE(y)",
Program([], [
Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]),
DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("V"), Numeral("II")])),
ExpressionStatement(BuiltIn("DICE", [ID("x")])),
ExpressionStatement(BuiltIn("DICE", [ID("y")])),
]),
ValStr("II"),
"V\nII\n",
),
]
class TestDestructuring(unittest.TestCase):
@parameterized.expand(destructuring_tests)
def test_destructuring(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
# --- Control flow --- # --- Control flow ---
control_tests = [ control_tests = [
@@ -460,6 +536,37 @@ builtin_tests = [
('LONGITVDO("salve")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("salve")]))]), ValInt(5)), ('LONGITVDO("salve")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("salve")]))]), ValInt(5)),
('LONGITVDO("")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("")]))]), ValInt(0)), ('LONGITVDO("")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("")]))]), ValInt(0)),
("CVM FORS\nFORTIS_ELECTIONIS([I, II, III])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("FORTIS_ELECTIONIS", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValInt(1)), ("CVM FORS\nFORTIS_ELECTIONIS([I, II, III])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("FORTIS_ELECTIONIS", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValInt(1)),
("CVM FORS\nSEMEN(XLII)\nFORTIS_NVMERVS(I, C)", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("FORTIS_NVMERVS", [Numeral("I"), Numeral("C")]))]), ValInt(82)),
# DECIMATIO: seed 42, 10 elements → removes 1 (element II)
("CVM FORS\nSEMEN(XLII)\nDECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X")])]))]), ValList([ValInt(1), ValInt(3), ValInt(4), ValInt(5), ValInt(6), ValInt(7), ValInt(8), ValInt(9), ValInt(10)])),
# DECIMATIO: seed 1, 3 elements → 3//10=0, nothing removed
("CVM FORS\nSEMEN(I)\nDECIMATIO([I, II, III])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("I")])), ExpressionStatement(BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])),
# DECIMATIO: empty array → empty array
("CVM FORS\nDECIMATIO([])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("DECIMATIO", [DataArray([])]))]), ValList([])),
# DECIMATIO: seed 42, 20 elements → removes 2 (elements I and IV)
("CVM FORS\nSEMEN(XLII)\nDECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X"), Numeral("XI"), Numeral("XII"), Numeral("XIII"), Numeral("XIV"), Numeral("XV"), Numeral("XVI"), Numeral("XVII"), Numeral("XVIII"), Numeral("XIX"), Numeral("XX")])]))]), ValList([ValInt(2), ValInt(3), ValInt(5), ValInt(6), ValInt(7), ValInt(8), ValInt(9), ValInt(10), ValInt(11), ValInt(12), ValInt(13), ValInt(14), ValInt(15), ValInt(16), ValInt(17), ValInt(18), ValInt(19), ValInt(20)])),
# SENATVS: majority true → VERITAS
("SENATVS(VERITAS, VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False)]))]), ValBool(True)),
# SENATVS: majority false → FALSITAS
("SENATVS(FALSITAS, FALSITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False), Bool(False), Bool(True)]))]), ValBool(False)),
# SENATVS: tie → FALSITAS
("SENATVS(VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(False)]))]), ValBool(False)),
# SENATVS: 4-arg tie → FALSITAS
("SENATVS(VERITAS, VERITAS, FALSITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False), Bool(False)]))]), ValBool(False)),
# SENATVS: single true → VERITAS
("SENATVS(VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True)]))]), ValBool(True)),
# SENATVS: single false → FALSITAS
("SENATVS(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False)]))]), ValBool(False)),
# SENATVS: empty → FALSITAS (vacuous)
("SENATVS()", Program([], [ExpressionStatement(BuiltIn("SENATVS", []))]), ValBool(False)),
# SENATVS: all true <20><> VERITAS
("SENATVS(VERITAS, VERITAS, VERITAS, VERITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(True), Bool(True), Bool(True)]))]), ValBool(True)),
# SENATVS: array input, majority true → VERITAS
("SENATVS([VERITAS, VERITAS, FALSITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(True), Bool(True), Bool(False)])]))]), ValBool(True)),
# SENATVS: array input, majority false → FALSITAS
("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)),
] ]
class TestBuiltins(unittest.TestCase): class TestBuiltins(unittest.TestCase):
@@ -494,11 +601,18 @@ error_tests = [
("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings ("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings
("I[I]", CentvrionError), # indexing a non-array ("I[I]", CentvrionError), # indexing a non-array
("DESIGNA x VT I\nDESIGNA x[I] VT II", CentvrionError), # index-assign to non-array ("DESIGNA x VT I\nDESIGNA x[I] VT II", CentvrionError), # index-assign to non-array
("SEMEN(I)", CentvrionError), # requires FORS module
('CVM FORS\nSEMEN("abc")', CentvrionError), # SEMEN requires integer seed
("FORTIS_ELECTIONIS([])", CentvrionError), # FORS required for FORTIS_ELECTIONIS ("FORTIS_ELECTIONIS([])", CentvrionError), # FORS required for FORTIS_ELECTIONIS
("CVM FORS\nFORTIS_ELECTIONIS([])", CentvrionError), # FORTIS_ELECTIONIS on empty array ("CVM FORS\nFORTIS_ELECTIONIS([])", CentvrionError), # FORTIS_ELECTIONIS on empty array
("CVM FORS\nFORTIS_NVMERVS(X, I)", CentvrionError), # FORTIS_NVMERVS a > b ("CVM FORS\nFORTIS_NVMERVS(X, I)", CentvrionError), # FORTIS_NVMERVS a > b
("PER i IN I FACE { DICE(i) }", CentvrionError), # PER over non-array ("PER i IN I FACE { DICE(i) }", CentvrionError), # PER over non-array
("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 ("LONGITVDO(I)", CentvrionError), # LONGITVDO on 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
("DESIGNA x VT I\nINVOCA x ()", CentvrionError), # invoking a non-function ("DESIGNA x VT I\nINVOCA x ()", CentvrionError), # invoking a non-function
("SI I TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: int ("SI I TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: int
("IIIS", CentvrionError), # fraction without FRACTIO module ("IIIS", CentvrionError), # fraction without FRACTIO module
@@ -511,6 +625,9 @@ error_tests = [
("NON I", CentvrionError), # NON on integer ("NON I", CentvrionError), # NON on integer
("DESIGNA z VT I - I\nNON z", CentvrionError), # NON on zero integer ("DESIGNA z VT I - I\nNON z", CentvrionError), # NON on zero integer
('NON "hello"', CentvrionError), # NON on string ('NON "hello"', CentvrionError), # NON on string
("DESIGNA a, b VT III", CentvrionError), # destructure non-array
("DESIGNA a, b VT [I]", CentvrionError), # destructure length mismatch: too many targets
("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets
] ]
class TestErrors(unittest.TestCase): class TestErrors(unittest.TestCase):

View File

@@ -65,7 +65,7 @@
"patterns": [ "patterns": [
{ {
"name": "support.function.builtin.cent", "name": "support.function.builtin.cent",
"match": "\\b(AVDI_NVMERVS|AVDI|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO)\\b" "match": "\\b(AVDI_NVMERVS|AVDI|DECIMATIO|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN)\\b"
} }
] ]
}, },