oppen 0.9.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a496d964de133aa177270080b9444d8e9ef865ce1b355bef4a6c4a43462d48c
4
- data.tar.gz: d2955b0983ef133fd374c1c46fc3424d9cff05c4ec910a6ca5ad94ec3f2db78d
3
+ metadata.gz: fdf36c265fd8c6c3d7335fe79b016ac1e6d8014a23f1b027fc4d956d05b442ab
4
+ data.tar.gz: d75772336cb34c8af929e95d5c4520f0388718739f7d6df722496f6f2f96a82a
5
5
  SHA512:
6
- metadata.gz: d4319e6b5be2d8fa394cf8d6cd593c4e8ab0b5c1c0a313cc42e287a93b46e23a95d844b8bddd01e5547ac67faa4b04395d5edb5ae911380572a0df1b7acf0c95
7
- data.tar.gz: 680e57e6f38f4cda980b57d04e787b4b33becc7b45de4c73810b243e82169411d62cd0d34b56a67df3e96debcc6d8a2ad777046aedec342429792fc91f3b3346
6
+ metadata.gz: 1d745467bc343f1262dbf36f1206a1a37336840014b6785badab602712fe167457b45598b4d2f10e4d438cb49935472c8fe9aea1e21109f349c6f52524831e8e
7
+ data.tar.gz: ad6279e7c29f9b6764fd19ddcdfc8fe006272eb481632d0a3f4f7fa9aad7a5b873e624ea5e829b89ee97401c474779034cfa956ff202e438a27afa22e6bd9151
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Oppen
4
+ # Mixins.
5
+ module Mixins
6
+ # Rotates circular array and triples its size.
7
+ # This method is not for public use.
8
+ #
9
+ # @param arr [Array]
10
+ # @param offset [Integer] Rotation amount
11
+ #
12
+ # @return [Array<Array, Integer, Integer>] upsized array, lhs, rhs
13
+ def upsize_circular_array(arr, offset)
14
+ size = arr.size
15
+ arr = arr.rotate(offset)
16
+ arr.fill(nil, size, 2 * size)
17
+ [arr, 0, size]
18
+ end
19
+
20
+ # Convert a list of tokens to its wadler representation.
21
+ #
22
+ # @param tokens [Array[Token]]
23
+ # @param base_indent [Integer]
24
+ #
25
+ # @return [String]
26
+ def tokens_to_wadler(tokens, base_indent = 4)
27
+ out = StringIO.new
28
+ write = ->(txt, nb_spaces) {
29
+ out.write("#{' ' * nb_spaces}#{txt}\n")
30
+ }
31
+ nb_spaces = base_indent
32
+ tokens.each do |token|
33
+ case token
34
+ in Token::String
35
+ write.call("out.text '#{token}'", nb_spaces)
36
+ in Token::LineBreak
37
+ write.call('out.break', nb_spaces)
38
+ in Token::Break
39
+ write.call('out.breakable', nb_spaces)
40
+ in Token::Begin
41
+ write.call('out.group {', nb_spaces)
42
+ nb_spaces += 2
43
+ in Token::End
44
+ nb_spaces -= 2
45
+ write.call('}', nb_spaces)
46
+ in Token::EOF
47
+ write.call('', nb_spaces) # new line
48
+ end
49
+ end
50
+ out.string
51
+ end
52
+ end
53
+ end
@@ -33,15 +33,15 @@ module Oppen
33
33
  # Delimiter between lines in output
34
34
  attr_reader :new_line
35
35
 
36
- # Page margin (Called length in the original paper).
37
- attr_reader :margin
36
+ # Maximum allowed width for printing (Called length in the original paper).
37
+ attr_reader :width
38
38
 
39
39
  # Current available space (Called index in the original paper).
40
40
  #
41
41
  # @return [Integer] Current available space (Called index in the original paper).
42
42
  attr_reader :space
43
43
 
44
- def initialize(margin, new_line, config, space, out)
44
+ def initialize(width, new_line, config, space, out)
45
45
  @buffer = out
46
46
  @config = config
47
47
  @genspace =
