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
147 lines
4.5 KiB
Elixir
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
|