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.
Motivation
Interactive editors (not just batch apply) need to map glyph ↔ rule both ways and to introspect inheritance:
!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.rulesare bare(selector, op, items)tuples —parse_dslknows the line numbernbut discards it, and!extendsmerge concatenatesbase.rules + child.ruleswith no record of origin), and (b) the matcher (_matches) and per-glyph queries are private/absent.Proposal
1. Rule provenance (foundational)
Attach a
sourceto every rule; preserve it throughparse_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)
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
rules_for_glyph: grid → editor highlighting.explain_glyph: an ordered trace — for each matching rule itsopand the accumulator after it, plus the final specs (resolved coords iffontgiven) — powering a "resolved rules for this glyph" view that makes deep!extendschains legible.Implementation notes
parse_dslmust keep the line number it already computes;_mergemust preserve each rule'ssource(do not flatten/renumber).accumulatecan stay as-is;rules_for_glyph/explain_glyphshare its scan logic.Acceptance
(ref, line)that points at its editor line.explain_glyph(...)final set equalsaccumulate(...).glyphs_for_selectoragrees withaccumulatemembership.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
.afeditor) and a per-glyph "effective rules" panel. With!extendsinheritance, 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.