@@ -55,8 +55,8 @@ module Oppen
55
55
  end
56
56
  @items = []
57
57
  @new_line = new_line
58
- @margin = margin
59
- @space = margin
58
+ @width = width
59
+ @space = width
60
60
  end
61
61
 
62
62
  # Returns the output of the print stack
@@ -71,32 +71,32 @@ module Oppen
71
71
  # @note Called Print in the original paper.
72
72
  #
73
73
  # @param token [Token]
74
- # @param token_length [Integer]
74
+ # @param token_width [Integer]
75
75
  #
76
76
  # @return [Nil]
77
- def print(token, token_length)
77
+ def print(token, token_width)
78
78
  case token
79
79
  in Token::Begin
80
- handle_begin token, token_length
80
+ handle_begin token, token_width
81
81
  in Token::End
82
82
  handle_end
83
83
  in Token::Break
84
- handle_break token, token_length
84
+ handle_break token, token_width
85
85
  in Token::String
86
- handle_string token, token_length
86
+ handle_string token, token_width
87
87
  end
88
88
  end
89
89
 
90
90
  # Handle Begin Token.
91
91
  #
92
92
  # @param token [Token]
93
- # @param token_length [Integer]
93
+ # @param token_width [Integer]
94
94
  #
95
95
  # @return [Nil]
96
96
  #
97
97
  # @see Token::Begin
98
- def handle_begin(token, token_length)
99
- if token_length > space
98
+ def handle_begin(token, token_width)
99
+ if token_width > space
100
100
  type =
101
101
  if token.break_type == Token::BreakType::CONSISTENT
102
102
  Token::BreakType::CONSISTENT
@@ -129,16 +129,16 @@ module Oppen
129
129
  # Handle Break Token.
130
130
  #
131
131
  # @param token [Token]
132
- # @param token_length [Integer]
132
+ # @param token_width [Integer]
133
133
  #
134
134
  # @return [Nil]
135
135
  #
136
136
  # @see Token::Break
137
- def handle_break(token, token_length)
137
+ def handle_break(token, token_width)
138
138
  block = top
139
139
  case block.break_type
140
140
  in Token::BreakType::FITS
141
- @space -= token.length
141
+ @space -= token.width
142
142
  write token
143
143
  in Token::BreakType::CONSISTENT
144
144
  @space = block.offset - token.offset
@@ -146,23 +146,23 @@ module Oppen
146
146
  if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
147
147
  token.offset
148
148
  else
149
- margin - space
149
+ width - space
150
150
  end
151
151
  write token.line_continuation
152
152
  print_new_line indent
153
153
  in Token::BreakType::INCONSISTENT
154
- if token_length > space
154
+ if token_width > space
155
155
  @space = block.offset - token.offset
156
156
  indent =
157
157
  if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
158
158
  token.offset
159
159
  else
160
- margin - space
160
+ width - space
161
161
  end
162
162
  write token.line_continuation
163
163
  print_new_line indent
164
164
  else
165
- @space -= token.length
165
+ @space -= token.width
166
166
  write token
167
167
  end
168
168
  end
@@ -171,13 +171,13 @@ module Oppen
171
171
  # Handle String Token.
172
172
  #
173
173
  # @param token [Token]
174
- # @param token_length [Integer]
174
+ # @param token_width [Integer]
175
175
  #
176
176
  # @return [Nil]
177
177
  #
178
178
  # @see Token::String
179
- def handle_string(token, token_length)
180
- @space = [0, space - token_length].max
179
+ def handle_string(token, token_width)
180
+ @space = [0, space - token_width].max
181
181
  write token
182
182
  end
183
183
 
@@ -222,8 +222,8 @@ module Oppen
222
222
  def print_new_line(amount)
223
223
  write new_line
224
224
  if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
225
- @space = margin - top.offset - amount
226
- indent margin - space
225
+ @space = width - top.offset - amount
226
+ indent width - space
227
227
  else
228
228
  indent amount
229
229
  end
data/lib/oppen/printer.rb CHANGED
@@ -4,11 +4,14 @@ require 'stringio'
4
4
 
5
5
  require_relative 'scan_stack'
6
6
  require_relative 'print_stack'
7
+ require_relative 'mixins'
7
8
 
8
9
  # Oppen.
9
10
  module Oppen
10
11
  # Oppen pretty-printer.
11
12
  class Printer
13
+ extend Mixins
14
+
12
15
  attr_reader :config
13
16
  # Ring buffer left index.
14
17
  #
@@ -48,9 +51,18 @@ module Oppen
48
51
  # @note Called token in the original paper.
49
52
  attr_reader :tokens
50
53
 
51
- # @note Called PrettyPrintInit in the original paper.
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
52
64
  #
53
- # @param margin [Integer] maximum line width desired.
65
+ # @param width [Integer] maximum line width desired.
54
66
  # @param new_line [String] the delimiter between lines.
55
67
  # @param config [Config]
56
68
  # @param space [String, Proc] could be a String or a callable.
@@ -60,18 +72,18 @@ module Oppen
60
72
  # If it's a callable, it will receive `n` and it needs to return
61
73
  # a string.
62
74
  # @param out [Object] should have a write and string method
63
- def initialize(margin, new_line, config = Config.oppen,
75
+ def initialize(width, new_line, config = Config.oppen,
64
76
  space = ' ', out = StringIO.new)
65
77
  # Maximum size if the stacks
66
- n = 3 * margin
78
+ n = 3 * width
67
79
 
68
80
  @config = config
69
81
  @left = 0
70
82
  @left_total = 1
71
- @print_stack = PrintStack.new margin, new_line, config, space, out
83
+ @print_stack = PrintStack.new width, new_line, config, space, out
72
84
  @right = 0
73
85
  @right_total = 1
74
- @scan_stack = ScanStack.new n
86
+ @scan_stack = ScanStack.new n, config
75
87
  @size = Array.new n
76
88
  @tokens = Array.new n
77
89
  end
@@ -148,7 +160,7 @@ module Oppen
148
160
  tokens[right] = token
149
161
  size[right] = -1
150
162
  scan_stack.push right
151
- if config&.eager_print &&
163
+ if config&.eager_print? &&
152
164
  (!scan_stack.empty? && right_total - left_total < print_stack.space)
153
165
  check_stack 0
154
166
  advance_left tokens[left], size[left]
@@ -174,7 +186,7 @@ module Oppen
174
186
  scan_stack.push right
175
187
  tokens[right] = token
176
188
  size[right] = -right_total
177
- @right_total += token.length
189
+ @right_total += token.width
178
190
  end
179
191
 
180
192
  # Handle String Token.
@@ -184,12 +196,12 @@ module Oppen
184
196
  # @see Token::String
185
197
  def handle_string(token)
186
198
  if scan_stack.empty?
187
- print_stack.print token, token.length
199
+ print_stack.print token, token.width
188
200
  else
189
201
  advance_right
190
202
  tokens[right] = token
191
- size[right] = token.length
192
- @right_total += token.length
203
+ size[right] = token.width
204
+ @right_total += token.width
193
205
  check_stream
194
206
  end
195
207
  end
@@ -217,10 +229,15 @@ module Oppen
217
229
  #
218
230
  # @return [Nil]
219
231
  def advance_right
220
- @right = (right + 1) % scan_stack.length
232
+ @right = (right + 1) % @size.length
233
+
221
234
  return if right != left
222
235
 
223
- raise 'Token queue full'
236
+ raise 'Token queue full' if !config.upsize_stack?
237
+
238
+ @scan_stack.update_indexes(-@left)
239
+ @size, _left, _right = ScanStack.upsize_circular_array(@size, @left)
240
+ @tokens, @left, @right = ScanStack.upsize_circular_array(@tokens, @left)
224
241
  end
225
242
 
226
243
  # Advances the `left` pointer and lets the print stack
@@ -229,16 +246,16 @@ module Oppen
229
246
  # @note Called AdvanceLeft as well in the original paper.
230
247
  #
231
248
  # @return [Nil]
232
- def advance_left(token, token_length)
233
- return if token_length.negative?
249
+ def advance_left(token, token_width)
250
+ return if token_width.negative?
234
251
 
235
- print_stack.print token, token_length
252
+ print_stack.print token, token_width
236
253
 
237
254
  case token
238
255
  when Token::Break
239
- @left_total += token.length
256
+ @left_total += token.width
240
257
  when Token::String
241
- @left_total += token_length
258
+ @left_total += token_width
242
259
  end
243
260
 
244
261
  return if left == right
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'mixins'
4
+
3
5
  # Oppen.
4
6
  module Oppen
5
7
  # A fixed-size stack that can be popped from top and bottom.
6
8
  class ScanStack
7
- def initialize(size)
9
+ extend Mixins
10
+
11
+ def initialize(size, config)
8
12
  @bottom = 0
13
+ @config = config
9
14
  @empty = true
10
15
  @stack = Array.new(size)
11
16
  @top = 0
@@ -68,7 +73,9 @@ module Oppen
68
73
  else
69
74
  @top = increment(@top)
70
75
  if @top == @bottom
71
- raise 'Stack full'
76
+ raise 'Stack full' if !@config.upsize_stack?
77
+
78
+ @stack, @bottom, @top = ScanStack.upsize_circular_array(@stack, @bottom)
72
79
  end
73
80
  end
74
81
  @stack[@top] = value
@@ -107,5 +114,16 @@ module Oppen
107
114
  end
108
115
  res
109
116
  end
117
+
118
+ # Offset the values of the stack.
119
+ #
120
+ # @param offset [Integer]
121
+ #
122
+ # @return [Array[Integer]]
123
+ def update_indexes(offset)
124
+ @stack = @stack.map { |val|
125
+ (val + offset) % length if val
126
+ }
127
+ end
110
128
  end
111
129
  end
data/lib/oppen/token.rb CHANGED
@@ -18,60 +18,50 @@ module Oppen
18
18
  CONSISTENT = 2
19
19
  end
20
20
 
21
- # Default token length
21
+ # Default token width
22
22
  # @return [Integer]
23
- def length
24
- 0
25
- end
23
+ def width = 0
26
24
 
27
25
  # String Token.
28
26
  class String < Token
29
27
  # @return [String] String value.
30
28
  attr_reader :value
29
+ # @return [Integer]
30
+ attr_reader :width
31
31
 
32
- def initialize(value)
32
+ def initialize(value, width: value.length)
33
33
  @value = value
34
+ @width = width
34
35
  super()
35
36
  end
36
37
 
37
- # @return [Integer]
38
- def length
39
- value.length
40
- end
41
-
42
38
  # @return [String]
43
- def to_s
44
- value
45
- end
39
+ def to_s = value
46
40
  end
47
41
 
48
42
  # Break Token.
49
43
  class Break < Token
50
- # @return [String] Break strings.
51
- attr_reader :str
52
44
  # @return [String] If a new line is needed display this string before the new line
53
45
  attr_reader :line_continuation
54
46
  # @return [Integer] Indentation.
55
47
  attr_reader :offset
48
+ # @return [String] Break strings.
49
+ attr_reader :str
50
+ # @return [Integer]
51
+ attr_reader :width
56
52
 
57
- def initialize(str = ' ', line_continuation: '', offset: 0)
53
+ def initialize(str = ' ', width: str.length, line_continuation: '', offset: 0)
58
54
  raise ArgumentError, 'line_continuation cannot be nil' if line_continuation.nil?
59
55
 
60
56
  @line_continuation = line_continuation
61
57
  @offset = offset
62
58
  @str = str
59
+ @width = width
63
60
  super()
64
61
  end
65
62
 
66
- # @return [Integer]
67
- def length
68
- str.length
69
- end
70
-
71
63
  # @return [String]
72
- def to_s
73
- str
74
- end
64
+ def to_s = str
75
65
  end
76
66
 
77
67
  # Distinguished instance of Break which forces a line break.
@@ -79,9 +69,7 @@ module Oppen
79
69
  # Mock string that represents an infinite string to force new line.
80
70
  class LineBreakString
81
71
  # @return [Integer]
82
- def length
83
- 999_999
84
- end
72
+ def length = 999_999
85
73
  end
86
74
 
87
75
  def initialize(line_continuation: '', offset: 0)
data/lib/oppen/version.rb CHANGED
@@ -5,5 +5,5 @@ module Oppen
5
5
  # Oppen version
6
6
  #
7
7
  # @return [String] current version
8
- VERSION = '0.9.0' # managed by release.sh
8
+ VERSION = '0.9.2' # managed by release.sh
9
9
  end
data/lib/oppen.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'oppen/mixins'
3
4
  require_relative 'oppen/printer'
4
5
  require_relative 'oppen/print_stack'
5
6
  require_relative 'oppen/scan_stack'
@@ -9,6 +10,8 @@ require_relative 'wadler/print'
9
10
 
10
11
  # Oppen.
11
12
  module Oppen
13
+ extend Mixins
14
+
12
15
  # Entry point of the pretty printer.
13
16
  #
14
17
  # @param config [Config]
@@ -18,15 +21,15 @@ module Oppen
18
21
  # to indent.
19
22
  # If it's a callable, it will receive `n` and it needs to return
20
23
  # a string.
21
- # @param margin [Integer] maximum line width desired
22
24
  # @param new_line [String] the delimiter between lines
23
25
  # @param out [Object] should have a write and string method
24
26
  # @param tokens [Array[Token]] the list of tokens to be printed
27
+ # @param width [Integer] maximum line width desired
25
28
  #
26
29
  # @return [String] output of the pretty printer
27
30
  def self.print(config: Config.oppen, space: ' ',
28
- margin: 80, new_line: "\n", out: StringIO.new, tokens: [])
29
- printer = Printer.new margin, new_line, config, space, out
31
+ new_line: "\n", out: StringIO.new, tokens: [], width: 80)
32
+ printer = Printer.new width, new_line, config, space, out
30
33
  tokens.each do |token|
