🐐 PER deconstructing
This commit is contained in:
13
README.md
13
README.md
@@ -253,6 +253,19 @@ condition. Exit the loop with `ERVMPE` (or `REDI` from inside a function).
|
|||||||
> V
|
> V
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Variables can be unpacked in `PER` loops, similar to `DESIGNA` destructuring:
|
||||||
|
|
||||||
|
```
|
||||||
|
PER a, b IN [[I, II], [III, IV]] FAC {
|
||||||
|
DIC(a + b)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
> III
|
||||||
|
> VII
|
||||||
|
```
|
||||||
|
|
||||||
## Error handling
|
## Error handling
|
||||||
|
|
||||||
Errors can be caught using `TEMPTA` (temptare = to try) and `CAPE` (capere = to catch). The `CAPE` block binds the error message to a variable as a string.
|
Errors can be caught using `TEMPTA` (temptare = to try) and `CAPE` (capere = to catch). The `CAPE` block binds the error message to a variable as a string.
|
||||||
|
|||||||
@@ -1140,6 +1140,10 @@ class PerStatement(Node):
|
|||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return type(self) == type(other) and self.data_list == other.data_list and self.variable_name == other.variable_name and self.statements == other.statements
|
return type(self) == type(other) and self.data_list == other.data_list and self.variable_name == other.variable_name and self.statements == other.statements
|
||||||
|
|
||||||
|
@property
|
||||||
|
def destructure(self):
|
||||||
|
return isinstance(self.variable_name, list)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
test = repr(self.data_list)
|
test = repr(self.data_list)
|
||||||
variable_name = repr(self.variable_name)
|
variable_name = repr(self.variable_name)
|
||||||
@@ -1149,7 +1153,23 @@ class PerStatement(Node):
|
|||||||
|
|
||||||
def print(self):
|
def print(self):
|
||||||
body = "\n".join(s.print() for s in self.statements)
|
body = "\n".join(s.print() for s in self.statements)
|
||||||
return f"PER {self.variable_name.print()} IN {self.data_list.print()} FAC {{\n{body}\n}}"
|
if self.destructure:
|
||||||
|
var_str = ", ".join(v.print() for v in self.variable_name)
|
||||||
|
else:
|
||||||
|
var_str = self.variable_name.print()
|
||||||
|
return f"PER {var_str} IN {self.data_list.print()} FAC {{\n{body}\n}}"
|
||||||
|
|
||||||
|
def _assign_loop_var(self, vtable, item):
|
||||||
|
if self.destructure:
|
||||||
|
if not isinstance(item, ValList):
|
||||||
|
raise CentvrionError("Cannot destructure non-array value in PER loop")
|
||||||
|
if len(item.value()) != len(self.variable_name):
|
||||||
|
raise CentvrionError(
|
||||||
|
f"Destructuring mismatch: {len(self.variable_name)} targets, {len(item.value())} values")
|
||||||
|
for id_node, val in zip(self.variable_name, item.value()):
|
||||||
|
vtable[id_node.name] = val
|
||||||
|
else:
|
||||||
|
vtable[self.variable_name.name] = item
|
||||||
|
|
||||||
def _eval(self, vtable):
|
def _eval(self, vtable):
|
||||||
vtable, array = self.data_list.eval(vtable)
|
vtable, array = self.data_list.eval(vtable)
|
||||||
@@ -1158,10 +1178,9 @@ class PerStatement(Node):
|
|||||||
array = ValList(keys)
|
array = ValList(keys)
|
||||||
if not isinstance(array, ValList):
|
if not isinstance(array, ValList):
|
||||||
raise CentvrionError("PER requires an array or dict")
|
raise CentvrionError("PER requires an array or dict")
|
||||||
variable_name = self.variable_name.name
|
|
||||||
last_val = ValNul()
|
last_val = ValNul()
|
||||||
for item in array:
|
for item in array:
|
||||||
vtable[variable_name] = item
|
self._assign_loop_var(vtable, item)
|
||||||
for statement in self.statements:
|
for statement in self.statements:
|
||||||
vtable, val = statement.eval(vtable)
|
vtable, val = statement.eval(vtable)
|
||||||
if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None:
|
if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None:
|
||||||
|
|||||||
@@ -65,23 +65,44 @@ def emit_stmt(node, ctx):
|
|||||||
if isinstance(node, PerStatement):
|
if isinstance(node, PerStatement):
|
||||||
arr_lines, arr_var = emit_expr(node.data_list, ctx)
|
arr_lines, arr_var = emit_expr(node.data_list, ctx)
|
||||||
i_var = ctx.fresh_tmp()
|
i_var = ctx.fresh_tmp()
|
||||||
var_name = node.variable_name.name
|
|
||||||
body_lines = _emit_body(node.statements, ctx)
|
body_lines = _emit_body(node.statements, ctx)
|
||||||
lines = arr_lines + [
|
|
||||||
f"if ({arr_var}.type == CENT_DICT) {{",
|
if node.destructure:
|
||||||
f" for (int {i_var} = 0; {i_var} < {arr_var}.dval.len; {i_var}++) {{",
|
# Destructuring PER — each element must be a list
|
||||||
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.dval.keys[{i_var}]);',
|
elem_var = ctx.fresh_tmp()
|
||||||
]
|
assign_lines = [
|
||||||
lines += [f" {l}" for l in body_lines]
|
f"CentValue {elem_var} = {arr_var}.lval.items[{i_var}];",
|
||||||
lines += [
|
f'if ({elem_var}.type != CENT_LIST) cent_type_error("Cannot destructure non-array value in PER loop");',
|
||||||
" }",
|
f'if ({elem_var}.lval.len != {len(node.variable_name)}) cent_runtime_error("Destructuring mismatch");',
|
||||||
"} else {",
|
]
|
||||||
f' if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array or dict");',
|
for j, id_node in enumerate(node.variable_name):
|
||||||
f" for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
|
tmp = ctx.fresh_tmp()
|
||||||
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.lval.items[{i_var}]);',
|
assign_lines.append(f"CentValue {tmp} = cent_list_index({elem_var}, cent_int({j + 1}));")
|
||||||
]
|
assign_lines.append(f'cent_scope_set(&_scope, "{id_node.name}", {tmp});')
|
||||||
lines += [f" {l}" for l in body_lines]
|
lines = arr_lines + [
|
||||||
lines += [" }", "}"]
|
f'if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array");',
|
||||||
|
f"for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
|
||||||
|
]
|
||||||
|
lines += [f" {l}" for l in assign_lines]
|
||||||
|
lines += [f" {l}" for l in body_lines]
|
||||||
|
lines += ["}"]
|
||||||
|
else:
|
||||||
|
var_name = node.variable_name.name
|
||||||
|
lines = arr_lines + [
|
||||||
|
f"if ({arr_var}.type == CENT_DICT) {{",
|
||||||
|
f" for (int {i_var} = 0; {i_var} < {arr_var}.dval.len; {i_var}++) {{",
|
||||||
|
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.dval.keys[{i_var}]);',
|
||||||
|
]
|
||||||
|
lines += [f" {l}" for l in body_lines]
|
||||||
|
lines += [
|
||||||
|
" }",
|
||||||
|
"} else {",
|
||||||
|
f' if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array or dict");',
|
||||||
|
f" for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
|
||||||
|
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.lval.items[{i_var}]);',
|
||||||
|
]
|
||||||
|
lines += [f" {l}" for l in body_lines]
|
||||||
|
lines += [" }", "}"]
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
if isinstance(node, Defini):
|
if isinstance(node, Defini):
|
||||||
|
|||||||
@@ -242,6 +242,10 @@ class Parser():
|
|||||||
def aeternvm(tokens):
|
def aeternvm(tokens):
|
||||||
return ast_nodes.DumStatement(ast_nodes.Bool(False), tokens[3])
|
return ast_nodes.DumStatement(ast_nodes.Bool(False), tokens[3])
|
||||||
|
|
||||||
|
@self.pg.production('per_statement : KEYWORD_PER id SYMBOL_COMMA id_list_rest KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
|
def per_destructure(tokens):
|
||||||
|
return ast_nodes.PerStatement(tokens[5], [tokens[1]] + tokens[3], tokens[8])
|
||||||
|
|
||||||
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
def per(tokens):
|
def per(tokens):
|
||||||
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
|
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{AETERNVM} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{AETERNVM} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
|
\languageline{statement}{\texttt{PER} \textbf{id}\texttt{,} \textbf{id-list} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\
|
\languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\
|
||||||
\languageline{statement}{\texttt{ERVMPE}} \\
|
\languageline{statement}{\texttt{ERVMPE}} \\
|
||||||
|
|||||||
41
tests.py
41
tests.py
@@ -473,6 +473,20 @@ control_tests = [
|
|||||||
("DONICVM i VT I VSQVE V FAC { DIC(i) }",
|
("DONICVM i VT I VSQVE V FAC { DIC(i) }",
|
||||||
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
ValStr("V"), "I\nII\nIII\nIV\nV\n"),
|
ValStr("V"), "I\nII\nIII\nIV\nV\n"),
|
||||||
|
# PER destructuring
|
||||||
|
("PER a, b IN [[I, II], [III, IV]] FAC { DIC(a + b) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]),
|
||||||
|
[ID("a"), ID("b")],
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")]))])]),
|
||||||
|
ValStr("VII"), "III\nVII\n"),
|
||||||
|
# PER destructuring: three variables
|
||||||
|
("PER a, b, c IN [[I, II, III]] FAC { DIC(a + b + c) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataArray([DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]),
|
||||||
|
[ID("a"), ID("b"), ID("c")],
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")]))])]),
|
||||||
|
ValStr("VI"), "VI\n"),
|
||||||
]
|
]
|
||||||
|
|
||||||
class TestControl(unittest.TestCase):
|
class TestControl(unittest.TestCase):
|
||||||
@@ -754,6 +768,8 @@ error_tests = [
|
|||||||
("DESIGNA a, b VT III", CentvrionError), # destructure non-array
|
("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]", CentvrionError), # destructure length mismatch: too many targets
|
||||||
("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets
|
("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets
|
||||||
|
("PER a, b IN [I, II, III] FAC { DIC(a) }", CentvrionError), # PER destructure: element is not an array
|
||||||
|
("PER a, b IN [[I], [II]] FAC { DIC(a) }", CentvrionError), # PER destructure: wrong number of elements
|
||||||
("[I, II, III][II VSQVE IV]", CentvrionError), # slice upper bound out of range
|
("[I, II, III][II VSQVE IV]", CentvrionError), # slice upper bound out of range
|
||||||
("[I, II, III][NVLLVS VSQVE II]", CentvrionError), # slice with non-integer bound
|
("[I, II, III][NVLLVS VSQVE II]", CentvrionError), # slice with non-integer bound
|
||||||
("I[I VSQVE II]", CentvrionError), # slice on non-array
|
("I[I VSQVE II]", CentvrionError), # slice on non-array
|
||||||
@@ -1549,6 +1565,31 @@ loop_edge_tests = [
|
|||||||
[Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]),
|
[Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]),
|
||||||
ExpressionStatement(ID("x"))]),
|
ExpressionStatement(ID("x"))]),
|
||||||
ValNul(), ""),
|
ValNul(), ""),
|
||||||
|
# PER destructuring with ERVMPE
|
||||||
|
("DESIGNA r VT I\nPER a, b IN [[I, II], [III, IV], [V, VI]] FAC {\nSI a EST III TVNC { ERVMPE }\nDESIGNA r VT r + a + b\n}\nr",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("r"), Numeral("I")),
|
||||||
|
PerStatement(
|
||||||
|
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), DataArray([Numeral("V"), Numeral("VI")])]),
|
||||||
|
[ID("a"), ID("b")],
|
||||||
|
[SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None),
|
||||||
|
Designa(ID("r"), BinOp(BinOp(ID("r"), ID("a"), "SYMBOL_PLUS"), ID("b"), "SYMBOL_PLUS"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("r")),
|
||||||
|
]),
|
||||||
|
ValInt(4)), # 1 + 1 + 2 = 4, breaks before [III, IV]
|
||||||
|
# PER destructuring with REDI
|
||||||
|
("DEFINI f () VT {\nPER a, b IN [[I, II], [III, IV]] FAC {\nSI a EST III TVNC { REDI (b) }\n}\n}\nINVOCA f ()",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("f"), [],
|
||||||
|
[PerStatement(
|
||||||
|
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]),
|
||||||
|
[ID("a"), ID("b")],
|
||||||
|
[SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Redi([ID("b")])], None)],
|
||||||
|
)]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [])),
|
||||||
|
]),
|
||||||
|
ValInt(4)), # returns b=IV when a=III
|
||||||
]
|
]
|
||||||
|
|
||||||
class TestLoopEdge(unittest.TestCase):
|
class TestLoopEdge(unittest.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user