lib/zig/manifest.ex

defmodule Zig.Manifest do
  # TODO: spec this better
  @type t :: term

  defmacro resolver(manifest, file \\ nil) do
    # TODO: input the file as passed parameter in case we're putting it from a different place.
    file =
      if file do
        file
      else
        __CALLER__.file
        |> Path.dirname()
        |> Path.absname()
        |> Path.join(".#{__CALLER__.module}.zig")
      end

    quote do
      @__zig_manifest unquote(manifest)

      # note that this resolver has to be rebuilt here, so that zigler can be compile-time only.

      defp __resolve(%{file_name: file, line: line}), do: __resolve(file, line)

      defp __resolve(unquote(file), line),
        do: __resolve(@__zig_manifest, [], line)

      defp __resolve(file, line) when is_binary(file), do: {file, line}

      defp __resolve([{anchor_line, _} = head | rest], stack, line) when anchor_line < line do
        __resolve(rest, [head | stack], line)
      end

      defp __resolve([{anchor_line, {file, _}} | _], stack, line) when anchor_line >= line do
        case List.first(stack) do
          {anchor, {file, relative_line}} ->
            {file, line - anchor + relative_line}

          _ ->
            {unquote(file), line}
        end
      end

      defp __resolve([], stack, line) do
        case List.first(stack) do
          {anchor, {file, relative_line}} ->
            {file, line - anchor + relative_line}

          _ ->
            {unquote(file), line}
        end
      end
    end
  end

  def create(code) do
    code
    |> String.split("\n")
    |> Enum.with_index(1)
    |> Enum.flat_map(fn
      {"// ref " <> rest, anchor} ->
        [file, line] = String.split(rest, ":")

        [{anchor, {Path.absname(file), String.to_integer(line)}}]

      _ ->
        []
    end)
  end

  def resolve(manifest, file, line) when is_integer(line) do
    resolve(manifest, [], file, line)
  end

  defp resolve([{lesser_line, _} = head | rest], stack, file, line) when lesser_line < line do
    resolve(rest, [head | stack], file, line)
  end

  defp resolve([{bigger_line, _} | _], stack, file, line) when bigger_line >= line do
    case List.first(stack) do
      {anchor, {new_file, relative_line}} ->
        {Path.relative_to_cwd(new_file), line - anchor + relative_line}

      _ ->
        {file, line}
    end
  end

  defp resolve([], stack, file, line) do
    case List.first(stack) do
      {anchor, {new_file, relative_line}} ->
        {Path.relative_to_cwd(new_file), line - anchor + relative_line}

      _ ->
        {file, line}
    end
  end
end