oppen 0.9.6 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/oppen/printer.rb CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'stringio'
4
4
 
5
- require_relative 'scan_stack'
6
- require_relative 'print_stack'
7
5
  require_relative 'mixins'
6
+ require_relative 'print_stack'
7
+ require_relative 'scan_stack'
8
8
 
9
9
  # Oppen.
10
10
  module Oppen
@@ -12,72 +12,82 @@ module Oppen
12
12
  class Printer
13
13
  extend Mixins
14
14
 
15
+ # The printer's configuration, altering its behavior.
16
+ #
17
+ # @return [Config]
15
18
  attr_reader :config
16
- # Ring buffer left index.
19
+ # Ring buffer's left index.
17
20
  #
18
- # @note Called left as well in the original paper.
21
+ # @note Called `left` as well in the original paper.
22
+ #
23
+ # @return [Integer]
19
24
  attr_reader :left
20
-
21
- # Total number of spaces needed to print from start of buffer to the left.
25
+ # Number of spaces needed to print from start of buffer to left.
26
+ #
27
+ # @note Called `leftTotal` as well in the original paper.
22
28
  #
23
- # @note Called leftTotal as well in the original paper.
29
+ # @return [Integer]
24
30
  attr_reader :left_total
25
-
26
- # @note Called printStack as well in the original paper.
31
+ # A stack of {Token}s; builds the the final output.
32
+ #
33
+ # @note Called `printStack` as well in the original paper.
34
+ #
35
+ # @return [PrintStack]
27
36
  attr_reader :print_stack
28
-
29
- # Ring buffer right index.
37
+ # Ring buffer's right index.
38
+ #
39
+ # @note Called `right` as well in the original paper.
30
40
  #
31
- # @note Called right as well in the original paper.
41
+ # @return [Integer]
32
42
  attr_reader :right
33
-
34
- # Total number of spaces needed to print from start of buffer to the right.
43
+ # Number of spaces needed to print from start of buffer to right.
44
+ #
45
+ # @note Called `leftTotal` as well in the original paper.
35
46
  #
36
- # @note Called leftTotal as well in the original paper.
47
+ # @return [Integer]
37
48
  attr_reader :right_total
38
-
39
49
  # Potential breaking positions.
40
50
  #
41
- # @note Called scanStack as well in the original paper.
51
+ # @note Called `scanStack` as well in the original paper.
52
+ #
53
+ # @return [ScanStack]
42
54
  attr_reader :scan_stack
43
-
44
55
  # Size buffer, initially filled with nil.
45
56
  #
46
- # @note Called size as well in the original paper.
57
+ # @note Called `size` as well in the original paper.
58
+ #
59
+ # @return [Integer]
47
60
  attr_reader :size
48
-
49
61
  # Token buffer, initially filled with nil.
50
62
  #
51
- # @note Called token in the original paper.
63
+ # @note Called `token` in the original paper.
64
+ #
65
+ # @return [Array<Tokens>]
52
66
  attr_reader :tokens
53
67
 
54
- # Some description
55
- #
56
- # @example
57
- # "This is a string" # => and this is a comment
58
- # out = Oppen::Wadler.new (margin: 13) # Hawn
59
- # # Baliz
60
- #
61
- # @example
62
- # "This is a string" # => and this is a comment
63
- # # var = 12
64
- #
65
- # @param width [Integer] maximum line width desired.
66
- # @param new_line [String] the delimiter between lines.
67
- # @param config [Config]
68
- # @param space [String, Proc] could be a String or a callable.
69
- # If it's a string, spaces will be generated with the the
70
- # lambda `->(n){ n * space }`, where `n` is the number of columns
71
- # to indent.
72
- # If it's a callable, it will receive `n` and it needs to return
73
- # a string.
74
- # @param out [Object] should have a write and string method
68
+ # @note Called `PrettyPrintInit` in the original paper.
69
+ #
70
+ # @param config [Config]
71
+ # to customize the printer's behavior.
72
+ # @param new_line [String]
73
+ # the delimiter between lines.
74
+ # @param space [String, Proc]
75
+ # indentation string or a string generator.
76
+ # - If a `String`, spaces will be generated with the the lambda
77
+ # `->(n){ space * n }`, where `n` is the number of columns to indent.
78
+ # - If a `Proc`, it will receive `n` and it needs to return a `String`.
79
+ # @param width [Integer]
80
+ # maximum line width desired.
81
+ # @param out [Object]
82
+ # the output string buffer. It should have both `write` and `string`
83
+ # methods.
75
84
  def initialize(width, new_line, config = Config.oppen,
76
85
  space = ' ', out = StringIO.new)
