Current section
Files
Jump to
Current section
Files
lib/eyeon/binary_decoder/symbol_table.ex
defmodule Eyeon.BinaryDecoder.SymbolTable do
@moduledoc false
alias Eyeon.{Catalog, LocalSymbolTable, SharedSymbolTable, SymbolTable}
@doc """
Resolve a symbol ID to a `{:symbol, name}` tuple.
SID 0 returns `{:symbol, "$0"}`.
"""
@spec resolve_symbol(non_neg_integer(), map()) :: {:symbol, String.t()}
def resolve_symbol(0, _state), do: {:symbol, "$0"}
def resolve_symbol(sid, state) do
case SymbolTable.lookup_name(state.symbol_table, sid) do
{:ok, name} ->
{:symbol, name}
nil ->
max = SymbolTable.max_id(state.symbol_table)
if sid > max do
raise "unmapped symbol ID #{sid} (max_id=#{max})"
else
{:symbol, "$#{sid}"}
end
end
end
@doc """
Resolve a symbol ID to its text string (for struct field names).
SID 0 returns `"$0"`.
"""
@spec resolve_symbol_text(non_neg_integer(), map()) :: String.t()
def resolve_symbol_text(0, _state), do: "$0"
def resolve_symbol_text(sid, state) do
case SymbolTable.lookup_name(state.symbol_table, sid) do
{:ok, name} ->
name
nil ->
max = SymbolTable.max_id(state.symbol_table)
if sid > max do
raise "unmapped symbol ID #{sid} (max_id=#{max})"
else
"$#{sid}"
end
end
end
@doc """
Process a symbol table value (struct or raw field list) and return updated state.
"""
@spec process_symbol_table(term(), map()) :: map()
def process_symbol_table(struct_value, state) when is_map(struct_value) do
imports = Map.get(struct_value, "imports", [])
symbols = Map.get(struct_value, "symbols", [])
resolved_imports = resolve_imports(imports, state.catalog)
local_symbols = extract_symbol_strings(symbols)
new_symtab = LocalSymbolTable.new(resolved_imports, local_symbols)
%{state | symbol_table: new_symtab}
end
def process_symbol_table({:raw_symtab_fields, fields}, state) do
validate_symtab_no_duplicate_fields(fields)
imports =
case List.keyfind(fields, "imports", 0) do
nil -> []
{_, val} -> val
end
symbols =
case List.keyfind(fields, "symbols", 0) do
nil -> []
{_, val} -> val
end
resolved_imports = resolve_imports(imports, state.catalog)
local_symbols = extract_symbol_strings(symbols)
new_symtab = LocalSymbolTable.new(resolved_imports, local_symbols)
%{state | symbol_table: new_symtab}
end
def process_symbol_table(_, state), do: state
@doc """
Validate that a symbol table struct does not have duplicate imports or symbols fields.
"""
@spec validate_symtab_no_duplicate_fields(list()) :: nil
def validate_symtab_no_duplicate_fields(fields) do
field_names = Enum.map(fields, &elem(&1, 0))
imports_count = Enum.count(field_names, &(&1 == "imports"))
symbols_count = Enum.count(field_names, &(&1 == "symbols"))
if imports_count > 1 do
raise "local symbol table has multiple 'imports' fields"
end
if symbols_count > 1 do
raise "local symbol table has multiple 'symbols' fields"
end
end
@doc """
Resolve import entries against the catalog.
"""
@spec resolve_imports(list() | term(), Catalog.t()) :: list()
def resolve_imports(imports, catalog) when is_list(imports) do
Enum.flat_map(imports, fn imp ->
case imp do
imp when is_map(imp) ->
name = Map.get(imp, "name")
version = Map.get(imp, "version", 1)
max_id = Map.get(imp, "max_id")
if name == "$ion" do
[]
else
case Catalog.find(catalog, name, version) do
nil ->
if max_id do
placeholder_symbols = List.duplicate(nil, max_id)
[SharedSymbolTable.new(name, version, placeholder_symbols)]
else
[]
end
table ->
if max_id do
[SharedSymbolTable.adjust(table, max_id)]
else
[table]
end
end
end
_ ->
[]
end
end)
end
def resolve_imports(_, _catalog), do: []
@doc """
Extract string values from a symbols list, replacing non-strings with empty strings.
"""
@spec extract_symbol_strings(list() | term()) :: list()
def extract_symbol_strings(symbols) when is_list(symbols) do
Enum.map(symbols, fn
s when is_binary(s) -> s
_ -> ""
end)
end
def extract_symbol_strings(_), do: []
end