diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index 6d90aaa..6c27c26 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -1287,10 +1287,6 @@ class PerStatement(Node): 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 - @property - def destructure(self): - return isinstance(self.variable_name, list) - def __repr__(self) -> str: test = repr(self.data_list) variable_name = repr(self.variable_name) @@ -1299,25 +1295,21 @@ class PerStatement(Node): return f"Per({dum_string})" def print(self): - body = "\n".join(s.print() for s in self.statements) - if self.destructure: - var_str = ", ".join(v.print() for v in self.variable_name) + # Re-sugar the parse-time destructure rewrite back into `PER a, b IN ...` + # so the printed source round-trips through the lexer (which rejects `#`). + 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: 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}}" - 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): vtable, array = self.data_list.eval(vtable) if isinstance(array, ValDict): @@ -1327,7 +1319,7 @@ class PerStatement(Node): raise CentvrionError("PER requires an array or dict") last_val = ValNul() for item in array: - self._assign_loop_var(vtable, item) + vtable[self.variable_name.name] = item for statement in self.statements: vtable, val = statement.eval(vtable) if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None: diff --git a/centvrion/compiler/emit_stmt.py b/centvrion/compiler/emit_stmt.py index 59e27f0..b03ae81 100644 --- a/centvrion/compiler/emit_stmt.py +++ b/centvrion/compiler/emit_stmt.py @@ -90,43 +90,22 @@ def _emit_stmt_body(node, ctx): arr_lines, arr_var = emit_expr(node.data_list, ctx) i_var = ctx.fresh_tmp() body_lines = _emit_body(node.statements, ctx) - - if node.destructure: - # Destructuring PER — each element must be a list - elem_var = ctx.fresh_tmp() - assign_lines = [ - 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");', - f'if ({elem_var}.lval.len != {len(node.variable_name)}) cent_runtime_error("Destructuring mismatch");', - ] - for j, id_node in enumerate(node.variable_name): - tmp = ctx.fresh_tmp() - 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 = arr_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 += [" }", "}"] + 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 if isinstance(node, Defini): diff --git a/centvrion/parser.py b/centvrion/parser.py index 2bb1b02..d2dd094 100644 --- a/centvrion/parser.py +++ b/centvrion/parser.py @@ -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') 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') def per(tokens): diff --git a/tests/02_test_con_flow.py b/tests/02_test_con_flow.py index 84b890f..7cb2ef8 100644 --- a/tests/02_test_con_flow.py +++ b/tests/02_test_con_flow.py @@ -138,15 +138,21 @@ control_tests = [ ("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")]))])]), + ID("#per_item"), + [ + DesignaDestructure([ID("a"), ID("b")], ID("#per_item")), + 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")]))])]), + ID("#per_item"), + [ + 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"), ] @@ -287,8 +293,9 @@ loop_edge_tests = [ 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), + ID("#per_item"), + [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"))], ), ExpressionStatement(ID("r")), @@ -300,8 +307,9 @@ loop_edge_tests = [ 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)], + ID("#per_item"), + [DesignaDestructure([ID("a"), ID("b")], ID("#per_item")), + SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Redi([ID("b")])], None)], )]), ExpressionStatement(Invoca(ID("f"), [])), ]),