3 # This class implements a pretty printing algorithm. It finds line breaks and
4 # nice indentations for grouped structure.
6 # By default, the class assumes that primitive elements are strings and each
7 # byte in the strings have single column in width. But it can be used for
8 # other situations by giving suitable arguments for some methods:
9 # * newline object and space generation block for PrettyPrint.new
10 # * optional width argument for PrettyPrint#text
11 # * PrettyPrint#breakable
13 # There are several candidate uses:
14 # * text formatting using proportional fonts
15 # * multibyte characters which has columns different to number of bytes
16 # * non-string formatting
19 # * Box based formatting?
20 # * Other (better) model/algorithm?
23 # Christian Lindig, Strictly Pretty, March 2000,
24 # http://www.st.cs.uni-sb.de/~lindig/papers/#pretty
26 # Philip Wadler, A prettier printer, March 1998,
27 # http://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier
30 # Tanaka Akira <akr@m17n.org>
34 # This is a convenience method which is same as follows:
37 # q = PrettyPrint.new(output, maxwidth, newline, &genspace)
43 def PrettyPrint.format(output='', maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n})
44 q = PrettyPrint.new(output, maxwidth, newline, &genspace)
50 # This is similar to PrettyPrint::format but the result has no breaks.
52 # +maxwidth+, +newline+ and +genspace+ are ignored.
54 # The invocation of +breakable+ in the block doesn't break a line and is
55 # treated as just an invocation of +text+.
57 def PrettyPrint.singleline_format(output='', maxwidth=nil, newline=nil, genspace=nil)
58 q = SingleLine.new(output)
63 # Creates a buffer for pretty printing.
65 # +output+ is an output target. If it is not specified, '' is assumed. It
66 # should have a << method which accepts the first argument +obj+ of
67 # PrettyPrint#text, the first argument +sep+ of PrettyPrint#breakable, the
68 # first argument +newline+ of PrettyPrint.new, and the result of a given
69 # block for PrettyPrint.new.
71 # +maxwidth+ specifies maximum line length. If it is not specified, 79 is
72 # assumed. However actual outputs may overflow +maxwidth+ if long
73 # non-breakable texts are provided.
75 # +newline+ is used for line breaks. "\n" is used if it is not specified.
77 # The block is used to generate spaces. {|width| ' ' * width} is used if it
80 def initialize(output='', maxwidth=79, newline="\n", &genspace)
84 @genspace = genspace || lambda {|n| ' ' * n}
90 root_group = Group.new(0)
91 @group_stack = [root_group]
92 @group_queue = GroupQueue.new(root_group)
95 attr_reader :output, :maxwidth, :newline, :genspace
96 attr_reader :indent, :group_queue
102 # first? is a predicate to test the call is a first call to first? with
105 # It is useful to format comma separated values as:
107 # q.group(1, '[', ']') {
113 # ... pretty printing yyy ...
117 # first? is obsoleted in 1.8.2.
120 warn "PrettyPrint#first? is obsoleted at 1.8.2."
124 def break_outmost_groups
125 while @maxwidth < @output_width + @buffer_width
126 return unless group = @group_queue.deq
127 until group.breakables.empty?
129 @output_width = data.output(@output, @output_width)
130 @buffer_width -= data.width
132 while !@buffer.empty? && Text === @buffer.first
134 @output_width = text.output(@output, @output_width)
135 @buffer_width -= text.width
140 # This adds +obj+ as a text of +width+ columns in width.
142 # If +width+ is not specified, obj.length is used.
144 def text(obj, width=obj.length)
147 @output_width += width
155 @buffer_width += width
160 def fill_breakable(sep=' ', width=sep.length)
161 group { breakable sep, width }
164 # This tells "you can break a line here if necessary", and a +width+\-column
165 # text +sep+ is inserted if a line is not broken at the point.
167 # If +sep+ is not specified, " " is used.
169 # If +width+ is not specified, +sep.length+ is used. You will have to
170 # specify this when +sep+ is a multibyte character, for example.
172 def breakable(sep=' ', width=sep.length)
173 group = @group_stack.last
177 @output << @genspace.call(@indent)
178 @output_width = @indent
181 @buffer << Breakable.new(sep, width, self)
182 @buffer_width += width
187 # Groups line break hints added in the block. The line break hints are all
190 # If +indent+ is specified, the method call is regarded as nested by
191 # nest(indent) { ... }.
193 # If +open_obj+ is specified, <tt>text open_obj, open_width</tt> is called
194 # before grouping. If +close_obj+ is specified, <tt>text close_obj,
195 # close_width</tt> is called after grouping.
197 def group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length)
198 text open_obj, open_width
204 text close_obj, close_width
208 group = Group.new(@group_stack.last.depth + 1)
209 @group_stack.push group
210 @group_queue.enq group
215 if group.breakables.empty?
216 @group_queue.delete group
221 # Increases left margin after newline with +indent+ for line breaks added in
233 # outputs buffered data.
237 @output_width = data.output(@output, @output_width)
250 def output(out, output_width)
251 @objs.each {|obj| out << obj}
252 output_width + @width
262 def initialize(sep, width, q)
267 @group = q.current_group
268 @group.breakables.push self
270 attr_reader :obj, :width, :indent
272 def output(out, output_width)
273 @group.breakables.shift
276 out << @pp.genspace.call(@indent)
279 @pp.group_queue.delete @group if @group.breakables.empty?
281 output_width + @width
287 def initialize(depth)
292 attr_reader :depth, :breakables
313 def initialize(*groups)
315 groups.each {|g| enq g}
320 @queue << [] until depth < @queue.length
321 @queue[depth] << group
326 (gs.length-1).downto(0) {|i|
327 unless gs[i].breakables.empty?
328 group = gs.slice!(i, 1).first
333 gs.each {|group| group.break}
340 @queue[group.depth].delete(group)
345 def initialize(output, maxwidth=nil, newline=nil)
350 def text(obj, width=nil)
354 def breakable(sep=' ', width=nil)
362 def group(indent=nil, open_obj='', close_obj='', open_width=nil, close_width=nil)
384 class WadlerExample < Test::Unit::TestCase # :nodoc:
386 @tree = Tree.new("aaaa", Tree.new("bbbbb", Tree.new("ccc"),
389 Tree.new("ffff", Tree.new("gg"),
395 PrettyPrint.format('', width) {|hello|
401 hello.breakable; hello.text 'a'
403 hello.breakable; hello.text 'b'
405 hello.breakable; hello.text 'c'
407 hello.breakable; hello.text 'd'
413 expected = <<'End'.chomp
420 assert_equal(expected, hello(0))
421 assert_equal(expected, hello(6))
425 expected = <<'End'.chomp
431 assert_equal(expected, hello(7))
432 assert_equal(expected, hello(8))
436 expected = <<'End'.chomp
441 out = hello(9); assert_equal(expected, out)
442 out = hello(10); assert_equal(expected, out)
446 expected = <<'End'.chomp
450 assert_equal(expected, hello(11))
451 assert_equal(expected, hello(12))
455 expected = <<'End'.chomp
458 assert_equal(expected, hello(13))
462 PrettyPrint.format('', width) {|q| @tree.show(q)}
466 expected = <<'End'.chomp
474 assert_equal(expected, tree(0))
475 assert_equal(expected, tree(19))
479 expected = <<'End'.chomp
486 assert_equal(expected, tree(20))
487 assert_equal(expected, tree(22))
491 expected = <<'End'.chomp
496 assert_equal(expected, tree(23))
497 assert_equal(expected, tree(43))
501 assert_equal(<<'End'.chomp, tree(44))
502 aaaa[bbbbb[ccc, dd], eee, ffff[gg, hhh, ii]]
507 PrettyPrint.format('', width) {|q| @tree.altshow(q)}
510 def test_tree_alt_00_18
511 expected = <<'End'.chomp
525 assert_equal(expected, tree_alt(0))
526 assert_equal(expected, tree_alt(18))
529 def test_tree_alt_19_20
530 expected = <<'End'.chomp
541 assert_equal(expected, tree_alt(19))
542 assert_equal(expected, tree_alt(20))
545 def test_tree_alt_20_49
546 expected = <<'End'.chomp
553 assert_equal(expected, tree_alt(21))
554 assert_equal(expected, tree_alt(49))
558 expected = <<'End'.chomp
559 aaaa[ bbbbb[ ccc, dd ], eee, ffff[ gg, hhh, ii ] ]
561 assert_equal(expected, tree_alt(50))
565 def initialize(string, *children)
573 q.nest(@string.length) {
574 unless @children.empty?
597 unless @children.empty?
621 class StrictPrettyExample < Test::Unit::TestCase # :nodoc:
623 PrettyPrint.format('', width) {|q|
626 q.text "if"; q.breakable;
629 q.group {q.text "a"; q.breakable; q.text "=="}
630 q.breakable; q.text "b"}}}}
633 q.text "then"; q.breakable;
636 q.group {q.text "a"; q.breakable; q.text "<<"}
637 q.breakable; q.text "2"}}}}
640 q.text "else"; q.breakable;
643 q.group {q.text "a"; q.breakable; q.text "+"}
644 q.breakable; q.text "b"}}}}}
649 expected = <<'End'.chomp
663 assert_equal(expected, prog(0))
664 assert_equal(expected, prog(4))
668 expected = <<'End'.chomp
681 assert_equal(expected, prog(5))
685 expected = <<'End'.chomp
696 assert_equal(expected, prog(6))
700 expected = <<'End'.chomp
710 assert_equal(expected, prog(7))
714 expected = <<'End'.chomp
722 assert_equal(expected, prog(8))
726 expected = <<'End'.chomp
733 assert_equal(expected, prog(9))
737 expected = <<'End'.chomp
743 assert_equal(expected, prog(10))
747 expected = <<'End'.chomp
752 assert_equal(expected, prog(11))
753 assert_equal(expected, prog(15))
754 assert_equal(expected, prog(31))
758 expected = <<'End'.chomp
759 if a == b then a << 2 else a + b
761 assert_equal(expected, prog(32))
766 class TailGroup < Test::Unit::TestCase # :nodoc:
768 out = PrettyPrint.format('', 10) {|q|
782 assert_equal("abc defghi\njkl", out)
786 class NonString < Test::Unit::TestCase # :nodoc:
788 PrettyPrint.format([], width, 'newline', lambda {|n| "#{n} spaces"}) {|q|
796 assert_equal([3, "newline", "0 spaces", 3], format(6))
800 assert_equal([3, 1, 3], format(7))
805 class Fill < Test::Unit::TestCase # :nodoc:
807 PrettyPrint.format('', width) {|q|
827 expected = <<'End'.chomp
836 assert_equal(expected, format(0))
837 assert_equal(expected, format(6))
841 expected = <<'End'.chomp
847 assert_equal(expected, format(7))
848 assert_equal(expected, format(10))
852 expected = <<'End'.chomp
857 assert_equal(expected, format(11))
858 assert_equal(expected, format(14))
862 expected = <<'End'.chomp
866 assert_equal(expected, format(15))
867 assert_equal(expected, format(18))
871 expected = <<'End'.chomp
875 assert_equal(expected, format(19))
876 assert_equal(expected, format(22))
880 expected = <<'End'.chomp
881 abc def ghi jkl mno pqr
884 assert_equal(expected, format(23))
885 assert_equal(expected, format(26))
889 expected = <<'End'.chomp
890 abc def ghi jkl mno pqr stu
892 assert_equal(expected, format(27))