| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | package dag |
| |
|
| | import ( |
| | "cmp" |
| | "fmt" |
| | "slices" |
| | "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) |
| | } |
| | slices.SortFunc(edges, func(a, b string) int { |
| | return cmp.Compare(g.byLabel[a], g.byLabel[b]) |
| | }) |
| | return edges |
| | } |
| |
|
| | |
| | |
| | |
| | func Parse(dag string) (*Graph, error) { |
| | g := newGraph() |
| | disallowed := []rule{} |
| |
|
| | rules, err := parseRules(dag) |
| | if err != nil { |
| | return nil, err |
| | } |
| |
|
| | |
| | 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) |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| | for _, tos := range g.edges { |
| | for to := range tos { |
| | if g.edges[to] == nil { |
| | errorf("missing definition for %s", to) |
| | } |
| | } |
| | } |
| |
|
| | |
| | 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 { |
| | |
| | |
| | |
| | errorf("graph cycle: %s < %s < %s", j, k, i) |
| | } |
| | g.AddEdge(i, j) |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| | 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 |
| | } |
| |
|
| | |
| | type rule struct { |
| | less []string |
| | op string |
| | def []string |
| | } |
| |
|
| | type syntaxError string |
| |
|
| | func (e syntaxError) Error() string { |
| | return string(e) |
| | } |
| |
|
| | |
| | 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 |
| | } |
| |
|
| | |
| | type rulesParser struct { |
| | lineno int |
| | lastWord string |
| | text string |
| | } |
| |
|
| | |
| | func (p *rulesParser) syntaxError(msg string) { |
| | panic(syntaxError(fmt.Sprintf("parsing graph: line %d: syntax error: %s near %s", p.lineno, msg, p.lastWord))) |
| | } |
| |
|
| | |
| | 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 |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | 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 |
| | } |
| | } |
| | } |
| |
|