🐐 Unpacking arrays
This commit is contained in:
15
README.md
15
README.md
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
@@ -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}} \\
|
||||
|
||||
58
tests.py
58
tests.py
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user