Skip to content

Adds Predicator.AST prewalk, postwalk, reduce - #248

Merged
johnnyt merged 1 commit into
mainfrom
px-ybw0-ast-walks
Oct 13, 2026
Merged

johnnyt merged 1 commit into
mainfrom
px-ybw0-ast-walks

Conversation

@johnnyt

@johnnyt johnnyt commented Oct 13, 2026

Copy link
Copy Markdown
Member

Adds Predicator.AST with prewalk/3, postwalk/3 and reduce/3 over the positioned AST. Tooling that reads or rewrites a parsed tree no longer hand-writes a clause per node kind.

What it does

  • Same shape as the standard library's Macro walks: prewalk(ast, acc, fun) and postwalk(ast, acc, fun) take fun.(node, acc) -> {node, acc} and return {ast, acc}; reduce(ast, acc, fun) takes fun.(node, acc) -> acc, visits pre-order and rewrites nothing.
  • Walks everything t:Predicator.Parser.visitable/0 names: expressions, statements, blocks and programs.
  • Children are visited in source order. The trailing position slot of every node is carried untouched, whether it is a {line, column}, a span or nil.
  • New module and one docs sentence; no existing function changes what it answers, and Predicator.Visitor is unchanged.

Rules for the three shapes that are not plain children

  • An object literal's key (t:Predicator.Parser.object_key/0) is outside the ast/0 and visitable/0 unions. The walks treat it as part of its entry: it is never handed to the callback and is carried through untouched; each entry's value is visited in entry order. Pinned by two tests (no key is ever seen; a rewrite leaves the key as it was).
  • :duration holds {integer, unit} pairs, which are data. It has no children.
  • :relative_date's first element is a node (its duration) and is walked as a child.

A tuple with an unknown tag raises FunctionClauseError; the sync test below is what keeps that from happening for a real node kind.

Sync test

test/predicator/ast_clause_coverage_test.exs reads the visitable/0 union from the compiled parser beam (Code.Typespec.fetch_types/1) and the map_children/3 clause heads from lib/predicator/ast.ex, and compares them both ways, so adding a node kind with no children clause turns it red (and so does a clause with no node kind). It follows the model of visitor_clause_coverage_test.exs. It is added to gate.sabotage.test_roots in .claude/wurk.json, as that file's convention asks of a binding test.

Provenance

The traversal's shape (Macro's, children in source order, programs and statement types walked) was decided by the conductor under a standing consent, 2026-10-12.

Gate and tier

  • Full mix quality green on this head: Format, Compile, Doc links, Dependencies, Docs, Credo, Tests, Dialyzer. Doctor, Gettext and Sobelow report skipped (not installed), the expected state for this repo.
  • Tier: size. The diff adds more than 300 lines outside docs (the module, its tests and the sync test), so it takes a cold pass rather than the in-turn review.
  • No ISA or corpus change; no conformance/ file moved.
  • Changelog: changelog.d/px-ybw0.md, ### Added, one line.

Sabotage (sync test and order tests)

Mutation Test that failed How
Renamed the :duration clause head to :durationx in ast.ex Predicator.ASTClauseCoverageTest "map_children/3 covers exactly the constructors of Parser.visitable/0, both ways" (also walk tests that reach a duration) assertion (missing and extra)
Added a {:sabotage_probe, ast(), position()} member to t:Predicator.Parser.ast/0 the same sync test assertion (missing)
Walked right before left in the comparison clause Predicator.ASTTest "binary nodes visit left then right" and "prewalk visits a parent before its children, postwalk after" assertion

Each file was restored byte-equal from a copy and the build forced before the next check.

Tooling that reads or rewrites a parsed tree hand-wrote a clause per
node kind. Predicator.AST adds the three walks in Macro's shape over
everything Parser.visitable/0 names: expressions, statements, blocks
and programs. An accumulator is threaded, children are visited in
source order, and the trailing position slot is carried untouched.

An object key is part of its entry, not a node, so the walks skip it.
A duration has no children; a relative date's duration is a child.

A binding test reads the typespec's constructor set and goes red when
a node kind has no map_children/3 clause. It is added to the
sabotage test roots.

Refs: px-ybw0
@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
lib/predicator/ast.ex 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@johnnyt
johnnyt merged commit 01f5ddc into main Oct 13, 2026
2 checks passed
@johnnyt
johnnyt deleted the px-ybw0-ast-walks branch October 13, 2026 13:09
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