Skip to content

Match AST subclasses by ancestry and add a block flag to AST::Code - #72

Merged
gschlager merged 2 commits into
mainfrom
ast-ancestry-and-block-code
Jul 27, 2026
Merged

Match AST subclasses by ancestry and add a block flag to AST::Code#72
gschlager merged 2 commits into
mainfrom
ast-ancestry-and-block-code

Conversation

@gschlager

Copy link
Copy Markdown
Member

This builds on #70 (it is based on that branch — merge #70 first, GitHub will retarget this one to main). These are the two deeper design changes from the same adapter review, split out so they can be reviewed on their own.

  • Normalizer rules, tag dispatch, and render_default matched the exact class of a node, so a consumer subclass (a common way to bind a custom tag to only some nodes) silently lost all base-class behavior — a block code subclass inside bold was not hoisted and broke the surrounding markers. All three lookups now match by ancestry: the most specific class wins, an exact registration always overrides an inherited one, and Tag::PASSTHROUGH opts a subclass out of an inherited tag. Frozen libraries and rule sets flatten the resolution at freeze time (descendant classes are known through AST::Node.inherited), so the shared no-customization path answers subclass lookups with a single hash hit. I benchmarked with bench/corpus_bench.rb: end-to-end cost stays within the noise floor, and flattened subclasses dispatch as fast as the base classes.
  • AST::Code has a block: flag. The renderer used to decide inline vs block only from the content (a newline means block), so a single-line code block lost its fence and its language — [code=ruby]x[/code] became `x`. Parsers set block: true where the source construct is a block by definition: BBCode [code]/[pre], HTML <pre>, s9e CODE, and MediaWiki indented and <pre> blocks. Inline constructs ([tt], <code>, <tt>) keep the heuristic, and the normalizer hoists a forced-block code out of inline containers even when it is single-line.

Behavior changes: AST subclasses now inherit rules and tags from their base class — register on the subclass to get different behavior. And single-line [code], [pre], <pre>, and s9e CODE content renders as a fenced block with its language instead of inline code.

@gschlager
gschlager marked this pull request as draft July 27, 2026 09:20
Base automatically changed from html-parser-headings-and-code-language to main July 27, 2026 14:29
Normalizer rules, tag dispatch, and render_default matched the exact
class of a node, so a consumer subclass silently lost all base-class
behavior — a block code subclass inside Bold was not hoisted and
broke the surrounding markers. All three lookups now match by
ancestry: the most specific class wins, an exact registration always
overrides an inherited one, and Tag::PASSTHROUGH opts a subclass out
of an inherited tag.

Frozen libraries and rule sets flatten the resolution at freeze time
(descendant classes are known through AST::Node.inherited), so the
shared no-customization path answers subclass lookups with a single
hash hit. Benchmarks show the end-to-end cost stays within noise.

docs/extending.md documents the normalizer extension points and the
ancestry matching. Also fixes a stale Normalizer.shared_for call that
crashed bench/corpus_bench.rb.
The renderer decides inline vs block per Code node by looking for a
newline in the text. Sources that clearly mean "code block" lost that
intent for single-line content: [code]x[/code] and <pre>x</pre> came
out as `x`, and the language was dropped because inline code has no
place for it.

AST::Code now takes block: true when the source construct is a code
block by definition. Such a node always renders as a fenced block (or
<pre><code> in html_mode), and the normalizer hoists it out of inline
containers even when it is single-line. Without the flag the newline
check applies unchanged.

The flag is set by:
- BBCode [code] and [pre] ([tt] stays inline)
- HTML <pre> (<code> and <tt> keep the newline check)
- s9e/TextFormatter CODE
- MediaWiki space-indented blocks and line-level <pre> blocks

Visible change: single-line [code], [pre], <pre> and CODE content now
renders as a fence with its language instead of inline code.
@gschlager
gschlager force-pushed the ast-ancestry-and-block-code branch from 5656bf8 to 58c3d17 Compare July 27, 2026 14:31
@gschlager
gschlager marked this pull request as ready for review July 27, 2026 14:44
@gschlager
gschlager merged commit 7156025 into main Jul 27, 2026
8 checks passed
@gschlager
gschlager deleted the ast-ancestry-and-block-code branch July 27, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant