oppen 0.1.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/README.md +55 -11
- data/lib/oppen/print_stack.rb +59 -16
- data/lib/oppen/printer.rb +22 -6
- data/lib/oppen/token.rb +49 -12
- data/lib/oppen/version.rb +1 -1
- data/lib/oppen.rb +125 -4
- data/lib/wadler/print.rb +132 -0
- metadata +4 -6
- data/bin/main.rb +0 -24
- data/bin/repl.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a496d964de133aa177270080b9444d8e9ef865ce1b355bef4a6c4a43462d48c
|
4
|
+
data.tar.gz: d2955b0983ef133fd374c1c46fc3424d9cff05c4ec910a6ca5ad94ec3f2db78d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4319e6b5be2d8fa394cf8d6cd593c4e8ab0b5c1c0a313cc42e287a93b46e23a95d844b8bddd01e5547ac67faa4b04395d5edb5ae911380572a0df1b7acf0c95
|
7
|
+
data.tar.gz: 680e57e6f38f4cda980b57d04e787b4b33becc7b45de4c73810b243e82169411d62cd0d34b56a67df3e96debcc6d8a2ad777046aedec342429792fc91f3b3346
|
data/README.md
CHANGED
@@ -1,30 +1,74 @@
|
|
1
1
|
# Oppen's Pretty Printer
|
2
2
|
[![CI badge]][CI]
|
3
3
|
[![Docs latest badge]][Docs latest]
|
4
|
-
|
4
|
+
[![rubygems.org badge]][rubygems.org]
|
5
5
|
|
6
6
|
[CI badge]: https://github.com/Faveod/oppen-ruby/actions/workflows/test.yml/badge.svg
|
7
7
|
[CI]: https://github.com/Faveod/oppen-ruby/actions/workflows/test.yml
|
8
8
|
[Docs latest badge]: https://github.com/Faveod/oppen-ruby/actions/workflows/docs.yml/badge.svg
|
9
9
|
[Docs latest]: https://faveod.github.io/oppen-ruby/
|
10
|
+
[rubygems.org badge]: https://img.shields.io/gem/v/oppen?label=rubygems.org
|
11
|
+
[rubygems.org]: https://rubygems.org/gems/oppen
|
10
12
|
|
11
13
|
An implementation of the pretty printing algorithm described by
|
12
14
|
[Derek C. Oppen](https://dl.acm.org/doi/pdf/10.1145/357114.357115).
|
13
15
|
|
14
|
-
|
16
|
+
We also provide an API similar to
|
17
|
+
[`ruby/prettyprint`](https://github.com/ruby/prettyprint), which we call
|
18
|
+
`Wadler`, in reference to Philip Wadler's paper, [_A prettier
|
19
|
+
printer_](https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf),
|
20
|
+
the basis for `prettyprint`. This can be really helpful if you decide to
|
21
|
+
transition from `ruby/prettyprint` to this gem.
|
22
|
+
|
23
|
+
`Wadler` is implemented on top of `Oppen`, and it provides more options than
|
24
|
+
`ruby/prettyprint`, notably:
|
25
|
+
1. Consistent and inconsistent breaking.
|
26
|
+
1. Explicit breaking, which is achievable in `ruby/prettyprint` with some
|
27
|
+
monkeypatching.
|
28
|
+
|
29
|
+
> [!CAUTION]
|
15
30
|
> This is still under development.
|
16
31
|
|
17
|
-
##
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
> [!WARNING]
|
35
|
+
> Lands when the APIs are stable.
|
36
|
+
|
37
|
+
## Oppen vs Wadler
|
38
|
+
|
39
|
+
`Wadler` calls `Oppen` under the hood, so it's not a separate implementation,
|
40
|
+
and it's not calling ruby's `prettyprint`.
|
41
|
+
|
42
|
+
Both implementations have their use cases:
|
43
|
+
1. Oppen gives more control over tokens sent to the printer.
|
44
|
+
1. Wadler gives a more _"functional"_ API, which is far nicer to work with.
|
45
|
+
|
46
|
+
That being said, both APIs in this gem can achieve the same results, especially
|
47
|
+
on consistent and inconsistent breaking.
|
48
|
+
|
49
|
+
## Noteworthy details
|
50
|
+
|
51
|
+
### Difference with Oppen's original algorithm
|
52
|
+
|
53
|
+
1. We took liberty to rename functions to make the API more modern and closer to
|
54
|
+
what we expect when writing Ruby code. All correspondences with the algorithm
|
55
|
+
as described in Oppen's paper are noted in the comments of classes and methods.
|
56
|
+
1. We do not raise exceptions when we overflow the margin. The only exceptions
|
57
|
+
that we raise indicate a bug in the implementation. Please report them.
|
58
|
+
|
59
|
+
### Difference with `ruby/prettyprint`
|
18
60
|
|
19
|
-
|
20
|
-
|
21
|
-
|
61
|
+
Oppen's algorithm and `ruby/prettyprint` do not have the same starting positions
|
62
|
+
for a group's indentation. That's why you need to pay particular attention to
|
63
|
+
calls for `nest`; you might want to decrease them by `1` if you care about keeping
|
64
|
+
the same behavior.
|
22
65
|
|
23
|
-
|
24
|
-
|
66
|
+
This is what we do in our test suite to verify the correspondence of the `Wadler`
|
67
|
+
API and the `ruby/prettyprint`. We decided to shift the burden to the user because
|
68
|
+
we think that the deicision taken by `ruby/prettyprint` does not suit us.
|
25
69
|
|
26
70
|
## Related projects
|
27
71
|
|
28
|
-
1. [
|
29
|
-
|
30
|
-
1. [
|
72
|
+
1. [`ruby/prettyprint`](https://github.com/ruby/prettyprint)
|
73
|
+
1. [rustc implementation](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_pretty/pp/index.html)
|
74
|
+
1. [`stevej2608/oppen-pretty-printer`](https://github.com/stevej2608/oppen-pretty-printer) as a library.
|
data/lib/oppen/print_stack.rb
CHANGED
@@ -21,6 +21,12 @@ module Oppen
|
|
21
21
|
# IO element that builds the output.
|
22
22
|
attr_reader :buffer
|
23
23
|
|
24
|
+
# Config containing customization flags
|
25
|
+
attr_reader :config
|
26
|
+
|
27
|
+
# Callable that generate spaces
|
28
|
+
attr_reader :genspace
|
29
|
+
|
24
30
|
# Array representing the stack of PrintStackEntries.
|
25
31
|
attr_reader :items
|
26
32
|
|
@@ -35,8 +41,18 @@ module Oppen
|
|
35
41
|
# @return [Integer] Current available space (Called index in the original paper).
|
36
42
|
attr_reader :space
|
37
43
|
|
38
|
-
def initialize(margin, new_line)
|
39
|
-
@buffer =
|
44
|
+
def initialize(margin, new_line, config, space, out)
|
45
|
+
@buffer = out
|
46
|
+
@config = config
|
47
|
+
@genspace =
|
48
|
+
if space.respond_to?(:call)
|
49
|
+
raise ArgumentError, 'space argument must be a Proc of arity 1' \
|
50
|
+
if space.to_proc.arity != 1
|
51
|
+
|
52
|
+
space
|
53
|
+
else
|
54
|
+
->(n) { space * n }
|
55
|
+
end
|
40
56
|
@items = []
|
41
57
|
@new_line = new_line
|
42
58
|
@margin = margin
|
@@ -45,7 +61,7 @@ module Oppen
|
|
45
61
|
|
46
62
|
# Returns the output of the print stack
|
47
63
|
#
|
48
|
-
# @return [
|
64
|
+
# @return [String]
|
49
65
|
def output
|
50
66
|
buffer.string
|
51
67
|
end
|
@@ -87,7 +103,15 @@ module Oppen
|
|
87
103
|
else
|
88
104
|
Token::BreakType::INCONSISTENT
|
89
105
|
end
|
90
|
-
|
106
|
+
if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
|
107
|
+
indent = token.offset
|
108
|
+
if !items.empty?
|
109
|
+
indent += top.offset
|
110
|
+
end
|
111
|
+
else
|
112
|
+
indent = space - token.offset
|
113
|
+
end
|
114
|
+
push PrintStackEntry.new indent, type
|
91
115
|
else
|
92
116
|
push PrintStackEntry.new 0, Token::BreakType::FITS
|
93
117
|
end
|
@@ -114,18 +138,32 @@ module Oppen
|
|
114
138
|
block = top
|
115
139
|
case block.break_type
|
116
140
|
in Token::BreakType::FITS
|
117
|
-
@space -= token.
|
118
|
-
|
141
|
+
@space -= token.length
|
142
|
+
write token
|
119
143
|
in Token::BreakType::CONSISTENT
|
120
144
|
@space = block.offset - token.offset
|
121
|
-
|
145
|
+
indent =
|
146
|
+
if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
|
147
|
+
token.offset
|
148
|
+
else
|
149
|
+
margin - space
|
150
|
+
end
|
151
|
+
write token.line_continuation
|
152
|
+
print_new_line indent
|
122
153
|
in Token::BreakType::INCONSISTENT
|
123
154
|
if token_length > space
|
124
155
|
@space = block.offset - token.offset
|
125
|
-
|
156
|
+
indent =
|
157
|
+
if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
|
158
|
+
token.offset
|
159
|
+
else
|
160
|
+
margin - space
|
161
|
+
end
|
162
|
+
write token.line_continuation
|
163
|
+
print_new_line indent
|
126
164
|
else
|
127
|
-
@space -= token.
|
128
|
-
|
165
|
+
@space -= token.length
|
166
|
+
write token
|
129
167
|
end
|
130
168
|
end
|
131
169
|
end
|
@@ -140,7 +178,7 @@ module Oppen
|
|
140
178
|
# @see Token::String
|
141
179
|
def handle_string(token, token_length)
|
142
180
|
@space = [0, space - token_length].max
|
143
|
-
write token
|
181
|
+
write token
|
144
182
|
end
|
145
183
|
|
146
184
|
# Push a PrintStackEntry into the stack.
|
@@ -183,16 +221,21 @@ module Oppen
|
|
183
221
|
# @return [Nil]
|
184
222
|
def print_new_line(amount)
|
185
223
|
write new_line
|
186
|
-
|
224
|
+
if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
|
225
|
+
@space = margin - top.offset - amount
|
226
|
+
indent margin - space
|
227
|
+
else
|
228
|
+
indent amount
|
229
|
+
end
|
187
230
|
end
|
188
231
|
|
189
232
|
# Write a string to the output.
|
190
233
|
#
|
191
|
-
# @param
|
234
|
+
# @param obj [Object]
|
192
235
|
#
|
193
236
|
# @return [Nil]
|
194
|
-
def write(
|
195
|
-
buffer.write(
|
237
|
+
def write(obj)
|
238
|
+
buffer.write(obj.to_s)
|
196
239
|
end
|
197
240
|
|
198
241
|
# Add indentation by `amount`.
|
@@ -203,7 +246,7 @@ module Oppen
|
|
203
246
|
#
|
204
247
|
# @return [Nil]
|
205
248
|
def indent(amount)
|
206
|
-
write
|
249
|
+
write genspace.call(amount)
|
207
250
|
end
|
208
251
|
end
|
209
252
|
end
|
data/lib/oppen/printer.rb
CHANGED
@@ -9,6 +9,7 @@ require_relative 'print_stack'
|
|
9
9
|
module Oppen
|
10
10
|
# Oppen pretty-printer.
|
11
11
|
class Printer
|
12
|
+
attr_reader :config
|
12
13
|
# Ring buffer left index.
|
13
14
|
#
|
14
15
|
# @note Called left as well in the original paper.
|
@@ -51,13 +52,23 @@ module Oppen
|
|
51
52
|
#
|
52
53
|
# @param margin [Integer] maximum line width desired.
|
53
54
|
# @param new_line [String] the delimiter between lines.
|
54
|
-
|
55
|
+
# @param config [Config]
|
56
|
+
# @param space [String, Proc] could be a String or a callable.
|
57
|
+
# If it's a string, spaces will be generated with the the
|
58
|
+
# lambda `->(n){ n * space }`, where `n` is the number of columns
|
59
|
+
# to indent.
|
60
|
+
# If it's a callable, it will receive `n` and it needs to return
|
61
|
+
# a string.
|
62
|
+
# @param out [Object] should have a write and string method
|
63
|
+
def initialize(margin, new_line, config = Config.oppen,
|
64
|
+
space = ' ', out = StringIO.new)
|
55
65
|
# Maximum size if the stacks
|
56
66
|
n = 3 * margin
|
57
67
|
|
68
|
+
@config = config
|
58
69
|
@left = 0
|
59
70
|
@left_total = 1
|
60
|
-
@print_stack = PrintStack.new margin, new_line
|
71
|
+
@print_stack = PrintStack.new margin, new_line, config, space, out
|
61
72
|
@right = 0
|
62
73
|
@right_total = 1
|
63
74
|
@scan_stack = ScanStack.new n
|
@@ -65,7 +76,7 @@ module Oppen
|
|
65
76
|
@tokens = Array.new n
|
66
77
|
end
|
67
78
|
|
68
|
-
# @return [
|
79
|
+
# @return [String]
|
69
80
|
def output
|
70
81
|
print_stack.output
|
71
82
|
end
|
@@ -137,6 +148,11 @@ module Oppen
|
|
137
148
|
tokens[right] = token
|
138
149
|
size[right] = -1
|
139
150
|
scan_stack.push right
|
151
|
+
if config&.eager_print &&
|
152
|
+
(!scan_stack.empty? && right_total - left_total < print_stack.space)
|
153
|
+
check_stack 0
|
154
|
+
advance_left tokens[left], size[left]
|
155
|
+
end
|
140
156
|
end
|
141
157
|
end
|
142
158
|
|
@@ -158,7 +174,7 @@ module Oppen
|
|
158
174
|
scan_stack.push right
|
159
175
|
tokens[right] = token
|
160
176
|
size[right] = -right_total
|
161
|
-
@right_total += token.
|
177
|
+
@right_total += token.length
|
162
178
|
end
|
163
179
|
|
164
180
|
# Handle String Token.
|
@@ -220,7 +236,7 @@ module Oppen
|
|
220
236
|
|
221
237
|
case token
|
222
238
|
when Token::Break
|
223
|
-
@left_total += token.
|
239
|
+
@left_total += token.length
|
224
240
|
when Token::String
|
225
241
|
@left_total += token_length
|
226
242
|
end
|
@@ -239,7 +255,7 @@ module Oppen
|
|
239
255
|
# @param depth [Integer] depth of the group
|
240
256
|
#
|
241
257
|
# @return [Nil]
|
242
|
-
def check_stack(depth)
|
258
|
+
def check_stack(depth)
|
243
259
|
return if scan_stack.empty?
|
244
260
|
|
245
261
|
x = scan_stack.top
|
data/lib/oppen/token.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# Oppen.
|
4
4
|
module Oppen
|
5
5
|
# Token.
|
6
|
-
|
6
|
+
class Token
|
7
7
|
# BreakType.
|
8
8
|
#
|
9
9
|
# FITS => No break is needed (the block fits on the line).
|
@@ -18,43 +18,79 @@ module Oppen
|
|
18
18
|
CONSISTENT = 2
|
19
19
|
end
|
20
20
|
|
21
|
+
# Default token length
|
22
|
+
# @return [Integer]
|
23
|
+
def length
|
24
|
+
0
|
25
|
+
end
|
26
|
+
|
21
27
|
# String Token.
|
22
|
-
class String
|
28
|
+
class String < Token
|
23
29
|
# @return [String] String value.
|
24
30
|
attr_reader :value
|
25
31
|
|
26
32
|
def initialize(value)
|
27
33
|
@value = value
|
34
|
+
super()
|
28
35
|
end
|
29
36
|
|
30
37
|
# @return [Integer]
|
31
38
|
def length
|
32
39
|
value.length
|
33
40
|
end
|
41
|
+
|
42
|
+
# @return [String]
|
43
|
+
def to_s
|
44
|
+
value
|
45
|
+
end
|
34
46
|
end
|
35
47
|
|
36
48
|
# Break Token.
|
37
|
-
class Break
|
38
|
-
# @return [
|
39
|
-
attr_reader :
|
49
|
+
class Break < Token
|
50
|
+
# @return [String] Break strings.
|
51
|
+
attr_reader :str
|
52
|
+
# @return [String] If a new line is needed display this string before the new line
|
53
|
+
attr_reader :line_continuation
|
40
54
|
# @return [Integer] Indentation.
|
41
55
|
attr_reader :offset
|
42
56
|
|
43
|
-
def initialize(
|
44
|
-
|
57
|
+
def initialize(str = ' ', line_continuation: '', offset: 0)
|
58
|
+
raise ArgumentError, 'line_continuation cannot be nil' if line_continuation.nil?
|
59
|
+
|
60
|
+
@line_continuation = line_continuation
|
45
61
|
@offset = offset
|
62
|
+
@str = str
|
63
|
+
super()
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Integer]
|
67
|
+
def length
|
68
|
+
str.length
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [String]
|
72
|
+
def to_s
|
73
|
+
str
|
46
74
|
end
|
47
75
|
end
|
48
76
|
|
49
77
|
# Distinguished instance of Break which forces a line break.
|
50
78
|
class LineBreak < Break
|
51
|
-
|
52
|
-
|
79
|
+
# Mock string that represents an infinite string to force new line.
|
80
|
+
class LineBreakString
|
81
|
+
# @return [Integer]
|
82
|
+
def length
|
83
|
+
999_999
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize(line_continuation: '', offset: 0)
|
88
|
+
super(LineBreakString.new, line_continuation:, offset:)
|
53
89
|
end
|
54
90
|
end
|
55
91
|
|
56
92
|
# Begin Token.
|
57
|
-
class Begin
|
93
|
+
class Begin < Token
|
58
94
|
# @return [BreakType]
|
59
95
|
attr_reader :break_type
|
60
96
|
# @return [Integer] Indentation.
|
@@ -63,16 +99,17 @@ module Oppen
|
|
63
99
|
def initialize(break_type: BreakType::INCONSISTENT, offset: 2)
|
64
100
|
@offset = offset
|
65
101
|
@break_type = break_type
|
102
|
+
super()
|
66
103
|
end
|
67
104
|
end
|
68
105
|
|
69
106
|
# End Token
|
70
|
-
class End
|
107
|
+
class End < Token
|
71
108
|
nil
|
72
109
|
end
|
73
110
|
|
74
111
|
# EOF Token
|
75
|
-
class EOF
|
112
|
+
class EOF < Token
|
76
113
|
nil
|
77
114
|
end
|
78
115
|
end
|
data/lib/oppen/version.rb
CHANGED
data/lib/oppen.rb
CHANGED
@@ -5,21 +5,142 @@ require_relative 'oppen/print_stack'
|
|
5
5
|
require_relative 'oppen/scan_stack'
|
6
6
|
require_relative 'oppen/token'
|
7
7
|
require_relative 'oppen/version'
|
8
|
+
require_relative 'wadler/print'
|
8
9
|
|
9
10
|
# Oppen.
|
10
11
|
module Oppen
|
11
12
|
# Entry point of the pretty printer.
|
12
13
|
#
|
13
|
-
# @param
|
14
|
+
# @param config [Config]
|
15
|
+
# @param space [String, Proc] could be a String or a callable.
|
16
|
+
# If it's a string, spaces will be generated with the the
|
17
|
+
# lambda `->(n){ n * space }`, where `n` is the number of columns
|
18
|
+
# to indent.
|
19
|
+
# If it's a callable, it will receive `n` and it needs to return
|
20
|
+
# a string.
|
14
21
|
# @param margin [Integer] maximum line width desired
|
15
22
|
# @param new_line [String] the delimiter between lines
|
23
|
+
# @param out [Object] should have a write and string method
|
24
|
+
# @param tokens [Array[Token]] the list of tokens to be printed
|
16
25
|
#
|
17
|
-
# @return [
|
18
|
-
def self.print(
|
19
|
-
|
26
|
+
# @return [String] output of the pretty printer
|
27
|
+
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
|
20
30
|
tokens.each do |token|
|
21
31
|
printer.print token
|
22
32
|
end
|
23
33
|
printer.output
|
24
34
|
end
|
35
|
+
|
36
|
+
# Config.
|
37
|
+
class Config
|
38
|
+
# IndentAnchor.
|
39
|
+
#
|
40
|
+
# ON_BREAK => anchor on break position (as in Oppen's original paper)
|
41
|
+
# ON_BEGIN => anchor on begin block position
|
42
|
+
module IndentAnchor
|
43
|
+
# @return [Integer]
|
44
|
+
ON_BREAK = 0
|
45
|
+
# @return [Integer]
|
46
|
+
ON_BEGIN = 1
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_accessor :indent_anchor
|
50
|
+
# Print groups eagerly
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# out = Oppen::Wadler.new (margin: 13)
|
54
|
+
# out.group {
|
55
|
+
# out.group {
|
56
|
+
# out.text 'abc'
|
57
|
+
# out.breakable
|
58
|
+
# out.text 'def'
|
59
|
+
# }
|
60
|
+
# out.group {
|
61
|
+
# out.text 'ghi'
|
62
|
+
# out.breakable
|
63
|
+
# out.text 'jkl'
|
64
|
+
# }
|
65
|
+
# }
|
66
|
+
# out.output
|
67
|
+
#
|
68
|
+
# # eager_print: false
|
69
|
+
# # =>
|
70
|
+
# # abc
|
71
|
+
# # defghi jkl
|
72
|
+
# #
|
73
|
+
# # eager_print: true
|
74
|
+
# # =>
|
75
|
+
# # abc defghi
|
76
|
+
# # jkl
|
77
|
+
#
|
78
|
+
# @return [Boolean]
|
79
|
+
attr_accessor :eager_print
|
80
|
+
|
81
|
+
def initialize(indent_anchor: IndentAnchor::ON_BREAK, eager_print: false)
|
82
|
+
@indent_anchor = indent_anchor
|
83
|
+
@eager_print = eager_print
|
84
|
+
end
|
85
|
+
|
86
|
+
# Default config for Oppen usage
|
87
|
+
# @return [Config]
|
88
|
+
def self.oppen
|
89
|
+
new
|
90
|
+
end
|
91
|
+
|
92
|
+
# Default config for Wadler usage
|
93
|
+
# @return [Config]
|
94
|
+
def self.wadler(eager_print: true)
|
95
|
+
new(indent_anchor: IndentAnchor::ON_BEGIN, eager_print:)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param value [String]
|
100
|
+
#
|
101
|
+
# @return [Oppen::Token::String] a new String token
|
102
|
+
def self.string(value)
|
103
|
+
Token::String.new(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# @param str [String]
|
107
|
+
# @param line_continuation [String] If a new line is needed display this string before the new line
|
108
|
+
# @param offset [Integer]
|
109
|
+
#
|
110
|
+
# @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:)
|
113
|
+
end
|
114
|
+
|
115
|
+
# @param line_continuation [String] If a new line is needed display this string before the new line
|
116
|
+
# @param offset [Integer]
|
117
|
+
#
|
118
|
+
# @return [Oppen::Token::LineBreak] a new LineBreak token
|
119
|
+
def self.line_break(line_continuation: '', offset: 0)
|
120
|
+
Token::LineBreak.new(line_continuation:, offset:)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @param offset [Integer]
|
124
|
+
#
|
125
|
+
# @return [Oppen::Token::Begin] a new consistent Begin token
|
126
|
+
def self.begin_consistent(offset: 2)
|
127
|
+
Token::Begin.new(break_type: Token::BreakType::CONSISTENT, offset:)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @param offset [Integer]
|
131
|
+
#
|
132
|
+
# @return [Oppen::Token::Begin] a new inconsistent Begin token
|
133
|
+
def self.begin_inconsistent(offset: 2)
|
134
|
+
Token::Begin.new(break_type: Token::BreakType::INCONSISTENT, offset:)
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [Oppen::Token::End] a new End token
|
138
|
+
def self.end
|
139
|
+
Token::End.new
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [Oppen::Token::EOF] a new EOF token
|
143
|
+
def self.eof
|
144
|
+
Token::EOF.new
|
145
|
+
end
|
25
146
|
end
|
data/lib/wadler/print.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Oppen.
|
4
|
+
module Oppen
|
5
|
+
# Wadler.
|
6
|
+
class Wadler
|
7
|
+
attr_reader :config
|
8
|
+
attr_reader :current_indent
|
9
|
+
attr_reader :space
|
10
|
+
attr_reader :margin
|
11
|
+
attr_reader :new_line
|
12
|
+
attr_reader :out
|
13
|
+
attr_reader :tokens
|
14
|
+
|
15
|
+
# @param config [Oppen::Config]
|
16
|
+
# @param space [String, Proc] could be a String or a callable.
|
17
|
+
# If it's a string, spaces will be generated with the the
|
18
|
+
# lambda `->(n){ n * space }`, where `n` is the number of columns
|
19
|
+
# to indent.
|
20
|
+
# If it's a callable, it will receive `n` and it needs to return
|
21
|
+
# a string.
|
22
|
+
# @param margin [Integer]
|
23
|
+
# @param new_line [String]
|
24
|
+
# @param out [Object] should have a write and string method
|
25
|
+
def initialize(config: Config.wadler, space: ' ',
|
26
|
+
margin: 80, new_line: "\n", out: StringIO.new)
|
27
|
+
@config = config
|
28
|
+
@current_indent = 0
|
29
|
+
@space = space
|
30
|
+
@margin = margin
|
31
|
+
@new_line = new_line
|
32
|
+
@out = out
|
33
|
+
@tokens = []
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String]
|
37
|
+
def output
|
38
|
+
if !tokens.last.is_a? Oppen::Token::EOF
|
39
|
+
tokens << Oppen.eof
|
40
|
+
end
|
41
|
+
Oppen.print(tokens:, margin:, new_line:, config:, space:, out:)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param indent [Integer] group indentation
|
45
|
+
# @param open_obj [String] group opening delimiter
|
46
|
+
# @param close_obj [String] group closing delimiter
|
47
|
+
# @param break_type [Oppen::Token::BreakType] group breaking type
|
48
|
+
#
|
49
|
+
# @yield the block of text in a group
|
50
|
+
#
|
51
|
+
# @return [Nil]
|
52
|
+
def group(indent = 0, open_obj = '', close_obj = '',
|
53
|
+
break_type = Oppen::Token::BreakType::CONSISTENT)
|
54
|
+
raise ArgumentError, "#{open_obj.nil? ? 'open_obj' : 'close_obj'} cannot be nil" \
|
55
|
+
if open_obj.nil? || close_obj.nil?
|
56
|
+
|
57
|
+
tokens <<
|
58
|
+
case break_type
|
59
|
+
in Oppen::Token::BreakType::CONSISTENT
|
60
|
+
Oppen.begin_consistent(offset: indent)
|
61
|
+
in Oppen::Token::BreakType::INCONSISTENT
|
62
|
+
Oppen.begin_inconsistent(offset: indent)
|
63
|
+
end
|
64
|
+
|
65
|
+
if !open_obj.empty?
|
66
|
+
self.break
|
67
|
+
text(open_obj)
|
68
|
+
end
|
69
|
+
|
70
|
+
yield
|
71
|
+
|
72
|
+
if !close_obj.empty?
|
73
|
+
self.break
|
74
|
+
text(close_obj)
|
75
|
+
end
|
76
|
+
|
77
|
+
tokens << Oppen.end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param indent [Integer] nest indentation
|
81
|
+
# @param open_obj [String] nest opening delimiter
|
82
|
+
# @param close_obj [String] nest closing delimiter
|
83
|
+
# @param break_type [Oppen::Token::BreakType] nest breaking type
|
84
|
+
#
|
85
|
+
# @return [Nil]
|
86
|
+
def nest(indent, open_obj = '', close_obj = '',
|
87
|
+
break_type = Oppen::Token::BreakType::CONSISTENT)
|
88
|
+
raise ArgumentError, "#{open_obj.nil? ? 'open_obj' : 'close_obj'} cannot be nil" \
|
89
|
+
if open_obj.nil? || close_obj.nil?
|
90
|
+
|
91
|
+
@current_indent += indent
|
92
|
+
|
93
|
+
if !open_obj.empty?
|
94
|
+
text(open_obj)
|
95
|
+
self.break
|
96
|
+
end
|
97
|
+
|
98
|
+
begin
|
99
|
+
yield
|
100
|
+
ensure
|
101
|
+
@current_indent -= indent
|
102
|
+
end
|
103
|
+
|
104
|
+
return if close_obj.empty?
|
105
|
+
|
106
|
+
self.break
|
107
|
+
text(close_obj)
|
108
|
+
end
|
109
|
+
|
110
|
+
# @param value [String]
|
111
|
+
#
|
112
|
+
# @return [Nil]
|
113
|
+
def text(value)
|
114
|
+
tokens << Oppen.string(value)
|
115
|
+
end
|
116
|
+
|
117
|
+
# @param str [String]
|
118
|
+
# @param line_continuation [String] If a new line is needed display this string before the new line
|
119
|
+
#
|
120
|
+
# @return [Nil]
|
121
|
+
def breakable(str = ' ', line_continuation: '')
|
122
|
+
tokens << Oppen.break(str, line_continuation:, offset: current_indent)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param line_continuation [String] If a new line is needed display this string before the new line
|
126
|
+
#
|
127
|
+
# @return [Nil]
|
128
|
+
def break(line_continuation: '')
|
129
|
+
tokens << Oppen.line_break(line_continuation:, offset: current_indent)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
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.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amine Mike El Maalouf <amine.el-maalouf@epita.fr>
|
@@ -9,25 +9,23 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-09-
|
12
|
+
date: 2024-09-27 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Implementation of the Oppen's pretty printing algorithm
|
15
15
|
email:
|
16
|
-
executables:
|
17
|
-
- main.rb
|
16
|
+
executables: []
|
18
17
|
extensions: []
|
19
18
|
extra_rdoc_files: []
|
20
19
|
files:
|
21
20
|
- LICENSE
|
22
21
|
- README.md
|
23
|
-
- bin/main.rb
|
24
|
-
- bin/repl.rb
|
25
22
|
- lib/oppen.rb
|
26
23
|
- lib/oppen/print_stack.rb
|
27
24
|
- lib/oppen/printer.rb
|
28
25
|
- lib/oppen/scan_stack.rb
|
29
26
|
- lib/oppen/token.rb
|
30
27
|
- lib/oppen/version.rb
|
28
|
+
- lib/wadler/print.rb
|
31
29
|
homepage: http://github.com/Faveod/oppen-ruby
|
32
30
|
licenses:
|
33
31
|
- MIT
|
data/bin/main.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
lib = File.expand_path('lib', __dir__)
|
5
|
-
$LOAD_PATH.unshift(lib) if !$LOAD_PATH.include?(lib)
|
6
|
-
|
7
|
-
require 'oppen'
|
8
|
-
|
9
|
-
list = [
|
10
|
-
Oppen::Token::Begin.new,
|
11
|
-
Oppen::Token::String.new('XXXXXXXXXX'),
|
12
|
-
Oppen::Token::Break.new,
|
13
|
-
Oppen::Token::String.new('+'),
|
14
|
-
Oppen::Token::Break.new,
|
15
|
-
Oppen::Token::String.new('YYYYYYYYYY'),
|
16
|
-
Oppen::Token::Break.new,
|
17
|
-
Oppen::Token::String.new('+'),
|
18
|
-
Oppen::Token::Break.new,
|
19
|
-
Oppen::Token::String.new('ZZZZZZZZZZ'),
|
20
|
-
Oppen::Token::End.new,
|
21
|
-
Oppen::Token::EOF.new,
|
22
|
-
]
|
23
|
-
|
24
|
-
puts Oppen.print tokens: list, line_width: 25
|
data/bin/repl.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'colored'
|
4
|
-
|
5
|
-
# Fancy
|
6
|
-
WELCOME = <<-'YGM'
|
7
|
-
__ __ ___ __ __ ____ ____ ___ ___ ___ ____ ____ ___ ___ ___
|
8
|
-
| | | / \ | | || \ / | / _]| | || \ / || | | / _]
|
9
|
-
| | || || | || D )| __| / [_ | _ _ || _ || o || _ _ | / [_
|
10
|
-
| ~ || O || | || / | | || _]| \_/ || | || || \_/ || _]
|
11
|
-
|___, || || : || \ | |_ || [_ | | || | || _ || | || [_
|
12
|
-
| || || || . \| ______ ______ ______ __ || |
|
13
|
-
|____/ \___/ \__,_||__|\_||___ /\ == \ /\ ___\ /\ == \ /\ \ ___||_____|
|
14
|
-
\ \ __< \ \ __\ \ \ _-/ \ \ \____
|
15
|
-
\ \_\ \_\ \ \_____\ \ \_\ \ \_____\
|
16
|
-
\/_/ /_/ \/_____/ \/_/ \/_____/
|
17
|
-
YGM
|
18
|
-
|
19
|
-
puts WELCOME.green
|