oppen 0.1.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 +7 -0
- data/LICENSE +25 -0
- data/README.md +30 -0
- data/bin/main.rb +24 -0
- data/bin/repl.rb +19 -0
- data/lib/oppen/print_stack.rb +209 -0
- data/lib/oppen/printer.rb +263 -0
- data/lib/oppen/scan_stack.rb +111 -0
- data/lib/oppen/token.rb +79 -0
- data/lib/oppen/version.rb +9 -0
- data/lib/oppen.rb +25 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3566ebc351b729f845e192fb3687bfae4a874879b3741de2e1c43fb97946aa23
|
4
|
+
data.tar.gz: 9499a00567a1ad0d7f7ffc551573888513eb952945a83d90022a499d31248828
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0f1348d6dac1afc87a495f2e395ed63a28ae42a722c235f1fe33fc317f1e13b0cbe5d7cae51c67a446755a7f26382c9b7555b80e394e9bdc161e620c2fbfde44
|
7
|
+
data.tar.gz: 430164e1995f18e4c75cddd59e6e39f6669c9312d01d29cb9670298b3459fb345ccd1a22742728b951859c248e628e0b1b968968ad00f025ea070173816fb476
|
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Copyright © `<2024>` `<Faveod>`
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
7
|
+
obtaining a copy of this software and associated documentation
|
8
|
+
files (the “Software”), to deal in the Software without
|
9
|
+
restriction, including without limitation the rights to use,
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
copies of the Software, and to permit persons to whom the
|
12
|
+
Software is furnished to do so, subject to the following
|
13
|
+
conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Oppen's Pretty Printer
|
2
|
+
[![CI badge]][CI]
|
3
|
+
[![Docs latest badge]][Docs latest]
|
4
|
+
<!-- [![rubygems.org badge]][rubygems.org] -->
|
5
|
+
|
6
|
+
[CI badge]: https://github.com/Faveod/oppen-ruby/actions/workflows/test.yml/badge.svg
|
7
|
+
[CI]: https://github.com/Faveod/oppen-ruby/actions/workflows/test.yml
|
8
|
+
[Docs latest badge]: https://github.com/Faveod/oppen-ruby/actions/workflows/docs.yml/badge.svg
|
9
|
+
[Docs latest]: https://faveod.github.io/oppen-ruby/
|
10
|
+
|
11
|
+
An implementation of the pretty printing algorithm described by
|
12
|
+
[Derek C. Oppen](https://dl.acm.org/doi/pdf/10.1145/357114.357115).
|
13
|
+
|
14
|
+
> [!WARNING]
|
15
|
+
> This is still under development.
|
16
|
+
|
17
|
+
## Difference with the original algorithm
|
18
|
+
|
19
|
+
We decided to diverge from Oppen's original algorithm to provide a more
|
20
|
+
idiomatic Ruby experience, in one particular aspect: exceptions/errors: we do
|
21
|
+
not raise exceptions when we overflow the margin.
|
22
|
+
|
23
|
+
The only exceptions that we raise indicate a bug in the implementation. Please
|
24
|
+
report them.
|
25
|
+
|
26
|
+
## Related projects
|
27
|
+
|
28
|
+
1. [Python implementation](https://github.com/stevej2608/oppen-pretty-printer)
|
29
|
+
as a library.
|
30
|
+
1. [rustc implementation](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_pretty/pp/index.html)
|
data/bin/main.rb
ADDED
@@ -0,0 +1,24 @@
|
|
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
ADDED
@@ -0,0 +1,19 @@
|
|
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
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Oppen.
|
4
|
+
module Oppen
|
5
|
+
# Class that represents a stack that builds an output string
|
6
|
+
# using the values of the tokens that were pushed into it.
|
7
|
+
class PrintStack
|
8
|
+
# Class that represents an item in the print stack.
|
9
|
+
class PrintStackEntry
|
10
|
+
# @return [Integer] Indentation level.
|
11
|
+
attr_reader :offset
|
12
|
+
# @return [Token::BreakType] (Called break in the original paper).
|
13
|
+
attr_reader :break_type
|
14
|
+
|
15
|
+
def initialize(offset, break_type)
|
16
|
+
@offset = offset
|
17
|
+
@break_type = break_type
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# IO element that builds the output.
|
22
|
+
attr_reader :buffer
|
23
|
+
|
24
|
+
# Array representing the stack of PrintStackEntries.
|
25
|
+
attr_reader :items
|
26
|
+
|
27
|
+
# Delimiter between lines in output
|
28
|
+
attr_reader :new_line
|
29
|
+
|
30
|
+
# Page margin (Called length in the original paper).
|
31
|
+
attr_reader :margin
|
32
|
+
|
33
|
+
# Current available space (Called index in the original paper).
|
34
|
+
#
|
35
|
+
# @return [Integer] Current available space (Called index in the original paper).
|
36
|
+
attr_reader :space
|
37
|
+
|
38
|
+
def initialize(margin, new_line)
|
39
|
+
@buffer = StringIO.new
|
40
|
+
@items = []
|
41
|
+
@new_line = new_line
|
42
|
+
@margin = margin
|
43
|
+
@space = margin
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the output of the print stack
|
47
|
+
#
|
48
|
+
# @return [StringIO]
|
49
|
+
def output
|
50
|
+
buffer.string
|
51
|
+
end
|
52
|
+
|
53
|
+
# Core method responsible for building the print stack and the output string.
|
54
|
+
#
|
55
|
+
# @note Called Print in the original paper.
|
56
|
+
#
|
57
|
+
# @param token [Token]
|
58
|
+
# @param token_length [Integer]
|
59
|
+
#
|
60
|
+
# @return [Nil]
|
61
|
+
def print(token, token_length)
|
62
|
+
case token
|
63
|
+
in Token::Begin
|
64
|
+
handle_begin token, token_length
|
65
|
+
in Token::End
|
66
|
+
handle_end
|
67
|
+
in Token::Break
|
68
|
+
handle_break token, token_length
|
69
|
+
in Token::String
|
70
|
+
handle_string token, token_length
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Handle Begin Token.
|
75
|
+
#
|
76
|
+
# @param token [Token]
|
77
|
+
# @param token_length [Integer]
|
78
|
+
#
|
79
|
+
# @return [Nil]
|
80
|
+
#
|
81
|
+
# @see Token::Begin
|
82
|
+
def handle_begin(token, token_length)
|
83
|
+
if token_length > space
|
84
|
+
type =
|
85
|
+
if token.break_type == Token::BreakType::CONSISTENT
|
86
|
+
Token::BreakType::CONSISTENT
|
87
|
+
else
|
88
|
+
Token::BreakType::INCONSISTENT
|
89
|
+
end
|
90
|
+
push PrintStackEntry.new space - token.offset, type
|
91
|
+
else
|
92
|
+
push PrintStackEntry.new 0, Token::BreakType::FITS
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Handle End Token.
|
97
|
+
#
|
98
|
+
# @return [Nil]
|
99
|
+
#
|
100
|
+
# @see Token::End
|
101
|
+
def handle_end
|
102
|
+
pop
|
103
|
+
end
|
104
|
+
|
105
|
+
# Handle Break Token.
|
106
|
+
#
|
107
|
+
# @param token [Token]
|
108
|
+
# @param token_length [Integer]
|
109
|
+
#
|
110
|
+
# @return [Nil]
|
111
|
+
#
|
112
|
+
# @see Token::Break
|
113
|
+
def handle_break(token, token_length)
|
114
|
+
block = top
|
115
|
+
case block.break_type
|
116
|
+
in Token::BreakType::FITS
|
117
|
+
@space -= token.blank_space
|
118
|
+
indent token.blank_space
|
119
|
+
in Token::BreakType::CONSISTENT
|
120
|
+
@space = block.offset - token.offset
|
121
|
+
print_new_line margin - space
|
122
|
+
in Token::BreakType::INCONSISTENT
|
123
|
+
if token_length > space
|
124
|
+
@space = block.offset - token.offset
|
125
|
+
print_new_line margin - space
|
126
|
+
else
|
127
|
+
@space -= token.blank_space
|
128
|
+
indent token.blank_space
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Handle String Token.
|
134
|
+
#
|
135
|
+
# @param token [Token]
|
136
|
+
# @param token_length [Integer]
|
137
|
+
#
|
138
|
+
# @return [Nil]
|
139
|
+
#
|
140
|
+
# @see Token::String
|
141
|
+
def handle_string(token, token_length)
|
142
|
+
@space = [0, space - token_length].max
|
143
|
+
write token.value
|
144
|
+
end
|
145
|
+
|
146
|
+
# Push a PrintStackEntry into the stack.
|
147
|
+
#
|
148
|
+
# @param print_stack_entry [PrintStackEntry]
|
149
|
+
#
|
150
|
+
# @return [Nil]
|
151
|
+
def push(print_stack_entry)
|
152
|
+
items.append(print_stack_entry)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Pop a PrintStackEntry from the stack.
|
156
|
+
#
|
157
|
+
# @return [PrintStackEntry]
|
158
|
+
def pop
|
159
|
+
if items.empty?
|
160
|
+
raise 'Popping empty stack'
|
161
|
+
end
|
162
|
+
|
163
|
+
items.pop
|
164
|
+
end
|
165
|
+
|
166
|
+
# Get the element at the top of the stack.
|
167
|
+
#
|
168
|
+
# @return [PrintStackEntry]
|
169
|
+
def top
|
170
|
+
if items.empty?
|
171
|
+
raise 'Accessing empty stack'
|
172
|
+
end
|
173
|
+
|
174
|
+
items.last
|
175
|
+
end
|
176
|
+
|
177
|
+
# Add a new line to the output.
|
178
|
+
#
|
179
|
+
# @note Called PrintNewLine as well in the original paper.
|
180
|
+
#
|
181
|
+
# @param amount [Integer] indentation amount.
|
182
|
+
#
|
183
|
+
# @return [Nil]
|
184
|
+
def print_new_line(amount)
|
185
|
+
write new_line
|
186
|
+
indent amount
|
187
|
+
end
|
188
|
+
|
189
|
+
# Write a string to the output.
|
190
|
+
#
|
191
|
+
# @param string [String]
|
192
|
+
#
|
193
|
+
# @return [Nil]
|
194
|
+
def write(string)
|
195
|
+
buffer.write(string)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Add indentation by `amount`.
|
199
|
+
#
|
200
|
+
# @note Called Indent as well in the original paper.
|
201
|
+
#
|
202
|
+
# @param amount [Integer]
|
203
|
+
#
|
204
|
+
# @return [Nil]
|
205
|
+
def indent(amount)
|
206
|
+
write ' ' * amount
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
require_relative 'scan_stack'
|
6
|
+
require_relative 'print_stack'
|
7
|
+
|
8
|
+
# Oppen.
|
9
|
+
module Oppen
|
10
|
+
# Oppen pretty-printer.
|
11
|
+
class Printer
|
12
|
+
# Ring buffer left index.
|
13
|
+
#
|
14
|
+
# @note Called left as well in the original paper.
|
15
|
+
attr_reader :left
|
16
|
+
|
17
|
+
# Total number of spaces needed to print from start of buffer to the left.
|
18
|
+
#
|
19
|
+
# @note Called leftTotal as well in the original paper.
|
20
|
+
attr_reader :left_total
|
21
|
+
|
22
|
+
# @note Called printStack as well in the original paper.
|
23
|
+
attr_reader :print_stack
|
24
|
+
|
25
|
+
# Ring buffer right index.
|
26
|
+
#
|
27
|
+
# @note Called right as well in the original paper.
|
28
|
+
attr_reader :right
|
29
|
+
|
30
|
+
# Total number of spaces needed to print from start of buffer to the right.
|
31
|
+
#
|
32
|
+
# @note Called leftTotal as well in the original paper.
|
33
|
+
attr_reader :right_total
|
34
|
+
|
35
|
+
# Potential breaking positions.
|
36
|
+
#
|
37
|
+
# @note Called scanStack as well in the original paper.
|
38
|
+
attr_reader :scan_stack
|
39
|
+
|
40
|
+
# Size buffer, initially filled with nil.
|
41
|
+
#
|
42
|
+
# @note Called size as well in the original paper.
|
43
|
+
attr_reader :size
|
44
|
+
|
45
|
+
# Token buffer, initially filled with nil.
|
46
|
+
#
|
47
|
+
# @note Called token in the original paper.
|
48
|
+
attr_reader :tokens
|
49
|
+
|
50
|
+
# @note Called PrettyPrintInit in the original paper.
|
51
|
+
#
|
52
|
+
# @param margin [Integer] maximum line width desired.
|
53
|
+
# @param new_line [String] the delimiter between lines.
|
54
|
+
def initialize(margin, new_line)
|
55
|
+
# Maximum size if the stacks
|
56
|
+
n = 3 * margin
|
57
|
+
|
58
|
+
@left = 0
|
59
|
+
@left_total = 1
|
60
|
+
@print_stack = PrintStack.new margin, new_line
|
61
|
+
@right = 0
|
62
|
+
@right_total = 1
|
63
|
+
@scan_stack = ScanStack.new n
|
64
|
+
@size = Array.new n
|
65
|
+
@tokens = Array.new n
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [StringIO]
|
69
|
+
def output
|
70
|
+
print_stack.output
|
71
|
+
end
|
72
|
+
|
73
|
+
# Core function of the algorithm responsible for populating the scan and print stack.
|
74
|
+
#
|
75
|
+
# @note Called PrettyPrint as well in the original paper.
|
76
|
+
#
|
77
|
+
# @param token [Token]
|
78
|
+
#
|
79
|
+
# @return [Nil]
|
80
|
+
def print(token)
|
81
|
+
case token
|
82
|
+
in Token::EOF
|
83
|
+
handle_eof
|
84
|
+
in Token::Begin
|
85
|
+
handle_begin token
|
86
|
+
in Token::End
|
87
|
+
handle_end token
|
88
|
+
in Token::Break
|
89
|
+
handle_break token
|
90
|
+
in Token::String
|
91
|
+
handle_string token
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Handle EOF Token.
|
96
|
+
#
|
97
|
+
# @return [Nil]
|
98
|
+
#
|
99
|
+
# @see Token::EOF
|
100
|
+
def handle_eof
|
101
|
+
if !scan_stack.empty?
|
102
|
+
check_stack 0
|
103
|
+
advance_left tokens[left], size[left]
|
104
|
+
end
|
105
|
+
print_stack.indent 0
|
106
|
+
end
|
107
|
+
|
108
|
+
# Handle Begin Token.
|
109
|
+
#
|
110
|
+
# @return [Nil]
|
111
|
+
#
|
112
|
+
# @see Token::Begin
|
113
|
+
def handle_begin(token)
|
114
|
+
if scan_stack.empty?
|
115
|
+
@left = 0
|
116
|
+
@left_total = 1
|
117
|
+
@right = 0
|
118
|
+
@right_total = 1
|
119
|
+
else
|
120
|
+
advance_right
|
121
|
+
end
|
122
|
+
tokens[right] = token
|
123
|
+
size[right] = -right_total
|
124
|
+
scan_stack.push right
|
125
|
+
end
|
126
|
+
|
127
|
+
# Handle End Token.
|
128
|
+
#
|
129
|
+
# @return [Nil]
|
130
|
+
#
|
131
|
+
# @see Token::End
|
132
|
+
def handle_end(token)
|
133
|
+
if scan_stack.empty?
|
134
|
+
print_stack.print token, 0
|
135
|
+
else
|
136
|
+
advance_right
|
137
|
+
tokens[right] = token
|
138
|
+
size[right] = -1
|
139
|
+
scan_stack.push right
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Handle Break Token.
|
144
|
+
#
|
145
|
+
# @return [Nil]
|
146
|
+
#
|
147
|
+
# @see Token::Break
|
148
|
+
def handle_break(token)
|
149
|
+
if scan_stack.empty?
|
150
|
+
@left = 0
|
151
|
+
@left_total = 1
|
152
|
+
@right = 0
|
153
|
+
@right_total = 1
|
154
|
+
else
|
155
|
+
advance_right
|
156
|
+
end
|
157
|
+
check_stack 0
|
158
|
+
scan_stack.push right
|
159
|
+
tokens[right] = token
|
160
|
+
size[right] = -right_total
|
161
|
+
@right_total += token.blank_space
|
162
|
+
end
|
163
|
+
|
164
|
+
# Handle String Token.
|
165
|
+
#
|
166
|
+
# @return [Nil]
|
167
|
+
#
|
168
|
+
# @see Token::String
|
169
|
+
def handle_string(token)
|
170
|
+
if scan_stack.empty?
|
171
|
+
print_stack.print token, token.length
|
172
|
+
else
|
173
|
+
advance_right
|
174
|
+
tokens[right] = token
|
175
|
+
size[right] = token.length
|
176
|
+
@right_total += token.length
|
177
|
+
check_stream
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Flushes the input if possible.
|
182
|
+
#
|
183
|
+
# @note Called CheckStream as well in the original paper.
|
184
|
+
#
|
185
|
+
# @return [Nil]
|
186
|
+
def check_stream
|
187
|
+
return if right_total - left_total <= print_stack.space
|
188
|
+
|
189
|
+
if !scan_stack.empty? && left == scan_stack.bottom
|
190
|
+
size[scan_stack.pop_bottom] = Float::INFINITY
|
191
|
+
end
|
192
|
+
advance_left tokens[left], size[left]
|
193
|
+
return if left == right
|
194
|
+
|
195
|
+
check_stream
|
196
|
+
end
|
197
|
+
|
198
|
+
# Advances the `right` pointer.
|
199
|
+
#
|
200
|
+
# @note Called AdvanceRight as well in the original paper.
|
201
|
+
#
|
202
|
+
# @return [Nil]
|
203
|
+
def advance_right
|
204
|
+
@right = (right + 1) % scan_stack.length
|
205
|
+
return if right != left
|
206
|
+
|
207
|
+
raise 'Token queue full'
|
208
|
+
end
|
209
|
+
|
210
|
+
# Advances the `left` pointer and lets the print stack
|
211
|
+
# print some of the tokens it contains.
|
212
|
+
#
|
213
|
+
# @note Called AdvanceLeft as well in the original paper.
|
214
|
+
#
|
215
|
+
# @return [Nil]
|
216
|
+
def advance_left(token, token_length)
|
217
|
+
return if token_length.negative?
|
218
|
+
|
219
|
+
print_stack.print token, token_length
|
220
|
+
|
221
|
+
case token
|
222
|
+
when Token::Break
|
223
|
+
@left_total += token.blank_space
|
224
|
+
when Token::String
|
225
|
+
@left_total += token_length
|
226
|
+
end
|
227
|
+
|
228
|
+
return if left == right
|
229
|
+
|
230
|
+
@left = (left + 1) % scan_stack.length
|
231
|
+
advance_left tokens[left], size[left]
|
232
|
+
end
|
233
|
+
|
234
|
+
# Updates the size buffer taking into
|
235
|
+
# account the length of the current group.
|
236
|
+
#
|
237
|
+
# @note Called CheckStack as well in the original paper.
|
238
|
+
#
|
239
|
+
# @param depth [Integer] depth of the group
|
240
|
+
#
|
241
|
+
# @return [Nil]
|
242
|
+
def check_stack(depth) # rubocop:disable Metrics/AbcSize
|
243
|
+
return if scan_stack.empty?
|
244
|
+
|
245
|
+
x = scan_stack.top
|
246
|
+
case tokens[x]
|
247
|
+
when Token::Begin
|
248
|
+
if depth.positive?
|
249
|
+
size[scan_stack.pop] = size[x] + right_total
|
250
|
+
check_stack depth - 1
|
251
|
+
end
|
252
|
+
when Token::End
|
253
|
+
size[scan_stack.pop] = 1
|
254
|
+
check_stack depth + 1
|
255
|
+
else
|
256
|
+
size[scan_stack.pop] = size[x] + right_total
|
257
|
+
if depth.positive?
|
258
|
+
check_stack depth
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Oppen.
|
4
|
+
module Oppen
|
5
|
+
# A fixed-size stack that can be popped from top and bottom.
|
6
|
+
class ScanStack
|
7
|
+
def initialize(size)
|
8
|
+
@bottom = 0
|
9
|
+
@empty = true
|
10
|
+
@stack = Array.new(size)
|
11
|
+
@top = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Boolean]
|
15
|
+
def empty?
|
16
|
+
@empty
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Integer]
|
20
|
+
def length
|
21
|
+
@stack.length
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Object]
|
25
|
+
def top
|
26
|
+
if empty?
|
27
|
+
raise 'Accessing empty stack from top'
|
28
|
+
end
|
29
|
+
|
30
|
+
@stack[@top]
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Object]
|
34
|
+
def bottom
|
35
|
+
if empty?
|
36
|
+
raise 'Accessing empty stack from bottom'
|
37
|
+
end
|
38
|
+
|
39
|
+
@stack[@bottom]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Increment index (no overflow).
|
43
|
+
#
|
44
|
+
# @param index [Integer]
|
45
|
+
#
|
46
|
+
# @return [Integer]
|
47
|
+
def increment(index)
|
48
|
+
(index + 1) % length
|
49
|
+
end
|
50
|
+
|
51
|
+
# Decrement index (no overflow).
|
52
|
+
#
|
53
|
+
# @param index [Integer]
|
54
|
+
#
|
55
|
+
# @return [Integer]
|
56
|
+
def decrement(index)
|
57
|
+
(index - 1) % length
|
58
|
+
end
|
59
|
+
|
60
|
+
# Push a value to the top.
|
61
|
+
#
|
62
|
+
# @param value [Object]
|
63
|
+
#
|
64
|
+
# @return [Nil]
|
65
|
+
def push(value)
|
66
|
+
if empty?
|
67
|
+
@empty = false
|
68
|
+
else
|
69
|
+
@top = increment(@top)
|
70
|
+
if @top == @bottom
|
71
|
+
raise 'Stack full'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@stack[@top] = value
|
75
|
+
end
|
76
|
+
|
77
|
+
# Pop a value from the top.
|
78
|
+
#
|
79
|
+
# @return [Nil]
|
80
|
+
def pop
|
81
|
+
if empty?
|
82
|
+
raise 'Popping empty stack from top'
|
83
|
+
end
|
84
|
+
|
85
|
+
res = top
|
86
|
+
if @top == @bottom
|
87
|
+
@empty = true
|
88
|
+
else
|
89
|
+
@top = decrement(@top)
|
90
|
+
end
|
91
|
+
res
|
92
|
+
end
|
93
|
+
|
94
|
+
# Pop a value from the bottom.
|
95
|
+
#
|
96
|
+
# @return [Nil]
|
97
|
+
def pop_bottom
|
98
|
+
if empty?
|
99
|
+
raise 'Popping empty stack from bottom'
|
100
|
+
end
|
101
|
+
|
102
|
+
res = bottom
|
103
|
+
if @top == @bottom
|
104
|
+
@empty = true
|
105
|
+
else
|
106
|
+
@bottom = increment(@bottom)
|
107
|
+
end
|
108
|
+
res
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/oppen/token.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Oppen.
|
4
|
+
module Oppen
|
5
|
+
# Token.
|
6
|
+
module Token
|
7
|
+
# BreakType.
|
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
|
+
# String Token.
|
22
|
+
class String
|
23
|
+
# @return [String] String value.
|
24
|
+
attr_reader :value
|
25
|
+
|
26
|
+
def initialize(value)
|
27
|
+
@value = value
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Integer]
|
31
|
+
def length
|
32
|
+
value.length
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Break Token.
|
37
|
+
class Break
|
38
|
+
# @return [Integer] Number of blank spaces.
|
39
|
+
attr_reader :blank_space
|
40
|
+
# @return [Integer] Indentation.
|
41
|
+
attr_reader :offset
|
42
|
+
|
43
|
+
def initialize(blank_space: 1, offset: 0)
|
44
|
+
@blank_space = blank_space
|
45
|
+
@offset = offset
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Distinguished instance of Break which forces a line break.
|
50
|
+
class LineBreak < Break
|
51
|
+
def initialize(offset: 0)
|
52
|
+
super(blank_space: 9999, offset:)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Begin Token.
|
57
|
+
class Begin
|
58
|
+
# @return [BreakType]
|
59
|
+
attr_reader :break_type
|
60
|
+
# @return [Integer] Indentation.
|
61
|
+
attr_reader :offset
|
62
|
+
|
63
|
+
def initialize(break_type: BreakType::INCONSISTENT, offset: 2)
|
64
|
+
@offset = offset
|
65
|
+
@break_type = break_type
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# End Token
|
70
|
+
class End
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# EOF Token
|
75
|
+
class EOF
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/oppen.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'oppen/printer'
|
4
|
+
require_relative 'oppen/print_stack'
|
5
|
+
require_relative 'oppen/scan_stack'
|
6
|
+
require_relative 'oppen/token'
|
7
|
+
require_relative 'oppen/version'
|
8
|
+
|
9
|
+
# Oppen.
|
10
|
+
module Oppen
|
11
|
+
# Entry point of the pretty printer.
|
12
|
+
#
|
13
|
+
# @param tokens [Array[Token]] the list of tokens to be printed
|
14
|
+
# @param margin [Integer] maximum line width desired
|
15
|
+
# @param new_line [String] the delimiter between lines
|
16
|
+
#
|
17
|
+
# @return [StringIO] output of the pretty printer
|
18
|
+
def self.print(tokens: [], margin: 80, new_line: "\n")
|
19
|
+
printer = Printer.new margin, new_line
|
20
|
+
tokens.each do |token|
|
21
|
+
printer.print token
|
22
|
+
end
|
23
|
+
printer.output
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: oppen
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Amine Mike El Maalouf <amine.el-maalouf@epita.fr>
|
8
|
+
- Firas al-Khalil <firasalkhalil@gmail.com>
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2024-09-13 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Implementation of the Oppen's pretty printing algorithm
|
15
|
+
email:
|
16
|
+
executables:
|
17
|
+
- main.rb
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- LICENSE
|
22
|
+
- README.md
|
23
|
+
- bin/main.rb
|
24
|
+
- bin/repl.rb
|
25
|
+
- lib/oppen.rb
|
26
|
+
- lib/oppen/print_stack.rb
|
27
|
+
- lib/oppen/printer.rb
|
28
|
+
- lib/oppen/scan_stack.rb
|
29
|
+
- lib/oppen/token.rb
|
30
|
+
- lib/oppen/version.rb
|
31
|
+
homepage: http://github.com/Faveod/oppen-ruby
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '3.2'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubygems_version: 3.4.19
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Pretty-printing library
|
54
|
+
test_files: []
|