| |
| |
| |
|
|
| package unify |
|
|
| import ( |
| "fmt" |
| "io" |
| "strings" |
|
|
| "gopkg.in/yaml.v3" |
| ) |
|
|
| |
| |
| const debugDotInHTML = false |
|
|
| var Debug struct { |
| |
| UnifyLog io.Writer |
|
|
| |
| HTML io.Writer |
| } |
|
|
| type tracer struct { |
| logw io.Writer |
|
|
| enc yamlEncoder |
|
|
| saveTree bool |
|
|
| path []string |
|
|
| node *traceTree |
| trees []*traceTree |
| } |
|
|
| type traceTree struct { |
| label string |
| v, w *Value |
| envIn envSet |
| res *Value |
| env envSet |
| err error |
|
|
| parent *traceTree |
| children []*traceTree |
| } |
|
|
| type tracerExit struct { |
| t *tracer |
| len int |
| node *traceTree |
| } |
|
|
| func (t *tracer) enter(pat string, vals ...any) tracerExit { |
| if t == nil { |
| return tracerExit{} |
| } |
|
|
| label := fmt.Sprintf(pat, vals...) |
|
|
| var p *traceTree |
| if t.saveTree { |
| p = t.node |
| if p != nil { |
| t.node = &traceTree{label: label, parent: p} |
| p.children = append(p.children, t.node) |
| } |
| } |
|
|
| t.path = append(t.path, label) |
| return tracerExit{t, len(t.path) - 1, p} |
| } |
|
|
| func (t *tracer) enterVar(id *ident, branch int) tracerExit { |
| if t == nil { |
| return tracerExit{} |
| } |
|
|
| |
| return t.enter("Var %s br %d", t.enc.idp.unique(id), branch) |
| } |
|
|
| func (te tracerExit) exit() { |
| if te.t == nil { |
| return |
| } |
| te.t.path = te.t.path[:te.len] |
| te.t.node = te.node |
| } |
|
|
| func indentf(prefix string, pat string, vals ...any) string { |
| s := fmt.Sprintf(pat, vals...) |
| if len(prefix) == 0 { |
| return s |
| } |
| if !strings.Contains(s, "\n") { |
| return prefix + s |
| } |
|
|
| indent := prefix |
| if strings.TrimLeft(prefix, " ") != "" { |
| |
| indent = strings.Repeat(" ", len(prefix)) |
| } |
| return prefix + strings.ReplaceAll(s, "\n", "\n"+indent) |
| } |
|
|
| func yamlf(prefix string, node *yaml.Node) string { |
| b, err := yaml.Marshal(node) |
| if err != nil { |
| return fmt.Sprintf("<marshal failed: %s>", err) |
| } |
| return strings.TrimRight(indentf(prefix, "%s", b), " \n") |
| } |
|
|
| func (t *tracer) logf(pat string, vals ...any) { |
| if t == nil || t.logw == nil { |
| return |
| } |
| prefix := fmt.Sprintf("[%s] ", strings.Join(t.path, "/")) |
| s := indentf(prefix, pat, vals...) |
| s = strings.TrimRight(s, " \n") |
| fmt.Fprintf(t.logw, "%s\n", s) |
| } |
|
|
| func (t *tracer) traceUnify(v, w *Value, e envSet) { |
| if t == nil { |
| return |
| } |
|
|
| t.enc.e = e |
| t.logf("Unify\n%s\nwith\n%s\nin\n%s", |
| yamlf(" ", t.enc.value(v)), |
| yamlf(" ", t.enc.value(w)), |
| yamlf(" ", t.enc.env(e))) |
| t.enc.e = envSet{} |
|
|
| if t.saveTree { |
| if t.node == nil { |
| t.node = &traceTree{} |
| t.trees = append(t.trees, t.node) |
| } |
| t.node.v, t.node.w, t.node.envIn = v, w, e |
| } |
| } |
|
|
| func (t *tracer) traceDone(res *Value, e envSet, err error) { |
| if t == nil { |
| return |
| } |
|
|
| if err != nil { |
| t.logf("==> %s", err) |
| } else { |
| t.logf("==>\n%s", yamlf(" ", t.enc.closure(Closure{res, e}))) |
| } |
|
|
| if t.saveTree { |
| node := t.node |
| if node == nil { |
| panic("popped top of trace stack") |
| } |
| node.res, node.err = res, err |
| node.env = e |
| } |
| } |
|
|