Add INI parser

The ini parser will come in handy for configurations, for example once
the time has come for adding shader packs.

The parser isn't tested yet; writing some (automatic) tests would be a
really good idea.
This commit is contained in:
Eric-Paul Ickhorn 2024-09-09 05:49:48 +02:00
parent c3fb07b871
commit 6644a59270
5 changed files with 633 additions and 1 deletions

View File

@ -22,6 +22,43 @@ typedef struct vx_pool vx_pool_s;
typedef struct vx_pool_slot_header vx_pool_slot_header_s; typedef struct vx_pool_slot_header vx_pool_slot_header_s;
typedef struct vx_arena vx_arena_s; typedef struct vx_arena vx_arena_s;
typedef enum
{
VX_ASCII_EXCLAMATION_MARK = '!',
VX_ASCII_DOUBLE_QUOTATION_MARK = '"',
VX_ASCII_NUMBER_SIGN = '#',
VX_ASCII_DOLLAR_SIGN = '$',
VX_ASCII_PERCENT_SIGN = '%',
VX_ASCII_AMPERSAND = '&',
VX_ASCII_SINGLE_QUOTATION_MARK = '\'',
VX_ASCII_OPENING_PARENTHESIS = '(',
VX_ASCII_CLOSING_PARENTHESIS = ')',
VX_ASCII_ASTERISK = '*',
VX_ASCII_ADDITION_SIGN = '+',
VX_ASCII_COMMA = ',',
VX_ASCII_HYPHEN = '-',
VX_ASCII_DOT = '.',
VX_ASCII_FORWARD_SLASH = '/',
VX_ASCII_COLON = ':',
VX_ASCII_SEMICOLON = ';',
VX_ASCII_LESS_THAN_SIGN = '<',
VX_ASCII_EQUALS_SIGN = '=',
VX_ASCII_GREATER_THAN_SIGN = '>',
VX_ASCII_QUESTION_MARK = '?',
VX_ASCII_AT_SIGN = '@',
VX_ASCII_OPENING_BRACKET = '[',
VX_ASCII_BACKSLASH = '\\',
VX_ASCII_CLOSING_BRACKET = ']',
VX_ASCII_CARET = '^',
VX_ASCII_UNDERSCORE = '_',
VX_ASCII_BACKTICK = '`',
VX_ASCII_OPENING_BRACE = '{',
VX_ASCII_VERTICAL_BAR = '|',
VX_ASCII_CLOSING_BRACE = '}',
VX_ACII_TILDE = '~'
} vx_ascii_sign_e;
struct vx_uuid_entry struct vx_uuid_entry
{ {
vx_uuid_d uuid; vx_uuid_d uuid;
@ -172,4 +209,24 @@ void * vx_arena_alloc(vx_arena_s *arena, uint32_t length);
char * vx_arena_dupe_string(vx_arena_s *arena, const char *string); char * vx_arena_dupe_string(vx_arena_s *arena, const char *string);
char * vx_arena_dupe_string_up_to(vx_arena_s *arena, const char *string, uint32_t maximum); char * vx_arena_dupe_string_up_to(vx_arena_s *arena, const char *string, uint32_t maximum);
// Character Functions
bool vx_is_ascii_sign(char character);
bool vx_is_ascii_sign_of_block_1(char character);
bool vx_is_ascii_sign_of_block_2(char character);
bool vx_is_ascii_sign_of_block_3(char character);
bool vx_is_ascii_sign_of_block_4(char character);
bool vx_is_ascii_letter(char character);
bool vx_is_ascii_lower(char character);
bool vx_is_ascii_upper(char character);
bool vx_is_ascii_digit(char character);
bool vx_is_ascii_blank(char character);
bool vx_is_ascii_control(char character);
uint8_t vx_digits_for_decimal(uint32_t decimal);
void vx_write_spaces(FILE *output, uint32_t count);
#endif // VOXULA_UTILITY_H #endif // VOXULA_UTILITY_H

View File

@ -0,0 +1,81 @@
#ifndef VOXULA_INI_H
#define VOXULA_INI_H
#include <stdbool.h>
#include <stdint.h>
#include <voxula/internals/utility.h>
typedef struct vx_ini vx_ini_s;
typedef struct vx_ini_section vx_ini_section_s;
typedef struct vx_ini_field vx_ini_field_s;
typedef struct vx_ini_value vx_ini_value_s;
typedef enum
{
VX_INI_VALUE_NULL = 0,
VX_INI_VALUE_BOOLEAN,
VX_INI_VALUE_STRING,
VX_INI_VALUE_INTEGER,
VX_INI_VALUE_REAL,
VX_INI_VALUE_ARRAY
} vx_ini_value_e;
struct vx_ini_value
{
vx_ini_value_e type;
union vx_ini_value_specifics
{
bool boolean;
char *string;
int64_t integer;
double real;
struct vx_ini_array
{
uint32_t values_capacity;
uint32_t num_values;
vx_ini_value_s *values;
} array;
} specifics;
};
struct vx_ini_field
{
uint32_t line;
uint32_t column;
uint32_t len_name;
char *name;
vx_ini_value_s value;
};
struct vx_ini_section
{
uint32_t len_name;
char *name;
uint32_t fields_capacity;
uint32_t num_fields;
vx_ini_field_s *fields;
};
struct vx_ini
{
uint32_t sections_capacity;
uint32_t num_sections;
vx_ini_section_s *sections;
vx_arena_s *string_arena;
};
vx_ini_s vx_parse_ini(const char *source);
void vx_delete_ini(vx_ini_s *ini);
vx_ini_value_s vx_get_ini(vx_ini_s *ini, const char *field);
#endif // VOXULA_INI_H

View File

@ -0,0 +1,64 @@
#ifndef VOXULA_ASCII_H
#define VOXULA_ASCII_H
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
typedef enum
{
VX_ASCII_EXCLAMATION_MARK = '!',
VX_ASCII_DOUBLE_QUOTATION_MARK = '"',
VX_ASCII_NUMBER_SIGN = '#',
VX_ASCII_DOLLAR_SIGN = '$',
VX_ASCII_PERCENT_SIGN = '%',
VX_ASCII_AMPERSAND = '&',
VX_ASCII_SINGLE_QUOTATION_MARK = '\'',
VX_ASCII_OPENING_PARENTHESIS = '(',
VX_ASCII_CLOSING_PARENTHESIS = ')',
VX_ASCII_ASTERISK = '*',
VX_ASCII_ADDITION_SIGN = '+',
VX_ASCII_COMMA = ',',
VX_ASCII_HYPHEN = '-',
VX_ASCII_DOT = '.',
VX_ASCII_FORWARD_SLASH = '/',
VX_ASCII_COLON = ':',
VX_ASCII_SEMICOLON = ';',
VX_ASCII_LESS_THAN_SIGN = '<',
VX_ASCII_EQUALS_SIGN = '=',
VX_ASCII_GREATER_THAN_SIGN = '>',
VX_ASCII_QUESTION_MARK = '?',
VX_ASCII_AT_SIGN = '@',
VX_ASCII_OPENING_BRACKET = '[',
VX_ASCII_BACKSLASH = '\\',
VX_ASCII_CLOSING_BRACKET = ']',
VX_ASCII_CARET = '^',
VX_ASCII_UNDERSCORE = '_',
VX_ASCII_BACKTICK = '`',
VX_ASCII_OPENING_BRACE = '{',
VX_ASCII_VERTICAL_BAR = '|',
VX_ASCII_CLOSING_BRACE = '}',
VX_ACII_TILDE = '~'
} vx_ascii_sign_e;
bool vx_is_ascii_sign(char character);
bool vx_is_ascii_sign_of_block_1(char character);
bool vx_is_ascii_sign_of_block_2(char character);
bool vx_is_ascii_sign_of_block_3(char character);
bool vx_is_ascii_sign_of_block_4(char character);
bool vx_is_ascii_letter(char character);
bool vx_is_ascii_lower(char character);
bool vx_is_ascii_upper(char character);
bool vx_is_ascii_digit(char character);
bool vx_is_ascii_blank(char character);
bool vx_is_ascii_control(char character);
uint8_t vx_digits_for_decimal(uint32_t decimal);
void vx_write_spaces(FILE *output, uint32_t count);
#endif // VOXULA_ASCII_H

View File

@ -0,0 +1,239 @@
#include <voxula/internals/utility/ini.h>
#include <stdlib.h>
#include <string.h>
char * vx_parse_ini_section_name(
vx_arena_s *arena,
const char *source,
uint32_t offset,
uint32_t *len_name
) {
++offset; // Jump over the starting square bracket
uint32_t start_offset = offset;
while(source[offset] != 0)
{
if(source[offset] == ']')
{
++offset;
}
++offset;
}
uint32_t name_word_length = offset - start_offset;
char *name = malloc(name_word_length + 1);
memcpy(name, &source[start_offset], name_word_length);
name[name_word_length] = 0;
if(len_name)
{
*len_name = name_word_length;
}
return name;
}
vx_ini_value_s vx_parse_ini_value(vx_ini_s *ini, const char *source, uint32_t offset, uint32_t *out_length)
{
// For string values
if(source[offset] == '"')
{
vx_ini_value_s value;
uint32_t start_offset = offset;
if(source[offset + 1] == '"')
{
// If this is an empty string
if(source[offset + 2] != '"')
{
value.type = VX_INI_VALUE_STRING;
value.specifics.string = vx_arena_alloc(ini->string_arena, 1);
value.specifics.string[0] = 0;
return value;
}
// If this is a multi-line string
offset += 3; // Jump over the quotation marks so they can't be picked up as the ending ones.
uint32_t string_data_start = offset;
while(source[offset != 0])
{
if(source[offset] != '"')
{
++offset;
continue;
}
if(source[offset + 1] != '"')
{
++offset;
continue;
}
if(source[offset + 2] != '"')
{
++offset;
continue;
}
break;
}
value.type = VX_INI_VALUE_STRING;
value.specifics.string = vx_arena_dupe_string_up_to(ini->string_arena, &source[string_data_start], offset - string_data_start);
return value;
}
// A regular, single-line string
++offset;
while(source[offset] != 0)
{
if(source[offset] == '\n')
{
break;
}
if(source[offset] == '"')
{
break;
}
++offset;
}
value.type = VX_INI_VALUE_STRING;
value.specifics.string = vx_arena_dupe_string_up_to(ini->string_arena, &source[start_offset], offset - start_offset);
return value;
}
if(vx_is_ascii_digit(source[offset]))
{
vx_ini_value_s value;
uint32_t start_offset = offset;
while(source[offset] != 0)
{
if( ! vx_is_ascii_digit(source[offset]))
{
break;
}
++offset;
}
// todo(delayed): Add floating-point numbers
char *integer_string = vx_arena_dupe_string_up_to(ini->string_arena, &source[start_offset], offset - start_offset);
value.type = VX_INI_VALUE_INTEGER;
value.specifics.integer = atol(integer_string);
return value;
}
vx_ini_value_s value;
value.type = VX_INI_VALUE_NULL;
return value;
}
vx_ini_field_s vx_parse_ini_field(vx_ini_s *ini, const char *source, uint32_t offset, uint32_t *out_length)
{
uint32_t start_offset = offset;
// Get the field's name
while(source[offset] != 0)
{
if( ! vx_is_ascii_letter(source[offset])
&& (source[offset] != '_'))
{
break;
}
++offset;
}
vx_ini_field_s field;
field.len_name = offset - start_offset;
field.name = vx_arena_dupe_string_up_to(ini->string_arena, &source[offset], field.len_name);
uint32_t len_value;
field.value = vx_parse_ini_value(ini, source, offset, &len_value);
if(out_length)
{
*out_length = (offset - start_offset) + len_value;
}
return field;
}
vx_ini_section_s vx_parse_ini_section(vx_ini_s *ini, const char *source, uint32_t offset, uint32_t *out_length)
{
uint32_t start_offset = offset;
vx_ini_section_s section;
section.name = vx_parse_ini_section_name(ini->string_arena, source, offset, &section.len_name);
offset += section.len_name + 2;
section.num_fields = 0;
section.fields_capacity = 32;
section.fields = malloc(section.fields_capacity * sizeof(vx_ini_field_s));
while(source[offset] != 0)
{
// Skip comments
if(source[offset] == '#')
{
while(source[offset] != 0)
{
if(source[offset] == '\n')
{
break;
}
++offset;
}
++offset;
}
// Get all the fields of this section
while(source[offset] != 0)
{
if(vx_is_ascii_letter(source[offset]
|| (source[offset] == '_'))
) {
break;
}
if(source[offset] == '[')
{
break;
}
}
if(source[offset] == '[')
{
break;
}
uint32_t bytes_to_jump = 0;
vx_parse_ini_field(ini, source, offset, &bytes_to_jump);
offset += bytes_to_jump;
}
++offset;
return section;
}
vx_ini_s vx_parse_ini(const char *source)
{
uint32_t len_source = strlen(source);
vx_ini_s ini;
ini.num_sections = 0;
ini.sections_capacity = 64;
ini.sections = malloc(ini.sections_capacity * sizeof(vx_ini_section_s));
ini.string_arena = vx_new_arena(len_source * 2);
uint32_t offset = 0;
while(offset < len_source)
{
if(source[offset] == '[')
{
uint32_t len_section;
ini.sections[ini.num_sections] = vx_parse_ini_section(&ini, source, offset, &len_section);
offset += len_section;
}
}
return ini;
}
void vx_delete_ini(vx_ini_s *ini)
{
}
vx_ini_value_s vx_get_ini(vx_ini_s *ini, const char *field)
{
uint32_t splitter = 0;
while(field[splitter] != 0)
{
if(field[splitter] == '.')
{
break;
}
++splitter;
}
}

View File

@ -0,0 +1,191 @@
#include <voxula/internals/utility/text.h>
bool vx_is_ascii_sign(char character)
{
if( vx_is_ascii_sign_of_block_1(character)
|| vx_is_ascii_sign_of_block_2(character)
|| vx_is_ascii_sign_of_block_3(character)
|| vx_is_ascii_sign_of_block_4(character)
) {
return true;
}
return false;
}
bool vx_is_ascii_sign_of_block_1(char character)
{
if(character < 0x21)
{
return false;
}
if(character > 0x2f)
{
return false;
}
return true;
}
bool vx_is_ascii_sign_of_block_2(char character)
{
if(character < 0x3a)
{
return false;
}
if(character > 0x40)
{
return false;
}
return true;
}
bool vx_is_ascii_sign_of_block_3(char character)
{
if(character < 0x5b)
{
return false;
}
if(character > 0x60)
{
return false;
}
return true;
}
bool vx_is_ascii_sign_of_block_4(char character)
{
if(character < 0x7b)
{
return false;
}
if(character > 0x7e)
{
return false;
}
return true;
}
bool vx_is_ascii_letter(char character)
{
if( vx_is_ascii_lower(character)
|| vx_is_ascii_upper(character)
) {
return true;
}
return false;
}
bool vx_is_ascii_lower(char character)
{
if(character < 'a')
{
return false;
}
if(character > 'z')
{
return false;
}
return true;
}
bool vx_is_ascii_upper(char character)
{
if(character < 'A')
{
return false;
}
if(character > 'Z')
{
return false;
}
return true;
}
bool vx_is_ascii_digit(char character)
{
if(character < '0')
{
return false;
}
if(character > '9')
{
return false;
}
return true;
}
bool vx_is_ascii_blank(char character)
{
if(character == ' ')
{
return true;
}
if(character == '\t')
{
return true;
}
return false;
}
bool vx_is_ascii_control(char character)
{
if(character < 32)
{
return true;
}
if(character == 127)
{
return true;
}
return false;
}
void vx_write_spaces(FILE *output, uint32_t count)
{
uint32_t space_index = 0;
while(space_index < count)
{
fputc(' ', output);
++space_index;
}
}
uint8_t vx_digits_for_decimal(uint32_t decimal)
{
if(decimal < 10)
{
return 1;
}
if(decimal < 100)
{
return 2;
}
if(decimal < 1000)
{
return 3;
}
if(decimal < 10000)
{
return 4;
}
if(decimal < 100000)
{
return 5;
}
if(decimal < 1000000)
{
return 6;
}
if(decimal < 10000000)
{
return 7;
}
if(decimal < 100000000)
{
return 8;
}
if(decimal < 1000000000)
{
return 9;
}
return 10;
}