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
242 lines
9.5 KiB
Elixir
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
|