1// Copyright 2020 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package debug prints a given ADT node.
16//
17// Note that the result is not valid CUE, but instead prints the internals
18// of an ADT node in human-readable form. It uses a simple indentation algorithm
19// for improved readability and diffing.
20package debug
21
22import (
23 "fmt"
24 "slices"
25 "strconv"
26 "strings"
27
28 "cuelang.org/go/cue/errors"
29 "cuelang.org/go/cue/literal"
30 "cuelang.org/go/internal/core/adt"
31)
32
33const (
34 openTuple = "\u3008"
35 closeTuple = "\u3009"
36)
37
38type Config struct {
39 Cwd string
40 Compact bool
41 Raw bool
42
43 // ExpandLetExpr causes the expression of let reference to be printed.
44 // Note that this may result in large outputs. Use with care.
45 // Only applies if Compact is false.
46 ExpandLetExpr bool
47}
48
49// AppendNode writes a string representation of the node to w.
50func AppendNode(dst []byte, i adt.StringIndexer, n adt.Node, config *Config) []byte {
51 if config == nil {
52 config = &Config{}
53 }
54 p := printer{dst: dst, index: i, cfg: config, compact: config.Compact}
55 p.node(n)
56 return p.dst
57}
58
59// NodeString returns a string representation of the given node.
60// The StringIndexer value i is used to translate elements of n to strings.
61// Commonly available implementations of StringIndexer include *adt.OpContext
62// and *runtime.Runtime.
63func NodeString(i adt.StringIndexer, n adt.Node, config *Config) string {
64 var buf [128]byte
65 return string(AppendNode(buf[:0], i, n, config))
66}
67
68type printer struct {
69 dst []byte
70 index adt.StringIndexer
71 indent string
72 cfg *Config
73 compact bool // copied from config.Compact
74
75 // keep track of vertices to avoid cycles.
76 stack []*adt.Vertex
77
78 // modes:
79 // - show vertex
80 // - show original conjuncts
81 // - show unevaluated
82 // - auto
83}
84
85// ReplaceArg implements the format.Printer interface. It wraps Vertex arguments
86// with a formatter value, that holds a pointer to w. This allows the stack
87// of processed vertices to be passed down, which in turn is used for cycle
88// detection.
89func (w *printer) ReplaceArg(arg any) (replacement any, replaced bool) {
90 var x adt.Node
91 var r adt.Runtime
92 switch v := arg.(type) {
93 case adt.Node:
94 x = v
95 case adt.Formatter:
96 x = v.X
97 r = v.R
98 case errors.Error:
99 // Wrap errors to ensure they are formatted with our printer,
100 // which enables cycle detection in nested error formatting.
101 return errorFormatter{p: w, err: v}, true
102 }
103
104 switch x := x.(type) {
105 default:
106 return arg, false
107 case *adt.Vertex:
108 // We replace the formatter (or node) with our own formatter that is
109 // capable of detecting cycles.
110 return formatter{p: w, x: x, r: r}, true
111 }
112}
113
114type formatter struct {
115 p *printer
116 x adt.Node
117 r adt.Runtime ``
118}
119
120func (f formatter) String() string {
121 p := printer{
122 dst: make([]byte, 0, 128),
123 index: f.r,
124 cfg: f.p.cfg,
125 compact: true, // Always compact for error arguments.
126 stack: f.p.stack,
127 }
128 p.node(f.x)
129 return string(p.dst)
130}
131
132// errorFormatter wraps an error to ensure it is formatted with a printer
133// that supports cycle detection.
134type errorFormatter struct {
135 p *printer
136 err errors.Error
137}
138
139func (f errorFormatter) String() string {
140 cfg := &errors.Config{Printer: f.p}
141 return errors.StringWithConfig(f.err, cfg)
142}
143
144func (w *printer) string(s string) {
145 if !w.compact && len(w.indent) > 0 {
146 s = strings.Replace(s, "\n", "\n"+w.indent, -1)
147 }
148 w.dst = append(w.dst, s...)
149}
150
151func (w *printer) int(i int64) {
152 w.dst = strconv.AppendInt(w.dst, i, 10)
153}
154
155func (w *printer) label(f adt.Feature) {
156 switch {
157 case f.IsHidden():
158 ident := f.IdentString(w.index)
159 if pkgName := f.PkgID(w.index); pkgName != "_" {
160 ident = fmt.Sprintf("%s(%s)", ident, pkgName)
161 }
162 w.string(ident)
163
164 case f.IsLet():
165 ident := f.RawString(w.index)
166 ident = strings.Replace(ident, "\x00", "#", 1)
167 w.string(ident)
168
169 default:
170 w.string(f.SelectorString(w.index))
171 }
172}
173
174func (w *printer) ident(f adt.Feature) {
175 w.string(f.IdentString(w.index))
176}
177
178func (w *printer) path(v *adt.Vertex) {
179 if p := v.Parent; p != nil && p.Label != 0 {
180 w.path(v.Parent)
181 w.string(".")
182 }
183 w.label(v.Label)
184}
185
186func (w *printer) shared(v *adt.Vertex) {
187 w.string("~(")
188 w.path(v)
189 w.string(")")
190}
191
192// printShared prints a reference to a structure-shared node that is a value
193// of v, if it is a shared node. It reports the dereferenced node and whether
194// the node was printed.
195func (w *printer) printShared(v0 *adt.Vertex) (x *adt.Vertex, ok bool) {
196 // Handle cyclic shared nodes differently. If a shared node was part of
197 // a disjunction, it will still be wrapped in a disjunct Vertex.
198 // Similarly, a shared node should never point to a disjunct directly,
199 // but rather to the original arc that subsequently points to a
200 // disjunct.
201 v0 = v0.DerefDisjunct()
202 s, ok := v0.BaseValue.(*adt.Vertex)
203 v1 := v0.DerefValue()
204 useReference := v0.IsShared && !v1.Internal()
205 // NOTE(debug): use this line instead of the following to expand shared
206 // cases where it is safe to do so.
207 // if useReference && isCyclic && ok && len(v.Arcs) > 0 {
208 if useReference && ok && len(v1.Arcs) > 0 {
209 w.shared(v1)
210 return v1, true
211 }
212 if !w.pushVertex(v1) {
213 if s != nil {
214 w.shared(s)
215 w.string(" =>")
216 }
217 w.shared(v1)
218 return v1, true
219 }
220 return v1, false
221}
222
223func (w *printer) pushVertex(v *adt.Vertex) bool {
224 if slices.Contains(w.stack, v) {
225 w.string("value at path '")
226 w.path(v)
227 w.string("'")
228 return false
229 }
230 w.stack = append(w.stack, v)
231 return true
232}
233
234func (w *printer) popVertex() {
235 w.stack = w.stack[:len(w.stack)-1]
236}
237
238// TODO: always print path? We allow a choice for keeping the error diff at a
239// minimum.
240func (w *printer) shortError(errs errors.Error, omitPath bool) {
241 w.string(errors.StringWithConfig(errs, &errors.Config{
242 Cwd: w.cfg.Cwd,
243 ToSlash: true,
244 OmitPath: omitPath,
245 Printer: w,
246 }))
247}
248
249func (w *printer) interpolation(x *adt.Interpolation) {
250 quote := `"`
251 if x.K == adt.BytesKind {
252 quote = `'`
253 }
254 w.string(quote)
255 for i := 0; i < len(x.Parts); i += 2 {
256 switch x.K {
257 case adt.StringKind:
258 if s, ok := x.Parts[i].(*adt.String); ok {
259 w.string(s.Str)
260 } else {
261 w.string("<bad string>")
262 }
263 case adt.BytesKind:
264 if s, ok := x.Parts[i].(*adt.Bytes); ok {
265 w.dst = append(w.dst, s.B...)
266 } else {
267 w.string("<bad bytes>")
268 }
269 }
270 if i+1 < len(x.Parts) {
271 w.string(`\(`)
272 w.node(x.Parts[i+1])
273 w.string(`)`)
274 }
275 }
276 w.string(quote)
277}
278
279func (w *printer) arg(n adt.Node) {
280 if x, ok := n.(*adt.Vertex); ok {
281 if x.Label != adt.InvalidLabel {
282 w.path(x)
283 return
284 }
285 }
286 w.node(n)
287}
288
289func (w *printer) node(n adt.Node) {
290 if w.compact {
291 w.compactNode(n)
292 return
293 }
294 switch x := n.(type) {
295 case *adt.Vertex:
296 x, ok := w.printShared(x)
297 if ok {
298 return
299 }
300 defer w.popVertex()
301
302 var kind adt.Kind
303 if x.BaseValue != nil {
304 kind = x.BaseValue.Kind()
305 }
306
307 kindStr := kind.String()
308
309 // TODO: replace with showing full closedness data.
310 if x.IsClosedList() || x.IsClosedStruct() {
311 if kind == adt.ListKind || kind == adt.StructKind {
312 kindStr = "#" + kindStr
313 }
314 }
315
316 w.dst = fmt.Appendf(w.dst, "(%s){", kindStr)
317
318 saved := w.indent
319 w.indent += " "
320 defer func() { w.indent = saved }()
321
322 switch v := x.BaseValue.(type) {
323 case nil:
324 case *adt.Bottom:
325 // TODO: reuse bottom.
326 saved := w.indent
327 w.indent += "// "
328 w.string("\n")
329 w.dst = fmt.Appendf(w.dst, "[%v]", v.Code)
330 if !v.ChildError || len(x.Arcs) == 0 {
331 msg := errors.Details(v.Err, &errors.Config{
332 Cwd: w.cfg.Cwd,
333 ToSlash: true,
334 Printer: w,
335 })
336 msg = strings.TrimSpace(msg)
337 if msg != "" {
338 w.string(" ")
339 w.string(msg)
340 }
341
342 // TODO: we could consider removing CycleError here. It does
343 // seem safer, however, as sometimes structural cycles are
344 // detected as regular cycles.
345 // Alternatively, we could consider to never report arcs if
346 // there is any error.
347 if v.Code == adt.CycleError || v.Code == adt.StructuralCycleError {
348 goto endVertex
349 }
350 }
351 w.indent = saved
352
353 case *adt.StructMarker, *adt.ListMarker:
354 // if len(x.Arcs) == 0 {
355 // // w.string("}")
356 // // return
357 // }
358
359 case adt.Value:
360 if len(x.Arcs) == 0 {
361 w.string(" ")
362 w.node(v)
363 w.string(" }")
364 return
365 }
366 w.string("\n")
367 w.node(v)
368 }
369
370 for _, a := range x.Arcs {
371 if a.ArcType == adt.ArcNotPresent {
372 continue
373 }
374 if a.Label.IsLet() {
375 w.string("\n")
376 w.string("let ")
377 w.label(a.Label)
378 if a.MultiLet {
379 w.string("multi")
380 }
381 w.string(" = ")
382 if c := a.ConjunctAt(0); a.MultiLet {
383 w.node(c.Expr())
384 continue
385 }
386 w.node(a)
387 } else {
388 w.string("\n")
389 w.label(a.Label)
390 w.string(a.ArcType.Suffix())
391 w.string(": ")
392 w.node(a)
393 }
394 }
395
396 if x.BaseValue == nil {
397 w.indent += "// "
398 w.string("// ")
399 for i, c := range x.Conjuncts {
400 if c.CloseInfo.FromDef || c.CloseInfo.FromEmbed {
401 w.string("[")
402 if c.CloseInfo.FromDef {
403 w.string("d")
404 }
405 if c.CloseInfo.FromEmbed {
406 w.string("e")
407 }
408 w.string("]")
409 }
410 if i > 0 {
411 w.string(" & ")
412 }
413 w.node(c.Elem()) // TODO: also include env?
414 }
415 }
416
417 endVertex:
418
419 w.indent = saved
420 w.string("\n")
421 w.string("}")
422
423 case *adt.StructMarker:
424 w.string("struct")
425
426 case *adt.ListMarker:
427 w.string("list")
428
429 case *adt.StructLit:
430 if len(x.Decls) == 0 {
431 w.string("{}")
432 break
433 }
434 w.string("{")
435 w.indent += " "
436 for _, d := range x.Decls {
437 w.string("\n")
438 w.node(d)
439 }
440 w.indent = w.indent[:len(w.indent)-2]
441 w.string("\n}")
442
443 case *adt.ListLit:
444 if len(x.Elems) == 0 {
445 w.string("[]")
446 break
447 }
448 w.string("[")
449 w.indent += " "
450 for _, d := range x.Elems {
451 w.string("\n")
452 w.node(d)
453 w.string(",")
454 }
455 w.indent = w.indent[:len(w.indent)-2]
456 w.string("\n]")
457
458 case *adt.Field:
459 w.label(x.Label)
460 w.string(x.ArcType.Suffix())
461 w.string(":")
462 w.string(" ")
463 w.node(x.Value)
464
465 case *adt.LetField:
466 w.string("let ")
467 w.label(x.Label)
468 if x.IsMulti {
469 w.string("multi")
470 }
471 w.string(" = ")
472 w.node(x.Value)
473
474 case *adt.BulkOptionalField:
475 w.string("[")
476 w.node(x.Filter)
477 w.string("]: ")
478 w.node(x.Value)
479
480 case *adt.DynamicField:
481 w.node(x.Key)
482 w.string(x.ArcType.Suffix())
483 w.string(": ")
484 w.node(x.Value)
485
486 case *adt.Ellipsis:
487 w.string("...")
488 if x.Value != nil {
489 w.node(x.Value)
490 }
491
492 case *adt.Bottom:
493 w.string(`_|_`)
494 if x.Err != nil {
495 w.string("(")
496 w.shortError(x.Err, true)
497 w.string(")")
498 }
499
500 case *adt.Null:
501 w.string("null")
502
503 case *adt.Bool:
504 w.dst = strconv.AppendBool(w.dst, x.B)
505
506 case *adt.Num:
507 w.string(x.X.String())
508
509 case *adt.String:
510 w.dst = literal.String.Append(w.dst, x.Str)
511
512 case *adt.Bytes:
513 w.dst = literal.Bytes.Append(w.dst, string(x.B))
514
515 case *adt.Top:
516 w.string("_")
517
518 case *adt.BasicType:
519 w.string(x.K.String())
520
521 case *adt.BoundExpr:
522 w.string(x.Op.String())
523 w.node(x.Expr)
524
525 case *adt.BoundValue:
526 w.string(x.Op.String())
527 w.node(x.Value)
528
529 case *adt.NodeLink:
530 w.string(openTuple)
531 for i, f := range x.Node.Path() {
532 if i > 0 {
533 w.string(".")
534 }
535 w.label(f)
536 }
537 w.string(closeTuple)
538
539 case *adt.FieldReference:
540 w.string(openTuple)
541 w.int(int64(x.UpCount))
542 w.string(";")
543 w.label(x.Label)
544 w.string(closeTuple)
545
546 case *adt.ValueReference:
547 w.string(openTuple)
548 w.int(int64(x.UpCount))
549 w.string(closeTuple)
550
551 case *adt.LabelReference:
552 w.string(openTuple)
553 w.int(int64(x.UpCount))
554 w.string(";-")
555 w.string(closeTuple)
556
557 case *adt.DynamicReference:
558 w.string(openTuple)
559 w.int(int64(x.UpCount))
560 w.string(";(")
561 w.node(x.Label)
562 w.string(")")
563 w.string(closeTuple)
564
565 case *adt.ImportReference:
566 w.string(openTuple + "import;")
567 w.label(x.ImportPath)
568 w.string(closeTuple)
569
570 case *adt.LetReference:
571 w.string(openTuple)
572 w.int(int64(x.UpCount))
573 w.string(";let ")
574 w.label(x.Label)
575 w.string(closeTuple)
576 if w.cfg.ExpandLetExpr {
577 w.string("=>")
578 w.node(x.X)
579 }
580
581 case *adt.SelectorExpr:
582 w.node(x.X)
583 w.string(".")
584 w.label(x.Sel)
585
586 case *adt.IndexExpr:
587 w.node(x.X)
588 w.string("[")
589 w.node(x.Index)
590 w.string("]")
591
592 case *adt.SliceExpr:
593 w.node(x.X)
594 w.string("[")
595 if x.Lo != nil {
596 w.node(x.Lo)
597 }
598 w.string(":")
599 if x.Hi != nil {
600 w.node(x.Hi)
601 }
602 if x.Stride != nil {
603 w.string(":")
604 w.node(x.Stride)
605 }
606 w.string("]")
607
608 case *adt.Interpolation:
609 w.interpolation(x)
610
611 case *adt.UnaryExpr:
612 w.string(x.Op.String())
613 w.node(x.X)
614
615 case *adt.BinaryExpr:
616 w.string("(")
617 w.node(x.X)
618 w.string(" ")
619 w.string(x.Op.String())
620 w.string(" ")
621 w.node(x.Y)
622 w.string(")")
623
624 case *adt.OpenExpr:
625 w.node(x.X)
626 w.string("...")
627
628 case *adt.CallExpr:
629 w.node(x.Fun)
630 w.string("(")
631 for i, a := range x.Args {
632 if i > 0 {
633 w.string(", ")
634 }
635 w.arg(a)
636 }
637 w.string(")")
638
639 case *adt.Builtin:
640 if x.Package != 0 {
641 w.label(x.Package)
642 w.string(".")
643 }
644 w.string(x.Name)
645
646 case *adt.BuiltinValidator:
647 w.node(x.Builtin)
648 w.string("(")
649 for i, a := range x.Args {
650 if i > 0 {
651 w.string(", ")
652 }
653 w.arg(a)
654 }
655 w.string(")")
656
657 case *adt.DisjunctionExpr:
658 w.string("(")
659 for i, a := range x.Values {
660 if i > 0 {
661 w.string("|")
662 }
663 // Disjunct
664 if a.Default {
665 w.string("*")
666 }
667 w.node(a.Val)
668 }
669 w.string(")")
670
671 case *adt.Conjunction:
672 w.string("&(")
673 for i, c := range x.Values {
674 if i > 0 {
675 w.string(", ")
676 }
677 w.node(c)
678 }
679 w.string(")")
680
681 case *adt.ConjunctGroup:
682 w.string("&[")
683 for i, c := range *x {
684 if i > 0 {
685 w.string(", ")
686 }
687 w.node(c.Expr())
688 }
689 w.string("]")
690
691 case *adt.Disjunction:
692 w.string("|(")
693 for i, c := range x.Values {
694 if i > 0 {
695 w.string(", ")
696 }
697 if i < x.NumDefaults {
698 w.string("*")
699 }
700 w.node(c)
701 }
702 w.string(")")
703
704 case *adt.Comprehension:
705 for _, c := range x.Clauses {
706 w.node(c)
707 }
708 w.node(adt.ToExpr(x.Value))
709
710 case *adt.ForClause:
711 w.string("for ")
712 w.ident(x.Key)
713 w.string(", ")
714 w.ident(x.Value)
715 w.string(" in ")
716 w.node(x.Src)
717 w.string(" ")
718
719 case *adt.IfClause:
720 w.string("if ")
721 w.node(x.Condition)
722 w.string(" ")
723
724 case *adt.LetClause:
725 w.string("let ")
726 w.ident(x.Label)
727 w.string(" = ")
728 w.node(x.Expr)
729 w.string(" ")
730
731 case *adt.ValueClause:
732
733 default:
734 panic(fmt.Sprintf("unknown type %T", x))
735 }
736}