github.com/bir3/gocompiler@v0.9.2202/src/go/doc/example.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Extract example functions from file ASTs. 6 7 package doc 8 9 import ( 10 "github.com/bir3/gocompiler/src/go/ast" 11 "github.com/bir3/gocompiler/src/go/token" 12 "github.com/bir3/gocompiler/src/internal/lazyregexp" 13 "path" 14 "sort" 15 "strconv" 16 "strings" 17 "unicode" 18 "unicode/utf8" 19 ) 20 21 // An Example represents an example function found in a test source file. 22 type Example struct { 23 Name string // name of the item being exemplified (including optional suffix) 24 Suffix string // example suffix, without leading '_' (only populated by NewFromFiles) 25 Doc string // example function doc string 26 Code ast.Node 27 Play *ast.File // a whole program version of the example 28 Comments []*ast.CommentGroup 29 Output string // expected output 30 Unordered bool 31 EmptyOutput bool // expect empty output 32 Order int // original source code order 33 } 34 35 // Examples returns the examples found in testFiles, sorted by Name field. 36 // The Order fields record the order in which the examples were encountered. 37 // The Suffix field is not populated when Examples is called directly, it is 38 // only populated by [NewFromFiles] for examples it finds in _test.go files. 39 // 40 // Playable Examples must be in a package whose name ends in "_test". 41 // An Example is "playable" (the Play field is non-nil) in either of these 42 // circumstances: 43 // - The example function is self-contained: the function references only 44 // identifiers from other packages (or predeclared identifiers, such as 45 // "int") and the test file does not include a dot import. 46 // - The entire test file is the example: the file contains exactly one 47 // example function, zero test, fuzz test, or benchmark function, and at 48 // least one top-level function, type, variable, or constant declaration 49 // other than the example function. 50 func Examples(testFiles ...*ast.File) []*Example { 51 var list []*Example 52 for _, file := range testFiles { 53 hasTests := false // file contains tests, fuzz test, or benchmarks 54 numDecl := 0 // number of non-import declarations in the file 55 var flist []*Example 56 for _, decl := range file.Decls { 57 if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT { 58 numDecl++ 59 continue 60 } 61 f, ok := decl.(*ast.FuncDecl) 62 if !ok || f.Recv != nil { 63 continue 64 } 65 numDecl++ 66 name := f.Name.Name 67 if isTest(name, "Test") || isTest(name, "Benchmark") || isTest(name, "Fuzz") { 68 hasTests = true 69 continue 70 } 71 if !isTest(name, "Example") { 72 continue 73 } 74 if params := f.Type.Params; len(params.List) != 0 { 75 continue // function has params; not a valid example 76 } 77 if f.Body == nil { // ast.File.Body nil dereference (see issue 28044) 78 continue 79 } 80 var doc string 81 if f.Doc != nil { 82 doc = f.Doc.Text() 83 } 84 output, unordered, hasOutput := exampleOutput(f.Body, file.Comments) 85 flist = append(flist, &Example{ 86 Name: name[len("Example"):], 87 Doc: doc, 88 Code: f.Body, 89 Play: playExample(file, f), 90 Comments: file.Comments, 91 Output: output, 92 Unordered: unordered, 93 EmptyOutput: output == "" && hasOutput, 94 Order: len(flist), 95 }) 96 } 97 if !hasTests && numDecl > 1 && len(flist) == 1 { 98 // If this file only has one example function, some 99 // other top-level declarations, and no tests or 100 // benchmarks, use the whole file as the example. 101 flist[0].Code = file 102 flist[0].Play = playExampleFile(file) 103 } 104 list = append(list, flist...) 105 } 106 // sort by name 107 sort.Slice(list, func(i, j int) bool { 108 return list[i].Name < list[j].Name 109 }) 110 return list 111 } 112 113 var outputPrefix = lazyregexp.New(`(?i)^[[:space:]]*(unordered )?output:`) 114 115 // Extracts the expected output and whether there was a valid output comment. 116 func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) { 117 if _, last := lastComment(b, comments); last != nil { 118 // test that it begins with the correct prefix 119 text := last.Text() 120 if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil { 121 if loc[2] != -1 { 122 unordered = true 123 } 124 text = text[loc[1]:] 125 // Strip zero or more spaces followed by \n or a single space. 126 text = strings.TrimLeft(text, " ") 127 if len(text) > 0 && text[0] == '\n' { 128 text = text[1:] 129 } 130 return text, unordered, true 131 } 132 } 133 return "", false, false // no suitable comment found 134 } 135 136 // isTest tells whether name looks like a test, example, fuzz test, or 137 // benchmark. It is a Test (say) if there is a character after Test that is not 138 // a lower-case letter. (We don't want Testiness.) 139 func isTest(name, prefix string) bool { 140 if !strings.HasPrefix(name, prefix) { 141 return false 142 } 143 if len(name) == len(prefix) { // "Test" is ok 144 return true 145 } 146 rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) 147 return !unicode.IsLower(rune) 148 } 149 150 // playExample synthesizes a new *ast.File based on the provided 151 // file with the provided function body as the body of main. 152 func playExample(file *ast.File, f *ast.FuncDecl) *ast.File { 153 body := f.Body 154 155 if !strings.HasSuffix(file.Name.Name, "_test") { 156 // We don't support examples that are part of the 157 // greater package (yet). 158 return nil 159 } 160 161 // Collect top-level declarations in the file. 162 topDecls := make(map[*ast.Object]ast.Decl) 163 typMethods := make(map[string][]ast.Decl) 164 165 for _, decl := range file.Decls { 166 switch d := decl.(type) { 167 case *ast.FuncDecl: 168 if d.Recv == nil { 169 topDecls[d.Name.Obj] = d 170 } else { 171 if len(d.Recv.List) == 1 { 172 t := d.Recv.List[0].Type 173 tname, _ := baseTypeName(t) 174 typMethods[tname] = append(typMethods[tname], d) 175 } 176 } 177 case *ast.GenDecl: 178 for _, spec := range d.Specs { 179 switch s := spec.(type) { 180 case *ast.TypeSpec: 181 topDecls[s.Name.Obj] = d 182 case *ast.ValueSpec: 183 for _, name := range s.Names { 184 topDecls[name.Obj] = d 185 } 186 } 187 } 188 } 189 } 190 191 // Find unresolved identifiers and uses of top-level declarations. 192 depDecls, unresolved := findDeclsAndUnresolved(body, topDecls, typMethods) 193 194 // Remove predeclared identifiers from unresolved list. 195 for n := range unresolved { 196 if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] { 197 delete(unresolved, n) 198 } 199 } 200 201 // Use unresolved identifiers to determine the imports used by this 202 // example. The heuristic assumes package names match base import 203 // paths for imports w/o renames (should be good enough most of the time). 204 var namedImports []ast.Spec 205 var blankImports []ast.Spec // _ imports 206 207 // To preserve the blank lines between groups of imports, find the 208 // start position of each group, and assign that position to all 209 // imports from that group. 210 groupStarts := findImportGroupStarts(file.Imports) 211 groupStart := func(s *ast.ImportSpec) token.Pos { 212 for i, start := range groupStarts { 213 if s.Path.ValuePos < start { 214 return groupStarts[i-1] 215 } 216 } 217 return groupStarts[len(groupStarts)-1] 218 } 219 220 for _, s := range file.Imports { 221 p, err := strconv.Unquote(s.Path.Value) 222 if err != nil { 223 continue 224 } 225 if p == "syscall/js" { 226 // We don't support examples that import syscall/js, 227 // because the package syscall/js is not available in the playground. 228 return nil 229 } 230 n := path.Base(p) 231 if s.Name != nil { 232 n = s.Name.Name 233 switch n { 234 case "_": 235 blankImports = append(blankImports, s) 236 continue 237 case ".": 238 // We can't resolve dot imports (yet). 239 return nil 240 } 241 } 242 if unresolved[n] { 243 // Copy the spec and its path to avoid modifying the original. 244 spec := *s 245 path := *s.Path 246 spec.Path = &path 247 spec.Path.ValuePos = groupStart(&spec) 248 namedImports = append(namedImports, &spec) 249 delete(unresolved, n) 250 } 251 } 252 253 // If there are other unresolved identifiers, give up because this 254 // synthesized file is not going to build. 255 if len(unresolved) > 0 { 256 return nil 257 } 258 259 // Include documentation belonging to blank imports. 260 var comments []*ast.CommentGroup 261 for _, s := range blankImports { 262 if c := s.(*ast.ImportSpec).Doc; c != nil { 263 comments = append(comments, c) 264 } 265 } 266 267 // Include comments that are inside the function body. 268 for _, c := range file.Comments { 269 if body.Pos() <= c.Pos() && c.End() <= body.End() { 270 comments = append(comments, c) 271 } 272 } 273 274 // Strip the "Output:" or "Unordered output:" comment and adjust body 275 // end position. 276 body, comments = stripOutputComment(body, comments) 277 278 // Include documentation belonging to dependent declarations. 279 for _, d := range depDecls { 280 switch d := d.(type) { 281 case *ast.GenDecl: 282 if d.Doc != nil { 283 comments = append(comments, d.Doc) 284 } 285 case *ast.FuncDecl: 286 if d.Doc != nil { 287 comments = append(comments, d.Doc) 288 } 289 } 290 } 291 292 // Synthesize import declaration. 293 importDecl := &ast.GenDecl{ 294 Tok: token.IMPORT, 295 Lparen: 1, // Need non-zero Lparen and Rparen so that printer 296 Rparen: 1, // treats this as a factored import. 297 } 298 importDecl.Specs = append(namedImports, blankImports...) 299 300 // Synthesize main function. 301 funcDecl := &ast.FuncDecl{ 302 Name: ast.NewIdent("main"), 303 Type: f.Type, 304 Body: body, 305 } 306 307 decls := make([]ast.Decl, 0, 2+len(depDecls)) 308 decls = append(decls, importDecl) 309 decls = append(decls, depDecls...) 310 decls = append(decls, funcDecl) 311 312 sort.Slice(decls, func(i, j int) bool { 313 return decls[i].Pos() < decls[j].Pos() 314 }) 315 sort.Slice(comments, func(i, j int) bool { 316 return comments[i].Pos() < comments[j].Pos() 317 }) 318 319 // Synthesize file. 320 return &ast.File{ 321 Name: ast.NewIdent("main"), 322 Decls: decls, 323 Comments: comments, 324 } 325 } 326 327 // findDeclsAndUnresolved returns all the top-level declarations mentioned in 328 // the body, and a set of unresolved symbols (those that appear in the body but 329 // have no declaration in the program). 330 // 331 // topDecls maps objects to the top-level declaration declaring them (not 332 // necessarily obj.Decl, as obj.Decl will be a Spec for GenDecls, but 333 // topDecls[obj] will be the GenDecl itself). 334 func findDeclsAndUnresolved(body ast.Node, topDecls map[*ast.Object]ast.Decl, typMethods map[string][]ast.Decl) ([]ast.Decl, map[string]bool) { 335 // This function recursively finds every top-level declaration used 336 // transitively by the body, populating usedDecls and usedObjs. Then it 337 // trims down the declarations to include only the symbols actually 338 // referenced by the body. 339 340 unresolved := make(map[string]bool) 341 var depDecls []ast.Decl 342 usedDecls := make(map[ast.Decl]bool) // set of top-level decls reachable from the body 343 usedObjs := make(map[*ast.Object]bool) // set of objects reachable from the body (each declared by a usedDecl) 344 345 var inspectFunc func(ast.Node) bool 346 inspectFunc = func(n ast.Node) bool { 347 switch e := n.(type) { 348 case *ast.Ident: 349 if e.Obj == nil && e.Name != "_" { 350 unresolved[e.Name] = true 351 } else if d := topDecls[e.Obj]; d != nil { 352 353 usedObjs[e.Obj] = true 354 if !usedDecls[d] { 355 usedDecls[d] = true 356 depDecls = append(depDecls, d) 357 } 358 } 359 return true 360 case *ast.SelectorExpr: 361 // For selector expressions, only inspect the left hand side. 362 // (For an expression like fmt.Println, only add "fmt" to the 363 // set of unresolved names, not "Println".) 364 ast.Inspect(e.X, inspectFunc) 365 return false 366 case *ast.KeyValueExpr: 367 // For key value expressions, only inspect the value 368 // as the key should be resolved by the type of the 369 // composite literal. 370 ast.Inspect(e.Value, inspectFunc) 371 return false 372 } 373 return true 374 } 375 376 inspectFieldList := func(fl *ast.FieldList) { 377 if fl != nil { 378 for _, f := range fl.List { 379 ast.Inspect(f.Type, inspectFunc) 380 } 381 } 382 } 383 384 // Find the decls immediately referenced by body. 385 ast.Inspect(body, inspectFunc) 386 // Now loop over them, adding to the list when we find a new decl that the 387 // body depends on. Keep going until we don't find anything new. 388 for i := 0; i < len(depDecls); i++ { 389 switch d := depDecls[i].(type) { 390 case *ast.FuncDecl: 391 // Inpect type parameters. 392 inspectFieldList(d.Type.TypeParams) 393 // Inspect types of parameters and results. See #28492. 394 inspectFieldList(d.Type.Params) 395 inspectFieldList(d.Type.Results) 396 397 // Functions might not have a body. See #42706. 398 if d.Body != nil { 399 ast.Inspect(d.Body, inspectFunc) 400 } 401 case *ast.GenDecl: 402 for _, spec := range d.Specs { 403 switch s := spec.(type) { 404 case *ast.TypeSpec: 405 inspectFieldList(s.TypeParams) 406 ast.Inspect(s.Type, inspectFunc) 407 depDecls = append(depDecls, typMethods[s.Name.Name]...) 408 case *ast.ValueSpec: 409 if s.Type != nil { 410 ast.Inspect(s.Type, inspectFunc) 411 } 412 for _, val := range s.Values { 413 ast.Inspect(val, inspectFunc) 414 } 415 } 416 } 417 } 418 } 419 420 // Some decls include multiple specs, such as a variable declaration with 421 // multiple variables on the same line, or a parenthesized declaration. Trim 422 // the declarations to include only the specs that are actually mentioned. 423 // However, if there is a constant group with iota, leave it all: later 424 // constant declarations in the group may have no value and so cannot stand 425 // on their own, and removing any constant from the group could change the 426 // values of subsequent ones. 427 // See testdata/examples/iota.go for a minimal example. 428 var ds []ast.Decl 429 for _, d := range depDecls { 430 switch d := d.(type) { 431 case *ast.FuncDecl: 432 ds = append(ds, d) 433 case *ast.GenDecl: 434 containsIota := false // does any spec have iota? 435 // Collect all Specs that were mentioned in the example. 436 var specs []ast.Spec 437 for _, s := range d.Specs { 438 switch s := s.(type) { 439 case *ast.TypeSpec: 440 if usedObjs[s.Name.Obj] { 441 specs = append(specs, s) 442 } 443 case *ast.ValueSpec: 444 if !containsIota { 445 containsIota = hasIota(s) 446 } 447 // A ValueSpec may have multiple names (e.g. "var a, b int"). 448 // Keep only the names that were mentioned in the example. 449 // Exception: the multiple names have a single initializer (which 450 // would be a function call with multiple return values). In that 451 // case, keep everything. 452 if len(s.Names) > 1 && len(s.Values) == 1 { 453 specs = append(specs, s) 454 continue 455 } 456 ns := *s 457 ns.Names = nil 458 ns.Values = nil 459 for i, n := range s.Names { 460 if usedObjs[n.Obj] { 461 ns.Names = append(ns.Names, n) 462 if s.Values != nil { 463 ns.Values = append(ns.Values, s.Values[i]) 464 } 465 } 466 } 467 if len(ns.Names) > 0 { 468 specs = append(specs, &ns) 469 } 470 } 471 } 472 if len(specs) > 0 { 473 // Constant with iota? Keep it all. 474 if d.Tok == token.CONST && containsIota { 475 ds = append(ds, d) 476 } else { 477 // Synthesize a GenDecl with just the Specs we need. 478 nd := *d // copy the GenDecl 479 nd.Specs = specs 480 if len(specs) == 1 { 481 // Remove grouping parens if there is only one spec. 482 nd.Lparen = 0 483 } 484 ds = append(ds, &nd) 485 } 486 } 487 } 488 } 489 return ds, unresolved 490 } 491 492 func hasIota(s ast.Spec) bool { 493 has := false 494 ast.Inspect(s, func(n ast.Node) bool { 495 // Check that this is the special built-in "iota" identifier, not 496 // a user-defined shadow. 497 if id, ok := n.(*ast.Ident); ok && id.Name == "iota" && id.Obj == nil { 498 has = true 499 return false 500 } 501 return true 502 }) 503 return has 504 } 505 506 // findImportGroupStarts finds the start positions of each sequence of import 507 // specs that are not separated by a blank line. 508 func findImportGroupStarts(imps []*ast.ImportSpec) []token.Pos { 509 startImps := findImportGroupStarts1(imps) 510 groupStarts := make([]token.Pos, len(startImps)) 511 for i, imp := range startImps { 512 groupStarts[i] = imp.Pos() 513 } 514 return groupStarts 515 } 516 517 // Helper for findImportGroupStarts to ease testing. 518 func findImportGroupStarts1(origImps []*ast.ImportSpec) []*ast.ImportSpec { 519 // Copy to avoid mutation. 520 imps := make([]*ast.ImportSpec, len(origImps)) 521 copy(imps, origImps) 522 // Assume the imports are sorted by position. 523 sort.Slice(imps, func(i, j int) bool { return imps[i].Pos() < imps[j].Pos() }) 524 // Assume gofmt has been applied, so there is a blank line between adjacent imps 525 // if and only if they are more than 2 positions apart (newline, tab). 526 var groupStarts []*ast.ImportSpec 527 prevEnd := token.Pos(-2) 528 for _, imp := range imps { 529 if imp.Pos()-prevEnd > 2 { 530 groupStarts = append(groupStarts, imp) 531 } 532 prevEnd = imp.End() 533 // Account for end-of-line comments. 534 if imp.Comment != nil { 535 prevEnd = imp.Comment.End() 536 } 537 } 538 return groupStarts 539 } 540 541 // playExampleFile takes a whole file example and synthesizes a new *ast.File 542 // such that the example is function main in package main. 543 func playExampleFile(file *ast.File) *ast.File { 544 // Strip copyright comment if present. 545 comments := file.Comments 546 if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") { 547 comments = comments[1:] 548 } 549 550 // Copy declaration slice, rewriting the ExampleX function to main. 551 var decls []ast.Decl 552 for _, d := range file.Decls { 553 if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") { 554 // Copy the FuncDecl, as it may be used elsewhere. 555 newF := *f 556 newF.Name = ast.NewIdent("main") 557 newF.Body, comments = stripOutputComment(f.Body, comments) 558 d = &newF 559 } 560 decls = append(decls, d) 561 } 562 563 // Copy the File, as it may be used elsewhere. 564 f := *file 565 f.Name = ast.NewIdent("main") 566 f.Decls = decls 567 f.Comments = comments 568 return &f 569 } 570 571 // stripOutputComment finds and removes the "Output:" or "Unordered output:" 572 // comment from body and comments, and adjusts the body block's end position. 573 func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) { 574 // Do nothing if there is no "Output:" or "Unordered output:" comment. 575 i, last := lastComment(body, comments) 576 if last == nil || !outputPrefix.MatchString(last.Text()) { 577 return body, comments 578 } 579 580 // Copy body and comments, as the originals may be used elsewhere. 581 newBody := &ast.BlockStmt{ 582 Lbrace: body.Lbrace, 583 List: body.List, 584 Rbrace: last.Pos(), 585 } 586 newComments := make([]*ast.CommentGroup, len(comments)-1) 587 copy(newComments, comments[:i]) 588 copy(newComments[i:], comments[i+1:]) 589 return newBody, newComments 590 } 591 592 // lastComment returns the last comment inside the provided block. 593 func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) { 594 if b == nil { 595 return 596 } 597 pos, end := b.Pos(), b.End() 598 for j, cg := range c { 599 if cg.Pos() < pos { 600 continue 601 } 602 if cg.End() > end { 603 break 604 } 605 i, last = j, cg 606 } 607 return 608 } 609 610 // classifyExamples classifies examples and assigns them to the Examples field 611 // of the relevant Func, Type, or Package that the example is associated with. 612 // 613 // The classification process is ambiguous in some cases: 614 // 615 // - ExampleFoo_Bar matches a type named Foo_Bar 616 // or a method named Foo.Bar. 617 // - ExampleFoo_bar matches a type named Foo_bar 618 // or Foo (with a "bar" suffix). 619 // 620 // Examples with malformed names are not associated with anything. 621 func classifyExamples(p *Package, examples []*Example) { 622 if len(examples) == 0 { 623 return 624 } 625 // Mapping of names for funcs, types, and methods to the example listing. 626 ids := make(map[string]*[]*Example) 627 ids[""] = &p.Examples // package-level examples have an empty name 628 for _, f := range p.Funcs { 629 if !token.IsExported(f.Name) { 630 continue 631 } 632 ids[f.Name] = &f.Examples 633 } 634 for _, t := range p.Types { 635 if !token.IsExported(t.Name) { 636 continue 637 } 638 ids[t.Name] = &t.Examples 639 for _, f := range t.Funcs { 640 if !token.IsExported(f.Name) { 641 continue 642 } 643 ids[f.Name] = &f.Examples 644 } 645 for _, m := range t.Methods { 646 if !token.IsExported(m.Name) { 647 continue 648 } 649 ids[strings.TrimPrefix(nameWithoutInst(m.Recv), "*")+"_"+m.Name] = &m.Examples 650 } 651 } 652 653 // Group each example with the associated func, type, or method. 654 for _, ex := range examples { 655 // Consider all possible split points for the suffix 656 // by starting at the end of string (no suffix case), 657 // then trying all positions that contain a '_' character. 658 // 659 // An association is made on the first successful match. 660 // Examples with malformed names that match nothing are skipped. 661 for i := len(ex.Name); i >= 0; i = strings.LastIndexByte(ex.Name[:i], '_') { 662 prefix, suffix, ok := splitExampleName(ex.Name, i) 663 if !ok { 664 continue 665 } 666 exs, ok := ids[prefix] 667 if !ok { 668 continue 669 } 670 ex.Suffix = suffix 671 *exs = append(*exs, ex) 672 break 673 } 674 } 675 676 // Sort list of example according to the user-specified suffix name. 677 for _, exs := range ids { 678 sort.Slice((*exs), func(i, j int) bool { 679 return (*exs)[i].Suffix < (*exs)[j].Suffix 680 }) 681 } 682 } 683 684 // nameWithoutInst returns name if name has no brackets. If name contains 685 // brackets, then it returns name with all the contents between (and including) 686 // the outermost left and right bracket removed. 687 // 688 // Adapted from debug/gosym/symtab.go:Sym.nameWithoutInst. 689 func nameWithoutInst(name string) string { 690 start := strings.Index(name, "[") 691 if start < 0 { 692 return name 693 } 694 end := strings.LastIndex(name, "]") 695 if end < 0 { 696 // Malformed name, should contain closing bracket too. 697 return name 698 } 699 return name[0:start] + name[end+1:] 700 } 701 702 // splitExampleName attempts to split example name s at index i, 703 // and reports if that produces a valid split. The suffix may be 704 // absent. Otherwise, it must start with a lower-case letter and 705 // be preceded by '_'. 706 // 707 // One of i == len(s) or s[i] == '_' must be true. 708 func splitExampleName(s string, i int) (prefix, suffix string, ok bool) { 709 if i == len(s) { 710 return s, "", true 711 } 712 if i == len(s)-1 { 713 return "", "", false 714 } 715 prefix, suffix = s[:i], s[i+1:] 716 return prefix, suffix, isExampleSuffix(suffix) 717 } 718 719 func isExampleSuffix(s string) bool { 720 r, size := utf8.DecodeRuneInString(s) 721 return size > 0 && unicode.IsLower(r) 722 }