require "./int.cr"
require "./pointer.cr"
class String
TYPE_ID = "".crystal_type_id
HEADER_SIZE = sizeof({Int32, Int32, Int32})
class Builder
@capacity : Int32 = 0
@bytesize : Int32 = 0
def capacity
@capacity
end
def bytesize
@bytesize
end
def initialize(capacity : Int32)
@buffer = Pointer(UInt8).malloc_atomic(capacity.to_u32 + 1 + HEADER_SIZE)
@capacity = capacity_for_ptr @buffer
@bytesize = 0
@finished = false
end
def initialize
@capacity = 0
@buffer = Pointer(UInt8).null
@bytesize = 0
@finished = false
end
private def capacity_for_ptr(ptr)
(Allocator.block_size_for_ptr(ptr) - HEADER_SIZE).to_i32
end
def buffer
@buffer + String::HEADER_SIZE
end
def empty?
@bytesize == 0
end
def back(amount : Int)
abort "overflow" if amount > @bytesize
@bytesize -= amount
end
def write_byte(other : UInt8)
if @bytesize == @capacity
if @buffer.null?
@buffer = Pointer(UInt8).malloc_atomic(5 + HEADER_SIZE)
@capacity = capacity_for_ptr @buffer
else
old_buffer = @buffer
old_size = @capacity.to_usize + 1 + HEADER_SIZE
@buffer = Pointer(UInt8).malloc_atomic(@bytesize.to_u32 + 1 + HEADER_SIZE)
@capacity = capacity_for_ptr @buffer
memcpy(@buffer, old_buffer, old_size)
end
end
buffer[@bytesize] = other
@bytesize += 1
end
def <<(other : String)
other.each_byte do |byte|
write_byte byte
end
end
def <<(other : Slice(UInt8))
other.each do |byte|
write_byte byte
end
end
def <<(other : Char)
if other.ord <= 0xFF
write_byte other.ord.to_u8
else
abort "TODO: support utf-8 for builder"
end
end
def putc(other : UInt8)
write_byte other
end
def <<(other)
other.to_s self
end
def to_s : String
abort "Can only invoke 'to_s' once on String::Builder" if @finished
@finished = true
write_byte 0u8
header = @buffer.as({Int32, Int32, Int32}*)
bytesize, length = String.calculate_length(buffer)
header.value = {String::TYPE_ID, bytesize, length}
@buffer.as(String)
end
def reset
@buffer = Pointer(UInt8).null
@capacity = 0
@bytesize = 0
@finished = false
end
def reset(capacity : Int32)
@buffer = Pointer(UInt8).malloc_atomic(capacity.to_u32 + 1 + HEADER_SIZE)
@capacity = capacity_for_ptr @buffer
@bytesize = 0
@finished = false
end
end
def self.new(capacity : Int)
str = Pointer(UInt8).malloc_atomic(capacity.to_u32 + HEADER_SIZE + 1)
buffer = str.as(String).to_unsafe
bytesize, size = yield buffer
unless 0 <= bytesize <= capacity
return nil
end
buffer[bytesize] = 0_u8
str_header = str.as({Int32, Int32, Int32}*)
str_header.value = {TYPE_ID, bytesize.to_i, size.to_i}
str.as(String)
end
def self.new(byte : UInt8)
(new(1) { |buffer|
buffer[0] = byte
buffer[1] = 0u8
{1, 1}
}).not_nil!
end
def self.new(bytes : Slice(UInt8))
(new(bytes.size + 1) { |buffer|
memcpy(buffer, bytes.to_unsafe, bytes.size.to_usize)
buffer[bytes.size] = 0u8
String.calculate_length(buffer)
}).not_nil!
end
def self.new(bytes : StaticArray)
(new(bytes.size + 1) { |buffer|
memcpy(buffer, bytes.to_unsafe, bytes.size.to_usize)
buffer[bytes.size] = 0u8
String.calculate_length(buffer)
}).not_nil!
end
def self.new(bytes : NullTerminatedSlice)
String.new Slice(UInt8).new(bytes.to_unsafe, bytes.size)
end
protected def self.calculate_length(buffer : UInt8*)
i = 0
length = 0
until (ch = buffer[i]) == 0u8
if ch >= 0b110_00000u8
i += 2
elsif ch >= 0b1110_0000u8
i += 3
elsif ch >= 0b11110_000u8
i += 4
else
i += 1
end
length += 1
end
{i, length}
end
private def mask_tail_char(ch : UInt8) : UInt32
(ch & 0b111111u8).to_u32
end
private def each_unicode_point
i = 0
points = 0
until (ch = to_unsafe[i]) == 0u8
point = 0u32
if ch >= 0b110_00000u8
point = (ch & 0b11111u8).to_u32 << 6 | mask_tail_char(to_unsafe[i + 1])
i += 2
elsif ch >= 0b1110_0000u8
point = (ch & 0b1111u8).to_u32 << 12 |
(mask_tail_char(to_unsafe[i + 1]) << 6) |
(mask_tail_char(to_unsafe[i + 2]))
i += 3
elsif ch >= 0b11110_000u8
point = (ch & 0b1111u8).to_u32 << 18 |
(mask_tail_char(to_unsafe[i + 1]) << 12) |
(mask_tail_char(to_unsafe[i + 2]) << 6) |
(mask_tail_char(to_unsafe[i + 3]))
i += 4
else
point = ch.to_u32
i += 1
end
yield point.unsafe_chr, points, i
points += 1
end
end
def size
@length
end
def bytesize
@bytesize
end
def to_unsafe : UInt8*
pointerof(@c)
end
def byte_slice
Slice(UInt8).new(to_unsafe, bytesize)
end
def each_char(&block)
each_unicode_point do |char|
yield char
end
end
def each_byte(&block)
@bytesize.times do |i|
yield to_unsafe[i]
end
end
def [](index : Int)
each_unicode_point do |char, i|
return char if i == index
end
0.unsafe_chr
end
def ==(other : self)
return true if same?(other)
return false unless bytesize == other.bytesize
memcmp(to_unsafe, other.to_unsafe, bytesize) == 0
end
def ==(other : Slice(UInt8))
return false unless bytesize == other.size
memcmp(to_unsafe, other.to_unsafe, bytesize) == 0
end
def ===(other)
self == other
end
def index(search)
each_unicode_point do |char, i|
return i if search == char
end
nil
end
def hash(hasher)
hasher.hash self
end
def interpolation(str)
parts = str.split(/\#\{({|})/)
result = ""
i = 0
while i < parts.size
if parts[i] == "{"
i += 1
expr = ""
depth = 1
while depth > 0
if parts[i] == "{"
depth += 1
elsif parts[i] == "}"
depth -= 1
end
expr += parts[i]
i += 1
end
expr = expr[1...-1]
result += eval(expr).to_s
else
result += parts[i]
i += 1
end
end
result
end
def to_s
self
end
def to_s(io)
each_byte do |char|
io.putc char
end
end
end