package engine import ( "slices" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) const inheritanceKey = "extends" // These private records keep structural evidence separate from acquired source // records. Later inspection can project a transfer onto individual descendants. type inheritanceTransfer struct { from Path to Path reference Path locations []Location } type inheritanceControl struct { path Path locations []Location cancelled bool } type inheritanceScope struct { root *document.Node transfers []inheritanceTransfer controls []inheritanceControl } type siblingFrame struct { name string parent string hasParent bool entered bool } // expandSiblingDefinitions resolves only the mappings directly in scope. // Nested instructions remain in the returned templates for destination-local // resolution. This private entry point does not participate in public loading. func expandSiblingDefinitions(scope *document.Node, scopePath Path) (*inheritanceScope, []Diagnostic) { if scope == nil || scope.Kind != document.Mapping { return nil, []Diagnostic{{ Code: CodeInheritanceInvalid, Severity: SeverityError, Path: scopePath, Message: "inheritance scope must be a mapping", }} } byName := make(map[string]document.Entry, len(scope.Mapping)) names := make([]string, 0, len(scope.Mapping)) for _, entry := range scope.Mapping { byName[entry.Key] = entry names = append(names, entry.Key) } slices.Sort(names) resolved := make(map[string]*document.Node, len(byName)) state := make(map[string]uint8, len(byName)) result := &inheritanceScope{} for _, name := range names { if byName[name].Value.Kind != document.Mapping || state[name] == 2 { continue } stack := []siblingFrame{{name: name}} for len(stack) != 0 { frame := &stack[len(stack)-1] entry := byName[frame.name] path := scopePath.Key(frame.name) if !frame.entered { state[frame.name] = 1 frame.entered = true reference, found := inheritanceDirective(entry.Value) if found { referencePath := path.Key(inheritanceKey) location := publicLocation(reference.Value.Location) result.controls = append(result.controls, inheritanceControl{ path: referencePath, locations: []Location{location}, cancelled: reference.Value.Kind == document.Null, }) switch reference.Value.Kind { case document.Null: case document.String: frame.parent, frame.hasParent = reference.Value.Text, true parent, exists := byName[frame.parent] if !exists { return nil, []Diagnostic{inheritanceDiagnostic(CodeInheritanceMissing, referencePath, location, nil, "referenced sibling does not exist")} } if parent.Value.Kind != document.Mapping { related := []Location{publicLocation(parent.Value.Location)} return nil, []Diagnostic{inheritanceDiagnostic(CodeInheritanceInvalid, referencePath, location, related, "referenced sibling is not a mapping")} } if state[frame.parent] == 1 { related := []Location{publicLocation(parent.Value.Location)} return nil, []Diagnostic{inheritanceDiagnostic(CodeInheritanceCycle, referencePath, location, related, "sibling inheritance contains a cycle")} } if state[frame.parent] == 0 { stack = append(stack, siblingFrame{name: frame.parent}) continue } default: return nil, []Diagnostic{inheritanceDiagnostic(CodeInheritanceInvalid, referencePath, location, nil, "inheritance reference must be a sibling name or null")} } } } payload := withoutInheritanceDirective(entry.Value) if frame.hasParent { parent := resolved[frame.parent] merged := document.Clone(parent) mergeRecursiveMappings(merged, payload, path, new(mappingIndex), recursiveMergeHooks{}) merged.Location = entry.Value.Location resolved[frame.name] = merged reference := path.Key(inheritanceKey) control := inheritanceDirectiveLocation(entry.Value) result.transfers = append(result.transfers, inheritanceTransfer{ from: scopePath.Key(frame.parent), to: path, reference: reference, locations: []Location{control}, }) } else { resolved[frame.name] = document.Clone(payload) } state[frame.name] = 2 stack = stack[:len(stack)-1] } } root := *scope root.Mapping = make([]document.Entry, len(scope.Mapping)) for index, entry := range scope.Mapping { root.Mapping[index] = entry if resolved[entry.Key] != nil { root.Mapping[index].Value = resolved[entry.Key] } else { root.Mapping[index].Value = document.Clone(entry.Value) } } result.root = &root return result, nil } func inheritanceDirective(node *document.Node) (document.Entry, bool) { for _, entry := range node.Mapping { if entry.Key == inheritanceKey { return entry, true } } return document.Entry{}, false } func inheritanceDirectiveLocation(node *document.Node) Location { entry, _ := inheritanceDirective(node) return publicLocation(entry.Value.Location) } func withoutInheritanceDirective(node *document.Node) *document.Node { payload := *node payload.Mapping = make([]document.Entry, 0, len(node.Mapping)) for _, entry := range node.Mapping { if entry.Key != inheritanceKey { payload.Mapping = append(payload.Mapping, entry) } } return &payload } func inheritanceDiagnostic(code Code, path Path, primary Location, related []Location, message string) Diagnostic { return Diagnostic{ Code: code, Severity: SeverityError, Path: path, Message: message, Primary: &primary, Related: related, } }