31
34
  printer.print token
32
35
  end
@@ -47,10 +50,17 @@ module Oppen
47
50
  end
48
51
 
49
52
  attr_accessor :indent_anchor
53
+
54
+ def initialize(indent_anchor: IndentAnchor::ON_BREAK, eager_print: false, upsize_stack: false)
55
+ @indent_anchor = indent_anchor
56
+ @eager_print = eager_print
57
+ @upsize_stack = upsize_stack
58
+ end
59
+
50
60
  # Print groups eagerly
51
61
  #
52
62
  # @example
53
- # out = Oppen::Wadler.new (margin: 13)
63
+ # out = Oppen::Wadler.new (width: 13)
54
64
  # out.group {
55
65
  # out.group {
56
66
  # out.text 'abc'
@@ -76,12 +86,9 @@ module Oppen
76
86
  # # jkl
77
87
  #
78
88
  # @return [Boolean]
79
- attr_accessor :eager_print
89
+ def eager_print? = @eager_print
80
90
 
81
- def initialize(indent_anchor: IndentAnchor::ON_BREAK, eager_print: false)
82
- @indent_anchor = indent_anchor
83
- @eager_print = eager_print
84
- end
91
+ def upsize_stack? = @upsize_stack
85
92
 
86
93
  # Default config for Oppen usage
