"""Parser tests for the dataflow graph assembly grammar.""" from textwrap import dedent import pytest from lark import exceptions, LarkError class TestInstDefs: def test_basic_instructions(self, parser): tree = parser.parse(dedent("""\ &my_add <| add &my_sub <| sub &my_const <| const, 10 &my_shift <| shl &my_not <| not """)) assert tree.data == "start" assert len(tree.children) == 5 assert all(child.data == "inst_def" for child in tree.children) def test_hex_const(self, parser): tree = parser.parse(dedent("""\ &mask <| const, 0xFF """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_named_args(self, parser): tree = parser.parse(dedent("""\ &serial <| ior, dest=0x45, addr=0x91, data=0x43 """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_system_config(self, parser): tree = parser.parse(dedent("""\ &loader <| load_inst, dest=0x01, addr=0x00, data_l=0xABCD, data_h=0x1234 """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" class TestEdges: def test_plain_edges(self, parser): tree = parser.parse(dedent("""\ &a |> &b:L &a |> &b:R &c |> &d, &e """)) assert tree.data == "start" assert len(tree.children) == 3 assert all(child.data == "plain_edge" for child in tree.children) def test_strong_inline_edge(self, parser): tree = parser.parse(dedent("""\ add &a, &b |> &c, &d """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "strong_edge" def test_weak_inline_edge(self, parser): tree = parser.parse(dedent("""\ &c, &d sub <| &a, &b """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "weak_edge" def test_fanout(self, parser): tree = parser.parse(dedent("""\ &splitter <| pass &input |> &splitter:L &splitter |> &consumer_a:L, &consumer_b:R """)) assert tree.data == "start" assert len(tree.children) == 3 assert tree.children[0].data == "inst_def" assert tree.children[1].data == "plain_edge" assert tree.children[2].data == "plain_edge" class TestFunctions: def test_fib_function(self, parser): tree = parser.parse(dedent("""\ $fib |> { &const_n <| const, 10 &sub1 <| sub &sub2 <| sub &branch <| sweq &const_n |> &branch:L &const_n |> &sub1:L &const_n |> &sub1:R &const_n |> &sub2:R &sub1 |> &recurse_a:L } """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "func_def" class TestPlacement: def test_pe_qualifiers(self, parser): tree = parser.parse(dedent("""\ &my_add|pe0 <| add &result|pe1 <| pass &my_add|pe0 |> &result|pe1:L """)) assert tree.data == "start" assert len(tree.children) == 3 assert tree.children[0].data == "inst_def" assert tree.children[1].data == "inst_def" assert tree.children[2].data == "plain_edge" def test_location_directive(self, parser): tree = parser.parse(dedent("""\ @data_section|sm0: """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "location_dir" def test_location_directive_with_placement_and_colon(self, parser): """AC6.1: Location directive with placement and trailing colon.""" tree = parser.parse(dedent("""\ @region|pe0: """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "location_dir" def test_node_ref_in_edge_context_no_colon(self, parser): """AC6.2: @node without colon in edge context parses as node_ref.""" tree = parser.parse(dedent("""\ @src |> @dest:L """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "plain_edge" # Verify the edge has two node refs, not a location_dir def test_location_directive_no_colon_produces_parse_error(self, parser): """AC6.3: Location directive without trailing colon produces PARSE error.""" # The grammar now requires trailing colon for location_dir. # A bare @ref without colon should produce a parse error. with pytest.raises(LarkError): parser.parse(dedent("""\ @data_section &a <| add """)) class TestDataDefs: def test_hex_data(self, parser): tree = parser.parse(dedent("""\ @hello|sm0:0 = 0x05 @hello|sm0:1 = 'h', 'e' @hello|sm0:2 = 'l', 'l' """)) assert tree.data == "start" assert len(tree.children) == 3 assert all(child.data == "data_def" for child in tree.children) def test_macro_invocation(self, parser): tree = parser.parse(dedent("""\ @hello = #str "hello" """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "data_def" def test_multi_line_string(self, parser): tree = parser.parse(dedent('''\ @msg = "hello world" ''')) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "data_def" def test_raw_string(self, parser): tree = parser.parse(dedent("""\ @path = r"no\\escapes\\here" """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "data_def" def test_byte_string(self, parser): tree = parser.parse(dedent("""\ @raw_data = b"\\x01\\x02\\x03" """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "data_def" class TestComments: def test_inline_comments(self, parser): tree = parser.parse(dedent("""\ &my_add <| add ; this is a comment &a |> &b:L ; wire a to b left port """)) assert tree.data == "start" assert len(tree.children) == 2 assert tree.children[0].data == "inst_def" assert tree.children[1].data == "plain_edge" class TestMixedPrograms: def test_mixed_program(self, parser): tree = parser.parse(dedent("""\ @counter|sm0:0 = 0x00 $main |> { &init <| const, 0 &loop_add <| add &cmp <| lte &branch <| breq &output <| iow, dest=0x01 &init |> &loop_add:L &loop_add |> &cmp:L &loop_add |> &output:L } """)) assert tree.data == "start" assert len(tree.children) == 2 assert tree.children[0].data == "data_def" assert tree.children[1].data == "func_def" class TestSMOps: def test_read_op(self, parser): tree = parser.parse(dedent("""\ &cell <| read """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_write_op(self, parser): tree = parser.parse(dedent("""\ &cell <| write """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_clear_op(self, parser): tree = parser.parse(dedent("""\ &cell <| clear """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_alloc_op(self, parser): tree = parser.parse(dedent("""\ &cell <| alloc """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_free_op(self, parser): tree = parser.parse(dedent("""\ &cell <| free """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_rd_inc_op(self, parser): tree = parser.parse(dedent("""\ &cell <| rd_inc """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_rd_dec_op(self, parser): tree = parser.parse(dedent("""\ &cell <| rd_dec """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" def test_cmp_sw_op(self, parser): tree = parser.parse(dedent("""\ &cell <| cmp_sw """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "inst_def" class TestSystemPragma: def test_system_pragma_minimal(self, parser): tree = parser.parse(dedent("""\ @system pe=4, sm=1 """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "system_pragma" def test_system_pragma_full(self, parser): tree = parser.parse(dedent("""\ @system pe=2, sm=1, iram=128, ctx=2 """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "system_pragma" def test_system_pragma_with_hex(self, parser): tree = parser.parse(dedent("""\ @system pe=0x04, sm=0x01 """)) assert tree.data == "start" assert len(tree.children) == 1 assert tree.children[0].data == "system_pragma" class TestUnknownOpcodes: def test_unknown_opcode_fails(self, parser): with pytest.raises((exceptions.UnexpectedToken, exceptions.UnexpectedCharacters)): parser.parse(dedent("""\ &unknown <| foobar """))