defmodule Mix.Tasks.Ptc.GenDocs do @shortdoc "Generate function reference and audit docs from registry" @moduledoc """ Generates documentation from `priv/functions.exs`: 1. `docs/function-reference.md` — all implemented functions grouped by section 2. `docs/clojure-core-audit.md` — clojure.core coverage audit 3. `docs/clojure-string-audit.md` — clojure.string coverage audit 4. `docs/clojure-set-audit.md` — clojure.set coverage audit 5. `docs/java-math-audit.md` — java.lang.Math coverage audit ## Usage mix ptc.gen_docs """ use Mix.Task alias PtcRunner.Lisp.Registry @function_ref_path "docs/function-reference.md" @audits [ %{ path: "docs/clojure-core-audit.md", title: "Clojure Core Audit for PTC-Lisp", description: "Comparison of `clojure.core` vars against PTC-Lisp builtins.", fetch: &Registry.clojure_core_audit/0 }, %{ path: "docs/clojure-string-audit.md", title: "Clojure String Audit for PTC-Lisp", description: "Comparison of `clojure.string` vars against PTC-Lisp builtins.", fetch: &Registry.clojure_string_audit/0 }, %{ path: "docs/clojure-set-audit.md", title: "Clojure Set Audit for PTC-Lisp", description: "Comparison of `clojure.set` vars against PTC-Lisp builtins.", fetch: &Registry.clojure_set_audit/0 }, %{ path: "docs/java-math-audit.md", title: "Java Math Audit for PTC-Lisp", description: "Comparison of `java.lang.Math` methods against PTC-Lisp builtins.", fetch: &Registry.java_math_audit/0 } ] @interop_path "docs/java-interop.md" @impl Mix.Task def run(_args) do Mix.Task.run("app.start") generate_function_reference() Enum.each(@audits, &generate_audit/1) generate_java_interop() end defp generate_function_reference do entries = Registry.implemented() sections = entries |> Enum.group_by(& &1.section) |> Enum.sort_by(fn {section, _} -> section_order(section) end) audit_links = @audits |> Enum.map_join(" | ", fn %{path: path, title: title} -> name = title |> String.replace(" for PTC-Lisp", "") |> String.replace(" Audit", "") "[#{name}](#{Path.basename(path)})" end) content = """ # PTC-Lisp Function Reference > **Warning:** This file is auto-generated by `mix ptc.gen_docs` from `priv/functions.exs`. > Manual edits will be overwritten. Edit `priv/functions.exs` instead. #{length(entries)} functions and special forms. See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | #{audit_links} ## Table of Contents #{toc(sections)} #{Enum.map_join(sections, "\n\n", &render_section/1)} """ File.write!(@function_ref_path, content) Mix.shell().info("Generated #{@function_ref_path} (#{length(entries)} entries)") end defp toc(sections) do Enum.map_join(sections, "\n", fn {section, entries} -> anchor = section |> String.downcase() |> String.replace(~r/[^a-z0-9]+/, "-") |> String.trim("-") "- [#{section}](##{anchor}) (#{length(entries)})" end) end defp section_order("Definitions & Bindings"), do: 0 defp section_order("Conditionals"), do: 1 defp section_order("Threading Macros"), do: 2 defp section_order("Control Flow"), do: 3 defp section_order("Iteration"), do: 4 defp section_order("Core"), do: 5 defp section_order("Predicate Builders"), do: 6 defp section_order("Functional Tools"), do: 7 defp section_order("Agent Control"), do: 8 defp section_order("String Functions"), do: 9 defp section_order("Set Operations"), do: 10 defp section_order("Regex Functions"), do: 11 defp section_order("Math Functions"), do: 12 defp section_order("Interop"), do: 13 defp section_order(_), do: 99 defp render_section({section, entries}) do rows = entries |> Enum.sort_by(& &1.name) |> Enum.map_join("\n", fn entry -> sigs = Enum.join(entry.signatures, ", ") ext = if entry.ptc_extension?, do: " *", else: "" "| `#{entry.name}`#{ext} | `#{sigs}` | #{entry.description} |" end) examples = entries |> Enum.flat_map(fn entry -> Enum.map(entry.examples, fn {code, result} -> {entry.name, code, result} end) end) example_block = if examples == [] do "" else examples_text = Enum.map_join(examples, "\n", fn {_name, code, result} -> "#{code}\n;; => #{result}" end) "\n```clojure\n#{examples_text}\n```" end """ ## #{section} | Function | Signature | Description | |----------|-----------|-------------| #{rows} #{example_block} """ end defp generate_audit(%{path: path, title: title, description: description, fetch: fetch}) do entries = fetch.() counts = Enum.frequencies_by(entries, & &1.status) rows = entries |> Enum.sort_by(& &1.name) |> Enum.map_join("\n", fn entry -> icon = status_icon(entry.status) "| `#{entry.name}` | #{icon} #{entry.status} | #{entry.description} | #{entry.notes} |" end) see_also = @audits |> Enum.reject(&(&1.path == path)) |> Enum.map_join(" | ", fn %{path: p, title: t} -> name = t |> String.replace(" for PTC-Lisp", "") "[#{name}](#{Path.basename(p)})" end) content = """ # #{title} > **Warning:** This file is auto-generated by `mix ptc.gen_docs` from `priv/functions.exs`. > Manual edits will be overwritten. Edit `priv/functions.exs` instead. #{description} See also: [Function Reference](function-reference.md) | #{see_also} ## Summary | Status | Count | |--------|-------| | Supported | #{counts[:supported] || 0} | | Candidate | #{counts[:candidate] || 0} | | Not Relevant | #{counts[:not_relevant] || 0} | | Not Classified | #{counts[:not_classified] || 0} | | **Total** | **#{length(entries)}** | ## Details | Var | Status | Description | Notes | |-----|--------|-------------|-------| #{rows} """ File.write!(path, content) Mix.shell().info("Generated #{path} (#{length(entries)} entries)") end defp generate_java_interop do entries = Registry.java_interop() by_class = entries |> Enum.group_by(& &1.class) |> Enum.sort_by(fn {class, _} -> class end) sections = Enum.map_join(by_class, "\n\n", fn {class, items} -> rows = items |> Enum.sort_by(& &1.name) |> Enum.map_join("\n", fn entry -> sigs = Enum.join(entry.signatures, ", ") kind = entry.kind |> to_string() |> String.capitalize() notes = if entry.notes != "", do: entry.notes, else: "" "| `#{entry.name}` | #{kind} | `#{sigs}` | #{entry.description} | #{notes} |" end) """ ### #{class} | Name | Kind | Signature | Description | Notes | |------|------|-----------|-------------|-------| #{rows} """ end) content = """ # Java Interop Reference for PTC-Lisp > **Warning:** This file is auto-generated by `mix ptc.gen_docs` from `priv/functions.exs`. > Manual edits will be overwritten. Edit `priv/functions.exs` instead. PTC-Lisp emulates a subset of Java interop for LLM compatibility. These are **not** real JVM calls — they are BEAM-native implementations that mirror the Java API surface LLMs are trained on. #{length(entries)} interop entries across #{length(by_class)} classes. See also: [Function Reference](function-reference.md) | [PTC-Lisp Specification](ptc-lisp-specification.md) #{sections} """ File.write!(@interop_path, content) Mix.shell().info("Generated #{@interop_path} (#{length(entries)} entries)") end defp status_icon(:supported), do: "✅" defp status_icon(:candidate), do: "🔲" defp status_icon(:not_relevant), do: "❌" defp status_icon(_), do: "❓" end