77
86
  # Maximum size if the stacks
78
87
  n = 3 * width
79
88
 
80
89
  @config = config
90
+ @last_whitespaces_width = 0 # Accumulates the width of the last Whitespace tokens encountered.
81
91
  @left = 0
82
92
  @left_total = 1
83
93
  @print_stack = PrintStack.new width, new_line, config, space, out
@@ -88,14 +98,16 @@ module Oppen
88
98
  @tokens = Array.new n
89
99
  end
90
100
 
101
+ # The final pretty-printed output.
102
+ #
91
103
  # @return [String]
92
- def output
93
- print_stack.output
94
- end
104
+ # the output of the print stack.
105
+ def output = print_stack.output
95
106
 
96
- # Core function of the algorithm responsible for populating the scan and print stack.
107
+ # Core function of the algorithm responsible for populating the {ScanStack}
108
+ # and {PrintStack}.
97
109
  #
98
- # @note Called PrettyPrint as well in the original paper.
110
+ # @note Called `PrettyPrint` as well in the original paper.
99
111
  #
100
112
  # @param token [Token]
101
113
  #
@@ -110,16 +122,18 @@ module Oppen
110
122
  handle_end token
111
123
  in Token::Break
112
124
  handle_break token
125
+ in Token::Whitespace
126
+ @last_whitespaces_width += token.width
127
+ handle_string token
113
128
  in Token::String
129
+ @last_whitespaces_width = 0
114
130
  handle_string token
115
131
  end
116
132
  end
117
133
 
118
- # Handle EOF Token.
134
+ # Handle {Token::EOF}.
119
135
  #
120
136
  # @return [Nil]
121
- #
122
- # @see Token::EOF
123
137
  def handle_eof
124
138
  if !scan_stack.empty?
125
139
  check_stack 0
@@ -128,17 +142,20 @@ module Oppen
128
142
  print_stack.indent 0
129
143
  end
130
144
 
131
- # Handle Begin Token.
145
+ # Handle {Token::Begin}.
132
146
  #
133
- # @return [Nil]
147
+ # @param token [Token::Begin]
134
148
  #
135
- # @see Token::Begin
149
+ # @return [Nil]
136
150
  def handle_begin(token)
137
151
  if scan_stack.empty?
138
152
  @left = 0
139
153
  @left_total = 1
140
154
  @right = 0
141
155
  @right_total = 1
156
+
157
+ # config.trim_trailing_whitespaces.
158
+ @tokens[-1] = nil
142
159
  else
143
160
  advance_right
144
161
  end
@@ -147,11 +164,11 @@ module Oppen
147
164
  scan_stack.push right
148
165
  end
149
166
 
150
- # Handle End Token.
167
+ # Handle {Token::End}.
151
168
  #
152
- # @return [Nil]
169
+ # @param token [Token::End]
153
170
  #
154
- # @see Token::End
171
+ # @return [Nil]
155
172
  def handle_end(token)
156
173
  if scan_stack.empty?
157
174
  print_stack.print token, 0
@@ -168,17 +185,22 @@ module Oppen
168
185
  end
169
186
  end
170
187
 
171
- # Handle Break Token.
188
+ # Handle {Token::Break}.
172
189
  #
173
- # @return [Nil]
190
+ # @param token [Token::Break]
174
191
  #
175
- # @see Token::Break
192
+ # @return [Nil]
176
193
  def handle_break(token)
177
194
  if scan_stack.empty?
178
195
  @left = 0
179
196
  @left_total = 1
180
197
  @right = 0
181
198
  @right_total = 1
199
+
200
+ # config.trim_trailing_whitespaces.
201
+ tokens[-1] = nil
202
+ print_stack.erase @last_whitespaces_width
203
+ @last_whitespaces_width = 0
182
204
  else
183
205
  advance_right
184
206
  end
@@ -189,11 +211,11 @@ module Oppen
189
211
  @right_total += token.width
190
212
  end
191
213
 
192
- # Handle String Token.
214
+ # Handle {Token::String}.
193
215
  #
194
- # @return [Nil]
216
+ # @param token [Token::String]
195
217
  #
196
- # @see Token::String
218
+ # @return [Nil]
197
219
  def handle_string(token)
198
220
  if scan_stack.empty?
