package fejkdata import ( "fmt" "github.com/larvit/fejkdata/internal/builtinfunc" "github.com/larvit/fejkdata/internal/grammar" ) // linkSite is a template to link, with the folder and category it sits in, "" for an inline // template, and eachNode's label for it. type linkSite struct { t *template folder []string category, label string } // linkCategories links every template of the categories, once all data is merged, so a reference // sees the override-resolved tree. func linkCategories(sites []categorySite, root map[string]node) error { var ts []linkSite for _, s := range sites { if err := eachNode(s.n, s.path, func(label string, n node) error { if t, isTemplate := n.(*template); isTemplate { ts = append(ts, linkSite{t: t, folder: s.dir, category: s.path, label: label}) } return nil }); err != nil { return err } } return linkTemplates(ts, root) } // linkInline links the templates of an inline node against the loaded tree. func linkInline(scope nodeScope, root map[string]node) error { var ts []linkSite if err := scope(func(label string, n node) error { if t, isTemplate := n.(*template); isTemplate { ts = append(ts, linkSite{t: t, label: label}) } return nil }); err != nil { return err } return linkTemplates(ts, root) } // nameTarget is what a binding resolves to in the assembled tree: the node its head names and the // path it reads into that node. type nameTarget struct { head node tail []string } // linkedNames is what the link resolved about the names of a set of templates: each binding's // target, the keys its reads address, and its reads. type linkedNames struct { targets map[*nameBinding]nameTarget addressed map[*nameBinding]map[string]string uses map[*nameBinding][]nameUse } // linkTemplates links ts in steps, each over every template before the next starts, each // returning what it builds and taking what an earlier step built as an argument: the references, // the names' targets, the compiled formats, the keys and reads of each name, and the column each // format reads. The bindings take their targets and keys once every check has passed. func linkTemplates(ts []linkSite, root map[string]node) error { for _, s := range ts { link, err := s.t.resolveLink(s.folder, s.label, s.category, root) if err != nil { return err } s.t.link = link } targets := nameTargets(ts) for _, s := range ts { compiled, err := compileFormat(s.t, targets) if err != nil { return fmt.Errorf("%s: %w", s.label, err) } s.t.compiled = compiled } names := linkedNames{targets: targets, addressed: addressedKeys(ts, targets), uses: nameUses(ts)} for _, s := range ts { s.t.readsColumn = columnReadOf(s.t, targets) } for _, s := range ts { if err := checkNameReads(s.label, s.t, names); err != nil { return err } } for _, s := range ts { if err := checkCalcNames(s.label, s.t); err != nil { return err } } for b, target := range targets { b.nameTarget, b.addressed = target, names.addressed[b] } return nil } // nameTargets resolves what each binding of ts binds, from its binder's link. func nameTargets(ts []linkSite) map[*nameBinding]nameTarget { targets := map[*nameBinding]nameTarget{} for _, s := range ts { for _, tok := range s.t.tokens { if tok.Kind == grammar.NameBind { a := splitArm(tok.BoundRef, s.t.link.refs) targets[s.t.nameScope.bindings[tok.Bound]] = nameTarget{head: s.t.head(a.head), tail: a.tail} } } } return targets } // addressedKeys is every key the reads of each name in ts land on or pass, from the name, with the // spelling of the first read reaching it. func addressedKeys(ts []linkSite, targets map[*nameBinding]nameTarget) map[*nameBinding]map[string]string { keys := map[*nameBinding]map[string]string{} for _, s := range ts { for _, r := range namedReads(s.t) { b := r.a.named if keys[b] == nil { keys[b] = map[string]string{} } for _, key := range append(r.a.levels[len(targets[b].tail):], r.a.path) { if _, seen := keys[b][key]; !seen { keys[b][key] = r.a.spelling } } } } return keys } // nameUses is every read of each name in ts. func nameUses(ts []linkSite) map[*nameBinding][]nameUse { uses := map[*nameBinding][]nameUse{} for _, s := range ts { for _, r := range namedReads(s.t) { b := r.a.named uses[b] = append(uses[b], nameUse{ tail: grammar.JoinSegments(r.a.tail), in: s.t, operand: r.operand, noRef: r.operand && builtinfunc.NoRefOperands(r.o.Fn), nested: s.t.nameScope != b.scope, }) } } return uses } // columnRead is a record's column read by a format that only reads one reference or name, // which is the column: it takes the column's datatype and null. category and field name it; // category is "" for a column of the reading template's own record, which checkColumns reaches // on its own. type columnRead struct { a arm category, field string column node } // columnReadOf is the record's column t's format reads, where the format only reads one reference // or name, and nil where it does not. func columnReadOf(t *template, targets map[*nameBinding]nameTarget) *columnRead { ops := t.compiled.ops if t.repeat != 1 || len(ops) != 1 || ops[0].Kind != grammar.NameRead || len(ops[0].arms) != 1 { return nil } a := ops[0].arms[0] head, category, tail := t.head(a.head), a.head[min(1, len(a.head)):], a.tail switch b := a.named; { case a.kind == namedRead && b.bindsField(): head, category = b.binder, "" tail = append(append([]string{splitArm(b.ref, nil).head}, targets[b].tail...), a.tail...) case a.kind == namedRead: head, category = targets[b].head, b.binder.link.refs[b.ref].head[1:] tail = append(targets[b].tail[:len(targets[b].tail):len(targets[b].tail)], a.tail...) case !grammar.IsRef(a.head): return nil } if target, isTemplate := head.(*template); isTemplate && target.isRecord && len(tail) == 1 { return &columnRead{a: a, category: category, field: tail[0], column: target.fields[tail[0]]} } return nil }