Skip to content

Rule provenance + query API for interactive editors (glyph <-> rule, inheritance) #3

@typedev

Description

@typedev

Motivation

Interactive editors (not just batch apply) need to map glyph ↔ rule both ways and to introspect inheritance:

  • select a glyph → highlight the rule(s) that affect it;
  • cursor on a rule → highlight the glyphs it matches in a grid;
  • with !extends, a glyph is often hit by several rules across base + user layers — show all of them and/or the resolved per-glyph result.

None of this is possible today because (a) rules carry no source provenance (Document.rules are bare (selector, op, items) tuples — parse_dsl knows the line number n but discards it, and !extends merge concatenates base.rules + child.rules with no record of origin), and (b) the matcher (_matches) and per-glyph queries are private/absent.

Proposal

1. Rule provenance (foundational)

@dataclass(frozen=True)
class RuleSource:
    ref: str    # preset name ("default") or file path
    line: int   # 1-based line within that source

Attach a source to every rule; preserve it through parse_dsl, _load, and _merge (stamp base rules with the base ref, do not renumber). This lets a UI map a rule to its editor location and tell user-layer rules from inherited (read-only) ones.

2. Matching primitives (public)

def selector_matches(selector, name: str, unicodes: Sequence[int]) -> bool   # promote _matches
def glyphs_for_selector(selector, glyphs: Iterable[tuple[str, Sequence[int]]]) -> list[str]

Cursor → grid: parse the cursor line to a selector with the existing DSL, then list matches. Pure over (name, unicodes) — no fontParts dependency ({category} selectors need unicodes).

3. Per-glyph queries

def rules_for_glyph(doc, name, unicodes) -> list[Rule]          # matching rules in order, with source + op
def explain_glyph(doc, name, unicodes, *, font=None) -> Explanation

rules_for_glyph: grid → editor highlighting. explain_glyph: an ordered trace — for each matching rule its op and the accumulator after it, plus the final specs (resolved coords if font given) — powering a "resolved rules for this glyph" view that makes deep !extends chains legible.

Implementation notes

  • parse_dsl must keep the line number it already computes; _merge must preserve each rule's source (do not flatten/renumber).
  • accumulate can stay as-is; rules_for_glyph / explain_glyph share its scan logic.

Acceptance

  • A user rule round-trips to (ref, line) that points at its editor line.
  • explain_glyph(...) final set equals accumulate(...).
  • glyphs_for_selector agrees with accumulate membership.

Relation to #2

Independent of #2 (compute_document), but both move the library toward a functional core + a thin imperative shell.

Context

Driving use case: a GTK editor that previews rule-driven anchors and wants bidirectional highlighting (grid selection ↔ rule in the .af editor) and a per-glyph "effective rules" panel. With !extends inheritance, a glyph's anchors come from base + override rules; without provenance and query helpers the editor can't point at the right rule or explain the result.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions