oppen 0.9.6 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
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