87
94
  # @return [Config]
@@ -91,25 +98,27 @@ module Oppen
91
98
 
92
99
  # Default config for Wadler usage
93
100
  # @return [Config]
94
- def self.wadler(eager_print: true)
95
- new(indent_anchor: IndentAnchor::ON_BEGIN, eager_print:)
101
+ def self.wadler(eager_print: true, upsize_stack: true)
102
+ new(indent_anchor: IndentAnchor::ON_BEGIN, eager_print:, upsize_stack:)
96
103
  end
97
104
  end
98
105
 
99
106
  # @param value [String]
107
+ # @param width [Integer] token width that defaults to value.length
100
108
  #
101
109
  # @return [Oppen::Token::String] a new String token
102
- def self.string(value)
103
- Token::String.new(value)
110
+ def self.string(value, width: value.length)
111
+ Token::String.new(value, width:)
104
112
  end
105
113
 
106
114
  # @param str [String]
107
115
  # @param line_continuation [String] If a new line is needed display this string before the new line
108
116
  # @param offset [Integer]
117
+ # @param width [Integer] token width that defaults to str.length
109
118
  #
110
119
  # @return [Oppen::Token::Break] a new Break token
111
- def self.break(str = ' ', line_continuation: '', offset: 0)
112
- Token::Break.new(str, line_continuation:, offset:)
120
+ def self.break(str = ' ', line_continuation: '', offset: 0, width: str.length)
121
+ Token::Break.new(str, width:, line_continuation:, offset:)
113
122
  end
