prosperon/source/engine/thirdparty/par/par_string_blocks.h

469 lines
15 KiB
C
Raw Permalink Normal View History

2023-05-12 22:21:11 -05:00
// STRING_BLOCKS :: https://github.com/prideout/par
// String extraction and concatenation, especially useful for snippets of GLSL or Lua.
//
// This little library extracts blocks of text from a memory blob or file, then lets you retrieve
// them by name. It also makes it easy to glue together a sequence of blocks.
//
// Each block of text is assigned a name using a prefix line that starts with three dash characters,
// such as "--- the_name" or "--- my.block".
//
// For example, suppose you have a file called "shaders.glsl" that looks like this:
//
// --- my_shader
// void main() { ... }
// --- common
// uniform vec4 resolution;
// uniform vec4 color;
//
// You can use this library to read in the file and extract one of the blocks:
//
// parsb_context* blocks = parsb_create_context((parsb_options){});
// parsb_add_blocks_from_file(blocks, "shaders.glsl");
// const char* single = parsb_get_blocks(blocks, "my_shader");
//
// You can also concatenate blocks using a space-delimited list of names:
//
// const char* concatenated = parsb_get_blocks(blocks, "common my_shader");
//
// You can also add or replace blocks on the fly:
//
// parsb_add_block(blocks, "prefix", "#version 330\n");
// const char* concatenated = parsb_get_blocks(blocks, "prefix common my_shader");
//
// The "blocks" context in the above examples holds a cache of generated strings, so be sure to
// destroy it when you're done:
//
// parsb_destroy_context(blocks);
//
// Distributed under the MIT License, see bottom of file.
#ifndef PAR_STRING_BLOCKS_H
#define PAR_STRING_BLOCKS_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
// OPTIONS
// -------
// line_directives ... adds #line annotations into concatenated strings for better error messages.
typedef struct parsb_options {
bool line_directives;
} parsb_options;
// CONTEXT CREATION AND DESTRUCTION
// --------------------------------
// A context is an opaque handle to a memory arena. All generated strings are owned by the context
// and freed when the context is destroyed.
typedef struct parsb_context_s parsb_context;
parsb_context* parsb_create_context(parsb_options);
void parsb_destroy_context(parsb_context*);
// ADDING AND REPLACING BLOCKS
// ---------------------------
// When using the plural form (add_blocks), the submitted buffer may contain multiple blocks, each
// with a name defined by its closest preceding triple-dash line. If a block with the specified name
// already exists, it gets replaced.
//
// The singular form (add_block) adds a single block whose name is explicitly specified as an
// argument. Again, if a block with the given name already exists, it gets replaced.
//
// These functions do not retain the passed-in strings so clients can free them after pushing them.
void parsb_add_blocks(parsb_context*, const char* buffer, int buffer_size);
void parsb_add_block(parsb_context*, const char* name, const char* body);
#ifndef PARSB_NO_STDIO
void parsb_add_blocks_from_file(parsb_context* context, const char* filename);
#endif
// EXTRACTING AND CONCATENATING BLOCKS
// -----------------------------------
// The block_names string is a space-separated list of block names that are being requested. The
// returned string is owned by the context, so please make a copy if you need it to outlive the
// context. If the returned string is null, then one or more of the block names could not be found.
const char* parsb_get_blocks(parsb_context*, const char* block_names);
// GETTING BLOCKS BY INDEX
// -----------------------
int parsb_get_num_blocks(const parsb_context*);
void parsb_get_block(const parsb_context*, int index, const char** name, const char** body);
// SAVING THE BLOCK LIST
// ---------------------
// These functions export the entire "database" of atomic blocks.
typedef void (*parsb_write_line)(const char* line, void* userdata);
void parsb_write_blocks(parsb_context*, parsb_write_line writefn, void* user);
#ifndef PARSB_NO_STDIO
void parsb_write_blocks_to_file(parsb_context*, const char* filename);
#endif
#ifndef PARSB_MAX_NUM_BLOCKS
#define PARSB_MAX_NUM_BLOCKS 128
#endif
#ifndef PARSB_MAX_NAME_LENGTH
#define PARSB_MAX_NAME_LENGTH 256
#endif
#ifndef PARSB_MAX_LINE_LENGTH
#define PARSB_MAX_LINE_LENGTH 256
#endif
#ifdef __cplusplus
}
#endif
// -----------------------------------------------------------------------------
// END PUBLIC API
// -----------------------------------------------------------------------------
#ifdef PAR_STRING_BLOCKS_IMPLEMENTATION
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#ifndef PARSB_NO_STDIO
#include <stdio.h>
#endif
#define PARSB_MIN(a, b) (a > b ? b : a)
typedef struct {
int count;
char* values[PARSB_MAX_NUM_BLOCKS];
char* names[PARSB_MAX_NUM_BLOCKS];
} parsb__list;
struct parsb_context_s {
parsb_options options;
parsb__list blocks;
parsb__list results;
};
static char* parsb__add_or_replace(parsb_context*, const char* id, const char* value,
int value_size, int line_number);
static char* parsb__list_add(parsb__list*, const char* id, const char* value, int value_size,
int line_number);
static char* parsb__list_get(parsb__list*, const char* id, int idlen);
static void parsb__list_free(parsb__list* );
parsb_context* parsb_create_context(parsb_options options) {
parsb_context* context = (parsb_context*) calloc(1, sizeof(parsb_context));
context->options = options;
return context;
}
void parsb_destroy_context(parsb_context* context) {
parsb__list_free(&context->blocks);
parsb__list_free(&context->results);
free(context);
}
void parsb_add_blocks(parsb_context* context, const char* blob, int buffer_size) {
const char* previous_block = 0;
char previous_name[PARSB_MAX_NAME_LENGTH];
int line_number = 0;
int block_line_number = 0;
for (int i = 0; i < buffer_size - 4; i++) {
if (blob[i] != '-' || blob[i + 1] != '-' || blob[i + 2] != '-' || blob[i + 3] != ' ') {
if (blob[i] == '\n') {
line_number++;
}
continue;
}
if (previous_block) {
parsb__add_or_replace(context, previous_name, previous_block,
i - (previous_block - blob), block_line_number);
}
i += 4;
const char* name = blob + i;
const char* block_start = 0;
for (; i < buffer_size; i++) {
if (blob[i] == '\n') {
line_number++;
int name_length = i - (name - blob);
memcpy(previous_name, name, name_length);
block_line_number = line_number + 2;
previous_name[name_length] = 0;
block_start = blob + i + 1;
break;
}
if (isspace(blob[i])) {
int name_length = i - (name - blob);
memcpy(previous_name, name, name_length);
block_line_number = line_number + 2;
previous_name[name_length] = 0;
for (i++; i < buffer_size; i++) {
if (blob[i] == '\n') {
line_number++;
block_start = blob + i + 1;
break;
}
}
break;
}
}
if (block_start == 0) {
return;
}
previous_block = block_start;
}
if (previous_block) {
parsb__add_or_replace(context, previous_name, previous_block,
buffer_size - (previous_block - blob), block_line_number);
}
}
void parsb_add_block(parsb_context* context, const char* name, const char* body) {
char* dup = strdup(body);
parsb__add_or_replace(context, name, dup, 1 + strlen(body), 0);
}
const char* parsb_get_blocks(parsb_context* context, const char* block_names) {
int len = strlen(block_names);
const char* name = block_names;
int name_length = 0;
int result_length = 0;
// First pass determines the amount of required memory.
int num_names = 0;
for (int i = 0; i < len; i++) {
char c = block_names[i];
if (isspace(c) || !c) {
const char* block = parsb__list_get(&context->blocks, name, name_length);
if (block) {
result_length += strlen(block);
num_names++;
} else {
return NULL;
}
name_length = 0;
name = block_names + i + 1;
} else {
name_length++;
}
}
const char* block = parsb__list_get(&context->blocks, name, name_length);
if (block) {
result_length += strlen(block);
num_names++;
}
// If no concatenation is required, return early.
if (num_names == 1) {
return parsb__list_get(&context->blocks, name, name_length);
}
// Allocate storage for the result.
char* result = parsb__list_add(&context->results, 0, 0, result_length, 0);
char* cursor = result;
// Second pass populates the result.
name = block_names;
name_length = 0;
for (int i = 0; i < len; i++) {
char c = block_names[i];
if (isspace(c) || !c) {
const char* block = parsb__list_get(&context->blocks, name, name_length);
if (block) {
memcpy(cursor, block, strlen(block));
cursor += strlen(block);
}
name_length = 0;
name = block_names + i + 1;
} else {
name_length++;
}
}
block = parsb__list_get(&context->blocks, name, name_length);
if (block) {
memcpy(cursor, block, strlen(block));
cursor += strlen(block);
}
return result;
}
int parsb_get_num_blocks(const parsb_context* context) {
return context->blocks.count;
}
void parsb_get_block(const parsb_context* context, int index, const char** name,
const char** body) {
if (index < 0 || index >= context->blocks.count) {
return;
}
*name = context->blocks.names[index];
*body = context->blocks.values[index];
}
void parsb_write_blocks(parsb_context* context, parsb_write_line writefn, void* userdata) {
char line[PARSB_MAX_LINE_LENGTH + 1] = {0};
for (int i = 0; i < context->blocks.count; i++) {
sprintf(line, "--- %s", context->blocks.names[i]);
writefn(line, userdata);
const char* cursor = context->blocks.values[i];
const int blocklen = strlen(cursor);
int previous = 0;
for (int i = 0; i < blocklen; i++) {
if (cursor[i] == '\n') {
int line_length = PARSB_MIN(i - previous, PARSB_MAX_LINE_LENGTH);
memcpy(line, cursor + previous, line_length);
line[line_length] = 0;
writefn(line, userdata);
previous = i + 1;
} else if (i == blocklen - 1) {
int line_length = PARSB_MIN(1 + i - previous, PARSB_MAX_LINE_LENGTH);
memcpy(line, cursor + previous, line_length);
line[line_length] = 0;
writefn(line, userdata);
previous = i + 1;
}
}
}
}
static char* parsb__add_or_replace(parsb_context* context, const char* id, const char* value,
int value_size, int line_number) {
line_number = context->options.line_directives ? line_number : 0;
const size_t idlen = strlen(id);
for (int i = 0; i < context->blocks.count; i++) {
if (strncmp(id, context->blocks.names[i], idlen) == 0) {
free(context->blocks.values[i]);
context->blocks.values[i] = strndup(value, value_size);
return context->blocks.values[i];
}
}
return parsb__list_add(&context->blocks, id, value, value_size, line_number);
}
static char* parsb__list_add(parsb__list* list, const char* name,
const char* value, int value_size, int line_number) {
if (value_size == 0) {
return NULL;
}
if (list->count == PARSB_MAX_NUM_BLOCKS) {
assert(false && "Please increase PARSB_MAX_NUM_BLOCKS.");
return NULL;
}
char* storage;
char* cursor;
if (line_number > 0) {
char line_directive[16] = {0};
int prefix_length = snprintf(line_directive, 16, "\n#line %d\n", line_number);
storage = (char*) calloc(1, prefix_length + value_size + 1);
memcpy(storage, line_directive, prefix_length);
cursor = storage + prefix_length;
} else {
storage = cursor = (char*) calloc(1, value_size + 1);
}
if (value) {
memcpy(cursor, value, value_size);
}
#if PARSB_ENABLE_TRIM
value_size--;
while (isspace(cursor[value_size])) {
cursor[value_size] = 0;
value_size--;
if (value_size == 0) {
break;
}
}
#endif
if (name) {
list->names[list->count] = strdup(name);
} else {
list->names[list->count] = 0;
}
list->values[list->count] = storage;
list->count++;
return storage;
}
static char* parsb__list_get(parsb__list* list, const char* name, int idlen) {
for (int i = 0; i < list->count; i++) {
if (strncmp(name, list->names[i], idlen) == 0) {
return list->values[i];
}
}
return NULL;
}
static void parsb__list_free(parsb__list* list) {
for (int i = 0; i < list->count; i++) {
free(list->names[i]);
free(list->values[i]);
}
list->count = 0;
}
#ifndef PARSB_NO_STDIO
void parsb_add_blocks_from_file(parsb_context* context, const char* filename) {
FILE* f = fopen(filename, "r");
if (!f) {
fprintf(stderr, "Unable to open %s\n", filename);
return;
}
fseek(f, 0, SEEK_END);
int length = ftell(f);
fseek(f, 0, SEEK_SET);
char* buffer = (char*) malloc(length);
fread(buffer, 1, length, f);
fclose(f);
parsb_add_blocks(context, buffer, length);
free(buffer);
}
static void writefn(const char* line, void* userdata) {
fprintf((FILE*) userdata, "%s\n", line);
}
void parsb_write_blocks_to_file(parsb_context* context, const char* filename) {
FILE* f = fopen(filename, "w");
if (!f) {
fprintf(stderr, "Unable to open %s\n", filename);
return;
}
parsb_write_blocks(context, writefn, f);
fclose(f);
}
#endif
#endif // PAR_STRING_BLOCKS_IMPLEMENTATION
#endif // PAR_STRING_BLOCKS_H
// par_string_blocks is distributed under the MIT license:
//
// Copyright (c) 2020 Philip Rideout
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.