199
221
  print_stack.print token, token.width
@@ -202,13 +224,13 @@ module Oppen
202
224
  tokens[right] = token
203
225
  size[right] = token.width
204
226
  @right_total += token.width
205
- check_stream
227
+ check_stream if @last_whitespaces_width.zero?
206
228
  end
207
229
  end
208
230
 
209
231
  # Flushes the input if possible.
210
232
  #
211
- # @note Called CheckStream as well in the original paper.
233
+ # @note Called `CheckStream` as well in the original paper.
212
234
  #
213
235
  # @return [Nil]
214
236
  def check_stream
@@ -223,9 +245,9 @@ module Oppen
223
245
  check_stream
224
246
  end
225
247
 
226
- # Advances the `right` pointer.
248
+ # Advances the {#right} pointer.
227
249
  #
228
- # @note Called AdvanceRight as well in the original paper.
250
+ # @note Called `AdvanceRight` as well in the original paper.
229
251
  #
230
252
  # @return [Nil]
231
253
  def advance_right
@@ -240,16 +262,38 @@ module Oppen
240
262
  @tokens, @left, @right = ScanStack.upsize_circular_array(@tokens, @left)
241
263
  end
242
264
 
243
- # Advances the `left` pointer and lets the print stack
244
- # print some of the tokens it contains.
265
+ # Advances the {#left} pointer and lets the print stack print some of the
266
+ # tokens it contains.
245
267
  #
246
- # @note Called AdvanceLeft as well in the original paper.
268
+ # @note Called `AdvanceLeft` as well in the original paper.
269
+ #
270
+ # @param token [Token]
271
+ # @param token_width [Integer]
247
272
  #
248
273
  # @return [Nil]
249
274
  def advance_left(token, token_width)
250
275
  return if token_width.negative?
251
276
 
252
- print_stack.print token, token_width
277
+ trim_on_break =
278
+ if token.is_a?(Token::Break)
279
+ # Find the first previous String token.
280
+ idx = (left - 1) % tokens.length
281
+ while idx != right && tokens[idx] && !tokens[idx].is_a?(Token::String) \
282
+ && !tokens[idx].is_a?(Token::Break)
283
+ idx = (idx - 1) % tokens.length
284
+ end
285
+ # Sum the widths of the last whitespace tokens.
286
+ total = 0
287
+ while tokens[idx].is_a?(Token::Whitespace)
288
+ total += tokens[idx].width
289
+ idx = (idx - 1) % tokens.length
290
+ end
291
+ @last_whitespaces_width = 0
292
+ total
293
+ end
294
+ trim_on_break ||= 0
295
+
296
+ print_stack.print(token, token_width, trim_on_break: trim_on_break)
253
297
 
254
298
  case token
255
299
  when Token::Break
@@ -260,16 +304,17 @@ module Oppen
260
304
 
261
305
  return if left == right
262
306
 
263
- @left = (left + 1) % scan_stack.length
307
+ @left = (left + 1) % tokens.length
264
308
  advance_left tokens[left], size[left]
265
309
  end
266
310
 
267
- # Updates the size buffer taking into
268
- # account the length of the current group.
311
+ # Updates the {#size} buffer taking into account the length of the current
312
+ # group.
269
313
  #
270
- # @note Called CheckStack as well in the original paper.
314
+ # @note Called `CheckStack` as well in the original paper.
271
315
  #
272
- # @param depth [Integer] depth of the group
316
+ # @param depth [Integer]
317
+ # depth of the group.
273
318
  #
274
319
  # @return [Nil]
275
320
  def check_stack(depth)
@@ -277,12 +322,12 @@ module Oppen
277
322
 
278
323
  x = scan_stack.top
279
324
  case tokens[x]
280
- when Token::Begin
325
+ in Token::Begin
281
326
  if depth.positive?
282
327
  size[scan_stack.pop] = size[x] + right_total
283
328
  check_stack depth - 1
284
329
  end
285
- when Token::End
330
+ in Token::End
286
331
  size[scan_stack.pop] = 1
287
332
  check_stack depth + 1
288
333
  else
@@ -9,23 +9,28 @@ module Oppen
9
9
  extend Mixins
10
10
 
11
11
  def initialize(size, config)
12
- @bottom = 0
13
- @config = config
14
- @empty = true
15
- @stack = Array.new(size)
16
- @top = 0
12
+ @bottom = 0 # Points to the bottom of the stack.
13
+ @config = config # Printing config.
14
+ @empty = true # Emptiness flag.
15
+ @stack = Array.new size # The fixed sized stack.
16
+ @top = 0 # Points to the top of the stack.
17
17
  end
