// Copyright 2022 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.
// Package dag implements a language for expressing directed acyclic// graphs.//// The general syntax of a rule is://// a, b < c, d;//// which means c and d come after a and b in the partial order// (that is, there are edges from c and d to a and b),// but doesn't provide a relative order between a vs b or c vs d.//// The rules can chain together, as in://// e < f, g < h;//// which is equivalent to//// e < f, g;// f, g < h;//// Except for the special bottom element "NONE", each name// must appear exactly once on the right-hand side of any rule.// That rule serves as the definition of the allowed successor// for that name. The definition must appear before any uses// of the name on the left-hand side of a rule. (That is, the// rules themselves must be ordered according to the partial// order, for easier reading by people.)//// Negative assertions double-check the partial order://// i !< j//// means that it must NOT be the case that i < j.// Negative assertions may appear anywhere in the rules,// even before i and j have been defined.//// Comments begin with #.
package dagimport ()typeGraphstruct { Nodes []string byLabel map[string]int edges map[string]map[string]bool}func newGraph() *Graph {return &Graph{byLabel: map[string]int{}, edges: map[string]map[string]bool{}}}func ( *Graph) ( string) bool {if , := .byLabel[]; {returnfalse } .byLabel[] = len(.Nodes) .Nodes = append(.Nodes, ) .edges[] = map[string]bool{}returntrue}func ( *Graph) (, string) { .edges[][] = true}func ( *Graph) (, string) {delete(.edges[], )}func ( *Graph) (, string) bool {return .edges[] != nil && .edges[][]}func ( *Graph) ( string) []string { := make([]string, 0, 16)for := range .edges[] { = append(, ) }slices.SortFunc(, func(, string) int {returncmp.Compare(.byLabel[], .byLabel[]) })return}// Parse parses the DAG language and returns the transitive closure of// the described graph. In the returned graph, there is an edge from "b"// to "a" if b < a (or a > b) in the partial order.func ( string) (*Graph, error) { := newGraph() := []rule{} , := parseRules()if != nil {returnnil, }// TODO: Add line numbers to errors.var []string := func( string, ...any) { = append(, fmt.Sprintf(, ...)) }for , := range {if .op == "!<" { = append(, )continue }for , := range .def {if == "NONE" { ("NONE cannot be a predecessor")continue }if !.addNode() { ("multiple definitions for %s", ) }for , := range .less {if == "NONE" {continue }if , := .byLabel[]; ! { ("use of %s before its definition", ) } else { .AddEdge(, ) } } } }// Check for missing definition.for , := range .edges {for := range {if .edges[] == nil { ("missing definition for %s", ) } } }// Complete transitive closure.for , := range .Nodes {for , := range .Nodes {for , := range .Nodes {if != && != && .HasEdge(, ) && .HasEdge(, ) {if == {// Can only happen along with a "use of X before deps" error above, // but this error is more specific - it makes clear that reordering the // rules will not be enough to fix the problem. ("graph cycle: %s < %s < %s", , , ) } .AddEdge(, ) } } } }// Check negative assertions against completed allowed graph.for , := range {for , := range .less {for , := range .def {if .HasEdge(, ) { ("graph edge assertion failed: %s !< %s", , ) } } } }iflen() > 0 {returnnil, fmt.Errorf("%s", strings.Join(, "\n")) }return , nil}// A rule is a line in the DAG language where "less < def" or "less !< def".type rule struct { less []string op string// Either "<" or "!<" def []string}type syntaxError stringfunc ( syntaxError) () string {returnstring()}// parseRules parses the rules of a DAG.func parseRules( string) ( []rule, error) {deferfunc() { := recover()switch e := .(type) {casenil:returncasesyntaxError: = default:panic() } }() := &rulesParser{lineno: 1, text: }var []stringvarstringfor { , := .nextList()if == "" {if == nil {break } .syntaxError("unexpected EOF") }if != nil { = append(, rule{, , }) } = if == ";" { = nil = ""continue }if != "<" && != "!<" { .syntaxError("missing <") } = }return , }// A rulesParser parses the depsRules syntax described above.type rulesParser struct { lineno int lastWord string text string}// syntaxError reports a parsing error.func ( *rulesParser) ( string) {panic(syntaxError(fmt.Sprintf("parsing graph: line %d: syntax error: %s near %s", .lineno, , .lastWord)))}// nextList parses and returns a comma-separated list of names.func ( *rulesParser) () ( []string, string) {for { := .nextToken()switch {case"":iflen() == 0 {returnnil, "" }fallthroughcase",", "<", "!<", ";": .syntaxError("bad list syntax") } = append(, ) = .nextToken()if != "," {return , } }}// nextToken returns the next token in the deps rules,// one of ";" "," "<" "!<" or a name.func ( *rulesParser) () string {for {if .text == "" {return"" }switch .text[0] {case';', ',', '<': := .text[:1] .text = .text[1:]returncase'!':iflen(.text) < 2 || .text[1] != '<' { .syntaxError("unexpected token !") } .text = .text[2:]return"!<"case'#': := strings.Index(.text, "\n")if < 0 { = len(.text) } .text = .text[:]continuecase'\n': .lineno++fallthroughcase' ', '\t': .text = .text[1:]continuedefault: := strings.IndexAny(.text, "!;,<#\n \t")if < 0 { = len(.text) } := .text[:] .text = .text[:] .lastWord = return } }}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.