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 origins map[string]Origin transfers []inheritanceTransfer controls []inheritanceControl replacements []inheritanceReplacement operations []inheritanceOperation } type inheritanceOperation struct { control *inheritanceControl transfer *inheritanceTransfer controlOrigin Origin parent *document.Node effective *document.Node parentOrigins map[string]Origin effectiveOrigins map[string]Origin } // A replacement retains the displaced source/derivation record while the // effective origin belongs to the child. Value trees are borrowed immutable // inputs or detached templates and are budgeted with retained history later. type inheritanceReplacement struct { path Path before *document.Node after *document.Node previous []displacedDefinition effective Origin } // A link represents one shared relationship edge. Each contributor retains // its original semantic path independently of source line/column coordinates. type inheritanceTrace struct { previous *inheritanceTrace transfer *inheritanceTransfer } type inheritanceContributor struct { source Path location Location trace *inheritanceTrace } 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) { return expandSiblingDefinitionsWithLimits(scope, scopePath, defaultProjectionLimits()) } func expandSiblingDefinitionsWithLimits(scope *document.Node, scopePath Path, limits projectionLimits) (*inheritanceScope, []Diagnostic) { return expandSiblingDefinitionsWithOrigins(scope, scopePath, limits, nil) } func expandSiblingDefinitionsWithOrigins(scope *document.Node, scopePath Path, limits projectionLimits, inputOrigins map[string]Origin, budget ...*retainedNodeCounter) (*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)) resolvedOrigins := make(map[string]map[string]Origin, len(byName)) state := make(map[string]uint8, len(byName)) result := &inheritanceScope{} counter := newRetainedNodeCounter(historyNodeLimit) if len(budget) != 0 { counter = budget[0] } retained := 1 // scope mapping node for _, entry := range scope.Mapping { if entry.Value.Kind == document.Mapping { continue } remaining := limits.nodes - retained - 1 if limits.nodes >= 0 && remaining < 0 { return nil, []Diagnostic{inheritanceBudgetDiagnostic(scopePath.Key(entry.Key), entry.Value.Location)} } count, within := inheritanceLowerBoundCount(entry.Value, remaining) if !within { return nil, []Diagnostic{inheritanceBudgetDiagnostic(scopePath.Key(entry.Key), entry.Value.Location)} } retained += 1 + count // key and value subtree } 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) operationStart, replacementStart := len(result.operations), len(result.replacements) childOrigins := directInheritanceOrigins(payload, path, inputOrigins) if reference, found := inheritanceDirective(entry.Value); found { control := inheritanceControl{ path: path.Key(inheritanceKey), locations: []Location{publicLocation(reference.Value.Location)}, cancelled: reference.Value.Kind == document.Null, } controlOrigins := directInheritanceOrigins(reference.Value, control.path, inputOrigins) result.operations = append(result.operations, inheritanceOperation{ control: &control, controlOrigin: controlOrigins[control.path.String()], }) } if limits.nodes >= 0 { remaining := limits.nodes - retained - 1 // the sibling key within := false if frame.hasParent { within = inheritanceOverlayLowerBoundWithin(resolved[frame.parent], payload, remaining) } else { within = inheritanceLowerBoundWithin(payload, remaining) } if !within { return nil, []Diagnostic{inheritanceBudgetDiagnostic(path, entry.Value.Location)} } } if frame.hasParent { parent := resolved[frame.parent] merged := document.Clone(parent) control := inheritanceDirectiveLocation(entry.Value) transfer := inheritanceTransfer{ from: scopePath.Key(frame.parent), to: path, reference: path.Key(inheritanceKey), locations: []Location{control}, } mergedOrigins := transferredInheritanceOrigins(resolvedOrigins[frame.parent], transfer.from, path, transfer) mergeInheritanceOrigin(mergedOrigins, path, childOrigins[path.String()]) mergeRecursiveMappings(merged, payload, path, new(mappingIndex), recursiveMergeHooks{ added: func(childPath Path, incoming *document.Node) { copyInheritanceSubtree(mergedOrigins, childOrigins, incoming, childPath) }, combined: func(childPath Path, _ *document.Node) { mergeInheritanceOrigin(mergedOrigins, childPath, childOrigins[childPath.String()]) }, replaced: func(childPath Path, existing, incoming *document.Node) { previous := displacedOrigins(existing, mergedOrigins, childPath) result.replacements = append(result.replacements, inheritanceReplacement{ path: childPath.clone(), before: existing, after: incoming, previous: previous, effective: cloneOrigin(childOrigins[childPath.String()]), }) deleteOriginSubtree(mergedOrigins, childPath, existing) copyInheritanceSubtree(mergedOrigins, childOrigins, incoming, childPath) }, }) merged.Location = entry.Value.Location resolved[frame.name] = merged resolvedOrigins[frame.name] = mergedOrigins result.transfers = append(result.transfers, transfer) result.operations = append(result.operations, inheritanceOperation{ transfer: &transfer, parent: parent, effective: merged, parentOrigins: resolvedOrigins[frame.parent], effectiveOrigins: mergedOrigins, }) } else { resolved[frame.name] = document.Clone(payload) resolvedOrigins[frame.name] = childOrigins } count, _ := inheritanceLowerBoundCount(resolved[frame.name], limits.nodes) retained += 1 + count for _, operation := range result.operations[operationStart:] { if !counter.add(operation.parent) || !counter.add(operation.effective) { return nil, []Diagnostic{inheritanceHistoryBudgetDiagnostic(path, entry.Value.Location)} } } for _, replacement := range result.replacements[replacementStart:] { if !counter.add(replacement.before) || !counter.add(replacement.after) { return nil, []Diagnostic{inheritanceHistoryBudgetDiagnostic(path, entry.Value.Location)} } } state[frame.name] = 2 stack = stack[:len(stack)-1] } } root := *scope root.Mapping = make([]document.Entry, len(scope.Mapping)) rootOrigin := directInheritanceOrigins(&document.Node{Kind: document.Mapping, Location: scope.Location}, scopePath, inputOrigins) result.origins = rootOrigin for index, entry := range scope.Mapping { root.Mapping[index] = entry if resolved[entry.Key] != nil { root.Mapping[index].Value = resolved[entry.Key] for pointer, origin := range resolvedOrigins[entry.Key] { result.origins[pointer] = origin } } else { root.Mapping[index].Value = document.Clone(entry.Value) for pointer, origin := range directInheritanceOrigins(entry.Value, scopePath.Key(entry.Key), inputOrigins) { result.origins[pointer] = origin } } } result.root = &root if !inheritanceLowerBoundWithin(result.root, limits.nodes) { return nil, []Diagnostic{inheritanceBudgetDiagnostic(scopePath, scope.Location)} } 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, } }