elipl/test/til/map_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

242 lines
9.5 KiB
Elixir

defmodule Til.MapParserTest do
use ExUnit.Case, async: true
alias Til.Parser
alias Til.TestHelpers
describe "parse/2 - Map Expressions" do
test "parses an empty map" do
source = "m{}"
{:ok, nodes_map} = Parser.parse(source)
# file_node + map_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)
map_node = TestHelpers.get_first_child_node(nodes_map)
assert map_node.ast_node_type == :map_expression
assert map_node.parent_id == file_node.id
assert map_node.children == []
# "m{}"
assert map_node.location == [0, 1, 1, 3, 1, 4]
assert map_node.raw_string == "m{}"
end
test "parses a simple map with symbol keys and values" do
source = "m{key1 val1 key2 val2}"
{:ok, nodes_map} = Parser.parse(source)
# 1 file_node + 1 map_expression node + 4 symbol nodes = 6 nodes
assert map_size(nodes_map) == 6
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
refute is_nil(file_node)
map_node = TestHelpers.get_first_child_node(nodes_map)
refute is_nil(map_node)
assert map_node.ast_node_type == :map_expression
assert map_node.parent_id == file_node.id
assert length(map_node.children) == 4
children_nodes = Enum.map(map_node.children, &TestHelpers.get_node_by_id(nodes_map, &1))
assert Enum.map(children_nodes, & &1.name) == ["key1", "val1", "key2", "val2"]
Enum.each(children_nodes, fn child ->
assert child.parent_id == map_node.id
assert child.ast_node_type == :symbol
end)
# "m{key1 val1 key2 val2}"
assert map_node.location == [0, 1, 1, 22, 1, 23]
assert map_node.raw_string == "m{key1 val1 key2 val2}"
end
test "parses a map with mixed type values" do
source = "m{s 'a string' i 123 sym value}"
{map_node, nodes_map} = TestHelpers.parse_and_get_first_node(source)
# 1 file_node + 1 map, 3 keys (symbols), 1 string val, 1 int val, 1 symbol val = 1 + 1 + 3 + 3 = 8 nodes
assert map_size(nodes_map) == 8
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
refute is_nil(file_node)
refute is_nil(map_node)
assert map_node.ast_node_type == :map_expression
assert map_node.parent_id == file_node.id
assert length(map_node.children) == 6
children_nodes = Enum.map(map_node.children, &TestHelpers.get_node_by_id(nodes_map, &1))
# s
assert Enum.at(children_nodes, 0).ast_node_type == :symbol
assert Enum.at(children_nodes, 0).name == "s"
# 'a string'
assert Enum.at(children_nodes, 1).ast_node_type == :literal_string
assert Enum.at(children_nodes, 1).value == "a string"
# i
assert Enum.at(children_nodes, 2).ast_node_type == :symbol
assert Enum.at(children_nodes, 2).name == "i"
# 123
assert Enum.at(children_nodes, 3).ast_node_type == :literal_integer
assert Enum.at(children_nodes, 3).value == 123
# sym
assert Enum.at(children_nodes, 4).ast_node_type == :symbol
assert Enum.at(children_nodes, 4).name == "sym"
# value
assert Enum.at(children_nodes, 5).ast_node_type == :symbol
assert Enum.at(children_nodes, 5).name == "value"
end
test "parses nested maps" do
source = "m{outer_key m{inner_key inner_val}}"
{:ok, nodes_map} = Parser.parse(source)
# Nodes: 1 file_node, outer_map, outer_key, inner_map, inner_key, inner_val => 6 nodes
assert map_size(nodes_map) == 6
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
refute is_nil(file_node)
outer_map = TestHelpers.get_first_child_node(nodes_map)
refute is_nil(outer_map)
assert outer_map.ast_node_type == :map_expression
assert outer_map.parent_id == file_node.id
# outer_key, inner_map
assert length(outer_map.children) == 2
outer_key_node = TestHelpers.get_nth_child_node(nodes_map, 0, outer_map.id)
inner_map_node = TestHelpers.get_nth_child_node(nodes_map, 1, outer_map.id)
assert outer_key_node.ast_node_type == :symbol
assert outer_key_node.name == "outer_key"
assert inner_map_node.ast_node_type == :map_expression
assert inner_map_node.parent_id == outer_map.id
# inner_key, inner_val
assert length(inner_map_node.children) == 2
inner_key_node = TestHelpers.get_nth_child_node(nodes_map, 0, inner_map_node.id)
inner_val_node = TestHelpers.get_nth_child_node(nodes_map, 1, inner_map_node.id)
assert inner_key_node.ast_node_type == :symbol
assert inner_key_node.name == "inner_key"
assert inner_val_node.ast_node_type == :symbol
assert inner_val_node.name == "inner_val"
end
test "parses map with varied spacing" do
source = "m{ key1 val1\n key2 val2 }"
{map_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(map_node)
assert map_node.ast_node_type == :map_expression
assert map_node.parent_id == file_node.id
assert length(map_node.children) == 4
children_names_values =
Enum.map(map_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 == ["key1", "val1", "key2", "val2"]
end
test "handles unclosed map" do
source = "m{key1 val1"
{map_node, nodes_map} = TestHelpers.parse_and_get_first_node(source)
# Expect 1 file_node, 1 map_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(map_node)
assert map_node.ast_node_type == :map_expression
assert map_node.parent_id == file_node.id
assert map_node.parsing_error == "Unclosed map"
# key1, val1
assert length(map_node.children) == 2
# "m{key1 val1"
assert map_node.location == [0, 1, 1, 11, 1, 12]
assert map_node.raw_string == "m{key1 val1"
end
test "handles unexpected closing curly brace at top level (not map specific, but related)" do
# This } is not part of m{}
source = "foo } bar"
{:ok, nodes_map} = Parser.parse(source)
# 1 file_node + 3 items
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]
end
test "parses map with odd number of elements (parser accepts, semantic check later)" do
source = "m{key1 val1 key2}"
{map_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(map_node)
assert map_node.ast_node_type == :map_expression
assert map_node.parent_id == file_node.id
# key1, val1, key2
assert length(map_node.children) == 3
children_nodes = Enum.map(map_node.children, &TestHelpers.get_node_by_id(nodes_map, &1))
assert Enum.map(children_nodes, & &1.name) == ["key1", "val1", "key2"]
end
test "map within an S-expression" do
source = "(do-something m{a 1 b 2})"
{s_expr_node, nodes_map} = TestHelpers.parse_and_get_first_node(source)
# 1 file_node, s-expr, do-something, map, a, 1, b, 2 => 8 nodes
assert map_size(nodes_map) == 8
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
refute is_nil(file_node)
refute is_nil(s_expr_node)
assert s_expr_node.ast_node_type == :s_expression
assert s_expr_node.parent_id == file_node.id
# do-something, map_node
assert length(s_expr_node.children) == 2
map_node = TestHelpers.get_nth_child_node(nodes_map, 1, s_expr_node.id)
assert map_node.ast_node_type == :map_expression
assert map_node.parent_id == s_expr_node.id
# a, 1, b, 2
assert length(map_node.children) == 4
end
test "map as a value in another map" do
source = "m{data m{x 10 y 20}}"
{outer_map_node, nodes_map} = TestHelpers.parse_and_get_first_node(source)
# 1 file_node, outer_map, data_symbol, inner_map, x_symbol, 10_int, y_symbol, 20_int => 8 nodes
assert map_size(nodes_map) == 8
file_node = Enum.find(Map.values(nodes_map), &(&1.ast_node_type == :file))
refute is_nil(file_node)
refute is_nil(outer_map_node)
assert outer_map_node.ast_node_type == :map_expression
assert outer_map_node.parent_id == file_node.id
# data_symbol, inner_map_node
assert length(outer_map_node.children) == 2
inner_map_node = TestHelpers.get_nth_child_node(nodes_map, 1, outer_map_node.id)
assert inner_map_node.ast_node_type == :map_expression
assert inner_map_node.parent_id == outer_map_node.id
# x, 10, y, 20
assert length(inner_map_node.children) == 4
end
end
end