Skip to main content

Module resolve

Module resolve 

Source
Expand description

Cross-reference resolver (manifest §6 stage 3, MVP 1).

Walks a lowered [Document] and, in three passes:

  1. Assigns hierarchical number attributes to every [NodeKind::Section] ("1", "1.1", "1.2", "2"), keyed off the existing level attribute.
  2. Assigns flat document-order number attributes to every numbered [NodeKind::Figure] ("1", "2", "3") and stamps a visible "{supplement} N: …" label onto each captioned figure. Figures are not hierarchical, so the counter never resets. A figure can opt out with numbered: false (skipped: no number, no caption prefix, does not advance the counter) or swap its supplement word with supplement: "…" (issue #76).
  3. Builds a label → LabelTarget index from every block carrying a label attribute, then rewrites each [NodeKind::Reference]’s text attribute to its target’s resolved string.

The label index is typed: each entry records what kind of thing the label points at (section, figure, or generic block). A section reference renders as its bare hierarchical number ("1.2"); a figure reference renders kind-aware as "{supplement} {n}" ("Figure 1" by default) from the figure’s flat document-order number. Generic targets (paragraphs, raw blocks, images, skipped figures, …) carry no counter and render as the bare label, matching prior behavior.

Diagnostics:

  • MOS0030: a label is declared more than once. The first occurrence wins; later occurrences keep their numbering but are not added to the index. Each duplicate also carries a structured rename [Suggestion]; the next free {label}-N (N >= 2) that no other declaration or earlier suggestion already uses: over the duplicate label token span.
  • MOS0033: a @label reference targets a label that doesn’t exist. The reference’s text is left at its lowered placeholder (?label?) so it remains visible in the rendered output.

Manifest §6 stage 3 calls for a fixpoint loop because later stages (page references, TOC) can re-trigger resolution. MVP 1 only needs a single pass: section numbering doesn’t depend on layout, but the driver shape mirrors the manifest’s “internal fixpoint” anyway: the loop runs until no rewrite changes the document, with a hard cap to detect pathological cycles.

Every pass is idempotent: resolve is public and re-entrant, so running it twice: inside the fixpoint above, or from a future page-reference stage: must reproduce the same document rather than compounding edits. Numbering overwrites attributes with the same value; caption labelling re-derives from a preserved source instead of re-reading the already-stamped text (which would nest the label into "Figure 1: Figure 1: …").

Structs§

LabelTarget 🔒
An entry in the label → target index.

Enums§

LabelTargetKind 🔒
What a label points at, captured at index-build time.

Constants§

MAX_FIXPOINT_ITERATIONS 🔒
Cap on resolver fixpoint iterations. MVP 1 always converges in one pass; the cap is a safety net against forward-reference loops once page numbering lands in MVP 3+.

Functions§

build_label_index 🔒
Build the label -> LabelTarget index from every label-declaring block, reporting MOS0030 for redeclarations. The first declaration of a label wins; later occurrences keep their numbering but are not indexed, and each carries a related note pointing at the first declaration plus a structured rename [Suggestion]; the next free {label}-N: over the duplicate label token span (see the module-level docs). Reads the document only, so resolve stays idempotent.
captured_number 🔒
Read a node’s resolved number attribute, or an empty string if it has none. Both section and figure numbering stash their counter there before the label index is built; an empty result means the numbering pass didn’t reach the node (a resolver/lowerer bug).
classify_target 🔒
Classify a labelled node into a LabelTargetKind. Only nodes that actually declare a label reach this function: references are filtered out by the caller.
declared_labels 🔒
Collect every label declared anywhere in the document: any non-reference block carrying a label attribute: regardless of document order or duplication. The duplicate-rename suggestion consults this set so it never proposes a name that some other declaration already uses.
edit_distance 🔒
Levenshtein edit distance between a and b over their bytes.
figure_caption_text 🔒
Find the text node of a figure’s caption, if it has one. The lowerer tags the caption paragraph with role = "caption" and gives it a single [NodeKind::Text] child carrying the caption string.
figure_is_numbered 🔒
Whether a figure participates in the auto Figure N counter. A figure opts out with #figure(numbered: false) (issue #76), recorded by the lowerer as a numbered = false attribute; absence means numbered.
figure_label_prefix 🔒
Join a figure’s supplement word and number into the cohesive label token used in both captions and references: "Figure\u{00A0}1", non-breaking so the word never wraps off its number. An empty supplement renders the number alone ("1"), with no word and no leading space.
figure_supplement 🔒
The human-facing supplement word prefixed to a figure’s number in generated reference and caption text; the “Figure” in “Figure 1”.
figure_supplement_attr 🔒
The supplement word for a figure’s caption and its references. An explicit #figure(supplement: …) value wins: including the empty string (supplement: "" / supplement: none), which means “number only, no word” (the “no visible prefix” form). Only an absent supplement falls back to the localized figure_supplement default ("Figure").
is_reference_label 🔒
Whether label can be spelled as an @ reference: i.e. it is drawn from the reference grammar’s alphabet [A-Za-z0-9_:.-] (mirrors scan_label_chars in mos-parse). #figure(label: …) and #image(label: …) accept arbitrary strings, so the label index can hold names such as "intro x" or non-ASCII labels that an @… reference can never name; suggesting one would produce a fix that does not parse.
label_span 🔒
nearest_label 🔒
The single nearest resolvable label to unknown, when one is a reasonable near-miss rather than an unrelated string; the candidate for a “did you mean @intro?” fix on an unknown reference.
nodes_of_kind 🔒
Collect the ids of every node of kind in document order. nodes() iterates the arena by ascending [mos_core::NodeId] (allocation order), and the lowerer allocates nodes in source order, so the result is stable document order regardless of nesting depth. Shared by figure numbering and section_order so both passes agree on what “document order” means.
nonconflicting_rename 🔒
Pick a deterministic, collision-aware rename for a duplicated label: the smallest integer suffix N >= 2 whose {label}-{N} is not already in declared. Boring and stable; no similarity ranking, but it steps over existing labels so the suggested fix never re-creates the clash it resolves. Among the first declared.len() + 1 candidates at least one is free (pigeonhole), so the bounded search always yields a name.
number_figures 🔒
Assign flat, document-order numbers to every figure ("1", "2", "3", …) and stamp a visible "Figure N: …" label onto each captioned figure. Figures are not hierarchical, so the counter never resets.
number_sections 🔒
Walk the document depth-first and assign hierarchical numbers to every section based on its level attribute. Sections without a readable level default to depth 1.
read_str_attr 🔒
Read a string attribute off a node by id, cloning it out. None if the node is missing or the attribute is absent or non-string.
render_target 🔒
Compute the display string for a reference to target.
resolve
Run the resolver pass over document in place. Returns any diagnostics produced; the document is modified regardless of whether errors are present so partial output is still renderable.
rewrite_references 🔒
Rewrite each Reference node’s text attribute to point at its target. Returns true if any node was mutated this iteration: callers use that signal to drive the §6 stage 3 fixpoint loop.
section_order 🔒
validate_page_references 🔒
Report an undeclared label in a @page(label) reference as MOS0033, mirroring the @label cross-reference check. A page reference resolves to a page number later, through the layout fixpoint (issue #72), but an unknown label is a lower-time error exactly like a bad @ref, and catching it here means mos check reports it without needing to lay the document out.