102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
package shared
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
|
)
|
|
|
|
// SourceRefOrder provides a stable snapshot of a source document's unit
|
|
// ordering for source-reference comparison and canonicalization.
|
|
type SourceRefOrder struct {
|
|
sourceID string
|
|
index source.DocumentIndex
|
|
}
|
|
|
|
// NewSourceRefOrder captures the source identity and unit positions from doc.
|
|
func NewSourceRefOrder(doc *source.SourceDocument) SourceRefOrder {
|
|
return NewSourceRefOrderWithIndex(doc, source.NewDocumentIndex(doc))
|
|
}
|
|
|
|
// NewSourceRefOrderWithIndex captures source identity with a supplied
|
|
// document index for source-reference comparison and canonicalization.
|
|
func NewSourceRefOrderWithIndex(doc *source.SourceDocument, index source.DocumentIndex) SourceRefOrder {
|
|
if doc == nil {
|
|
return SourceRefOrder{}
|
|
}
|
|
return SourceRefOrder{sourceID: doc.ID, index: index}
|
|
}
|
|
|
|
// Less orders references by source identity, then document positions when
|
|
// available, and finally literal endpoint IDs.
|
|
func (o SourceRefOrder) Less(left, right source.SourceRef) bool {
|
|
if left.SourceID != right.SourceID {
|
|
return left.SourceID < right.SourceID
|
|
}
|
|
if o.lessEndpoint(left.SourceID, left.StartUnitID, right.StartUnitID) {
|
|
return true
|
|
}
|
|
if o.lessEndpoint(left.SourceID, right.StartUnitID, left.StartUnitID) {
|
|
return false
|
|
}
|
|
return o.lessEndpoint(left.SourceID, left.EndUnitID, right.EndUnitID)
|
|
}
|
|
|
|
// EarliestValid returns the earliest document position among valid refs.
|
|
func (o SourceRefOrder) EarliestValid(refs []source.SourceRef) (int, bool) {
|
|
if len(refs) == 0 || o.sourceID == "" {
|
|
return 0, false
|
|
}
|
|
found := false
|
|
earliest := 0
|
|
for _, ref := range refs {
|
|
if o.index.ValidateRef(ref) != nil {
|
|
continue
|
|
}
|
|
start, _ := o.index.Position(ref.StartUnitID)
|
|
if !found || start < earliest {
|
|
earliest = start
|
|
found = true
|
|
}
|
|
}
|
|
return earliest, found
|
|
}
|
|
|
|
// Canonicalize returns an owned, stable-sorted, exactly de-duplicated copy of
|
|
// refs. It deliberately preserves invalid references for diagnostics.
|
|
func (o SourceRefOrder) Canonicalize(refs []source.SourceRef) []source.SourceRef {
|
|
if refs == nil {
|
|
return nil
|
|
}
|
|
canonical := append([]source.SourceRef{}, refs...)
|
|
sort.SliceStable(canonical, func(left, right int) bool {
|
|
return o.Less(canonical[left], canonical[right])
|
|
})
|
|
unique := make([]source.SourceRef, 0, len(canonical))
|
|
for _, ref := range canonical {
|
|
if len(unique) == 0 || unique[len(unique)-1] != ref {
|
|
unique = append(unique, ref)
|
|
}
|
|
}
|
|
return unique
|
|
}
|
|
|
|
func (o SourceRefOrder) lessEndpoint(sourceID string, left, right int) bool {
|
|
leftPosition, leftOK := o.position(sourceID, left)
|
|
rightPosition, rightOK := o.position(sourceID, right)
|
|
if leftOK != rightOK {
|
|
return leftOK
|
|
}
|
|
if leftOK && leftPosition != rightPosition {
|
|
return leftPosition < rightPosition
|
|
}
|
|
return left < right
|
|
}
|
|
|
|
func (o SourceRefOrder) position(sourceID string, unitID int) (int, bool) {
|
|
if o.sourceID == "" || sourceID != o.sourceID {
|
|
return 0, false
|
|
}
|
|
return o.index.Position(unitID)
|
|
}
|