this repo has no description
at master 618 lines 13 kB view raw
1// Copyright 2021 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 15package astinternal 16 17import ( 18 "fmt" 19 gotoken "go/token" 20 "reflect" 21 "strconv" 22 "strings" 23 24 "cuelang.org/go/cue/ast" 25 "cuelang.org/go/cue/token" 26) 27 28// AppendDebug writes a multi-line Go-like representation of a syntax tree node, 29// including node position information and any relevant Go types. 30func AppendDebug(dst []byte, node ast.Node, config DebugConfig) []byte { 31 d := &debugPrinter{ 32 cfg: config, 33 buf: dst, 34 } 35 if config.IncludeNodeRefs { 36 d.nodeRefs = make(map[ast.Node]int) 37 d.addNodeRefs(reflect.ValueOf(node)) 38 } 39 if d.value(reflect.ValueOf(node), nil) { 40 d.newline() 41 } 42 return d.buf 43} 44 45// DebugConfig configures the behavior of [AppendDebug]. 46type DebugConfig struct { 47 // Filter is called before each value in a syntax tree. 48 // Values for which the function returns false are omitted. 49 Filter func(reflect.Value) bool 50 51 // OmitEmpty causes empty strings, empty structs, empty lists, 52 // nil pointers, invalid positions, and missing tokens to be omitted. 53 OmitEmpty bool 54 55 // IncludeNodeRefs causes a Node reference in an identifier 56 // to indicate which (if any) ast.Node it refers to. 57 IncludeNodeRefs bool 58 59 // IncludePointers causes all nodes to be printed with their pointer 60 // values; setting this also implies [DebugConfig.IncludeNodeRefs] 61 // and references will be printed as pointers. 62 IncludePointers bool 63 64 // AllPositions causes all [ast.Node] implementions to emit their start 65 // and end positions. 66 AllPositions bool 67} 68 69type debugPrinter struct { 70 buf []byte 71 cfg DebugConfig 72 level int 73 nodeRefs map[ast.Node]int 74 refID int 75} 76 77// value produces the given value, omitting type information if 78// its type is the same as implied type. It reports whether 79// anything was produced. 80func (d *debugPrinter) value(v reflect.Value, impliedType reflect.Type) bool { 81 start := d.pos() 82 d.value0(v, impliedType) 83 return d.pos() > start 84} 85 86func (d *debugPrinter) value0(v reflect.Value, impliedType reflect.Type) { 87 if d.cfg.Filter != nil && !d.cfg.Filter(v) { 88 return 89 } 90 // Skip over interfaces and pointers, stopping early if nil. 91 concreteType := v.Type() 92 refName := "" 93 var startPos, endPos token.Pos 94 ptrVal := uintptr(0) 95 for { 96 k := v.Kind() 97 if k != reflect.Interface && k != reflect.Pointer { 98 break 99 } 100 if v.IsNil() { 101 if !d.cfg.OmitEmpty { 102 d.printf("nil") 103 } 104 return 105 } 106 if k == reflect.Pointer { 107 if n, ok := v.Interface().(ast.Node); ok { 108 ptrVal = v.Pointer() 109 if id, ok := d.nodeRefs[n]; ok { 110 refName = refIDToName(id) 111 } 112 startPos, endPos = n.Pos(), n.End() 113 } 114 } 115 v = v.Elem() 116 if k == reflect.Interface { 117 // For example, *ast.Ident can be the concrete type behind an ast.Expr. 118 concreteType = v.Type() 119 } 120 } 121 122 if d.cfg.OmitEmpty && v.IsZero() { 123 return 124 } 125 126 t := v.Type() 127 switch v := v.Interface().(type) { 128 // Simple types which can stringify themselves. 129 case token.Pos: 130 d.printf("%s(%q", t, v) 131 // Show relative positions too, if there are any, as they affect formatting. 132 if v.HasRelPos() { 133 d.printf(", %v", v.RelPos()) 134 } 135 d.printf(")") 136 return 137 case token.Token: 138 d.printf("%s(%q)", t, v) 139 return 140 } 141 142 switch t.Kind() { 143 default: 144 // We assume all other kinds are basic in practice, like string or bool. 145 if t.PkgPath() != "" { 146 // Mention defined and non-predeclared types, for clarity. 147 d.printf("%s(%#v)", t, v) 148 } else { 149 d.printf("%#v", v) 150 } 151 152 case reflect.Slice, reflect.Struct: 153 valueStart := d.pos() 154 // We print the concrete type when it differs from an implied type. 155 if concreteType != impliedType { 156 d.printf("%s", concreteType) 157 } 158 if d.cfg.IncludePointers { 159 if ptrVal != 0 { 160 d.printf("@%#x", ptrVal) 161 } 162 } else if refName != "" { 163 d.printf("@%s", refName) 164 } 165 if d.cfg.AllPositions && startPos.IsValid() { 166 d.printf("[%v]", positionRange(startPos, endPos)) 167 } 168 d.printf("{") 169 d.level++ 170 var anyElems bool 171 if t.Kind() == reflect.Slice { 172 anyElems = d.sliceElems(v, t.Elem()) 173 } else { 174 anyElems = d.structFields(v) 175 } 176 d.level-- 177 if !anyElems && d.cfg.OmitEmpty { 178 d.truncate(valueStart) 179 } else { 180 if anyElems { 181 d.newline() 182 } 183 d.printf("}") 184 } 185 } 186} 187 188// positionRange returns a string representing the range 189// of positions between p0 and p1, as returned 190// by [ast.Node.Pos] and [ast.Node.End] respectively. 191func positionRange(p0, p1 token.Pos) string { 192 if !p1.IsValid() { 193 return p0.String() 194 } 195 pos0, pos1 := p0.Position(), p1.Position() 196 if pos1.Filename != pos0.Filename { 197 return fmt.Sprintf("%v,%v", pos0, pos1) 198 } 199 var buf strings.Builder 200 if len(pos0.Filename) != 0 { 201 buf.WriteString(pos0.Filename) 202 buf.WriteString(":") 203 } 204 fmt.Fprintf(&buf, "%d:%d,%d:%d", pos0.Line, pos0.Column, pos1.Line, pos1.Column) 205 return buf.String() 206} 207 208func (d *debugPrinter) sliceElems(v reflect.Value, elemType reflect.Type) (anyElems bool) { 209 for i := 0; i < v.Len(); i++ { 210 ev := v.Index(i) 211 elemStart := d.pos() 212 d.newline() 213 // Note: a slice literal implies the type of its elements 214 // so we can avoid mentioning the type 215 // of each element if it matches. 216 if d.value(ev, elemType) { 217 anyElems = true 218 } else { 219 d.truncate(elemStart) 220 } 221 } 222 return anyElems 223} 224 225func (d *debugPrinter) structFields(v reflect.Value) (anyElems bool) { 226 t := v.Type() 227 for i := range v.NumField() { 228 f := t.Field(i) 229 if !gotoken.IsExported(f.Name) { 230 continue 231 } 232 if f.Name == "Node" { 233 nodeVal := v.Field(i) 234 if (!d.cfg.IncludeNodeRefs && !d.cfg.IncludePointers) || nodeVal.IsNil() { 235 continue 236 } 237 d.newline() 238 if d.cfg.IncludePointers { 239 if nodeVal.Kind() == reflect.Interface { 240 nodeVal = nodeVal.Elem() 241 } 242 d.printf("Node: @%#v (%v)", nodeVal.Pointer(), nodeVal.Elem().Type()) 243 } else { 244 d.printf("Node: @%s (%v)", refIDToName(d.nodeRefs[nodeVal.Interface().(ast.Node)]), nodeVal.Elem().Type()) 245 } 246 continue 247 } 248 switch f.Name { 249 // These fields are cyclic, and they don't represent the syntax anyway. 250 case "Scope", "Unresolved": 251 continue 252 } 253 elemStart := d.pos() 254 d.newline() 255 d.printf("%s: ", f.Name) 256 if d.value(v.Field(i), nil) { 257 anyElems = true 258 } else { 259 d.truncate(elemStart) 260 } 261 } 262 val := v.Addr().Interface() 263 if val, ok := val.(ast.Node); ok { 264 // Comments attached to a node aren't a regular field, but are still useful. 265 // The majority of nodes won't have comments, so skip them when empty. 266 if comments := ast.Comments(val); len(comments) > 0 { 267 anyElems = true 268 d.newline() 269 d.printf("Comments: ") 270 d.value(reflect.ValueOf(comments), nil) 271 } 272 } 273 return anyElems 274} 275 276func (d *debugPrinter) printf(format string, args ...any) { 277 d.buf = fmt.Appendf(d.buf, format, args...) 278} 279 280func (d *debugPrinter) newline() { 281 d.buf = fmt.Appendf(d.buf, "\n%s", strings.Repeat("\t", d.level)) 282} 283 284func (d *debugPrinter) pos() int { 285 return len(d.buf) 286} 287 288func (d *debugPrinter) truncate(pos int) { 289 d.buf = d.buf[:pos] 290} 291 292// addNodeRefs does a first pass over the value looking for 293// [ast.Ident] nodes that refer to other nodes. 294// This means when we find such a node, we can include 295// an anchor name for it 296func (d *debugPrinter) addNodeRefs(v reflect.Value) { 297 // Skip over interfaces and pointers, stopping early if nil. 298 for ; v.Kind() == reflect.Interface || v.Kind() == reflect.Pointer; v = v.Elem() { 299 if v.IsNil() { 300 return 301 } 302 } 303 304 t := v.Type() 305 switch v := v.Interface().(type) { 306 case token.Pos, token.Token: 307 // Simple types which can't contain an ast.Node. 308 return 309 case ast.Ident: 310 if v.Node != nil { 311 if _, ok := d.nodeRefs[v.Node]; !ok { 312 d.refID++ 313 d.nodeRefs[v.Node] = d.refID 314 } 315 } 316 return 317 } 318 319 switch t.Kind() { 320 case reflect.Slice: 321 for i := 0; i < v.Len(); i++ { 322 d.addNodeRefs(v.Index(i)) 323 } 324 case reflect.Struct: 325 t := v.Type() 326 for i := range v.NumField() { 327 f := t.Field(i) 328 if !gotoken.IsExported(f.Name) { 329 continue 330 } 331 switch f.Name { 332 // These fields don't point to any nodes that Node can refer to. 333 case "Scope", "Node", "Unresolved": 334 continue 335 } 336 d.addNodeRefs(v.Field(i)) 337 } 338 } 339} 340 341func refIDToName(id int) string { 342 if id == 0 { 343 return "unknown" 344 } 345 return fmt.Sprintf("ref%03d", id) 346} 347 348func DebugStr(x interface{}) (out string) { 349 if n, ok := x.(ast.Node); ok { 350 comments := "" 351 for _, g := range ast.Comments(n) { 352 comments += DebugStr(g) 353 } 354 if comments != "" { 355 defer func() { out = "<" + comments + out + ">" }() 356 } 357 } 358 switch v := x.(type) { 359 case *ast.File: 360 out := "" 361 out += DebugStr(v.Decls) 362 return out 363 364 case *ast.Package: 365 out := "package " 366 out += DebugStr(v.Name) 367 return out 368 369 case *ast.LetClause: 370 out := "let " 371 out += DebugStr(v.Ident) 372 out += "=" 373 out += DebugStr(v.Expr) 374 return out 375 376 case *ast.Alias: 377 out := DebugStr(v.Ident) 378 out += "=" 379 out += DebugStr(v.Expr) 380 return out 381 382 case *ast.BottomLit: 383 return "_|_" 384 385 case *ast.BasicLit: 386 return v.Value 387 388 case *ast.Interpolation: 389 for _, e := range v.Elts { 390 out += DebugStr(e) 391 } 392 return out 393 394 case *ast.EmbedDecl: 395 out += DebugStr(v.Expr) 396 return out 397 398 case *ast.ImportDecl: 399 out := "import " 400 if v.Lparen.IsValid() { 401 out += "( " 402 out += DebugStr(v.Specs) 403 out += " )" 404 } else { 405 out += DebugStr(v.Specs) 406 } 407 return out 408 409 case *ast.Comprehension: 410 out := DebugStr(v.Clauses) 411 out += DebugStr(v.Value) 412 return out 413 414 case *ast.StructLit: 415 out := "{" 416 out += DebugStr(v.Elts) 417 out += "}" 418 return out 419 420 case *ast.ListLit: 421 out := "[" 422 out += DebugStr(v.Elts) 423 out += "]" 424 return out 425 426 case *ast.Ellipsis: 427 out := "..." 428 if v.Type != nil { 429 out += DebugStr(v.Type) 430 } 431 return out 432 433 case *ast.ForClause: 434 out := "for " 435 if v.Key != nil { 436 out += DebugStr(v.Key) 437 out += ": " 438 } 439 out += DebugStr(v.Value) 440 out += " in " 441 out += DebugStr(v.Source) 442 return out 443 444 case *ast.IfClause: 445 out := "if " 446 out += DebugStr(v.Condition) 447 return out 448 449 case *ast.Field: 450 out := DebugStr(v.Label) 451 if v.Alias != nil { 452 out += "~" 453 if v.Alias.Label != nil { 454 // Dual form 455 out += "(" 456 out += DebugStr(v.Alias.Label) 457 out += "," 458 out += DebugStr(v.Alias.Field) 459 out += ")" 460 } else { 461 // Simple form 462 out += DebugStr(v.Alias.Field) 463 } 464 } 465 if t := v.Constraint; t != token.ILLEGAL { 466 out += t.String() 467 } 468 if v.Value != nil { 469 out += ": " 470 out += DebugStr(v.Value) 471 for _, a := range v.Attrs { 472 out += " " 473 out += DebugStr(a) 474 } 475 } 476 return out 477 478 case *ast.Attribute: 479 return v.Text 480 481 case *ast.Ident: 482 return v.Name 483 484 case *ast.SelectorExpr: 485 return DebugStr(v.X) + "." + DebugStr(v.Sel) 486 487 case *ast.CallExpr: 488 out := DebugStr(v.Fun) 489 out += "(" 490 out += DebugStr(v.Args) 491 out += ")" 492 return out 493 494 case *ast.ParenExpr: 495 out := "(" 496 out += DebugStr(v.X) 497 out += ")" 498 return out 499 500 case *ast.UnaryExpr: 501 return v.Op.String() + DebugStr(v.X) 502 503 case *ast.BinaryExpr: 504 out := DebugStr(v.X) 505 op := v.Op.String() 506 if 'a' <= op[0] && op[0] <= 'z' { 507 op = fmt.Sprintf(" %s ", op) 508 } 509 out += op 510 out += DebugStr(v.Y) 511 return out 512 513 case *ast.PostfixExpr: 514 return DebugStr(v.X) + v.Op.String() 515 516 case []*ast.CommentGroup: 517 var a []string 518 for _, c := range v { 519 a = append(a, DebugStr(c)) 520 } 521 return strings.Join(a, "\n") 522 523 case *ast.CommentGroup: 524 str := "[" 525 if v.Doc { 526 str += "d" 527 } 528 if v.Line { 529 str += "l" 530 } 531 str += strconv.Itoa(int(v.Position)) 532 var a = []string{} 533 for _, c := range v.List { 534 a = append(a, c.Text) 535 } 536 return str + strings.Join(a, " ") + "] " 537 538 case *ast.IndexExpr: 539 out := DebugStr(v.X) 540 out += "[" 541 out += DebugStr(v.Index) 542 out += "]" 543 return out 544 545 case *ast.SliceExpr: 546 out := DebugStr(v.X) 547 out += "[" 548 out += DebugStr(v.Low) 549 out += ":" 550 out += DebugStr(v.High) 551 out += "]" 552 return out 553 554 case *ast.ImportSpec: 555 out := "" 556 if v.Name != nil { 557 out += DebugStr(v.Name) 558 out += " " 559 } 560 out += DebugStr(v.Path) 561 return out 562 563 case *ast.Func: 564 return fmt.Sprintf("func(%v): %v", DebugStr(v.Args), DebugStr(v.Ret)) 565 566 case []ast.Decl: 567 if len(v) == 0 { 568 return "" 569 } 570 out := "" 571 for _, d := range v { 572 out += DebugStr(d) 573 out += sep 574 } 575 return out[:len(out)-len(sep)] 576 577 case []ast.Clause: 578 if len(v) == 0 { 579 return "" 580 } 581 out := "" 582 for _, c := range v { 583 out += DebugStr(c) 584 out += " " 585 } 586 return out 587 588 case []ast.Expr: 589 if len(v) == 0 { 590 return "" 591 } 592 out := "" 593 for _, d := range v { 594 out += DebugStr(d) 595 out += sep 596 } 597 return out[:len(out)-len(sep)] 598 599 case []*ast.ImportSpec: 600 if len(v) == 0 { 601 return "" 602 } 603 out := "" 604 for _, d := range v { 605 out += DebugStr(d) 606 out += sep 607 } 608 return out[:len(out)-len(sep)] 609 610 default: 611 if v == nil { 612 return "" 613 } 614 return fmt.Sprintf("<%T>", x) 615 } 616} 617 618const sep = ", "