rule.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. /* Copyright 2018 The Bazel Authors. All rights reserved.
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. // Package rule provides tools for editing Bazel build files. It is intended to
  13. // be a more powerful replacement for
  14. // github.com/bazelbuild/buildtools/build.Rule, adapted for Gazelle's usage. It
  15. // is language agnostic, but it may be used for language-specific rules by
  16. // providing configuration.
  17. //
  18. // File is the primary interface to this package. Rule and Load are used to
  19. // create, read, update, and delete rules. Once modifications are performed,
  20. // File.Sync() may be called to write the changes back to the original AST,
  21. // which may then be formatted and written back to a file.
  22. package rule
  23. import (
  24. "io/ioutil"
  25. "os"
  26. "path/filepath"
  27. "sort"
  28. "strings"
  29. bzl "github.com/bazelbuild/buildtools/build"
  30. bt "github.com/bazelbuild/buildtools/tables"
  31. )
  32. // File provides editing functionality on top of a Skylark syntax tree. This
  33. // is the primary interface Gazelle uses for reading and updating build files.
  34. // To use, create a new file with EmptyFile or wrap a syntax tree with
  35. // LoadFile. Perform edits on Loads and Rules, then call Sync() to write
  36. // changes back to the AST.
  37. type File struct {
  38. // File is the underlying build file syntax tree. Some editing operations
  39. // may modify this, but editing is not complete until Sync() is called.
  40. File *bzl.File
  41. // Pkg is the Bazel package this build file defines.
  42. Pkg string
  43. // Path is the file system path to the build file (same as File.Path).
  44. Path string
  45. // Directives is a list of configuration directives found in top-level
  46. // comments in the file. This should not be modified after the file is read.
  47. Directives []Directive
  48. // Loads is a list of load statements within the file. This should not
  49. // be modified directly; use Load methods instead.
  50. Loads []*Load
  51. // Rules is a list of rules within the file (or function calls that look like
  52. // rules). This should not be modified directly; use Rule methods instead.
  53. Rules []*Rule
  54. }
  55. // EmptyFile creates a File wrapped around an empty syntax tree.
  56. func EmptyFile(path, pkg string) *File {
  57. return &File{
  58. File: &bzl.File{Path: path},
  59. Path: path,
  60. Pkg: pkg,
  61. }
  62. }
  63. // LoadFile loads a build file from disk, parses it, and scans for rules and
  64. // load statements. The syntax tree within the returned File will be modified
  65. // by editing methods.
  66. //
  67. // This function returns I/O and parse errors without modification. It's safe
  68. // to use os.IsNotExist and similar predicates.
  69. func LoadFile(path, pkg string) (*File, error) {
  70. data, err := ioutil.ReadFile(path)
  71. if err != nil {
  72. return nil, err
  73. }
  74. return LoadData(path, pkg, data)
  75. }
  76. // LoadData parses a build file from a byte slice and scans it for rules and
  77. // load statements. The syntax tree within the returned File will be modified
  78. // by editing methods.
  79. func LoadData(path, pkg string, data []byte) (*File, error) {
  80. ast, err := bzl.Parse(path, data)
  81. if err != nil {
  82. return nil, err
  83. }
  84. return ScanAST(pkg, ast), nil
  85. }
  86. // ScanAST creates a File wrapped around the given syntax tree. This tree
  87. // will be modified by editing methods.
  88. func ScanAST(pkg string, bzlFile *bzl.File) *File {
  89. f := &File{
  90. File: bzlFile,
  91. Pkg: pkg,
  92. Path: bzlFile.Path,
  93. }
  94. for i, stmt := range f.File.Stmt {
  95. call, ok := stmt.(*bzl.CallExpr)
  96. if !ok {
  97. continue
  98. }
  99. x, ok := call.X.(*bzl.LiteralExpr)
  100. if !ok {
  101. continue
  102. }
  103. if x.Token == "load" {
  104. if l := loadFromExpr(i, call); l != nil {
  105. f.Loads = append(f.Loads, l)
  106. }
  107. } else {
  108. if r := ruleFromExpr(i, call); r != nil {
  109. f.Rules = append(f.Rules, r)
  110. }
  111. }
  112. }
  113. f.Directives = ParseDirectives(bzlFile)
  114. return f
  115. }
  116. // MatchBuildFileName looks for a file in files that has a name from names.
  117. // If there is at least one matching file, a path will be returned by joining
  118. // dir and the first matching name. If there are no matching files, the
  119. // empty string is returned.
  120. func MatchBuildFileName(dir string, names []string, files []os.FileInfo) string {
  121. for _, name := range names {
  122. for _, fi := range files {
  123. if fi.Name() == name && !fi.IsDir() {
  124. return filepath.Join(dir, name)
  125. }
  126. }
  127. }
  128. return ""
  129. }
  130. // Sync writes all changes back to the wrapped syntax tree. This should be
  131. // called after editing operations, before reading the syntax tree again.
  132. func (f *File) Sync() {
  133. var inserts, deletes, stmts []*stmt
  134. var r, w int
  135. for r, w = 0, 0; r < len(f.Loads); r++ {
  136. s := f.Loads[r]
  137. s.sync()
  138. if s.deleted {
  139. deletes = append(deletes, &s.stmt)
  140. continue
  141. }
  142. if s.inserted {
  143. inserts = append(inserts, &s.stmt)
  144. s.inserted = false
  145. } else {
  146. stmts = append(stmts, &s.stmt)
  147. }
  148. f.Loads[w] = s
  149. w++
  150. }
  151. f.Loads = f.Loads[:w]
  152. for r, w = 0, 0; r < len(f.Rules); r++ {
  153. s := f.Rules[r]
  154. s.sync()
  155. if s.deleted {
  156. deletes = append(deletes, &s.stmt)
  157. continue
  158. }
  159. if s.inserted {
  160. inserts = append(inserts, &s.stmt)
  161. s.inserted = false
  162. } else {
  163. stmts = append(stmts, &s.stmt)
  164. }
  165. f.Rules[w] = s
  166. w++
  167. }
  168. f.Rules = f.Rules[:w]
  169. sort.Stable(byIndex(deletes))
  170. sort.Stable(byIndex(inserts))
  171. sort.Stable(byIndex(stmts))
  172. oldStmt := f.File.Stmt
  173. f.File.Stmt = make([]bzl.Expr, 0, len(oldStmt)-len(deletes)+len(inserts))
  174. var ii, di, si int
  175. for i, stmt := range oldStmt {
  176. for ii < len(inserts) && inserts[ii].index == i {
  177. inserts[ii].index = len(f.File.Stmt)
  178. f.File.Stmt = append(f.File.Stmt, inserts[ii].call)
  179. ii++
  180. }
  181. if di < len(deletes) && deletes[di].index == i {
  182. di++
  183. continue
  184. }
  185. if si < len(stmts) && stmts[si].call == stmt {
  186. stmts[si].index = len(f.File.Stmt)
  187. si++
  188. }
  189. f.File.Stmt = append(f.File.Stmt, stmt)
  190. }
  191. for ii < len(inserts) {
  192. inserts[ii].index = len(f.File.Stmt)
  193. f.File.Stmt = append(f.File.Stmt, inserts[ii].call)
  194. ii++
  195. }
  196. }
  197. // Format formats the build file in a form that can be written to disk.
  198. // This method calls Sync internally.
  199. func (f *File) Format() []byte {
  200. f.Sync()
  201. return bzl.Format(f.File)
  202. }
  203. // Save writes the build file to disk. This method calls Sync internally.
  204. func (f *File) Save(path string) error {
  205. f.Sync()
  206. data := bzl.Format(f.File)
  207. return ioutil.WriteFile(path, data, 0666)
  208. }
  209. type stmt struct {
  210. index int
  211. deleted, inserted, updated bool
  212. call *bzl.CallExpr
  213. }
  214. // Index returns the index for this statement within the build file. For
  215. // inserted rules, this is where the rule will be inserted (rules with the
  216. // same index will be inserted in the order Insert was called). For existing
  217. // rules, this is the index of the original statement.
  218. func (s *stmt) Index() int { return s.index }
  219. // Delete marks this statement for deletion. It will be removed from the
  220. // syntax tree when File.Sync is called.
  221. func (s *stmt) Delete() { s.deleted = true }
  222. type byIndex []*stmt
  223. func (s byIndex) Len() int {
  224. return len(s)
  225. }
  226. func (s byIndex) Less(i, j int) bool {
  227. return s[i].index < s[j].index
  228. }
  229. func (s byIndex) Swap(i, j int) {
  230. s[i], s[j] = s[j], s[i]
  231. }
  232. // Load represents a load statement within a build file.
  233. type Load struct {
  234. stmt
  235. name string
  236. symbols map[string]bzl.Expr
  237. }
  238. // NewLoad creates a new, empty load statement for the given file name.
  239. func NewLoad(name string) *Load {
  240. return &Load{
  241. stmt: stmt{
  242. call: &bzl.CallExpr{
  243. X: &bzl.LiteralExpr{Token: "load"},
  244. List: []bzl.Expr{&bzl.StringExpr{Value: name}},
  245. ForceCompact: true,
  246. },
  247. },
  248. name: name,
  249. symbols: make(map[string]bzl.Expr),
  250. }
  251. }
  252. func loadFromExpr(index int, call *bzl.CallExpr) *Load {
  253. l := &Load{
  254. stmt: stmt{index: index, call: call},
  255. symbols: make(map[string]bzl.Expr),
  256. }
  257. if len(call.List) == 0 {
  258. return nil
  259. }
  260. name, ok := call.List[0].(*bzl.StringExpr)
  261. if !ok {
  262. return nil
  263. }
  264. l.name = name.Value
  265. for _, arg := range call.List[1:] {
  266. switch arg := arg.(type) {
  267. case *bzl.StringExpr:
  268. l.symbols[arg.Value] = arg
  269. case *bzl.BinaryExpr:
  270. x, ok := arg.X.(*bzl.LiteralExpr)
  271. if !ok {
  272. return nil
  273. }
  274. if _, ok := arg.Y.(*bzl.StringExpr); !ok {
  275. return nil
  276. }
  277. l.symbols[x.Token] = arg
  278. default:
  279. return nil
  280. }
  281. }
  282. return l
  283. }
  284. // Name returns the name of the file this statement loads.
  285. func (l *Load) Name() string {
  286. return l.name
  287. }
  288. // Symbols returns a list of symbols this statement loads.
  289. func (l *Load) Symbols() []string {
  290. syms := make([]string, 0, len(l.symbols))
  291. for sym := range l.symbols {
  292. syms = append(syms, sym)
  293. }
  294. sort.Strings(syms)
  295. return syms
  296. }
  297. // Has returns true if sym is loaded by this statement.
  298. func (l *Load) Has(sym string) bool {
  299. _, ok := l.symbols[sym]
  300. return ok
  301. }
  302. // Add inserts a new symbol into the load statement. This has no effect if
  303. // the symbol is already loaded. Symbols will be sorted, so the order
  304. // doesn't matter.
  305. func (l *Load) Add(sym string) {
  306. if _, ok := l.symbols[sym]; !ok {
  307. l.symbols[sym] = &bzl.StringExpr{Value: sym}
  308. l.updated = true
  309. }
  310. }
  311. // Remove deletes a symbol from the load statement. This has no effect if
  312. // the symbol is not loaded.
  313. func (l *Load) Remove(sym string) {
  314. if _, ok := l.symbols[sym]; ok {
  315. delete(l.symbols, sym)
  316. l.updated = true
  317. }
  318. }
  319. // IsEmpty returns whether this statement loads any symbols.
  320. func (l *Load) IsEmpty() bool {
  321. return len(l.symbols) == 0
  322. }
  323. // Insert marks this statement for insertion at the given index. If multiple
  324. // statements are inserted at the same index, they will be inserted in the
  325. // order Insert is called.
  326. func (l *Load) Insert(f *File, index int) {
  327. l.index = index
  328. l.inserted = true
  329. f.Loads = append(f.Loads, l)
  330. }
  331. func (l *Load) sync() {
  332. if !l.updated {
  333. return
  334. }
  335. l.updated = false
  336. args := make([]*bzl.StringExpr, 0, len(l.symbols))
  337. kwargs := make([]*bzl.BinaryExpr, 0, len(l.symbols))
  338. for _, e := range l.symbols {
  339. if a, ok := e.(*bzl.StringExpr); ok {
  340. args = append(args, a)
  341. } else {
  342. kwargs = append(kwargs, e.(*bzl.BinaryExpr))
  343. }
  344. }
  345. sort.Slice(args, func(i, j int) bool {
  346. return args[i].Value < args[j].Value
  347. })
  348. sort.Slice(kwargs, func(i, j int) bool {
  349. return kwargs[i].X.(*bzl.StringExpr).Value < kwargs[j].Y.(*bzl.StringExpr).Value
  350. })
  351. list := make([]bzl.Expr, 0, 1+len(l.symbols))
  352. list = append(list, l.call.List[0])
  353. for _, a := range args {
  354. list = append(list, a)
  355. }
  356. for _, a := range kwargs {
  357. list = append(list, a)
  358. }
  359. l.call.List = list
  360. l.call.ForceCompact = len(kwargs) == 0
  361. }
  362. // Rule represents a rule statement within a build file.
  363. type Rule struct {
  364. stmt
  365. kind string
  366. args []bzl.Expr
  367. attrs map[string]*bzl.BinaryExpr
  368. private map[string]interface{}
  369. }
  370. // NewRule creates a new, empty rule with the given kind and name.
  371. func NewRule(kind, name string) *Rule {
  372. nameAttr := &bzl.BinaryExpr{
  373. X: &bzl.LiteralExpr{Token: "name"},
  374. Y: &bzl.StringExpr{Value: name},
  375. Op: "=",
  376. }
  377. r := &Rule{
  378. stmt: stmt{
  379. call: &bzl.CallExpr{
  380. X: &bzl.LiteralExpr{Token: kind},
  381. List: []bzl.Expr{nameAttr},
  382. },
  383. },
  384. kind: kind,
  385. attrs: map[string]*bzl.BinaryExpr{"name": nameAttr},
  386. private: map[string]interface{}{},
  387. }
  388. return r
  389. }
  390. func ruleFromExpr(index int, expr bzl.Expr) *Rule {
  391. call, ok := expr.(*bzl.CallExpr)
  392. if !ok {
  393. return nil
  394. }
  395. x, ok := call.X.(*bzl.LiteralExpr)
  396. if !ok {
  397. return nil
  398. }
  399. kind := x.Token
  400. var args []bzl.Expr
  401. attrs := make(map[string]*bzl.BinaryExpr)
  402. for _, arg := range call.List {
  403. attr, ok := arg.(*bzl.BinaryExpr)
  404. if ok && attr.Op == "=" {
  405. key := attr.X.(*bzl.LiteralExpr) // required by parser
  406. attrs[key.Token] = attr
  407. } else {
  408. args = append(args, arg)
  409. }
  410. }
  411. return &Rule{
  412. stmt: stmt{
  413. index: index,
  414. call: call,
  415. },
  416. kind: kind,
  417. args: args,
  418. attrs: attrs,
  419. private: map[string]interface{}{},
  420. }
  421. }
  422. // ShouldKeep returns whether the rule is marked with a "# keep" comment. Rules
  423. // that are kept should not be modified. This does not check whether
  424. // subexpressions within the rule should be kept.
  425. func (r *Rule) ShouldKeep() bool {
  426. return ShouldKeep(r.call)
  427. }
  428. func (r *Rule) Kind() string {
  429. return r.kind
  430. }
  431. func (r *Rule) SetKind(kind string) {
  432. r.kind = kind
  433. r.updated = true
  434. }
  435. func (r *Rule) Name() string {
  436. return r.AttrString("name")
  437. }
  438. func (r *Rule) SetName(name string) {
  439. r.SetAttr("name", name)
  440. }
  441. // AttrKeys returns a sorted list of attribute keys used in this rule.
  442. func (r *Rule) AttrKeys() []string {
  443. keys := make([]string, 0, len(r.attrs))
  444. for k := range r.attrs {
  445. keys = append(keys, k)
  446. }
  447. sort.SliceStable(keys, func(i, j int) bool {
  448. if cmp := bt.NamePriority[keys[i]] - bt.NamePriority[keys[j]]; cmp != 0 {
  449. return cmp < 0
  450. }
  451. return keys[i] < keys[j]
  452. })
  453. return keys
  454. }
  455. // Attr returns the value of the named attribute. nil is returned when the
  456. // attribute is not set.
  457. func (r *Rule) Attr(key string) bzl.Expr {
  458. attr, ok := r.attrs[key]
  459. if !ok {
  460. return nil
  461. }
  462. return attr.Y
  463. }
  464. // AttrString returns the value of the named attribute if it is a scalar string.
  465. // "" is returned if the attribute is not set or is not a string.
  466. func (r *Rule) AttrString(key string) string {
  467. attr, ok := r.attrs[key]
  468. if !ok {
  469. return ""
  470. }
  471. str, ok := attr.Y.(*bzl.StringExpr)
  472. if !ok {
  473. return ""
  474. }
  475. return str.Value
  476. }
  477. // AttrStrings returns the string values of an attribute if it is a list.
  478. // nil is returned if the attribute is not set or is not a list. Non-string
  479. // values within the list won't be returned.
  480. func (r *Rule) AttrStrings(key string) []string {
  481. attr, ok := r.attrs[key]
  482. if !ok {
  483. return nil
  484. }
  485. list, ok := attr.Y.(*bzl.ListExpr)
  486. if !ok {
  487. return nil
  488. }
  489. strs := make([]string, 0, len(list.List))
  490. for _, e := range list.List {
  491. if str, ok := e.(*bzl.StringExpr); ok {
  492. strs = append(strs, str.Value)
  493. }
  494. }
  495. return strs
  496. }
  497. // DelAttr removes the named attribute from the rule.
  498. func (r *Rule) DelAttr(key string) {
  499. delete(r.attrs, key)
  500. r.updated = true
  501. }
  502. // SetAttr adds or replaces the named attribute with an expression produced
  503. // by ExprFromValue.
  504. func (r *Rule) SetAttr(key string, value interface{}) {
  505. y := ExprFromValue(value)
  506. if attr, ok := r.attrs[key]; ok {
  507. attr.Y = y
  508. } else {
  509. r.attrs[key] = &bzl.BinaryExpr{
  510. X: &bzl.LiteralExpr{Token: key},
  511. Y: y,
  512. Op: "=",
  513. }
  514. }
  515. r.updated = true
  516. }
  517. // PrivateAttrKeys returns a sorted list of private attribute names.
  518. func (r *Rule) PrivateAttrKeys() []string {
  519. keys := make([]string, 0, len(r.private))
  520. for k := range r.private {
  521. keys = append(keys, k)
  522. }
  523. sort.Strings(keys)
  524. return keys
  525. }
  526. // PrivateAttr return the private value associated with a key.
  527. func (r *Rule) PrivateAttr(key string) interface{} {
  528. return r.private[key]
  529. }
  530. // SetPrivateAttr associates a value with a key. Unlike SetAttr, this value
  531. // is not converted to a build syntax tree and will not be written to a build
  532. // file.
  533. func (r *Rule) SetPrivateAttr(key string, value interface{}) {
  534. r.private[key] = value
  535. }
  536. // Args returns positional arguments passed to a rule.
  537. func (r *Rule) Args() []bzl.Expr {
  538. return r.args
  539. }
  540. // Insert marks this statement for insertion at the end of the file. Multiple
  541. // statements will be inserted in the order Insert is called.
  542. func (r *Rule) Insert(f *File) {
  543. // TODO(jayconrod): should rules always be inserted at the end? Should there
  544. // be some sort order?
  545. r.index = len(f.File.Stmt)
  546. r.inserted = true
  547. f.Rules = append(f.Rules, r)
  548. }
  549. // IsEmpty returns true when the rule contains none of the attributes in attrs
  550. // for its kind. attrs should contain attributes that make the rule buildable
  551. // like srcs or deps and not descriptive attributes like name or visibility.
  552. func (r *Rule) IsEmpty(info KindInfo) bool {
  553. if info.NonEmptyAttrs == nil {
  554. return false
  555. }
  556. for k := range info.NonEmptyAttrs {
  557. if _, ok := r.attrs[k]; ok {
  558. return false
  559. }
  560. }
  561. return true
  562. }
  563. func (r *Rule) IsEmptyOld(attrs map[string]bool) bool {
  564. if attrs == nil {
  565. return false
  566. }
  567. for k := range attrs {
  568. if _, ok := r.attrs[k]; ok {
  569. return false
  570. }
  571. }
  572. return true
  573. }
  574. func (r *Rule) sync() {
  575. if !r.updated {
  576. return
  577. }
  578. r.updated = false
  579. for _, k := range []string{"srcs", "deps"} {
  580. if attr, ok := r.attrs[k]; ok {
  581. bzl.Walk(attr.Y, sortExprLabels)
  582. }
  583. }
  584. call := r.call
  585. call.X.(*bzl.LiteralExpr).Token = r.kind
  586. list := make([]bzl.Expr, 0, len(r.args)+len(r.attrs))
  587. list = append(list, r.args...)
  588. for _, attr := range r.attrs {
  589. list = append(list, attr)
  590. }
  591. sortedAttrs := list[len(r.args):]
  592. key := func(e bzl.Expr) string { return e.(*bzl.BinaryExpr).X.(*bzl.LiteralExpr).Token }
  593. sort.SliceStable(sortedAttrs, func(i, j int) bool {
  594. ki := key(sortedAttrs[i])
  595. kj := key(sortedAttrs[j])
  596. if cmp := bt.NamePriority[ki] - bt.NamePriority[kj]; cmp != 0 {
  597. return cmp < 0
  598. }
  599. return ki < kj
  600. })
  601. r.call.List = list
  602. r.updated = false
  603. }
  604. // ShouldKeep returns whether e is marked with a "# keep" comment. Kept
  605. // expressions should not be removed or modified.
  606. func ShouldKeep(e bzl.Expr) bool {
  607. for _, c := range append(e.Comment().Before, e.Comment().Suffix...) {
  608. text := strings.TrimSpace(strings.TrimPrefix(c.Token, "#"))
  609. if text == "keep" {
  610. return true
  611. }
  612. }
  613. return false
  614. }
  615. type byAttrName []KeyValue
  616. var _ sort.Interface = byAttrName{}
  617. func (s byAttrName) Len() int {
  618. return len(s)
  619. }
  620. func (s byAttrName) Less(i, j int) bool {
  621. if cmp := bt.NamePriority[s[i].Key] - bt.NamePriority[s[j].Key]; cmp != 0 {
  622. return cmp < 0
  623. }
  624. return s[i].Key < s[j].Key
  625. }
  626. func (s byAttrName) Swap(i, j int) {
  627. s[i], s[j] = s[j], s[i]
  628. }