/
builder.cr
129 lines (101 loc) · 2.86 KB
/
builder.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
require "io"
# Similar to `IO::Memory`, but optimized for building a single string.
#
# You should never have to deal with this class. Instead, use `String.build`.
class String::Builder < IO
getter bytesize : Int32
getter capacity : Int32
getter buffer : Pointer(UInt8)
def initialize(capacity : Int = 64)
String.check_capacity_in_bounds(capacity)
# Make sure to also be able to hold
# the header size plus the trailing zero byte
capacity += String::HEADER_SIZE + 1
String.check_capacity_in_bounds(capacity)
@buffer = GC.malloc_atomic(capacity.to_u32).as(UInt8*)
@bytesize = 0
@capacity = capacity.to_i
@finished = false
end
def self.build(capacity : Int = 64) : String
builder = new(capacity)
yield builder
builder.to_s
end
def self.new(string : String)
io = new(string.bytesize)
io << string
io
end
def read(slice : Bytes)
raise "Not implemented"
end
def write(slice : Bytes) : Nil
return if slice.empty?
count = slice.size
new_bytesize = real_bytesize + count
if new_bytesize > @capacity
resize_to_capacity(Math.pw2ceil(new_bytesize))
end
slice.copy_to(@buffer + real_bytesize, count)
@bytesize += count
nil
end
def write_byte(byte : UInt8)
new_bytesize = real_bytesize + 1
if new_bytesize > @capacity
resize_to_capacity(Math.pw2ceil(new_bytesize))
end
@buffer[real_bytesize] = byte
@bytesize += 1
nil
end
def buffer
@buffer + String::HEADER_SIZE
end
def empty?
@bytesize == 0
end
# Chomps the last byte from the string buffer.
# If the byte is `'\n'` and there's a `'\r'` before it, it is also removed.
def chomp!(byte : UInt8)
if bytesize > 0 && buffer[bytesize - 1] == byte
back(1)
if byte === '\n' && bytesize > 0 && buffer[bytesize - 1] === '\r'
back(1)
end
end
self
end
# Moves the write pointer, and the resulting string bytesize,
# by the given *amount*.
def back(amount : Int)
unless 0 <= amount <= @bytesize
raise ArgumentError.new "Invalid back amount"
end
@bytesize -= amount
end
def to_s : String
raise "Can only invoke 'to_s' once on String::Builder" if @finished
@finished = true
write_byte 0_u8
# Try to reclaim some memory if capacity is bigger than what we need
real_bytesize = real_bytesize()
if @capacity > real_bytesize
resize_to_capacity(real_bytesize)
end
header = @buffer.as({Int32, Int32, Int32}*)
header.value = {String::TYPE_ID, @bytesize - 1, 0}
@buffer.as(String)
end
private def real_bytesize
@bytesize + String::HEADER_SIZE
end
private def check_needs_resize
resize_to_capacity(@capacity * 2) if real_bytesize == @capacity
end
private def resize_to_capacity(capacity)
@capacity = capacity
@buffer = @buffer.realloc(@capacity)
end
end