114
123
 
115
124
  # @param line_continuation [String] If a new line is needed display this string before the new line
data/lib/wadler/print.rb CHANGED
@@ -7,10 +7,10 @@ module Oppen
7
7
  attr_reader :config
8
8
  attr_reader :current_indent
9
9
  attr_reader :space
10
- attr_reader :margin
11
10
  attr_reader :new_line
12
11
  attr_reader :out
13
12
  attr_reader :tokens
13
+ attr_reader :width
14
14
 
15
15
  # @param config [Oppen::Config]
16
16
  # @param space [String, Proc] could be a String or a callable.
@@ -19,15 +19,15 @@ module Oppen
19
19
  # to indent.
20
20
  # If it's a callable, it will receive `n` and it needs to return
21
21
  # a string.
22
- # @param margin [Integer]
23
22
  # @param new_line [String]
24
23
  # @param out [Object] should have a write and string method
24
+ # @param width [Integer]
25
25
  def initialize(config: Config.wadler, space: ' ',
26
- margin: 80, new_line: "\n", out: StringIO.new)
26
+ new_line: "\n", out: StringIO.new, width: 80)
27
27
  @config = config
28
28
  @current_indent = 0
29
29
  @space = space
30
- @margin = margin
30
+ @width = width
31
31
  @new_line = new_line
32
32
  @out = out
33
33
  @tokens = []
@@ -38,7 +38,7 @@ module Oppen
38
38
  if !tokens.last.is_a? Oppen::Token::EOF
39
39
  tokens << Oppen.eof
40
40
  end
41
- Oppen.print(tokens:, margin:, new_line:, config:, space:, out:)
41
+ Oppen.print(tokens:, new_line:, config:, space:, out:, width:)
42
42
  end
43
43
 
44
44
  # @param indent [Integer] group indentation
@@ -110,16 +110,16 @@ module Oppen
110
110
  # @param value [String]
111
111
  #
112
112
  # @return [Nil]
113
- def text(value)
114
- tokens << Oppen.string(value)
113
+ def text(value, width: value.length)
114
+ tokens << Oppen.string(value, width:)
115
115
  end
116
116
 
117
117
  # @param str [String]
118
118
  # @param line_continuation [String] If a new line is needed display this string before the new line
119
119
  #
120
120
  # @return [Nil]
121
- def breakable(str = ' ', line_continuation: '')
122
- tokens << Oppen.break(str, line_continuation:, offset: current_indent)
121
+ def breakable(str = ' ', width: str.length, line_continuation: '')
122
+ tokens << Oppen.break(str, width:, line_continuation:, offset: current_indent)
123
123
  end
124
124
 
125
125
  # @param line_continuation [String] If a new line is needed display this string before the new line
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oppen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amine Mike El Maalouf <amine.el-maalouf@epita.fr>
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-09-27 00:00:00.000000000 Z
12
+ date: 2024-10-25 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Implementation of the Oppen's pretty printing algorithm
15
15
  email:
@@ -20,6 +20,7 @@ files:
20
20
  - LICENSE
21
21
  - README.md
22
22
  - lib/oppen.rb
23
+ - lib/oppen/mixins.rb
23
24
  - lib/oppen/print_stack.rb
24
25
  - lib/oppen/printer.rb
25
26
  - lib/oppen/scan_stack.rb