syntax_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1132 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # This class implements a pretty printing algorithm. It finds line breaks and
4
+ # nice indentations for grouped structure.
5
+ #
6
+ # By default, the class assumes that primitive elements are strings and each
7
+ # byte in the strings is a single column in width. But it can be used for other
8
+ # situations by giving suitable arguments for some methods:
9
+ #
10
+ # * newline object and space generation block for PrettyPrint.new
11
+ # * optional width argument for PrettyPrint#text
12
+ # * PrettyPrint#breakable
13
+ #
14
+ # There are several candidate uses:
15
+ # * text formatting using proportional fonts
16
+ # * multibyte characters which has columns different to number of bytes
17
+ # * non-string formatting
18
+ #
19
+ # == Usage
20
+ #
21
+ # To use this module, you will need to generate a tree of print nodes that
22
+ # represent indentation and newline behavior before it gets sent to the printer.
23
+ # Each node has different semantics, depending on the desired output.
24
+ #
25
+ # The most basic node is a Text node. This represents plain text content that
26
+ # cannot be broken up even if it doesn't fit on one line. You would create one
27
+ # of those with the text method, as in:
28
+ #
29
+ # PrettyPrint.format { |q| q.text('my content') }
30
+ #
31
+ # No matter what the desired output width is, the output for the snippet above
32
+ # will always be the same.
33
+ #
34
+ # If you want to allow the printer to break up the content on the space
35
+ # character when there isn't enough width for the full string on the same line,
36
+ # you can use the Breakable and Group nodes. For example:
37
+ #
38
+ # PrettyPrint.format do |q|
39
+ # q.group do
40
+ # q.text('my')
41
+ # q.breakable
42
+ # q.text('content')
43
+ # end
44
+ # end
45
+ #
46
+ # Now, if everything fits on one line (depending on the maximum width specified)
47
+ # then it will be the same output as the first example. If, however, there is
48
+ # not enough room on the line, then you will get two lines of output, one for
49
+ # the first string and one for the second.
50
+ #
51
+ # There are other nodes for the print tree as well, described in the
52
+ # documentation below. They control alignment, indentation, conditional
53
+ # formatting, and more.
54
+ #
55
+ # == Bugs
56
+ # * Box based formatting?
57
+ #
58
+ # Report any bugs at http://bugs.ruby-lang.org
59
+ #
60
+ # == References
61
+ # Christian Lindig, Strictly Pretty, March 2000,
62
+ # https://lindig.github.io/papers/strictly-pretty-2000.pdf
63
+ #
64
+ # Philip Wadler, A prettier printer, March 1998,
65
+ # https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
66
+ #
67
+ # == Author
68
+ # Tanaka Akira <akr@fsij.org>
69
+ #
70
+ class PrettyPrint
71
+ # A node in the print tree that represents aligning nested nodes to a certain
72
+ # prefix width or string.
73
+ class Align
74
+ attr_reader :indent, :contents
75
+
76
+ def initialize(indent:, contents: [])
77
+ @indent = indent
78
+ @contents = contents
79
+ end
80
+
81
+ def pretty_print(q)
82
+ q.group(2, "align([", "])") do
83
+ q.seplist(contents) { |content| q.pp(content) }
84
+ end
85
+ end
86
+ end
87
+
88
+ # A node in the print tree that represents a place in the buffer that the
89
+ # content can be broken onto multiple lines.
90
+ class Breakable
91
+ attr_reader :separator, :width
92
+
93
+ def initialize(
94
+ separator = " ",
95
+ width = separator.length,
96
+ force: false,
97
+ indent: true
98
+ )
99
+ @separator = separator
100
+ @width = width
101
+ @force = force
102
+ @indent = indent
103
+ end
104
+
105
+ def force?
106
+ @force
107
+ end
108
+
109
+ def indent?
110
+ @indent
111
+ end
112
+
113
+ def pretty_print(q)
114
+ q.text("breakable")
115
+
116
+ attributes =
117
+ [("force=true" if force?), ("indent=false" unless indent?)].compact
118
+
119
+ if attributes.any?
120
+ q.text("(")
121
+ q.seplist(attributes, -> { q.text(", ") }) do |attribute|
122
+ q.text(attribute)
123
+ end
124
+ q.text(")")
125
+ end
126
+ end
127
+ end
128
+
129
+ # A node in the print tree that forces the surrounding group to print out in
130
+ # the "break" mode as opposed to the "flat" mode. Useful for when you need to
131
+ # force a newline into a group.
132
+ class BreakParent
133
+ def pretty_print(q)
134
+ q.text("break-parent")
135
+ end
136
+ end
137
+
138
+ # A node in the print tree that represents a group of items which the printer
139
+ # should try to fit onto one line. This is the basic command to tell the
140
+ # printer when to break. Groups are usually nested, and the printer will try
141
+ # to fit everything on one line, but if it doesn't fit it will break the
142
+ # outermost group first and try again. It will continue breaking groups until
143
+ # everything fits (or there are no more groups to break).
144
+ class Group
145
+ attr_reader :depth, :contents
146
+
147
+ def initialize(depth, contents: [])
148
+ @depth = depth
149
+ @contents = contents
150
+ @break = false
151
+ end
152
+
153
+ def break
154
+ @break = true
155
+ end
156
+
157
+ def break?
158
+ @break
159
+ end
160
+
161
+ def pretty_print(q)
162
+ q.group(2, "group([", "])") do
163
+ q.seplist(contents) { |content| q.pp(content) }
164
+ end
165
+ end
166
+ end
167
+
168
+ # A node in the print tree that represents printing one thing if the
169
+ # surrounding group node is broken and another thing if the surrounding group
170
+ # node is flat.
171
+ class IfBreak
172
+ attr_reader :break_contents, :flat_contents
173
+
174
+ def initialize(break_contents: [], flat_contents: [])
175
+ @break_contents = break_contents
176
+ @flat_contents = flat_contents
177
+ end
178
+
179
+ def pretty_print(q)
180
+ q.group(2, "if-break(", ")") do
181
+ q.breakable("")
182
+ q.group(2, "[", "],") do
183
+ q.seplist(break_contents) { |content| q.pp(content) }
184
+ end
185
+ q.breakable
186
+ q.group(2, "[", "]") do
187
+ q.seplist(flat_contents) { |content| q.pp(content) }
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ # A node in the print tree that is a variant of the Align node that indents
194
+ # its contents by one level.
195
+ class Indent
196
+ attr_reader :contents
197
+
198
+ def initialize(contents: [])
199
+ @contents = contents
200
+ end
201
+
202
+ def pretty_print(q)
203
+ q.group(2, "indent([", "])") do
204
+ q.seplist(contents) { |content| q.pp(content) }
205
+ end
206
+ end
207
+ end
208
+
209
+ # A node in the print tree that has its own special buffer for implementing
210
+ # content that should flush before any newline.
211
+ #
212
+ # Useful for implementating trailing content, as it's not always practical to
213
+ # constantly check where the line ends to avoid accidentally printing some
214
+ # content after a line suffix node.
215
+ class LineSuffix
216
+ attr_reader :contents
217
+
218
+ def initialize(contents: [])
219
+ @contents = contents
220
+ end
221
+
222
+ def pretty_print(q)
223
+ q.group(2, "line-suffix([", "])") do
224
+ q.seplist(contents) { |content| q.pp(content) }
225
+ end
226
+ end
227
+ end
228
+
229
+ # A node in the print tree that represents plain content that cannot be broken
230
+ # up (by default this assumes strings, but it can really be anything).
231
+ class Text
232
+ attr_reader :objects, :width
233
+
234
+ def initialize
235
+ @objects = []
236
+ @width = 0
237
+ end
238
+
239
+ def add(object: "", width: object.length)
240
+ @objects << object
241
+ @width += width
242
+ end
243
+
244
+ def pretty_print(q)
245
+ q.group(2, "text([", "])") do
246
+ q.seplist(objects) { |object| q.pp(object) }
247
+ end
248
+ end
249
+ end
250
+
251
+ # A node in the print tree that represents trimming all of the indentation of
252
+ # the current line, in the rare case that you need to ignore the indentation
253
+ # that you've already created. This node should be placed after a Breakable.
254
+ class Trim
255
+ def pretty_print(q)
256
+ q.text("trim")
257
+ end
258
+ end
259
+
260
+ # When building up the contents in the output buffer, it's convenient to be
261
+ # able to trim trailing whitespace before newlines. If the output object is a
262
+ # string or array or strings, then we can do this with some gsub calls. If
263
+ # not, then this effectively just wraps the output object and forwards on
264
+ # calls to <<.
265
+ module Buffer
266
+ # This is the default output buffer that provides a base implementation of
267
+ # trim! that does nothing. It's effectively a wrapper around whatever output
268
+ # object was given to the format command.
269
+ class DefaultBuffer
270
+ attr_reader :output
271
+
272
+ def initialize(output = [])
273
+ @output = output
274
+ end
275
+
276
+ def <<(object)
277
+ @output << object
278
+ end
279
+
280
+ def trim!
281
+ 0
282
+ end
283
+ end
284
+
285
+ # This is an output buffer that wraps a string output object. It provides a
286
+ # trim! method that trims off trailing whitespace from the string using
287
+ # gsub!.
288
+ class StringBuffer < DefaultBuffer
289
+ def initialize(output = "".dup)
290
+ super(output)
291
+ end
292
+
293
+ def trim!
294
+ length = output.length
295
+ output.gsub!(/[\t ]*\z/, "")
296
+ length - output.length
297
+ end
298
+ end
299
+
300
+ # This is an output buffer that wraps an array output object. It provides a
301
+ # trim! method that trims off trailing whitespace from the last element in
302
+ # the array if it's an unfrozen string using the same method as the
303
+ # StringBuffer.
304
+ class ArrayBuffer < DefaultBuffer
305
+ def initialize(output = [])
306
+ super(output)
307
+ end
308
+
309
+ def trim!
310
+ return 0 if output.empty?
311
+
312
+ trimmed = 0
313
+
314
+ while output.any? && output.last.is_a?(String) &&
315
+ output.last.match?(/\A[\t ]*\z/)
316
+ trimmed += output.pop.length
317
+ end
318
+
319
+ if output.any? && output.last.is_a?(String) && !output.last.frozen?
320
+ length = output.last.length
321
+ output.last.gsub!(/[\t ]*\z/, "")
322
+ trimmed += length - output.last.length
323
+ end
324
+
325
+ trimmed
326
+ end
327
+ end
328
+
329
+ # This is a switch for building the correct output buffer wrapper class for
330
+ # the given output object.
331
+ def self.for(output)
332
+ case output
333
+ when String
334
+ StringBuffer.new(output)
335
+ when Array
336
+ ArrayBuffer.new(output)
337
+ else
338
+ DefaultBuffer.new(output)
339
+ end
340
+ end
341
+ end
342
+
343
+ # PrettyPrint::SingleLine is used by PrettyPrint.singleline_format
344
+ #
345
+ # It is passed to be similar to a PrettyPrint object itself, by responding to
346
+ # all of the same print tree node builder methods, as well as the #flush
347
+ # method.
348
+ #
349
+ # The significant difference here is that there are no line breaks in the
350
+ # output. If an IfBreak node is used, only the flat contents are printed.
351
+ # LineSuffix nodes are printed at the end of the buffer when #flush is called.
352
+ class SingleLine
353
+ # The output object. It stores rendered text and should respond to <<.
354
+ attr_reader :output
355
+
356
+ # The current array of contents that the print tree builder methods should
357
+ # append to.
358
+ attr_reader :target
359
+
360
+ # A buffer output that wraps any calls to line_suffix that will be flushed
361
+ # at the end of printing.
362
+ attr_reader :line_suffixes
363
+
364
+ # Create a PrettyPrint::SingleLine object
365
+ #
366
+ # Arguments:
367
+ # * +output+ - String (or similar) to store rendered text. Needs to respond
368
+ # to '<<'.
369
+ # * +maxwidth+ - Argument position expected to be here for compatibility.
370
+ # This argument is a noop.
371
+ # * +newline+ - Argument position expected to be here for compatibility.
372
+ # This argument is a noop.
373
+ def initialize(output, maxwidth = nil, newline = nil)
374
+ @output = Buffer.for(output)
375
+ @target = @output
376
+ @line_suffixes = Buffer::ArrayBuffer.new
377
+ end
378
+
379
+ # Flushes the line suffixes onto the output buffer.
380
+ def flush
381
+ line_suffixes.output.each { |doc| output << doc }
382
+ end
383
+
384
+ # --------------------------------------------------------------------------
385
+ # Markers node builders
386
+ # --------------------------------------------------------------------------
387
+
388
+ # Appends +separator+ to the text to be output. By default +separator+ is
389
+ # ' '
390
+ #
391
+ # The +width+, +indent+, and +force+ arguments are here for compatibility.
392
+ # They are all noop arguments.
393
+ def breakable(
394
+ separator = " ",
395
+ width = separator.length,
396
+ indent: nil,
397
+ force: nil
398
+ )
399
+ target << separator
400
+ end
401
+
402
+ # Here for compatibility, does nothing.
403
+ def break_parent
404
+ end
405
+
406
+ # Appends +separator+ to the output buffer. +width+ is a noop here for
407
+ # compatibility.
408
+ def fill_breakable(separator = " ", width = separator.length)
409
+ target << separator
410
+ end
411
+
412
+ # Immediately trims the output buffer.
413
+ def trim
414
+ target.trim!
415
+ end
416
+
417
+ # ----------------------------------------------------------------------------
418
+ # Container node builders
419
+ # ----------------------------------------------------------------------------
420
+
421
+ # Opens a block for grouping objects to be pretty printed.
422
+ #
423
+ # Arguments:
424
+ # * +indent+ - noop argument. Present for compatibility.
425
+ # * +open_obj+ - text appended before the &block. Default is ''
426
+ # * +close_obj+ - text appended after the &block. Default is ''
427
+ # * +open_width+ - noop argument. Present for compatibility.
428
+ # * +close_width+ - noop argument. Present for compatibility.
429
+ def group(
430
+ indent = nil,
431
+ open_object = "",
432
+ close_object = "",
433
+ open_width = nil,
434
+ close_width = nil
435
+ )
436
+ target << open_object
437
+ yield
438
+ target << close_object
439
+ end
440
+
441
+ # A class that wraps the ability to call #if_flat. The contents of the
442
+ # #if_flat block are executed immediately, so effectively this class and the
443
+ # #if_break method that triggers it are unnecessary, but they're here to
444
+ # maintain compatibility.
445
+ class IfBreakBuilder
446
+ def if_flat
447
+ yield
448
+ end
449
+ end
450
+
451
+ # Effectively unnecessary, but here for compatibility.
452
+ def if_break
453
+ IfBreakBuilder.new
454
+ end
455
+
456
+ # A noop that immediately yields.
457
+ def indent
458
+ yield
459
+ end
460
+
461
+ # Changes the target output buffer to the line suffix output buffer which
462
+ # will get flushed at the end of printing.
463
+ def line_suffix
464
+ previous_target, @target = @target, line_suffixes
465
+ yield
466
+ @target = previous_target
467
+ end
468
+
469
+ # Takes +indent+ arg, but does nothing with it.
470
+ #
471
+ # Yields to a block.
472
+ def nest(indent)
473
+ yield
474
+ end
475
+
476
+ # Add +object+ to the text to be output.
477
+ #
478
+ # +width+ argument is here for compatibility. It is a noop argument.
479
+ def text(object = "", width = nil)
480
+ target << object
481
+ end
482
+ end
483
+
484
+ # This object represents the current level of indentation within the printer.
485
+ # It has the ability to generate new levels of indentation through the #align
486
+ # and #indent methods.
487
+ class IndentLevel
488
+ IndentPart = Object.new
489
+ DedentPart = Object.new
490
+
491
+ StringAlignPart = Struct.new(:n)
492
+ NumberAlignPart = Struct.new(:n)
493
+
494
+ attr_reader :genspace, :value, :length, :queue, :root
495
+
496
+ def initialize(
497
+ genspace:,
498
+ value: genspace.call(0),
499
+ length: 0,
500
+ queue: [],
501
+ root: nil
502
+ )
503
+ @genspace = genspace
504
+ @value = value
505
+ @length = length
506
+ @queue = queue
507
+ @root = root
508
+ end
509
+
510
+ # This can accept a whole lot of different kinds of objects, due to the
511
+ # nature of the flexibility of the Align node.
512
+ def align(n)
513
+ case n
514
+ when NilClass
515
+ self
516
+ when String
517
+ indent(StringAlignPart.new(n))
518
+ else
519
+ indent(n < 0 ? DedentPart : NumberAlignPart.new(n))
520
+ end
521
+ end
522
+
523
+ def indent(part = IndentPart)
524
+ next_value = genspace.call(0)
525
+ next_length = 0
526
+ next_queue = (part == DedentPart ? queue[0...-1] : [*queue, part])
527
+
528
+ last_spaces = 0
529
+
530
+ add_spaces = ->(count) do
531
+ next_value << genspace.call(count)
532
+ next_length += count
533
+ end
534
+
535
+ flush_spaces = -> do
536
+ add_spaces[last_spaces] if last_spaces > 0
537
+ last_spaces = 0
538
+ end
539
+
540
+ next_queue.each do |part|
541
+ case part
542
+ when IndentPart
543
+ flush_spaces.call
544
+ add_spaces.call(2)
545
+ when StringAlignPart
546
+ flush_spaces.call
547
+ next_value += part.n
548
+ next_length += part.n.length
549
+ when NumberAlignPart
550
+ last_spaces += part.n
551
+ end
552
+ end
553
+
554
+ flush_spaces.call
555
+
556
+ IndentLevel.new(
557
+ genspace: genspace,
558
+ value: next_value,
559
+ length: next_length,
560
+ queue: next_queue,
561
+ root: root
562
+ )
563
+ end
564
+ end
565
+
566
+ # When printing, you can optionally specify the value that should be used
567
+ # whenever a group needs to be broken onto multiple lines. In this case the
568
+ # default is \n.
569
+ DEFAULT_NEWLINE = "\n"
570
+
571
+ # When generating spaces after a newline for indentation, by default we
572
+ # generate one space per character needed for indentation. You can change this
573
+ # behavior (for instance to use tabs) by passing a different genspace
574
+ # procedure.
575
+ DEFAULT_GENSPACE = ->(n) { " " * n }
576
+
577
+ # There are two modes in printing, break and flat. When we're in break mode,
578
+ # any lines will use their newline, any if-breaks will use their break
579
+ # contents, etc.
580
+ MODE_BREAK = 1
581
+
582
+ # This is another print mode much like MODE_BREAK. When we're in flat mode, we
583
+ # attempt to print everything on one line until we either hit a broken group,
584
+ # a forced line, or the maximum width.
585
+ MODE_FLAT = 2
586
+
587
+ # This is a convenience method which is same as follows:
588
+ #
589
+ # begin
590
+ # q = PrettyPrint.new(output, maxwidth, newline, &genspace)
591
+ # ...
592
+ # q.flush
593
+ # output
594
+ # end
595
+ #
596
+ def self.format(
597
+ output = "".dup,
598
+ maxwidth = 80,
599
+ newline = DEFAULT_NEWLINE,
600
+ genspace = DEFAULT_GENSPACE
601
+ )
602
+ q = new(output, maxwidth, newline, &genspace)
603
+ yield q
604
+ q.flush
605
+ output
606
+ end
607
+
608
+ # This is similar to PrettyPrint::format but the result has no breaks.
609
+ #
610
+ # +maxwidth+, +newline+ and +genspace+ are ignored.
611
+ #
612
+ # The invocation of +breakable+ in the block doesn't break a line and is
613
+ # treated as just an invocation of +text+.
614
+ #
615
+ def self.singleline_format(
616
+ output = "".dup,
617
+ maxwidth = nil,
618
+ newline = nil,
619
+ genspace = nil
620
+ )
621
+ q = SingleLine.new(output)
622
+ yield q
623
+ output
624
+ end
625
+
626
+ # The output object. It represents the final destination of the contents of
627
+ # the print tree. It should respond to <<.
628
+ #
629
+ # This defaults to "".dup
630
+ attr_reader :output
631
+
632
+ # This is an output buffer that wraps the output object and provides
633
+ # additional functionality depending on its type.
634
+ #
635
+ # This defaults to Buffer::StringBuffer.new("".dup)
636
+ attr_reader :buffer
637
+
638
+ # The maximum width of a line, before it is separated in to a newline
639
+ #
640
+ # This defaults to 80, and should be an Integer
641
+ attr_reader :maxwidth
642
+
643
+ # The value that is appended to +output+ to add a new line.
644
+ #
645
+ # This defaults to "\n", and should be String
646
+ attr_reader :newline
647
+
648
+ # An object that responds to call that takes one argument, of an Integer, and
649
+ # returns the corresponding number of spaces.
650
+ #
651
+ # By default this is: ->(n) { ' ' * n }
652
+ attr_reader :genspace
653
+
654
+ # The stack of groups that are being printed.
655
+ attr_reader :groups
656
+
657
+ # The current array of contents that calls to methods that generate print tree
658
+ # nodes will append to.
659
+ attr_reader :target
660
+
661
+ # Creates a buffer for pretty printing.
662
+ #
663
+ # +output+ is an output target. If it is not specified, '' is assumed. It
664
+ # should have a << method which accepts the first argument +obj+ of
665
+ # PrettyPrint#text, the first argument +separator+ of PrettyPrint#breakable,
666
+ # the first argument +newline+ of PrettyPrint.new, and the result of a given
667
+ # block for PrettyPrint.new.
668
+ #
669
+ # +maxwidth+ specifies maximum line length. If it is not specified, 80 is
670
+ # assumed. However actual outputs may overflow +maxwidth+ if long
671
+ # non-breakable texts are provided.
672
+ #
673
+ # +newline+ is used for line breaks. "\n" is used if it is not specified.
674
+ #
675
+ # The block is used to generate spaces. ->(n) { ' ' * n } is used if it is not
676
+ # given.
677
+ def initialize(
678
+ output = "".dup,
679
+ maxwidth = 80,
680
+ newline = DEFAULT_NEWLINE,
681
+ &genspace
682
+ )
683
+ @output = output
684
+ @buffer = Buffer.for(output)
685
+ @maxwidth = maxwidth
686
+ @newline = newline
687
+ @genspace = genspace || DEFAULT_GENSPACE
688
+ reset
689
+ end
690
+
691
+ # Returns the group most recently added to the stack.
692
+ #
693
+ # Contrived example:
694
+ # out = ""
695
+ # => ""
696
+ # q = PrettyPrint.new(out)
697
+ # => #<PrettyPrint:0x0>
698
+ # q.group {
699
+ # q.text q.current_group.inspect
700
+ # q.text q.newline
701
+ # q.group(q.current_group.depth + 1) {
702
+ # q.text q.current_group.inspect
703
+ # q.text q.newline
704
+ # q.group(q.current_group.depth + 1) {
705
+ # q.text q.current_group.inspect
706
+ # q.text q.newline
707
+ # q.group(q.current_group.depth + 1) {
708
+ # q.text q.current_group.inspect
709
+ # q.text q.newline
710
+ # }
711
+ # }
712
+ # }
713
+ # }
714
+ # => 284
715
+ # puts out
716
+ # #<PrettyPrint::Group:0x0 @depth=1>
717
+ # #<PrettyPrint::Group:0x0 @depth=2>
718
+ # #<PrettyPrint::Group:0x0 @depth=3>
719
+ # #<PrettyPrint::Group:0x0 @depth=4>
720
+ def current_group
721
+ groups.last
722
+ end
723
+
724
+ # Flushes all of the generated print tree onto the output buffer, then clears
725
+ # the generated tree from memory.
726
+ def flush
727
+ # First, get the root group, since we placed one at the top to begin with.
728
+ doc = groups.first
729
+
730
+ # This represents how far along the current line we are. It gets reset
731
+ # back to 0 when we encounter a newline.
732
+ position = 0
733
+
734
+ # This is our command stack. A command consists of a triplet of an
735
+ # indentation level, the mode (break or flat), and a doc node.
736
+ commands = [[IndentLevel.new(genspace: genspace), MODE_BREAK, doc]]
737
+
738
+ # This is a small optimization boolean. It keeps track of whether or not
739
+ # when we hit a group node we should check if it fits on the same line.
740
+ should_remeasure = false
741
+
742
+ # This is a separate command stack that includes the same kind of triplets
743
+ # as the commands variable. It is used to keep track of things that should
744
+ # go at the end of printed lines once the other doc nodes are
745
+ # accounted for. Typically this is used to implement comments.
746
+ line_suffixes = []
747
+
748
+ # This is a linear stack instead of a mutually recursive call defined on
749
+ # the individual doc nodes for efficiency.
750
+ while commands.any?
751
+ indent, mode, doc = commands.pop
752
+
753
+ case doc
754
+ when Text
755
+ doc.objects.each { |object| buffer << object }
756
+ position += doc.width
757
+ when Array
758
+ doc.reverse_each { |part| commands << [indent, mode, part] }
759
+ when Indent
760
+ commands << [indent.indent, mode, doc.contents]
761
+ when Align
762
+ commands << [indent.align(doc.indent), mode, doc.contents]
763
+ when Trim
764
+ position -= buffer.trim!
765
+ when Group
766
+ if mode == MODE_FLAT && !should_remeasure
767
+ commands <<
768
+ [indent, doc.break? ? MODE_BREAK : MODE_FLAT, doc.contents]
769
+ else
770
+ should_remeasure = false
771
+ next_cmd = [indent, MODE_FLAT, doc.contents]
772
+
773
+ if !doc.break? && fits?(next_cmd, commands, maxwidth - position)
774
+ commands << next_cmd
775
+ else
776
+ commands << [indent, MODE_BREAK, doc.contents]
777
+ end
778
+ end
779
+ when IfBreak
780
+ if mode == MODE_BREAK
781
+ commands << [indent, mode, doc.break_contents] if doc.break_contents
782
+ elsif mode == MODE_FLAT
783
+ commands << [indent, mode, doc.flat_contents] if doc.flat_contents
784
+ end
785
+ when LineSuffix
786
+ line_suffixes << [indent, mode, doc.contents]
787
+ when Breakable
788
+ if mode == MODE_FLAT
789
+ if doc.force?
790
+ # This line was forced into the output even if we were in flat mode,
791
+ # so we need to tell the next group that no matter what, it needs to
792
+ # remeasure because the previous measurement didn't accurately
793
+ # capture the entire expression (this is necessary for nested
794
+ # groups).
795
+ should_remeasure = true
796
+ else
797
+ buffer << doc.separator
798
+ position += doc.width
799
+ next
800
+ end
801
+ end
802
+
803
+ # If there are any commands in the line suffix buffer, then we're going
804
+ # to flush them now, as we are about to add a newline.
805
+ if line_suffixes.any?
806
+ commands << [indent, mode, doc]
807
+ commands += line_suffixes.reverse
808
+ line_suffixes = []
809
+ next
810
+ end
811
+
812
+ if !doc.indent?
813
+ buffer << newline
814
+
815
+ if indent.root
816
+ buffer << indent.root.value
817
+ position = indent.root.length
818
+ else
819
+ position = 0
820
+ end
821
+ else
822
+ position -= buffer.trim!
823
+ buffer << newline
824
+ buffer << indent.value
825
+ position = indent.length
826
+ end
827
+ when BreakParent
828
+ # do nothing
829
+ else
830
+ # Special case where the user has defined some way to get an extra doc
831
+ # node that we don't explicitly support into the list. In this case
832
+ # we're going to assume it's 0-width and just append it to the output
833
+ # buffer.
834
+ #
835
+ # This is useful behavior for putting marker nodes into the list so that
836
+ # you can know how things are getting mapped before they get printed.
837
+ buffer << doc
838
+ end
839
+
840
+ if commands.empty? && line_suffixes.any?
841
+ commands += line_suffixes.reverse
842
+ line_suffixes = []
843
+ end
844
+ end
845
+
846
+ # Reset the group stack and target array so that this pretty printer object
847
+ # can continue to be used before calling flush again if desired.
848
+ reset
849
+ end
850
+
851
+ # ----------------------------------------------------------------------------
852
+ # Markers node builders
853
+ # ----------------------------------------------------------------------------
854
+
855
+ # This says "you can break a line here if necessary", and a +width+\-column
856
+ # text +separator+ is inserted if a line is not broken at the point.
857
+ #
858
+ # If +separator+ is not specified, ' ' is used.
859
+ #
860
+ # If +width+ is not specified, +separator.length+ is used. You will have to
861
+ # specify this when +separator+ is a multibyte character, for example.
862
+ #
863
+ # By default, if the surrounding group is broken and a newline is inserted,
864
+ # the printer will indent the subsequent line up to the current level of
865
+ # indentation. You can disable this behavior with the +indent+ argument if
866
+ # that's not desired (rare).
867
+ #
868
+ # By default, when you insert a Breakable into the print tree, it only breaks
869
+ # the surrounding group when the group's contents cannot fit onto the
870
+ # remaining space of the current line. You can force it to break the
871
+ # surrounding group instead if you always want the newline with the +force+
872
+ # argument.
873
+ def breakable(
874
+ separator = " ",
875
+ width = separator.length,
876
+ indent: true,
877
+ force: false
878
+ )
879
+ doc = Breakable.new(separator, width, indent: indent, force: force)
880
+
881
+ target << doc
882
+ break_parent if force
883
+
884
+ doc
885
+ end
886
+
887
+ # This inserts a BreakParent node into the print tree which forces the
888
+ # surrounding and all parent group nodes to break.
889
+ def break_parent
890
+ doc = BreakParent.new
891
+ target << doc
892
+
893
+ groups.reverse_each do |group|
894
+ break if group.break?
895
+ group.break
896
+ end
897
+
898
+ doc
899
+ end
900
+
901
+ # This is similar to #breakable except the decision to break or not is
902
+ # determined individually.
903
+ #
904
+ # Two #fill_breakable under a group may cause 4 results:
905
+ # (break,break), (break,non-break), (non-break,break), (non-break,non-break).
906
+ # This is different to #breakable because two #breakable under a group
907
+ # may cause 2 results: (break,break), (non-break,non-break).
908
+ #
909
+ # The text +separator+ is inserted if a line is not broken at this point.
910
+ #
911
+ # If +separator+ is not specified, ' ' is used.
912
+ #
913
+ # If +width+ is not specified, +separator.length+ is used. You will have to
914
+ # specify this when +separator+ is a multibyte character, for example.
915
+ def fill_breakable(separator = " ", width = separator.length)
916
+ group { breakable(separator, width) }
917
+ end
918
+
919
+ # This inserts a Trim node into the print tree which, when printed, will clear
920
+ # all whitespace at the end of the output buffer. This is useful for the rare
921
+ # case where you need to delete printed indentation and force the next node
922
+ # to start at the beginning of the line.
923
+ def trim
924
+ doc = Trim.new
925
+ target << doc
926
+
927
+ doc
928
+ end
929
+
930
+ # ----------------------------------------------------------------------------
931
+ # Container node builders
932
+ # ----------------------------------------------------------------------------
933
+
934
+ # Groups line break hints added in the block. The line break hints are all to
935
+ # be used or not.
936
+ #
937
+ # If +indent+ is specified, the method call is regarded as nested by
938
+ # nest(indent) { ... }.
939
+ #
940
+ # If +open_object+ is specified, <tt>text(open_object, open_width)</tt> is
941
+ # called before grouping. If +close_object+ is specified,
942
+ # <tt>text(close_object, close_width)</tt> is called after grouping.
943
+ def group(
944
+ indent = 0,
945
+ open_object = "",
946
+ close_object = "",
947
+ open_width = open_object.length,
948
+ close_width = close_object.length
949
+ )
950
+ text(open_object, open_width) if open_object != ""
951
+
952
+ doc = Group.new(groups.last.depth + 1)
953
+ groups << doc
954
+ target << doc
955
+
956
+ with_target(doc.contents) do
957
+ if indent != 0
958
+ nest(indent) { yield }
959
+ else
960
+ yield
961
+ end
962
+ end
963
+
964
+ groups.pop
965
+ text(close_object, close_width) if close_object != ""
966
+
967
+ doc
968
+ end
969
+
970
+ # A small DSL-like object used for specifying the alternative contents to be
971
+ # printed if the surrounding group doesn't break for an IfBreak node.
972
+ class IfBreakBuilder
973
+ attr_reader :builder, :if_break
974
+
975
+ def initialize(builder, if_break)
976
+ @builder = builder
977
+ @if_break = if_break
978
+ end
979
+
980
+ def if_flat(&block)
981
+ builder.with_target(if_break.flat_contents, &block)
982
+ end
983
+ end
984
+
985
+ # Inserts an IfBreak node with the contents of the block being added to its
986
+ # list of nodes that should be printed if the surrounding node breaks. If it
987
+ # doesn't, then you can specify the contents to be printed with the #if_flat
988
+ # method used on the return object from this method. For example,
989
+ #
990
+ # q.if_break { q.text('do') }.if_flat { q.text('{') }
991
+ #
992
+ # In the example above, if the surrounding group is broken it will print 'do'
993
+ # and if it is not it will print '{'.
994
+ def if_break
995
+ doc = IfBreak.new
996
+ target << doc
997
+
998
+ with_target(doc.break_contents) { yield }
999
+ IfBreakBuilder.new(self, doc)
1000
+ end
1001
+
1002
+ # Very similar to the #nest method, this indents the nested content by one
1003
+ # level by inserting an Indent node into the print tree. The contents of the
1004
+ # node are determined by the block.
1005
+ def indent
1006
+ doc = Indent.new
1007
+ target << doc
1008
+
1009
+ with_target(doc.contents) { yield }
1010
+ doc
1011
+ end
1012
+
1013
+ # Inserts a LineSuffix node into the print tree. The contents of the node are
1014
+ # determined by the block.
1015
+ def line_suffix
1016
+ doc = LineSuffix.new
1017
+ target << doc
1018
+
1019
+ with_target(doc.contents) { yield }
1020
+ doc
1021
+ end
1022
+
1023
+ # Increases left margin after newline with +indent+ for line breaks added in
1024
+ # the block.
1025
+ def nest(indent)
1026
+ doc = Align.new(indent: indent)
1027
+ target << doc
1028
+
1029
+ with_target(doc.contents) { yield }
1030
+ doc
1031
+ end
1032
+
1033
+ # This adds +object+ as a text of +width+ columns in width.
1034
+ #
1035
+ # If +width+ is not specified, object.length is used.
1036
+ def text(object = "", width = object.length)
1037
+ doc = target.last
1038
+
1039
+ unless Text === doc
1040
+ doc = Text.new
1041
+ target << doc
1042
+ end
1043
+
1044
+ doc.add(object: object, width: width)
1045
+ doc
1046
+ end
1047
+
1048
+ # ----------------------------------------------------------------------------
1049
+ # Internal APIs
1050
+ # ----------------------------------------------------------------------------
1051
+
1052
+ # A convenience method used by a lot of the print tree node builders that
1053
+ # temporarily changes the target that the builders will append to.
1054
+ def with_target(target)
1055
+ previous_target, @target = @target, target
1056
+ yield
1057
+ @target = previous_target
1058
+ end
1059
+
1060
+ private
1061
+
1062
+ # This method returns a boolean as to whether or not the remaining commands
1063
+ # fit onto the remaining space on the current line. If we finish printing
1064
+ # all of the commands or if we hit a newline, then we return true. Otherwise
1065
+ # if we continue printing past the remaining space, we return false.
1066
+ def fits?(next_command, rest_commands, remaining)
1067
+ # This is the index in the remaining commands that we've handled so far.
1068
+ # We reverse through the commands and add them to the stack if we've run
1069
+ # out of nodes to handle.
1070
+ rest_index = rest_commands.length
1071
+
1072
+ # This is our stack of commands, very similar to the commands list in the
1073
+ # print method.
1074
+ commands = [next_command]
1075
+
1076
+ # This is our output buffer, really only necessary to keep track of
1077
+ # because we could encounter a Trim doc node that would actually add
1078
+ # remaining space.
1079
+ fit_buffer = buffer.class.new
1080
+
1081
+ while remaining >= 0
1082
+ if commands.empty?
1083
+ return true if rest_index == 0
1084
+
1085
+ rest_index -= 1
1086
+ commands << rest_commands[rest_index]
1087
+ next
1088
+ end
1089
+
1090
+ indent, mode, doc = commands.pop
1091
+
1092
+ case doc
1093
+ when Text
1094
+ doc.objects.each { |object| fit_buffer << object }
1095
+ remaining -= doc.width
1096
+ when Array
1097
+ doc.reverse_each { |part| commands << [indent, mode, part] }
1098
+ when Indent
1099
+ commands << [indent.indent, mode, doc.contents]
1100
+ when Align
1101
+ commands << [indent.align(doc.indent), mode, doc.contents]
1102
+ when Trim
1103
+ remaining += fit_buffer.trim!
1104
+ when Group
1105
+ commands << [indent, doc.break? ? MODE_BREAK : mode, doc.contents]
1106
+ when IfBreak
1107
+ if mode == MODE_BREAK
1108
+ commands << [indent, mode, doc.break_contents] if doc.break_contents
1109
+ else
1110
+ commands << [indent, mode, doc.flat_contents] if doc.flat_contents
1111
+ end
1112
+ when Breakable
1113
+ if mode == MODE_FLAT && !doc.force?
1114
+ fit_buffer << doc.separator
1115
+ remaining -= doc.width
1116
+ next
1117
+ end
1118
+
1119
+ return true
1120
+ end
1121
+ end
1122
+
1123
+ false
1124
+ end
1125
+
1126
+ # Resets the group stack and target array so that this pretty printer object
1127
+ # can continue to be used before calling flush again if desired.
1128
+ def reset
1129
+ @groups = [Group.new(0)]
1130
+ @target = @groups.last.contents
1131
+ end
1132
+ end