elipl/test/tilly/bdd/ops_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

192 lines
8.6 KiB
Elixir

defmodule Tilly.BDD.OpsTest do
use ExUnit.Case, async: true
alias Tilly.BDD
alias Tilly.BDD.Node
alias Tilly.BDD.Ops
alias Tilly.BDD.IntegerBoolOps # Using a concrete ops_module for testing
setup do
typing_ctx = BDD.init_bdd_store(%{})
# Pre-intern some common elements for tests if needed, e.g., integers
# For now, rely on ops to intern elements as they are used.
%{initial_ctx: typing_ctx}
end
describe "leaf/3" do
test "interning an empty leaf value returns predefined false_id", %{initial_ctx: ctx} do
{new_ctx, node_id} = Ops.leaf(ctx, false, IntegerBoolOps)
assert node_id == BDD.false_node_id()
assert new_ctx.bdd_store.ops_cache == ctx.bdd_store.ops_cache # Cache not used for this path
end
test "interning a full leaf value returns predefined true_id", %{initial_ctx: ctx} do
{new_ctx, node_id} = Ops.leaf(ctx, true, IntegerBoolOps)
assert node_id == BDD.true_node_id()
assert new_ctx.bdd_store.ops_cache == ctx.bdd_store.ops_cache
end
@tag :skip
test "interning a new 'other' leaf value returns a new ID", %{initial_ctx: _ctx} do
# Assuming IntegerBoolOps.test_leaf_value/1 would return :other for non-booleans
# For this test, we'd need an ops_module where e.g. an integer is an :other leaf.
# Let's simulate with a mock or by extending IntegerBoolOps if it were not read-only.
# For now, this test is conceptual for boolean leaves.
# If IntegerBoolOps was extended:
# defmodule MockIntegerOps do
# defdelegate compare_elements(e1, e2), to: IntegerBoolOps
# defdelegate equal_element?(e1, e2), to: IntegerBoolOps
# # ... other delegates
# def test_leaf_value(10), do: :other # Treat 10 as a specific leaf
# def test_leaf_value(true), do: :full
# def test_leaf_value(false), do: :empty
# end
# {ctx_after_intern, node_id} = Ops.leaf(ctx, 10, MockIntegerOps)
# assert node_id != BDD.true_node_id() and node_id != BDD.false_node_id()
# assert BDD.get_node_data(ctx_after_intern, node_id).structure == Node.mk_leaf(10)
# Placeholder for more complex leaf types. Test is skipped.
end
end
describe "split/6 basic simplifications" do
test "if i_id is true, returns true_id", %{initial_ctx: ctx} do
{_p_ctx, p_id} = Ops.leaf(ctx, false, IntegerBoolOps) # dummy
{_n_ctx, n_id} = Ops.leaf(ctx, false, IntegerBoolOps) # dummy
true_id = BDD.true_node_id()
{new_ctx, result_id} = Ops.split(ctx, 10, p_id, true_id, n_id, IntegerBoolOps)
assert result_id == true_id
assert new_ctx == ctx # No new nodes or cache entries expected for this rule
end
test "if p_id == n_id and p_id == i_id, returns p_id", %{initial_ctx: ctx} do
{ctx, p_id} = BDD.get_or_intern_node(ctx, Node.mk_leaf(false), IntegerBoolOps) # some leaf
i_id = p_id
n_id = p_id
{_new_ctx, result_id} = Ops.split(ctx, 10, p_id, i_id, n_id, IntegerBoolOps)
assert result_id == p_id
# Cache might be touched if union_bdds was called, but this rule is direct.
# For p_id == i_id, it's direct.
end
test "if p_id == n_id and p_id != i_id, returns union(p_id, i_id)", %{initial_ctx: ctx} do
{ctx, p_id} = BDD.get_or_intern_node(ctx, Node.mk_leaf(false), IntegerBoolOps)
{ctx, i_id} = BDD.get_or_intern_node(ctx, Node.mk_leaf(true), IntegerBoolOps) # different leaf
n_id = p_id
# Expected union of p_id (false_leaf) and i_id (true_leaf) is true_id
# This relies on union_bdds working.
{_new_ctx, result_id} = Ops.split(ctx, 10, p_id, i_id, n_id, IntegerBoolOps)
expected_union_id = BDD.true_node_id() # Union of false_leaf and true_leaf
assert result_id == expected_union_id
end
test "interns a new split node if no simplification rule applies", %{initial_ctx: ctx} do
{ctx, p_id} = Ops.leaf(ctx, false, IntegerBoolOps) # false_node_id
{ctx, i_id} = Ops.leaf(ctx, false, IntegerBoolOps) # false_node_id
{ctx, n_id} = Ops.leaf(ctx, true, IntegerBoolOps) # true_node_id (different from p_id)
element = 20
{new_ctx, split_node_id} = Ops.split(ctx, element, p_id, i_id, n_id, IntegerBoolOps)
assert split_node_id != p_id and split_node_id != i_id and split_node_id != n_id
assert split_node_id != BDD.true_node_id() and split_node_id != BDD.false_node_id()
node_data = BDD.get_node_data(new_ctx, split_node_id)
assert node_data.structure == Node.mk_split(element, p_id, i_id, n_id)
assert node_data.ops_module == IntegerBoolOps
assert new_ctx.bdd_store.next_node_id > ctx.bdd_store.next_node_id
end
end
describe "union_bdds/3" do
test "A U A = A", %{initial_ctx: ctx} do
{ctx, a_id} = Ops.leaf(ctx, false, IntegerBoolOps) # false_node_id
{new_ctx, result_id} = Ops.union_bdds(ctx, a_id, a_id)
assert result_id == a_id
assert Map.has_key?(new_ctx.bdd_store.ops_cache, {:union, a_id, a_id})
end
test "A U True = True", %{initial_ctx: ctx} do
{ctx, a_id} = Ops.leaf(ctx, false, IntegerBoolOps)
true_id = BDD.true_node_id()
{_new_ctx, result_id} = Ops.union_bdds(ctx, a_id, true_id)
assert result_id == true_id
end
test "A U False = A", %{initial_ctx: ctx} do
{ctx, a_id} = Ops.leaf(ctx, true, IntegerBoolOps) # true_node_id
false_id = BDD.false_node_id()
{_new_ctx, result_id} = Ops.union_bdds(ctx, a_id, false_id)
assert result_id == a_id
end
test "union of two distinct leaves", %{initial_ctx: ctx} do
# leaf(false) U leaf(true) = leaf(true OR false) = leaf(true) -> true_node_id
{ctx, leaf_false_id} = Ops.leaf(ctx, false, IntegerBoolOps)
{ctx, leaf_true_id} = Ops.leaf(ctx, true, IntegerBoolOps) # This is BDD.true_node_id()
{_new_ctx, result_id} = Ops.union_bdds(ctx, leaf_false_id, leaf_true_id)
assert result_id == BDD.true_node_id()
end
test "union of two simple split nodes with same element", %{initial_ctx: ctx} do
# BDD1: split(10, True, False, False)
# BDD2: split(10, False, True, False)
# Union: split(10, True U False, False U True, False U False)
# = split(10, True, True, False)
true_id = BDD.true_node_id()
false_id = BDD.false_node_id()
{ctx, bdd1_id} = Ops.split(ctx, 10, true_id, false_id, false_id, IntegerBoolOps)
{ctx, bdd2_id} = Ops.split(ctx, 10, false_id, true_id, false_id, IntegerBoolOps)
{final_ctx, union_id} = Ops.union_bdds(ctx, bdd1_id, bdd2_id)
# Expected structure
{_final_ctx, expected_bdd_id} = Ops.split(final_ctx, 10, true_id, true_id, false_id, IntegerBoolOps)
assert union_id == expected_bdd_id
end
test "union of two simple split nodes with different elements (x1 < x2)", %{initial_ctx: ctx} do
# BDD1: split(10, True, False, False)
# BDD2: split(20, False, True, False)
# Union (x1 < x2): split(10, p1, i1 U BDD2, n1)
# = split(10, True, False U BDD2, False)
# = split(10, True, BDD2, False)
{ctx, bdd1_p1_id} = Ops.leaf(ctx, true, IntegerBoolOps)
{ctx, bdd1_i1_id} = Ops.leaf(ctx, false, IntegerBoolOps)
{ctx, bdd1_n1_id} = Ops.leaf(ctx, false, IntegerBoolOps)
{ctx, bdd1_id} = Ops.split(ctx, 10, bdd1_p1_id, bdd1_i1_id, bdd1_n1_id, IntegerBoolOps)
{ctx, bdd2_p2_id} = Ops.leaf(ctx, false, IntegerBoolOps)
{ctx, bdd2_i2_id} = Ops.leaf(ctx, true, IntegerBoolOps)
{ctx, bdd2_n2_id} = Ops.leaf(ctx, false, IntegerBoolOps)
{ctx, bdd2_id} = Ops.split(ctx, 20, bdd2_p2_id, bdd2_i2_id, bdd2_n2_id, IntegerBoolOps)
{final_ctx, union_id} = Ops.union_bdds(ctx, bdd1_id, bdd2_id)
# Expected structure: split(10, True, BDD2, False)
{_final_ctx, expected_bdd_id} = Ops.split(final_ctx, 10, bdd1_p1_id, bdd2_id, bdd1_n1_id, IntegerBoolOps)
assert union_id == expected_bdd_id
end
test "uses cache for repeated union operations", %{initial_ctx: ctx} do
{ctx, a_id} = Ops.leaf(ctx, false, IntegerBoolOps)
{ctx, b_id} = Ops.leaf(ctx, true, IntegerBoolOps)
{ctx_after_first_union, _result1_id} = Ops.union_bdds(ctx, a_id, b_id)
cache_after_first = ctx_after_first_union.bdd_store.ops_cache
{ctx_after_second_union, _result2_id} = Ops.union_bdds(ctx_after_first_union, a_id, b_id)
# The BDD store itself (nodes, next_id) should not change on a cache hit.
# The ops_cache map reference will be the same if the result was cached.
assert ctx_after_second_union.bdd_store.ops_cache == cache_after_first
assert ctx_after_second_union.bdd_store.next_node_id == ctx_after_first_union.bdd_store.next_node_id
end
end
end