Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -378,38 +378,36 @@ type op struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// formatOps is a compiled format: its ops, the size of its literal text, and the
|
// formatOps is a compiled format: its ops, the size of its literal text, and the
|
||||||
// names it holds. pathHeads maps each head a path starts from to the first such path;
|
// names it holds: every head a path starts from, plus the fields an operand reads.
|
||||||
// held is every such head plus the fields an operand reads; holder maps each held
|
|
||||||
// name to the first reader holding it, for error messages.
|
|
||||||
type formatOps struct {
|
type formatOps struct {
|
||||||
ops []op
|
ops []op
|
||||||
grow int
|
grow int
|
||||||
pathHeads map[string]string
|
held map[string]heldName
|
||||||
held map[string]bool
|
|
||||||
holder map[string]string
|
|
||||||
heldLocal bool // some held name is not a reference path, so expand makes a hold
|
heldLocal bool // some held name is not a reference path, so expand makes a hold
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// heldName is how a format first reaches a held name: the reader holding it, for
|
||||||
|
// error messages, and the first path starting from it, "" where none does.
|
||||||
|
type heldName struct {
|
||||||
|
holder string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
func (c *formatOps) holdName(a arm, label string) {
|
func (c *formatOps) holdName(a arm, label string) {
|
||||||
if c.held == nil {
|
if c.held == nil {
|
||||||
c.held = map[string]bool{}
|
c.held = map[string]heldName{}
|
||||||
c.holder = map[string]string{}
|
|
||||||
}
|
}
|
||||||
c.held[a.head] = true
|
|
||||||
if !isRef(a.head) || len(a.tail) == 0 {
|
if !isRef(a.head) || len(a.tail) == 0 {
|
||||||
c.heldLocal = true
|
c.heldLocal = true
|
||||||
}
|
}
|
||||||
if _, named := c.holder[a.head]; !named {
|
h, named := c.held[a.head]
|
||||||
c.holder[a.head] = label
|
if !named {
|
||||||
|
h.holder = label
|
||||||
}
|
}
|
||||||
if len(a.tail) > 0 {
|
if len(a.tail) > 0 && h.path == "" {
|
||||||
if c.pathHeads == nil {
|
h.path = a.spelling
|
||||||
c.pathHeads = map[string]string{}
|
|
||||||
}
|
|
||||||
if _, named := c.pathHeads[a.head]; !named {
|
|
||||||
c.pathHeads[a.head] = a.spelling
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
c.held[a.head] = h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *formatOps) function(tok formatToken, refs map[string]refBinding) {
|
func (c *formatOps) function(tok formatToken, refs map[string]refBinding) {
|
||||||
|
|||||||
+16
-15
@@ -15,9 +15,9 @@ func heldCheck(path string, n node) error {
|
|||||||
if !ok || len(t.compiled.held) == 0 {
|
if !ok || len(t.compiled.held) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
readers := pathHeadReaders(t.compiled.ops, t.compiled.pathHeads)
|
readers := pathHeadReaders(t.compiled)
|
||||||
for _, name := range heldNames(t) {
|
for _, name := range heldNames(t) {
|
||||||
if _, isPath := t.compiled.pathHeads[name]; isPath && isRef(name) {
|
if t.compiled.held[name].path != "" && isRef(name) {
|
||||||
continue // held for the render: drawFence compares every read of it, by path, across the render and its groups
|
continue // held for the render: drawFence compares every read of it, by path, across the render and its groups
|
||||||
}
|
}
|
||||||
if err := checkNameHeld(t, name, readers); err != nil {
|
if err := checkNameHeld(t, name, readers); err != nil {
|
||||||
@@ -36,8 +36,8 @@ func heldNames(t *template) []string {
|
|||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
sort.Slice(names, func(i, j int) bool {
|
sort.Slice(names, func(i, j int) bool {
|
||||||
_, pi := t.compiled.pathHeads[names[i]]
|
pi := t.compiled.held[names[i]].path != ""
|
||||||
_, pj := t.compiled.pathHeads[names[j]]
|
pj := t.compiled.held[names[j]].path != ""
|
||||||
if pi != pj {
|
if pi != pj {
|
||||||
return !pi
|
return !pi
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func heldNames(t *template) []string {
|
|||||||
// produces.
|
// produces.
|
||||||
func heldNodes(t *template, name string, readers []reader) map[node]bool {
|
func heldNodes(t *template, name string, readers []reader) map[node]bool {
|
||||||
held := map[node]bool{}
|
held := map[node]bool{}
|
||||||
if _, isPath := t.compiled.pathHeads[name]; !isPath {
|
if t.compiled.held[name].path == "" {
|
||||||
operandDraw(t.head(name), held)
|
operandDraw(t.head(name), held)
|
||||||
return held
|
return held
|
||||||
}
|
}
|
||||||
@@ -71,13 +71,13 @@ func checkNameHeld(t *template, name string, readers []reader) error {
|
|||||||
if len(held) == 0 {
|
if len(held) == 0 {
|
||||||
return nil // a fixed head holds nothing to reach
|
return nil // a fixed head holds nothing to reach
|
||||||
}
|
}
|
||||||
reader, isPath := t.compiled.pathHeads[name]
|
reader := t.compiled.held[name].path
|
||||||
seen := map[node]bool{}
|
seen := map[node]bool{}
|
||||||
for _, e := range renderEdges(t) {
|
for _, e := range renderEdges(t) {
|
||||||
if e.read.head == name || !renders(e.to, held, seen) {
|
if e.read.head == name || !renders(e.to, held, seen) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if isPath {
|
if reader != "" {
|
||||||
return fmt.Errorf("%s renders %q, which {%s} reads a path into; name the fields you want instead", e.reached(), name, reader)
|
return fmt.Errorf("%s renders %q, which {%s} reads a path into; name the fields you want instead", e.reached(), name, reader)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s renders %q, which a {%s()} also reads; reach it one way so it is drawn once", e.reached(), name, operandReader(t, name))
|
return fmt.Errorf("%s renders %q, which a {%s()} also reads; reach it one way so it is drawn once", e.reached(), name, operandReader(t, name))
|
||||||
@@ -175,8 +175,8 @@ func renders(n node, want, seen map[node]bool) bool {
|
|||||||
// disagree. Reads are compared in sorted order, so which pair is reported does not
|
// disagree. Reads are compared in sorted order, so which pair is reported does not
|
||||||
// depend on where the tokens sit.
|
// depend on where the tokens sit.
|
||||||
// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held
|
// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held
|
||||||
func checkNoOverlap(ops []op, pathHeads map[string]string) error {
|
func checkNoOverlap(c formatOps) error {
|
||||||
names := pathHeadReaders(ops, pathHeads)
|
names := pathHeadReaders(c)
|
||||||
// Stable over one format-order scan, so two readers of one name (a token and a
|
// Stable over one format-order scan, so two readers of one name (a token and a
|
||||||
// calc operand both naming "p") are reported as the format writes them.
|
// calc operand both naming "p") are reported as the format writes them.
|
||||||
sort.SliceStable(names, func(i, j int) bool { return names[i].a.path < names[j].a.path })
|
sort.SliceStable(names, func(i, j int) bool { return names[i].a.path < names[j].a.path })
|
||||||
@@ -199,16 +199,16 @@ type reader struct {
|
|||||||
// pathHeadReaders lists every way a format reaches a sibling path head, in the order the
|
// pathHeadReaders lists every way a format reaches a sibling path head, in the order the
|
||||||
// format writes them. An operand renders its field, so it names a level exactly
|
// format writes them. An operand renders its field, so it names a level exactly
|
||||||
// as a token does; one scan finds both, which is what puts them in one order.
|
// as a token does; one scan finds both, which is what puts them in one order.
|
||||||
func pathHeadReaders(ops []op, pathHeads map[string]string) []reader {
|
func pathHeadReaders(c formatOps) []reader {
|
||||||
var names []reader
|
var names []reader
|
||||||
for _, o := range ops {
|
for _, o := range c.ops {
|
||||||
for _, a := range o.operands {
|
for _, a := range o.operands {
|
||||||
if _, isPathHead := pathHeads[a.head]; isPathHead && !isRef(a.head) {
|
if c.held[a.head].path != "" && !isRef(a.head) {
|
||||||
names = append(names, reader{a, fmt.Sprintf("%s operand %q", o.fn, a.spelling)})
|
names = append(names, reader{a, fmt.Sprintf("%s operand %q", o.fn, a.spelling)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, a := range o.arms {
|
for _, a := range o.arms {
|
||||||
if _, isPathHead := pathHeads[a.head]; isPathHead && !isRef(a.head) {
|
if c.held[a.head].path != "" && !isRef(a.head) {
|
||||||
names = append(names, reader{a, "token {" + a.spelling + "}"})
|
names = append(names, reader{a, "token {" + a.spelling + "}"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,11 +224,12 @@ func checkNoRepeatedRead(c formatOps) error {
|
|||||||
count := map[string]int{}
|
count := map[string]int{}
|
||||||
for _, o := range c.ops {
|
for _, o := range c.ops {
|
||||||
for _, a := range o.arms {
|
for _, a := range o.arms {
|
||||||
if len(a.tail) > 0 || !c.held[a.head] {
|
h, held := c.held[a.head]
|
||||||
|
if len(a.tail) > 0 || !held {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if count[a.head]++; count[a.head] > 1 {
|
if count[a.head]++; count[a.head] > 1 {
|
||||||
return fmt.Errorf("token {%s} is repeated, and %s holds %q to one draw per expansion; write {%s} once", a.spelling, c.holder[a.head], a.head, a.spelling)
|
return fmt.Errorf("token {%s} is repeated, and %s holds %q to one draw per expansion; write {%s} once", a.spelling, h.holder, a.head, a.spelling)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ func (t *template) fixedText() (string, bool) {
|
|||||||
// compiled reads.
|
// compiled reads.
|
||||||
func compileFormat(toks []formatToken, refs map[string]refBinding) (formatOps, error) {
|
func compileFormat(toks []formatToken, refs map[string]refBinding) (formatOps, error) {
|
||||||
c := compileOps(toks, refs)
|
c := compileOps(toks, refs)
|
||||||
if err := checkNoOverlap(c.ops, c.pathHeads); err != nil {
|
if err := checkNoOverlap(c); err != nil {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
return c, checkNoRepeatedRead(c)
|
return c, checkNoRepeatedRead(c)
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) readV
|
|||||||
if sc.set.trace != nil {
|
if sc.set.trace != nil {
|
||||||
traceRead(sc.set.trace, t, sc, a)
|
traceRead(sc.set.trace, t, sc, a)
|
||||||
}
|
}
|
||||||
if !t.compiled.held[a.head] {
|
if _, expansionHolds := t.compiled.held[a.head]; !expansionHolds {
|
||||||
if len(a.tail) > 0 {
|
if len(a.tail) > 0 {
|
||||||
panic(internalError("%q reads a path into %q, which the expansion does not hold", a.spelling, a.head))
|
panic(internalError("%q reads a path into %q, which the expansion does not hold", a.spelling, a.head))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ on 2026-09-29 a third time (Navigation 7.0, Locality 5.6, Shape 6.3, Self-suffic
|
|||||||
|
|
||||||
This round, in order:
|
This round, in order:
|
||||||
|
|
||||||
- Replace `formatOps`' `held`, `holder` and `pathHeads` with one map from a held name
|
|
||||||
to its first reader and the path it starts, if any: four seats traced
|
|
||||||
`formatOps.holdName` writing four fields at once.
|
|
||||||
- Give `arm` its head as written, set where `splitArm` splits, and build
|
- Give `arm` its head as written, set where `splitArm` splits, and build
|
||||||
`pathProbe.readRow`'s selector spelling from it: six seats found the slice of
|
`pathProbe.readRow`'s selector spelling from it: six seats found the slice of
|
||||||
`arm.spelling` by `len(joinSegments(tail))` resting on an invariant nothing states.
|
`arm.spelling` by `len(joinSegments(tail))` resting on an invariant nothing states.
|
||||||
|
|||||||
Reference in New Issue
Block a user