mcap/modules/Basic/String_Builder.jai

346 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// String_Builder guarantees that buffers do not move after they have
// been allocated. Thus you can take pointers to positions inside the
// builder and know they have not changed.
//
//
// builder_to_string() is the function that people call at the end
// to get the result; we have made this free the memory by default,
// to avoid an extra step where you have to free things.
//
//
// You can copy the String_Builder by value and the copy will
// function just fine. (But it will also own all the buffers that were
// owned by the old copy, so make sure to stop using the old copy,
// or chaos will ensue.)
//
STRING_BUILDER_BUFFER_SIZE :: 4096 - size_of(String_Builder.Buffer);
String_Builder :: struct {
Buffer :: struct {
count: s64;
//
// The data pointer is implicit and points to the memory after the buffer itself
// (see "get_buffer_data()"). Otherwise we wouldn't be able to copy the String_Builder
// without having to adjust pointers.
//
// We could also use a relative pointer, but since it will always point after buffer,
// there's no need to store it explicitly. -rluba, 2021-12-15
//
allocated: s64;
#if ENABLE_ASSERT ensured_count: s64; // Used for error-checking on ensure_contiguous_space.
next: *Buffer;
}
initialized := false;
failed := false; // If we ever fail to allocate enough memory, this gets set to true ... we can't be sure the entire string has been added.
subsequent_buffer_size := -1;
allocator: Allocator;
current_buffer: *Buffer; // if current_buffer == null, it means use initial_bytes as a base buffer. We don't point it at initial_bytes, so that you can copy a String_Builder in memory and it's still valid.
initial_bytes: [STRING_BUILDER_BUFFER_SIZE] u8; // We don't dynamically allocate if we only need one buffer's worth of data.
}
init_string_builder :: (using builder: *String_Builder, buffer_size := -1) {
if (!allocator.proc) {
allocator = context.allocator;
}
subsequent_buffer_size = ifx buffer_size > 0 then buffer_size else STRING_BUILDER_BUFFER_SIZE;
base_buffer := get_base_buffer(builder);
base_buffer.count = 0;
base_buffer.allocated = initial_bytes.count - size_of(Buffer);
base_buffer.next = null;
current_buffer = null;
initialized = true;
}
free_buffers :: (using builder: *String_Builder) { // free_buffers will be deprecated pretty soon, and the only entry point to resetting a String_Builder will be reset(). -jblow, 1 June 2022
base_buffer := get_base_buffer(builder);
buffer := base_buffer.next;
while buffer {
next := buffer.next;
free(buffer,, allocator=allocator);
buffer = next;
}
base_buffer.next = null; // Just in case we call this twice.
}
reset :: (using builder: *String_Builder) {
free_buffers(builder);
base_buffer := get_base_buffer(builder);
base_buffer.count = 0;
current_buffer = null;
}
ensure_contiguous_space :: (using builder: *String_Builder, bytes: s64) -> pointer: *void { // 'pointer' points to the base of the space, or is null if we failed.
available := ensure_contiguous_space_and_return_available_range(builder, bytes);
if !available.count then return null;
#if ENABLE_ASSERT {
buffer := get_current_buffer(builder);
buffer.ensured_count = buffer.count + bytes;
}
return available.data;
}
ensure_contiguous_space_and_return_available_range :: (using builder: *String_Builder, minimum_bytes := 1) -> [] u8 {
if !initialized init_string_builder(builder);
buffer := get_current_buffer(builder);
if minimum_bytes > buffer.allocated then return .[]; // Nothing we can do there, bro; it's bigger than our buffers!
available := buffer.allocated - buffer.count;
if available < minimum_bytes {
success := expand(builder);
if !success then return .[];
buffer = get_current_buffer(builder);
}
#if ENABLE_ASSERT buffer.ensured_count = buffer.allocated;
result: [] u8;
result.data = get_buffer_data(buffer) + buffer.count;
result.count = buffer.allocated - buffer.count;
return result;
}
advance_through_ensured_space :: (using builder: *String_Builder, bytes: s64) {
// If you ensure space and use the pointer you get back, you might write data
// directly into that pointer, but then want to advance it without packing stuff
// manually into the builder. That's what this is for.
buffer := get_current_buffer(builder);
length_max := buffer.allocated - buffer.count;
assert(length_max >= bytes);
// In order to check whether you didn't use ensure_contiguous_space wrong, we
// don't only check whether you happen to have enough space in the buffer
// (above), but we check whether you guaranteed the space correctly, which
// doesn't depend on chance of where we are in the buffer.
#if ENABLE_ASSERT assert(buffer.count + bytes <= buffer.ensured_count);
buffer.count += bytes;
assert(initialized); // There
}
append :: (using builder: *String_Builder, s: *u8, length: s64) {
if !initialized init_string_builder(builder);
while length > 0 {
buffer := get_current_buffer(builder);
length_max := buffer.allocated - buffer.count;
if length_max <= 0 {
success := expand(builder);
if !success {
failed = true;
return;
}
// expand() changes current_buffer, so let's reacquire!
buffer = current_buffer;
assert(buffer != null);
length_max = buffer.allocated - buffer.count; // Avoid a recursion doing no work that would happen if we left length_max == 0. (It should never be < 0 unless something very bad has happened).
assert(length_max > 0);
}
to_copy := min(length, length_max);
if length > 0 assert(to_copy >= 0);
memcpy(get_buffer_data(buffer) + buffer.count, s, to_copy);
buffer.count += to_copy;
length -= to_copy;
s += to_copy;
}
}
append :: inline (builder: *String_Builder, s: string) {
append(builder, s.data, s.count);
}
append :: inline (builder: *String_Builder, byte: u8) {
local_byte := byte; // So we can take the address, yay.
append(builder, *local_byte, 1);
}
append_by_pointer :: inline (builder: *String_Builder, pointer: *$T) {
append(builder, cast(*u8) pointer, size_of(T));
}
builder_string_length :: (using builder: *String_Builder) -> int {
if !allocator.proc return 0;
buffer := get_base_buffer(builder);
bytes := 0;
while buffer {
bytes += buffer.count;
buffer = buffer.next;
}
return bytes;
}
builder_to_string :: (builder: *String_Builder, extra_bytes_to_prepend := 0, do_reset := true) -> string {
// 'extra_bytes_to_prepend' gives you extra space at the beginning, so you can
// do something like put in a length that you didn't know when you were
// writing out the data. This may not be worth the extra complexity, though;
// for heavier-duty use you probably want something like the String_Builder bucket stealing
// that is in the compiler's source but hasn't yet made it into this port yet.
// -jblow, 30 September 2020
if !builder.initialized init_string_builder(builder);
count := builder_string_length(builder);
if !count && !extra_bytes_to_prepend {
return "";
}
result: string = ---;
result.data = alloc(count + extra_bytes_to_prepend);
result.count = count + extra_bytes_to_prepend;
data := result.data + extra_bytes_to_prepend;
buffer := get_base_buffer(builder);
while buffer {
memcpy(data, get_buffer_data(buffer), buffer.count);
data += buffer.count;
buffer = buffer.next;
}
if do_reset reset(builder); // @Speed: Could free buffers inline with the bucket traversal above. Code would be less clear.
return result;
}
write_builder :: (using builder: *String_Builder, do_reset := true, to_standard_error := false) -> s64 {
//
// Future expanded functionality: We can make a macro
// that calls its argument code with a string representing
// the contents of each bucket. Then you can do whatever
// operation you want.
// -jblow, 19 July 2019
//
if !allocator.proc return 0;
buffer := get_base_buffer(builder);
written := 0;
while buffer {
s: string = ---;
s.data = get_buffer_data(buffer);
s.count = buffer.count;
write_string(s, to_standard_error);
written += s.count;
buffer = buffer.next;
}
if do_reset reset(builder);
return written;
}
consume_u8_and_length :: (input: *string, s: *u8, length: s64) -> bool { // Looks a lot like append_u8_and_length.
if length < 0 return false; // Error!
if length > input.count return false; // Error!
if length == 0 return true;
memcpy(s, input.data, length);
input.count -= length;
input.data += length;
return true;
}
get_current_buffer :: inline (using builder: *String_Builder) -> *String_Builder.Buffer {
if current_buffer return current_buffer;
assert(builder.allocator.proc != null);
return get_base_buffer(builder);
}
get_base_buffer :: inline (using builder: *String_Builder) -> *String_Builder.Buffer {
return cast(*Buffer) initial_bytes.data;
}
get_buffer_data :: inline (using buffer: *String_Builder.Buffer) -> *u8 {
return (cast (*u8) buffer) + size_of(String_Builder.Buffer);
}
simple_memcpy :: inline (using builder: *String_Builder, x: $T) {
// Don't call this unless you know the buffer has been initialized and there is enough space in the current buffer!
// Otherwise you will corrupt memory and be sad.
// This is included here just as a utility routine you can call
// when writing serializers and whatnot.
using buffer := get_current_buffer(builder);
assert(size_of(T) <= allocated - count);
memcpy(get_buffer_data(buffer) + count, *x, size_of(T));
count += size_of(T);
}
expand :: (using builder: *String_Builder) -> bool {
assert (allocator.proc != null); // This is initialized in init_string_builder.
subsequent := ifx subsequent_buffer_size > 0 then subsequent_buffer_size else STRING_BUILDER_BUFFER_SIZE;
bytes := cast(*u8) alloc(size_of(Buffer) + subsequent,, allocator);
if !bytes return false; // Failed!
buffer := cast(*Buffer) bytes;
buffer.next = null;
buffer.count = 0;
buffer.allocated = subsequent;
old_buffer := get_current_buffer(builder);
old_buffer.next = buffer;
current_buffer = buffer;
return true;
}
// Move all data from "steal_from" to "builder", taking ownership of all dynamically allocated buffers in the process.
append_and_steal_buffers :: (builder: *String_Builder, steal_from: *String_Builder) {
buffer := get_base_buffer(steal_from);
if !buffer.count return;
// We need to copy the base buffer because it’s part of the other builder’s struct
// so we can’t take ownership of it:
append(builder, get_buffer_data(buffer), buffer.count);
assert(builder.allocator.proc == steal_from.allocator.proc);
assert(builder.allocator.data == steal_from.allocator.data);
// Take ownership of the rest of the buffer chain:
our_end := get_current_buffer(builder);
our_end.next = buffer.next;
// Reset the victim’s base buffer
buffer.count = 0;
buffer.next = null;
if steal_from.current_buffer {
builder.current_buffer = steal_from.current_buffer; // Use the victim’s final buffer as our current buffer.
steal_from.current_buffer = null; // Reset the victim to use the base buffer again
}
}