98 lines
2.3 KiB
Go
98 lines
2.3 KiB
Go
|
package analysis
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"unicode"
|
||
|
)
|
||
|
|
||
|
// Validate reports an error if any of the analyses are misconfigured.
|
||
|
// Checks include:
|
||
|
// - that the name is a valid identifier;
|
||
|
// - that analysis names are unique;
|
||
|
// - that the Requires graph is acylic;
|
||
|
// - that analyses' lemma and output types are unique.
|
||
|
// - that each lemma type is a pointer.
|
||
|
func Validate(analyses []*Analysis) error {
|
||
|
names := make(map[string]bool)
|
||
|
|
||
|
// Map each lemma/output type to its sole generating analysis.
|
||
|
lemmaTypes := make(map[reflect.Type]*Analysis)
|
||
|
outputTypes := make(map[reflect.Type]*Analysis)
|
||
|
|
||
|
// Traverse the Requires graph, depth first.
|
||
|
color := make(map[*Analysis]uint8) // 0=white 1=grey 2=black
|
||
|
var visit func(a *Analysis) error
|
||
|
visit = func(a *Analysis) error {
|
||
|
if a == nil {
|
||
|
return fmt.Errorf("nil *Analysis")
|
||
|
}
|
||
|
if color[a] == 0 { // white
|
||
|
color[a] = 1 // grey
|
||
|
|
||
|
// names
|
||
|
if !validIdent(a.Name) {
|
||
|
return fmt.Errorf("invalid analysis name %q", a)
|
||
|
}
|
||
|
if names[a.Name] {
|
||
|
return fmt.Errorf("duplicate analysis name %q", a)
|
||
|
}
|
||
|
names[a.Name] = true
|
||
|
|
||
|
if a.Doc == "" {
|
||
|
return fmt.Errorf("analysis %q is undocumented", a)
|
||
|
}
|
||
|
|
||
|
// lemma types
|
||
|
for _, t := range a.LemmaTypes {
|
||
|
if t == nil {
|
||
|
return fmt.Errorf("analysis %s has nil LemmaType", a)
|
||
|
}
|
||
|
if prev := lemmaTypes[t]; prev != nil {
|
||
|
return fmt.Errorf("lemma type %s registered by two analyses: %v, %v",
|
||
|
t, a, prev)
|
||
|
}
|
||
|
if t.Kind() != reflect.Ptr {
|
||
|
return fmt.Errorf("%s: lemma type %s is not a pointer", a, t)
|
||
|
}
|
||
|
lemmaTypes[t] = a
|
||
|
}
|
||
|
|
||
|
// output types
|
||
|
if a.OutputType != nil {
|
||
|
if prev := outputTypes[a.OutputType]; prev != nil {
|
||
|
return fmt.Errorf("output type %s registered by two analyses: %v, %v",
|
||
|
a.OutputType, a, prev)
|
||
|
}
|
||
|
outputTypes[a.OutputType] = a
|
||
|
}
|
||
|
|
||
|
// recursion
|
||
|
for i, req := range a.Requires {
|
||
|
if err := visit(req); err != nil {
|
||
|
return fmt.Errorf("%s.Requires[%d]: %v", a.Name, i, err)
|
||
|
}
|
||
|
}
|
||
|
color[a] = 2 // black
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
for _, a := range analyses {
|
||
|
if err := visit(a); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func validIdent(name string) bool {
|
||
|
for i, r := range name {
|
||
|
if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return name != ""
|
||
|
}
|