elipl/test/til/tuple_test.exs
Kacper Marzecki 748f87636a checkpoint
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
2025-06-13 23:48:07 +02:00

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