🐐 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`.
|
`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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ 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')
|
@self.pg.production('statement : id KEYWORD_AVGE expression')
|
||||||
def statement_avge(tokens):
|
def statement_avge(tokens):
|
||||||
return ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_PLUS"))
|
return ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_PLUS"))
|
||||||
@@ -258,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.
@@ -30,6 +30,7 @@
|
|||||||
\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{AVGE} \textit{expression}} \\
|
||||||
\languageline{statement}{\textbf{id} \texttt{MINVE} \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}} \\
|
||||||
|
|||||||
58
tests.py
58
tests.py
@@ -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,
|
||||||
@@ -298,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 = [
|
||||||
@@ -539,6 +590,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):
|
||||||
|
|||||||
Reference in New Issue
Block a user