"""Tests for Enhancement 2: Parameterized placement and port qualifiers (macro-enh.E2.*). Tests verify: - macro-enh.E2.1: Grammar accepts param_ref in placement position - macro-enh.E2.2: Grammar accepts param_ref in port position - macro-enh.E2.3: Context slot bracket syntax parses - macro-enh.E2.4: Expand pass resolves placement ParamRef - macro-enh.E2.5: Expand pass resolves port ParamRef - macro-enh.E2.6: Expand pass resolves context slot ParamRef - macro-enh.E2.7: Full pipeline with placement/port params Note: Placement qualifiers attach to the LHS qualified_ref in inst_def, not to the opcode. So `&n|${pe} <| add` is the correct syntax, not `&n <| add|${pe}`. """ from pathlib import Path from lark import Lark from asm import assemble, run_pipeline from asm.expand import expand from asm.lower import lower from asm.errors import ErrorCategory from asm.ir import ( IRNode, ParamRef, PlacementRef, PortRef, ActSlotRef, ActSlotRange, ) from cm_inst import ArithOp, Port def _get_parser(): grammar_path = Path(__file__).parent.parent / "dfasm.lark" return Lark( grammar_path.read_text(), parser="earley", propagate_positions=True, ) def parse_and_lower(source: str): parser = _get_parser() tree = parser.parse(source) return lower(tree) def parse_lower_expand(source: str): graph = parse_and_lower(source) return expand(graph) class TestE21_PlacementParamRef: """E2.1: Grammar accepts param_ref in placement position.""" def test_placement_param_ref_in_macro_body(self): """&n|${pe} <| add parses and lowers to PlacementRef.""" source = """ @system pe=1, sm=1 #place pe |> { &n|${pe} <| add } """ graph = parse_and_lower(source) assert not graph.errors body_nodes = graph.macro_defs[0].body.nodes node = list(body_nodes.values())[0] assert isinstance(node.pe, PlacementRef) assert node.pe.param.param == "pe" class TestE22_PortParamRef: """E2.2: Grammar accepts param_ref in port position.""" def test_port_param_ref_in_edge(self): """&src |> &dst:${port} parses and lowers to PortRef.""" source = """ @system pe=1, sm=1 #wire port |> { &src <| pass &dst <| add &src |> &dst:${port} } """ graph = parse_and_lower(source) assert not graph.errors body_edges = graph.macro_defs[0].body.edges port_ref_edges = [e for e in body_edges if isinstance(e.port, PortRef)] assert len(port_ref_edges) == 1 assert port_ref_edges[0].port.param.param == "port" class TestE23_CtxSlotSyntax: """E2.3: Context slot bracket syntax parses.""" def test_literal_ctx_slot(self): """&node[2] parses — literal context slot in bracket syntax.""" source = """ @system pe=1, sm=1 #slot_macro |> { &n[2] <| add } """ graph = parse_and_lower(source) assert not graph.errors body_nodes = graph.macro_defs[0].body.nodes node = list(body_nodes.values())[0] assert isinstance(node.act_slot, ActSlotRange) assert node.act_slot.start == 2 assert node.act_slot.end == 2 def test_full_qualifier_chain(self): """&node|pe0[2]:L parses — placement + ctx_slot + port.""" source = """ @system pe=1, sm=1 &n|pe0[2]:L <| add """ graph = parse_and_lower(source) assert not graph.errors node = list(graph.nodes.values())[0] assert node.pe == 0 assert isinstance(node.act_slot, ActSlotRange) assert node.act_slot.start == 2 assert node.act_slot.end == 2 def test_parameterized_ctx_slot(self): """&node[${ctx}] parses — parameterized context slot.""" source = """ @system pe=1, sm=1 #ctx_macro ctx |> { &n[${ctx}] <| add } """ graph = parse_and_lower(source) assert not graph.errors body_nodes = graph.macro_defs[0].body.nodes node = list(body_nodes.values())[0] assert isinstance(node.act_slot, ActSlotRef) assert node.act_slot.param.param == "ctx" def test_ctx_slot_range(self): """&node[0..4] parses — range reservation.""" source = """ @system pe=1, sm=1 &n[0..4] <| add """ graph = parse_and_lower(source) assert not graph.errors node = list(graph.nodes.values())[0] assert isinstance(node.act_slot, ActSlotRange) assert node.act_slot.start == 0 assert node.act_slot.end == 4 class TestE24_ExpandResolvesPlacement: """E2.4: Expand pass resolves placement ParamRef.""" def test_resolve_pe0(self): """Placement param 'pe0' resolves to PE 0.""" source = """ @system pe=2, sm=1 #place pe |> { &n|${pe} <| add } #place pe0 """ graph = parse_lower_expand(source) assert not graph.errors node = list(graph.nodes.values())[0] assert node.pe == 0 def test_resolve_pe1(self): """Placement param 'pe1' resolves to PE 1.""" source = """ @system pe=2, sm=1 #place pe |> { &n|${pe} <| add } #place pe1 """ graph = parse_lower_expand(source) assert not graph.errors node = list(graph.nodes.values())[0] assert node.pe == 1 def test_invalid_placement_error(self): """Invalid placement value produces MACRO error.""" source = """ @system pe=1, sm=1 #place pe |> { &n|${pe} <| add } #place &banana """ graph = parse_lower_expand(source) macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO] assert len(macro_errors) >= 1 assert "placement" in macro_errors[0].message.lower() class TestE25_ExpandResolvesPort: """E2.5: Expand pass resolves port ParamRef.""" def test_resolve_port_L(self): """Port param 'L' resolves to Port.L.""" source = """ @system pe=1, sm=1 #wire port |> { &src <| pass &dst <| add &src |> &dst:${port} } #wire L """ graph = parse_lower_expand(source) assert not graph.errors edges = [e for e in graph.edges if e.dest.endswith("&dst")] assert len(edges) >= 1 assert edges[0].port == Port.L def test_resolve_port_R(self): """Port param 'R' resolves to Port.R.""" source = """ @system pe=1, sm=1 #wire port |> { &src <| pass &dst <| add &src |> &dst:${port} } #wire R """ graph = parse_lower_expand(source) assert not graph.errors edges = [e for e in graph.edges if e.dest.endswith("&dst")] assert len(edges) >= 1 assert edges[0].port == Port.R def test_invalid_port_value_produces_error(self): """E2.5c: Invalid port value produces MACRO error.""" source = """ @system pe=1, sm=1 #wire port |> { &src <| pass &dst <| add &src |> &dst:${port} } #wire X """ graph = parse_lower_expand(source) macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO] assert len(macro_errors) >= 1 assert "port" in macro_errors[0].message.lower() def test_invalid_source_port_value_produces_error(self): """Invalid source port value produces MACRO error.""" source = """ @system pe=1, sm=1 #wire port |> { &src <| pass &dst <| add &src:${port} |> &dst } #wire Z """ graph = parse_lower_expand(source) macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO] assert len(macro_errors) >= 1 assert "port" in macro_errors[0].message.lower() class TestE26_ExpandResolvesCtxSlot: """E2.6: Expand pass resolves context slot ParamRef.""" def test_resolve_ctx_slot_int(self): """Ctx slot param resolves to CtxSlotRange.""" source = """ @system pe=1, sm=1 #ctx_macro ctx |> { &n[${ctx}] <| add } #ctx_macro 2 """ graph = parse_lower_expand(source) assert not graph.errors node = list(graph.nodes.values())[0] assert isinstance(node.act_slot, ActSlotRange) assert node.act_slot.start == 2 assert node.act_slot.end == 2 def test_non_numeric_ctx_slot_error(self): """Non-numeric ctx slot value produces MACRO error.""" source = """ @system pe=1, sm=1 #ctx_macro ctx |> { &n[${ctx}] <| add } #ctx_macro &banana """ graph = parse_lower_expand(source) macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO] assert len(macro_errors) >= 1 assert "act_slot" in macro_errors[0].message.lower() or "ctx_slot" in macro_errors[0].message.lower() class TestE27_FullPipeline: """E2.7: Full pipeline with placement/port params.""" def test_full_pipeline_placement_param(self): """Placement-parameterized macro assembles; node on correct PE.""" source = """ @system pe=2, sm=1 #place pe |> { &n|${pe} <| add } &seed <| const, 5 #place pe1 &seed |> #place_0.&n:L &seed |> #place_0.&n:R """ graph = run_pipeline(source) assert not graph.errors placed_node = graph.nodes["#place_0.&n"] assert placed_node.pe == 1 def test_full_pipeline_port_param(self): """Port-parameterized macro assembles; edge targets correct port.""" source = """ @system pe=1, sm=1 #wire port |> { &src <| pass &dst <| add &src |> &dst:${port} } &seed <| const, 5 #wire L &seed |> #wire_0.&src &seed |> #wire_0.&dst:R """ result = assemble(source) assert result is not None