defmodule Firebird.Compiler.SourceMap do @moduledoc """ Source mapping for compiled WASM modules. Tracks which Elixir functions map to which WASM exports, enabling better error reporting and debugging. ## Usage {:ok, source_map} = SourceMap.generate(module_ir, source_path) # Look up Elixir source info for a WASM export SourceMap.lookup(source_map, "fibonacci") # => %{module: :Math, function: :fibonacci, arity: 1, source: "lib/math.ex"} """ alias Firebird.Compiler.IR @type t :: %__MODULE__{ module: atom(), source: String.t() | nil, entries: [entry()], compiled_at: DateTime.t() } @type entry :: %{ function: atom(), arity: non_neg_integer(), export_name: String.t(), params: [atom()], tail_recursive: boolean() } defstruct [:module, :source, :entries, :compiled_at] @doc """ Generate a source map from a module IR. ## Parameters - `module_ir` - The IR module - `source` - Optional source file path ## Returns - `{:ok, %SourceMap{}}` with mapping information """ @spec generate(IR.Module.t(), String.t() | nil) :: {:ok, t()} def generate(%IR.Module{} = module_ir, source \\ nil) do entries = Enum.map(module_ir.functions, fn func -> %{ function: func.name, arity: func.arity, export_name: Atom.to_string(func.name), params: func.params, tail_recursive: is_tail_loop?(func.body) } end) source_map = %__MODULE__{ module: module_ir.name, source: source, entries: entries, compiled_at: DateTime.utc_now() } {:ok, source_map} end @doc """ Look up source information for a WASM export name. """ @spec lookup(t(), String.t()) :: entry() | nil def lookup(%__MODULE__{} = source_map, export_name) do Enum.find(source_map.entries, fn entry -> entry.export_name == export_name end) end @doc """ List all exported function names. """ @spec exports(t()) :: [String.t()] def exports(%__MODULE__{} = source_map) do Enum.map(source_map.entries, & &1.export_name) end @doc """ Serialize source map to JSON-compatible format. """ @spec to_json(t()) :: map() def to_json(%__MODULE__{} = sm) do %{ "module" => Atom.to_string(sm.module), "source" => sm.source, "compiled_at" => DateTime.to_iso8601(sm.compiled_at), "exports" => Enum.map(sm.entries, fn e -> %{ "function" => Atom.to_string(e.function), "arity" => e.arity, "export_name" => e.export_name, "params" => Enum.map(e.params, &Atom.to_string/1), "tail_recursive" => e.tail_recursive } end) } end defp is_tail_loop?({:tail_loop, _, _}), do: true defp is_tail_loop?(_), do: false end