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