Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
7 changed files with 320 additions and 306 deletions
Showing only changes of commit 0e3c986801 - Show all commits
+4 -3
View File
@@ -957,17 +957,18 @@ fejkdata.go Generator, New, options, List
shipped.go the embedded data set, and loading a shipped category on the first call reaching it shipped.go the embedded data set, and loading a shipped category on the first call reaching it
shippedindex.go the index of shipped categories, generated from data/ shippedindex.go the index of shipped categories, generated from data/
node.go the node model and JSON -> node compilation node.go the node model and JSON -> node compilation
table.go tables: their options, format and cells, and the link to the parent beside them table.go tables: their options, format and cells, the link to the parent beside them, and how a path passes and draws a table
name.go names: {x as n} bindings, the scopes they live in, and the reads they answer name.go names: {x as n} bindings, the scopes they live in, and the reads they answer
namefence.go the load fences over names namefence.go the load fences over names
pick.go a render's named picks: the frames of the name scopes rendering, the memo a pick keeps, and the reads kept under it pick.go a render's named picks: the frames of the name scopes rendering, the memo a pick keeps, and the reads kept under it
env.go renderEnv: what a render reads names and rows through env.go renderEnv: what a render reads names and rows through, and the frames a read opens and finds
path.go the dotted-path walks, and proving a path reaches a node path.go the dotted-path walks, and proving a path reaches a node
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
struct.go structs: FakeStruct, fake tags as one record's columns, and a field's Go type as its column's datatype struct.go structs: FakeStruct, fake tags as one record's columns, and a field's Go type as its column's datatype
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and resolve inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and resolve
format.go a format's tokens checked against their fields, and compiled format.go a format's tokens checked against their fields, and compiled into ops
arm.go the compiled read: an arm, the path from its head to its leaf, and the op holding it
reference.go one reference's resolution: its sigil, its category, its tail reference.go one reference's resolution: its sigil, its category, its tail
resolve.go resolving templates against the assembled tree in steps, each over every template before the next resolve.go resolving templates against the assembled tree in steps, each over every template before the next
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
+131
View File
@@ -0,0 +1,131 @@
package fejkdata
import (
"fmt"
"github.com/larvit/fejkdata/internal/builtinfunc"
"github.com/larvit/fejkdata/internal/grammar"
"github.com/larvit/fejkdata/internal/invariant"
)
// arm is one alternative of a {a|b} token or one operand, split into the head,
// whose node `template.startOf` finds, and the tail of a dotted path into it.
type arm struct {
spelling string // as written, for messages
head string
writtenHead string // head as written, sigil included
tail []string
levels []pickKey // the key of each level the steps pass, from the head they start at to the leaf
steps []pathStep
leaves []node // every node the path may land on, one per variant it passes
kind armKind
named *nameBinding // namedRead: the binding of the name it reads through
}
// armKind is how expand reads an arm, fixed at compile.
type armKind uint8
const (
freshRead armKind = iota // drawn afresh at every read
namedRead // read through a name, kept in its pick
)
// splitArm splits one name into head and tail. refs maps a reference to what
// resolveRefs resolved it to; before that, a reference is whole.
func splitArm(name string, refs map[string]resolvedRef) arm {
if grammar.IsRef(name) {
r, resolved := refs[name]
if !resolved || len(r.tail) == 0 {
head := name
if resolved {
head = r.head
}
return arm{spelling: name, head: head, levels: []pickKey{pickKey(head)}}
}
sigil, rest, _ := grammar.RefShape(name) // resolveRefs proved it, and took r.tail as a suffix of its segments
written, _ := grammar.SplitPath(rest)
return pathArm(name, r.head, sigil+grammar.JoinSegments(written[:len(written)-len(r.tail)]), r.tail)
}
segs, err := grammar.SplitPath(name)
if err != nil || len(segs) == 1 {
return arm{spelling: name, head: name, levels: []pickKey{pickKey(name)}}
}
return pathArm(name, segs[0], segs[0], segs[1:])
}
func pathArm(name, head, writtenHead string, segs []string) arm {
return arm{spelling: name, head: head, writtenHead: writtenHead, tail: segs, levels: levelKeys(append([]string{head}, segs...), 1)}
}
// levelKeys is the key of each prefix of path holding at least from segments, shortest first.
func levelKeys(path []string, from int) []pickKey {
levels := make([]pickKey, len(path)-from+1)
for i := range levels {
levels[i] = pickKey(grammar.JoinSegments(path[:from+i]))
}
return levels
}
// key is the key of the leaf a lands on, the one key every way of writing this read shares.
func (a arm) key() pickKey { return a.levels[len(a.levels)-1] }
func checkSegments(a arm) error {
if len(a.tail) == 0 {
return nil
}
return grammar.CheckSegments(append([]string{a.head}, a.tail...))
}
// op is one compiled unit of a format string: a literal run, a name read,
// or a builtin already prepared with its args.
type op struct {
grammar.Token
arms []arm // grammar.NameRead: the '|' alternatives, split into head and tail once
call builtinfunc.Call
// operands are the fields the builtin reads, in the order its operands func
// fixed; expand reads them before the call. nil for a builtin that reads none.
operands []arm
}
func (t *template) compileArms(names []string, targets map[*nameBinding]nameTarget) ([]arm, error) {
if len(names) == 0 {
return nil, nil
}
arms := make([]arm, len(names))
for i, name := range names {
a, err := t.compileArm(name, targets)
if err != nil {
return nil, err
}
arms[i] = a
}
return arms, nil
}
// compileArm compiles one read into a path: from the head it names, or, for a read through a
// name, from what that name binds.
func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget) (arm, error) {
a := splitArm(name, t.refs.byName)
if !t.isName(a.head) {
start := t.startOf(a.head)
if start == nil {
panic(invariant.Broken("{%s} reads a head nothing resolved", a.spelling))
}
w := compilePath(start, a.tail)
a.steps, a.leaves = w.steps, w.leaves
return a, nil
}
b := t.nameScope.lookup(a.head)
if grammar.HasSelector(a.tail) {
return a, fmt.Errorf("a path through name %q may not select a row; read it directly, {%s.%s}, or bind the row to a name of its own", a.head, b.ref, grammar.JoinSegments(a.tail))
}
target := targets[b]
full := append(target.tail[:len(target.tail):len(target.tail)], a.tail...)
if err := checkPathReaches(target.start, full, a.head); err != nil {
return a, err
}
w := compilePath(target.start, full)
a.kind, a.named, a.steps, a.leaves = namedRead, b, w.steps, w.leaves
a.levels = levelKeys(full, 0)
return a, nil
}
+57 -1
View File
@@ -1,6 +1,9 @@
package fejkdata package fejkdata
import "github.com/larvit/fejkdata/internal/invariant" import (
"github.com/larvit/fejkdata/internal/drawstate"
"github.com/larvit/fejkdata/internal/invariant"
)
// readValue is what one read drew: its text, and whether it landed on a null. // readValue is what one read drew: its text, and whether it landed on a null.
type readValue struct { type readValue struct {
@@ -47,3 +50,56 @@ func (env renderEnv) rowOf(t *table) int {
} }
return env.row.index return env.row.index
} }
// enter starts a read landing on n: the read sees no frame opened before it, and gets a frame for
// each name scope around n, from memo where there is one, so reads sharing memo read one pick of
// each name. It returns the mark that closes those frames.
func (env renderEnv) enter(n node, memo *drawMemo) (renderEnv, int) {
env = env.entering()
for scope := scopeAround(n); scope != nil; scope = scope.up {
if len(scope.order) > 0 {
env.frames.push(memo.enteredFrame(scope))
}
}
return env, env.base
}
// entering is env as a read entering a category sees it: no frame opened before it, and no pick.
func (env renderEnv) entering() renderEnv {
env.base, env.pick = len(env.frames.frames), nil
return env
}
// renderFrame opens a fresh frame of t's scope, where t binds names and no read entering the
// category opened one, returning the mark that closes it, or -1.
func (env renderEnv) renderFrame(t *template) int {
own := t.ownScope()
if own == nil || env.frameOf(own) != nil {
return -1
}
return env.frames.push(newPickFrame(own))
}
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
func (env renderEnv) frameOf(scope *nameScope) *pickFrame {
for i := len(env.frames.frames) - 1; i >= env.base; i-- {
if f := env.frames.frames[i]; f.scope == scope {
return f
}
}
return nil
}
// drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one.
func (env renderEnv) drawRowOf(s *drawstate.State, t *table) int {
if env.pick != nil {
return t.rows.DrawIn(s, &env.pick.pins)
}
return t.rows.Draw(s)
}
// keeps reports whether a read of env's name addresses the level a starts at.
func (env renderEnv) keeps(a arm) bool {
_, kept := env.pick.named.addressed[env.pickAt.under(a.levels[0])]
return kept
}
-123
View File
@@ -6,7 +6,6 @@ import (
"github.com/larvit/fejkdata/internal/builtinfunc" "github.com/larvit/fejkdata/internal/builtinfunc"
"github.com/larvit/fejkdata/internal/grammar" "github.com/larvit/fejkdata/internal/grammar"
"github.com/larvit/fejkdata/internal/invariant"
) )
// checkFunc validates a call at compile time: a known builtin, its args, and, for calc, the // checkFunc validates a call at compile time: a known builtin, its args, and, for calc, the
@@ -196,85 +195,6 @@ func checkNoRepeatedArm(body string, names []string) error {
return nil return nil
} }
// arm is one alternative of a {a|b} token or one operand, split into the head,
// whose node `template.startOf` finds, and the tail of a dotted path into it.
type arm struct {
spelling string // as written, for messages
head string
writtenHead string // head as written, sigil included
tail []string
levels []pickKey // the key of each level the steps pass, from the head they start at to the leaf
steps []pathStep
leaves []node // every node the path may land on, one per variant it passes
kind armKind
named *nameBinding // namedRead: the binding of the name it reads through
}
// armKind is how expand reads an arm, fixed at compile.
type armKind uint8
const (
freshRead armKind = iota // drawn afresh at every read
namedRead // read through a name, kept in its pick
)
// splitArm splits one name into head and tail. refs maps a reference to what
// resolveRefs resolved it to; before that, a reference is whole.
func splitArm(name string, refs map[string]resolvedRef) arm {
if grammar.IsRef(name) {
r, resolved := refs[name]
if !resolved || len(r.tail) == 0 {
head := name
if resolved {
head = r.head
}
return arm{spelling: name, head: head, levels: []pickKey{pickKey(head)}}
}
sigil, rest, _ := grammar.RefShape(name) // resolveRefs proved it, and took r.tail as a suffix of its segments
written, _ := grammar.SplitPath(rest)
return pathArm(name, r.head, sigil+grammar.JoinSegments(written[:len(written)-len(r.tail)]), r.tail)
}
segs, err := grammar.SplitPath(name)
if err != nil || len(segs) == 1 {
return arm{spelling: name, head: name, levels: []pickKey{pickKey(name)}}
}
return pathArm(name, segs[0], segs[0], segs[1:])
}
func pathArm(name, head, writtenHead string, segs []string) arm {
return arm{spelling: name, head: head, writtenHead: writtenHead, tail: segs, levels: levelKeys(append([]string{head}, segs...), 1)}
}
// levelKeys is the key of each prefix of path holding at least from segments, shortest first.
func levelKeys(path []string, from int) []pickKey {
levels := make([]pickKey, len(path)-from+1)
for i := range levels {
levels[i] = pickKey(grammar.JoinSegments(path[:from+i]))
}
return levels
}
// key is the key of the leaf a lands on, the one key every way of writing this read shares.
func (a arm) key() pickKey { return a.levels[len(a.levels)-1] }
func checkSegments(a arm) error {
if len(a.tail) == 0 {
return nil
}
return grammar.CheckSegments(append([]string{a.head}, a.tail...))
}
// op is one compiled unit of a format string: a literal run, a name read,
// or a builtin already prepared with its args.
type op struct {
grammar.Token
arms []arm // grammar.NameRead: the '|' alternatives, split into head and tail once
call builtinfunc.Call
// operands are the fields the builtin reads, in the order its operands func
// fixed; expand reads them before the call. nil for a builtin that reads none.
operands []arm
}
// formatOps is a compiled format: its ops, and the size of its literal text. // formatOps is a compiled format: its ops, and the size of its literal text.
type formatOps struct { type formatOps struct {
ops []op ops []op
@@ -306,46 +226,3 @@ func compileFormat(t *template, targets map[*nameBinding]nameTarget) (formatOps,
} }
return c, nil return c, nil
} }
func (t *template) compileArms(names []string, targets map[*nameBinding]nameTarget) ([]arm, error) {
if len(names) == 0 {
return nil, nil
}
arms := make([]arm, len(names))
for i, name := range names {
a, err := t.compileArm(name, targets)
if err != nil {
return nil, err
}
arms[i] = a
}
return arms, nil
}
// compileArm compiles one read into a path: from the head it names, or, for a read through a
// name, from what that name binds.
func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget) (arm, error) {
a := splitArm(name, t.refs.byName)
if !t.isName(a.head) {
start := t.startOf(a.head)
if start == nil {
panic(invariant.Broken("{%s} reads a head nothing resolved", a.spelling))
}
w := compilePath(start, a.tail)
a.steps, a.leaves = w.steps, w.leaves
return a, nil
}
b := t.nameScope.lookup(a.head)
if grammar.HasSelector(a.tail) {
return a, fmt.Errorf("a path through name %q may not select a row; read it directly, {%s.%s}, or bind the row to a name of its own", a.head, b.ref, grammar.JoinSegments(a.tail))
}
target := targets[b]
full := append(target.tail[:len(target.tail):len(target.tail)], a.tail...)
if err := checkPathReaches(target.start, full, a.head); err != nil {
return a, err
}
w := compilePath(target.start, full)
a.kind, a.named, a.steps, a.leaves = namedRead, b, w.steps, w.leaves
a.levels = levelKeys(full, 0)
return a, nil
}
-126
View File
@@ -42,52 +42,6 @@ func stepInto(n node, seg string) (node, error) {
return nil, fmt.Errorf("no field %q", seg) return nil, fmt.Errorf("no field %q", seg)
} }
// tableRoute is how a path passes one table: the row it reads, by selector or
// drawn, what it goes on into, and whether that is a linked table.
type tableRoute struct {
sel string
draw bool
next node
rest []string
descends bool
up bool
}
// route is how tail passes t. descended means the previous route stepped into t
// from a row of an ancestor table, so t reads a row even where tail is empty; a
// table reached otherwise, with no selector and an empty tail, is left to a render's
// own draw.
func (t *table) route(tail []string, descended bool) (tableRoute, error) {
sel, tail, err := t.selector(tail)
if err != nil {
return tableRoute{}, err
}
if len(tail) > 0 && tail[0] == ".." {
if err := t.rows.ProveStepUp(tail[1:]); err != nil {
return tableRoute{}, err
}
return tableRoute{sel: sel, draw: sel == "", next: t.rows.Parent().Owner(), rest: tail[2:], descends: true, up: true}, nil
}
column, child, err := t.step(tail)
if err != nil {
return tableRoute{}, err
}
// A selector further down pins this table by ancestry, so the walk draws only
// where none follows; drawing first could draw a row the selector is not inside.
r := tableRoute{sel: sel, draw: sel == "" && (descended || len(tail) > 0) && !grammar.HasSelector(tail)}
switch {
case len(tail) == 0 && sel == "" && !descended:
r.next = t
case len(tail) == 0:
r.next = t.rowNode
case child != nil:
r.next, r.rest, r.descends = child, tail[1:], true
default:
r.next, r.rest = column, tail[1:]
}
return r, nil
}
// pathStep is one step of a compiled path, taken at the node the steps before it // pathStep is one step of a compiled path, taken at the node the steps before it
// reached, a choice there drawn first; at indexes the tail where it is taken, and // reached, a choice there drawn first; at indexes the tail where it is taken, and
// only a table's step sits past the tail's end. A step holds no node, so a // only a table's step sits past the tail's end. A step holds no node, so a
@@ -183,38 +137,6 @@ func takeStep(at *pathPos, whole []string, level string, steps []pathStep, pins
return steps, nil return steps, nil
} }
// routeSteps appends the steps r takes past t, the first at at, pinning in pins the row
// it selects.
func routeSteps(steps []pathStep, pins *pinSet, t *table, r tableRoute, at int) ([]pathStep, error) {
if r.sel != "" {
row, err := pins.Select(t.rows, r.sel)
if err != nil {
return steps, err
}
steps = append(steps, pathStep{kind: stepSelect, at: at, name: r.sel, row: row})
at++
}
if r.draw {
steps = append(steps, pathStep{kind: stepDraw, at: at})
}
switch next := r.next.(type) {
case *table:
switch {
case r.up:
steps = append(steps, pathStep{kind: stepParent, at: at})
case next != t:
steps = append(steps, pathStep{kind: stepChild, at: at, name: next.rows.Segment()})
}
case *tableRow:
steps = append(steps, pathStep{kind: stepRow, at: at})
case *tableColumn:
steps = append(steps, pathStep{kind: stepColumn, at: at, name: next.name()})
default:
panic(invariant.Broken("%s: a route onto %T", t.rows.Segment(), r.next))
}
return steps, nil
}
// walkEvery walks every variant and keeps the first one's steps: every variant carries // walkEvery walks every variant and keeps the first one's steps: every variant carries
// the rest of the path, so each compiles to the same steps past the choice. // the rest of the path, so each compiles to the same steps past the choice.
func (w *pathCheck) walkEvery(c *choice, tail []string) (node, error) { func (w *pathCheck) walkEvery(c *choice, tail []string) (node, error) {
@@ -291,28 +213,6 @@ func drawSteps(s *drawstate.State, n node, steps []pathStep, pins *pinSet, memo
return n, pins return n, pins
} }
func (t *table) drawStep(s *drawstate.State, st pathStep, pins *pinSet) node {
switch st.kind {
case stepSelect:
if err := pins.PinRow(t.rows, st.row); err != nil {
panic(invariant.Broken("%s[%s]: %v", t.rows.Segment(), st.name, err))
}
return t
case stepDraw:
t.rows.DrawIn(s, pins)
return t
case stepRow:
return t.rowNode
case stepColumn:
return t.formatTemplate.fields[st.name]
case stepChild:
return t.rows.Descendant(st.name).Owner()
case stepParent:
return t.rows.Parent().Owner()
}
panic(invariant.Broken("drawStep has no case for step kind %d", st.kind))
}
func drawVariant(s *drawstate.State, c *choice, memo *drawMemo, levels []pickKey, at int) node { func drawVariant(s *drawstate.State, c *choice, memo *drawMemo, levels []pickKey, at int) node {
if memo == nil { if memo == nil {
return drawThroughChoices(s, c) return drawThroughChoices(s, c)
@@ -320,32 +220,6 @@ func drawVariant(s *drawstate.State, c *choice, memo *drawMemo, levels []pickKey
return memo.variantOf(s, c, levels[at]) return memo.variantOf(s, c, levels[at])
} }
func (t *table) selector(tail []string) (sel string, rest []string, err error) {
if len(tail) == 0 || !grammar.IsSelector(tail[0]) {
return "", tail, nil
}
sel, rest = grammar.SelectorOf(tail[0]), tail[1:]
if len(rest) > 0 && grammar.IsSelector(rest[0]) {
return "", nil, fmt.Errorf("%s[%s] is selected twice; one selector names its row", t.rows.Segment(), sel)
}
return sel, rest, nil
}
// step is what a tail's first segment names in t: a column, or a table linked to it.
func (t *table) step(tail []string) (column node, child *table, err error) {
if len(tail) == 0 {
return nil, nil, nil
}
if column, ok := t.formatTemplate.fields[tail[0]]; ok {
return column, nil, nil
}
d := t.rows.Descendant(tail[0])
if d == nil {
return nil, nil, fmt.Errorf("no column or linked table %q in %s", tail[0], t.rows.Segment())
}
return nil, d.Owner(), nil
}
// carriedByAll is the choice rule a path that must reach a node on every call obeys: // carriedByAll is the choice rule a path that must reach a node on every call obeys:
// the rest of the tail must be one every variant carries. // the rest of the tail must be one every variant carries.
func carriedByAll(c *choice, rest []string) error { func carriedByAll(c *choice, rest []string) error {
-53
View File
@@ -85,25 +85,6 @@ func (st *frameStack) push(f *pickFrame) int {
func (st *frameStack) pop(mark int) { st.frames = st.frames[:mark] } func (st *frameStack) pop(mark int) { st.frames = st.frames[:mark] }
// enter starts a read landing on n: the read sees no frame opened before it, and gets a frame for
// each name scope around n, from memo where there is one, so reads sharing memo read one pick of
// each name. It returns the mark that closes those frames.
func (env renderEnv) enter(n node, memo *drawMemo) (renderEnv, int) {
env = env.entering()
for scope := scopeAround(n); scope != nil; scope = scope.up {
if len(scope.order) > 0 {
env.frames.push(memo.enteredFrame(scope))
}
}
return env, env.base
}
// entering is env as a read entering a category sees it: no frame opened before it, and no pick.
func (env renderEnv) entering() renderEnv {
env.base, env.pick = len(env.frames.frames), nil
return env
}
// scopeAround is the innermost name scope a render of n reads names in, short of the frames n // scopeAround is the innermost name scope a render of n reads names in, short of the frames n
// renders itself, one per iteration of a repeat. // renders itself, one per iteration of a repeat.
func scopeAround(n node) *nameScope { func scopeAround(n node) *nameScope {
@@ -134,26 +115,6 @@ func (m *drawMemo) enteredFrame(scope *nameScope) *pickFrame {
return f return f
} }
// renderFrame opens a fresh frame of t's scope, where t binds names and no read entering the
// category opened one, returning the mark that closes it, or -1.
func (env renderEnv) renderFrame(t *template) int {
own := t.ownScope()
if own == nil || env.frameOf(own) != nil {
return -1
}
return env.frames.push(newPickFrame(own))
}
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
func (env renderEnv) frameOf(scope *nameScope) *pickFrame {
for i := len(env.frames.frames) - 1; i >= env.base; i-- {
if f := env.frames.frames[i]; f.scope == scope {
return f
}
}
return nil
}
// readName reads the arm a from the pick of its name, held in the frame of the name's scope. // readName reads the arm a from the pick of its name, held in the frame of the name's scope.
// When the pick renders another category, enter takes that category's frames from the pick's // When the pick renders another category, enter takes that category's frames from the pick's
// memo, and renderFrame finds them and opens no new one. // memo, and renderFrame finds them and opens no new one.
@@ -179,20 +140,6 @@ func readName(s *drawstate.State, env renderEnv, a arm) readValue {
return r return r
} }
// drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one.
func (env renderEnv) drawRowOf(s *drawstate.State, t *table) int {
if env.pick != nil {
return t.rows.DrawIn(s, &env.pick.pins)
}
return t.rows.Draw(s)
}
// keeps reports whether a read of env's name addresses the level a starts at.
func (env renderEnv) keeps(a arm) bool {
_, kept := env.pick.named.addressed[env.pickAt.under(a.levels[0])]
return kept
}
// readUnder reads a, an arm of t, once per pick: t renders in env.pick at env.pickAt, and a read of // readUnder reads a, an arm of t, once per pick: t renders in env.pick at env.pickAt, and a read of
// the name addresses the level a starts at. It keys the value by a's key under env.pickAt. // the name addresses the level a starts at. It keys the value by a's key under env.pickAt.
func readUnder(s *drawstate.State, t *template, env renderEnv, a arm) readValue { func readUnder(s *drawstate.State, t *template, env renderEnv, a arm) readValue {
+128
View File
@@ -4,7 +4,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/larvit/fejkdata/internal/drawstate"
"github.com/larvit/fejkdata/internal/grammar" "github.com/larvit/fejkdata/internal/grammar"
"github.com/larvit/fejkdata/internal/invariant"
"github.com/larvit/fejkdata/internal/rows" "github.com/larvit/fejkdata/internal/rows"
) )
@@ -233,3 +235,129 @@ func (t *table) linkParent(siblings map[string]node) error {
return nil return nil
}) })
} }
// tableRoute is how a path passes one table: the row it reads, by selector or
// drawn, what it goes on into, and whether that is a linked table.
type tableRoute struct {
sel string
draw bool
next node
rest []string
descends bool
up bool
}
// route is how tail passes t. descended means the previous route stepped into t
// from a row of an ancestor table, so t reads a row even where tail is empty; a
// table reached otherwise, with no selector and an empty tail, is left to a render's
// own draw.
func (t *table) route(tail []string, descended bool) (tableRoute, error) {
sel, tail, err := t.selector(tail)
if err != nil {
return tableRoute{}, err
}
if len(tail) > 0 && tail[0] == ".." {
if err := t.rows.ProveStepUp(tail[1:]); err != nil {
return tableRoute{}, err
}
return tableRoute{sel: sel, draw: sel == "", next: t.rows.Parent().Owner(), rest: tail[2:], descends: true, up: true}, nil
}
column, child, err := t.step(tail)
if err != nil {
return tableRoute{}, err
}
// A selector further down pins this table by ancestry, so the walk draws only
// where none follows; drawing first could draw a row the selector is not inside.
r := tableRoute{sel: sel, draw: sel == "" && (descended || len(tail) > 0) && !grammar.HasSelector(tail)}
switch {
case len(tail) == 0 && sel == "" && !descended:
r.next = t
case len(tail) == 0:
r.next = t.rowNode
case child != nil:
r.next, r.rest, r.descends = child, tail[1:], true
default:
r.next, r.rest = column, tail[1:]
}
return r, nil
}
// routeSteps appends the steps r takes past t, the first at at, pinning in pins the row
// it selects.
func routeSteps(steps []pathStep, pins *pinSet, t *table, r tableRoute, at int) ([]pathStep, error) {
if r.sel != "" {
row, err := pins.Select(t.rows, r.sel)
if err != nil {
return steps, err
}
steps = append(steps, pathStep{kind: stepSelect, at: at, name: r.sel, row: row})
at++
}
if r.draw {
steps = append(steps, pathStep{kind: stepDraw, at: at})
}
switch next := r.next.(type) {
case *table:
switch {
case r.up:
steps = append(steps, pathStep{kind: stepParent, at: at})
case next != t:
steps = append(steps, pathStep{kind: stepChild, at: at, name: next.rows.Segment()})
}
case *tableRow:
steps = append(steps, pathStep{kind: stepRow, at: at})
case *tableColumn:
steps = append(steps, pathStep{kind: stepColumn, at: at, name: next.name()})
default:
panic(invariant.Broken("%s: a route onto %T", t.rows.Segment(), r.next))
}
return steps, nil
}
func (t *table) drawStep(s *drawstate.State, st pathStep, pins *pinSet) node {
switch st.kind {
case stepSelect:
if err := pins.PinRow(t.rows, st.row); err != nil {
panic(invariant.Broken("%s[%s]: %v", t.rows.Segment(), st.name, err))
}
return t
case stepDraw:
t.rows.DrawIn(s, pins)
return t
case stepRow:
return t.rowNode
case stepColumn:
return t.formatTemplate.fields[st.name]
case stepChild:
return t.rows.Descendant(st.name).Owner()
case stepParent:
return t.rows.Parent().Owner()
}
panic(invariant.Broken("drawStep has no case for step kind %d", st.kind))
}
func (t *table) selector(tail []string) (sel string, rest []string, err error) {
if len(tail) == 0 || !grammar.IsSelector(tail[0]) {
return "", tail, nil
}
sel, rest = grammar.SelectorOf(tail[0]), tail[1:]
if len(rest) > 0 && grammar.IsSelector(rest[0]) {
return "", nil, fmt.Errorf("%s[%s] is selected twice; one selector names its row", t.rows.Segment(), sel)
}
return sel, rest, nil
}
// step is what a tail's first segment names in t: a column, or a table linked to it.
func (t *table) step(tail []string) (column node, child *table, err error) {
if len(tail) == 0 {
return nil, nil, nil
}
if column, ok := t.formatTemplate.fields[tail[0]]; ok {
return column, nil, nil
}
d := t.rows.Descendant(tail[0])
if d == nil {
return nil, nil, fmt.Errorf("no column or linked table %q in %s", tail[0], t.rows.Segment())
}
return nil, d.Owner(), nil
}