checkpoint failing test after fixing tests checkpoint checkpoint checkpoint re-work asd checkpoint checkpoint checkpoint mix proj checkpoint mix first parser impl checkpoint fix tests re-org parser checkpoint strings fix multiline strings tuples checkpoint maps checkpoint checkpoint checkpoint checkpoint fix weird eof expression parse error checkpoint before typing checkpoint checpoint checkpoint checkpoint checkpoint ids in primitive types checkpoint checkpoint fix tests initial annotation checkpoint checkpoint checkpoint union subtyping conventions refactor - split typer typing tuples checkpoint test refactor checkpoint test refactor parsing atoms checkpoint atoms wip lists checkpoint typing lists checkopint checkpoint wip fixing correct list typing map discussion checkpoint map basic typing fix tests checkpoint checkpoint checkpoint checkpoint fix condition typing fix literal keys in map types checkpoint union types checkpoint union type checkpoint row types discussion & bidirectional typecheck checkpoint basic lambdas checkpoint lambdas typing application wip function application checkpoint checkpoint checkpoint cduce checkpoint checkpoint checkpoint checkpoint checkpoint checkpoint checkpoint
268 lines
11 KiB
Elixir
268 lines
11 KiB
Elixir
defmodule Til.TupleParserTest do
|
|
use ExUnit.Case, async: true
|
|
alias Til.Parser
|
|
alias Til.TestHelpers
|
|
|
|
describe "parse/2 - Tuple Expressions" do
|
|
test "parses an empty tuple" do
|
|
source = "{}"
|
|
{:ok, nodes_map} = Parser.parse(source)
|
|
# file_node + tuple_node
|
|
assert map_size(nodes_map) == 2
|
|
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node)
|
|
tuple_node = TestHelpers.get_first_child_node(nodes_map)
|
|
|
|
assert tuple_node.ast_node_type == :tuple_expression
|
|
assert tuple_node.parent_id == file_node.id
|
|
assert tuple_node.children == []
|
|
# "{}"
|
|
assert tuple_node.location == [0, 1, 1, 2, 1, 3]
|
|
assert tuple_node.raw_string == "{}"
|
|
end
|
|
|
|
test "parses a simple tuple with integers" do
|
|
source = "{1 22 -3}"
|
|
{:ok, nodes_map} = Parser.parse(source)
|
|
# 1 file_node + 1 tuple_expression node + 3 integer nodes = 5 nodes
|
|
assert map_size(nodes_map) == 5
|
|
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node)
|
|
tuple_node = TestHelpers.get_first_child_node(nodes_map)
|
|
|
|
refute is_nil(tuple_node)
|
|
assert tuple_node.ast_node_type == :tuple_expression
|
|
assert tuple_node.parent_id == file_node.id
|
|
assert length(tuple_node.children) == 3
|
|
|
|
children_nodes = Enum.map(tuple_node.children, &TestHelpers.get_node_by_id(nodes_map, &1))
|
|
assert Enum.map(children_nodes, & &1.value) == [1, 22, -3]
|
|
|
|
Enum.each(children_nodes, fn child ->
|
|
assert child.parent_id == tuple_node.id
|
|
assert child.ast_node_type == :literal_integer
|
|
end)
|
|
|
|
# "{1 22 -3}"
|
|
assert tuple_node.location == [0, 1, 1, 9, 1, 10]
|
|
end
|
|
|
|
test "parses a simple tuple with symbols" do
|
|
source = "{foo bar baz}"
|
|
{tuple_node, nodes_map} = TestHelpers.parse_and_get_first_node(source)
|
|
# 1 file_node, 1 tuple_expr, 3 symbols = 5 nodes
|
|
assert map_size(nodes_map) == 5
|
|
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node)
|
|
|
|
refute is_nil(tuple_node)
|
|
assert tuple_node.ast_node_type == :tuple_expression
|
|
assert tuple_node.parent_id == file_node.id
|
|
assert length(tuple_node.children) == 3
|
|
children_nodes = Enum.map(tuple_node.children, &TestHelpers.get_node_by_id(nodes_map, &1))
|
|
assert Enum.map(children_nodes, & &1.name) == ["foo", "bar", "baz"]
|
|
end
|
|
|
|
test "parses nested tuples" do
|
|
source = "{a {b 1} c}"
|
|
{outer_tuple, nodes_map} = TestHelpers.parse_and_get_first_node(source)
|
|
# Nodes: 1 file_node, outer_tuple, a, inner_tuple, c, b, 1 => 7 nodes
|
|
assert map_size(nodes_map) == 7
|
|
|
|
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node)
|
|
|
|
refute is_nil(outer_tuple)
|
|
assert outer_tuple.ast_node_type == :tuple_expression
|
|
assert outer_tuple.parent_id == file_node.id
|
|
assert length(outer_tuple.children) == 3
|
|
|
|
child1 = TestHelpers.get_nth_child_node(nodes_map, 0, outer_tuple.id)
|
|
inner_tuple = TestHelpers.get_nth_child_node(nodes_map, 1, outer_tuple.id)
|
|
child3 = TestHelpers.get_nth_child_node(nodes_map, 2, outer_tuple.id)
|
|
|
|
assert child1.ast_node_type == :symbol && child1.name == "a"
|
|
assert inner_tuple.ast_node_type == :tuple_expression
|
|
assert child3.ast_node_type == :symbol && child3.name == "c"
|
|
|
|
assert inner_tuple.parent_id == outer_tuple.id
|
|
assert length(inner_tuple.children) == 2
|
|
grandchild1 = TestHelpers.get_nth_child_node(nodes_map, 0, inner_tuple.id)
|
|
grandchild2 = TestHelpers.get_nth_child_node(nodes_map, 1, inner_tuple.id)
|
|
|
|
assert grandchild1.ast_node_type == :symbol && grandchild1.name == "b"
|
|
assert grandchild2.ast_node_type == :literal_integer && grandchild2.value == 1
|
|
end
|
|
|
|
test "parses tuples with varied spacing" do
|
|
source = "{ foo 1\nbar }"
|
|
{tuple_node, nodes_map} = TestHelpers.parse_and_get_first_node(source)
|
|
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node)
|
|
|
|
refute is_nil(tuple_node)
|
|
assert tuple_node.ast_node_type == :tuple_expression
|
|
assert tuple_node.parent_id == file_node.id
|
|
assert length(tuple_node.children) == 3
|
|
|
|
children_names_values =
|
|
Enum.map(tuple_node.children, fn id ->
|
|
node = TestHelpers.get_node_by_id(nodes_map, id)
|
|
if node.ast_node_type == :symbol, do: node.name, else: node.value
|
|
end)
|
|
|
|
assert children_names_values == ["foo", 1, "bar"]
|
|
end
|
|
|
|
test "handles unclosed tuple" do
|
|
source = "{foo bar"
|
|
{tuple_node, nodes_map} = TestHelpers.parse_and_get_first_node(source)
|
|
# Expect 1 file_node, 1 tuple_expression node (error), 2 symbol nodes = 4 nodes
|
|
assert map_size(nodes_map) == 4
|
|
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node)
|
|
|
|
refute is_nil(tuple_node)
|
|
assert tuple_node.ast_node_type == :tuple_expression
|
|
assert tuple_node.parent_id == file_node.id
|
|
assert tuple_node.parsing_error == "Unclosed tuple"
|
|
# foo, bar
|
|
assert length(tuple_node.children) == 2
|
|
# "{foo bar"
|
|
assert tuple_node.location == [0, 1, 1, 8, 1, 9]
|
|
end
|
|
|
|
test "handles unexpected closing curly brace at top level" do
|
|
source = "foo } bar"
|
|
{:ok, nodes_map} = Parser.parse(source)
|
|
# 1 file_node, foo, error_node_for_}, bar = 4 nodes
|
|
assert map_size(nodes_map) == 4
|
|
|
|
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node)
|
|
|
|
top_level_children =
|
|
Enum.map(file_node.children, &TestHelpers.get_node_by_id(nodes_map, &1))
|
|
|
|
error_node =
|
|
Enum.find(top_level_children, &(&1.ast_node_type == :unknown && &1.raw_string == "}"))
|
|
|
|
refute is_nil(error_node)
|
|
assert error_node.parent_id == file_node.id
|
|
assert error_node.parsing_error == "Unexpected '}'"
|
|
# location of "}"
|
|
assert error_node.location == [4, 1, 5, 5, 1, 6]
|
|
|
|
symbol_foo =
|
|
Enum.find(top_level_children, &(&1.ast_node_type == :symbol && &1.name == "foo"))
|
|
|
|
refute is_nil(symbol_foo)
|
|
assert symbol_foo.parent_id == file_node.id
|
|
|
|
symbol_bar =
|
|
Enum.find(top_level_children, &(&1.ast_node_type == :symbol && &1.name == "bar"))
|
|
|
|
refute is_nil(symbol_bar)
|
|
assert symbol_bar.parent_id == file_node.id
|
|
end
|
|
|
|
test "parses a tuple with mixed elements including strings, S-expressions, lists, and other tuples" do
|
|
source = "{1 'hello' (a b) [x y] {z} 'end'}"
|
|
{:ok, nodes_map} = Parser.parse(source)
|
|
|
|
# Expected items: 1 outer tuple, 1 int, 1 str, 1 s-expr (2 children), 1 list (2 children), 1 inner tuple (1 child), 1 str_end
|
|
# Node counts: outer_tuple (1) + int (1) + str (1) + s-expr (1) + sym_a (1) + sym_b (1) + list (1) + sym_x (1) + sym_y (1) + inner_tuple (1) + sym_z (1) + str_end (1) = 12 nodes
|
|
# Plus 1 file_node = 13 nodes
|
|
assert map_size(nodes_map) == 13
|
|
|
|
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node)
|
|
outer_tuple_node = TestHelpers.get_first_child_node(nodes_map)
|
|
|
|
refute is_nil(outer_tuple_node)
|
|
assert outer_tuple_node.ast_node_type == :tuple_expression
|
|
assert outer_tuple_node.parent_id == file_node.id
|
|
assert length(outer_tuple_node.children) == 6
|
|
|
|
children = Enum.map(outer_tuple_node.children, &TestHelpers.get_node_by_id(nodes_map, &1))
|
|
|
|
assert Enum.at(children, 0).ast_node_type == :literal_integer
|
|
assert Enum.at(children, 0).value == 1
|
|
|
|
assert Enum.at(children, 1).ast_node_type == :literal_string
|
|
assert Enum.at(children, 1).value == "hello"
|
|
|
|
s_expr_child = Enum.at(children, 2)
|
|
assert s_expr_child.ast_node_type == :s_expression
|
|
assert length(s_expr_child.children) == 2
|
|
|
|
s_expr_children =
|
|
Enum.map(s_expr_child.children, &TestHelpers.get_node_by_id(nodes_map, &1))
|
|
|
|
assert Enum.map(s_expr_children, & &1.name) == ["a", "b"]
|
|
|
|
list_child = Enum.at(children, 3)
|
|
assert list_child.ast_node_type == :list_expression
|
|
assert length(list_child.children) == 2
|
|
list_children = Enum.map(list_child.children, &TestHelpers.get_node_by_id(nodes_map, &1))
|
|
assert Enum.map(list_children, & &1.name) == ["x", "y"]
|
|
|
|
inner_tuple_child = Enum.at(children, 4)
|
|
assert inner_tuple_child.ast_node_type == :tuple_expression
|
|
assert length(inner_tuple_child.children) == 1
|
|
|
|
inner_tuple_children =
|
|
Enum.map(inner_tuple_child.children, &TestHelpers.get_node_by_id(nodes_map, &1))
|
|
|
|
assert Enum.map(inner_tuple_children, & &1.name) == ["z"]
|
|
|
|
assert Enum.at(children, 5).ast_node_type == :literal_string
|
|
assert Enum.at(children, 5).value == "end"
|
|
end
|
|
|
|
test "symbol cannot contain curly braces" do
|
|
# Test "foo{"
|
|
source1 = "foo{"
|
|
{:ok, nodes_map1} = Parser.parse(source1)
|
|
# 1 file_node, "foo" symbol, "{" (unclosed tuple_expression) = 3 nodes
|
|
assert map_size(nodes_map1) == 3
|
|
file_node1 = Enum.find(Map.values(nodes_map1), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node1)
|
|
children1 = Enum.map(file_node1.children, &TestHelpers.get_node_by_id(nodes_map1, &1))
|
|
|
|
foo_node = Enum.find(children1, &(&1.name == "foo"))
|
|
tuple_node = Enum.find(children1, &(&1.ast_node_type == :tuple_expression))
|
|
|
|
refute is_nil(foo_node)
|
|
assert foo_node.parent_id == file_node1.id
|
|
refute is_nil(tuple_node)
|
|
assert tuple_node.parent_id == file_node1.id
|
|
assert tuple_node.parsing_error == "Unclosed tuple"
|
|
assert tuple_node.children == []
|
|
|
|
# Test "foo{bar"
|
|
source2 = "foo{bar"
|
|
{:ok, nodes_map2} = Parser.parse(source2)
|
|
# 1 file_node, "foo" symbol, "{" (tuple_expression), "bar" symbol inside tuple = 4 nodes
|
|
assert map_size(nodes_map2) == 4
|
|
file_node2 = Enum.find(Map.values(nodes_map2), &(&1.ast_node_type == :file))
|
|
refute is_nil(file_node2)
|
|
children2 = Enum.map(file_node2.children, &TestHelpers.get_node_by_id(nodes_map2, &1))
|
|
|
|
foo_node2 = Enum.find(children2, &(&1.name == "foo"))
|
|
tuple_node2 = Enum.find(children2, &(&1.ast_node_type == :tuple_expression))
|
|
|
|
refute is_nil(foo_node2)
|
|
assert foo_node2.parent_id == file_node2.id
|
|
refute is_nil(tuple_node2)
|
|
assert tuple_node2.parent_id == file_node2.id
|
|
assert tuple_node2.parsing_error == "Unclosed tuple"
|
|
assert length(tuple_node2.children) == 1
|
|
bar_node_id = hd(tuple_node2.children)
|
|
bar_node = TestHelpers.get_node_by_id(nodes_map2, bar_node_id)
|
|
assert bar_node.name == "bar"
|
|
assert bar_node.parent_id == tuple_node2.id
|
|
end
|
|
end
|
|
end
|