18
18
 
19
+ # Whether the stack is empty.
20
+ #
19
21
  # @return [Boolean]
20
- def empty?
21
- @empty
22
- end
22
+ def empty? = @empty
23
23
 
24
+ # The current length of the stack.
25
+ #
24
26
  # @return [Integer]
25
- def length
26
- @stack.length
27
- end
27
+ def length = @stack.length
28
28
 
29
+ # The top element of the stack.
30
+ #
31
+ # @raise [RuntimeError]
32
+ # when accessing empty stack.
33
+ #
29
34
  # @return [Object]
30
35
  def top
31
36
  if empty?
@@ -35,6 +40,11 @@ module Oppen
35
40
  @stack[@top]
36
41
  end
37
42
 
43
+ # The bottom element of the stack.
44
+ #
45
+ # @raise [RuntimeError]
46
+ # when accessing empty stack.
47
+ #
38
48
  # @return [Object]
39
49
  def bottom
40
50
  if empty?
@@ -66,16 +76,20 @@ module Oppen
66
76
  #
67
77
  # @param value [Object]
68
78
  #
79
+ # @raise [RuntimeError]
80
+ # when the stack is full and the `upsize_stack` flag is not activated in
81
+ # {Config}.
82
+ #
69
83
  # @return [Nil]
70
84
  def push(value)
71
85
  if empty?
72
86
  @empty = false
73
87
  else
74
- @top = increment(@top)
88
+ @top = increment @top
75
89
  if @top == @bottom
76
90
  raise 'Stack full' if !@config.upsize_stack?
77
91
 
78
- @stack, @bottom, @top = ScanStack.upsize_circular_array(@stack, @bottom)
92
+ @stack, @bottom, @top = ScanStack.upsize_circular_array @stack, @bottom
79
93
  end
80
94
  end
81
95
  @stack[@top] = value
@@ -83,6 +97,9 @@ module Oppen
83
97
 
84
98
  # Pop a value from the top.
85
99
  #
100
+ # @raise [RuntimeError]
101
+ # when accessing empty stack.
102
+ #
86
103
  # @return [Nil]
87
104
  def pop
88
105
  if empty?
@@ -93,13 +110,16 @@ module Oppen
93
110
  if @top == @bottom
94
111
  @empty = true
95
112
  else
96
- @top = decrement(@top)
113
+ @top = decrement @top
97
114
  end
98
115
  res
99
116
  end
100
117
 
101
118
  # Pop a value from the bottom.
102
119
  #
120
+ # @raise [RuntimeError]
121
+ # when accessing empty stack.
122
+ #
103
123
  # @return [Nil]
104
124
  def pop_bottom
105
125
  if empty?
@@ -110,7 +130,7 @@ module Oppen
110
130
  if @top == @bottom
111
131
  @empty = true
112
132
  else
113
- @bottom = increment(@bottom)
133
+ @bottom = increment @bottom
114
134
  end
115
135
  res
116
136
  end
@@ -119,7 +139,7 @@ module Oppen
119
139
  #
120
140
  # @param offset [Integer]
121
141
  #
122
- # @return [Array[Integer]]
142
+ # @return [Array<Integer>]
123
143
  def update_indexes(offset)
