🐐 Unpacking arrays

This commit is contained in:
2026-04-21 14:54:36 +02:00
parent c28ffbbf45
commit 63c35605a2
7 changed files with 127 additions and 4 deletions

View File

@@ -34,6 +34,21 @@ Variable can consist of lower-case letters, numbers, as well as `_`.
`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
### 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.

View File

@@ -431,6 +431,35 @@ class DesignaIndex(Node):
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):
def __init__(self, name: ID, parameters: list[ID], statements: list[Node]) -> None:
self.name = name

View File

@@ -1,6 +1,6 @@
from centvrion.ast_nodes import (
Designa, DesignaIndex, SiStatement, DumStatement, PerStatement,
Defini, Redi, Erumpe, Continva, ExpressionStatement, ID,
Designa, DesignaIndex, DesignaDestructure, SiStatement, DumStatement,
PerStatement, Defini, Redi, Erumpe, Continva, ExpressionStatement, ID,
)
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):
cond_lines, cond_var = emit_expr(node.test, ctx)
then_lines = _emit_body(node.statements, ctx)

View File

@@ -74,6 +74,10 @@ class Parser():
def statement_designa_index(tokens):
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"))
@@ -258,6 +262,14 @@ class Parser():
else:
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")
def id_expression(tokens):
return ast_nodes.ID(tokens[0].value)

Binary file not shown.

View File

@@ -30,6 +30,7 @@
\multicolumn{3}{|c|}{\textbf{Statements}} \\ \hline
\languageline{statement}{\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}} \\

View File

@@ -11,8 +11,8 @@ from fractions import Fraction
from centvrion.ast_nodes import (
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini,
Continva, Designa, DesignaIndex, DumStatement, Erumpe, ExpressionStatement, ID,
Invoca, ModuleCall, Nullus, Numeral, PerStatement,
Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, Erumpe,
ExpressionStatement, ID, Invoca, ModuleCall, Nullus, Numeral, PerStatement,
Program, Redi, SiStatement, String, UnaryMinus, UnaryNot,
Fractio, frac_to_fraction, fraction_to_frac,
num_to_int, int_to_num, make_string,
@@ -298,6 +298,57 @@ class TestAssignment(unittest.TestCase):
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_tests = [
@@ -539,6 +590,9 @@ error_tests = [
("NON I", CentvrionError), # NON on integer
("DESIGNA z VT I - I\nNON z", CentvrionError), # NON on zero integer
('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):