Skip to content

Add ROADMAP, a documentation code-block checker, and fix the docs it found broken - #80

Merged
kthompson merged 3 commits into
mainfrom
claude/codebase-roadmap-doc-2375c3
Sep 2, 2026
Merged

kthompson merged 3 commits into
mainfrom
claude/codebase-roadmap-doc-2375c3

Conversation

@kthompson

@kthompson kthompson commented Sep 2, 2026 •

Copy link
Copy Markdown
Owner

Adds a ROADMAP grounded in measurements, a tool that checks the documentation against the compiler, and the documentation fixes that tool demanded.

Why

The three stated goals — self-hosting, generics, usable sample programs — needed a plan based on what the compiler actually does rather than on estimates. Measuring that turned up more than expected, so this PR is the roadmap plus the first item burned down.

What changed

tools/doccheck/ — a new sbt project. Extracts every ```panther block from the markdown and runs it through the real pncs front end. Blocks take directives in an HTML comment above the fence (parse-only, expect-error, skip reason="..."), which renderers drop. A malformed directive is reported rather than ignored, so a typo cannot quietly disable checking. tools/doccheck/baseline.txt records known-broken blocks and is picked up automatically; the check fails both when a passing snippet breaks and when a baselined snippet starts passing, so the list cannot go stale. Wired in as a fourth CI job.

The documentation now compiles: 157 failing → 0.

200 blocks: 198 ok, 0 failing, 2 skipped

The baseline is empty, so CI enforces a clean run. Corrections were systematic, not scattered typos:

Docs said Panther accepts
Array<Int>, Option<User> Array[int], Option[User]
Int, String, Bool int, string, bool
fun f(): T { return x } def f(): T = x
for (item in items) for (i <- 0 to n)
match (x) { 1 => .. } x match { case 1 => .. }
"n: " + count "n: " + string(count)

ROADMAP.md — every number is measured and every command reproduces it.

What the measurements found

Worth a reviewer's attention; these are the reason the roadmap is ordered the way it is.

  • sbt pnc/compile reports 1058 diagnostics, and 685 mention an unsolved type variable — which is why generic inference leads the plan over everything else.
  • Nothing is ever written to disk. Compilation.emit(output) ignores its argument and Emitter.emit() returns in-memory structures. The documented pncs output.pnb source.pn produces no file, there is no .pnb reader, and no CLI path to the working Compilation.exec(). This is what blocks sample programs.
  • The compiler never exits non-zero — pnc/compile "succeeds" with 1058 errors.
  • pnc/src/ast.pn and printer.pn are stale; transpile now emits Ast.pn/AstPrinter.pn instead. CI cannot see this because git diff --exit-code ignores untracked files. Accounts for 32 of the duplicate-definition diagnostics.
  • in/out variance keywords are inverted in both the transpiler and the binder relative to Variance.scala's own comments. The two inversions cancel, so nothing breaks until someone hand-writes [out T].

Fixing the docs surfaced nine further gaps, all verified against the compiler:

  • val is not enforced — val x = 10; x = 20 compiles with zero diagnostics. The docs promised an error that never happens; that prose is now corrected.
  • break/continue crash the compiler — they parse, then boundErrorStatement calls panic().
  • No return, no compound assignment, no pattern guards/alternation/tuples, no nested functions; decimal literals do not lex; block comments do not nest.

Reviewer notes

  • functions/higher-order-functions.md was rewritten, not fixed. It was 275 lines teaching lambdas. Lambdas, function-typed parameters, and nested def all fail to parse, so higher-order functions do not exist in any form. It is now a short page that says so and shows the workarounds. Its two skip blocks are the only skips in the tree. This is a content decision worth a second opinion.
  • guides/ still duplicates the topic directories. I fixed it in place rather than deleting it — that call is yours. Noted in the roadmap.
  • A checker limitation shapes the docs today. Option, Result, List, and Dictionary are not builtins (only int, string, bool, char, unit, Array are), so a single-block compilation cannot mention Option. Blocks had to work around it. Compiling each block alongside the runtime .pn sources would fix it; it is the top item under ROADMAP §4.4 and deliberately not in this PR.
  • Commits are meant to be read in order; the tool commit alone predates the doc fixes.
  • No compiler source changed, so pnc/src/ needed no regeneration and the transpile-sync invariant is untouched.

Verification

  • sbt test/test — 246/246
  • sbt scalafmtCheckAll — clean
  • sbt "doccheck/run docs/src/content/docs" — 0 failing
  • check-primitives-map.mjs — 15 primitives, 4 invariants, all paths resolve
  • Line endings preserved: 17 CRLF doc files unchanged

🤖 Generated with Claude Code

System recap — adds a new primitive (high risk)

Mode: recap · Base: main @ 1bdb118 · Head: a2b1309

Classification: adds — a new doc-check primitive (the doccheck sbt project and its CI job) plus a full rewrite of the language docs. No compiler source is touched, so no pipeline stage changes behaviour.

Primitives touched

Primitive Group Impact
doc-check quality adds — new sbt project, CLI, baseline, and CI job; new entry in primitives.yaml
language-docs quality extends — 16 pages rewritten; every block now compiles
visual-recap-tooling quality composes — taxonomy gains the doc-check entry
transpiler tooling composes — build.sbt gains the doccheck project; transpile/bootstrap tasks untouched

System map

A new checker compiles documentation snippets through the existing compiler front end; the docs are the input, CI is the gate, and no compiler stage is modified.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	languageDocs["language-docs<br/>Panther language docs"]:::extended
	docCheck["doc-check<br/>Documentation code-block checker"]:::added
	lexer["lexer<br/>Lexer & tokens"]:::untouched
	parser["parser<br/>Parser & syntax tree"]:::untouched
	binder["binder<br/>Binder & type inference"]:::untouched
	transpiler["transpiler<br/>Build & sbt tasks"]:::touched
	languageDocs -->|"extracted panther blocks + check directives"| docCheck
	docCheck -->|"SourceFile per block"| lexer
	lexer -->|"token stream"| parser
	parser -->|"SyntaxTree via MakeSyntaxTree"| binder
	binder -->|"Diagnostics, rebased onto markdown line numbers"| docCheck
	transpiler -->|"new doccheck project + CI job"| docCheck
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Change flow

sequenceDiagram
	participant CI
	participant DC as DocCheck
	participant MS as MarkdownScanner
	participant BC as BlockChecker
	participant PC as pncs front end
	CI->>DC: doccheck/run docs/src/content/docs
	DC->>MS: scan each .md/.mdx
	MS-->>DC: DocBlock(id, code, directive)
	loop per block
		DC->>BC: check(block, Bind)
		BC->>PC: parseSourceFile + MakeCompilation.create
		PC-->>BC: Diagnostics (or a thrown panic)
		BC-->>DC: Ok / Failed / Crashed / Skipped
	end
	DC->>DC: compare against baseline.txt
	DC-->>CI: exit 0 (no new failures) or 1
Loading

Before / after

before:  215 blocks: 58 ok, 157 failing, 0 skipped   (4 crash the compiler)
after:   200 blocks: 198 ok, 0 failing, 2 skipped    (baseline.txt empty)

CI gains a fourth job:

Job Command
docs (new) sbt "doccheck/run docs/src/content/docs"

Invariants

  • transpile-sync — not affected. No file under pncs/, runtime/, metadata/, or text/ changed, so pnc/src/ needed no regeneration.
  • pncs-is-canonical — respected; pnc/src/ is untouched.
  • diagnostics-not-exceptions — violations documented, not fixed. "text " + int and break/continue still panic() instead of reporting. The checker now reports these as CRASH rather than a plain failure; ROADMAP §4.2 tracks the fix.

kthompson and others added 3 commits September 1, 2026 22:02
Nothing was checking that the ```panther snippets in docs/ are valid
Panther, and most of them were not. doccheck extracts every block and
runs it through the real pncs front end (lexer, parser, binder), so the
docs cannot drift away from the language the compiler accepts.

