elipl/lib/tilly/bdd.ex
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

147 lines
4.5 KiB
Elixir

defmodule Tilly.BDD do
@moduledoc """
Manages the BDD store, including hash-consing of BDD nodes.
The BDD store is expected to be part of a `typing_ctx` map under the key `:bdd_store`.
"""
alias Tilly.BDD.Node
@false_node_id 0
@true_node_id 1
@initial_next_node_id 2
@universal_ops_module :universal_ops
@doc """
Initializes the BDD store within the typing context.
Pre-interns canonical `false` and `true` BDD nodes.
"""
def init_bdd_store(typing_ctx) when is_map(typing_ctx) do
false_structure = Node.mk_false()
true_structure = Node.mk_true()
bdd_store = %{
nodes_by_structure: %{
{false_structure, @universal_ops_module} => @false_node_id,
{true_structure, @universal_ops_module} => @true_node_id
},
structures_by_id: %{
@false_node_id => %{structure: false_structure, ops_module: @universal_ops_module},
@true_node_id => %{structure: true_structure, ops_module: @universal_ops_module}
},
next_node_id: @initial_next_node_id,
ops_cache: %{} # Cache for BDD operations {op_key, id1, id2} -> result_id
}
Map.put(typing_ctx, :bdd_store, bdd_store)
end
@doc """
Gets an existing BDD node ID or interns a new one if it's not already in the store.
Returns a tuple `{new_typing_ctx, node_id}`.
The `typing_ctx` is updated if a new node is interned.
"""
def get_or_intern_node(typing_ctx, logical_structure, ops_module_atom) do
bdd_store = Map.get(typing_ctx, :bdd_store)
unless bdd_store do
raise ArgumentError, "BDD store not initialized in typing_ctx. Call init_bdd_store first."
end
key = {logical_structure, ops_module_atom}
case Map.get(bdd_store.nodes_by_structure, key) do
nil ->
# Node not found, intern it
node_id = bdd_store.next_node_id
new_nodes_by_structure = Map.put(bdd_store.nodes_by_structure, key, node_id)
node_data = %{structure: logical_structure, ops_module: ops_module_atom}
new_structures_by_id = Map.put(bdd_store.structures_by_id, node_id, node_data)
new_next_node_id = node_id + 1
new_bdd_store =
%{
bdd_store
| nodes_by_structure: new_nodes_by_structure,
structures_by_id: new_structures_by_id,
next_node_id: new_next_node_id
}
new_typing_ctx = Map.put(typing_ctx, :bdd_store, new_bdd_store)
{new_typing_ctx, node_id}
existing_node_id ->
# Node found
{typing_ctx, existing_node_id}
end
end
@doc """
Retrieves the node's structure and ops_module from the BDD store.
Returns `%{structure: logical_structure_tuple, ops_module: ops_module_atom}` or `nil` if not found.
"""
def get_node_data(typing_ctx, node_id) do
with %{bdd_store: %{structures_by_id: structures_by_id}} <- typing_ctx,
data when not is_nil(data) <- Map.get(structures_by_id, node_id) do
data
else
_ -> nil
end
end
@doc """
Checks if the given node ID corresponds to the canonical `false` BDD node.
"""
def is_false_node?(typing_ctx, node_id) do
# Optimized check for the predefined ID
if node_id == @false_node_id do
true
else
# Fallback for cases where a node might be structurally false but not have the canonical ID.
# This should ideally not happen with proper interning of Node.mk_false() via get_or_intern_node.
case get_node_data(typing_ctx, node_id) do
%{structure: structure, ops_module: @universal_ops_module} ->
structure == Node.mk_false()
_ ->
false
end
end
end
@doc """
Checks if the given node ID corresponds to the canonical `true` BDD node.
"""
def is_true_node?(typing_ctx, node_id) do
# Optimized check for the predefined ID
if node_id == @true_node_id do
true
else
# Fallback for cases where a node might be structurally true but not have the canonical ID.
case get_node_data(typing_ctx, node_id) do
%{structure: structure, ops_module: @universal_ops_module} ->
structure == Node.mk_true()
_ ->
false
end
end
end
@doc """
Returns the canonical ID for the `false` BDD node.
"""
def false_node_id(), do: @false_node_id
@doc """
Returns the canonical ID for the `true` BDD node.
"""
def true_node_id(), do: @true_node_id
@doc """
Returns the atom used as the `ops_module` for universal nodes like `true` and `false`.
"""
def universal_ops_module(), do: @universal_ops_module
end