defmodule Til.ListParserTest do use ExUnit.Case, async: true alias Til.Parser import Til.TestHelpers describe "List Parsing" do test "parses an empty list []" do source = "[]" {:ok, nodes_map} = Parser.parse(source) file_node = get_file_node_from_map(nodes_map) list_node_id = hd(file_node.children) list_node = Map.get(nodes_map, list_node_id) assert list_node.ast_node_type == :list_expression assert list_node.raw_string == "[]" assert list_node.children == [] assert list_node.parsing_error == nil # Location check # "[]" # ^ offset 0, line 1, col 1 # ^ offset 1, line 1, col 2 # ^ offset 2, line 1, col 3 (end position, exclusive for offset, inclusive for col) assert list_node.location == [0, 1, 1, 2, 1, 3] # file_node is already fetched and used to get list_node assert file_node.children == [list_node.id] end test "parses a list of integers [1 2 3]" do source = "[1 2 3]" {:ok, nodes_map} = Parser.parse(source) file_node = get_file_node_from_map(nodes_map) list_node_id = hd(file_node.children) list_node = Map.get(nodes_map, list_node_id) assert list_node.ast_node_type == :list_expression assert list_node.raw_string == "[1 2 3]" assert list_node.parsing_error == nil assert length(list_node.children) == 3 # Location check # "[1 2 3]" # ^ offset 0, line 1, col 1 # ^ offset 7, line 1, col 8 assert list_node.location == [0, 1, 1, 7, 1, 8] # Check children child1_id = Enum.at(list_node.children, 0) child2_id = Enum.at(list_node.children, 1) child3_id = Enum.at(list_node.children, 2) child1 = Map.get(nodes_map, child1_id) child2 = Map.get(nodes_map, child2_id) child3 = Map.get(nodes_map, child3_id) assert child1.ast_node_type == :literal_integer assert child1.value == 1 assert child1.raw_string == "1" assert child1.parent_id == list_node.id # "[1 2 3]" # ^ offset 1, line 1, col 2 # ^ offset 2, line 1, col 3 assert child1.location == [1, 1, 2, 2, 1, 3] assert child2.ast_node_type == :literal_integer assert child2.value == 2 assert child2.raw_string == "2" assert child2.parent_id == list_node.id # "[1 2 3]" # ^ offset 3, line 1, col 4 # ^ offset 4, line 1, col 5 assert child2.location == [3, 1, 4, 4, 1, 5] assert child3.ast_node_type == :literal_integer assert child3.value == 3 assert child3.raw_string == "3" assert child3.parent_id == list_node.id # "[1 2 3]" # ^ offset 5, line 1, col 6 # ^ offset 6, line 1, col 7 assert child3.location == [5, 1, 6, 6, 1, 7] # file_node is already fetched and used to get list_node assert file_node.children == [list_node.id] end test "parses an unclosed list [1 2" do source = "[1 2" {:ok, nodes_map} = Parser.parse(source) file_node = get_file_node_from_map(nodes_map) list_node_id = hd(file_node.children) list_node = Map.get(nodes_map, list_node_id) assert list_node.ast_node_type == :list_expression assert list_node.raw_string == "[1 2" # Raw string is what was consumed for the list assert list_node.parsing_error == "Unclosed list" assert length(list_node.children) == 2 # Children that were successfully parsed # Location check for the unclosed list node # "[1 2" # ^ offset 0, line 1, col 1 # ^ offset 4, line 1, col 5 (end of consumed input for this node) assert list_node.location == [0, 1, 1, 4, 1, 5] child1 = get_node_by_id(nodes_map, Enum.at(list_node.children, 0)) child2 = get_node_by_id(nodes_map, Enum.at(list_node.children, 1)) assert child1.value == 1 assert child2.value == 2 # file_node is already fetched and used to get list_node assert file_node.children == [list_node.id] end test "parses an unexpected closing bracket ] at top level" do source = "]" {:ok, nodes_map} = Parser.parse(source) file_node = get_file_node_from_map(nodes_map) error_node_id = hd(file_node.children) error_node = Map.get(nodes_map, error_node_id) assert error_node.ast_node_type == :unknown # Or a more specific error type if desired assert error_node.raw_string == "]" assert error_node.parsing_error == "Unexpected ']'" # Location check # "]" # ^ offset 0, line 1, col 1 # ^ offset 1, line 1, col 2 assert error_node.location == [0, 1, 1, 1, 1, 2] # file_node is already fetched and used to get error_node assert file_node.children == [error_node.id] end test "parses an unexpected closing bracket ] inside S-expression (foo ])" do source = "(foo ])" {:ok, nodes_map} = Parser.parse(source) file_node = get_file_node_from_map(nodes_map) sexpr_node_id = hd(file_node.children) # S-expression is the top-level sexpr_node = Map.get(nodes_map, sexpr_node_id) assert sexpr_node.ast_node_type == :s_expression assert sexpr_node.raw_string == "(foo ])" assert sexpr_node.parsing_error == nil # The S-expression itself is not unclosed assert length(sexpr_node.children) == 2 # 'foo' and the error node for ']' # First child 'foo' foo_node = get_node_by_id(nodes_map, Enum.at(sexpr_node.children, 0)) assert foo_node.ast_node_type == :symbol assert foo_node.name == "foo" # Second child is the error node for ']' error_node = get_node_by_id(nodes_map, Enum.at(sexpr_node.children, 1)) assert error_node.ast_node_type == :unknown assert error_node.raw_string == "]" assert error_node.parsing_error == "Unexpected ']'" assert error_node.parent_id == sexpr_node.id # Location check for ']' # "(foo ])" # ^ offset 5, line 1, col 6 # ^ offset 6, line 1, col 7 assert error_node.location == [5, 1, 6, 6, 1, 7] end end end