Blocks can be annotated in place with an HTML comment above the fence --
parse-only, expect-error, or skip reason="..." -- which markdown renderers
drop from the output. A malformed directive is reported rather than
silently ignored, so a typo cannot quietly disable checking.

Known-broken blocks live in tools/doccheck/baseline.txt, which is picked
up automatically so a normal run reports only what changed. The check
fails in both directions: a passing snippet breaking, and a baselined
snippet starting to pass, so the list cannot go stale.

Wired into CI as a fourth job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
157 of 215 panther blocks did not compile; four crashed the compiler
outright, including the second example on the Getting Started page.
The docs described an aspirational language, not the implemented one.

Systematic corrections across every page:

  Array<Int>        -> Array[int]        (square brackets, lowercase)
  Int/String/Bool   -> int/string/bool
  fun f(): T { .. } -> def f(): T = ..   (no fun, no return)
  for (x in xs)     -> for (i <- 0 to n) (only numeric ranges exist)
  match (x) { .. }  -> x match { case .. }
  "n: " + count     -> "n: " + string(count)

Several sections documented features the language does not have and were
rewritten to teach what it does: array literals, float literals, compound
assignment, nested block comments, pattern guards and alternation, tuples,
break/continue, and readLine. basics/variables.md no longer claims val
reassignment is a compile error, because it is not.

functions/higher-order-functions.md was 275 lines teaching lambdas.
Neither lambdas nor function-typed parameters nor nested defs parse, so
higher-order functions do not exist in any form. It is now a short page
that says so and shows the workarounds: top-level named functions,
generic functions, and enum + match for behaviour dispatch. Its two
skip-marked blocks are the only skips in the tree.

Result: 200 blocks, 198 compiling, 0 failing, 2 skipped. The baseline is
empty, so CI now enforces a clean run rather than a ratchet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ROADMAP.md covers the three stated goals -- self-hosting, generics, and
runnable sample programs -- with measured numbers and the command that
reproduces each, rather than estimates.

What the measurements turned up:

  sbt pnc/compile reports 1058 diagnostics, and 685 of them mention an
  unsolved type variable, which is why generic inference leads the plan.

  pnc/src/ast.pn and printer.pn are stale committed artifacts that
  transpile no longer regenerates; CI cannot see the drift because
  git diff --exit-code ignores the untracked Ast.pn it now emits.

  The compiler never exits non-zero, so pnc/compile "succeeds" with 1058
  errors.

  Nothing is ever written to disk. Compilation.emit(output) ignores its
  argument and Emitter.emit() returns in-memory structures, so the
  documented pncs output.pnb source.pn produces no file, and there is no
  reader and no runner. This blocks the sample programs.

  The in/out variance keywords are inverted in both the transpiler and
  the binder relative to Variance.scala's own comments. The two
  inversions cancel, so nothing breaks until someone writes [out T].

Fixing the docs surfaced nine further compiler gaps, all verified and
recorded under cross-cutting: val immutability is not enforced at all,
break/continue panic the binder through boundErrorStatement, decimal
literals do not lex, and there is no return, no compound assignment, no
pattern guards, alternation, or tuples, and no nested functions.

README corrections: the output path is not honoured, and pnc/compile
exits zero despite failing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kthompson
kthompson merged commit 3a5eb4a into main Sep 2, 2026
4 checks passed
@kthompson
kthompson deleted the claude/codebase-roadmap-doc-2375c3 branch September 2, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant