PageRenderTime 63ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/ruby/1.8/prettyprint.rb

https://bitbucket.org/nicksieger/jruby
Ruby | 896 lines | 706 code | 83 blank | 107 comment | 26 complexity | cedbd04b5d93edf5d0e36dc4b3a72f2b MD5 | raw file
Possible License(s): GPL-3.0, JSON
  1. # $Id$
  2. # This class implements a pretty printing algorithm. It finds line breaks and
  3. # nice indentations for grouped structure.
  4. #
  5. # By default, the class assumes that primitive elements are strings and each
  6. # byte in the strings have single column in width. But it can be used for
  7. # other situations by giving suitable arguments for some methods:
  8. # * newline object and space generation block for PrettyPrint.new
  9. # * optional width argument for PrettyPrint#text
  10. # * PrettyPrint#breakable
  11. #
  12. # There are several candidate uses:
  13. # * text formatting using proportional fonts
  14. # * multibyte characters which has columns different to number of bytes
  15. # * non-string formatting
  16. #
  17. # == Bugs
  18. # * Box based formatting?
  19. # * Other (better) model/algorithm?
  20. #
  21. # == References
  22. # Christian Lindig, Strictly Pretty, March 2000,
  23. # http://www.st.cs.uni-sb.de/~lindig/papers/#pretty
  24. #
  25. # Philip Wadler, A prettier printer, March 1998,
  26. # http://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier
  27. #
  28. # == Author
  29. # Tanaka Akira <akr@m17n.org>
  30. #
  31. class PrettyPrint
  32. # This is a convenience method which is same as follows:
  33. #
  34. # begin
  35. # q = PrettyPrint.new(output, maxwidth, newline, &genspace)
  36. # ...
  37. # q.flush
  38. # output
  39. # end
  40. #
  41. def PrettyPrint.format(output='', maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n})
  42. q = PrettyPrint.new(output, maxwidth, newline, &genspace)
  43. yield q
  44. q.flush
  45. output
  46. end
  47. # This is similar to PrettyPrint::format but the result has no breaks.
  48. #
  49. # +maxwidth+, +newline+ and +genspace+ are ignored.
  50. #
  51. # The invocation of +breakable+ in the block doesn't break a line and is
  52. # treated as just an invocation of +text+.
  53. #
  54. def PrettyPrint.singleline_format(output='', maxwidth=nil, newline=nil, genspace=nil)
  55. q = SingleLine.new(output)
  56. yield q
  57. output
  58. end
  59. # Creates a buffer for pretty printing.
  60. #
  61. # +output+ is an output target. If it is not specified, '' is assumed. It
  62. # should have a << method which accepts the first argument +obj+ of
  63. # PrettyPrint#text, the first argument +sep+ of PrettyPrint#breakable, the
  64. # first argument +newline+ of PrettyPrint.new, and the result of a given
  65. # block for PrettyPrint.new.
  66. #
  67. # +maxwidth+ specifies maximum line length. If it is not specified, 79 is
  68. # assumed. However actual outputs may overflow +maxwidth+ if long
  69. # non-breakable texts are provided.
  70. #
  71. # +newline+ is used for line breaks. "\n" is used if it is not specified.
  72. #
  73. # The block is used to generate spaces. {|width| ' ' * width} is used if it
  74. # is not given.
  75. #
  76. def initialize(output='', maxwidth=79, newline="\n", &genspace)
  77. @output = output
  78. @maxwidth = maxwidth
  79. @newline = newline
  80. @genspace = genspace || lambda {|n| ' ' * n}
  81. @output_width = 0
  82. @buffer_width = 0
  83. @buffer = []
  84. root_group = Group.new(0)
  85. @group_stack = [root_group]
  86. @group_queue = GroupQueue.new(root_group)
  87. @indent = 0
  88. end
  89. attr_reader :output, :maxwidth, :newline, :genspace
  90. attr_reader :indent, :group_queue
  91. def current_group
  92. @group_stack.last
  93. end
  94. # first? is a predicate to test the call is a first call to first? with
  95. # current group.
  96. #
  97. # It is useful to format comma separated values as:
  98. #
  99. # q.group(1, '[', ']') {
  100. # xxx.each {|yyy|
  101. # unless q.first?
  102. # q.text ','
  103. # q.breakable
  104. # end
  105. # ... pretty printing yyy ...
  106. # }
  107. # }
  108. #
  109. # first? is obsoleted in 1.8.2.
  110. #
  111. def first?
  112. warn "PrettyPrint#first? is obsoleted at 1.8.2."
  113. current_group.first?
  114. end
  115. def break_outmost_groups
  116. while @maxwidth < @output_width + @buffer_width
  117. return unless group = @group_queue.deq
  118. until group.breakables.empty?
  119. data = @buffer.shift
  120. @output_width = data.output(@output, @output_width)
  121. @buffer_width -= data.width
  122. end
  123. while !@buffer.empty? && Text === @buffer.first
  124. text = @buffer.shift
  125. @output_width = text.output(@output, @output_width)
  126. @buffer_width -= text.width
  127. end
  128. end
  129. end
  130. # This adds +obj+ as a text of +width+ columns in width.
  131. #
  132. # If +width+ is not specified, obj.length is used.
  133. #
  134. def text(obj, width=obj.length)
  135. if @buffer.empty?
  136. @output << obj
  137. @output_width += width
  138. else
  139. text = @buffer.last
  140. unless Text === text
  141. text = Text.new
  142. @buffer << text
  143. end
  144. text.add(obj, width)
  145. @buffer_width += width
  146. break_outmost_groups
  147. end
  148. end
  149. def fill_breakable(sep=' ', width=sep.length)
  150. group { breakable sep, width }
  151. end
  152. # This tells "you can break a line here if necessary", and a +width+\-column
  153. # text +sep+ is inserted if a line is not broken at the point.
  154. #
  155. # If +sep+ is not specified, " " is used.
  156. #
  157. # If +width+ is not specified, +sep.length+ is used. You will have to
  158. # specify this when +sep+ is a multibyte character, for example.
  159. #
  160. def breakable(sep=' ', width=sep.length)
  161. group = @group_stack.last
  162. if group.break?
  163. flush
  164. @output << @newline
  165. @output << @genspace.call(@indent)
  166. @output_width = @indent
  167. @buffer_width = 0
  168. else
  169. @buffer << Breakable.new(sep, width, self)
  170. @buffer_width += width
  171. break_outmost_groups
  172. end
  173. end
  174. # Groups line break hints added in the block. The line break hints are all
  175. # to be used or not.
  176. #
  177. # If +indent+ is specified, the method call is regarded as nested by
  178. # nest(indent) { ... }.
  179. #
  180. # If +open_obj+ is specified, <tt>text open_obj, open_width</tt> is called
  181. # before grouping. If +close_obj+ is specified, <tt>text close_obj,
  182. # close_width</tt> is called after grouping.
  183. #
  184. def group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length)
  185. text open_obj, open_width
  186. group_sub {
  187. nest(indent) {
  188. yield
  189. }
  190. }
  191. text close_obj, close_width
  192. end
  193. def group_sub
  194. group = Group.new(@group_stack.last.depth + 1)
  195. @group_stack.push group
  196. @group_queue.enq group
  197. begin
  198. yield
  199. ensure
  200. @group_stack.pop
  201. if group.breakables.empty?
  202. @group_queue.delete group
  203. end
  204. end
  205. end
  206. # Increases left margin after newline with +indent+ for line breaks added in
  207. # the block.
  208. #
  209. def nest(indent)
  210. @indent += indent
  211. begin
  212. yield
  213. ensure
  214. @indent -= indent
  215. end
  216. end
  217. # outputs buffered data.
  218. #
  219. def flush
  220. @buffer.each {|data|
  221. @output_width = data.output(@output, @output_width)
  222. }
  223. @buffer.clear
  224. @buffer_width = 0
  225. end
  226. class Text
  227. def initialize
  228. @objs = []
  229. @width = 0
  230. end
  231. attr_reader :width
  232. def output(out, output_width)
  233. @objs.each {|obj| out << obj}
  234. output_width + @width
  235. end
  236. def add(obj, width)
  237. @objs << obj
  238. @width += width
  239. end
  240. end
  241. class Breakable
  242. def initialize(sep, width, q)
  243. @obj = sep
  244. @width = width
  245. @pp = q
  246. @indent = q.indent
  247. @group = q.current_group
  248. @group.breakables.push self
  249. end
  250. attr_reader :obj, :width, :indent
  251. def output(out, output_width)
  252. @group.breakables.shift
  253. if @group.break?
  254. out << @pp.newline
  255. out << @pp.genspace.call(@indent)
  256. @indent
  257. else
  258. @pp.group_queue.delete @group if @group.breakables.empty?
  259. out << @obj
  260. output_width + @width
  261. end
  262. end
  263. end
  264. class Group
  265. def initialize(depth)
  266. @depth = depth
  267. @breakables = []
  268. @break = false
  269. end
  270. attr_reader :depth, :breakables
  271. def break
  272. @break = true
  273. end
  274. def break?
  275. @break
  276. end
  277. def first?
  278. if defined? @first
  279. false
  280. else
  281. @first = false
  282. true
  283. end
  284. end
  285. end
  286. class GroupQueue
  287. def initialize(*groups)
  288. @queue = []
  289. groups.each {|g| enq g}
  290. end
  291. def enq(group)
  292. depth = group.depth
  293. @queue << [] until depth < @queue.length
  294. @queue[depth] << group
  295. end
  296. def deq
  297. @queue.each {|gs|
  298. (gs.length-1).downto(0) {|i|
  299. unless gs[i].breakables.empty?
  300. group = gs.slice!(i, 1).first
  301. group.break
  302. return group
  303. end
  304. }
  305. gs.each {|group| group.break}
  306. gs.clear
  307. }
  308. return nil
  309. end
  310. def delete(group)
  311. @queue[group.depth].delete(group)
  312. end
  313. end
  314. class SingleLine
  315. def initialize(output, maxwidth=nil, newline=nil)
  316. @output = output
  317. @first = [true]
  318. end
  319. def text(obj, width=nil)
  320. @output << obj
  321. end
  322. def breakable(sep=' ', width=nil)
  323. @output << sep
  324. end
  325. def nest(indent)
  326. yield
  327. end
  328. def group(indent=nil, open_obj='', close_obj='', open_width=nil, close_width=nil)
  329. @first.push true
  330. @output << open_obj
  331. yield
  332. @output << close_obj
  333. @first.pop
  334. end
  335. def flush
  336. end
  337. def first?
  338. result = @first[-1]
  339. @first[-1] = false
  340. result
  341. end
  342. end
  343. end
  344. if __FILE__ == $0
  345. require 'test/unit'
  346. class WadlerExample < Test::Unit::TestCase # :nodoc:
  347. def setup
  348. @tree = Tree.new("aaaa", Tree.new("bbbbb", Tree.new("ccc"),
  349. Tree.new("dd")),
  350. Tree.new("eee"),
  351. Tree.new("ffff", Tree.new("gg"),
  352. Tree.new("hhh"),
  353. Tree.new("ii")))
  354. end
  355. def hello(width)
  356. PrettyPrint.format('', width) {|hello|
  357. hello.group {
  358. hello.group {
  359. hello.group {
  360. hello.group {
  361. hello.text 'hello'
  362. hello.breakable; hello.text 'a'
  363. }
  364. hello.breakable; hello.text 'b'
  365. }
  366. hello.breakable; hello.text 'c'
  367. }
  368. hello.breakable; hello.text 'd'
  369. }
  370. }
  371. end
  372. def test_hello_00_06
  373. expected = <<'End'.chomp
  374. hello
  375. a
  376. b
  377. c
  378. d
  379. End
  380. assert_equal(expected, hello(0))
  381. assert_equal(expected, hello(6))
  382. end
  383. def test_hello_07_08
  384. expected = <<'End'.chomp
  385. hello a
  386. b
  387. c
  388. d
  389. End
  390. assert_equal(expected, hello(7))
  391. assert_equal(expected, hello(8))
  392. end
  393. def test_hello_09_10
  394. expected = <<'End'.chomp
  395. hello a b
  396. c
  397. d
  398. End
  399. out = hello(9); assert_equal(expected, out)
  400. out = hello(10); assert_equal(expected, out)
  401. end
  402. def test_hello_11_12
  403. expected = <<'End'.chomp
  404. hello a b c
  405. d
  406. End
  407. assert_equal(expected, hello(11))
  408. assert_equal(expected, hello(12))
  409. end
  410. def test_hello_13
  411. expected = <<'End'.chomp
  412. hello a b c d
  413. End
  414. assert_equal(expected, hello(13))
  415. end
  416. def tree(width)
  417. PrettyPrint.format('', width) {|q| @tree.show(q)}
  418. end
  419. def test_tree_00_19
  420. expected = <<'End'.chomp
  421. aaaa[bbbbb[ccc,
  422. dd],
  423. eee,
  424. ffff[gg,
  425. hhh,
  426. ii]]
  427. End
  428. assert_equal(expected, tree(0))
  429. assert_equal(expected, tree(19))
  430. end
  431. def test_tree_20_22
  432. expected = <<'End'.chomp
  433. aaaa[bbbbb[ccc, dd],
  434. eee,
  435. ffff[gg,
  436. hhh,
  437. ii]]
  438. End
  439. assert_equal(expected, tree(20))
  440. assert_equal(expected, tree(22))
  441. end
  442. def test_tree_23_43
  443. expected = <<'End'.chomp
  444. aaaa[bbbbb[ccc, dd],
  445. eee,
  446. ffff[gg, hhh, ii]]
  447. End
  448. assert_equal(expected, tree(23))
  449. assert_equal(expected, tree(43))
  450. end
  451. def test_tree_44
  452. assert_equal(<<'End'.chomp, tree(44))
  453. aaaa[bbbbb[ccc, dd], eee, ffff[gg, hhh, ii]]
  454. End
  455. end
  456. def tree_alt(width)
  457. PrettyPrint.format('', width) {|q| @tree.altshow(q)}
  458. end
  459. def test_tree_alt_00_18
  460. expected = <<'End'.chomp
  461. aaaa[
  462. bbbbb[
  463. ccc,
  464. dd
  465. ],
  466. eee,
  467. ffff[
  468. gg,
  469. hhh,
  470. ii
  471. ]
  472. ]
  473. End
  474. assert_equal(expected, tree_alt(0))
  475. assert_equal(expected, tree_alt(18))
  476. end
  477. def test_tree_alt_19_20
  478. expected = <<'End'.chomp
  479. aaaa[
  480. bbbbb[ ccc, dd ],
  481. eee,
  482. ffff[
  483. gg,
  484. hhh,
  485. ii
  486. ]
  487. ]
  488. End
  489. assert_equal(expected, tree_alt(19))
  490. assert_equal(expected, tree_alt(20))
  491. end
  492. def test_tree_alt_20_49
  493. expected = <<'End'.chomp
  494. aaaa[
  495. bbbbb[ ccc, dd ],
  496. eee,
  497. ffff[ gg, hhh, ii ]
  498. ]
  499. End
  500. assert_equal(expected, tree_alt(21))
  501. assert_equal(expected, tree_alt(49))
  502. end
  503. def test_tree_alt_50
  504. expected = <<'End'.chomp
  505. aaaa[ bbbbb[ ccc, dd ], eee, ffff[ gg, hhh, ii ] ]
  506. End
  507. assert_equal(expected, tree_alt(50))
  508. end
  509. class Tree # :nodoc:
  510. def initialize(string, *children)
  511. @string = string
  512. @children = children
  513. end
  514. def show(q)
  515. q.group {
  516. q.text @string
  517. q.nest(@string.length) {
  518. unless @children.empty?
  519. q.text '['
  520. q.nest(1) {
  521. first = true
  522. @children.each {|t|
  523. if first
  524. first = false
  525. else
  526. q.text ','
  527. q.breakable
  528. end
  529. t.show(q)
  530. }
  531. }
  532. q.text ']'
  533. end
  534. }
  535. }
  536. end
  537. def altshow(q)
  538. q.group {
  539. q.text @string
  540. unless @children.empty?
  541. q.text '['
  542. q.nest(2) {
  543. q.breakable
  544. first = true
  545. @children.each {|t|
  546. if first
  547. first = false
  548. else
  549. q.text ','
  550. q.breakable
  551. end
  552. t.altshow(q)
  553. }
  554. }
  555. q.breakable
  556. q.text ']'
  557. end
  558. }
  559. end
  560. end
  561. end
  562. class StrictPrettyExample < Test::Unit::TestCase # :nodoc:
  563. def prog(width)
  564. PrettyPrint.format('', width) {|q|
  565. q.group {
  566. q.group {q.nest(2) {
  567. q.text "if"; q.breakable;
  568. q.group {
  569. q.nest(2) {
  570. q.group {q.text "a"; q.breakable; q.text "=="}
  571. q.breakable; q.text "b"}}}}
  572. q.breakable
  573. q.group {q.nest(2) {
  574. q.text "then"; q.breakable;
  575. q.group {
  576. q.nest(2) {
  577. q.group {q.text "a"; q.breakable; q.text "<<"}
  578. q.breakable; q.text "2"}}}}
  579. q.breakable
  580. q.group {q.nest(2) {
  581. q.text "else"; q.breakable;
  582. q.group {
  583. q.nest(2) {
  584. q.group {q.text "a"; q.breakable; q.text "+"}
  585. q.breakable; q.text "b"}}}}}
  586. }
  587. end
  588. def test_00_04
  589. expected = <<'End'.chomp
  590. if
  591. a
  592. ==
  593. b
  594. then
  595. a
  596. <<
  597. 2
  598. else
  599. a
  600. +
  601. b
  602. End
  603. assert_equal(expected, prog(0))
  604. assert_equal(expected, prog(4))
  605. end
  606. def test_05
  607. expected = <<'End'.chomp
  608. if
  609. a
  610. ==
  611. b
  612. then
  613. a
  614. <<
  615. 2
  616. else
  617. a +
  618. b
  619. End
  620. assert_equal(expected, prog(5))
  621. end
  622. def test_06
  623. expected = <<'End'.chomp
  624. if
  625. a ==
  626. b
  627. then
  628. a <<
  629. 2
  630. else
  631. a +
  632. b
  633. End
  634. assert_equal(expected, prog(6))
  635. end
  636. def test_07
  637. expected = <<'End'.chomp
  638. if
  639. a ==
  640. b
  641. then
  642. a <<
  643. 2
  644. else
  645. a + b
  646. End
  647. assert_equal(expected, prog(7))
  648. end
  649. def test_08
  650. expected = <<'End'.chomp
  651. if
  652. a == b
  653. then
  654. a << 2
  655. else
  656. a + b
  657. End
  658. assert_equal(expected, prog(8))
  659. end
  660. def test_09
  661. expected = <<'End'.chomp
  662. if a == b
  663. then
  664. a << 2
  665. else
  666. a + b
  667. End
  668. assert_equal(expected, prog(9))
  669. end
  670. def test_10
  671. expected = <<'End'.chomp
  672. if a == b
  673. then
  674. a << 2
  675. else a + b
  676. End
  677. assert_equal(expected, prog(10))
  678. end
  679. def test_11_31
  680. expected = <<'End'.chomp
  681. if a == b
  682. then a << 2
  683. else a + b
  684. End
  685. assert_equal(expected, prog(11))
  686. assert_equal(expected, prog(15))
  687. assert_equal(expected, prog(31))
  688. end
  689. def test_32
  690. expected = <<'End'.chomp
  691. if a == b then a << 2 else a + b
  692. End
  693. assert_equal(expected, prog(32))
  694. end
  695. end
  696. class TailGroup < Test::Unit::TestCase # :nodoc:
  697. def test_1
  698. out = PrettyPrint.format('', 10) {|q|
  699. q.group {
  700. q.group {
  701. q.text "abc"
  702. q.breakable
  703. q.text "def"
  704. }
  705. q.group {
  706. q.text "ghi"
  707. q.breakable
  708. q.text "jkl"
  709. }
  710. }
  711. }
  712. assert_equal("abc defghi\njkl", out)
  713. end
  714. end
  715. class NonString < Test::Unit::TestCase # :nodoc:
  716. def format(width)
  717. PrettyPrint.format([], width, 'newline', lambda {|n| "#{n} spaces"}) {|q|
  718. q.text(3, 3)
  719. q.breakable(1, 1)
  720. q.text(3, 3)
  721. }
  722. end
  723. def test_6
  724. assert_equal([3, "newline", "0 spaces", 3], format(6))
  725. end
  726. def test_7
  727. assert_equal([3, 1, 3], format(7))
  728. end
  729. end
  730. class Fill < Test::Unit::TestCase # :nodoc:
  731. def format(width)
  732. PrettyPrint.format('', width) {|q|
  733. q.group {
  734. q.text 'abc'
  735. q.fill_breakable
  736. q.text 'def'
  737. q.fill_breakable
  738. q.text 'ghi'
  739. q.fill_breakable
  740. q.text 'jkl'
  741. q.fill_breakable
  742. q.text 'mno'
  743. q.fill_breakable
  744. q.text 'pqr'
  745. q.fill_breakable
  746. q.text 'stu'
  747. }
  748. }
  749. end
  750. def test_00_06
  751. expected = <<'End'.chomp
  752. abc
  753. def
  754. ghi
  755. jkl
  756. mno
  757. pqr
  758. stu
  759. End
  760. assert_equal(expected, format(0))
  761. assert_equal(expected, format(6))
  762. end
  763. def test_07_10
  764. expected = <<'End'.chomp
  765. abc def
  766. ghi jkl
  767. mno pqr
  768. stu
  769. End
  770. assert_equal(expected, format(7))
  771. assert_equal(expected, format(10))
  772. end
  773. def test_11_14
  774. expected = <<'End'.chomp
  775. abc def ghi
  776. jkl mno pqr
  777. stu
  778. End
  779. assert_equal(expected, format(11))
  780. assert_equal(expected, format(14))
  781. end
  782. def test_15_18
  783. expected = <<'End'.chomp
  784. abc def ghi jkl
  785. mno pqr stu
  786. End
  787. assert_equal(expected, format(15))
  788. assert_equal(expected, format(18))
  789. end
  790. def test_19_22
  791. expected = <<'End'.chomp
  792. abc def ghi jkl mno
  793. pqr stu
  794. End
  795. assert_equal(expected, format(19))
  796. assert_equal(expected, format(22))
  797. end
  798. def test_23_26
  799. expected = <<'End'.chomp
  800. abc def ghi jkl mno pqr
  801. stu
  802. End
  803. assert_equal(expected, format(23))
  804. assert_equal(expected, format(26))
  805. end
  806. def test_27
  807. expected = <<'End'.chomp
  808. abc def ghi jkl mno pqr stu
  809. End
  810. assert_equal(expected, format(27))
  811. end
  812. end
  813. end