diff options
author | Matthew Dempsky <mdempsky@google.com> | 2022-08-04 10:12:28 -0700 |
---|---|---|
committer | Matthew Dempsky <mdempsky@google.com> | 2022-08-04 10:12:28 -0700 |
commit | d558507db42d600e5ad82748bda0cb91df57b97d (patch) | |
tree | 169457500d42144774eb68c5ab2ef70ad67aa673 /src/internal/dag/parse.go | |
parent | c9f2150cfb3c1db87f6434f727c25403d985a6e4 (diff) | |
parent | 85d87b9c7507628144db51bd1e7e80cc3afed128 (diff) | |
download | go-git-dev.unified.tar.gz |
[dev.unified] all: merge master (85d87b9) into dev.unifieddev.unified
Merge List:
+ 2022-08-04 85d87b9c75 all: update vendored golang.org/x dependencies for Go 1.20 development
+ 2022-08-04 fb1bfd4d37 all: remove pre-Go 1.17 workarounds
+ 2022-08-04 44ff9bff0c runtime: clean up panic and deadlock lock ranks
+ 2022-08-04 f42dc0de74 runtime: make the lock rank DAG make more sense
+ 2022-08-04 d29a0282e9 runtime: add mayAcquire annotation for finlock
+ 2022-08-04 c5be4ed7df runtime: add missing trace lock edges
+ 2022-08-04 2b8a9a484f runtime: generate the lock ranking from a DAG description
+ 2022-08-04 ddfd639408 runtime: delete unused lock ranks
+ 2022-08-04 426ea5702b internal/dag: add a Graph type and make node order deterministic
+ 2022-08-04 d37cc9a8cd go/build, internal/dag: lift DAG parser into an internal package
+ 2022-08-04 ab0a94c6d3 cmd/dist: require Go 1.17 for building Go
+ 2022-08-04 1e3c19f3fe runtime: support riscv64 SV57 mode
+ 2022-08-03 f28fa952b5 make.bat, make.rc: show bootstrap toolchain version
+ 2022-08-03 87384801dc cmd/asm: update package doc to describe "-p" option
+ 2022-08-03 c6a2dada0d net: disable TestIPv6WriteMsgUDPAddrPortTargetAddrIPVersion [sic] on DragonflyBSD
+ 2022-08-02 29b9a328d2 runtime: trivial replacements of g in remaining files
+ 2022-08-02 c647264619 runtime: trivial replacements of g in signal_unix.go
+ 2022-08-02 399f50c9d7 runtime: tricky replacements of g in traceback.go
+ 2022-08-02 4509e951ec runtime: tricky replacements of g in proc.go
+ 2022-08-02 4400238ec8 runtime: trivial replacements of _g_ in remaining files
+ 2022-08-02 5999a28de8 runtime: trivial replacements of _g_ in os files
+ 2022-08-02 0e18cf6d09 runtime: trivial replacements of _g_ in GC files
+ 2022-08-02 4358a53a97 runtime: trivial replacements of _g_ in proc.go
+ 2022-08-02 b486518964 runtime: tricky replacements of _g_ in os3_solaris.go
+ 2022-08-02 54a0ab3f7b runtime: tricky replacements of _g_ in os3_plan9.go
+ 2022-08-02 4240ff764b runtime: tricky replacements of _g_ in signal_windows.go
+ 2022-08-02 8666d89ca8 runtime: tricky replacements of _g_ in signal_unix.go
+ 2022-08-02 74cee276fe runtime: tricky replacements of _g_ in trace.go
+ 2022-08-02 222799fde6 runtime: tricky replacements of _g_ in mgc.go
+ 2022-08-02 e9d7f54a1a runtime: tricky replacements of _g_ in proc.go
+ 2022-08-02 5e8d261918 runtime: rename _p_ to pp
+ 2022-08-02 0ad2ec6596 runtime: clean up dopanic_m
+ 2022-08-02 7e952962df runtime: clean up canpanic
+ 2022-08-02 9dbc0f3556 runtime: fix outdated g.m comment in traceback.go
+ 2022-08-02 d723df76da internal/goversion: update Version to 1.20
+ 2022-08-02 1b7e71e8ae all: disable tests that fail on Alpine
+ 2022-08-01 f2a9f3e2e0 test: improve generic type assertion test
+ 2022-08-01 27038b70f8 cmd/compile: fix wrong dict pass condition for type assertions
+ 2022-08-01 e99f53fed9 doc: move Go 1.19 release notes to x/website
+ 2022-08-01 8b13a073a1 doc: mention removal of cmd/compile's -importmap and -installsuffix flags
+ 2022-08-01 e95fd4c238 doc/go1.19: fix typo: EM_LONGARCH -> EM_LOONGARCH
+ 2022-08-01 dee3efd9f8 doc/go1.19: fix a few links that were missing trailing slashes
+ 2022-07-30 f32519e5fb runtime: fix typos
+ 2022-07-29 9a2001a8cc cmd/dist: always pass -short=true with -quick
+ 2022-07-28 5c8ec89cb5 doc/go1.19: minor adjustments and links
+ 2022-07-28 417be37048 doc/go1.19: improve the loong64 release notes
+ 2022-07-28 027855e8d8 os/exec: add GODEBUG setting to opt out of ErrDot changes
Change-Id: Idc0fbe93978c0dff7600b90a2c3ecc067fd9f5f2
Diffstat (limited to 'src/internal/dag/parse.go')
-rw-r--r-- | src/internal/dag/parse.go | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/src/internal/dag/parse.go b/src/internal/dag/parse.go new file mode 100644 index 0000000000..9d5b918b11 --- /dev/null +++ b/src/internal/dag/parse.go @@ -0,0 +1,314 @@ +// 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 dag + +import ( + "fmt" + "sort" + "strings" +) + +type Graph struct { + 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 (g *Graph) addNode(label string) bool { + if _, ok := g.byLabel[label]; ok { + return false + } + g.byLabel[label] = len(g.Nodes) + g.Nodes = append(g.Nodes, label) + g.edges[label] = map[string]bool{} + return true +} + +func (g *Graph) AddEdge(from, to string) { + g.edges[from][to] = true +} + +func (g *Graph) DelEdge(from, to string) { + delete(g.edges[from], to) +} + +func (g *Graph) HasEdge(from, to string) bool { + return g.edges[from] != nil && g.edges[from][to] +} + +func (g *Graph) Edges(from string) []string { + edges := make([]string, 0, 16) + for k := range g.edges[from] { + edges = append(edges, k) + } + sort.Slice(edges, func(i, j int) bool { return g.byLabel[edges[i]] < g.byLabel[edges[j]] }) + return edges +} + +// 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 Parse(dag string) (*Graph, error) { + g := newGraph() + disallowed := []rule{} + + rules, err := parseRules(dag) + if err != nil { + return nil, err + } + + // TODO: Add line numbers to errors. + var errors []string + errorf := func(format string, a ...any) { + errors = append(errors, fmt.Sprintf(format, a...)) + } + for _, r := range rules { + if r.op == "!<" { + disallowed = append(disallowed, r) + continue + } + for _, def := range r.def { + if def == "NONE" { + errorf("NONE cannot be a predecessor") + continue + } + if !g.addNode(def) { + errorf("multiple definitions for %s", def) + } + for _, less := range r.less { + if less == "NONE" { + continue + } + if _, ok := g.byLabel[less]; !ok { + errorf("use of %s before its definition", less) + } else { + g.AddEdge(def, less) + } + } + } + } + + // Check for missing definition. + for _, tos := range g.edges { + for to := range tos { + if g.edges[to] == nil { + errorf("missing definition for %s", to) + } + } + } + + // Complete transitive closure. + for _, k := range g.Nodes { + for _, i := range g.Nodes { + for _, j := range g.Nodes { + if i != k && k != j && g.HasEdge(i, k) && g.HasEdge(k, j) { + if i == j { + // 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. + errorf("graph cycle: %s < %s < %s", j, k, i) + } + g.AddEdge(i, j) + } + } + } + } + + // Check negative assertions against completed allowed graph. + for _, bad := range disallowed { + for _, less := range bad.less { + for _, def := range bad.def { + if g.HasEdge(def, less) { + errorf("graph edge assertion failed: %s !< %s", less, def) + } + } + } + } + + if len(errors) > 0 { + return nil, fmt.Errorf("%s", strings.Join(errors, "\n")) + } + + return g, 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 string + +func (e syntaxError) Error() string { + return string(e) +} + +// parseRules parses the rules of a DAG. +func parseRules(rules string) (out []rule, err error) { + defer func() { + e := recover() + switch e := e.(type) { + case nil: + return + case syntaxError: + err = e + default: + panic(e) + } + }() + p := &rulesParser{lineno: 1, text: rules} + + var prev []string + var op string + for { + list, tok := p.nextList() + if tok == "" { + if prev == nil { + break + } + p.syntaxError("unexpected EOF") + } + if prev != nil { + out = append(out, rule{prev, op, list}) + } + prev = list + if tok == ";" { + prev = nil + op = "" + continue + } + if tok != "<" && tok != "!<" { + p.syntaxError("missing <") + } + op = tok + } + + return out, err +} + +// A rulesParser parses the depsRules syntax described above. +type rulesParser struct { + lineno int + lastWord string + text string +} + +// syntaxError reports a parsing error. +func (p *rulesParser) syntaxError(msg string) { + panic(syntaxError(fmt.Sprintf("parsing graph: line %d: syntax error: %s near %s", p.lineno, msg, p.lastWord))) +} + +// nextList parses and returns a comma-separated list of names. +func (p *rulesParser) nextList() (list []string, token string) { + for { + tok := p.nextToken() + switch tok { + case "": + if len(list) == 0 { + return nil, "" + } + fallthrough + case ",", "<", "!<", ";": + p.syntaxError("bad list syntax") + } + list = append(list, tok) + + tok = p.nextToken() + if tok != "," { + return list, tok + } + } +} + +// nextToken returns the next token in the deps rules, +// one of ";" "," "<" "!<" or a name. +func (p *rulesParser) nextToken() string { + for { + if p.text == "" { + return "" + } + switch p.text[0] { + case ';', ',', '<': + t := p.text[:1] + p.text = p.text[1:] + return t + + case '!': + if len(p.text) < 2 || p.text[1] != '<' { + p.syntaxError("unexpected token !") + } + p.text = p.text[2:] + return "!<" + + case '#': + i := strings.Index(p.text, "\n") + if i < 0 { + i = len(p.text) + } + p.text = p.text[i:] + continue + + case '\n': + p.lineno++ + fallthrough + case ' ', '\t': + p.text = p.text[1:] + continue + + default: + i := strings.IndexAny(p.text, "!;,<#\n \t") + if i < 0 { + i = len(p.text) + } + t := p.text[:i] + p.text = p.text[i:] + p.lastWord = t + return t + } + } +} |