defmodule Eyeon.TextDecoder do @moduledoc """ Decodes Ion text format to Elixir values. Delegates to Eyeon.TextParser for single-pass recursive descent parsing. """ alias Eyeon.Catalog @ivm_pattern ~r/^\$ion_\d+_\d+$/ @spec decode_all(iodata(), Catalog.t(), map()) :: {:ok, [any()]} | {:error, any()} def decode_all(data, catalog \\ Catalog.new([]), options \\ %{}) do case Eyeon.TextParser.parse(data, %{catalog: catalog, options: options}) do {:ok, values} when is_list(values) -> {:ok, strip_directives(values)} {:ok, value} -> {:ok, strip_directives([value])} other -> other end end @spec decode(iodata(), Catalog.t(), map()) :: {:ok, any()} | {:error, any()} def decode(data, catalog \\ Catalog.new([]), options \\ %{}) do case Eyeon.TextParser.parse(data, %{catalog: catalog, options: options}) do {:ok, values} when is_list(values) -> {:ok, strip_directives(values)} {:ok, value} -> if directive_value?(value), do: {:ok, []}, else: {:ok, value} other -> other end end # IVM symbols ($ion_N_M) and $ion_symbol_table structs are directives, # not data values — filter from output. defp directive_value?({:symbol, name}), do: Regex.match?(@ivm_pattern, name) defp directive_value?({:annotated, {:symbol, "$ion_symbol_table"}, val}) when is_map(val), do: true defp directive_value?(_), do: false defp strip_directives(decoded) do Enum.reject(decoded, &directive_value?/1) end end