124
144
  @stack = @stack.map { |val|
125
145
  (val + offset) % length if val
data/lib/oppen/token.rb CHANGED
@@ -4,27 +4,14 @@
4
4
  module Oppen
5
5
  # Token.
6
6
  class Token
7
- # BreakType.
7
+ # Default token width.
8
8
  #
9
- # FITS => No break is needed (the block fits on the line).
10
- # INCONSISTENT => New line will be forced only if necessary.
11
- # CONSISTENT => Each subblock of the block will be placed on a new line.
12
- module BreakType
13
- # @return [Integer]
14
- FITS = 0
15
- # @return [Integer]
16
- INCONSISTENT = 1
17
- # @return [Integer]
18
- CONSISTENT = 2
19
- end
20
-
21
- # Default token width
22
9
  # @return [Integer]
23
10
  def width = 0
24
11
 
25
12
  # String Token.
26
13
  class String < Token
27
- # @return [String] String value.
14
+ # @return [String]
28
15
  attr_reader :value
29
16
  # @return [Integer]
30
17
  attr_reader :width
@@ -39,9 +26,21 @@ module Oppen
39
26
  def to_s = value
40
27
  end
41
28
 
29
+ # This token is not part of Oppen's original work. We introduced it to
30
+ # handle trailing whitespaces.
31
+ #
32
+ # When the config flag `trim_trailing_whitespaces == true`, and a new line
33
+ # is needed, all the {Token::Whitespace} figuring after the last {Token::String}
34
+ # will be be skipped.
35
+ class Whitespace < ::Oppen::Token::String
36
+ end
37
+
42
38
  # Break Token.
43
39
  class Break < Token
44
- # @return [String] If a new line is needed display this string before the new line
40
+ # @return [String]
41
+ # If a new line is needed, display this string before the new line.
42
+ #
43
+ # @see Wadler#break example on `line_continuation`.
45
44
  attr_reader :line_continuation
46
45
  # @return [Integer] Indentation.
47
46
  attr_reader :offset
@@ -50,7 +49,7 @@ module Oppen
50
49
  # @return [Integer]
51
50
  attr_reader :width
52
51
 
53
- def initialize(str = ' ', width: str.length, line_continuation: '', offset: 0)
52
+ def initialize(str = ' ', line_continuation: '', offset: 0, width: str.length)
54
53
  raise ArgumentError, 'line_continuation cannot be nil' if line_continuation.nil?
55
54
 
56
55
  @line_continuation = line_continuation
@@ -60,6 +59,8 @@ module Oppen
60
59
  super()
61
60
  end
62
61
 
62
+ # Convert token to String.
63
+ #
63
64
  # @return [String]
64
65
  def to_s = str
65
66
  end
@@ -73,7 +74,7 @@ module Oppen
73
74
  end
74
75
 
75
76
  def initialize(line_continuation: '', offset: 0)
76
- super(LineBreakString.new, line_continuation:, offset:)
77
+ super(LineBreakString.new, line_continuation: line_continuation, offset: offset)
77
78
  end
78
79
  end
79
80
 
@@ -81,22 +82,53 @@ module Oppen
81
82
  class Begin < Token
82
83
  # @return [BreakType]
83
84
  attr_reader :break_type
84
- # @return [Integer] Indentation.
85
+ # @return [Integer]
85
86
  attr_reader :offset
86
87
 
87
- def initialize(break_type: BreakType::INCONSISTENT, offset: 2)
88
+ def initialize(break_type: :inconsistent, offset: 2)
88
89
  @offset = offset
89
90
  @break_type = break_type
90
91
  super()
91
92
  end
92
93
  end
93
94
 
94
- # End Token
95
+ # End Token.
95
96
  class End < Token
96
97
  nil
97
98
  end
98
99
 
99
- # EOF Token
100
+ # The EOF token can be interpreted as an output flush operation.
101
+ #
102
+ # @note Multiple {Token::EOF} tokens can be present in the same list of tokens.
103
+ #
104
+ # @example
105
+ # tokens = [
106
+ # Oppen::Token::Begin.new,
107
+ # Oppen::Token::String.new('XXXXXXXXXX'),
108
+ # Oppen::Token::End.new,
109
+ # Oppen::Token::EOF.new,
110
+ # Oppen::Token::Begin.new,
111
+ # Oppen::Token::String.new('YYYYYYYYYY'),
112
+ # Oppen::Token::End.new,
113
+ # ]
114
+ # Oppen.print tokens:
115
+ #
116
+ # # =>
117
+ # # XXXXXXXXXX
118
+ #
119
+ # tokens = [
120
+ # Oppen::Token::Begin.new,
121
+ # Oppen::Token::String.new('XXXXXXXXXX'),
122
+ # Oppen::Token::End.new,
123
+ # Oppen::Token::Begin.new,
124
+ # Oppen::Token::String.new('YYYYYYYYYY'),
125
+ # Oppen::Token::End.new,
126
+ # Oppen::Token::EOF.new,
127
+ # ]
128
+ # Oppen.print tokens:
129
+ #
130
+ # # =>
131
+ # # XXXXXXXXXXYYYYYYYYYY
100
132
  class EOF < Token
101
133
  nil
102
134
  end
data/lib/oppen/version.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  module Oppen
5
5
  # Oppen version
6
6
  #
7
- # @return [String] current version
8
- VERSION = '0.9.6' # managed by release.sh
7
+ # @return [String]
8
+ # current version
9
+ VERSION = '0.9.8' # managed by release.sh
9
10
  end