🐐 Per destructuring better

This commit is contained in:
2026-05-30 17:57:44 +02:00
parent d6b064efcd
commit 19f8cb5232
4 changed files with 49 additions and 66 deletions
+12 -20
View File
@@ -1287,10 +1287,6 @@ 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)
@@ -1299,25 +1295,21 @@ class PerStatement(Node):
return f"Per({dum_string})" return f"Per({dum_string})"
def print(self): def print(self):
body = "\n".join(s.print() for s in self.statements) # Re-sugar the parse-time destructure rewrite back into `PER a, b IN ...`
if self.destructure: # so the printed source round-trips through the lexer (which rejects `#`).
var_str = ", ".join(v.print() for v in self.variable_name) if (isinstance(self.variable_name, ID)
and self.variable_name.name == "#per_item"
and self.statements
and isinstance(self.statements[0], DesignaDestructure)
and isinstance(self.statements[0].value, ID)
and self.statements[0].value.name == "#per_item"):
var_str = ", ".join(i.print() for i in self.statements[0].ids)
body = "\n".join(s.print() for s in self.statements[1:])
else: else:
var_str = self.variable_name.print() var_str = self.variable_name.print()
body = "\n".join(s.print() for s in self.statements)
return f"PER {var_str} IN {self.data_list.print()} FAC {{\n{body}\n}}" 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)
if isinstance(array, ValDict): if isinstance(array, ValDict):
@@ -1327,7 +1319,7 @@ class PerStatement(Node):
raise CentvrionError("PER requires an array or dict") raise CentvrionError("PER requires an array or dict")
last_val = ValNul() last_val = ValNul()
for item in array: for item in array:
self._assign_loop_var(vtable, item) vtable[self.variable_name.name] = 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:
+16 -37
View File
@@ -90,43 +90,22 @@ def _emit_stmt_body(node, ctx):
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()
body_lines = _emit_body(node.statements, ctx) body_lines = _emit_body(node.statements, ctx)
var_name = node.variable_name.name
if node.destructure: lines = arr_lines + [
# Destructuring PER — each element must be a list f"if ({arr_var}.type == CENT_DICT) {{",
elem_var = ctx.fresh_tmp() f" for (int {i_var} = 0; {i_var} < {arr_var}.dval.len; {i_var}++) {{",
assign_lines = [ f' cent_scope_set(&_scope, "{var_name}", {arr_var}.dval.keys[{i_var}]);',
f"CentValue {elem_var} = {arr_var}.lval.items[{i_var}];", ]
f'if ({elem_var}.type != CENT_LIST) cent_type_error("Cannot destructure non-array value in PER loop");', lines += [f" {l}" for l in body_lines]
f'if ({elem_var}.lval.len != {len(node.variable_name)}) cent_runtime_error("Destructuring mismatch");', lines += [
] " }",
for j, id_node in enumerate(node.variable_name): "} else {",
tmp = ctx.fresh_tmp() f' if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array or dict");',
assign_lines.append(f"CentValue {tmp} = cent_list_index({elem_var}, cent_int({j + 1}));") f" for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
assign_lines.append(f'cent_scope_set(&_scope, "{id_node.name}", {tmp});') f' cent_scope_set(&_scope, "{var_name}", {arr_var}.lval.items[{i_var}]);',
lines = arr_lines + [ ]
f'if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array");', lines += [f" {l}" for l in body_lines]
f"for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{", lines += [" }", "}"]
]
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):
+5 -1
View File
@@ -284,7 +284,11 @@ class Parser():
@self.pg.production('per_statement : KEYWORD_PER id SYMBOL_COMMA id_list_rest KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL') @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): def per_destructure(tokens):
return _at(ast_nodes.PerStatement(tokens[5], [tokens[1]] + tokens[3], tokens[8]), tokens[0]) ids = [tokens[1]] + tokens[3]
sentinel = ast_nodes.ID("#per_item")
destructure = _at(ast_nodes.DesignaDestructure(ids, sentinel), tokens[0])
body = [destructure] + tokens[8]
return _at(ast_nodes.PerStatement(tokens[5], sentinel, body), tokens[0])
@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):
+16 -8
View File
@@ -138,15 +138,21 @@ control_tests = [
("PER a, b IN [[I, II], [III, IV]] FAC { DIC(a + b) }", ("PER a, b IN [[I, II], [III, IV]] FAC { DIC(a + b) }",
Program([], [PerStatement( Program([], [PerStatement(
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]),
[ID("a"), ID("b")], ID("#per_item"),
[ExpressionStatement(BuiltIn("DIC", [BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")]))])]), [
DesignaDestructure([ID("a"), ID("b")], ID("#per_item")),
ExpressionStatement(BuiltIn("DIC", [BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")])),
])]),
ValStr("VII"), "III\nVII\n"), ValStr("VII"), "III\nVII\n"),
# PER destructuring: three variables # PER destructuring: three variables
("PER a, b, c IN [[I, II, III]] FAC { DIC(a + b + c) }", ("PER a, b, c IN [[I, II, III]] FAC { DIC(a + b + c) }",
Program([], [PerStatement( Program([], [PerStatement(
DataArray([DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]), DataArray([DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]),
[ID("a"), ID("b"), ID("c")], ID("#per_item"),
[ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")]))])]), [
DesignaDestructure([ID("a"), ID("b"), ID("c")], ID("#per_item")),
ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")])),
])]),
ValStr("VI"), "VI\n"), ValStr("VI"), "VI\n"),
] ]
@@ -287,8 +293,9 @@ loop_edge_tests = [
Designa(ID("r"), Numeral("I")), Designa(ID("r"), Numeral("I")),
PerStatement( PerStatement(
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), DataArray([Numeral("V"), Numeral("VI")])]), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), DataArray([Numeral("V"), Numeral("VI")])]),
[ID("a"), ID("b")], ID("#per_item"),
[SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None), [DesignaDestructure([ID("a"), ID("b")], ID("#per_item")),
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"))], Designa(ID("r"), BinOp(BinOp(ID("r"), ID("a"), "SYMBOL_PLUS"), ID("b"), "SYMBOL_PLUS"))],
), ),
ExpressionStatement(ID("r")), ExpressionStatement(ID("r")),
@@ -300,8 +307,9 @@ loop_edge_tests = [
Defini(ID("f"), [], Defini(ID("f"), [],
[PerStatement( [PerStatement(
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]),
[ID("a"), ID("b")], ID("#per_item"),
[SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Redi([ID("b")])], None)], [DesignaDestructure([ID("a"), ID("b")], ID("#per_item")),
SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Redi([ID("b")])], None)],
)]), )]),
ExpressionStatement(Invoca(ID("f"), [])), ExpressionStatement(Invoca(ID("f"), [])),
]), ]),