PageRenderTime 51ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/third_party/gofrontend/libgo/go/go/printer/printer.go

http://github.com/axw/llgo
Go | 1296 lines | 826 code | 123 blank | 347 comment | 264 complexity | 16e2b774cdd87128c3a751b24a6c0159 MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
  1. // Copyright 2009 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. // Package printer implements printing of AST nodes.
  5. package printer
  6. import (
  7. "fmt"
  8. "go/ast"
  9. "go/token"
  10. "io"
  11. "os"
  12. "strconv"
  13. "strings"
  14. "text/tabwriter"
  15. "unicode"
  16. )
  17. const (
  18. maxNewlines = 2 // max. number of newlines between source text
  19. debug = false // enable for debugging
  20. infinity = 1 << 30
  21. )
  22. type whiteSpace byte
  23. const (
  24. ignore = whiteSpace(0)
  25. blank = whiteSpace(' ')
  26. vtab = whiteSpace('\v')
  27. newline = whiteSpace('\n')
  28. formfeed = whiteSpace('\f')
  29. indent = whiteSpace('>')
  30. unindent = whiteSpace('<')
  31. )
  32. // A pmode value represents the current printer mode.
  33. type pmode int
  34. const (
  35. noExtraBlank pmode = 1 << iota // disables extra blank after /*-style comment
  36. noExtraLinebreak // disables extra line break after /*-style comment
  37. )
  38. type commentInfo struct {
  39. cindex int // current comment index
  40. comment *ast.CommentGroup // = printer.comments[cindex]; or nil
  41. commentOffset int // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity
  42. commentNewline bool // true if the comment group contains newlines
  43. }
  44. type printer struct {
  45. // Configuration (does not change after initialization)
  46. Config
  47. fset *token.FileSet
  48. // Current state
  49. output []byte // raw printer result
  50. indent int // current indentation
  51. mode pmode // current printer mode
  52. impliedSemi bool // if set, a linebreak implies a semicolon
  53. lastTok token.Token // last token printed (token.ILLEGAL if it's whitespace)
  54. prevOpen token.Token // previous non-brace "open" token (, [, or token.ILLEGAL
  55. wsbuf []whiteSpace // delayed white space
  56. // Positions
  57. // The out position differs from the pos position when the result
  58. // formatting differs from the source formatting (in the amount of
  59. // white space). If there's a difference and SourcePos is set in
  60. // ConfigMode, //line comments are used in the output to restore
  61. // original source positions for a reader.
  62. pos token.Position // current position in AST (source) space
  63. out token.Position // current position in output space
  64. last token.Position // value of pos after calling writeString
  65. linePtr *int // if set, record out.Line for the next token in *linePtr
  66. // The list of all source comments, in order of appearance.
  67. comments []*ast.CommentGroup // may be nil
  68. useNodeComments bool // if not set, ignore lead and line comments of nodes
  69. // Information about p.comments[p.cindex]; set up by nextComment.
  70. commentInfo
  71. // Cache of already computed node sizes.
  72. nodeSizes map[ast.Node]int
  73. // Cache of most recently computed line position.
  74. cachedPos token.Pos
  75. cachedLine int // line corresponding to cachedPos
  76. }
  77. func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
  78. p.Config = *cfg
  79. p.fset = fset
  80. p.pos = token.Position{Line: 1, Column: 1}
  81. p.out = token.Position{Line: 1, Column: 1}
  82. p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short
  83. p.nodeSizes = nodeSizes
  84. p.cachedPos = -1
  85. }
  86. func (p *printer) internalError(msg ...interface{}) {
  87. if debug {
  88. fmt.Print(p.pos.String() + ": ")
  89. fmt.Println(msg...)
  90. panic("go/printer")
  91. }
  92. }
  93. // commentsHaveNewline reports whether a list of comments belonging to
  94. // an *ast.CommentGroup contains newlines. Because the position information
  95. // may only be partially correct, we also have to read the comment text.
  96. func (p *printer) commentsHaveNewline(list []*ast.Comment) bool {
  97. // len(list) > 0
  98. line := p.lineFor(list[0].Pos())
  99. for i, c := range list {
  100. if i > 0 && p.lineFor(list[i].Pos()) != line {
  101. // not all comments on the same line
  102. return true
  103. }
  104. if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) {
  105. return true
  106. }
  107. }
  108. _ = line
  109. return false
  110. }
  111. func (p *printer) nextComment() {
  112. for p.cindex < len(p.comments) {
  113. c := p.comments[p.cindex]
  114. p.cindex++
  115. if list := c.List; len(list) > 0 {
  116. p.comment = c
  117. p.commentOffset = p.posFor(list[0].Pos()).Offset
  118. p.commentNewline = p.commentsHaveNewline(list)
  119. return
  120. }
  121. // we should not reach here (correct ASTs don't have empty
  122. // ast.CommentGroup nodes), but be conservative and try again
  123. }
  124. // no more comments
  125. p.commentOffset = infinity
  126. }
  127. // commentBefore reports whether the current comment group occurs
  128. // before the next position in the source code and printing it does
  129. // not introduce implicit semicolons.
  130. //
  131. func (p *printer) commentBefore(next token.Position) bool {
  132. return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline)
  133. }
  134. // commentSizeBefore returns the estimated size of the
  135. // comments on the same line before the next position.
  136. //
  137. func (p *printer) commentSizeBefore(next token.Position) int {
  138. // save/restore current p.commentInfo (p.nextComment() modifies it)
  139. defer func(info commentInfo) {
  140. p.commentInfo = info
  141. }(p.commentInfo)
  142. size := 0
  143. for p.commentBefore(next) {
  144. for _, c := range p.comment.List {
  145. size += len(c.Text)
  146. }
  147. p.nextComment()
  148. }
  149. return size
  150. }
  151. // recordLine records the output line number for the next non-whitespace
  152. // token in *linePtr. It is used to compute an accurate line number for a
  153. // formatted construct, independent of pending (not yet emitted) whitespace
  154. // or comments.
  155. //
  156. func (p *printer) recordLine(linePtr *int) {
  157. p.linePtr = linePtr
  158. }
  159. // linesFrom returns the number of output lines between the current
  160. // output line and the line argument, ignoring any pending (not yet
  161. // emitted) whitespace or comments. It is used to compute an accurate
  162. // size (in number of lines) for a formatted construct.
  163. //
  164. func (p *printer) linesFrom(line int) int {
  165. return p.out.Line - line
  166. }
  167. func (p *printer) posFor(pos token.Pos) token.Position {
  168. // not used frequently enough to cache entire token.Position
  169. return p.fset.Position(pos)
  170. }
  171. func (p *printer) lineFor(pos token.Pos) int {
  172. if pos != p.cachedPos {
  173. p.cachedPos = pos
  174. p.cachedLine = p.fset.Position(pos).Line
  175. }
  176. return p.cachedLine
  177. }
  178. // atLineBegin emits a //line comment if necessary and prints indentation.
  179. func (p *printer) atLineBegin(pos token.Position) {
  180. // write a //line comment if necessary
  181. if p.Config.Mode&SourcePos != 0 && pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) {
  182. p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation
  183. p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...)
  184. p.output = append(p.output, tabwriter.Escape)
  185. // p.out must match the //line comment
  186. p.out.Filename = pos.Filename
  187. p.out.Line = pos.Line
  188. }
  189. // write indentation
  190. // use "hard" htabs - indentation columns
  191. // must not be discarded by the tabwriter
  192. n := p.Config.Indent + p.indent // include base indentation
  193. for i := 0; i < n; i++ {
  194. p.output = append(p.output, '\t')
  195. }
  196. // update positions
  197. p.pos.Offset += n
  198. p.pos.Column += n
  199. p.out.Column += n
  200. }
  201. // writeByte writes ch n times to p.output and updates p.pos.
  202. func (p *printer) writeByte(ch byte, n int) {
  203. if p.out.Column == 1 {
  204. p.atLineBegin(p.pos)
  205. }
  206. for i := 0; i < n; i++ {
  207. p.output = append(p.output, ch)
  208. }
  209. // update positions
  210. p.pos.Offset += n
  211. if ch == '\n' || ch == '\f' {
  212. p.pos.Line += n
  213. p.out.Line += n
  214. p.pos.Column = 1
  215. p.out.Column = 1
  216. return
  217. }
  218. p.pos.Column += n
  219. p.out.Column += n
  220. }
  221. // writeString writes the string s to p.output and updates p.pos, p.out,
  222. // and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters
  223. // to protect s from being interpreted by the tabwriter.
  224. //
  225. // Note: writeString is only used to write Go tokens, literals, and
  226. // comments, all of which must be written literally. Thus, it is correct
  227. // to always set isLit = true. However, setting it explicitly only when
  228. // needed (i.e., when we don't know that s contains no tabs or line breaks)
  229. // avoids processing extra escape characters and reduces run time of the
  230. // printer benchmark by up to 10%.
  231. //
  232. func (p *printer) writeString(pos token.Position, s string, isLit bool) {
  233. if p.out.Column == 1 {
  234. p.atLineBegin(pos)
  235. }
  236. if pos.IsValid() {
  237. // update p.pos (if pos is invalid, continue with existing p.pos)
  238. // Note: Must do this after handling line beginnings because
  239. // atLineBegin updates p.pos if there's indentation, but p.pos
  240. // is the position of s.
  241. p.pos = pos
  242. }
  243. if isLit {
  244. // Protect s such that is passes through the tabwriter
  245. // unchanged. Note that valid Go programs cannot contain
  246. // tabwriter.Escape bytes since they do not appear in legal
  247. // UTF-8 sequences.
  248. p.output = append(p.output, tabwriter.Escape)
  249. }
  250. if debug {
  251. p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos!
  252. }
  253. p.output = append(p.output, s...)
  254. // update positions
  255. nlines := 0
  256. var li int // index of last newline; valid if nlines > 0
  257. for i := 0; i < len(s); i++ {
  258. // Go tokens cannot contain '\f' - no need to look for it
  259. if s[i] == '\n' {
  260. nlines++
  261. li = i
  262. }
  263. }
  264. p.pos.Offset += len(s)
  265. if nlines > 0 {
  266. p.pos.Line += nlines
  267. p.out.Line += nlines
  268. c := len(s) - li
  269. p.pos.Column = c
  270. p.out.Column = c
  271. } else {
  272. p.pos.Column += len(s)
  273. p.out.Column += len(s)
  274. }
  275. if isLit {
  276. p.output = append(p.output, tabwriter.Escape)
  277. }
  278. p.last = p.pos
  279. }
  280. // writeCommentPrefix writes the whitespace before a comment.
  281. // If there is any pending whitespace, it consumes as much of
  282. // it as is likely to help position the comment nicely.
  283. // pos is the comment position, next the position of the item
  284. // after all pending comments, prev is the previous comment in
  285. // a group of comments (or nil), and tok is the next token.
  286. //
  287. func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *ast.Comment, tok token.Token) {
  288. if len(p.output) == 0 {
  289. // the comment is the first item to be printed - don't write any whitespace
  290. return
  291. }
  292. if pos.IsValid() && pos.Filename != p.last.Filename {
  293. // comment in a different file - separate with newlines
  294. p.writeByte('\f', maxNewlines)
  295. return
  296. }
  297. if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') {
  298. // comment on the same line as last item:
  299. // separate with at least one separator
  300. hasSep := false
  301. if prev == nil {
  302. // first comment of a comment group
  303. j := 0
  304. for i, ch := range p.wsbuf {
  305. switch ch {
  306. case blank:
  307. // ignore any blanks before a comment
  308. p.wsbuf[i] = ignore
  309. continue
  310. case vtab:
  311. // respect existing tabs - important
  312. // for proper formatting of commented structs
  313. hasSep = true
  314. continue
  315. case indent:
  316. // apply pending indentation
  317. continue
  318. }
  319. j = i
  320. break
  321. }
  322. p.writeWhitespace(j)
  323. }
  324. // make sure there is at least one separator
  325. if !hasSep {
  326. sep := byte('\t')
  327. if pos.Line == next.Line {
  328. // next item is on the same line as the comment
  329. // (which must be a /*-style comment): separate
  330. // with a blank instead of a tab
  331. sep = ' '
  332. }
  333. p.writeByte(sep, 1)
  334. }
  335. } else {
  336. // comment on a different line:
  337. // separate with at least one line break
  338. droppedLinebreak := false
  339. j := 0
  340. for i, ch := range p.wsbuf {
  341. switch ch {
  342. case blank, vtab:
  343. // ignore any horizontal whitespace before line breaks
  344. p.wsbuf[i] = ignore
  345. continue
  346. case indent:
  347. // apply pending indentation
  348. continue
  349. case unindent:
  350. // if this is not the last unindent, apply it
  351. // as it is (likely) belonging to the last
  352. // construct (e.g., a multi-line expression list)
  353. // and is not part of closing a block
  354. if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent {
  355. continue
  356. }
  357. // if the next token is not a closing }, apply the unindent
  358. // if it appears that the comment is aligned with the
  359. // token; otherwise assume the unindent is part of a
  360. // closing block and stop (this scenario appears with
  361. // comments before a case label where the comments
  362. // apply to the next case instead of the current one)
  363. if tok != token.RBRACE && pos.Column == next.Column {
  364. continue
  365. }
  366. case newline, formfeed:
  367. p.wsbuf[i] = ignore
  368. droppedLinebreak = prev == nil // record only if first comment of a group
  369. }
  370. j = i
  371. break
  372. }
  373. p.writeWhitespace(j)
  374. // determine number of linebreaks before the comment
  375. n := 0
  376. if pos.IsValid() && p.last.IsValid() {
  377. n = pos.Line - p.last.Line
  378. if n < 0 { // should never happen
  379. n = 0
  380. }
  381. }
  382. // at the package scope level only (p.indent == 0),
  383. // add an extra newline if we dropped one before:
  384. // this preserves a blank line before documentation
  385. // comments at the package scope level (issue 2570)
  386. if p.indent == 0 && droppedLinebreak {
  387. n++
  388. }
  389. // make sure there is at least one line break
  390. // if the previous comment was a line comment
  391. if n == 0 && prev != nil && prev.Text[1] == '/' {
  392. n = 1
  393. }
  394. if n > 0 {
  395. // use formfeeds to break columns before a comment;
  396. // this is analogous to using formfeeds to separate
  397. // individual lines of /*-style comments
  398. p.writeByte('\f', nlimit(n))
  399. }
  400. }
  401. }
  402. // Returns true if s contains only white space
  403. // (only tabs and blanks can appear in the printer's context).
  404. //
  405. func isBlank(s string) bool {
  406. for i := 0; i < len(s); i++ {
  407. if s[i] > ' ' {
  408. return false
  409. }
  410. }
  411. return true
  412. }
  413. // commonPrefix returns the common prefix of a and b.
  414. func commonPrefix(a, b string) string {
  415. i := 0
  416. for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') {
  417. i++
  418. }
  419. return a[0:i]
  420. }
  421. // trimRight returns s with trailing whitespace removed.
  422. func trimRight(s string) string {
  423. return strings.TrimRightFunc(s, unicode.IsSpace)
  424. }
  425. // stripCommonPrefix removes a common prefix from /*-style comment lines (unless no
  426. // comment line is indented, all but the first line have some form of space prefix).
  427. // The prefix is computed using heuristics such that is likely that the comment
  428. // contents are nicely laid out after re-printing each line using the printer's
  429. // current indentation.
  430. //
  431. func stripCommonPrefix(lines []string) {
  432. if len(lines) <= 1 {
  433. return // at most one line - nothing to do
  434. }
  435. // len(lines) > 1
  436. // The heuristic in this function tries to handle a few
  437. // common patterns of /*-style comments: Comments where
  438. // the opening /* and closing */ are aligned and the
  439. // rest of the comment text is aligned and indented with
  440. // blanks or tabs, cases with a vertical "line of stars"
  441. // on the left, and cases where the closing */ is on the
  442. // same line as the last comment text.
  443. // Compute maximum common white prefix of all but the first,
  444. // last, and blank lines, and replace blank lines with empty
  445. // lines (the first line starts with /* and has no prefix).
  446. // In cases where only the first and last lines are not blank,
  447. // such as two-line comments, or comments where all inner lines
  448. // are blank, consider the last line for the prefix computation
  449. // since otherwise the prefix would be empty.
  450. //
  451. // Note that the first and last line are never empty (they
  452. // contain the opening /* and closing */ respectively) and
  453. // thus they can be ignored by the blank line check.
  454. prefix := ""
  455. prefixSet := false
  456. if len(lines) > 2 {
  457. for i, line := range lines[1 : len(lines)-1] {
  458. if isBlank(line) {
  459. lines[1+i] = "" // range starts with lines[1]
  460. } else {
  461. if !prefixSet {
  462. prefix = line
  463. prefixSet = true
  464. }
  465. prefix = commonPrefix(prefix, line)
  466. }
  467. }
  468. }
  469. // If we don't have a prefix yet, consider the last line.
  470. if !prefixSet {
  471. line := lines[len(lines)-1]
  472. prefix = commonPrefix(line, line)
  473. }
  474. /*
  475. * Check for vertical "line of stars" and correct prefix accordingly.
  476. */
  477. lineOfStars := false
  478. if i := strings.Index(prefix, "*"); i >= 0 {
  479. // Line of stars present.
  480. if i > 0 && prefix[i-1] == ' ' {
  481. i-- // remove trailing blank from prefix so stars remain aligned
  482. }
  483. prefix = prefix[0:i]
  484. lineOfStars = true
  485. } else {
  486. // No line of stars present.
  487. // Determine the white space on the first line after the /*
  488. // and before the beginning of the comment text, assume two
  489. // blanks instead of the /* unless the first character after
  490. // the /* is a tab. If the first comment line is empty but
  491. // for the opening /*, assume up to 3 blanks or a tab. This
  492. // whitespace may be found as suffix in the common prefix.
  493. first := lines[0]
  494. if isBlank(first[2:]) {
  495. // no comment text on the first line:
  496. // reduce prefix by up to 3 blanks or a tab
  497. // if present - this keeps comment text indented
  498. // relative to the /* and */'s if it was indented
  499. // in the first place
  500. i := len(prefix)
  501. for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
  502. i--
  503. }
  504. if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
  505. i--
  506. }
  507. prefix = prefix[0:i]
  508. } else {
  509. // comment text on the first line
  510. suffix := make([]byte, len(first))
  511. n := 2 // start after opening /*
  512. for n < len(first) && first[n] <= ' ' {
  513. suffix[n] = first[n]
  514. n++
  515. }
  516. if n > 2 && suffix[2] == '\t' {
  517. // assume the '\t' compensates for the /*
  518. suffix = suffix[2:n]
  519. } else {
  520. // otherwise assume two blanks
  521. suffix[0], suffix[1] = ' ', ' '
  522. suffix = suffix[0:n]
  523. }
  524. // Shorten the computed common prefix by the length of
  525. // suffix, if it is found as suffix of the prefix.
  526. prefix = strings.TrimSuffix(prefix, string(suffix))
  527. }
  528. }
  529. // Handle last line: If it only contains a closing */, align it
  530. // with the opening /*, otherwise align the text with the other
  531. // lines.
  532. last := lines[len(lines)-1]
  533. closing := "*/"
  534. i := strings.Index(last, closing) // i >= 0 (closing is always present)
  535. if isBlank(last[0:i]) {
  536. // last line only contains closing */
  537. if lineOfStars {
  538. closing = " */" // add blank to align final star
  539. }
  540. lines[len(lines)-1] = prefix + closing
  541. } else {
  542. // last line contains more comment text - assume
  543. // it is aligned like the other lines and include
  544. // in prefix computation
  545. prefix = commonPrefix(prefix, last)
  546. }
  547. // Remove the common prefix from all but the first and empty lines.
  548. for i, line := range lines {
  549. if i > 0 && line != "" {
  550. lines[i] = line[len(prefix):]
  551. }
  552. }
  553. }
  554. func (p *printer) writeComment(comment *ast.Comment) {
  555. text := comment.Text
  556. pos := p.posFor(comment.Pos())
  557. const linePrefix = "//line "
  558. if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) {
  559. // possibly a line directive
  560. ldir := strings.TrimSpace(text[len(linePrefix):])
  561. if i := strings.LastIndex(ldir, ":"); i >= 0 {
  562. if line, err := strconv.Atoi(ldir[i+1:]); err == nil && line > 0 {
  563. // The line directive we are about to print changed
  564. // the Filename and Line number used for subsequent
  565. // tokens. We have to update our AST-space position
  566. // accordingly and suspend indentation temporarily.
  567. indent := p.indent
  568. p.indent = 0
  569. defer func() {
  570. p.pos.Filename = ldir[:i]
  571. p.pos.Line = line
  572. p.pos.Column = 1
  573. p.indent = indent
  574. }()
  575. }
  576. }
  577. }
  578. // shortcut common case of //-style comments
  579. if text[1] == '/' {
  580. p.writeString(pos, trimRight(text), true)
  581. return
  582. }
  583. // for /*-style comments, print line by line and let the
  584. // write function take care of the proper indentation
  585. lines := strings.Split(text, "\n")
  586. // The comment started in the first column but is going
  587. // to be indented. For an idempotent result, add indentation
  588. // to all lines such that they look like they were indented
  589. // before - this will make sure the common prefix computation
  590. // is the same independent of how many times formatting is
  591. // applied (was issue 1835).
  592. if pos.IsValid() && pos.Column == 1 && p.indent > 0 {
  593. for i, line := range lines[1:] {
  594. lines[1+i] = " " + line
  595. }
  596. }
  597. stripCommonPrefix(lines)
  598. // write comment lines, separated by formfeed,
  599. // without a line break after the last line
  600. for i, line := range lines {
  601. if i > 0 {
  602. p.writeByte('\f', 1)
  603. pos = p.pos
  604. }
  605. if len(line) > 0 {
  606. p.writeString(pos, trimRight(line), true)
  607. }
  608. }
  609. }
  610. // writeCommentSuffix writes a line break after a comment if indicated
  611. // and processes any leftover indentation information. If a line break
  612. // is needed, the kind of break (newline vs formfeed) depends on the
  613. // pending whitespace. The writeCommentSuffix result indicates if a
  614. // newline was written or if a formfeed was dropped from the whitespace
  615. // buffer.
  616. //
  617. func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) {
  618. for i, ch := range p.wsbuf {
  619. switch ch {
  620. case blank, vtab:
  621. // ignore trailing whitespace
  622. p.wsbuf[i] = ignore
  623. case indent, unindent:
  624. // don't lose indentation information
  625. case newline, formfeed:
  626. // if we need a line break, keep exactly one
  627. // but remember if we dropped any formfeeds
  628. if needsLinebreak {
  629. needsLinebreak = false
  630. wroteNewline = true
  631. } else {
  632. if ch == formfeed {
  633. droppedFF = true
  634. }
  635. p.wsbuf[i] = ignore
  636. }
  637. }
  638. }
  639. p.writeWhitespace(len(p.wsbuf))
  640. // make sure we have a line break
  641. if needsLinebreak {
  642. p.writeByte('\n', 1)
  643. wroteNewline = true
  644. }
  645. return
  646. }
  647. // intersperseComments consumes all comments that appear before the next token
  648. // tok and prints it together with the buffered whitespace (i.e., the whitespace
  649. // that needs to be written before the next token). A heuristic is used to mix
  650. // the comments and whitespace. The intersperseComments result indicates if a
  651. // newline was written or if a formfeed was dropped from the whitespace buffer.
  652. //
  653. func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
  654. var last *ast.Comment
  655. for p.commentBefore(next) {
  656. for _, c := range p.comment.List {
  657. p.writeCommentPrefix(p.posFor(c.Pos()), next, last, c, tok)
  658. p.writeComment(c)
  659. last = c
  660. }
  661. p.nextComment()
  662. }
  663. if last != nil {
  664. // if the last comment is a /*-style comment and the next item
  665. // follows on the same line but is not a comma, and not a "closing"
  666. // token immediately following its corresponding "opening" token,
  667. // add an extra blank for separation unless explicitly disabled
  668. if p.mode&noExtraBlank == 0 &&
  669. last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line &&
  670. tok != token.COMMA &&
  671. (tok != token.RPAREN || p.prevOpen == token.LPAREN) &&
  672. (tok != token.RBRACK || p.prevOpen == token.LBRACK) {
  673. p.writeByte(' ', 1)
  674. }
  675. // ensure that there is a line break after a //-style comment,
  676. // before a closing '}' unless explicitly disabled, or at eof
  677. needsLinebreak :=
  678. last.Text[1] == '/' ||
  679. tok == token.RBRACE && p.mode&noExtraLinebreak == 0 ||
  680. tok == token.EOF
  681. return p.writeCommentSuffix(needsLinebreak)
  682. }
  683. // no comment was written - we should never reach here since
  684. // intersperseComments should not be called in that case
  685. p.internalError("intersperseComments called without pending comments")
  686. return
  687. }
  688. // whiteWhitespace writes the first n whitespace entries.
  689. func (p *printer) writeWhitespace(n int) {
  690. // write entries
  691. for i := 0; i < n; i++ {
  692. switch ch := p.wsbuf[i]; ch {
  693. case ignore:
  694. // ignore!
  695. case indent:
  696. p.indent++
  697. case unindent:
  698. p.indent--
  699. if p.indent < 0 {
  700. p.internalError("negative indentation:", p.indent)
  701. p.indent = 0
  702. }
  703. case newline, formfeed:
  704. // A line break immediately followed by a "correcting"
  705. // unindent is swapped with the unindent - this permits
  706. // proper label positioning. If a comment is between
  707. // the line break and the label, the unindent is not
  708. // part of the comment whitespace prefix and the comment
  709. // will be positioned correctly indented.
  710. if i+1 < n && p.wsbuf[i+1] == unindent {
  711. // Use a formfeed to terminate the current section.
  712. // Otherwise, a long label name on the next line leading
  713. // to a wide column may increase the indentation column
  714. // of lines before the label; effectively leading to wrong
  715. // indentation.
  716. p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
  717. i-- // do it again
  718. continue
  719. }
  720. fallthrough
  721. default:
  722. p.writeByte(byte(ch), 1)
  723. }
  724. }
  725. // shift remaining entries down
  726. l := copy(p.wsbuf, p.wsbuf[n:])
  727. p.wsbuf = p.wsbuf[:l]
  728. }
  729. // ----------------------------------------------------------------------------
  730. // Printing interface
  731. // nlines limits n to maxNewlines.
  732. func nlimit(n int) int {
  733. if n > maxNewlines {
  734. n = maxNewlines
  735. }
  736. return n
  737. }
  738. func mayCombine(prev token.Token, next byte) (b bool) {
  739. switch prev {
  740. case token.INT:
  741. b = next == '.' // 1.
  742. case token.ADD:
  743. b = next == '+' // ++
  744. case token.SUB:
  745. b = next == '-' // --
  746. case token.QUO:
  747. b = next == '*' // /*
  748. case token.LSS:
  749. b = next == '-' || next == '<' // <- or <<
  750. case token.AND:
  751. b = next == '&' || next == '^' // && or &^
  752. }
  753. return
  754. }
  755. // print prints a list of "items" (roughly corresponding to syntactic
  756. // tokens, but also including whitespace and formatting information).
  757. // It is the only print function that should be called directly from
  758. // any of the AST printing functions in nodes.go.
  759. //
  760. // Whitespace is accumulated until a non-whitespace token appears. Any
  761. // comments that need to appear before that token are printed first,
  762. // taking into account the amount and structure of any pending white-
  763. // space for best comment placement. Then, any leftover whitespace is
  764. // printed, followed by the actual token.
  765. //
  766. func (p *printer) print(args ...interface{}) {
  767. for _, arg := range args {
  768. // information about the current arg
  769. var data string
  770. var isLit bool
  771. var impliedSemi bool // value for p.impliedSemi after this arg
  772. // record previous opening token, if any
  773. switch p.lastTok {
  774. case token.ILLEGAL:
  775. // ignore (white space)
  776. case token.LPAREN, token.LBRACK:
  777. p.prevOpen = p.lastTok
  778. default:
  779. // other tokens followed any opening token
  780. p.prevOpen = token.ILLEGAL
  781. }
  782. switch x := arg.(type) {
  783. case pmode:
  784. // toggle printer mode
  785. p.mode ^= x
  786. continue
  787. case whiteSpace:
  788. if x == ignore {
  789. // don't add ignore's to the buffer; they
  790. // may screw up "correcting" unindents (see
  791. // LabeledStmt)
  792. continue
  793. }
  794. i := len(p.wsbuf)
  795. if i == cap(p.wsbuf) {
  796. // Whitespace sequences are very short so this should
  797. // never happen. Handle gracefully (but possibly with
  798. // bad comment placement) if it does happen.
  799. p.writeWhitespace(i)
  800. i = 0
  801. }
  802. p.wsbuf = p.wsbuf[0 : i+1]
  803. p.wsbuf[i] = x
  804. if x == newline || x == formfeed {
  805. // newlines affect the current state (p.impliedSemi)
  806. // and not the state after printing arg (impliedSemi)
  807. // because comments can be interspersed before the arg
  808. // in this case
  809. p.impliedSemi = false
  810. }
  811. p.lastTok = token.ILLEGAL
  812. continue
  813. case *ast.Ident:
  814. data = x.Name
  815. impliedSemi = true
  816. p.lastTok = token.IDENT
  817. case *ast.BasicLit:
  818. data = x.Value
  819. isLit = true
  820. impliedSemi = true
  821. p.lastTok = x.Kind
  822. case token.Token:
  823. s := x.String()
  824. if mayCombine(p.lastTok, s[0]) {
  825. // the previous and the current token must be
  826. // separated by a blank otherwise they combine
  827. // into a different incorrect token sequence
  828. // (except for token.INT followed by a '.' this
  829. // should never happen because it is taken care
  830. // of via binary expression formatting)
  831. if len(p.wsbuf) != 0 {
  832. p.internalError("whitespace buffer not empty")
  833. }
  834. p.wsbuf = p.wsbuf[0:1]
  835. p.wsbuf[0] = ' '
  836. }
  837. data = s
  838. // some keywords followed by a newline imply a semicolon
  839. switch x {
  840. case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
  841. token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
  842. impliedSemi = true
  843. }
  844. p.lastTok = x
  845. case token.Pos:
  846. if x.IsValid() {
  847. p.pos = p.posFor(x) // accurate position of next item
  848. }
  849. continue
  850. case string:
  851. // incorrect AST - print error message
  852. data = x
  853. isLit = true
  854. impliedSemi = true
  855. p.lastTok = token.STRING
  856. default:
  857. fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg)
  858. panic("go/printer type")
  859. }
  860. // data != ""
  861. next := p.pos // estimated/accurate position of next item
  862. wroteNewline, droppedFF := p.flush(next, p.lastTok)
  863. // intersperse extra newlines if present in the source and
  864. // if they don't cause extra semicolons (don't do this in
  865. // flush as it will cause extra newlines at the end of a file)
  866. if !p.impliedSemi {
  867. n := nlimit(next.Line - p.pos.Line)
  868. // don't exceed maxNewlines if we already wrote one
  869. if wroteNewline && n == maxNewlines {
  870. n = maxNewlines - 1
  871. }
  872. if n > 0 {
  873. ch := byte('\n')
  874. if droppedFF {
  875. ch = '\f' // use formfeed since we dropped one before
  876. }
  877. p.writeByte(ch, n)
  878. impliedSemi = false
  879. }
  880. }
  881. // the next token starts now - record its line number if requested
  882. if p.linePtr != nil {
  883. *p.linePtr = p.out.Line
  884. p.linePtr = nil
  885. }
  886. p.writeString(next, data, isLit)
  887. p.impliedSemi = impliedSemi
  888. }
  889. }
  890. // flush prints any pending comments and whitespace occurring textually
  891. // before the position of the next token tok. The flush result indicates
  892. // if a newline was written or if a formfeed was dropped from the whitespace
  893. // buffer.
  894. //
  895. func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
  896. if p.commentBefore(next) {
  897. // if there are comments before the next item, intersperse them
  898. wroteNewline, droppedFF = p.intersperseComments(next, tok)
  899. } else {
  900. // otherwise, write any leftover whitespace
  901. p.writeWhitespace(len(p.wsbuf))
  902. }
  903. return
  904. }
  905. // getNode returns the ast.CommentGroup associated with n, if any.
  906. func getDoc(n ast.Node) *ast.CommentGroup {
  907. switch n := n.(type) {
  908. case *ast.Field:
  909. return n.Doc
  910. case *ast.ImportSpec:
  911. return n.Doc
  912. case *ast.ValueSpec:
  913. return n.Doc
  914. case *ast.TypeSpec:
  915. return n.Doc
  916. case *ast.GenDecl:
  917. return n.Doc
  918. case *ast.FuncDecl:
  919. return n.Doc
  920. case *ast.File:
  921. return n.Doc
  922. }
  923. return nil
  924. }
  925. func (p *printer) printNode(node interface{}) error {
  926. // unpack *CommentedNode, if any
  927. var comments []*ast.CommentGroup
  928. if cnode, ok := node.(*CommentedNode); ok {
  929. node = cnode.Node
  930. comments = cnode.Comments
  931. }
  932. if comments != nil {
  933. // commented node - restrict comment list to relevant range
  934. n, ok := node.(ast.Node)
  935. if !ok {
  936. goto unsupported
  937. }
  938. beg := n.Pos()
  939. end := n.End()
  940. // if the node has associated documentation,
  941. // include that commentgroup in the range
  942. // (the comment list is sorted in the order
  943. // of the comment appearance in the source code)
  944. if doc := getDoc(n); doc != nil {
  945. beg = doc.Pos()
  946. }
  947. // token.Pos values are global offsets, we can
  948. // compare them directly
  949. i := 0
  950. for i < len(comments) && comments[i].End() < beg {
  951. i++
  952. }
  953. j := i
  954. for j < len(comments) && comments[j].Pos() < end {
  955. j++
  956. }
  957. if i < j {
  958. p.comments = comments[i:j]
  959. }
  960. } else if n, ok := node.(*ast.File); ok {
  961. // use ast.File comments, if any
  962. p.comments = n.Comments
  963. }
  964. // if there are no comments, use node comments
  965. p.useNodeComments = p.comments == nil
  966. // get comments ready for use
  967. p.nextComment()
  968. // format node
  969. switch n := node.(type) {
  970. case ast.Expr:
  971. p.expr(n)
  972. case ast.Stmt:
  973. // A labeled statement will un-indent to position the label.
  974. // Set p.indent to 1 so we don't get indent "underflow".
  975. if _, ok := n.(*ast.LabeledStmt); ok {
  976. p.indent = 1
  977. }
  978. p.stmt(n, false)
  979. case ast.Decl:
  980. p.decl(n)
  981. case ast.Spec:
  982. p.spec(n, 1, false)
  983. case []ast.Stmt:
  984. // A labeled statement will un-indent to position the label.
  985. // Set p.indent to 1 so we don't get indent "underflow".
  986. for _, s := range n {
  987. if _, ok := s.(*ast.LabeledStmt); ok {
  988. p.indent = 1
  989. }
  990. }
  991. p.stmtList(n, 0, false)
  992. case []ast.Decl:
  993. p.declList(n)
  994. case *ast.File:
  995. p.file(n)
  996. default:
  997. goto unsupported
  998. }
  999. return nil
  1000. unsupported:
  1001. return fmt.Errorf("go/printer: unsupported node type %T", node)
  1002. }
  1003. // ----------------------------------------------------------------------------
  1004. // Trimmer
  1005. // A trimmer is an io.Writer filter for stripping tabwriter.Escape
  1006. // characters, trailing blanks and tabs, and for converting formfeed
  1007. // and vtab characters into newlines and htabs (in case no tabwriter
  1008. // is used). Text bracketed by tabwriter.Escape characters is passed
  1009. // through unchanged.
  1010. //
  1011. type trimmer struct {
  1012. output io.Writer
  1013. state int
  1014. space []byte
  1015. }
  1016. // trimmer is implemented as a state machine.
  1017. // It can be in one of the following states:
  1018. const (
  1019. inSpace = iota // inside space
  1020. inEscape // inside text bracketed by tabwriter.Escapes
  1021. inText // inside text
  1022. )
  1023. func (p *trimmer) resetSpace() {
  1024. p.state = inSpace
  1025. p.space = p.space[0:0]
  1026. }
  1027. // Design note: It is tempting to eliminate extra blanks occurring in
  1028. // whitespace in this function as it could simplify some
  1029. // of the blanks logic in the node printing functions.
  1030. // However, this would mess up any formatting done by
  1031. // the tabwriter.
  1032. var aNewline = []byte("\n")
  1033. func (p *trimmer) Write(data []byte) (n int, err error) {
  1034. // invariants:
  1035. // p.state == inSpace:
  1036. // p.space is unwritten
  1037. // p.state == inEscape, inText:
  1038. // data[m:n] is unwritten
  1039. m := 0
  1040. var b byte
  1041. for n, b = range data {
  1042. if b == '\v' {
  1043. b = '\t' // convert to htab
  1044. }
  1045. switch p.state {
  1046. case inSpace:
  1047. switch b {
  1048. case '\t', ' ':
  1049. p.space = append(p.space, b)
  1050. case '\n', '\f':
  1051. p.resetSpace() // discard trailing space
  1052. _, err = p.output.Write(aNewline)
  1053. case tabwriter.Escape:
  1054. _, err = p.output.Write(p.space)
  1055. p.state = inEscape
  1056. m = n + 1 // +1: skip tabwriter.Escape
  1057. default:
  1058. _, err = p.output.Write(p.space)
  1059. p.state = inText
  1060. m = n
  1061. }
  1062. case inEscape:
  1063. if b == tabwriter.Escape {
  1064. _, err = p.output.Write(data[m:n])
  1065. p.resetSpace()
  1066. }
  1067. case inText:
  1068. switch b {
  1069. case '\t', ' ':
  1070. _, err = p.output.Write(data[m:n])
  1071. p.resetSpace()
  1072. p.space = append(p.space, b)
  1073. case '\n', '\f':
  1074. _, err = p.output.Write(data[m:n])
  1075. p.resetSpace()
  1076. _, err = p.output.Write(aNewline)
  1077. case tabwriter.Escape:
  1078. _, err = p.output.Write(data[m:n])
  1079. p.state = inEscape
  1080. m = n + 1 // +1: skip tabwriter.Escape
  1081. }
  1082. default:
  1083. panic("unreachable")
  1084. }
  1085. if err != nil {
  1086. return
  1087. }
  1088. }
  1089. n = len(data)
  1090. switch p.state {
  1091. case inEscape, inText:
  1092. _, err = p.output.Write(data[m:n])
  1093. p.resetSpace()
  1094. }
  1095. return
  1096. }
  1097. // ----------------------------------------------------------------------------
  1098. // Public interface
  1099. // A Mode value is a set of flags (or 0). They control printing.
  1100. type Mode uint
  1101. const (
  1102. RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
  1103. TabIndent // use tabs for indentation independent of UseSpaces
  1104. UseSpaces // use spaces instead of tabs for alignment
  1105. SourcePos // emit //line comments to preserve original source positions
  1106. )
  1107. // A Config node controls the output of Fprint.
  1108. type Config struct {
  1109. Mode Mode // default: 0
  1110. Tabwidth int // default: 8
  1111. Indent int // default: 0 (all code is indented at least by this much)
  1112. }
  1113. // fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
  1114. func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) {
  1115. // print node
  1116. var p printer
  1117. p.init(cfg, fset, nodeSizes)
  1118. if err = p.printNode(node); err != nil {
  1119. return
  1120. }
  1121. // print outstanding comments
  1122. p.impliedSemi = false // EOF acts like a newline
  1123. p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
  1124. // redirect output through a trimmer to eliminate trailing whitespace
  1125. // (Input to a tabwriter must be untrimmed since trailing tabs provide
  1126. // formatting information. The tabwriter could provide trimming
  1127. // functionality but no tabwriter is used when RawFormat is set.)
  1128. output = &trimmer{output: output}
  1129. // redirect output through a tabwriter if necessary
  1130. if cfg.Mode&RawFormat == 0 {
  1131. minwidth := cfg.Tabwidth
  1132. padchar := byte('\t')
  1133. if cfg.Mode&UseSpaces != 0 {
  1134. padchar = ' '
  1135. }
  1136. twmode := tabwriter.DiscardEmptyColumns
  1137. if cfg.Mode&TabIndent != 0 {
  1138. minwidth = 0
  1139. twmode |= tabwriter.TabIndent
  1140. }
  1141. output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
  1142. }
  1143. // write printer result via tabwriter/trimmer to output
  1144. if _, err = output.Write(p.output); err != nil {
  1145. return
  1146. }
  1147. // flush tabwriter, if any
  1148. if tw, _ := output.(*tabwriter.Writer); tw != nil {
  1149. err = tw.Flush()
  1150. }
  1151. return
  1152. }
  1153. // A CommentedNode bundles an AST node and corresponding comments.
  1154. // It may be provided as argument to any of the Fprint functions.
  1155. //
  1156. type CommentedNode struct {
  1157. Node interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
  1158. Comments []*ast.CommentGroup
  1159. }
  1160. // Fprint "pretty-prints" an AST node to output for a given configuration cfg.
  1161. // Position information is interpreted relative to the file set fset.
  1162. // The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt,
  1163. // or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
  1164. //
  1165. func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
  1166. return cfg.fprint(output, fset, node, make(map[ast.Node]int))
  1167. }
  1168. // Fprint "pretty-prints" an AST node to output.
  1169. // It calls Config.Fprint with default settings.
  1170. //
  1171. func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
  1172. return (&Config{Tabwidth: 8}).Fprint(output, fset, node)
  1173. }