From 767beedb4ae73d5f8028ac43670d014b22d0e42f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Apr 2026 22:23:41 +0000 Subject: [PATCH 1/2] Refactor Directive to use grammar() with g:'directive' setting - Build a declarative GrammarSpec encoding open-rule mods, close-rule mods and the directive rule's own alts - Apply via jsonic.grammar(spec, { rule: { alt: { g: 'directive' } } }) so every generated alt gets 'directive' appended to its group tags - Keep jsonic.rule(name, ...) solely for rs.clear()/bo/bc hooks which aren't expressible in a GrammarSpec - All 9 tests pass --- dist/directive.js | 128 ++++++++++++++++++++---------------- dist/directive.js.map | 2 +- src/directive.ts | 148 +++++++++++++++++++++++------------------- 3 files changed, 155 insertions(+), 123 deletions(-) diff --git a/dist/directive.js b/dist/directive.js index 0eab053..b08e1bf 100644 --- a/dist/directive.js +++ b/dist/directive.js @@ -80,74 +80,90 @@ appear without the start characters "{open}" appearing first: CLOSE = null == close ? null : jsonic.fixed(close); // NOTE: RuleSpec.open|close refers to Rule state, whereas // OPEN|CLOSE refers to opening and closing tokens for the directive. + // Pre-seed the directive rule's hooks (clear existing alts, set bo/bc). + // `grammar()` below will then install the open/close alts with the + // `directive` group tag appended via the setting arg. + jsonic.rule(name, (rs) => rs + .clear() + .bo((rule) => ((rule.node = {}), undefined)) + .bc(function (rule, ctx, next, tkn) { + let out = action.call(this, rule, ctx, next, tkn); + if (out?.isToken) { + return out; + } + })); + // Build a declarative grammar spec covering every rule modification + // plus the directive rule's own alts. + const grammarSpec = { rule: {} }; + const ruleFor = (rn) => (grammarSpec.rule[rn] = grammarSpec.rule[rn] || {}); Object.entries(rules.open).forEach((entry) => { const [rulename, rulespec] = entry; - jsonic.rule(rulename, (rs) => { - rs.open({ - s: [OPEN], + const openAlts = []; + const closeAlts = []; + if (null != close) { + // More-specific OPEN+CLOSE alt first so it's tried before OPEN alone. + openAlts.push({ + s: [OPEN, CLOSE], + b: 1, p: name, n: { ['dr_' + name]: 1 }, - g: 'start', - c: rulespec.c, + g: 'start,end', }); - if (null != close) { - rs.open({ - s: [OPEN, CLOSE], - b: 1, - p: name, - n: { ['dr_' + name]: 1 }, - g: 'start,end', - }); - rs.close({ - s: [CLOSE], - b: 1, - g: 'end', - }); - } - return rs; + closeAlts.push({ + s: [CLOSE], + b: 1, + g: 'end', + }); + } + openAlts.push({ + s: [OPEN], + p: name, + n: { ['dr_' + name]: 1 }, + g: 'start', + c: rulespec.c, }); + const r = ruleFor(rulename); + r.open = openAlts; + if (closeAlts.length) + r.close = closeAlts; }); if (null != close) { Object.entries(rules.close).forEach((entry) => { const [rulename, rulespec] = entry; - jsonic.rule(rulename, (rs) => { - rs.close([ - { - s: [CLOSE], - c: (r, ctx) => 1 === r.n['dr_' + name] && (rulespec.c ? rulespec.c(r, ctx) : true), - b: 1, - g: 'end', - }, - { - s: [CA, CLOSE], - c: (r) => 1 === r.n['dr_' + name], - b: 1, - g: 'end,comma', - }, - ]); - }); + const r = ruleFor(rulename); + r.close = [ + { + s: [CLOSE], + c: (r, ctx) => 1 === r.n['dr_' + name] && + (rulespec.c ? rulespec.c(r, ctx) : true), + b: 1, + g: 'end', + }, + { + s: [CA, CLOSE], + c: (r) => 1 === r.n['dr_' + name], + b: 1, + g: 'end,comma', + }, + ]; }); } - jsonic.rule(name, (rs) => rs - .clear() - .bo((rule) => ((rule.node = {}), undefined)) - .open([ - null != close ? { s: [CLOSE], b: 1 } : null, - { - p: 'val', - // Only accept implicits when there is a CLOSE token, - // otherwise we'll eat all following siblings. - // n: null == close ? {} : { pk: -1, il: 0 }, - n: null == close ? { dlist: 1, dmap: 1 } : { dlist: 0, dmap: 0 }, - }, - ]) - .bc(function (rule, ctx, next, tkn) { - let out = action.call(this, rule, ctx, next, tkn); - if (out?.isToken) { - return out; - } - }) - .close(null != close ? [{ s: [CLOSE] }, { s: [CA, CLOSE] }] : [])); + const directiveOpenAlts = []; + if (null != close) { + directiveOpenAlts.push({ s: [CLOSE], b: 1 }); + } + directiveOpenAlts.push({ + p: 'val', + // Only accept implicits when there is a CLOSE token, + // otherwise we'll eat all following siblings. + n: null == close ? { dlist: 1, dmap: 1 } : { dlist: 0, dmap: 0 }, + }); + const dr = ruleFor(name); + dr.open = directiveOpenAlts; + if (null != close) { + dr.close = [{ s: [CLOSE] }, { s: [CA, CLOSE] }]; + } + jsonic.grammar(grammarSpec, { rule: { alt: { g: 'directive' } } }); if (custom) { custom(jsonic, { OPEN, CLOSE, name }); } diff --git a/dist/directive.js.map b/dist/directive.js.map index 8ec9403..bd3f90e 100644 --- a/dist/directive.js.map +++ b/dist/directive.js.map @@ -1 +1 @@ -{"version":3,"file":"directive.js","sourceRoot":"","sources":["../src/directive.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"directive.js","sourceRoot":"","sources":["../src/directive.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/directive.ts b/src/directive.ts index 1273e16..75ec760 100644 --- a/src/directive.ts +++ b/src/directive.ts @@ -125,74 +125,13 @@ appear without the start characters "{open}" appearing first: // NOTE: RuleSpec.open|close refers to Rule state, whereas // OPEN|CLOSE refers to opening and closing tokens for the directive. - Object.entries(rules.open).forEach((entry: any[]) => { - const [rulename, rulespec] = entry - jsonic.rule(rulename, (rs: RuleSpec) => { - rs.open({ - s: [OPEN], - p: name, - n: { ['dr_' + name]: 1 }, - g: 'start', - c: rulespec.c, - }) - - if (null != close) { - rs.open({ - s: [OPEN, CLOSE], - b: 1, - p: name, - n: { ['dr_' + name]: 1 }, - g: 'start,end', - }) - - rs.close({ - s: [CLOSE], - b: 1, - g: 'end', - }) - } - - return rs - }) - }) - - if (null != close) { - Object.entries(rules.close).forEach((entry: any[]) => { - const [rulename, rulespec] = entry - jsonic.rule(rulename, (rs: RuleSpec) => { - rs.close([ - { - s: [CLOSE], - c: (r, ctx) => 1 === r.n['dr_' + name] && (rulespec.c ? rulespec.c(r, ctx) : true), - b: 1, - g: 'end', - }, - { - s: [CA, CLOSE], - c: (r) => 1 === r.n['dr_' + name], - b: 1, - g: 'end,comma', - }, - ]) - }) - }) - } - + // Pre-seed the directive rule's hooks (clear existing alts, set bo/bc). + // `grammar()` below will then install the open/close alts with the + // `directive` group tag appended via the setting arg. jsonic.rule(name, (rs) => rs .clear() .bo((rule: Rule) => ((rule.node = {}), undefined)) - .open([ - null != close ? { s: [CLOSE], b: 1 } : null, - { - p: 'val', - - // Only accept implicits when there is a CLOSE token, - // otherwise we'll eat all following siblings. - // n: null == close ? {} : { pk: -1, il: 0 }, - n: null == close ? { dlist: 1, dmap: 1 } : { dlist: 0, dmap: 0 }, - }, - ]) .bc(function( this: RuleSpec, rule: Rule, @@ -204,10 +143,87 @@ appear without the start characters "{open}" appearing first: if (out?.isToken) { return out } - }) - .close(null != close ? [{ s: [CLOSE] }, { s: [CA, CLOSE] }] : []), + }), ) + // Build a declarative grammar spec covering every rule modification + // plus the directive rule's own alts. + const grammarSpec: any = { rule: {} } + const ruleFor = (rn: string) => + (grammarSpec.rule[rn] = grammarSpec.rule[rn] || {}) + + Object.entries(rules.open).forEach((entry: any[]) => { + const [rulename, rulespec] = entry + const openAlts: any[] = [] + const closeAlts: any[] = [] + if (null != close) { + // More-specific OPEN+CLOSE alt first so it's tried before OPEN alone. + openAlts.push({ + s: [OPEN, CLOSE], + b: 1, + p: name, + n: { ['dr_' + name]: 1 }, + g: 'start,end', + }) + closeAlts.push({ + s: [CLOSE], + b: 1, + g: 'end', + }) + } + openAlts.push({ + s: [OPEN], + p: name, + n: { ['dr_' + name]: 1 }, + g: 'start', + c: rulespec.c, + }) + const r = ruleFor(rulename) + r.open = openAlts + if (closeAlts.length) r.close = closeAlts + }) + + if (null != close) { + Object.entries(rules.close).forEach((entry: any[]) => { + const [rulename, rulespec] = entry + const r = ruleFor(rulename) + r.close = [ + { + s: [CLOSE], + c: (r: Rule, ctx: Context) => + 1 === r.n['dr_' + name] && + (rulespec.c ? rulespec.c(r, ctx) : true), + b: 1, + g: 'end', + }, + { + s: [CA, CLOSE], + c: (r: Rule) => 1 === r.n['dr_' + name], + b: 1, + g: 'end,comma', + }, + ] + }) + } + + const directiveOpenAlts: any[] = [] + if (null != close) { + directiveOpenAlts.push({ s: [CLOSE], b: 1 }) + } + directiveOpenAlts.push({ + p: 'val', + // Only accept implicits when there is a CLOSE token, + // otherwise we'll eat all following siblings. + n: null == close ? { dlist: 1, dmap: 1 } : { dlist: 0, dmap: 0 }, + }) + const dr = ruleFor(name) + dr.open = directiveOpenAlts + if (null != close) { + dr.close = [{ s: [CLOSE] }, { s: [CA, CLOSE] }] + } + + jsonic.grammar(grammarSpec, { rule: { alt: { g: 'directive' } } }) + if (custom) { custom(jsonic, { OPEN, CLOSE, name }) } From 10cb72e471af12859d10c64bf91bcc6781c5bbfc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 19 Apr 2026 12:53:06 +0000 Subject: [PATCH 2/2] Refactor Go Directive to use Grammar() with g:'directive' setting - Build a declarative GrammarSpec for all rule modifications plus the directive rule's own alts - Wire bo/bc state actions via auto-resolved FuncRefs (@-bo, @-bc) registered in the GrammarSpec.Ref map - Register per-rule condition closures as AltCond FuncRefs to support rulemod C predicates in open/close grammar alts - Apply via j.Grammar(gs, &GrammarSetting{Rule:{Alt:{G:"directive"}}}) so every alt auto-gets the 'directive' group tag - Resolve shared close tokens by looking up the existing TinName so the grammar spec references the correct registered token - All 5 Go tests pass --- go/plugin.go | 297 +++++++++++++++++++++++++++++---------------------- 1 file changed, 171 insertions(+), 126 deletions(-) diff --git a/go/plugin.go b/go/plugin.go index e4a4382..1f7e95a 100644 --- a/go/plugin.go +++ b/go/plugin.go @@ -44,171 +44,216 @@ func Directive(j *jsonic.Jsonic, pluginOpts map[string]any) error { // Register or look up the close fixed token. var CLOSE jsonic.Tin = -1 + closeTN := "" if hasClose { - closeTN := "#CD_" + name if existing, exists := cfg.FixedTokens[close_]; exists { + // Reuse an existing close token (e.g. shared with another + // directive). Grab its registered name so the grammar spec + // below resolves to the same Tin via j.Token(name). CLOSE = existing + closeTN = j.TinName(existing) } else { + closeTN = "#CD_" + name CLOSE = j.Token(closeTN, close_) } } - // Look up the comma token for close-with-comma alternatives. - CA := j.Token("#CA") + // Build a Ref map for all state actions and condition functions + // referenced by the grammar spec below. + ref := map[jsonic.FuncRef]any{} + + // Auto-wired state actions on the directive rule (@-bo, @-bc). + ref[jsonic.FuncRef("@"+name+"-bo")] = jsonic.StateAction( + func(r *jsonic.Rule, ctx *jsonic.Context) { + r.Node = make(map[string]any) + }, + ) + ref[jsonic.FuncRef("@"+name+"-bc")] = jsonic.StateAction( + func(r *jsonic.Rule, ctx *jsonic.Context) { + // Follow the replacement chain to get the final child node. + // When a val rule is replaced by a list rule (implicit list), + // the original child's Node may be stale in Go because slice + // append can reallocate. Walk the Prev-linked replacement + // chain to find the last replacement and adopt its Node. + if r.Child != nil && r.Child != jsonic.NoRule { + final := r.Child + for final.Next != nil && final.Next != jsonic.NoRule && + final.Next.Prev == final { + final = final.Next + } + if final != r.Child { + r.Child.Node = final.Node + } + } + if action != nil { + action(r, ctx) + } + }, + ) + + // Declarative grammar spec built up below and applied via j.Grammar(). + gs := &jsonic.GrammarSpec{ + Ref: ref, + Rule: map[string]*jsonic.GrammarRuleSpec{}, + } + ruleFor := func(rn string) *jsonic.GrammarRuleSpec { + if existing, ok := gs.Rule[rn]; ok { + return existing + } + r := &jsonic.GrammarRuleSpec{} + gs.Rule[rn] = r + return r + } // ---- Modify existing rules for OPEN token detection ---- for rulename, rulemod := range openRules { + rn := rulename rm := rulemod - dn := name - j.Rule(rulename, func(rs *jsonic.RuleSpec) { - // Match OPEN token → push to directive rule. - openAlt := &jsonic.AltSpec{ - S: [][]jsonic.Tin{{OPEN}}, - P: dn, - N: map[string]int{"dr_" + dn: 1}, - G: "start", - } - if rm.C != nil { - openAlt.C = rm.C - } - if hasClose { - // Also match OPEN+CLOSE (empty directive). - emptyAlt := &jsonic.AltSpec{ - S: [][]jsonic.Tin{{OPEN}, {CLOSE}}, - B: 1, - P: dn, - N: map[string]int{"dr_" + dn: 1}, - G: "start,end", - } + var openAlts []*jsonic.GrammarAltSpec + var closeAlts []*jsonic.GrammarAltSpec - // Prepend close detection to this rule. - closeAlt := &jsonic.AltSpec{ - S: [][]jsonic.Tin{{CLOSE}}, - B: 1, - G: "end", - } + if hasClose { + // OPEN+CLOSE (empty directive) must be tried before OPEN alone. + openAlts = append(openAlts, &jsonic.GrammarAltSpec{ + S: openTN + " " + closeTN, + B: 1, + P: name, + N: map[string]int{"dr_" + name: 1}, + G: "start,end", + }) + closeAlts = append(closeAlts, &jsonic.GrammarAltSpec{ + S: closeTN, + B: 1, + G: "end", + }) + } - // OPEN+CLOSE must be checked before OPEN alone. - rs.PrependOpen(openAlt) - rs.PrependOpen(emptyAlt) - rs.PrependClose(closeAlt) - } else { - rs.PrependOpen(openAlt) - } - }) + openAlt := &jsonic.GrammarAltSpec{ + S: openTN, + P: name, + N: map[string]int{"dr_" + name: 1}, + G: "start", + } + if rm.C != nil { + cref := jsonic.FuncRef("@dr-open-c-" + name + "-" + rn) + ref[cref] = rm.C + openAlt.C = string(cref) + } + openAlts = append(openAlts, openAlt) + + r := ruleFor(rn) + r.Open = openAlts + if len(closeAlts) > 0 { + r.Close = closeAlts + } } // ---- Modify existing rules for CLOSE token detection ---- if hasClose { for rulename, rulemod := range closeRules { + rn := rulename rm := rulemod - dn := name - j.Rule(rulename, func(rs *jsonic.RuleSpec) { - // CLOSE token ends the directive content. - closeAlt := &jsonic.AltSpec{ - S: [][]jsonic.Tin{{CLOSE}}, - C: func(r *jsonic.Rule, ctx *jsonic.Context) bool { - if r.N["dr_"+dn] != 1 { - return false - } - if rm.C != nil { - return rm.C(r, ctx) - } - return true - }, + + closeCRef := jsonic.FuncRef("@dr-close-c-" + name + "-" + rn) + ref[closeCRef] = jsonic.AltCond( + func(r *jsonic.Rule, ctx *jsonic.Context) bool { + if r.N["dr_"+name] != 1 { + return false + } + if rm.C != nil { + return rm.C(r, ctx) + } + return true + }, + ) + commaCRef := jsonic.FuncRef("@dr-close-ca-c-" + name + "-" + rn) + ref[commaCRef] = jsonic.AltCond( + func(r *jsonic.Rule, ctx *jsonic.Context) bool { + return r.N["dr_"+name] == 1 + }, + ) + + closeAlts := []*jsonic.GrammarAltSpec{ + { + S: closeTN, + C: string(closeCRef), B: 1, G: "end", - } - - // COMMA + CLOSE also ends the directive. - commaCloseAlt := &jsonic.AltSpec{ - S: [][]jsonic.Tin{{CA}, {CLOSE}}, - C: func(r *jsonic.Rule, ctx *jsonic.Context) bool { - return r.N["dr_"+dn] == 1 - }, + }, + { + S: "#CA " + closeTN, + C: string(commaCRef), B: 1, G: "end,comma", - } + }, + } - rs.PrependClose(closeAlt, commaCloseAlt) - }) + r := ruleFor(rn) + r.Close = closeAlts } } - // ---- Create the directive rule ---- + // ---- Directive rule alts ---- - j.Rule(name, func(rs *jsonic.RuleSpec) { - rs.Clear() - - // Before open: initialize node as empty map. - rs.AddBO(func(r *jsonic.Rule, ctx *jsonic.Context) { - r.Node = make(map[string]any) + var dirOpen []*jsonic.GrammarAltSpec + if hasClose { + // Check for immediate close (empty directive). + dirOpen = append(dirOpen, &jsonic.GrammarAltSpec{ + S: closeTN, + B: 1, }) + } + // Push to val rule to parse directive content. + // Counter settings control implicit list/map creation: + // With close: reset counters (allow implicits within boundaries) + // Without close: increment counters (prevent implicits consuming siblings) + counters := map[string]int{} + if hasClose { + counters["dlist"] = 0 + counters["dmap"] = 0 + } else { + counters["dlist"] = 1 + counters["dmap"] = 1 + } + dirOpen = append(dirOpen, &jsonic.GrammarAltSpec{ + P: "val", + N: counters, + }) - // Open alternatives. - openAlts := make([]*jsonic.AltSpec, 0, 2) - - // If close token exists, check for immediate close (empty directive). - if hasClose { - openAlts = append(openAlts, &jsonic.AltSpec{ - S: [][]jsonic.Tin{{CLOSE}}, - B: 1, - }) - } - - // Push to val rule to parse directive content. - // Counter settings control implicit list/map creation: - // With close: reset counters (allow implicits within boundaries) - // Without close: increment counters (prevent implicits consuming siblings) - counters := map[string]int{} - if hasClose { - counters["dlist"] = 0 - counters["dmap"] = 0 - } else { - counters["dlist"] = 1 - counters["dmap"] = 1 + var dirClose []*jsonic.GrammarAltSpec + if hasClose { + dirClose = []*jsonic.GrammarAltSpec{ + {S: closeTN}, + {S: "#CA " + closeTN}, } - openAlts = append(openAlts, &jsonic.AltSpec{ - P: "val", - N: counters, - }) - - rs.Open = openAlts + } - // Before close: resolve the child node and invoke the action. - rs.AddBC(func(r *jsonic.Rule, ctx *jsonic.Context) { - // Follow the replacement chain to get the final child node. - // When a val rule is replaced by a list rule (implicit list), - // the original child's Node may be stale in Go because slice - // append can reallocate. Walk the Prev-linked replacement - // chain to find the last replacement and adopt its Node. - if r.Child != nil && r.Child != jsonic.NoRule { - final := r.Child - for final.Next != nil && final.Next != jsonic.NoRule && - final.Next.Prev == final { - final = final.Next - } - if final != r.Child { - r.Child.Node = final.Node - } - } - if action != nil { - action(r, ctx) - } - }) + dr := ruleFor(name) + dr.Open = dirOpen + if len(dirClose) > 0 { + dr.Close = dirClose + } - // Close alternatives (only if close token exists). - if hasClose { - rs.Close = []*jsonic.AltSpec{ - {S: [][]jsonic.Tin{{CLOSE}}}, - {S: [][]jsonic.Tin{{CA}, {CLOSE}}}, - } - } + // Clear any pre-existing alts/state actions on the directive rule so + // that j.Grammar() below installs a clean set via wireStateActions + + // prepend onto empty slices. + j.Rule(name, func(rs *jsonic.RuleSpec) { + rs.Clear() }) + // Apply grammar with 'directive' group tag appended to every alt. + setting := &jsonic.GrammarSetting{ + Rule: &jsonic.GrammarSettingRule{ + Alt: &jsonic.GrammarSettingAlt{G: "directive"}, + }, + } + if err := j.Grammar(gs, setting); err != nil { + return err + } + // ---- Custom callback ---- if custom != nil {