1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package adt
16
17 import (
18 "bytes"
19 "fmt"
20 "html/template"
21 "io"
22 "log"
23 "os"
24 "os/exec"
25 "path/filepath"
26 "runtime"
27 "strings"
28 )
29
30
31
32 func RecordDebugGraph(ctx *OpContext, v *Vertex, name string) {
33 graph, hasError := CreateMermaidGraph(ctx, v, true)
34 if hasError {
35 if ctx.ErrorGraphs == nil {
36 ctx.ErrorGraphs = map[string]string{}
37 }
38 path := ctx.PathToString(v.Path())
39 ctx.ErrorGraphs[path] = graph
40 }
41 }
42
43
44
45 func OpenNodeGraph(title, path, code, out, graph string) {
46 err := os.MkdirAll(path, 0755)
47 if err != nil {
48 log.Fatal(err)
49 }
50 url := filepath.Join(path, "graph.html")
51
52 w, err := os.Create(url)
53 if err != nil {
54 log.Fatal(err)
55 }
56 defer w.Close()
57
58 data := struct {
59 Title string
60 Code string
61 Out string
62 Graph string
63 }{
64 Title: title,
65 Code: code,
66 Out: out,
67 Graph: graph,
68 }
69
70 tmpl := template.Must(template.New("").Parse(`
71 <!DOCTYPE html>
72 <html>
73 <head>
74 <title>{{.Title}}</title>
75 <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
76 <script>mermaid.initialize({startOnLoad:true});</script>
77 <style>
78 .container {
79 display: flex;
80 flex-direction: column;
81 align-items: stretch;
82 }
83 .row {
84 display: flex;
85 flex-direction: row;
86 }
87 // ...
88 </style>
89 </head>
90 <body>
91 <div class="mermaid">{{.Graph}}</div>
92 <div class="row">
93 <div class="column">
94 <h1><b>Input</b></h1>
95 <pre>{{.Code}}</pre>
96 </div>
97 <div class="column">
98 <h1><b>Output</b></h1>
99 <pre>{{.Out}}</pre>
100 </div>
101 </div>
102 </body>
103 </html>
104 `))
105
106 err = tmpl.Execute(w, data)
107 if err != nil {
108 log.Fatal(err)
109 }
110
111 openBrowser(url)
112 }
113
114
115
116
117
118 func openDebugGraph(ctx *OpContext, v *Vertex, name string) {
119 graph, _ := CreateMermaidGraph(ctx, v, true)
120 path := filepath.Join(".debug", "TestX", name)
121 OpenNodeGraph(name, path, "in", "out", graph)
122 }
123
124
125
126
127
128 type depKind int
129
130 const (
131
132
133 PARENT depKind = iota + 1
134
135
136
137 ARC
138
139
140 NOTIFY
141
142
143 TASK
144
145
146
147
148
149
150 EVAL
151
152
153
154 ROOT
155
156
157
158
159 INIT
160
161
162 DEFER
163
164
165 TEST
166 )
167
168 func (k depKind) String() string {
169 switch k {
170 case PARENT:
171 return "PARENT"
172 case ARC:
173 return "ARC"
174 case NOTIFY:
175 return "NOTIFY"
176 case TASK:
177 return "TASK"
178 case EVAL:
179 return "EVAL"
180 case ROOT:
181 return "ROOT"
182
183 case INIT:
184 return "INIT"
185 case DEFER:
186 return "DEFER"
187 case TEST:
188 return "TEST"
189 }
190 panic("unreachable")
191 }
192
193
194
195
196 type ccDep struct {
197 dependency *closeContext
198 kind depKind
199 decremented bool
200
201
202 task *task
203
204 taskID int
205 }
206
207
208
209
210
211
212 var DebugDeps = false
213
214 func (c *closeContext) addDependent(kind depKind, dependant *closeContext) *ccDep {
215 if !DebugDeps {
216 return nil
217 }
218
219 if dependant == nil {
220 dependant = c
221 }
222
223 if Verbosity > 1 {
224 var state *nodeContext
225 if c.src != nil && c.src.state != nil {
226 state = c.src.state
227 } else if dependant != nil && dependant.src != nil && dependant.src.state != nil {
228 state = dependant.src.state
229 }
230 if state != nil {
231 state.Logf("INC(%s, %d) %v; %p (parent: %p) <= %p\n", kind, c.conjunctCount, c.Label(), c, c.parent, dependant)
232 } else {
233 log.Printf("INC(%s) %v %p parent: %p %d\n", kind, c.Label(), c, c.parent, c.conjunctCount)
234 }
235 }
236
237 dep := &ccDep{kind: kind, dependency: dependant}
238 c.dependencies = append(c.dependencies, dep)
239
240 return dep
241 }
242
243
244 func (c *closeContext) matchDecrement(v *Vertex, kind depKind, dependant *closeContext) {
245 if !DebugDeps {
246 return
247 }
248
249 if dependant == nil {
250 dependant = c
251 }
252
253 if Verbosity > 1 {
254 if v.state != nil {
255 v.state.Logf("DEC(%s) %v %p %d\n", kind, c.Label(), c, c.conjunctCount)
256 } else {
257 log.Printf("DEC(%s) %v %p %d\n", kind, c.Label(), c, c.conjunctCount)
258 }
259 }
260
261 for _, d := range c.dependencies {
262 if d.kind != kind {
263 continue
264 }
265 if d.dependency != dependant {
266 continue
267 }
268
269 if d.decremented {
270
271 continue
272 }
273
274 d.decremented = true
275 return
276 }
277
278 panic(fmt.Sprintf("unmatched decrement: %s", kind))
279 }
280
281
282 type mermaidContext struct {
283 ctx *OpContext
284 v *Vertex
285
286 all bool
287
288 hasError bool
289
290
291
292 roots map[*closeContext]*mermaidVertex
293
294
295
296 processed map[*closeContext]bool
297
298
299
300 inConjuncts map[*closeContext]bool
301
302
303 ccID map[*closeContext]string
304
305 w io.Writer
306
307
308
309 vertices []*mermaidVertex
310 }
311
312 type mermaidVertex struct {
313 f Feature
314 w *bytes.Buffer
315 tasks *bytes.Buffer
316 intra *bytes.Buffer
317 }
318
319
320
321 func CreateMermaidGraph(ctx *OpContext, v *Vertex, all bool) (graph string, hasError bool) {
322 if !DebugDeps {
323 return "", false
324 }
325
326 buf := &strings.Builder{}
327
328 m := &mermaidContext{
329 ctx: ctx,
330 v: v,
331 roots: map[*closeContext]*mermaidVertex{},
332 processed: map[*closeContext]bool{},
333 inConjuncts: map[*closeContext]bool{},
334 ccID: map[*closeContext]string{},
335 w: buf,
336 all: all,
337 }
338
339 io.WriteString(m.w, "graph TD\n")
340 io.WriteString(m.w, " classDef err fill:#e01010,stroke:#000000,stroke-width:3,font-size:medium\n")
341
342 indent(m.w, 1)
343 fmt.Fprintf(m.w, "style %s stroke-width:5\n\n", m.vertexID(v))
344
345
346 m.vertex(v)
347
348
349 for i, v := range m.vertices {
350 v.closeVertex()
351 if i == 0 || len(m.ccID) > 0 {
352 m.w.Write(v.w.Bytes())
353 }
354 }
355
356 return buf.String(), m.hasError
357 }
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394 func (m *mermaidContext) vertex(v *Vertex) *mermaidVertex {
395 root := v.rootCloseContext()
396
397 vc := m.roots[root]
398 if vc != nil {
399 return vc
400 }
401
402 vc = &mermaidVertex{
403 f: v.Label,
404 w: &bytes.Buffer{},
405 intra: &bytes.Buffer{},
406 }
407 m.vertices = append(m.vertices, vc)
408
409 m.tagReferencedConjuncts(v.Conjuncts)
410
411 m.roots[root] = vc
412 w := vc.w
413
414 var status string
415 switch {
416 case v.status == finalized:
417 status = "finalized"
418 case v.state == nil:
419 status = "ready"
420 default:
421 status = v.state.scheduler.state.String()
422 }
423 path := m.vertexPath(v)
424 if v.ArcType != ArcMember {
425 path += fmt.Sprintf("/%v", v.ArcType)
426 }
427
428 indentOnNewline(w, 1)
429 fmt.Fprintf(w, "subgraph %s[%s: %s]\n", m.vertexID(v), path, status)
430
431 m.cc(root)
432
433 return vc
434 }
435
436 func (m *mermaidContext) tagReferencedConjuncts(a []Conjunct) {
437 for _, c := range a {
438 m.inConjuncts[c.CloseInfo.cc] = true
439
440 if g, ok := c.x.(*ConjunctGroup); ok {
441 m.tagReferencedConjuncts([]Conjunct(*g))
442 }
443 }
444 }
445
446 func (v *mermaidVertex) closeVertex() {
447 w := v.w
448
449 if v.tasks != nil {
450 indent(v.tasks, 2)
451 fmt.Fprintf(v.tasks, "end\n")
452 w.Write(v.tasks.Bytes())
453 }
454
455
456
457 indent(w, 1)
458 fmt.Fprintf(w, "end\n")
459 }
460
461 func (m *mermaidContext) task(d *ccDep) string {
462 v := d.dependency.src
463
464
465 vc := m.vertex(v)
466
467 if vc.tasks == nil {
468 vc.tasks = &bytes.Buffer{}
469 indentOnNewline(vc.tasks, 2)
470 fmt.Fprintf(vc.tasks, "subgraph %s_tasks[tasks]\n", m.vertexID(v))
471 }
472
473 if v != d.task.node.node {
474 panic("inconsistent task")
475 }
476 taskID := fmt.Sprintf("%s_%d", m.vertexID(v), d.taskID)
477 var state string
478 var completes condition
479 var kind string
480 if d.task != nil {
481 state = d.task.state.String()[:2]
482 completes = d.task.completes
483 kind = d.task.run.name
484 }
485 indentOnNewline(vc.tasks, 3)
486 fmt.Fprintf(vc.tasks, "%s(%d", taskID, d.taskID)
487 indentOnNewline(vc.tasks, 4)
488 io.WriteString(vc.tasks, state)
489 indentOnNewline(vc.tasks, 4)
490 io.WriteString(vc.tasks, kind)
491 indentOnNewline(vc.tasks, 4)
492 fmt.Fprintf(vc.tasks, "%x)\n", completes)
493
494 if s := d.task.blockedOn; s != nil {
495 m.vertex(s.node.node)
496 fmt.Fprintf(m.w, "%s_tasks == BLOCKED ==> %s\n", m.vertexID(s.node.node), taskID)
497 }
498
499 return taskID
500 }
501
502 func (m *mermaidContext) cc(cc *closeContext) {
503 if m.processed[cc] {
504 return
505 }
506 m.processed[cc] = true
507
508
509 v := m.vertex(cc.src)
510
511
512 global := m.w
513 node := v.w
514
515 for _, d := range cc.dependencies {
516 indentLevel := 2
517 var w io.Writer
518 var name, link string
519
520 switch {
521 case !d.decremented:
522 link = fmt.Sprintf(`--%s-->`, d.kind.String())
523 case m.all:
524 link = fmt.Sprintf("-. %s .->", d.kind.String()[0:1])
525 default:
526 continue
527 }
528
529
530 switch d.kind {
531 case PARENT:
532 w = node
533 name = m.pstr(d.dependency)
534 case EVAL:
535 if cc.Label().IsLet() {
536
537
538
539 return
540 }
541 fallthrough
542 case ARC, NOTIFY:
543 w = global
544 indentLevel = 1
545 name = m.pstr(d.dependency)
546
547 case TASK:
548 w = node
549 taskID := m.task(d)
550 name = fmt.Sprintf("%s((%d))", taskID, d.taskID)
551 case ROOT, INIT:
552 w = node
553 src := cc.src
554 if v.f != src.Label {
555 panic("incompatible labels")
556 }
557 name = fmt.Sprintf("root_%s", m.vertexID(src))
558 }
559
560 if w != nil {
561 dst := m.pstr(cc)
562 indent(w, indentLevel)
563 fmt.Fprintf(w, "%s %s %s\n", name, link, dst)
564 }
565
566
567
568
569
570
571 if dep := d.dependency; dep != nil && dep != cc {
572 m.cc(dep)
573 }
574 }
575 }
576
577 func (m *mermaidContext) vertexPath(v *Vertex) string {
578 path := m.ctx.PathToString(v.Path())
579 if path == "" {
580 return "_"
581 }
582 return path
583 }
584
585 const sigPtrLen = 6
586
587 func (m *mermaidContext) vertexID(v *Vertex) string {
588 s := fmt.Sprintf("%p", v)
589 return "v" + s[len(s)-sigPtrLen:]
590 }
591
592 func (m *mermaidContext) pstr(cc *closeContext) string {
593 if id, ok := m.ccID[cc]; ok {
594 return id
595 }
596
597 ptr := fmt.Sprintf("%p", cc)
598 ptr = ptr[len(ptr)-sigPtrLen:]
599 id := fmt.Sprintf("cc%s", ptr)
600 m.ccID[cc] = id
601
602 v := m.vertex(cc.src)
603
604 w := v.w
605
606 indent(w, 2)
607 w.WriteString(id)
608
609 var open, close = "((", "))"
610 if m.inConjuncts[cc] {
611 open, close = "(((", ")))"
612 }
613
614 w.WriteString(open)
615 w.WriteString("cc")
616 if cc.conjunctCount > 0 {
617 fmt.Fprintf(w, " c:%d", cc.conjunctCount)
618 }
619 indentOnNewline(w, 3)
620 w.WriteString(ptr)
621
622 flags := &bytes.Buffer{}
623 addFlag := func(test bool, flag byte) {
624 if test {
625 flags.WriteByte(flag)
626 }
627 }
628 addFlag(cc.isDef, '#')
629 addFlag(cc.isEmbed, 'E')
630 addFlag(cc.isClosed, 'c')
631 addFlag(cc.isClosedOnce, 'C')
632 addFlag(cc.hasEllipsis, 'o')
633 io.Copy(w, flags)
634
635 w.WriteString(close)
636
637 if cc.conjunctCount > 0 {
638 fmt.Fprintf(w, ":::err")
639 if cc.src == m.v {
640 m.hasError = true
641 }
642 }
643
644 w.WriteString("\n")
645
646 return id
647 }
648
649 func indentOnNewline(w io.Writer, level int) {
650 w.Write([]byte{'\n'})
651 indent(w, level)
652 }
653
654 func indent(w io.Writer, level int) {
655 for i := 0; i < level; i++ {
656 io.WriteString(w, " ")
657 }
658 }
659
660
661 func openBrowser(url string) {
662 var cmd *exec.Cmd
663
664 switch runtime.GOOS {
665 case "windows":
666 cmd = exec.Command("cmd", "/c", "start", url)
667 case "darwin":
668 cmd = exec.Command("open", url)
669 default:
670 cmd = exec.Command("xdg-open", url)
671 }
672
673 err := cmd.Start()
674 if err != nil {
675 log.Fatal(err)
676 }
677 go cmd.Wait()
678 }
679
View as plain text