diff --git a/External/json.h b/External/json.h new file mode 100644 index 0000000..bdf3eae --- /dev/null +++ b/External/json.h @@ -0,0 +1,3451 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/json.h. +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + 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 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. + + For more information, please refer to . +*/ + +#ifndef SHEREDOM_JSON_H_INCLUDED +#define SHEREDOM_JSON_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +/* disable warning: no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) + +/* disable warning: 'bytes padding added after construct' */ +#pragma warning(disable : 4820) +#endif + +#include +#include + +#if defined(_MSC_VER) || defined(__WATCOMC__) +#define json_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define json_weak __attribute__((weak)) +#else +#error Non clang, non gcc, non MSVC, non WATCOM compiler found! +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct json_value_s; +struct json_parse_result_s; + +enum json_parse_flags_e { + json_parse_flags_default = 0, + + /* allow trailing commas in objects and arrays. For example, both [true,] and + {"a" : null,} would be allowed with this option on. */ + json_parse_flags_allow_trailing_comma = 0x1, + + /* allow unquoted keys for objects. For example, {a : null} would be allowed + with this option on. */ + json_parse_flags_allow_unquoted_keys = 0x2, + + /* allow a global unbracketed object. For example, a : null, b : true, c : {} + would be allowed with this option on. */ + json_parse_flags_allow_global_object = 0x4, + + /* allow objects to use '=' instead of ':' between key/value pairs. For + example, a = null, b : true would be allowed with this option on. */ + json_parse_flags_allow_equals_in_object = 0x8, + + /* allow that objects don't have to have comma separators between key/value + pairs. */ + json_parse_flags_allow_no_commas = 0x10, + + /* allow c-style comments (either variants) to be ignored in the input JSON + file. */ + json_parse_flags_allow_c_style_comments = 0x20, + + /* deprecated flag, unused. */ + json_parse_flags_deprecated = 0x40, + + /* record location information for each value. */ + json_parse_flags_allow_location_information = 0x80, + + /* allow strings to be 'single quoted'. */ + json_parse_flags_allow_single_quoted_strings = 0x100, + + /* allow numbers to be hexadecimal. */ + json_parse_flags_allow_hexadecimal_numbers = 0x200, + + /* allow numbers like +123 to be parsed. */ + json_parse_flags_allow_leading_plus_sign = 0x400, + + /* allow numbers like .0123 or 123. to be parsed. */ + json_parse_flags_allow_leading_or_trailing_decimal_point = 0x800, + + /* allow Infinity, -Infinity, NaN, -NaN. */ + json_parse_flags_allow_inf_and_nan = 0x1000, + + /* allow multi line string values. */ + json_parse_flags_allow_multi_line_strings = 0x2000, + + /* allow simplified JSON to be parsed. Simplified JSON is an enabling of a set + of other parsing options. */ + json_parse_flags_allow_simplified_json = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_global_object | + json_parse_flags_allow_equals_in_object | + json_parse_flags_allow_no_commas), + + /* allow JSON5 to be parsed. JSON5 is an enabling of a set of other parsing + options. */ + json_parse_flags_allow_json5 = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_c_style_comments | + json_parse_flags_allow_single_quoted_strings | + json_parse_flags_allow_hexadecimal_numbers | + json_parse_flags_allow_leading_plus_sign | + json_parse_flags_allow_leading_or_trailing_decimal_point | + json_parse_flags_allow_inf_and_nan | + json_parse_flags_allow_multi_line_strings) +}; + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to malloc for the entire encoding. + * Returns 0 if an error occurred (malformed JSON input, or malloc failed). */ +json_weak struct json_value_s *json_parse(const void *src, size_t src_size); + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to alloc_func_ptr for the entire + * encoding. Returns 0 if an error occurred (malformed JSON input, or malloc + * failed). If an error occurred, the result struct (if not NULL) will explain + * the type of error, and the location in the input it occurred. If + * alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *, size_t), void *user_data, + struct json_parse_result_s *result); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to malloc for the entire encoding. + */ +json_weak struct json_value_s * +json_extract_value(const struct json_value_s *value); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to alloc_func_ptr for the entire + * encoding. If alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, size_t), void *user_data); + +/* Write out a minified JSON utf-8 string. This string is an encoding of the + * minimal string characters required to still encode the same data. + * json_write_minified performs 1 call to malloc for the entire encoding. Return + * 0 if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_minified(const struct json_value_s *value, + size_t *out_size); + +/* Write out a pretty JSON utf-8 string. This string is encoded such that the + * resultant JSON is pretty in that it is easily human readable. The indent and + * newline parameters allow a user to specify what kind of indentation and + * newline they want (two spaces / three spaces / tabs? \r, \n, \r\n ?). Both + * indent and newline can be NULL, indent defaults to two spaces (" "), and + * newline defaults to linux newlines ('\n' as the newline character). + * json_write_pretty performs 1 call to malloc for the entire encoding. Return 0 + * if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_pretty(const struct json_value_s *value, + const char *indent, const char *newline, + size_t *out_size); + +/* Reinterpret a JSON value as a string. Returns null is the value was not a + * string. */ +json_weak struct json_string_s * +json_value_as_string(struct json_value_s *const value); + +/* Reinterpret a JSON value as a number. Returns null is the value was not a + * number. */ +json_weak struct json_number_s * +json_value_as_number(struct json_value_s *const value); + +/* Reinterpret a JSON value as an object. Returns null is the value was not an + * object. */ +json_weak struct json_object_s * +json_value_as_object(struct json_value_s *const value); + +/* Reinterpret a JSON value as an array. Returns null is the value was not an + * array. */ +json_weak struct json_array_s * +json_value_as_array(struct json_value_s *const value); + +/* Whether the value is true. */ +json_weak int json_value_is_true(const struct json_value_s *const value); + +/* Whether the value is false. */ +json_weak int json_value_is_false(const struct json_value_s *const value); + +/* Whether the value is null. */ +json_weak int json_value_is_null(const struct json_value_s *const value); + +/* The various types JSON values can be. Used to identify what a value is. */ +typedef enum json_type_e { + json_type_string, + json_type_number, + json_type_object, + json_type_array, + json_type_true, + json_type_false, + json_type_null + +} json_type_t; + +/* A JSON string value. */ +typedef struct json_string_s { + /* utf-8 string */ + const char *string; + /* The size (in bytes) of the string */ + size_t string_size; + +} json_string_t; + +/* A JSON string value (extended). */ +typedef struct json_string_ex_s { + /* The JSON string this extends. */ + struct json_string_s string; + + /* The character offset for the value in the JSON input. */ + size_t offset; + + /* The line number for the value in the JSON input. */ + size_t line_no; + + /* The row number for the value in the JSON input, in bytes. */ + size_t row_no; + +} json_string_ex_t; + +/* A JSON number value. */ +typedef struct json_number_s { + /* ASCII string containing representation of the number. */ + const char *number; + /* the size (in bytes) of the number. */ + size_t number_size; + +} json_number_t; + +/* an element of a JSON object. */ +typedef struct json_object_element_s { + /* the name of this element. */ + struct json_string_s *name; + /* the value of this element. */ + struct json_value_s *value; + /* the next object element (can be NULL if the last element in the object). */ + struct json_object_element_s *next; + +} json_object_element_t; + +/* a JSON object value. */ +typedef struct json_object_s { + /* a linked list of the elements in the object. */ + struct json_object_element_s *start; + /* the number of elements in the object. */ + size_t length; + +} json_object_t; + +/* an element of a JSON array. */ +typedef struct json_array_element_s { + /* the value of this element. */ + struct json_value_s *value; + /* the next array element (can be NULL if the last element in the array). */ + struct json_array_element_s *next; + +} json_array_element_t; + +/* a JSON array value. */ +typedef struct json_array_s { + /* a linked list of the elements in the array. */ + struct json_array_element_s *start; + /* the number of elements in the array. */ + size_t length; + +} json_array_t; + +/* a JSON value. */ +typedef struct json_value_s { + /* a pointer to either a json_string_s, json_number_s, json_object_s, or. */ + /* json_array_s. Should be cast to the appropriate struct type based on what. + */ + /* the type of this value is. */ + void *payload; + /* must be one of json_type_e. If type is json_type_true, json_type_false, or. + */ + /* json_type_null, payload will be NULL. */ + size_t type; + +} json_value_t; + +/* a JSON value (extended). */ +typedef struct json_value_ex_s { + /* the JSON value this extends. */ + struct json_value_s value; + + /* the character offset for the value in the JSON input. */ + size_t offset; + + /* the line number for the value in the JSON input. */ + size_t line_no; + + /* the row number for the value in the JSON input, in bytes. */ + size_t row_no; + +} json_value_ex_t; + +/* a parsing error code. */ +enum json_parse_error_e { + /* no error occurred (huzzah!). */ + json_parse_error_none = 0, + + /* expected either a comma or a closing '}' or ']' to close an object or. */ + /* array! */ + json_parse_error_expected_comma_or_closing_bracket, + + /* colon separating name/value pair was missing! */ + json_parse_error_expected_colon, + + /* expected string to begin with '"'! */ + json_parse_error_expected_opening_quote, + + /* invalid escaped sequence in string! */ + json_parse_error_invalid_string_escape_sequence, + + /* invalid number format! */ + json_parse_error_invalid_number_format, + + /* invalid value! */ + json_parse_error_invalid_value, + + /* reached end of buffer before object/array was complete! */ + json_parse_error_premature_end_of_buffer, + + /* string was malformed! */ + json_parse_error_invalid_string, + + /* a call to malloc, or a user provider allocator, failed. */ + json_parse_error_allocator_failed, + + /* the JSON input had unexpected trailing characters that weren't part of the. + JSON value. */ + json_parse_error_unexpected_trailing_characters, + + /* catch-all error for everything else that exploded (real bad chi!). */ + json_parse_error_unknown +}; + +/* error report from json_parse_ex(). */ +typedef struct json_parse_result_s { + /* the error code (one of json_parse_error_e). */ + size_t error; + + /* the character offset for the error in the JSON input. */ + size_t error_offset; + + /* the line number for the error in the JSON input. */ + size_t error_line_no; + + /* the row number for the error, in bytes. */ + size_t error_row_no; + +} json_parse_result_t; + +#ifdef __cplusplus +} /* extern "C". */ +#endif + +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#define json_uintmax_t unsigned __int64 +#else +#include +#define json_uintmax_t uintmax_t +#endif + +#if defined(_MSC_VER) +#define json_strtoumax _strtoui64 +#else +#define json_strtoumax strtoumax +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) +#define json_null nullptr +#else +#define json_null 0 +#endif + +#if defined(__clang__) +#pragma clang diagnostic push + +/* we do one big allocation via malloc, then cast aligned slices of this for. */ +/* our structures - we don't have a way to tell the compiler we know what we. */ +/* are doing, so disable the warning instead! */ +#pragma clang diagnostic ignored "-Wcast-align" + +/* We use C style casts everywhere. */ +#pragma clang diagnostic ignored "-Wold-style-cast" + +/* We need long long for strtoull. */ +#pragma clang diagnostic ignored "-Wc++11-long-long" + +/* Who cares if nullptr doesn't work with C++98, we don't use it there! */ +#pragma clang diagnostic ignored "-Wc++98-compat" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#elif defined(_MSC_VER) +#pragma warning(push) + +/* disable 'function selected for inline expansion' warning. */ +#pragma warning(disable : 4711) + +/* disable '#pragma warning: there is no warning number' warning. */ +#pragma warning(disable : 4619) + +/* disable 'warning number not a valid compiler warning' warning. */ +#pragma warning(disable : 4616) + +/* disable 'Compiler will insert Spectre mitigation for memory load if + * /Qspectre. */ +/* switch specified' warning. */ +#pragma warning(disable : 5045) +#endif + +struct json_parse_state_s { + const char *src; + size_t size; + size_t offset; + size_t flags_bitset; + char *data; + char *dom; + size_t dom_size; + size_t data_size; + size_t line_no; /* line counter for error reporting. */ + size_t line_offset; /* (offset-line_offset) is the character number (in + bytes). */ + size_t error; +}; + +json_weak int json_hexadecimal_digit(const char c); +int json_hexadecimal_digit(const char c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } + if ('a' <= c && c <= 'f') { + return c - 'a' + 10; + } + if ('A' <= c && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +json_weak int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result); +int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result) { + const char *p; + int digit; + + if (size > sizeof(unsigned long) * 2) { + return 0; + } + + *result = 0; + for (p = c; (unsigned long)(p - c) < size; ++p) { + *result <<= 4; + digit = json_hexadecimal_digit(*p); + if (digit < 0 || digit > 15) { + return 0; + } + *result |= (unsigned char)digit; + } + return 1; +} + +json_weak int json_skip_whitespace(struct json_parse_state_s *state); +int json_skip_whitespace(struct json_parse_state_s *state) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + + if (offset >= state->size) { + return 0; + } + + /* the only valid whitespace according to ECMA-404 is ' ', '\n', '\r' and + * '\t'. */ + switch (src[offset]) { + default: + return 0; + case ' ': + case '\r': + case '\t': + case '\n': + break; + } + + do { + switch (src[offset]) { + default: + /* Update offset. */ + state->offset = offset; + return 1; + case ' ': + case '\r': + case '\t': + break; + case '\n': + state->line_no++; + state->line_offset = offset; + break; + } + + offset++; + } while (offset < size); + + /* Update offset. */ + state->offset = offset; + return 1; +} + +json_weak int json_skip_c_style_comments(struct json_parse_state_s *state); +int json_skip_c_style_comments(struct json_parse_state_s *state) { + /* to have a C-style comment we need at least 2 characters of space */ + if ((state->offset + 2) > state->size) { + return 0; + } + + /* do we have a comment? */ + if ('/' == state->src[state->offset]) { + if ('/' == state->src[state->offset + 1]) { + /* we had a comment of the form // */ + + /* skip first '/' */ + state->offset++; + + /* skip second '/' */ + state->offset++; + + while (state->offset < state->size) { + switch (state->src[state->offset]) { + default: + /* skip the character in the comment */ + state->offset++; + break; + case '\n': + /* if we have a newline, our comment has ended! Skip the newline */ + state->offset++; + + /* we entered a newline, so move our line info forward */ + state->line_no++; + state->line_offset = state->offset; + return 1; + } + } + + /* we reached the end of the JSON file! */ + return 1; + } else if ('*' == state->src[state->offset + 1]) { + /* we had a comment in the C-style long form */ + + /* skip '/' */ + state->offset++; + + /* skip '*' */ + state->offset++; + + while (state->offset + 1 < state->size) { + if (('*' == state->src[state->offset]) && + ('/' == state->src[state->offset + 1])) { + /* we reached the end of our comment! */ + state->offset += 2; + return 1; + } else if ('\n' == state->src[state->offset]) { + /* we entered a newline, so move our line info forward */ + state->line_no++; + state->line_offset = state->offset; + } + + /* skip character within comment */ + state->offset++; + } + + /* comment wasn't ended correctly which is a failure */ + return 1; + } + } + + /* we didn't have any comment, which is ok too! */ + return 0; +} + +json_weak int json_skip_all_skippables(struct json_parse_state_s *state); +int json_skip_all_skippables(struct json_parse_state_s *state) { + /* skip all whitespace and other skippables until there are none left. note + * that the previous version suffered from read past errors should. the + * stream end on json_skip_c_style_comments eg. '{"a" ' with comments flag. + */ + + int did_consume = 0; + const size_t size = state->size; + + if (json_parse_flags_allow_c_style_comments & state->flags_bitset) { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + + /* This should really be checked on access, not in front of every call. + */ + if (state->offset >= size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume |= json_skip_c_style_comments(state); + } while (0 != did_consume); + } else { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + } while (0 != did_consume); + } + + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); + +json_weak int json_get_string_size(struct json_parse_state_s *state, + size_t is_key); +int json_get_string_size(struct json_parse_state_s *state, size_t is_key) { + size_t offset = state->offset; + const size_t size = state->size; + size_t data_size = 0; + const char *const src = state->src; + const int is_single_quote = '\'' == src[offset]; + const char quote_to_use = is_single_quote ? '\'' : '"'; + const size_t flags_bitset = state->flags_bitset; + unsigned long codepoint; + unsigned long high_surrogate = 0; + + if ((json_parse_flags_allow_location_information & flags_bitset) != 0 && + is_key != 0) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + if ('"' != src[offset]) { + /* if we are allowed single quoted strings check for that too. */ + if (!((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + is_single_quote)) { + state->error = json_parse_error_expected_opening_quote; + state->offset = offset; + return 1; + } + } + + /* skip leading '"' or '\''. */ + offset++; + + while ((offset < size) && (quote_to_use != src[offset])) { + /* add space for the character. */ + data_size++; + + switch (src[offset]) { + default: + break; + case '\0': + case '\t': + state->error = json_parse_error_invalid_string; + state->offset = offset; + return 1; + } + + if ('\\' == src[offset]) { + /* skip reverse solidus character. */ + offset++; + + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset; + return 1; + } + + switch (src[offset]) { + default: + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + /* all valid characters! */ + offset++; + break; + case 'u': + if (!(offset + 5 < size)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + codepoint = 0; + if (!json_hexadecimal_value(&src[offset + 1], 4, &codepoint)) { + /* escaped unicode sequences must contain 4 hexadecimal digits! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + /* Valid sequence! + * see: https://en.wikipedia.org/wiki/UTF-8#Invalid_code_points. + * 1 7 U + 0000 U + 007F 0xxxxxxx. + * 2 11 U + 0080 U + 07FF 110xxxxx + * 10xxxxxx. + * 3 16 U + 0800 U + FFFF 1110xxxx + * 10xxxxxx 10xxxxxx. + * 4 21 U + 10000 U + 10FFFF 11110xxx + * 10xxxxxx 10xxxxxx 10xxxxxx. + * Note: the high and low surrogate halves used by UTF-16 (U+D800 + * through U+DFFF) and code points not encodable by UTF-16 (those after + * U+10FFFF) are not legal Unicode values, and their UTF-8 encoding must + * be treated as an invalid byte sequence. */ + + if (high_surrogate != 0) { + /* we previously read the high half of the \uxxxx\uxxxx pair, so now + * we expect the low half. */ + if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate range. */ + data_size += 3; + high_surrogate = 0; + } else { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + } else if (codepoint <= 0x7f) { + data_size += 0; + } else if (codepoint <= 0x7ff) { + data_size += 1; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate range. */ + /* The codepoint is the first half of a "utf-16 surrogate pair". so we + * need the other half for it to be valid: \uHHHH\uLLLL. */ + if (offset + 11 > size || '\\' != src[offset + 5] || + 'u' != src[offset + 6]) { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + high_surrogate = codepoint; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdfff) { /* low surrogate range. */ + /* we did not read the other half before. */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } else { + data_size += 2; + } + /* escaped codepoints after 0xffff are supported in json through utf-16 + * surrogate pairs: \uD83D\uDD25 for U+1F525. */ + + offset += 5; + break; + } + } else if (('\r' == src[offset]) || ('\n' == src[offset])) { + if (!(json_parse_flags_allow_multi_line_strings & flags_bitset)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + offset++; + } else { + /* skip character (valid part of sequence). */ + offset++; + } + } + + /* If the offset is equal to the size, we had a non-terminated string! */ + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset - 1; + return 1; + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* add enough space to store the string. */ + state->data_size += data_size; + + /* one more byte for null terminator ending the string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int is_valid_unquoted_key_char(const char c); +int is_valid_unquoted_key_char(const char c) { + return (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || ('_' == c)); +} + +json_weak int json_get_key_size(struct json_parse_state_s *state); +int json_get_key_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + + if (json_parse_flags_allow_unquoted_keys & flags_bitset) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + size_t data_size = state->data_size; + + /* if we are allowing unquoted keys, first grok for a quote... */ + if ('"' == src[offset]) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else if ((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + ('\'' == src[offset])) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else { + while ((offset < size) && is_valid_unquoted_key_char(src[offset])) { + offset++; + data_size++; + } + + /* one more byte for null terminator ending the string! */ + data_size++; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + /* update offset. */ + state->offset = offset; + + /* update data_size. */ + state->data_size = data_size; + + return 0; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + return json_get_string_size(state, 1); + } +} + +json_weak int json_get_object_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_object_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + int found_closing_brace = 0; + + if (is_global_object) { + /* if we found an opening '{' of an object, we actually have a normal JSON + * object at the root of the DOM... */ + if (!json_skip_all_skippables(state) && '{' == state->src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + if ('{' != src[state->offset]) { + state->error = json_parse_error_unknown; + return 1; + } + + /* skip leading '{'. */ + state->offset++; + } + + state->dom_size += sizeof(struct json_object_s); + + if ((state->offset == size) && !is_global_object) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + do { + if (!is_global_object) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + found_closing_brace = 1; + + /* finished the object! */ + break; + } + } else { + /* we don't require brackets, so that means the object ends when the input + * stream ends! */ + if (json_skip_all_skippables(state)) { + break; + } + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (json_parse_flags_allow_no_commas & flags_bitset) { + /* we don't require a comma, and we didn't find one, which is ok! */ + allow_comma = 0; + } else { + /* otherwise we are required to have a comma, and we found none. */ + state->error = json_parse_error_expected_comma_or_closing_bracket; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + } + } + + if (json_get_key_size(state)) { + /* key parsing failed! */ + state->error = json_parse_error_invalid_string; + return 1; + } + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + const char current = src[state->offset]; + if ((':' != current) && ('=' != current)) { + state->error = json_parse_error_expected_colon; + return 1; + } + } else { + if (':' != src[state->offset]) { + state->error = json_parse_error_expected_colon; + return 1; + } + } + + /* skip colon. */ + state->offset++; + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + return 1; + } + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + if ((state->offset == size) && !is_global_object && !found_closing_brace) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + state->dom_size += sizeof(struct json_object_element_s) * elements; + + return 0; +} + +json_weak int json_get_array_size(struct json_parse_state_s *state); +int json_get_array_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t elements = 0; + int allow_comma = 0; + const char *const src = state->src; + const size_t size = state->size; + + if ('[' != src[state->offset]) { + /* expected array to begin with leading '['. */ + state->error = json_parse_error_unknown; + return 1; + } + + /* skip leading '['. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_s); + + while (state->offset < size) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_element_s) * elements; + + /* finished the object! */ + return 0; + } + + /* if we parsed at least once element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (!(json_parse_flags_allow_no_commas & flags_bitset)) { + state->error = json_parse_error_expected_comma_or_closing_bracket; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + allow_comma = 0; + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + } + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + return 1; + } + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } + + /* we consumed the entire input before finding the closing ']' of the array! + */ + state->error = json_parse_error_premature_end_of_buffer; + return 1; +} + +json_weak int json_get_number_size(struct json_parse_state_s *state); +int json_get_number_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + int had_leading_digits = 0; + const char *const src = state->src; + + state->dom_size += sizeof(struct json_number_s); + + if ((json_parse_flags_allow_hexadecimal_numbers & flags_bitset) && + (offset + 1 < size) && ('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* skip the leading 0x that identifies a hexadecimal number. */ + offset += 2; + + /* consume hexadecimal digits. */ + while ((offset < size) && (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F'))) { + offset++; + } + } else { + int found_sign = 0; + int inf_or_nan = 0; + + if ((offset < size) && + (('-' == src[offset]) || + ((json_parse_flags_allow_leading_plus_sign & flags_bitset) && + ('+' == src[offset])))) { + /* skip valid leading '-' or '+'. */ + offset++; + + found_sign = 1; + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const char inf[9] = "Infinity"; + const size_t inf_strlen = sizeof(inf) - 1; + const char nan[4] = "NaN"; + const size_t nan_strlen = sizeof(nan) - 1; + + if (offset + inf_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < inf_strlen; i++) { + if (inf[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'Infinity' keyword! */ + offset += inf_strlen; + + inf_or_nan = 1; + } + } + + if (offset + nan_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < nan_strlen; i++) { + if (nan[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'NaN' keyword! */ + offset += nan_strlen; + + inf_or_nan = 1; + } + } + + if (inf_or_nan) { + if (offset < size) { + switch (src[offset]) { + default: + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'e': + case 'E': + /* cannot follow an inf or nan with digits! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + } + } + + if (found_sign && !inf_or_nan && (offset < size) && + !('0' <= src[offset] && src[offset] <= '9')) { + /* check if we are allowing leading '.'. */ + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + ('.' != src[offset])) { + /* a leading '-' must be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + if ((offset < size) && ('0' == src[offset])) { + /* skip valid '0'. */ + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + + if ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + /* a leading '0' must not be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* the main digits of our number next. */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + } + + if ((offset < size) && ('.' == src[offset])) { + offset++; + + if ((offset >= size) || !('0' <= src[offset] && src[offset] <= '9')) { + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + !had_leading_digits) { + /* a decimal point must be followed by at least one digit. */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* a decimal point can be followed by more digits of course! */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + } + } + + if ((offset < size) && ('e' == src[offset] || 'E' == src[offset])) { + /* our number has an exponent! Skip 'e' or 'E'. */ + offset++; + + if ((offset < size) && ('-' == src[offset] || '+' == src[offset])) { + /* skip optional '-' or '+'. */ + offset++; + } + + if ((offset < size) && !('0' <= src[offset] && src[offset] <= '9')) { + /* an exponent must have at least one digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + + /* consume exponent digits. */ + do { + offset++; + } while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')); + } + } + + if (offset < size) { + switch (src[offset]) { + case ' ': + case '\t': + case '\r': + case '\n': + case '}': + case ',': + case ']': + /* all of the above are ok. */ + break; + case '=': + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + break; + } + + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + default: + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + state->data_size += offset - state->offset; + + /* one more byte for null terminator ending the number string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_value_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + size_t offset; + const size_t size = state->size; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_value_ex_s); + } else { + state->dom_size += sizeof(struct json_value_s); + } + + if (is_global_object) { + return json_get_object_size(state, /* is_global_object = */ 1); + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + /* can cache offset now. */ + offset = state->offset; + + switch (src[offset]) { + case '"': + return json_get_string_size(state, 0); + case '\'': + if (json_parse_flags_allow_single_quoted_strings & flags_bitset) { + return json_get_string_size(state, 0); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + case '{': + return json_get_object_size(state, /* is_global_object = */ 0); + case '[': + return json_get_array_size(state); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return json_get_number_size(state); + case '+': + if (json_parse_flags_allow_leading_plus_sign & flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + case '.': + if (json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + state->offset += 5; + return 0; + } else if ((offset + 4) <= size && 'n' == state->src[offset + 0] && + 'u' == state->src[offset + 1] && + 'l' == state->src[offset + 2] && + 'l' == state->src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + return json_get_number_size(state); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + return json_get_number_size(state); + } + + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + } +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); + +json_weak void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string) { + size_t offset = state->offset; + size_t bytes_written = 0; + const char *const src = state->src; + const char quote_to_use = '\'' == src[offset] ? '\'' : '"'; + char *data = state->data; + unsigned long high_surrogate = 0; + unsigned long codepoint; + + string->string = data; + + /* skip leading '"' or '\''. */ + offset++; + + while (quote_to_use != src[offset]) { + if ('\\' == src[offset]) { + /* skip the reverse solidus. */ + offset++; + + switch (src[offset++]) { + default: + return; /* we cannot ever reach here. */ + case 'u': { + codepoint = 0; + if (!json_hexadecimal_value(&src[offset], 4, &codepoint)) { + return; /* this shouldn't happen as the value was already validated. + */ + } + + offset += 4; + + if (codepoint <= 0x7fu) { + data[bytes_written++] = (char)codepoint; /* 0xxxxxxx. */ + } else if (codepoint <= 0x7ffu) { + data[bytes_written++] = + (char)(0xc0u | (codepoint >> 6)); /* 110xxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate. */ + high_surrogate = codepoint; + continue; /* we need the low half to form a complete codepoint. */ + } else if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate. */ + /* combine with the previously read half to obtain the complete + * codepoint. */ + const unsigned long surrogate_offset = + 0x10000u - (0xD800u << 10) - 0xDC00u; + codepoint = (high_surrogate << 10) + codepoint + surrogate_offset; + high_surrogate = 0; + data[bytes_written++] = + (char)(0xF0u | (codepoint >> 18)); /* 11110xxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 12) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else { + /* we assume the value was validated and thus is within the valid + * range. */ + data[bytes_written++] = + (char)(0xe0u | (codepoint >> 12)); /* 1110xxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } + } break; + case '"': + data[bytes_written++] = '"'; + break; + case '\\': + data[bytes_written++] = '\\'; + break; + case '/': + data[bytes_written++] = '/'; + break; + case 'b': + data[bytes_written++] = '\b'; + break; + case 'f': + data[bytes_written++] = '\f'; + break; + case 'n': + data[bytes_written++] = '\n'; + break; + case 'r': + data[bytes_written++] = '\r'; + break; + case 't': + data[bytes_written++] = '\t'; + break; + case '\r': + data[bytes_written++] = '\r'; + + /* check if we have a "\r\n" sequence. */ + if ('\n' == src[offset]) { + data[bytes_written++] = '\n'; + offset++; + } + + break; + case '\n': + data[bytes_written++] = '\n'; + break; + } + } else { + /* copy the character. */ + data[bytes_written++] = src[offset++]; + } + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* record the size of the string. */ + string->string_size = bytes_written; + + /* add null terminator to string. */ + data[bytes_written++] = '\0'; + + /* move data along. */ + state->data += bytes_written; + + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string) { + if (json_parse_flags_allow_unquoted_keys & state->flags_bitset) { + const char *const src = state->src; + char *const data = state->data; + size_t offset = state->offset; + + /* if we are allowing unquoted keys, check for quoted anyway... */ + if (('"' == src[offset]) || ('\'' == src[offset])) { + /* ... if we got a quote, just parse the key as a string as normal. */ + json_parse_string(state, string); + } else { + size_t size = 0; + + string->string = state->data; + + while (is_valid_unquoted_key_char(src[offset])) { + data[size++] = src[offset++]; + } + + /* add null terminator to string. */ + data[size] = '\0'; + + /* record the size of the string. */ + string->string_size = size++; + + /* move data along. */ + state->data += size; + + /* update offset. */ + state->offset = offset; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + json_parse_string(state, string); + } +} + +json_weak void json_parse_object(struct json_parse_state_s *state, + int is_global_object, + struct json_object_s *object); +void json_parse_object(struct json_parse_state_s *state, int is_global_object, + struct json_object_s *object) { + const size_t flags_bitset = state->flags_bitset; + const size_t size = state->size; + const char *const src = state->src; + size_t elements = 0; + int allow_comma = 0; + struct json_object_element_s *previous = json_null; + + if (is_global_object) { + /* if we skipped some whitespace, and then found an opening '{' of an. */ + /* object, we actually have a normal JSON object at the root of the DOM... + */ + if ('{' == src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + /* skip leading '{'. */ + state->offset++; + } + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + while (state->offset < size) { + struct json_object_element_s *element = json_null; + struct json_string_s *string = json_null; + struct json_value_s *value = json_null; + + if (!is_global_object) { + (void)json_skip_all_skippables(state); + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + /* finished the object! */ + break; + } + } else { + if (json_skip_all_skippables(state)) { + /* global object ends when the file ends! */ + break; + } + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_object_element_s *)state->dom; + + state->dom += sizeof(struct json_object_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our object. */ + object->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_string_ex_s *string_ex = + (struct json_string_ex_s *)state->dom; + state->dom += sizeof(struct json_string_ex_s); + + string_ex->offset = state->offset; + string_ex->line_no = state->line_no; + string_ex->row_no = state->offset - state->line_offset; + + string = &(string_ex->string); + } else { + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + } + + element->name = string; + + (void)json_parse_key(state, string); + + (void)json_skip_all_skippables(state); + + /* skip colon or equals. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } + + /* if we had at least one element, end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + object->start = json_null; + } + + object->length = elements; +} + +json_weak void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array); +void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array) { + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + struct json_array_element_s *previous = json_null; + + /* skip leading '['. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + do { + struct json_array_element_s *element = json_null; + struct json_value_s *value = json_null; + + (void)json_skip_all_skippables(state); + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + /* finished the array! */ + break; + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_array_element_s *)state->dom; + + state->dom += sizeof(struct json_array_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our array. */ + array->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & state->flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + /* end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + array->start = json_null; + } + + array->length = elements; +} + +json_weak void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number); +void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + size_t bytes_written = 0; + const char *const src = state->src; + char *data = state->data; + + number->number = data; + + if (json_parse_flags_allow_hexadecimal_numbers & flags_bitset) { + if (('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* consume hexadecimal digits. */ + while ((offset < size) && + (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F') || + ('x' == src[offset]) || ('X' == src[offset]))) { + data[bytes_written++] = src[offset++]; + } + } + } + + while (offset < size) { + int end = 0; + + switch (src[offset]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'e': + case 'E': + case '+': + case '-': + data[bytes_written++] = src[offset++]; + break; + default: + end = 1; + break; + } + + if (0 != end) { + break; + } + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const size_t inf_strlen = 8; /* = strlen("Infinity");. */ + const size_t nan_strlen = 3; /* = strlen("NaN");. */ + + if (offset + inf_strlen < size) { + if ('I' == src[offset]) { + size_t i; + /* We found our special 'Infinity' keyword! */ + for (i = 0; i < inf_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + + if (offset + nan_strlen < size) { + if ('N' == src[offset]) { + size_t i; + /* We found our special 'NaN' keyword! */ + for (i = 0; i < nan_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + } + + /* record the size of the number. */ + number->number_size = bytes_written; + /* add null terminator to number string. */ + data[bytes_written++] = '\0'; + /* move data along. */ + state->data += bytes_written; + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); +void json_parse_value(struct json_parse_state_s *state, int is_global_object, + struct json_value_s *value) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t offset; + + (void)json_skip_all_skippables(state); + + /* cache offset now. */ + offset = state->offset; + + if (is_global_object) { + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 1, + (struct json_object_s *)value->payload); + } else { + switch (src[offset]) { + case '"': + case '\'': + value->type = json_type_string; + value->payload = state->dom; + state->dom += sizeof(struct json_string_s); + json_parse_string(state, (struct json_string_s *)value->payload); + break; + case '{': + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 0, + (struct json_object_s *)value->payload); + break; + case '[': + value->type = json_type_array; + value->payload = state->dom; + state->dom += sizeof(struct json_array_s); + json_parse_array(state, (struct json_array_s *)value->payload); + break; + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + break; + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + value->type = json_type_true; + value->payload = json_null; + state->offset += 4; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + value->type = json_type_false; + value->payload = json_null; + state->offset += 5; + } else if ((offset + 4) <= size && 'n' == src[offset + 0] && + 'u' == src[offset + 1] && 'l' == src[offset + 2] && + 'l' == src[offset + 3]) { + value->type = json_type_null; + value->payload = json_null; + state->offset += 4; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } + break; + } + } +} + +struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *user_data, size_t size), + void *user_data, struct json_parse_result_s *result) { + struct json_parse_state_s state; + void *allocation; + struct json_value_s *value; + size_t total_size; + int input_error; + + if (result) { + result->error = json_parse_error_none; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + if (json_null == src) { + /* invalid src pointer was null! */ + return json_null; + } + + state.src = (const char *)src; + state.size = src_size; + state.offset = 0; + state.line_no = 1; + state.line_offset = 0; + state.error = json_parse_error_none; + state.dom_size = 0; + state.data_size = 0; + state.flags_bitset = flags_bitset; + + input_error = json_get_value_size( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset)); + + if (0 == input_error) { + json_skip_all_skippables(&state); + + if (state.offset != state.size) { + /* our parsing didn't have an error, but there are characters remaining in + * the input that weren't part of the JSON! */ + + state.error = json_parse_error_unexpected_trailing_characters; + input_error = 1; + } + } + + if (input_error) { + /* parsing value's size failed (most likely an invalid JSON DOM!). */ + if (result) { + result->error = state.error; + result->error_offset = state.offset; + result->error_line_no = state.line_no; + result->error_row_no = state.offset - state.line_offset; + } + return json_null; + } + + /* our total allocation is the combination of the dom and data sizes (we. */ + /* first encode the structure of the JSON, and then the data referenced by. */ + /* the JSON values). */ + total_size = state.dom_size + state.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + if (json_null == allocation) { + /* malloc failed! */ + if (result) { + result->error = json_parse_error_allocator_failed; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + return json_null; + } + + /* reset offset so we can reuse it. */ + state.offset = 0; + + /* reset the line information so we can reuse it. */ + state.line_no = 1; + state.line_offset = 0; + + state.dom = (char *)allocation; + state.data = state.dom + state.dom_size; + + if (json_parse_flags_allow_location_information & state.flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state.dom; + state.dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state.offset; + value_ex->line_no = state.line_no; + value_ex->row_no = state.offset - state.line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state.dom; + state.dom += sizeof(struct json_value_s); + } + + json_parse_value( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset), + value); + + return (struct json_value_s *)allocation; +} + +struct json_value_s *json_parse(const void *src, size_t src_size) { + return json_parse_ex(src, src_size, json_parse_flags_default, json_null, + json_null, json_null); +} + +struct json_extract_result_s { + size_t dom_size; + size_t data_size; +}; + +struct json_value_s *json_extract_value(const struct json_value_s *value) { + return json_extract_value_ex(value, json_null, json_null); +} + +json_weak struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number); +json_weak struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string); +json_weak struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object); +json_weak struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array); +json_weak struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value); + +struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_number_s); + result.data_size = number->number_size; + return result; +} + +struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_string_s); + result.data_size = string->string_size + 1; + return result; +} + +struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object) { + struct json_extract_result_s result; + size_t i; + const struct json_object_element_s *element = object->start; + + result.dom_size = sizeof(struct json_object_s) + + (sizeof(struct json_object_element_s) * object->length); + result.data_size = 0; + + for (i = 0; i < object->length; i++) { + const struct json_extract_result_s string_result = + json_extract_get_string_size(element->name); + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += string_result.dom_size; + result.data_size += string_result.data_size; + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array) { + struct json_extract_result_s result; + size_t i; + const struct json_array_element_s *element = array->start; + + result.dom_size = sizeof(struct json_array_s) + + (sizeof(struct json_array_element_s) * array->length); + result.data_size = 0; + + for (i = 0; i < array->length; i++) { + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value) { + struct json_extract_result_s result = {0, 0}; + + switch (value->type) { + default: + break; + case json_type_object: + result = json_extract_get_object_size( + (const struct json_object_s *)value->payload); + break; + case json_type_array: + result = json_extract_get_array_size( + (const struct json_array_s *)value->payload); + break; + case json_type_number: + result = json_extract_get_number_size( + (const struct json_number_s *)value->payload); + break; + case json_type_string: + result = json_extract_get_string_size( + (const struct json_string_s *)value->payload); + break; + } + + result.dom_size += sizeof(struct json_value_s); + + return result; +} + +struct json_extract_state_s { + char *dom; + char *data; +}; + +json_weak void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value); +void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value) { + struct json_string_s *string; + struct json_number_s *number; + struct json_object_s *object; + struct json_array_s *array; + struct json_value_s *new_value; + + memcpy(state->dom, value, sizeof(struct json_value_s)); + new_value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + new_value->payload = state->dom; + + if (json_type_string == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + } else if (json_type_number == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_number_s)); + number = (struct json_number_s *)state->dom; + state->dom += sizeof(struct json_number_s); + + memcpy(state->data, number->number, number->number_size); + number->number = state->data; + state->data += number->number_size; + } else if (json_type_object == value->type) { + struct json_object_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_object_s)); + object = (struct json_object_s *)state->dom; + state->dom += sizeof(struct json_object_s); + + element = object->start; + object->start = (struct json_object_element_s *)state->dom; + + for (i = 0; i < object->length; i++) { + struct json_value_s *previous_value; + struct json_object_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_object_element_s)); + element = (struct json_object_element_s *)state->dom; + state->dom += sizeof(struct json_object_element_s); + + string = element->name; + memcpy(state->dom, string, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + element->name = string; + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_object_element_s *)state->dom; + } + } + } else if (json_type_array == value->type) { + struct json_array_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_array_s)); + array = (struct json_array_s *)state->dom; + state->dom += sizeof(struct json_array_s); + + element = array->start; + array->start = (struct json_array_element_s *)state->dom; + + for (i = 0; i < array->length; i++) { + struct json_value_s *previous_value; + struct json_array_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_array_element_s)); + element = (struct json_array_element_s *)state->dom; + state->dom += sizeof(struct json_array_element_s); + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_array_element_s *)state->dom; + } + } + } +} + +struct json_value_s *json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, + size_t), + void *user_data) { + void *allocation; + struct json_extract_result_s result; + struct json_extract_state_s state; + size_t total_size; + + if (json_null == value) { + /* invalid value was null! */ + return json_null; + } + + result = json_extract_get_value_size(value); + total_size = result.dom_size + result.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + state.dom = (char *)allocation; + state.data = state.dom + result.dom_size; + + json_extract_copy_value(&state, value); + + return (struct json_value_s *)allocation; +} + +struct json_string_s *json_value_as_string(struct json_value_s *const value) { + if (value->type != json_type_string) { + return json_null; + } + + return (struct json_string_s *)value->payload; +} + +struct json_number_s *json_value_as_number(struct json_value_s *const value) { + if (value->type != json_type_number) { + return json_null; + } + + return (struct json_number_s *)value->payload; +} + +struct json_object_s *json_value_as_object(struct json_value_s *const value) { + if (value->type != json_type_object) { + return json_null; + } + + return (struct json_object_s *)value->payload; +} + +struct json_array_s *json_value_as_array(struct json_value_s *const value) { + if (value->type != json_type_array) { + return json_null; + } + + return (struct json_array_s *)value->payload; +} + +int json_value_is_true(const struct json_value_s *const value) { + return value->type == json_type_true; +} + +int json_value_is_false(const struct json_value_s *const value) { + return value->type == json_type_false; +} + +int json_value_is_null(const struct json_value_s *const value) { + return value->type == json_type_null; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); + +json_weak int json_write_get_number_size(const struct json_number_s *number, + size_t *size); +int json_write_get_number_size(const struct json_number_s *number, + size_t *size) { + json_uintmax_t parsed_number; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* the number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + i = 0; + + while (0 != parsed_number) { + parsed_number /= 10; + i++; + } + + *size += i; + return 0; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '+' or '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf) { + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + *size += 22; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *size += 1; + } + } + + return 0; + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan) { + /* NaN becomes 1 because JSON can't support it. */ + *size += 1; + + return 0; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a leading decimal point. */ + *size += 1; + goto cleanup; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a trailing decimal point. */ + *size += 1; + goto cleanup; + } + +cleanup: + *size += number->number_size; /* the actual string of the number. */ + + /* if we had a leading '+' we don't record it in the JSON output. */ + if ('+' == number->number[0]) { + *size -= 1; + } + + return 0; +} + +json_weak int json_write_get_string_size(const struct json_string_s *string, + size_t *size); +int json_write_get_string_size(const struct json_string_s *string, + size_t *size) { + size_t i; + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + *size += 2; + break; + default: + *size += 1; + break; + } + } + + *size += 2; /* need to encode the surrounding '"' characters. */ + + return 0; +} + +json_weak int +json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size); +int json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size) { + struct json_array_element_s *element; + + *size += 2; /* '[' and ']'. */ + + if (1 < array->length) { + *size += array->length - 1; /* ','s seperate each element. */ + } + + for (element = array->start; json_null != element; element = element->next) { + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size); +int json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size) { + struct json_object_element_s *element; + + *size += 2; /* '{' and '}'. */ + + *size += object->length; /* ':'s seperate each name/value pair. */ + + if (1 < object->length) { + *size += object->length - 1; /* ','s seperate each element. */ + } + + for (element = object->start; json_null != element; element = element->next) { + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); +int json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_minified_get_array_size( + (struct json_array_s *)value->payload, size); + case json_type_object: + return json_write_minified_get_object_size( + (struct json_object_s *)value->payload, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); + +json_weak char *json_write_number(const struct json_number_s *number, + char *data); +char *json_write_number(const struct json_number_s *number, char *data) { + json_uintmax_t parsed_number, backup; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* The number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + /* We need a copy of parsed number twice, so take a backup of it. */ + backup = parsed_number; + + i = 0; + + while (0 != parsed_number) { + parsed_number /= 10; + i++; + } + + /* Restore parsed_number to its original value stored in the backup. */ + parsed_number = backup; + + /* Now use backup to take a copy of i, or the length of the string. */ + backup = i; + + do { + *(data + i - 1) = '0' + (char)(parsed_number % 10); + parsed_number /= 10; + i--; + } while (0 != parsed_number); + + data += backup; + + return data; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf++) { + const char *dbl_max; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *data++ = '-'; + } + + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + for (dbl_max = "1.7976931348623158e308"; '\0' != *dbl_max; dbl_max++) { + *data++ = *dbl_max; + } + + return data; + } + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan++) { + /* NaN becomes 0 because JSON can't support it. */ + *data++ = '0'; + return data; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* insert a '0' to fix the leading decimal point for JSON output. */ + *data++ = '0'; + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + /* insert a '0' to fix the trailing decimal point for JSON output. */ + *data++ = '0'; + + return data; + } + + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; +} + +json_weak char *json_write_string(const struct json_string_s *string, + char *data); +char *json_write_string(const struct json_string_s *string, char *data) { + size_t i; + + *data++ = '"'; /* open the string. */ + + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + *data++ = '\\'; /* escape the control character. */ + *data++ = '"'; + break; + case '\\': + *data++ = '\\'; /* escape the control character. */ + *data++ = '\\'; + break; + case '\b': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'b'; + break; + case '\f': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'f'; + break; + case '\n': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'n'; + break; + case '\r': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'r'; + break; + case '\t': + *data++ = '\\'; /* escape the control character. */ + *data++ = 't'; + break; + default: + *data++ = string->string[i]; + break; + } + } + + *data++ = '"'; /* close the string. */ + + return data; +} + +json_weak char *json_write_minified_array(const struct json_array_s *array, + char *data); +char *json_write_minified_array(const struct json_array_s *array, char *data) { + struct json_array_element_s *element = json_null; + + *data++ = '['; /* open the array. */ + + for (element = array->start; json_null != element; element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_minified_object(const struct json_object_s *object, + char *data); +char *json_write_minified_object(const struct json_object_s *object, + char *data) { + struct json_object_element_s *element = json_null; + + *data++ = '{'; /* open the object. */ + + for (element = object->start; json_null != element; element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + *data++ = ':'; /* ':'s seperate each name/value pair. */ + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); +char *json_write_minified_value(const struct json_value_s *value, char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_minified_array((struct json_array_s *)value->payload, + data); + case json_type_object: + return json_write_minified_object((struct json_object_s *)value->payload, + data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_minified(const struct json_value_s *value, size_t *out_size) { + size_t size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_write_minified_get_value_size(value, &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_minified_value(value, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); + +json_weak int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_array_element_s *element; + + *size += 1; /* '['. */ + + if (0 < array->length) { + /* if we have any elements we need to add a newline after our '['. */ + *size += newline_size; + + *size += array->length - 1; /* ','s seperate each element. */ + + for (element = array->start; json_null != element; + element = element->next) { + /* each element gets an indent. */ + *size += (depth + 1) * indent_size; + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + + /* each element gets a newline too. */ + *size += newline_size; + } + + /* since we wrote out some elements, need to add a newline and indentation. + */ + /* to the trailing ']'. */ + *size += depth * indent_size; + } + + *size += 1; /* ']'. */ + + return 0; +} + +json_weak int +json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size); +int json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_object_element_s *element; + + *size += 1; /* '{'. */ + + if (0 < object->length) { + *size += newline_size; /* need a newline next. */ + + *size += object->length - 1; /* ','s seperate each element. */ + + for (element = object->start; json_null != element; + element = element->next) { + /* each element gets an indent and newline. */ + *size += (depth + 1) * indent_size; + *size += newline_size; + + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + *size += 3; /* seperate each name/value pair with " : ". */ + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + } + + *size += depth * indent_size; + } + + *size += 1; /* '}'. */ + + return 0; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_pretty_get_array_size( + (struct json_array_s *)value->payload, depth, indent_size, newline_size, + size); + case json_type_object: + return json_write_pretty_get_object_size( + (struct json_object_s *)value->payload, depth, indent_size, + newline_size, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); + +json_weak char *json_write_pretty_array(const struct json_array_s *array, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_array(const struct json_array_s *array, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_array_element_s *element; + + *data++ = '['; /* open the array. */ + + if (0 < array->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = array->start; json_null != element; + element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_pretty_object(const struct json_object_s *object, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_object(const struct json_object_s *object, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_object_element_s *element; + + *data++ = '{'; /* open the object. */ + + if (0 < object->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = object->start; json_null != element; + element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + /* " : "s seperate each name/value pair. */ + *data++ = ' '; + *data++ = ':'; + *data++ = ' '; + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_value(const struct json_value_s *value, size_t depth, + const char *indent, const char *newline, + char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_pretty_array((struct json_array_s *)value->payload, depth, + indent, newline, data); + case json_type_object: + return json_write_pretty_object((struct json_object_s *)value->payload, + depth, indent, newline, data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_pretty(const struct json_value_s *value, const char *indent, + const char *newline, size_t *out_size) { + size_t size = 0; + size_t indent_size = 0; + size_t newline_size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_null == indent) { + indent = " "; /* default to two spaces. */ + } + + if (json_null == newline) { + newline = "\n"; /* default to linux newlines. */ + } + + while ('\0' != indent[indent_size]) { + ++indent_size; /* skip non-null terminating characters. */ + } + + while ('\0' != newline[newline_size]) { + ++newline_size; /* skip non-null terminating characters. */ + } + + if (json_write_pretty_get_value_size(value, 0, indent_size, newline_size, + &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_pretty_value(value, 0, indent, newline, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#endif /* SHEREDOM_JSON_H_INCLUDED. */ diff --git a/External/metadesk/md.c b/External/metadesk/md.c new file mode 100644 index 0000000..6be1e69 --- /dev/null +++ b/External/metadesk/md.c @@ -0,0 +1,4450 @@ +// LICENSE AT END OF FILE (MIT). + +/* +** Overrides & Options Macros +** +** Overridable +** "basic types" ** REQUIRED +** #define/typedef MD_i8, MD_i16, MD_i32, MD_i64 +** #define/typedef MD_u8, MD_u16, MD_u32, MD_u64 +** #define/typedef MD_f32, MD_f64 +** +** "memset" ** REQUIRED +** #define MD_IMPL_Memset (void*, int, uint64) -> void* +** #define MD_IMPL_Memmove (void*, void*, uint64) -> void* +** +** "file iteration" ** OPTIONAL (required for the metadesk FileIter helpers to work) +** #define MD_IMPL_FileIterBegin (MD_FileIter*, MD_String8) -> Boolean +** #define MD_IMPL_FileIterNext (MD_Arena*, MD_FileIter*) -> MD_FileInfo +** #define MD_IMPL_FileIterEnd (MD_FileIter*) -> void +** +** "file load" ** OPTIONAL (required for MD_ParseWholeFile to work) +** #define MD_IMPL_LoadEntireFile (MD_Arena*, MD_String8 filename) -> MD_String8 +** +** "low level memory" ** OPTIONAL (required when relying on the default arenas) +** #define MD_IMPL_Reserve (uint64) -> void* +** #define MD_IMPL_Commit (void*, uint64) -> MD_b32 +** #define MD_IMPL_Decommit (void*, uint64) -> void +** #define MD_IMPL_Release (void*, uint64) -> void +** +** +** "arena" ** REQUIRED +** #define MD_IMPL_Arena (must set before including md.h) +** #define MD_IMPL_ArenaMinPos uint64 +** #define MD_IMPL_ArenaAlloc () -> MD_IMPL_Arena* +** #define MD_IMPL_ArenaRelease (MD_IMPL_Arena*) -> void +** #define MD_IMPL_ArenaGetPos (MD_IMPL_Arena*) -> uint64 +** #define MD_IMPL_ArenaPush (MD_IMPL_Arena*, uint64) -> void* +** #define MD_IMPL_ArenaPopTo (MD_IMPL_Arena*, uint64) -> void +** #define MD_IMPL_ArenaSetAutoAlign (MD_IMPL_Arena*, uint64) -> void +** +** "scratch" ** REQUIRED +** #define MD_IMPL_GetScratch (MD_IMPL_Arena**, uint64) -> MD_IMPL_Arena* +** "scratch constants" ** OPTIONAL (required for default scratch) +** #define MD_IMPL_ScratchCount uint64 [default 2] +** +** "sprintf" ** REQUIRED +** #define MD_IMPL_Vsnprintf (char*, uint64, char const*, va_list) -> uint64 +** +** Static Parameters to the Default Arena Implementation +** #define MD_DEFAULT_ARENA_RES_SIZE uint64 [default 64 megabytes] +** #define MD_DEFAULT_ARENA_CMT_SIZE uint64 [default 64 kilabytes] +** +** Default Implementation Controls +** These controls default to '1' i.e. 'enabled' +** #define MD_DEFAULT_BASIC_TYPES -> construct "basic types" from stdint.h header +** #define MD_DEFAULT_MEMSET -> construct "memset" from CRT +** #define MD_DEFAULT_FILE_ITER -> construct "file iteration" from OS headers +** #define MD_DEFAULT_MEMORY -> construct "low level memory" from OS headers +** #define MD_DEFAULT_ARENA -> construct "arena" from "low level memory" +** #define MD_DEFAULT_SCRATCH -> construct "scratch" from "arena" +** #define MD_DEFAULT_SPRINTF -> construct "vsnprintf" from internal implementaion +** +*/ + +#if !defined(MD_C) +#define MD_C + +//~///////////////////////////////////////////////////////////////////////////// +/////////////////////////// CRT Implementation ///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +#if MD_DEFAULT_MEMSET + +#include +#include + +#if !defined(MD_IMPL_Memset) +# define MD_IMPL_Memset MD_CRT_Memset +#endif +#if !defined(MD_IMPL_Memmove) +# define MD_IMPL_Memmove MD_CRT_Memmove +#endif + +#define MD_CRT_Memset memset +#define MD_CRT_Memmove memmove + +#endif + +#if MD_DEFAULT_FILE_LOAD + +#include + +#if !defined(MD_IMPL_LoadEntireFile) +# define MD_IMPL_LoadEntireFile MD_CRT_LoadEntireFile +#endif + +MD_FUNCTION MD_String8 +MD_CRT_LoadEntireFile(MD_Arena *arena, MD_String8 filename) +{ + MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); + MD_String8 file_contents = MD_ZERO_STRUCT; + MD_String8 filename_copy = MD_S8Copy(scratch.arena, filename); + FILE *file = fopen((char*)filename_copy.str, "rb"); + if(file != 0) + { + fseek(file, 0, SEEK_END); + MD_u64 file_size = ftell(file); + fseek(file, 0, SEEK_SET); + file_contents.str = MD_PushArray(arena, MD_u8, file_size+1); + if(file_contents.str) + { + file_contents.size = file_size; + fread(file_contents.str, 1, file_size, file); + file_contents.str[file_contents.size] = 0; + } + fclose(file); + } + MD_ReleaseScratch(scratch); + return file_contents; +} + +#endif + + +//~///////////////////////////////////////////////////////////////////////////// +/////////////////////////// Win32 Implementation /////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +//- win32 header +#if (MD_DEFAULT_FILE_ITER || MD_2DEFAULT_MEMORY) && MD_OS_WINDOWS +# include +# pragma comment(lib, "User32.lib") +#endif + +//- win32 "file iteration" +#if MD_DEFAULT_FILE_ITER && MD_OS_WINDOWS + +#if !defined(MD_IMPL_FileIterBegin) +# define MD_IMPL_FileIterBegin MD_WIN32_FileIterBegin +#endif +#if !defined(MD_IMPL_FileIterNext) +# define MD_IMPL_FileIterNext MD_WIN32_FileIterNext +#endif +#if !defined(MD_IMPL_FileIterEnd) +# define MD_IMPL_FileIterEnd MD_WIN32_FileIterEnd +#endif + +typedef struct MD_WIN32_FileIter{ + HANDLE state; + MD_u64 first; + WIN32_FIND_DATAW find_data; +} MD_WIN32_FileIter; + +MD_StaticAssert(sizeof(MD_FileIter) >= sizeof(MD_WIN32_FileIter), file_iter_size_check); + +static MD_b32 +MD_WIN32_FileIterBegin(MD_FileIter *it, MD_String8 path) +{ + //- init search + MD_ArenaTemp scratch = MD_GetScratch(0, 0); + + MD_u8 c = path.str[path.size - 1]; + MD_b32 need_star = (c == '/' || c == '\\'); + MD_String8 cpath = need_star ? MD_S8Fmt(scratch.arena, "%.*s*", MD_S8VArg(path)) : path; + MD_String16 cpath16 = MD_S16FromS8(scratch.arena, cpath); + + WIN32_FIND_DATAW find_data = MD_ZERO_STRUCT; + HANDLE state = FindFirstFileW((WCHAR*)cpath16.str, &find_data); + + MD_ReleaseScratch(scratch); + + //- fill results + MD_b32 result = !!state; + if (result) + { + MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; + win32_it->state = state; + win32_it->first = 1; + MD_MemoryCopy(&win32_it->find_data, &find_data, sizeof(find_data)); + } + return(result); +} + +static MD_FileInfo +MD_WIN32_FileIterNext(MD_Arena *arena, MD_FileIter *it) +{ + //- get low-level file info for this step + MD_b32 good = 0; + + MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; + WIN32_FIND_DATAW *find_data = &win32_it->find_data; + if (win32_it->first) + { + win32_it->first = 0; + good = 1; + } + else + { + good = FindNextFileW(win32_it->state, find_data); + } + + //- convert to MD_FileInfo + MD_FileInfo result = {0}; + if (good) + { + if (find_data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + result.flags |= MD_FileFlag_Directory; + } + MD_u16 *filename_base = (MD_u16*)find_data->cFileName; + MD_u16 *ptr = filename_base; + for (;*ptr != 0; ptr += 1); + MD_String16 filename16 = {0}; + filename16.str = filename_base; + filename16.size = (MD_u64)(ptr - filename_base); + result.filename = MD_S8FromS16(arena, filename16); + result.file_size = ((((MD_u64)find_data->nFileSizeHigh) << 32) | + ((MD_u64)find_data->nFileSizeLow)); + } + return(result); +} + +static void +MD_WIN32_FileIterEnd(MD_FileIter *it) +{ + MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; + FindClose(win32_it->state); +} + +#endif + +//- win32 "low level memory" +#if MD_DEFAULT_MEMORY && MD_OS_WINDOWS + +#if !defined(MD_IMPL_Reserve) +# define MD_IMPL_Reserve MD_WIN32_Reserve +#endif +#if !defined(MD_IMPL_Commit) +# define MD_IMPL_Commit MD_WIN32_Commit +#endif +#if !defined(MD_IMPL_Decommit) +# define MD_IMPL_Decommit MD_WIN32_Decommit +#endif +#if !defined(MD_IMPL_Release) +# define MD_IMPL_Release MD_WIN32_Release +#endif + +static void* +MD_WIN32_Reserve(MD_u64 size) +{ + void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); + return(result); +} + +static MD_b32 +MD_WIN32_Commit(void *ptr, MD_u64 size) +{ + MD_b32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); + return(result); +} + +static void +MD_WIN32_Decommit(void *ptr, MD_u64 size) +{ + VirtualFree(ptr, size, MEM_DECOMMIT); +} + +static void +MD_WIN32_Release(void *ptr, MD_u64 size) +{ + (void)size; + VirtualFree(ptr, 0, MEM_RELEASE); +} + +#endif + +//~///////////////////////////////////////////////////////////////////////////// +////////////////////////// Linux Implementation //////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +//- linux headers +#if (MD_DEFAULT_FILE_ITER || MD_DEFAULT_MEMORY) && (MD_OS_LINUX || MD_OS_MAC) +# include +# include +# include +# include +# include +# include +// NOTE(mal): To get these constants I need to #define _GNU_SOURCE, +// which invites non-POSIX behavior I'd rather avoid +# ifndef O_PATH +# define O_PATH 010000000 +# endif +# ifndef AT_NO_AUTOMOUNT +# define AT_NO_AUTOMOUNT 0x800 +# endif +# ifndef AT_SYMLINK_NOFOLLOW +# define AT_SYMLINK_NOFOLLOW 0x100 +# endif +#endif + +//- linux "file iteration" +#if MD_DEFAULT_FILE_ITER && MD_OS_LINUX + +#if !defined(MD_IMPL_FileIterIncrement) +# define MD_IMPL_FileIterIncrement MD_LINUX_FileIterIncrement +#endif + +typedef struct MD_LINUX_FileIter MD_LINUX_FileIter; +struct MD_LINUX_FileIter +{ + int dir_fd; + DIR *dir; +}; +MD_StaticAssert(sizeof(MD_LINUX_FileIter) <= sizeof(MD_FileIter), file_iter_size_check); + +static MD_b32 +MD_LINUX_FileIterIncrement(MD_Arena *arena, MD_FileIter *opaque_it, MD_String8 path, + MD_FileInfo *out_info) +{ + MD_b32 result = 0; + + MD_LINUX_FileIter *it = (MD_LINUX_FileIter *)opaque_it; + if(it->dir == 0) + { + it->dir = opendir((char*)path.str); + it->dir_fd = open((char *)path.str, O_PATH|O_CLOEXEC); + } + + if(it->dir != 0 && it->dir_fd != -1) + { + struct dirent *dir_entry = readdir(it->dir); + if(dir_entry) + { + out_info->filename = MD_S8Fmt(arena, "%s", dir_entry->d_name); + out_info->flags = 0; + + struct stat st; + if(fstatat(it->dir_fd, dir_entry->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) == 0) + { + if((st.st_mode & S_IFMT) == S_IFDIR) + { + out_info->flags |= MD_FileFlag_Directory; + } + out_info->file_size = st.st_size; + } + result = 1; + } + } + + if(result == 0) + { + if(it->dir != 0) + { + closedir(it->dir); + it->dir = 0; + } + if(it->dir_fd != -1) + { + close(it->dir_fd); + it->dir_fd = -1; + } + } + + return result; +} + +#endif + +//- linux "low level memory" +#if MD_DEFAULT_MEMORY && (MD_OS_LINUX || MD_OS_MAC) + +#if !defined(MD_IMPL_Reserve) +# define MD_IMPL_Reserve MD_LINUX_Reserve +#endif +#if !defined(MD_IMPL_Commit) +# define MD_IMPL_Commit MD_LINUX_Commit +#endif +#if !defined(MD_IMPL_Decommit) +# define MD_IMPL_Decommit MD_LINUX_Decommit +#endif +#if !defined(MD_IMPL_Release) +# define MD_IMPL_Release MD_LINUX_Release +#endif + +static void* +MD_LINUX_Reserve(MD_u64 size) +{ + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, (off_t)0); + if(result == (void *)-1) + { + result = 0; + } + return(result); +} + +static MD_b32 +MD_LINUX_Commit(void *ptr, MD_u64 size) +{ + MD_b32 result = (mprotect(ptr, size, PROT_READ|PROT_WRITE) == 0); + return(result); +} + +static void +MD_LINUX_Decommit(void *ptr, MD_u64 size) +{ + mprotect(ptr, size, PROT_NONE); + madvise(ptr, size, MADV_DONTNEED); +} + +static void +MD_LINUX_Release(void *ptr, MD_u64 size) +{ + munmap(ptr, size); +} + +#endif + +//~///////////////////////////////////////////////////////////////////////////// +///////////// MD Arena From Reserve/Commit/Decommit/Release //////////////////// +//////////////////////////////////////////////////////////////////////////////// + +#if MD_DEFAULT_ARENA + +#if !defined(MD_DEFAULT_ARENA_RES_SIZE) +# define MD_DEFAULT_ARENA_RES_SIZE (64 << 20) +#endif +#if !defined(MD_DEFAULT_ARENA_CMT_SIZE) +# define MD_DEFAULT_ARENA_CMT_SIZE (64 << 10) +#endif + +#define MD_DEFAULT_ARENA_VERY_BIG (MD_DEFAULT_ARENA_RES_SIZE - MD_IMPL_ArenaMinPos)/2 + +//- "low level memory" implementation check +#if !defined(MD_IMPL_Reserve) +# error Missing implementation for MD_IMPL_Reserve +#endif +#if !defined(MD_IMPL_Commit) +# error Missing implementation for MD_IMPL_Commit +#endif +#if !defined(MD_IMPL_Decommit) +# error Missing implementation for MD_IMPL_Decommit +#endif +#if !defined(MD_IMPL_Release) +# error Missing implementation for MD_IMPL_Release +#endif + +#define MD_IMPL_ArenaMinPos 64 +MD_StaticAssert(sizeof(MD_ArenaDefault) <= MD_IMPL_ArenaMinPos, arena_def_size_check); + +#define MD_IMPL_ArenaAlloc MD_ArenaDefaultAlloc +#define MD_IMPL_ArenaRelease MD_ArenaDefaultRelease +#define MD_IMPL_ArenaGetPos MD_ArenaDefaultGetPos +#define MD_IMPL_ArenaPush MD_ArenaDefaultPush +#define MD_IMPL_ArenaPopTo MD_ArenaDefaultPopTo +#define MD_IMPL_ArenaSetAutoAlign MD_ArenaDefaultSetAutoAlign + +static MD_ArenaDefault* +MD_ArenaDefaultAlloc__Size(MD_u64 cmt, MD_u64 res) +{ + MD_Assert(MD_IMPL_ArenaMinPos < cmt && cmt <= res); + MD_u64 cmt_clamped = MD_ClampTop(cmt, res); + MD_ArenaDefault *result = 0; + void *mem = MD_IMPL_Reserve(res); + if (MD_IMPL_Commit(mem, cmt_clamped)) + { + result = (MD_ArenaDefault*)mem; + result->prev = 0; + result->current = result; + result->base_pos = 0; + result->pos = MD_IMPL_ArenaMinPos; + result->cmt = cmt_clamped; + result->cap = res; + result->align = 8; + } + return(result); +} + +static MD_ArenaDefault* +MD_ArenaDefaultAlloc(void) +{ + MD_ArenaDefault *result = MD_ArenaDefaultAlloc__Size(MD_DEFAULT_ARENA_CMT_SIZE, + MD_DEFAULT_ARENA_RES_SIZE); + return(result); +} + +static void +MD_ArenaDefaultRelease(MD_ArenaDefault *arena) +{ + for (MD_ArenaDefault *node = arena->current, *prev = 0; + node != 0; + node = prev) + { + prev = node->prev; + MD_IMPL_Release(node, node->cap); + } +} + +static MD_u64 +MD_ArenaDefaultGetPos(MD_ArenaDefault *arena) +{ + MD_ArenaDefault *current = arena->current; + MD_u64 result = current->base_pos + current->pos; + return(result); +} + +static void* +MD_ArenaDefaultPush(MD_ArenaDefault *arena, MD_u64 size) +{ + // try to be fast! + MD_ArenaDefault *current = arena->current; + MD_u64 align = arena->align; + MD_u64 pos = current->pos; + MD_u64 pos_aligned = MD_AlignPow2(pos, align); + MD_u64 new_pos = pos_aligned + size; + void *result = (MD_u8*)current + pos_aligned; + current->pos = new_pos; + + // if it's not going to work do the slow path + if (new_pos > current->cmt) + { + result = 0; + current->pos = pos; + + // new chunk if necessary + if (new_pos > current->cap) + { + MD_ArenaDefault *new_arena = 0; + if (size > MD_DEFAULT_ARENA_VERY_BIG) + { + MD_u64 big_size_unrounded = size + MD_IMPL_ArenaMinPos; + MD_u64 big_size = MD_AlignPow2(big_size_unrounded, (4 << 10)); + new_arena = MD_ArenaDefaultAlloc__Size(big_size, big_size); + } + else + { + new_arena = MD_ArenaDefaultAlloc(); + } + + // link in new chunk & recompute new_pos + if (new_arena != 0) + { + new_arena->base_pos = current->base_pos + current->cap; + new_arena->prev = current; + current = new_arena; + pos_aligned = current->pos; + new_pos = pos_aligned + size; + } + } + + // move ahead if the current chunk has enough reserve + if (new_pos <= current->cap) + { + + // extend commit if necessary + if (new_pos > current->cmt) + { + MD_u64 new_cmt_unclamped = MD_AlignPow2(new_pos, MD_DEFAULT_ARENA_CMT_SIZE); + MD_u64 new_cmt = MD_ClampTop(new_cmt_unclamped, current->cap); + MD_u64 cmt_size = new_cmt - current->cmt; + if (MD_IMPL_Commit((MD_u8*)current + current->cmt, cmt_size)) + { + current->cmt = new_cmt; + } + } + + // move ahead if the current chunk has enough commit + if (new_pos <= current->cmt) + { + result = (MD_u8*)current + current->pos; + current->pos = new_pos; + } + } + } + + return(result); +} + +static void +MD_ArenaDefaultPopTo(MD_ArenaDefault *arena, MD_u64 pos) +{ + // pop chunks in the chain + MD_u64 pos_clamped = MD_ClampBot(MD_IMPL_ArenaMinPos, pos); + (void)pos_clamped; + { + MD_ArenaDefault *node = arena->current; + for (MD_ArenaDefault *prev = 0; + node != 0 && node->base_pos >= pos; + node = prev) + { + prev = node->prev; + MD_IMPL_Release(node, node->cap); + } + arena->current = node; + } + + // reset the pos of the current + { + MD_ArenaDefault *current = arena->current; + MD_u64 local_pos_unclamped = pos - current->base_pos; + MD_u64 local_pos = MD_ClampBot(local_pos_unclamped, MD_IMPL_ArenaMinPos); + current->pos = local_pos; + } +} + +static void +MD_ArenaDefaultSetAutoAlign(MD_ArenaDefault *arena, MD_u64 align) +{ + arena->align = align; +} + +static void +MD_ArenaDefaultAbsorb(MD_ArenaDefault *arena, MD_ArenaDefault *sub_arena) +{ + MD_ArenaDefault *current = arena->current; + MD_u64 base_pos_shift = current->base_pos + current->cap; + for (MD_ArenaDefault *node = sub_arena->current; + node != 0; + node = node->prev) + { + node->base_pos += base_pos_shift; + } + sub_arena->prev = arena->current; + arena->current = sub_arena->current; +} + +#endif + +//- "arena" implementation checks +#if !defined(MD_IMPL_ArenaAlloc) +# error Missing implementation for MD_IMPL_ArenaAlloc +#endif +#if !defined(MD_IMPL_ArenaRelease) +# error Missing implementation for MD_IMPL_ArenaRelease +#endif +#if !defined(MD_IMPL_ArenaGetPos) +# error Missing implementation for MD_IMPL_ArenaGetPos +#endif +#if !defined(MD_IMPL_ArenaPush) +# error Missing implementation for MD_IMPL_ArenaPush +#endif +#if !defined(MD_IMPL_ArenaPopTo) +# error Missing implementation for MD_IMPL_ArenaPopTo +#endif +#if !defined(MD_IMPL_ArenaSetAutoAlign) +# error Missing implementation for MD_IMPL_ArenaSetAutoAlign +#endif +#if !defined(MD_IMPL_ArenaMinPos) +# error Missing implementation for MD_IMPL_ArenaMinPos +#endif + +//~///////////////////////////////////////////////////////////////////////////// +///////////////////////////// MD Scratch Pool ////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +#if MD_DEFAULT_SCRATCH + +#if !defined(MD_IMPL_ScratchCount) +# define MD_IMPL_ScratchCount 2llu +#endif + +#if !defined(MD_IMPL_GetScratch) +# define MD_IMPL_GetScratch MD_GetScratchDefault +#endif + +MD_THREAD_LOCAL MD_Arena *md_thread_scratch_pool[MD_IMPL_ScratchCount] = {0, 0}; + +static MD_Arena* +MD_GetScratchDefault(MD_Arena **conflicts, MD_u64 count) +{ + MD_Arena **scratch_pool = md_thread_scratch_pool; + if (scratch_pool[0] == 0) + { + MD_Arena **arena_ptr = scratch_pool; + for (MD_u64 i = 0; i < MD_IMPL_ScratchCount; i += 1, arena_ptr += 1) + { + *arena_ptr = MD_ArenaAlloc(); + } + } + MD_Arena *result = 0; + MD_Arena **arena_ptr = scratch_pool; + for (MD_u64 i = 0; i < MD_IMPL_ScratchCount; i += 1, arena_ptr += 1) + { + MD_Arena *arena = *arena_ptr; + MD_Arena **conflict_ptr = conflicts; + for (MD_u32 j = 0; j < count; j += 1, conflict_ptr += 1) + { + if (arena == *conflict_ptr) + { + arena = 0; + break; + } + } + if (arena != 0) + { + result = arena; + break; + } + } + return(result); +} + +#endif + +//~///////////////////////////////////////////////////////////////////////////// +//////////////////////// MD Library Implementation ///////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +#if MD_DEFAULT_SPRINTF +#define STB_SPRINTF_IMPLEMENTATION +#define STB_SPRINTF_DECORATE(name) md_stbsp_##name +#include "md_stb_sprintf.h" +#endif + + +//~ Nil Node Definition + +static MD_Node _md_nil_node = +{ + &_md_nil_node, // next + &_md_nil_node, // prev + &_md_nil_node, // parent + &_md_nil_node, // first_child + &_md_nil_node, // last_child + &_md_nil_node, // first_tag + &_md_nil_node, // last_tag + MD_NodeKind_Nil, // kind + 0, // flags + MD_ZERO_STRUCT, // string + MD_ZERO_STRUCT, // raw_string + 0, // at + &_md_nil_node, // ref_target + MD_ZERO_STRUCT, // prev_comment + MD_ZERO_STRUCT, // next_comment +}; + +//~ Arena Functions + +MD_FUNCTION MD_Arena* +MD_ArenaAlloc(void) +{ + return(MD_IMPL_ArenaAlloc()); +} + +MD_FUNCTION void +MD_ArenaRelease(MD_Arena *arena) +{ + MD_IMPL_ArenaRelease(arena); +} + +MD_FUNCTION void* +MD_ArenaPush(MD_Arena *arena, MD_u64 size) +{ + void *result = MD_IMPL_ArenaPush(arena, size); + return(result); +} + +MD_FUNCTION void +MD_ArenaPutBack(MD_Arena *arena, MD_u64 size) +{ + MD_u64 pos = MD_IMPL_ArenaGetPos(arena); + MD_u64 new_pos = pos - size; + MD_u64 new_pos_clamped = MD_ClampBot(MD_IMPL_ArenaMinPos, new_pos); + MD_IMPL_ArenaPopTo(arena, new_pos_clamped); +} + +MD_FUNCTION void +MD_ArenaSetAlign(MD_Arena *arena, MD_u64 boundary) +{ + MD_IMPL_ArenaSetAutoAlign(arena, boundary); +} + +MD_FUNCTION void +MD_ArenaPushAlign(MD_Arena *arena, MD_u64 boundary) +{ + MD_u64 pos = MD_IMPL_ArenaGetPos(arena); + MD_u64 align_m1 = boundary - 1; + MD_u64 new_pos_aligned = (pos + align_m1)&(~align_m1); + if (new_pos_aligned > pos) + { + MD_u64 amt = new_pos_aligned - pos; + MD_MemoryZero(MD_IMPL_ArenaPush(arena, amt), amt); + } +} + +MD_FUNCTION void +MD_ArenaClear(MD_Arena *arena) +{ + MD_IMPL_ArenaPopTo(arena, MD_IMPL_ArenaMinPos); +} + +MD_FUNCTION MD_ArenaTemp +MD_ArenaBeginTemp(MD_Arena *arena) +{ + MD_ArenaTemp result; + result.arena = arena; + result.pos = MD_IMPL_ArenaGetPos(arena); + return(result); +} + +MD_FUNCTION void +MD_ArenaEndTemp(MD_ArenaTemp temp) +{ + MD_IMPL_ArenaPopTo(temp.arena, temp.pos); +} + +//~ Arena Scratch Pool + +MD_FUNCTION MD_ArenaTemp +MD_GetScratch(MD_Arena **conflicts, MD_u64 count) +{ + MD_Arena *arena = MD_IMPL_GetScratch(conflicts, count); + MD_ArenaTemp result = MD_ZERO_STRUCT; + if (arena != 0) + { + result = MD_ArenaBeginTemp(arena); + } + return(result); +} + +//~ Characters + +MD_FUNCTION MD_b32 +MD_CharIsAlpha(MD_u8 c) +{ + return MD_CharIsAlphaUpper(c) || MD_CharIsAlphaLower(c); +} + +MD_FUNCTION MD_b32 +MD_CharIsAlphaUpper(MD_u8 c) +{ + return c >= 'A' && c <= 'Z'; +} + +MD_FUNCTION MD_b32 +MD_CharIsAlphaLower(MD_u8 c) +{ + return c >= 'a' && c <= 'z'; +} + +MD_FUNCTION MD_b32 +MD_CharIsDigit(MD_u8 c) +{ + return (c >= '0' && c <= '9'); +} + +MD_FUNCTION MD_b32 +MD_CharIsUnreservedSymbol(MD_u8 c) +{ + return (c == '~' || c == '!' || c == '$' || c == '%' || c == '^' || + c == '&' || c == '*' || c == '-' || c == '=' || c == '+' || + c == '<' || c == '.' || c == '>' || c == '/' || c == '?' || + c == '|'); +} + +MD_FUNCTION MD_b32 +MD_CharIsReservedSymbol(MD_u8 c) +{ + return (c == '{' || c == '}' || c == '(' || c == ')' || c == '\\' || + c == '[' || c == ']' || c == '#' || c == ',' || c == ';' || + c == ':' || c == '@'); +} + +MD_FUNCTION MD_b32 +MD_CharIsSpace(MD_u8 c) +{ + return c == ' ' || c == '\r' || c == '\t' || c == '\f' || c == '\v' || c == '\n'; +} + +MD_FUNCTION MD_u8 +MD_CharToUpper(MD_u8 c) +{ + return (c >= 'a' && c <= 'z') ? ('A' + (c - 'a')) : c; +} + +MD_FUNCTION MD_u8 +MD_CharToLower(MD_u8 c) +{ + return (c >= 'A' && c <= 'Z') ? ('a' + (c - 'A')) : c; +} + +MD_FUNCTION MD_u8 +MD_CharToForwardSlash(MD_u8 c) +{ + return (c == '\\' ? '/' : c); +} + +//~ Strings + +MD_FUNCTION MD_u64 +MD_CalculateCStringLength(char *cstr) +{ + MD_u64 i = 0; + for(; cstr[i]; i += 1); + return i; +} + +MD_FUNCTION MD_String8 +MD_S8(MD_u8 *str, MD_u64 size) +{ + MD_String8 string; + string.str = str; + string.size = size; + return string; +} + +MD_FUNCTION MD_String8 +MD_S8Range(MD_u8 *first, MD_u8 *opl) +{ + MD_String8 string; + string.str = first; + string.size = (MD_u64)(opl - first); + return string; +} + +MD_FUNCTION MD_String8 +MD_S8Substring(MD_String8 str, MD_u64 min, MD_u64 max) +{ + if(max > str.size) + { + max = str.size; + } + if(min > str.size) + { + min = str.size; + } + if(min > max) + { + MD_u64 swap = min; + min = max; + max = swap; + } + str.size = max - min; + str.str += min; + return str; +} + +MD_FUNCTION MD_String8 +MD_S8Skip(MD_String8 str, MD_u64 min) +{ + return MD_S8Substring(str, min, str.size); +} + +MD_FUNCTION MD_String8 +MD_S8Chop(MD_String8 str, MD_u64 nmax) +{ + return MD_S8Substring(str, 0, str.size - nmax); +} + +MD_FUNCTION MD_String8 +MD_S8Prefix(MD_String8 str, MD_u64 size) +{ + return MD_S8Substring(str, 0, size); +} + +MD_FUNCTION MD_String8 +MD_S8Suffix(MD_String8 str, MD_u64 size) +{ + return MD_S8Substring(str, str.size - size, str.size); +} + +MD_FUNCTION MD_b32 +MD_S8Match(MD_String8 a, MD_String8 b, MD_MatchFlags flags) +{ + int result = 0; + if(a.size == b.size || flags & MD_StringMatchFlag_RightSideSloppy) + { + result = 1; + for(MD_u64 i = 0; i < a.size && i < b.size; i += 1) + { + MD_b32 match = (a.str[i] == b.str[i]); + if(flags & MD_StringMatchFlag_CaseInsensitive) + { + match |= (MD_CharToLower(a.str[i]) == MD_CharToLower(b.str[i])); + } + if(flags & MD_StringMatchFlag_SlashInsensitive) + { + match |= (MD_CharToForwardSlash(a.str[i]) == MD_CharToForwardSlash(b.str[i])); + } + if(match == 0) + { + result = 0; + break; + } + } + } + return result; +} + +MD_FUNCTION MD_u64 +MD_S8FindSubstring(MD_String8 str, MD_String8 substring, MD_u64 start_pos, MD_MatchFlags flags) +{ + MD_b32 found = 0; + MD_u64 found_idx = str.size; + for(MD_u64 i = start_pos; i < str.size; i += 1) + { + if(i + substring.size <= str.size) + { + MD_String8 substr_from_str = MD_S8Substring(str, i, i+substring.size); + if(MD_S8Match(substr_from_str, substring, flags)) + { + found_idx = i; + found = 1; + if(!(flags & MD_MatchFlag_FindLast)) + { + break; + } + } + } + } + return found_idx; +} + +MD_FUNCTION MD_String8 +MD_S8Copy(MD_Arena *arena, MD_String8 string) +{ + MD_String8 res; + res.size = string.size; + res.str = MD_PushArray(arena, MD_u8, string.size + 1); + MD_MemoryCopy(res.str, string.str, string.size); + res.str[string.size] = 0; + return(res); +} + +MD_FUNCTION MD_String8 +MD_S8FmtV(MD_Arena *arena, char *fmt, va_list args) +{ + MD_String8 result = MD_ZERO_STRUCT; + va_list args2; + va_copy(args2, args); + MD_u64 needed_bytes = MD_IMPL_Vsnprintf(0, 0, fmt, args)+1; + result.str = MD_PushArray(arena, MD_u8, needed_bytes); + result.size = needed_bytes - 1; + result.str[needed_bytes-1] = 0; + MD_IMPL_Vsnprintf((char*)result.str, (int)needed_bytes, fmt, args2); + return result; +} + +MD_FUNCTION MD_String8 +MD_S8Fmt(MD_Arena *arena, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + MD_String8 result = MD_S8FmtV(arena, fmt, args); + va_end(args); + return result; +} + +MD_FUNCTION void +MD_S8ListPush(MD_Arena *arena, MD_String8List *list, MD_String8 string) +{ + MD_String8Node *node = MD_PushArrayZero(arena, MD_String8Node, 1); + node->string = string; + + MD_QueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size += string.size; +} + +MD_FUNCTION void +MD_S8ListPushFmt(MD_Arena *arena, MD_String8List *list, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + MD_String8 string = MD_S8FmtV(arena, fmt, args); + va_end(args); + MD_S8ListPush(arena, list, string); +} + +MD_FUNCTION void +MD_S8ListConcat(MD_String8List *list, MD_String8List *to_push) +{ + if(to_push->first) + { + list->node_count += to_push->node_count; + list->total_size += to_push->total_size; + + if(list->last == 0) + { + *list = *to_push; + } + else + { + list->last->next = to_push->first; + list->last = to_push->last; + } + } + MD_MemoryZeroStruct(to_push); +} + +MD_FUNCTION MD_String8List +MD_S8Split(MD_Arena *arena, MD_String8 string, int splitter_count, + MD_String8 *splitters) +{ + MD_String8List list = MD_ZERO_STRUCT; + + MD_u64 split_start = 0; + for(MD_u64 i = 0; i < string.size; i += 1) + { + MD_b32 was_split = 0; + for(int split_idx = 0; split_idx < splitter_count; split_idx += 1) + { + MD_b32 match = 0; + if(i + splitters[split_idx].size <= string.size) + { + match = 1; + for(MD_u64 split_i = 0; split_i < splitters[split_idx].size && i + split_i < string.size; split_i += 1) + { + if(splitters[split_idx].str[split_i] != string.str[i + split_i]) + { + match = 0; + break; + } + } + } + if(match) + { + MD_String8 split_string = MD_S8(string.str + split_start, i - split_start); + MD_S8ListPush(arena, &list, split_string); + split_start = i + splitters[split_idx].size; + i += splitters[split_idx].size - 1; + was_split = 1; + break; + } + } + + if(was_split == 0 && i == string.size - 1) + { + MD_String8 split_string = MD_S8(string.str + split_start, i+1 - split_start); + MD_S8ListPush(arena, &list, split_string); + break; + } + } + + return list; +} + +MD_FUNCTION MD_String8 +MD_S8ListJoin(MD_Arena *arena, MD_String8List list, MD_StringJoin *join_ptr) +{ + // setup join parameters + MD_StringJoin join = MD_ZERO_STRUCT; + if (join_ptr != 0) + { + MD_MemoryCopy(&join, join_ptr, sizeof(join)); + } + + // calculate size & allocate + MD_u64 sep_count = 0; + if (list.node_count > 1) + { + sep_count = list.node_count - 1; + } + MD_String8 result = MD_ZERO_STRUCT; + result.size = (list.total_size + join.pre.size + + sep_count*join.mid.size + join.post.size); + result.str = MD_PushArrayZero(arena, MD_u8, result.size); + + // fill + MD_u8 *ptr = result.str; + MD_MemoryCopy(ptr, join.pre.str, join.pre.size); + ptr += join.pre.size; + for(MD_String8Node *node = list.first; node; node = node->next) + { + MD_MemoryCopy(ptr, node->string.str, node->string.size); + ptr += node->string.size; + if (node != list.last) + { + MD_MemoryCopy(ptr, join.mid.str, join.mid.size); + ptr += join.mid.size; + } + } + MD_MemoryCopy(ptr, join.post.str, join.post.size); + ptr += join.post.size; + + return(result); +} + +MD_FUNCTION MD_String8 +MD_S8ListJoinMid(MD_Arena *arena, MD_String8List list, + MD_String8 mid_separator) +{ + MD_StringJoin join = MD_ZERO_STRUCT; + join.pre = MD_S8Lit(""); + join.post = MD_S8Lit(""); + join.mid = mid_separator; + MD_String8 result = MD_S8ListJoin(arena, list, &join); + return result; +} + +MD_FUNCTION MD_String8 +MD_S8Stylize(MD_Arena *arena, MD_String8 string, MD_IdentifierStyle word_style, + MD_String8 separator) +{ + MD_String8 result = MD_ZERO_STRUCT; + + MD_String8List words = MD_ZERO_STRUCT; + + MD_b32 break_on_uppercase = 0; + { + break_on_uppercase = 1; + for(MD_u64 i = 0; i < string.size; i += 1) + { + if(!MD_CharIsAlpha(string.str[i]) && !MD_CharIsDigit(string.str[i])) + { + break_on_uppercase = 0; + break; + } + } + } + + MD_b32 making_word = 0; + MD_String8 word = MD_ZERO_STRUCT; + + for(MD_u64 i = 0; i < string.size;) + { + if(making_word) + { + if((break_on_uppercase && MD_CharIsAlphaUpper(string.str[i])) || + string.str[i] == '_' || MD_CharIsSpace(string.str[i]) || + i == string.size - 1) + { + if(i == string.size - 1) + { + word.size += 1; + } + making_word = 0; + MD_S8ListPush(arena, &words, word); + } + else + { + word.size += 1; + i += 1; + } + } + else + { + if(MD_CharIsAlpha(string.str[i])) + { + making_word = 1; + word.str = string.str + i; + word.size = 1; + } + i += 1; + } + } + + result.size = words.total_size; + if(words.node_count > 1) + { + result.size += separator.size*(words.node_count-1); + } + result.str = MD_PushArrayZero(arena, MD_u8, result.size); + + { + MD_u64 write_pos = 0; + for(MD_String8Node *node = words.first; node; node = node->next) + { + + // NOTE(rjf): Write word string to result. + { + MD_MemoryCopy(result.str + write_pos, node->string.str, node->string.size); + + // NOTE(rjf): Transform string based on word style. + switch(word_style) + { + case MD_IdentifierStyle_UpperCamelCase: + { + result.str[write_pos] = MD_CharToUpper(result.str[write_pos]); + for(MD_u64 i = write_pos+1; i < write_pos + node->string.size; i += 1) + { + result.str[i] = MD_CharToLower(result.str[i]); + } + }break; + + case MD_IdentifierStyle_LowerCamelCase: + { + MD_b32 is_first = (node == words.first); + result.str[write_pos] = (is_first ? + MD_CharToLower(result.str[write_pos]) : + MD_CharToUpper(result.str[write_pos])); + for(MD_u64 i = write_pos+1; i < write_pos + node->string.size; i += 1) + { + result.str[i] = MD_CharToLower(result.str[i]); + } + }break; + + case MD_IdentifierStyle_UpperCase: + { + for(MD_u64 i = write_pos; i < write_pos + node->string.size; i += 1) + { + result.str[i] = MD_CharToUpper(result.str[i]); + } + }break; + + case MD_IdentifierStyle_LowerCase: + { + for(MD_u64 i = write_pos; i < write_pos + node->string.size; i += 1) + { + result.str[i] = MD_CharToLower(result.str[i]); + } + }break; + + default: break; + } + + write_pos += node->string.size; + } + + if(node->next) + { + MD_MemoryCopy(result.str + write_pos, separator.str, separator.size); + write_pos += separator.size; + } + } + } + + return result; +} + +//~ Unicode Conversions + +MD_GLOBAL MD_u8 md_utf8_class[32] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5, +}; + +MD_FUNCTION MD_DecodedCodepoint +MD_DecodeCodepointFromUtf8(MD_u8 *str, MD_u64 max) +{ +#define MD_bitmask1 0x01 +#define MD_bitmask2 0x03 +#define MD_bitmask3 0x07 +#define MD_bitmask4 0x0F +#define MD_bitmask5 0x1F +#define MD_bitmask6 0x3F +#define MD_bitmask7 0x7F +#define MD_bitmask8 0xFF +#define MD_bitmask9 0x01FF +#define MD_bitmask10 0x03FF + + MD_DecodedCodepoint result = {~((MD_u32)0), 1}; + MD_u8 byte = str[0]; + MD_u8 byte_class = md_utf8_class[byte >> 3]; + switch (byte_class) + { + case 1: + { + result.codepoint = byte; + }break; + + case 2: + { + if (2 <= max) + { + MD_u8 cont_byte = str[1]; + if (md_utf8_class[cont_byte >> 3] == 0) + { + result.codepoint = (byte & MD_bitmask5) << 6; + result.codepoint |= (cont_byte & MD_bitmask6); + result.advance = 2; + } + } + }break; + + case 3: + { + if (3 <= max) + { + MD_u8 cont_byte[2] = {0}; + cont_byte[0] = str[1]; + cont_byte[1] = str[2]; + if (md_utf8_class[cont_byte[0] >> 3] == 0 && + md_utf8_class[cont_byte[1] >> 3] == 0) + { + result.codepoint = (byte & MD_bitmask4) << 12; + result.codepoint |= ((cont_byte[0] & MD_bitmask6) << 6); + result.codepoint |= (cont_byte[1] & MD_bitmask6); + result.advance = 3; + } + } + }break; + + case 4: + { + if (4 <= max) + { + MD_u8 cont_byte[3] = {0}; + cont_byte[0] = str[1]; + cont_byte[1] = str[2]; + cont_byte[2] = str[3]; + if (md_utf8_class[cont_byte[0] >> 3] == 0 && + md_utf8_class[cont_byte[1] >> 3] == 0 && + md_utf8_class[cont_byte[2] >> 3] == 0) + { + result.codepoint = (byte & MD_bitmask3) << 18; + result.codepoint |= ((cont_byte[0] & MD_bitmask6) << 12); + result.codepoint |= ((cont_byte[1] & MD_bitmask6) << 6); + result.codepoint |= (cont_byte[2] & MD_bitmask6); + result.advance = 4; + } + } + }break; + } + + return(result); +} + +MD_FUNCTION MD_DecodedCodepoint +MD_DecodeCodepointFromUtf16(MD_u16 *out, MD_u64 max) +{ + MD_DecodedCodepoint result = {~((MD_u32)0), 1}; + result.codepoint = out[0]; + result.advance = 1; + if (1 < max && 0xD800 <= out[0] && out[0] < 0xDC00 && 0xDC00 <= out[1] && out[1] < 0xE000) + { + result.codepoint = ((out[0] - 0xD800) << 10) | (out[1] - 0xDC00); + result.advance = 2; + } + return(result); +} + +MD_FUNCTION MD_u32 +MD_Utf8FromCodepoint(MD_u8 *out, MD_u32 codepoint) +{ +#define MD_bit8 0x80 + MD_u32 advance = 0; + if (codepoint <= 0x7F) + { + out[0] = (MD_u8)codepoint; + advance = 1; + } + else if (codepoint <= 0x7FF) + { + out[0] = (MD_bitmask2 << 6) | ((codepoint >> 6) & MD_bitmask5); + out[1] = MD_bit8 | (codepoint & MD_bitmask6); + advance = 2; + } + else if (codepoint <= 0xFFFF) + { + out[0] = (MD_bitmask3 << 5) | ((codepoint >> 12) & MD_bitmask4); + out[1] = MD_bit8 | ((codepoint >> 6) & MD_bitmask6); + out[2] = MD_bit8 | ( codepoint & MD_bitmask6); + advance = 3; + } + else if (codepoint <= 0x10FFFF) + { + out[0] = (MD_bitmask4 << 3) | ((codepoint >> 18) & MD_bitmask3); + out[1] = MD_bit8 | ((codepoint >> 12) & MD_bitmask6); + out[2] = MD_bit8 | ((codepoint >> 6) & MD_bitmask6); + out[3] = MD_bit8 | ( codepoint & MD_bitmask6); + advance = 4; + } + else + { + out[0] = '?'; + advance = 1; + } + return(advance); +} + +MD_FUNCTION MD_u32 +MD_Utf16FromCodepoint(MD_u16 *out, MD_u32 codepoint) +{ + MD_u32 advance = 1; + if (codepoint == ~((MD_u32)0)) + { + out[0] = (MD_u16)'?'; + } + else if (codepoint < 0x10000) + { + out[0] = (MD_u16)codepoint; + } + else + { + MD_u64 v = codepoint - 0x10000; + out[0] = (MD_u16)(0xD800 + (v >> 10)); + out[1] = 0xDC00 + (v & MD_bitmask10); + advance = 2; + } + return(advance); +} + +MD_FUNCTION MD_String8 +MD_S8FromS16(MD_Arena *arena, MD_String16 in) +{ + MD_u64 cap = in.size*3; + MD_u8 *str = MD_PushArrayZero(arena, MD_u8, cap + 1); + MD_u16 *ptr = in.str; + MD_u16 *opl = ptr + in.size; + MD_u64 size = 0; + MD_DecodedCodepoint consume; + for (;ptr < opl;) + { + consume = MD_DecodeCodepointFromUtf16(ptr, opl - ptr); + ptr += consume.advance; + size += MD_Utf8FromCodepoint(str + size, consume.codepoint); + } + str[size] = 0; + MD_ArenaPutBack(arena, cap - size); // := ((cap + 1) - (size + 1)) + return(MD_S8(str, size)); +} + +MD_FUNCTION MD_String16 +MD_S16FromS8(MD_Arena *arena, MD_String8 in) +{ + MD_u64 cap = in.size*2; + MD_u16 *str = MD_PushArrayZero(arena, MD_u16, cap + 1); + MD_u8 *ptr = in.str; + MD_u8 *opl = ptr + in.size; + MD_u64 size = 0; + MD_DecodedCodepoint consume; + for (;ptr < opl;) + { + consume = MD_DecodeCodepointFromUtf8(ptr, opl - ptr); + ptr += consume.advance; + size += MD_Utf16FromCodepoint(str + size, consume.codepoint); + } + str[size] = 0; + MD_ArenaPutBack(arena, 2*(cap - size)); // := 2*((cap + 1) - (size + 1)) + MD_String16 result = {0}; + result.str = str; + result.size = size; + return(result); +} + +MD_FUNCTION MD_String8 +MD_S8FromS32(MD_Arena *arena, MD_String32 in) +{ + MD_u64 cap = in.size*4; + MD_u8 *str = MD_PushArrayZero(arena, MD_u8, cap + 1); + MD_u32 *ptr = in.str; + MD_u32 *opl = ptr + in.size; + MD_u64 size = 0; + for (;ptr < opl; ptr += 1) + { + size += MD_Utf8FromCodepoint(str + size, *ptr); + } + str[size] = 0; + MD_ArenaPutBack(arena, cap - size); // := ((cap + 1) - (size + 1)) + return(MD_S8(str, size)); +} + +MD_FUNCTION MD_String32 +MD_S32FromS8(MD_Arena *arena, MD_String8 in) +{ + MD_u64 cap = in.size; + MD_u32 *str = MD_PushArrayZero(arena, MD_u32, cap + 1); + MD_u8 *ptr = in.str; + MD_u8 *opl = ptr + in.size; + MD_u64 size = 0; + MD_DecodedCodepoint consume; + for (;ptr < opl;) + { + consume = MD_DecodeCodepointFromUtf8(ptr, opl - ptr); + ptr += consume.advance; + str[size] = consume.codepoint; + size += 1; + } + str[size] = 0; + MD_ArenaPutBack(arena, 4*(cap - size)); // := 4*((cap + 1) - (size + 1)) + MD_String32 result = {0}; + result.str = str; + result.size = size; + return(result); +} + +//~ File Name Strings + +MD_FUNCTION MD_String8 +MD_PathChopLastPeriod(MD_String8 string) +{ + MD_u64 period_pos = MD_S8FindSubstring(string, MD_S8Lit("."), 0, MD_MatchFlag_FindLast); + if(period_pos < string.size) + { + string.size = period_pos; + } + return string; +} + +MD_FUNCTION MD_String8 +MD_PathSkipLastSlash(MD_String8 string) +{ + MD_u64 slash_pos = MD_S8FindSubstring(string, MD_S8Lit("/"), 0, + MD_StringMatchFlag_SlashInsensitive| + MD_MatchFlag_FindLast); + if(slash_pos < string.size) + { + string.str += slash_pos+1; + string.size -= slash_pos+1; + } + return string; +} + +MD_FUNCTION MD_String8 +MD_PathSkipLastPeriod(MD_String8 string) +{ + MD_u64 period_pos = MD_S8FindSubstring(string, MD_S8Lit("."), 0, MD_MatchFlag_FindLast); + if(period_pos < string.size) + { + string.str += period_pos+1; + string.size -= period_pos+1; + } + return string; +} + +MD_FUNCTION MD_String8 +MD_PathChopLastSlash(MD_String8 string) +{ + MD_u64 slash_pos = MD_S8FindSubstring(string, MD_S8Lit("/"), 0, + MD_StringMatchFlag_SlashInsensitive| + MD_MatchFlag_FindLast); + if(slash_pos < string.size) + { + string.size = slash_pos; + } + return string; +} + +MD_FUNCTION MD_String8 +MD_S8SkipWhitespace(MD_String8 string) +{ + for(MD_u64 i = 0; i < string.size; i += 1) + { + if(!MD_CharIsSpace(string.str[i])) + { + string = MD_S8Skip(string, i); + break; + } + } + return string; +} + +MD_FUNCTION MD_String8 +MD_S8ChopWhitespace(MD_String8 string) +{ + for(MD_u64 i = string.size-1; i < string.size; i -= 1) + { + if(!MD_CharIsSpace(string.str[i])) + { + string = MD_S8Prefix(string, i+1); + break; + } + } + return string; +} + +//~ Numeric Strings + +MD_GLOBAL MD_u8 md_char_to_value[] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +}; + +MD_GLOBAL MD_u8 md_char_is_integer[] = { + 0,0,0,0,0,0,1,1, + 1,0,0,0,1,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, +}; + +MD_FUNCTION MD_b32 +MD_StringIsU64(MD_String8 string, MD_u32 radix) +{ + MD_b32 result = 0; + if (string.size > 0) + { + result = 1; + for (MD_u8 *ptr = string.str, *opl = string.str + string.size; + ptr < opl; + ptr += 1) + { + MD_u8 c = *ptr; + if (!md_char_is_integer[c >> 3]) + { + result = 0; + break; + } + if (md_char_to_value[(c - 0x30)&0x1F] >= radix) + { + result = 0; + break; + } + } + } + return(result); +} + +MD_FUNCTION MD_b32 +MD_StringIsCStyleInt(MD_String8 string) +{ + MD_u8 *ptr = string.str; + MD_u8 *opl = string.str + string.size; + + // consume sign + for (;ptr < opl && (*ptr == '+' || *ptr == '-'); ptr += 1); + + // radix from prefix + MD_u32 radix = 10; + if (ptr < opl) + { + MD_u8 c0 = *ptr; + if (c0 == '0') + { + ptr += 1; + radix = 8; + if (ptr < opl) + { + MD_u8 c1 = *ptr; + if (c1 == 'x') + { + ptr += 1; + radix = 0x10; + } + else if (c1 == 'b') + { + ptr += 1; + radix = 2; + } + } + } + } + + // check integer "digits" + MD_String8 digits_substr = MD_S8Range(ptr, opl); + MD_b32 result = MD_StringIsU64(digits_substr, radix); + + return(result); +} + +MD_FUNCTION MD_u64 +MD_U64FromString(MD_String8 string, MD_u32 radix) +{ + MD_Assert(2 <= radix && radix <= 16); + MD_u64 value = 0; + for (MD_u64 i = 0; i < string.size; i += 1) + { + value *= radix; + MD_u8 c = string.str[i]; + value += md_char_to_value[(c - 0x30)&0x1F]; + } + return(value); +} + +MD_FUNCTION MD_i64 +MD_CStyleIntFromString(MD_String8 string) +{ + MD_u64 p = 0; + + // consume sign + MD_i64 sign = +1; + if (p < string.size) + { + MD_u8 c = string.str[p]; + if (c == '-') + { + sign = -1; + p += 1; + } + else if (c == '+') + { + p += 1; + } + } + + // radix from prefix + MD_u32 radix = 10; + if (p < string.size) + { + MD_u8 c0 = string.str[p]; + if (c0 == '0') + { + p += 1; + radix = 8; + if (p < string.size) + { + MD_u8 c1 = string.str[p]; + if (c1 == 'x') + { + p += 1; + radix = 16; + } + else if (c1 == 'b') + { + p += 1; + radix = 2; + } + } + } + } + + // consume integer "digits" + MD_String8 digits_substr = MD_S8Skip(string, p); + MD_u64 n = MD_U64FromString(digits_substr, radix); + + // combine result + MD_i64 result = sign*n; + return(result); +} + +MD_FUNCTION MD_f64 +MD_F64FromString(MD_String8 string) +{ + char str[64]; + MD_u64 str_size = string.size; + if (str_size > sizeof(str) - 1) + { + str_size = sizeof(str) - 1; + } + MD_MemoryCopy(str, string.str, str_size); + str[str_size] = 0; + return(atof(str)); +} + + +MD_FUNCTION MD_String8 +MD_CStyleHexStringFromU64(MD_Arena *arena, MD_u64 x, MD_b32 caps) +{ + static char md_int_value_to_char[] = "0123456789abcdef"; + MD_u8 buffer[10]; + MD_u8 *opl = buffer + 10; + MD_u8 *ptr = opl; + if (x == 0) + { + ptr -= 1; + *ptr = '0'; + } + else + { + for (;;) + { + MD_u32 val = x%16; + x /= 16; + MD_u8 c = (MD_u8)md_int_value_to_char[val]; + if (caps) + { + c = MD_CharToUpper(c); + } + ptr -= 1; + *ptr = c; + if (x == 0) + { + break; + } + } + } + ptr -= 1; + *ptr = 'x'; + ptr -= 1; + *ptr = '0'; + + MD_String8 result = MD_ZERO_STRUCT; + result.size = (MD_u64)(ptr - buffer); + result.str = MD_PushArray(arena, MD_u8, result.size + 1); + MD_MemoryCopy(result.str, buffer, result.size); + result.str[result.size] =0; + return(result); +} + +//~ Enum/Flag Strings + +MD_FUNCTION MD_String8 +MD_StringFromNodeKind(MD_NodeKind kind) +{ + // NOTE(rjf): @maintenance Must be kept in sync with MD_NodeKind enum. + static char *cstrs[MD_NodeKind_COUNT] = + { + "Nil", + + "File", + "ErrorMarker", + + "Main", + "Tag", + + "List", + "Reference", + }; + return MD_S8CString(cstrs[kind]); +} + +MD_FUNCTION MD_String8List +MD_StringListFromNodeFlags(MD_Arena *arena, MD_NodeFlags flags) +{ + // NOTE(rjf): @maintenance Must be kept in sync with MD_NodeFlags enum. + static char *flag_cstrs[] = + { + "HasParenLeft", + "HasParenRight", + "HasBracketLeft", + "HasBracketRight", + "HasBraceLeft", + "HasBraceRight", + + "IsBeforeSemicolon", + "IsAfterSemicolon", + + "IsBeforeComma", + "IsAfterComma", + + "StringSingleQuote", + "StringDoubleQuote", + "StringTick", + "StringTriplet", + + "Numeric", + "Identifier", + "StringLiteral", + "Symbol", + }; + + MD_String8List list = MD_ZERO_STRUCT; + MD_u64 bits = sizeof(flags) * 8; + for(MD_u64 i = 0; i < bits && i < MD_ArrayCount(flag_cstrs); i += 1) + { + if(flags & (1ull << i)) + { + MD_S8ListPush(arena, &list, MD_S8CString(flag_cstrs[i])); + } + } + return list; +} + +//~ Map Table Data Structure + +MD_FUNCTION MD_u64 +MD_HashStr(MD_String8 string) +{ + MD_u64 result = 5381; + for(MD_u64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + +// NOTE(mal): Generic 64-bit hash function (https://nullprogram.com/blog/2018/07/31/) +// Reversible, so no collisions. Assumes all bits of the pointer matter. +MD_FUNCTION MD_u64 +MD_HashPtr(void *p) +{ + MD_u64 h = (MD_u64)p; + h = (h ^ (h >> 30)) * 0xbf58476d1ce4e5b9; + h = (h ^ (h >> 27)) * 0x94d049bb133111eb; + h = h ^ (h >> 31); + return h; +} + +MD_FUNCTION MD_Map +MD_MapMakeBucketCount(MD_Arena *arena, MD_u64 bucket_count) +{ + MD_Map result = {0}; + result.bucket_count = bucket_count; + result.buckets = MD_PushArrayZero(arena, MD_MapBucket, bucket_count); + return(result); +} + +MD_FUNCTION MD_Map +MD_MapMake(MD_Arena *arena) +{ + MD_Map result = MD_MapMakeBucketCount(arena, 4093); + return(result); +} + +MD_FUNCTION MD_MapKey +MD_MapKeyStr(MD_String8 string) +{ + MD_MapKey result = {0}; + if (string.size != 0) + { + result.hash = MD_HashStr(string); + result.size = string.size; + if (string.size > 0) + { + result.ptr = string.str; + } + } + return(result); +} + +MD_FUNCTION MD_MapKey +MD_MapKeyPtr(void *ptr) +{ + MD_MapKey result = {0}; + if (ptr != 0) + { + result.hash = MD_HashPtr(ptr); + result.size = 0; + result.ptr = ptr; + } + return(result); +} + +MD_FUNCTION MD_MapSlot* +MD_MapLookup(MD_Map *map, MD_MapKey key) +{ + MD_MapSlot *result = 0; + if (map->bucket_count > 0) + { + MD_u64 index = key.hash%map->bucket_count; + result = MD_MapScan(map->buckets[index].first, key); + } + return(result); +} + +MD_FUNCTION MD_MapSlot* +MD_MapScan(MD_MapSlot *first_slot, MD_MapKey key) +{ + MD_MapSlot *result = 0; + if (first_slot != 0) + { + MD_b32 ptr_kind = (key.size == 0); + MD_String8 key_string = MD_S8((MD_u8*)key.ptr, key.size); + for (MD_MapSlot *slot = first_slot; + slot != 0; + slot = slot->next) + { + if (slot->key.hash == key.hash) + { + if (ptr_kind) + { + if (slot->key.size == 0 && slot->key.ptr == key.ptr) + { + result = slot; + break; + } + } + else + { + MD_String8 slot_string = MD_S8((MD_u8*)slot->key.ptr, slot->key.size); + if (MD_S8Match(slot_string, key_string, 0)) + { + result = slot; + break; + } + } + } + } + } + return(result); +} + +MD_FUNCTION MD_MapSlot* +MD_MapInsert(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val) +{ + MD_MapSlot *result = 0; + if (map->bucket_count > 0) + { + MD_u64 index = key.hash%map->bucket_count; + MD_MapSlot *slot = MD_PushArrayZero(arena, MD_MapSlot, 1); + MD_MapBucket *bucket = &map->buckets[index]; + MD_QueuePush(bucket->first, bucket->last, slot); + slot->key = key; + slot->val = val; + result = slot; + } + return(result); +} + +MD_FUNCTION MD_MapSlot* +MD_MapOverwrite(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val) +{ + MD_MapSlot *result = MD_MapLookup(map, key); + if (result != 0) + { + result->val = val; + } + else + { + result = MD_MapInsert(arena, map, key, val); + } + return(result); +} + +//~ Parsing + +MD_FUNCTION MD_Token +MD_TokenFromString(MD_String8 string) +{ + MD_Token token = MD_ZERO_STRUCT; + + MD_u8 *one_past_last = string.str + string.size; + MD_u8 *first = string.str; + + if(first < one_past_last) + { + MD_u8 *at = first; + MD_u32 skip_n = 0; + MD_u32 chop_n = 0; + +#define MD_TokenizerScan(cond) for (; at < one_past_last && (cond); at += 1) + + switch (*at) + { + // NOTE(allen): Whitespace parsing + case '\n': + { + token.kind = MD_TokenKind_Newline; + at += 1; + }break; + + case ' ': case '\r': case '\t': case '\f': case '\v': + { + token.kind = MD_TokenKind_Whitespace; + at += 1; + MD_TokenizerScan(*at == ' ' || *at == '\r' || *at == '\t' || *at == '\f' || *at == '\v'); + }break; + + // NOTE(allen): Comment parsing + case '/': + { + if (at + 1 < one_past_last) + { + if (at[1] == '/') + { + // trim off the first '//' + skip_n = 2; + at += 2; + token.kind = MD_TokenKind_Comment; + MD_TokenizerScan(*at != '\n' && *at != '\r'); + } + else if (at[1] == '*') + { + // trim off the first '/*' + skip_n = 2; + at += 2; + token.kind = MD_TokenKind_BrokenComment; + int counter = 1; + for (;at < one_past_last && counter > 0; at += 1) + { + if (at + 1 < one_past_last) + { + if (at[0] == '*' && at[1] == '/') + { + at += 1; + counter -= 1; + } + else if (at[0] == '/' && at[1] == '*') + { + at += 1; + counter += 1; + } + } + } + if(counter == 0) + { + token.kind = MD_TokenKind_Comment; + chop_n = 2; + } + } + } + if (token.kind == 0) goto symbol_lex; + }break; + + // NOTE(allen): Strings + case '"': + case '\'': + case '`': + { + token.kind = MD_TokenKind_BrokenStringLiteral; + + // determine delimiter setup + MD_u8 d = *at; + MD_b32 is_triplet = (at + 2 < one_past_last && at[1] == d && at[2] == d); + + // lex triple-delimiter string + if (is_triplet) + { + skip_n = 3; + at += 3; + MD_u32 consecutive_d = 0; + for (;;) + { + // fail condition + if (at >= one_past_last) + { + break; + } + + if(at[0] == d) + { + consecutive_d += 1; + at += 1; + // close condition + if (consecutive_d == 3) + { + chop_n = 3; + token.kind = MD_TokenKind_StringLiteral; + break; + } + } + else + { + consecutive_d = 0; + + // escaping rule + if(at[0] == '\\') + { + at += 1; + if(at < one_past_last && (at[0] == d || at[0] == '\\')) + { + at += 1; + } + } + else{ + at += 1; + } + } + } + } + + // lex single-delimiter string + if (!is_triplet) + { + skip_n = 1; + at += 1; + for (;at < one_past_last;) + { + // close condition + if (*at == d) + { + at += 1; + chop_n = 1; + token.kind = MD_TokenKind_StringLiteral; + break; + } + + // fail condition + if (*at == '\n') + { + break; + } + + // escaping rule + if (at[0] == '\\') + { + at += 1; + if (at < one_past_last && (at[0] == d || at[0] == '\\')) + { + at += 1; + } + } + else + { + at += 1; + } + } + } + + //- rjf: set relevant node flags on token + token.node_flags |= MD_NodeFlag_StringLiteral; + switch(d) + { + case '\'': token.node_flags |= MD_NodeFlag_StringSingleQuote; break; + case '"': token.node_flags |= MD_NodeFlag_StringDoubleQuote; break; + case '`': token.node_flags |= MD_NodeFlag_StringTick; break; + default: break; + } + if(is_triplet) + { + token.node_flags |= MD_NodeFlag_StringTriplet; + } + + }break; + + // NOTE(allen): Identifiers, Numbers, Symbols + default: + { + if (MD_CharIsAlpha(*at) || *at == '_') + { + token.node_flags |= MD_NodeFlag_Identifier; + token.kind = MD_TokenKind_Identifier; + at += 1; + MD_TokenizerScan(MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '_'); + } + + else if (MD_CharIsDigit(*at)) + { + token.node_flags |= MD_NodeFlag_Numeric; + token.kind = MD_TokenKind_Numeric; + at += 1; + + for (; at < one_past_last;) + { + MD_b32 good = 0; + if (*at == 'e' || *at == 'E') + { + good = 1; + at += 1; + if (at < one_past_last && (*at == '+' || *at == '-')) + { + at += 1; + } + } + else if (MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '.' || *at == '_') + { + good = 1; + at += 1; + } + if (!good) + { + break; + } + } + } + + else if (MD_CharIsUnreservedSymbol(*at)) + { + symbol_lex: + + token.node_flags |= MD_NodeFlag_Symbol; + token.kind = MD_TokenKind_Symbol; + at += 1; + MD_TokenizerScan(MD_CharIsUnreservedSymbol(*at)); + } + + else if (MD_CharIsReservedSymbol(*at)) + { + token.kind = MD_TokenKind_Reserved; + at += 1; + } + + else + { + token.kind = MD_TokenKind_BadCharacter; + at += 1; + } + }break; + } + + token.raw_string = MD_S8Range(first, at); + token.string = MD_S8Substring(token.raw_string, skip_n, token.raw_string.size - chop_n); + +#undef MD_TokenizerScan + + } + + return token; +} + +MD_FUNCTION MD_u64 +MD_LexAdvanceFromSkips(MD_String8 string, MD_TokenKind skip_kinds) +{ + MD_u64 result = string.size; + MD_u64 p = 0; + for (;;) + { + MD_Token token = MD_TokenFromString(MD_S8Skip(string, p)); + if ((skip_kinds & token.kind) == 0) + { + result = p; + break; + } + p += token.raw_string.size; + } + return(result); +} + +MD_FUNCTION MD_ParseResult +MD_ParseResultZero(void) +{ + MD_ParseResult result = MD_ZERO_STRUCT; + result.node = MD_NilNode(); + return result; +} + +MD_FUNCTION MD_ParseResult +MD_ParseNodeSet(MD_Arena *arena, MD_String8 string, MD_u64 offset, MD_Node *parent, + MD_ParseSetRule rule) +{ + MD_ParseResult result = MD_ParseResultZero(); + MD_u64 off = offset; + + //- rjf: fill data from set opener + MD_Token initial_token = MD_TokenFromString(MD_S8Skip(string, offset)); + MD_u8 set_opener = 0; + MD_NodeFlags set_opener_flags = 0; + MD_b32 close_with_brace = 0; + MD_b32 close_with_paren = 0; + MD_b32 close_with_separator = 0; + MD_b32 parse_all = 0; + switch(rule) + { + default: break; + + case MD_ParseSetRule_EndOnDelimiter: + { + MD_u64 opener_check_off = off; + opener_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, opener_check_off), MD_TokenGroup_Irregular); + initial_token = MD_TokenFromString(MD_S8Skip(string, opener_check_off)); + if(initial_token.kind == MD_TokenKind_Reserved) + { + MD_u8 c = initial_token.raw_string.str[0]; + if(c == '{') + { + set_opener = '{'; + set_opener_flags |= MD_NodeFlag_HasBraceLeft; + opener_check_off += initial_token.raw_string.size; + off = opener_check_off; + close_with_brace = 1; + } + else if(c == '(') + { + set_opener = '('; + set_opener_flags |= MD_NodeFlag_HasParenLeft; + opener_check_off += initial_token.raw_string.size; + off = opener_check_off; + close_with_paren = 1; + } + else if(c == '[') + { + set_opener = '['; + set_opener_flags |= MD_NodeFlag_HasBracketLeft; + opener_check_off += initial_token.raw_string.size; + off = opener_check_off; + close_with_paren = 1; + } + else + { + close_with_separator = 1; + } + } + else + { + close_with_separator = 1; + } + }break; + + case MD_ParseSetRule_Global: + { + parse_all = 1; + }break; + } + + //- rjf: fill parent data from opener + parent->flags |= set_opener_flags; + + //- rjf: parse children + MD_b32 got_closer = 0; + MD_u64 parsed_child_count = 0; + if(set_opener != 0 || close_with_separator || parse_all) + { + MD_NodeFlags next_child_flags = 0; + for(;off < string.size;) + { + + //- rjf: check for separator closers + if(close_with_separator) + { + MD_u64 closer_check_off = off; + + //- rjf: check newlines + { + MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); + if(potential_closer.kind == MD_TokenKind_Newline) + { + closer_check_off += potential_closer.raw_string.size; + // TODO(rjf): As far as I can tell, we can't actually do this, + // because higher-level unscoped sets may depend on this newline + // so they can be terminated. + // off = closer_check_off; + + // NOTE(rjf): always terminate with a newline if we have >0 children + if(parsed_child_count > 0) + { + // TODO(rjf): As far as I can tell, we can't actually do this, + // because higher-level unscoped sets may depend on this newline + // so they can be terminated. + // off = closer_check_off; + got_closer = 1; + break; + } + + // NOTE(rjf): terminate after double newline if we have 0 children + MD_Token next_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); + if(next_closer.kind == MD_TokenKind_Newline) + { + closer_check_off += next_closer.raw_string.size; + off = closer_check_off; + got_closer = 1; + break; + } + } + } + + //- rjf: check separators and possible braces from higher parents + { + closer_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); + MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); + if(potential_closer.kind == MD_TokenKind_Reserved) + { + MD_u8 c = potential_closer.raw_string.str[0]; + if(c == ',' || c == ';') + { + off = closer_check_off; + closer_check_off += potential_closer.raw_string.size; + break; + } + else if(c == '}' || c == ']'|| c == ')') + { + goto end_parse; + } + } + } + + } + + //- rjf: check for non-separator closers + if(!close_with_separator && !parse_all) + { + MD_u64 closer_check_off = off; + closer_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); + MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); + if(potential_closer.kind == MD_TokenKind_Reserved) + { + MD_u8 c = potential_closer.raw_string.str[0]; + if(close_with_brace && c == '}') + { + closer_check_off += potential_closer.raw_string.size; + off = closer_check_off; + parent->flags |= MD_NodeFlag_HasBraceRight; + got_closer = 1; + break; + } + else if(close_with_paren && c == ']') + { + closer_check_off += potential_closer.raw_string.size; + off = closer_check_off; + parent->flags |= MD_NodeFlag_HasBracketRight; + got_closer = 1; + break; + } + else if(close_with_paren && c == ')') + { + closer_check_off += potential_closer.raw_string.size; + off = closer_check_off; + parent->flags |= MD_NodeFlag_HasParenRight; + got_closer = 1; + break; + } + } + } + + //- rjf: parse next child + MD_ParseResult child_parse = MD_ParseOneNode(arena, string, off); + MD_MessageListConcat(&result.errors, &child_parse.errors); + off += child_parse.string_advance; + + //- rjf: hook child into parent + if(!MD_NodeIsNil(child_parse.node)) + { + // NOTE(rjf): @error No unnamed set children of implicitly-delimited sets + if(close_with_separator && + child_parse.node->string.size == 0 && + child_parse.node->flags & (MD_NodeFlag_HasParenLeft | + MD_NodeFlag_HasParenRight | + MD_NodeFlag_HasBracketLeft | + MD_NodeFlag_HasBracketRight | + MD_NodeFlag_HasBraceLeft | + MD_NodeFlag_HasBraceRight )) + { + MD_String8 error_str = MD_S8Lit("Unnamed set children of implicitly-delimited sets are not legal."); + MD_Message *error = MD_MakeNodeError(arena, child_parse.node, MD_MessageKind_Warning, + error_str); + MD_MessageListPush(&result.errors, error); + } + + MD_PushChild(parent, child_parse.node); + parsed_child_count += 1; + } + + //- rjf: check trailing separator + MD_NodeFlags trailing_separator_flags = 0; + if(!close_with_separator) + { + off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); + MD_Token trailing_separator = MD_TokenFromString(MD_S8Skip(string, off)); + if (trailing_separator.kind == MD_TokenKind_Reserved) + { + MD_u8 c = trailing_separator.string.str[0]; + if(c == ',') + { + trailing_separator_flags |= MD_NodeFlag_IsBeforeComma; + off += trailing_separator.raw_string.size; + } + else if(c == ';') + { + trailing_separator_flags |= MD_NodeFlag_IsBeforeSemicolon; + off += trailing_separator.raw_string.size; + } + } + } + + //- rjf: fill child flags + child_parse.node->flags |= next_child_flags | trailing_separator_flags; + + //- rjf: setup next_child_flags + next_child_flags = MD_NodeFlag_AfterFromBefore(trailing_separator_flags); + } + } + end_parse:; + + //- rjf: push missing closer error, if we have one + if(set_opener != 0 && got_closer == 0) + { + // NOTE(rjf): @error We didn't get a closer for the set + MD_String8 error_str = MD_S8Fmt(arena, "Unbalanced \"%c\"", set_opener); + MD_Message *error = MD_MakeTokenError(arena, string, initial_token, + MD_MessageKind_FatalError, error_str); + MD_MessageListPush(&result.errors, error); + } + + //- rjf: push empty implicit set error, + if(close_with_separator && parsed_child_count == 0) + { + // NOTE(rjf): @error No empty implicitly-delimited sets + MD_Message *error = MD_MakeTokenError(arena, string, initial_token, MD_MessageKind_Error, + MD_S8Lit("Empty implicitly-delimited node list")); + MD_MessageListPush(&result.errors, error); + } + + //- rjf: fill result info + result.node = parent; + result.string_advance = off - offset; + + return result; +} + +MD_FUNCTION MD_ParseResult +MD_ParseOneNode(MD_Arena *arena, MD_String8 string, MD_u64 offset) +{ + MD_ParseResult result = MD_ParseResultZero(); + MD_u64 off = offset; + + //- rjf: parse pre-comment + MD_String8 prev_comment = MD_ZERO_STRUCT; + { + MD_Token comment_token = MD_ZERO_STRUCT; + for(;off < string.size;) + { + MD_Token token = MD_TokenFromString(MD_S8Skip(string, off)); + if(token.kind == MD_TokenKind_Comment) + { + off += token.raw_string.size; + comment_token = token; + } + else if(token.kind == MD_TokenKind_Newline) + { + off += token.raw_string.size; + MD_Token next_token = MD_TokenFromString(MD_S8Skip(string, off)); + if(next_token.kind == MD_TokenKind_Comment) + { + // NOTE(mal): If more than one comment, use the last comment + comment_token = next_token; + } + else if(next_token.kind == MD_TokenKind_Newline) + { + MD_MemoryZeroStruct(&comment_token); + } + } + else if((token.kind & MD_TokenGroup_Whitespace) != 0) + { + off += token.raw_string.size; + } + else + { + break; + } + prev_comment = comment_token.string; + } + } + + //- rjf: parse tag list + MD_Node *first_tag = MD_NilNode(); + MD_Node *last_tag = MD_NilNode(); + { + for(;off < string.size;) + { + //- rjf: parse @ symbol, signifying start of tag + off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); + MD_Token next_token = MD_TokenFromString(MD_S8Skip(string, off)); + if(next_token.kind != MD_TokenKind_Reserved || + next_token.string.str[0] != '@') + { + break; + } + off += next_token.raw_string.size; + + //- rjf: parse string of tag node + MD_Token name = MD_TokenFromString(MD_S8Skip(string, off)); + MD_u64 name_off = off; + if((name.kind & MD_TokenGroup_Label) == 0) + { + // NOTE(rjf): @error Improper token for tag string + MD_String8 error_str = MD_S8Fmt(arena, "\"%.*s\" is not a proper tag label", + MD_S8VArg(name.raw_string)); + MD_Message *error = MD_MakeTokenError(arena, string, name, MD_MessageKind_Error, error_str); + MD_MessageListPush(&result.errors, error); + break; + } + off += name.raw_string.size; + + //- rjf: build tag + MD_Node *tag = MD_MakeNode(arena, MD_NodeKind_Tag, name.string, name.raw_string, name_off); + + //- rjf: parse tag arguments + MD_Token open_paren = MD_TokenFromString(MD_S8Skip(string, off)); + MD_ParseResult args_parse = MD_ParseResultZero(); + if(open_paren.kind == MD_TokenKind_Reserved && + open_paren.string.str[0] == '(') + { + args_parse = MD_ParseNodeSet(arena, string, off, tag, MD_ParseSetRule_EndOnDelimiter); + MD_MessageListConcat(&result.errors, &args_parse.errors); + } + off += args_parse.string_advance; + + //- rjf: push tag to result + MD_NodeDblPushBack(first_tag, last_tag, tag); + } + } + + //- rjf: parse node + MD_Node *parsed_node = MD_NilNode(); + MD_ParseResult children_parse = MD_ParseResultZero(); + retry:; + { + //- rjf: try to parse an unnamed set + off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); + MD_Token unnamed_set_opener = MD_TokenFromString(MD_S8Skip(string, off)); + if(unnamed_set_opener.kind == MD_TokenKind_Reserved) + { + MD_u8 c = unnamed_set_opener.string.str[0]; + if (c == '(' || c == '{' || c == '[') + { + parsed_node = MD_MakeNode(arena, MD_NodeKind_Main, MD_S8Lit(""), MD_S8Lit(""), + unnamed_set_opener.raw_string.str - string.str); + children_parse = MD_ParseNodeSet(arena, string, off, parsed_node, + MD_ParseSetRule_EndOnDelimiter); + off += children_parse.string_advance; + MD_MessageListConcat(&result.errors, &children_parse.errors); + } + else if (c == ')' || c == '}' || c == ']') + { + // NOTE(rjf): @error Unexpected set closing symbol + MD_String8 error_str = MD_S8Fmt(arena, "Unbalanced \"%c\"", c); + MD_Message *error = MD_MakeTokenError(arena, string, unnamed_set_opener, + MD_MessageKind_FatalError, error_str); + MD_MessageListPush(&result.errors, error); + off += unnamed_set_opener.raw_string.size; + } + else + { + // NOTE(rjf): @error Unexpected reserved symbol + MD_String8 error_str = MD_S8Fmt(arena, "Unexpected reserved symbol \"%c\"", c); + MD_Message *error = MD_MakeTokenError(arena, string, unnamed_set_opener, + MD_MessageKind_Error, error_str); + MD_MessageListPush(&result.errors, error); + off += unnamed_set_opener.raw_string.size; + } + goto end_parse; + + } + + //- rjf: try to parse regular node, with/without children + off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); + MD_Token label_name = MD_TokenFromString(MD_S8Skip(string, off)); + if((label_name.kind & MD_TokenGroup_Label) != 0) + { + off += label_name.raw_string.size; + parsed_node = MD_MakeNode(arena, MD_NodeKind_Main, label_name.string, label_name.raw_string, + label_name.raw_string.str - string.str); + parsed_node->flags |= label_name.node_flags; + + //- rjf: try to parse children for this node + MD_u64 colon_check_off = off; + colon_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, colon_check_off), MD_TokenGroup_Irregular); + MD_Token colon = MD_TokenFromString(MD_S8Skip(string, colon_check_off)); + if(colon.kind == MD_TokenKind_Reserved && + colon.string.str[0] == ':') + { + colon_check_off += colon.raw_string.size; + off = colon_check_off; + + children_parse = MD_ParseNodeSet(arena, string, off, parsed_node, + MD_ParseSetRule_EndOnDelimiter); + off += children_parse.string_advance; + MD_MessageListConcat(&result.errors, &children_parse.errors); + } + goto end_parse; + } + + //- rjf: collect bad token + MD_Token bad_token = MD_TokenFromString(MD_S8Skip(string, off)); + if(bad_token.kind & MD_TokenGroup_Error) + { + off += bad_token.raw_string.size; + + switch (bad_token.kind) + { + case MD_TokenKind_BadCharacter: + { + MD_String8List bytes = {0}; + for(int i_byte = 0; i_byte < bad_token.raw_string.size; ++i_byte) + { + MD_u8 b = bad_token.raw_string.str[i_byte]; + MD_S8ListPush(arena, &bytes, MD_CStyleHexStringFromU64(arena, b, 1)); + } + + MD_StringJoin join = MD_ZERO_STRUCT; + join.mid = MD_S8Lit(" "); + MD_String8 byte_string = MD_S8ListJoin(arena, bytes, &join); + + // NOTE(rjf): @error Bad character + MD_String8 error_str = MD_S8Fmt(arena, "Non-ASCII character \"%.*s\"", + MD_S8VArg(byte_string)); + MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, + error_str); + MD_MessageListPush(&result.errors, error); + }break; + + case MD_TokenKind_BrokenComment: + { + // NOTE(rjf): @error Broken Comments + MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, + MD_S8Lit("Unterminated comment")); + MD_MessageListPush(&result.errors, error); + }break; + + case MD_TokenKind_BrokenStringLiteral: + { + // NOTE(rjf): @error Broken String Literals + MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, + MD_S8Lit("Unterminated string literal")); + MD_MessageListPush(&result.errors, error); + }break; + } + goto retry; + } + } + + end_parse:; + + //- rjf: parse comments after nodes. + MD_String8 next_comment = MD_ZERO_STRUCT; + { + MD_Token comment_token = MD_ZERO_STRUCT; + for(;;) + { + MD_Token token = MD_TokenFromString(MD_S8Skip(string, off)); + if(token.kind == MD_TokenKind_Comment) + { + comment_token = token; + off += token.raw_string.size; + break; + } + + else if(token.kind == MD_TokenKind_Newline) + { + break; + } + else if((token.kind & MD_TokenGroup_Whitespace) != 0) + { + off += token.raw_string.size; + } + else + { + break; + } + } + next_comment = comment_token.string; + } + + //- rjf: fill result + parsed_node->prev_comment = prev_comment; + parsed_node->next_comment = next_comment; + result.node = parsed_node; + if(!MD_NodeIsNil(result.node)) + { + result.node->first_tag = first_tag; + result.node->last_tag = last_tag; + for(MD_Node *tag = first_tag; !MD_NodeIsNil(tag); tag = tag->next) + { + tag->parent = result.node; + } + } + result.string_advance = off - offset; + + return result; +} + +MD_FUNCTION MD_ParseResult +MD_ParseWholeString(MD_Arena *arena, MD_String8 filename, MD_String8 contents) +{ + MD_Node *root = MD_MakeNode(arena, MD_NodeKind_File, filename, contents, 0); + MD_ParseResult result = MD_ParseNodeSet(arena, contents, 0, root, MD_ParseSetRule_Global); + result.node = root; + for(MD_Message *error = result.errors.first; error != 0; error = error->next) + { + if(MD_NodeIsNil(error->node->parent)) + { + error->node->parent = root; + } + } + return result; +} + +MD_FUNCTION MD_ParseResult +MD_ParseWholeFile(MD_Arena *arena, MD_String8 filename) +{ + MD_String8 file_contents = MD_LoadEntireFile(arena, filename); + MD_ParseResult parse = MD_ParseWholeString(arena, filename, file_contents); + if(file_contents.str == 0) + { + // NOTE(rjf): @error File failing to load + MD_String8 error_str = MD_S8Fmt(arena, "Could not read file \"%.*s\"", MD_S8VArg(filename)); + MD_Message *error = MD_MakeNodeError(arena, parse.node, MD_MessageKind_FatalError, + error_str); + MD_MessageListPush(&parse.errors, error); + } + return parse; +} + +//~ Messages (Errors/Warnings) + +MD_FUNCTION MD_Node* +MD_MakeErrorMarkerNode(MD_Arena *arena, MD_String8 parse_contents, MD_u64 offset) +{ + MD_Node *result = MD_MakeNode(arena, MD_NodeKind_ErrorMarker, MD_S8Lit(""), parse_contents, + offset); + return(result); +} + +MD_FUNCTION MD_Message* +MD_MakeNodeError(MD_Arena *arena, MD_Node *node, MD_MessageKind kind, MD_String8 str) +{ + MD_Message *error = MD_PushArrayZero(arena, MD_Message, 1); + error->node = node; + error->kind = kind; + error->string = str; + return error; +} + +MD_FUNCTION MD_Message * +MD_MakeTokenError(MD_Arena *arena, MD_String8 parse_contents, MD_Token token, + MD_MessageKind kind, MD_String8 str) +{ + MD_u64 offset = token.raw_string.str - parse_contents.str; + MD_Node *err_node = MD_MakeErrorMarkerNode(arena, parse_contents, offset); + return MD_MakeNodeError(arena, err_node, kind, str); +} + +MD_FUNCTION void +MD_MessageListPush(MD_MessageList *list, MD_Message *message) +{ + MD_QueuePush(list->first, list->last, message); + if(message->kind > list->max_message_kind) + { + list->max_message_kind = message->kind; + } + list->node_count += 1; +} + +MD_FUNCTION void +MD_MessageListConcat(MD_MessageList *list, MD_MessageList *to_push) +{ + if(to_push->node_count != 0) + { + if(list->last != 0) + { + list->last->next = to_push->first; + list->last = to_push->last; + list->node_count += to_push->node_count; + if(to_push->max_message_kind > list->max_message_kind) + { + list->max_message_kind = to_push->max_message_kind; + } + } + else + { + *list = *to_push; + } + MD_MemoryZeroStruct(to_push); + } +} + +//~ Location Conversions + +MD_FUNCTION MD_CodeLoc +MD_CodeLocFromFileOffset(MD_String8 filename, MD_u8 *base, MD_u64 offset) +{ + MD_CodeLoc loc; + loc.filename = filename; + loc.line = 1; + loc.column = 1; + if(base != 0) + { + MD_u8 *at = base + offset; + for(MD_u64 i = 0; base+i < at && base[i]; i += 1) + { + if(base[i] == '\n') + { + loc.line += 1; + loc.column = 1; + } + else + { + loc.column += 1; + } + } + } + return loc; +} + +MD_FUNCTION MD_CodeLoc +MD_CodeLocFromNode(MD_Node *node) +{ + MD_Node *file_root = MD_NilNode(); + for(MD_Node *parent = node->parent; !MD_NodeIsNil(parent); parent = parent->parent) + { + if(parent->kind == MD_NodeKind_File) + { + file_root = parent; + break; + } + } + MD_Node *first_tag = file_root->first_tag; + MD_CodeLoc loc = {0}; + if(MD_NodeIsNil(first_tag)) + { + loc = MD_CodeLocFromFileOffset(file_root->string, file_root->raw_string.str, node->offset); + } + else + { + loc = MD_CodeLocFromFileOffset(file_root->string, first_tag->raw_string.str, node->offset); + } + return loc; +} + +//~ Tree/List Building + +MD_FUNCTION MD_b32 +MD_NodeIsNil(MD_Node *node) +{ + return(node == 0 || node == &_md_nil_node || node->kind == MD_NodeKind_Nil); +} + +MD_FUNCTION MD_Node * +MD_NilNode(void) { return &_md_nil_node; } + +MD_FUNCTION MD_Node * +MD_MakeNode(MD_Arena *arena, MD_NodeKind kind, MD_String8 string, MD_String8 raw_string, + MD_u64 offset) +{ + MD_Node *node = MD_PushArrayZero(arena, MD_Node, 1); + node->kind = kind; + node->string = string; + node->raw_string = raw_string; + node->next = node->prev = node->parent = + node->first_child = node->last_child = + node->first_tag = node->last_tag = node->ref_target = MD_NilNode(); + node->offset = offset; + return node; +} + +MD_FUNCTION void +MD_PushChild(MD_Node *parent, MD_Node *new_child) +{ + if (!MD_NodeIsNil(new_child)) + { + MD_NodeDblPushBack(parent->first_child, parent->last_child, new_child); + new_child->parent = parent; + } +} + +MD_FUNCTION void +MD_PushTag(MD_Node *node, MD_Node *tag) +{ + if (!MD_NodeIsNil(tag)) + { + MD_NodeDblPushBack(node->first_tag, node->last_tag, tag); + tag->parent = node; + } +} + +MD_FUNCTION MD_Node* +MD_MakeList(MD_Arena *arena) +{ + MD_String8 empty = {0}; + MD_Node *result = MD_MakeNode(arena, MD_NodeKind_List, empty, empty, 0); + return(result); +} + +MD_FUNCTION void +MD_ListConcatInPlace(MD_Node *list, MD_Node *to_push) +{ + if (!MD_NodeIsNil(to_push->first_child)) + { + if (!MD_NodeIsNil(list->first_child)) + { + list->last_child->next = to_push->first_child; + list->last_child = to_push->last_child; + } + else + { + list->first_child = to_push->first_child; + list->last_child = to_push->last_child; + } + to_push->first_child = to_push->last_child = MD_NilNode(); + } +} + +MD_FUNCTION MD_Node* +MD_PushNewReference(MD_Arena *arena, MD_Node *list, MD_Node *target) +{ + MD_Node *n = MD_MakeNode(arena, MD_NodeKind_Reference, target->string, target->raw_string, + target->offset); + n->ref_target = target; + MD_PushChild(list, n); + return(n); +} + +//~ Introspection Helpers + +MD_FUNCTION MD_Node * +MD_FirstNodeWithString(MD_Node *first, MD_String8 string, MD_MatchFlags flags) +{ + MD_Node *result = MD_NilNode(); + for(MD_Node *node = first; !MD_NodeIsNil(node); node = node->next) + { + if(MD_S8Match(string, node->string, flags)) + { + result = node; + break; + } + } + return result; +} + +MD_FUNCTION MD_Node * +MD_NodeAtIndex(MD_Node *first, int n) +{ + MD_Node *result = MD_NilNode(); + if(n >= 0) + { + int idx = 0; + for(MD_Node *node = first; !MD_NodeIsNil(node); node = node->next, idx += 1) + { + if(idx == n) + { + result = node; + break; + } + } + } + return result; +} + +MD_FUNCTION MD_Node * +MD_FirstNodeWithFlags(MD_Node *first, MD_NodeFlags flags) +{ + MD_Node *result = MD_NilNode(); + for(MD_Node *n = first; !MD_NodeIsNil(n); n = n->next) + { + if(n->flags & flags) + { + result = n; + break; + } + } + return result; +} + +MD_FUNCTION int +MD_IndexFromNode(MD_Node *node) +{ + int idx = 0; + for(MD_Node *last = node->prev; !MD_NodeIsNil(last); last = last->prev, idx += 1); + return idx; +} + +MD_FUNCTION MD_Node * +MD_RootFromNode(MD_Node *node) +{ + MD_Node *parent = node; + for(MD_Node *p = parent; !MD_NodeIsNil(p); p = p->parent) + { + parent = p; + } + return parent; +} + +MD_FUNCTION MD_Node * +MD_ChildFromString(MD_Node *node, MD_String8 child_string, MD_MatchFlags flags) +{ + return MD_FirstNodeWithString(node->first_child, child_string, flags); +} + +MD_FUNCTION MD_Node * +MD_TagFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags) +{ + return MD_FirstNodeWithString(node->first_tag, tag_string, flags); +} + +MD_FUNCTION MD_Node * +MD_ChildFromIndex(MD_Node *node, int n) +{ + return MD_NodeAtIndex(node->first_child, n); +} + +MD_FUNCTION MD_Node * +MD_TagFromIndex(MD_Node *node, int n) +{ + return MD_NodeAtIndex(node->first_tag, n); +} + +MD_FUNCTION MD_Node * +MD_TagArgFromIndex(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags, int n) +{ + MD_Node *tag = MD_TagFromString(node, tag_string, flags); + return MD_ChildFromIndex(tag, n); +} + +MD_FUNCTION MD_Node * +MD_TagArgFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags tag_str_flags, MD_String8 arg_string, MD_MatchFlags arg_str_flags) +{ + MD_Node *tag = MD_TagFromString(node, tag_string, tag_str_flags); + MD_Node *arg = MD_ChildFromString(tag, arg_string, arg_str_flags); + return arg; +} + +MD_FUNCTION MD_b32 +MD_NodeHasChild(MD_Node *node, MD_String8 string, MD_MatchFlags flags) +{ + return !MD_NodeIsNil(MD_ChildFromString(node, string, flags)); +} + +MD_FUNCTION MD_b32 +MD_NodeHasTag(MD_Node *node, MD_String8 string, MD_MatchFlags flags) +{ + return !MD_NodeIsNil(MD_TagFromString(node, string, flags)); +} + +MD_FUNCTION MD_i64 +MD_ChildCountFromNode(MD_Node *node) +{ + MD_i64 result = 0; + for(MD_EachNode(child, node->first_child)) + { + result += 1; + } + return result; +} + +MD_FUNCTION MD_i64 +MD_TagCountFromNode(MD_Node *node) +{ + MD_i64 result = 0; + for(MD_EachNode(tag, node->first_tag)) + { + result += 1; + } + return result; +} + +MD_FUNCTION MD_Node * +MD_ResolveNodeFromReference(MD_Node *node) +{ + MD_u64 safety = 100; + for(; safety > 0 && node->kind == MD_NodeKind_Reference; + safety -= 1, node = node->ref_target); + MD_Node *result = node; + return(result); +} + +MD_FUNCTION MD_Node* +MD_NodeNextWithLimit(MD_Node *node, MD_Node *opl) +{ + node = node->next; + if (node == opl) + { + node = MD_NilNode(); + } + return(node); +} + +MD_FUNCTION MD_String8 +MD_PrevCommentFromNode(MD_Node *node) +{ + return(node->prev_comment); +} + +MD_FUNCTION MD_String8 +MD_NextCommentFromNode(MD_Node *node) +{ + return(node->next_comment); +} + +//~ Error/Warning Helpers + +MD_FUNCTION MD_String8 +MD_StringFromMessageKind(MD_MessageKind kind) +{ + MD_String8 result = MD_ZERO_STRUCT; + switch (kind) + { + default: break; + case MD_MessageKind_Note: result = MD_S8Lit("note"); break; + case MD_MessageKind_Warning: result = MD_S8Lit("warning"); break; + case MD_MessageKind_Error: result = MD_S8Lit("error"); break; + case MD_MessageKind_FatalError: result = MD_S8Lit("fatal error"); break; + } + return(result); +} + +MD_FUNCTION MD_String8 +MD_FormatMessage(MD_Arena *arena, MD_CodeLoc loc, MD_MessageKind kind, MD_String8 string) +{ + MD_String8 kind_string = MD_StringFromMessageKind(kind); + MD_String8 result = MD_S8Fmt(arena, "" MD_FmtCodeLoc " %.*s: %.*s\n", + MD_CodeLocVArg(loc), MD_S8VArg(kind_string), MD_S8VArg(string)); + return(result); +} + +#if !MD_DISABLE_PRINT_HELPERS + +MD_FUNCTION void +MD_PrintMessage(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, MD_String8 string) +{ + MD_ArenaTemp scratch = MD_GetScratch(0, 0); + MD_String8 message = MD_FormatMessage(scratch.arena, code_loc, kind, string); + fwrite(message.str, message.size, 1, file); + MD_ReleaseScratch(scratch); +} + +MD_FUNCTION void +MD_PrintMessageFmt(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, char *fmt, ...) +{ + MD_ArenaTemp scratch = MD_GetScratch(0, 0); + va_list args; + va_start(args, fmt); + MD_String8 string = MD_S8FmtV(scratch.arena, fmt, args); + va_end(args); + MD_String8 message = MD_FormatMessage(scratch.arena, code_loc, kind, string); + fwrite(message.str, message.size, 1, file); + MD_ReleaseScratch(scratch); +} + +#endif + +//~ Tree Comparison/Verification + +MD_FUNCTION MD_b32 +MD_NodeMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags) +{ + MD_b32 result = 0; + if(a->kind == b->kind && MD_S8Match(a->string, b->string, flags)) + { + result = 1; + if(result && flags & MD_NodeMatchFlag_NodeFlags) + { + result = result && a->flags == b->flags; + } + if(result && a->kind != MD_NodeKind_Tag && (flags & MD_NodeMatchFlag_Tags)) + { + for(MD_Node *a_tag = a->first_tag, *b_tag = b->first_tag; + !MD_NodeIsNil(a_tag) || !MD_NodeIsNil(b_tag); + a_tag = a_tag->next, b_tag = b_tag->next) + { + if(MD_NodeMatch(a_tag, b_tag, flags)) + { + if(flags & MD_NodeMatchFlag_TagArguments) + { + for(MD_Node *a_tag_arg = a_tag->first_child, *b_tag_arg = b_tag->first_child; + !MD_NodeIsNil(a_tag_arg) || !MD_NodeIsNil(b_tag_arg); + a_tag_arg = a_tag_arg->next, b_tag_arg = b_tag_arg->next) + { + if(!MD_NodeDeepMatch(a_tag_arg, b_tag_arg, flags)) + { + result = 0; + goto end; + } + } + } + } + else + { + result = 0; + goto end; + } + } + } + } + end:; + return result; +} + +MD_FUNCTION MD_b32 +MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags) +{ + MD_b32 result = MD_NodeMatch(a, b, flags); + if(result) + { + for(MD_Node *a_child = a->first_child, *b_child = b->first_child; + !MD_NodeIsNil(a_child) || !MD_NodeIsNil(b_child); + a_child = a_child->next, b_child = b_child->next) + { + if(!MD_NodeDeepMatch(a_child, b_child, flags)) + { + result = 0; + goto end; + } + } + } + end:; + return result; +} + +//~ Expression Parsing + +MD_FUNCTION void +MD_ExprOprPush(MD_Arena *arena, MD_ExprOprList *list, + MD_ExprOprKind kind, MD_u32 precedence, MD_String8 string, + MD_u32 op_id, void *op_ptr) +{ + MD_ExprOpr *op = MD_PushArrayZero(arena, MD_ExprOpr, 1); + MD_QueuePush(list->first, list->last, op); + list->count += 1; + op->op_id = op_id; + op->kind = kind; + op->precedence = precedence; + op->string = string; + op->op_ptr = op_ptr; +} + +MD_GLOBAL MD_BakeOperatorErrorHandler md_bake_operator_error_handler = 0; + +MD_FUNCTION MD_BakeOperatorErrorHandler +MD_ExprSetBakeOperatorErrorHandler(MD_BakeOperatorErrorHandler handler){ + MD_BakeOperatorErrorHandler old_handler = md_bake_operator_error_handler; + md_bake_operator_error_handler = handler; + return old_handler; +} + +MD_FUNCTION MD_ExprOprTable +MD_ExprBakeOprTableFromList(MD_Arena *arena, MD_ExprOprList *list) +{ + MD_ExprOprTable result = MD_ZERO_STRUCT; + + // TODO(allen): @upgrade_potential(minor) + + for(MD_ExprOpr *op = list->first; + op != 0; + op = op->next) + { + MD_ExprOprKind op_kind = op->kind; + MD_String8 op_s = op->string; + + // error checking + MD_String8 error_str = MD_ZERO_STRUCT; + + MD_Token op_token = MD_TokenFromString(op_s); + MD_b32 is_setlike_op = + (op_s.size == 2 && + (MD_S8Match(op_s, MD_S8Lit("[]"), 0) || MD_S8Match(op_s, MD_S8Lit("()"), 0) || + MD_S8Match(op_s, MD_S8Lit("[)"), 0) || MD_S8Match(op_s, MD_S8Lit("(]"), 0) || + MD_S8Match(op_s, MD_S8Lit("{}"), 0))); + + if(op_kind != MD_ExprOprKind_Prefix && op_kind != MD_ExprOprKind_Postfix && + op_kind != MD_ExprOprKind_Binary && op_kind != MD_ExprOprKind_BinaryRightAssociative) + { + error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because its kind value (%d) does not match " + "any valid operator kind", MD_S8VArg(op_s), op_kind); + } + else if(is_setlike_op && op_kind != MD_ExprOprKind_Postfix) + { + error_str = + MD_S8Fmt(arena, "Ignored operator \"%.*s\". \"%.*s\" is only allowed as unary postfix", + MD_S8VArg(op_s), MD_S8VArg(op_s)); + } + else if(!is_setlike_op && + (op_token.kind != MD_TokenKind_Identifier && op_token.kind != MD_TokenKind_Symbol)) + { + error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because it is neither a symbol " + "nor an identifier token", MD_S8VArg(op_s)); + } + else if(!is_setlike_op && op_token.string.size < op_s.size) + { + error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because its prefix \"%.*s\" " + "constitutes a standalone operator", + MD_S8VArg(op_s), MD_S8VArg(op_token.string)); + } + else + { + for(MD_ExprOpr *op2 = list->first; + op2 != op; + op2 = op2->next) + { // NOTE(mal): O(n^2) + MD_ExprOprKind op2_kind = op2->kind; + MD_String8 op2_s = op2->string; + if(op->precedence == op2->precedence && + ((op_kind == MD_ExprOprKind_Binary && + op2_kind == MD_ExprOprKind_BinaryRightAssociative) || + (op_kind == MD_ExprOprKind_BinaryRightAssociative && + op2_kind == MD_ExprOprKind_Binary))) + { + error_str = + MD_S8Fmt(arena, "Ignored binary operator \"%.*s\" because another binary operator" + "has the same precedence and different associativity", MD_S8VArg(op_s)); + } + else if(MD_S8Match(op_s, op2_s, 0)) + { + if(op_kind == op2_kind) + { + error_str = MD_S8Fmt(arena, "Ignored repeat operator \"%.*s\"", MD_S8VArg(op_s)); + } + else if(op_kind != MD_ExprOprKind_Prefix && op2_kind != MD_ExprOprKind_Prefix) + { + error_str = + MD_S8Fmt(arena, "Ignored conflicting repeat operator \"%.*s\". There can't be" + "more than one posfix/binary operator associated to the same token", + MD_S8VArg(op_s)); + } + } + } + } + + // save error + if(error_str.size != 0 && md_bake_operator_error_handler) + { + md_bake_operator_error_handler(MD_MessageKind_Warning, error_str); + } + + // save list + else + { + MD_ExprOprList *saved_list = result.table + op_kind; + MD_ExprOpr *op_node_copy = MD_PushArray(arena, MD_ExprOpr, 1); + *op_node_copy = *op; + MD_QueuePush(saved_list->first, saved_list->last, op_node_copy); + saved_list->count += 1; + } + } + + return(result); +} + +MD_FUNCTION MD_ExprOpr* +MD_ExprOprFromKindString(MD_ExprOprTable *table, MD_ExprOprKind kind, MD_String8 s) +{ + // TODO(allen): @upgrade_potential + + // NOTE(mal): Look for operator on one or all (kind == MD_ExprOprKind_Null) tables + MD_ExprOpr *result = 0; + for(MD_ExprOprKind cur_kind = (MD_ExprOprKind)(MD_ExprOprKind_Null + 1); + cur_kind < MD_ExprOprKind_COUNT; + cur_kind = (MD_ExprOprKind)(cur_kind + 1)) + { + if(kind == MD_ExprOprKind_Null || kind == cur_kind) + { + MD_ExprOprList *op_list = table->table+cur_kind; + for(MD_ExprOpr *op = op_list->first; + op != 0; + op = op->next) + { + if(MD_S8Match(op->string, s, 0)) + { + result = op; + goto dbl_break; + } + } + } + } + dbl_break:; + return result; +} + +MD_FUNCTION MD_ExprParseResult +MD_ExprParse(MD_Arena *arena, MD_ExprOprTable *op_table, MD_Node *first, MD_Node *opl) +{ + // setup a context + MD_ExprParseCtx ctx = MD_ExprParse_MakeContext(op_table); + + // parse the top level + MD_Expr *expr = MD_ExprParse_TopLevel(arena, &ctx, first, opl); + + // fill result + MD_ExprParseResult result = {0}; + result.expr = expr; + result.errors = ctx.errors; + return(result); +} + +MD_FUNCTION MD_Expr* +MD_Expr_NewLeaf(MD_Arena *arena, MD_Node *node) +{ + MD_Expr *result = MD_PushArrayZero(arena, MD_Expr, 1); + result->md_node = node; + return(result); +} + +MD_FUNCTION MD_Expr* +MD_Expr_NewOpr(MD_Arena *arena, MD_ExprOpr *op, MD_Node *op_node, MD_Expr *l, MD_Expr *r) +{ + MD_Expr *result = MD_PushArrayZero(arena, MD_Expr, 1); + result->op = op; + result->md_node = op_node; + result->parent = 0; + result->left = l; + result->right = r; + if (l != 0) + { + MD_Assert(l->parent == 0); + l->parent = result; + } + if(r != 0) + { + MD_Assert(r->parent == 0); + r->parent = result; + } + return(result); +} + +MD_FUNCTION MD_ExprParseCtx +MD_ExprParse_MakeContext(MD_ExprOprTable *op_table) +{ + MD_ExprParseCtx result = MD_ZERO_STRUCT; + result.op_table = op_table; + + result.accel.postfix_set_ops[0] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("()")); + result.accel.postfix_set_flags[0] = MD_NodeFlag_HasParenLeft | MD_NodeFlag_HasParenRight; + + result.accel.postfix_set_ops[1] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("[]")); + result.accel.postfix_set_flags[1] = MD_NodeFlag_HasBracketLeft | MD_NodeFlag_HasBracketRight; + + result.accel.postfix_set_ops[2] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("{}")); + result.accel.postfix_set_flags[2] = MD_NodeFlag_HasBraceLeft | MD_NodeFlag_HasBraceRight; + + result.accel.postfix_set_ops[3] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("[)")); + result.accel.postfix_set_flags[3] = MD_NodeFlag_HasBracketLeft | MD_NodeFlag_HasParenRight; + + result.accel.postfix_set_ops[4] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("(]")); + result.accel.postfix_set_flags[4] = MD_NodeFlag_HasParenLeft | MD_NodeFlag_HasBracketRight; + + return(result); +} + +MD_FUNCTION MD_Expr* +MD_ExprParse_TopLevel(MD_Arena *arena, MD_ExprParseCtx *ctx, MD_Node *first, MD_Node *opl) +{ + // parse the node range + MD_Node *iter = first; + MD_Expr *expr = MD_ExprParse_MinPrecedence(arena, ctx, &iter, first, opl, 0); + + // check for failed-to-reach-end error + if(ctx->errors.max_message_kind == MD_MessageKind_Null) + { + MD_Node *stop_node = iter; + if(!MD_NodeIsNil(stop_node)) + { + MD_String8 error_str = MD_S8Lit("Expected binary or unary postfix operator."); + MD_Message *error = MD_MakeNodeError(arena,stop_node,MD_MessageKind_FatalError,error_str); + MD_MessageListPush(&ctx->errors, error); + } + } + + return(expr); +} + +MD_FUNCTION MD_b32 +MD_ExprParse_OprConsume(MD_ExprParseCtx *ctx, MD_Node **iter, MD_Node *opl, + MD_ExprOprKind kind, MD_u32 min_precedence, MD_ExprOpr **op_out) +{ + MD_b32 result = 0; + MD_Node *node = *iter; + if(!MD_NodeIsNil(node)) + { + MD_ExprOpr *op = MD_ExprOprFromKindString(ctx->op_table, kind, node->string); + if(op != 0 && op->precedence >= min_precedence) + { + result = 1; + *op_out = op; + *iter= MD_NodeNextWithLimit(*iter, opl); + } + } + return result; +} + +MD_FUNCTION MD_Expr* +MD_ExprParse_Atom(MD_Arena *arena, MD_ExprParseCtx *ctx, MD_Node **iter, + MD_Node *first, MD_Node *opl) +{ + // TODO(allen): nil + MD_Expr* result = 0; + + MD_Node *node = *iter; + MD_ExprOpr *op = 0; + + if(MD_NodeIsNil(node)) + { + MD_Node *last = first; + for (;last->next != opl; last = last->next); + + MD_Node *error_node = last->next; + if (MD_NodeIsNil(error_node)) + { + MD_Node *root = MD_RootFromNode(node); + MD_String8 parse_contents = root->raw_string; + MD_u64 offset = last->offset + last->raw_string.size; + error_node = MD_MakeErrorMarkerNode(arena, parse_contents, offset); + } + + MD_String8 error_str = MD_S8Lit("Unexpected end of expression."); + MD_Message *error = MD_MakeNodeError(arena, error_node, MD_MessageKind_FatalError, + error_str); + MD_MessageListPush(&ctx->errors, error); + } + else if((node->flags & MD_NodeFlag_HasParenLeft) && + (node->flags & MD_NodeFlag_HasParenRight)) + { // NOTE(mal): Parens + *iter = MD_NodeNextWithLimit(*iter, opl); + result = MD_ExprParse_TopLevel(arena, ctx, node->first_child, MD_NilNode()); + } + else if(((node->flags & MD_NodeFlag_HasBraceLeft) && (node->flags & MD_NodeFlag_HasBraceRight)) || + ((node->flags & MD_NodeFlag_HasBracketLeft) && (node->flags & MD_NodeFlag_HasBracketRight)) || + ((node->flags & MD_NodeFlag_HasBracketLeft) && (node->flags & MD_NodeFlag_HasParenRight)) || + ((node->flags & MD_NodeFlag_HasParenLeft) && (node->flags & MD_NodeFlag_HasBracketRight))) + { // NOTE(mal): Unparsed leaf sets ({...}, [...], [...), (...]) + *iter = MD_NodeNextWithLimit(*iter, opl); + result = MD_Expr_NewLeaf(arena, node); + } + else if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Prefix, 1, &op)) + { + MD_u32 min_precedence = op->precedence + 1; + MD_Expr *sub_expr = + MD_ExprParse_MinPrecedence(arena, ctx, iter, first, opl, min_precedence); + if(ctx->errors.max_message_kind == MD_MessageKind_Null) + { + result = MD_Expr_NewOpr(arena, op, node, sub_expr, 0); + } + } + else if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Null, 1, &op)) + { + MD_String8 error_str = MD_S8Fmt(arena, "Expected leaf. Got operator \"%.*s\".", MD_S8VArg(node->string)); + + MD_Message *error = MD_MakeNodeError(arena, node, MD_MessageKind_FatalError, error_str); + MD_MessageListPush(&ctx->errors, error); + } + else if(node->flags & + (MD_NodeFlag_HasParenLeft|MD_NodeFlag_HasParenRight|MD_NodeFlag_HasBracketLeft| + MD_NodeFlag_HasBracketRight|MD_NodeFlag_HasBraceLeft|MD_NodeFlag_HasBraceRight)) + { + MD_String8 error_str = MD_S8Fmt(arena, "Unexpected set.", MD_S8VArg(node->string)); + MD_Message *error = MD_MakeNodeError(arena, node, MD_MessageKind_FatalError, error_str); + MD_MessageListPush(&ctx->errors, error); + } + else{ // NOTE(mal): leaf + *iter = MD_NodeNextWithLimit(*iter, opl); + result = MD_Expr_NewLeaf(arena, node); + } + + return(result); +} + +MD_FUNCTION MD_Expr* +MD_ExprParse_MinPrecedence(MD_Arena *arena, MD_ExprParseCtx *ctx, + MD_Node **iter, MD_Node *first, MD_Node *opl, + MD_u32 min_precedence) +{ + // TODO(allen): nil + MD_Expr* result = 0; + + result = MD_ExprParse_Atom(arena, ctx, iter, first, opl); + if(ctx->errors.max_message_kind == MD_MessageKind_Null) + { + for (;!MD_NodeIsNil(*iter);) + { + MD_Node *node = *iter; + MD_ExprOpr *op = 0; + + if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Binary, + min_precedence, &op) || + MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_BinaryRightAssociative, + min_precedence, &op)) + { + MD_u32 next_min_precedence = op->precedence + (op->kind == MD_ExprOprKind_Binary); + MD_Expr *sub_expr = + MD_ExprParse_MinPrecedence(arena, ctx, iter, first, opl, next_min_precedence); + if(ctx->errors.max_message_kind == MD_MessageKind_Null) + { + result = MD_Expr_NewOpr(arena, op, node, result, sub_expr); + } + else{ + break; + } + } + + else + { + MD_b32 found_postfix_setlike_operator = 0; + for(MD_u32 i_op = 0; + i_op < MD_ArrayCount(ctx->accel.postfix_set_ops); + ++i_op) + { + MD_ExprOpr *op2 = ctx->accel.postfix_set_ops[i_op]; + if(op2 && op2->precedence >= min_precedence && + node->flags == ctx->accel.postfix_set_flags[i_op]) + { + *iter = MD_NodeNextWithLimit(*iter, opl); + result = MD_Expr_NewOpr(arena, op2, node, result, 0); + found_postfix_setlike_operator = 1; + break; + } + } + + if(!found_postfix_setlike_operator) + { + if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Postfix, + min_precedence, &op)) + { + result = MD_Expr_NewOpr(arena, op, node, result, 0); + } + else + { + break; // NOTE: Due to lack of progress + } + } + + } + + } + } + + return(result); +} + + + + +//~ String Generation + +MD_FUNCTION void +MD_DebugDumpFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, + int indent, MD_String8 indent_string, MD_GenerateFlags flags) +{ +#define MD_PrintIndent(_indent_level) do\ +{\ +for(int i = 0; i < (_indent_level); i += 1)\ +{\ +MD_S8ListPush(arena, out, indent_string);\ +}\ +}while(0) + + //- rjf: prev-comment + if(flags & MD_GenerateFlag_Comments && node->prev_comment.size != 0) + { + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, node->prev_comment); + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("*/\n")); + } + + //- rjf: tags of node + if(flags & MD_GenerateFlag_Tags) + { + for(MD_EachNode(tag, node->first_tag)) + { + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("@")); + MD_S8ListPush(arena, out, tag->string); + if(flags & MD_GenerateFlag_TagArguments && !MD_NodeIsNil(tag->first_child)) + { + int tag_arg_indent = (int)(indent + 1 + tag->string.size + 1); + MD_S8ListPush(arena, out, MD_S8Lit("(")); + for(MD_EachNode(child, tag->first_child)) + { + int child_indent = tag_arg_indent; + if(MD_NodeIsNil(child->prev)) + { + child_indent = 0; + } + MD_DebugDumpFromNode(arena, out, child, child_indent, MD_S8Lit(" "), flags); + if(!MD_NodeIsNil(child->next)) + { + MD_S8ListPush(arena, out, MD_S8Lit(",\n")); + } + } + MD_S8ListPush(arena, out, MD_S8Lit(")\n")); + } + else + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + } + } + } + + //- rjf: node kind + if(flags & MD_GenerateFlag_NodeKind) + { + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("// kind: \"")); + MD_S8ListPush(arena, out, MD_StringFromNodeKind(node->kind)); + MD_S8ListPush(arena, out, MD_S8Lit("\"\n")); + } + + //- rjf: node flags + if(flags & MD_GenerateFlag_NodeFlags) + { + MD_PrintIndent(indent); + MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); + MD_String8List flag_strs = MD_StringListFromNodeFlags(scratch.arena, node->flags); + MD_StringJoin join = { MD_S8LitComp(""), MD_S8LitComp("|"), MD_S8LitComp("") }; + MD_String8 flag_str = MD_S8ListJoin(arena, flag_strs, &join); + MD_S8ListPush(arena, out, MD_S8Lit("// flags: \"")); + MD_S8ListPush(arena, out, flag_str); + MD_S8ListPush(arena, out, MD_S8Lit("\"\n")); + MD_ReleaseScratch(scratch); + } + + //- rjf: location + if(flags & MD_GenerateFlag_Location) + { + MD_PrintIndent(indent); + MD_CodeLoc loc = MD_CodeLocFromNode(node); + MD_String8 string = MD_S8Fmt(arena, "// location: %.*s:%i:%i\n", MD_S8VArg(loc.filename), (int)loc.line, (int)loc.column); + MD_S8ListPush(arena, out, string); + } + + //- rjf: name of node + if(node->string.size != 0) + { + MD_PrintIndent(indent); + if(node->kind == MD_NodeKind_File) + { + MD_S8ListPush(arena, out, MD_S8Lit("`")); + MD_S8ListPush(arena, out, node->string); + MD_S8ListPush(arena, out, MD_S8Lit("`")); + } + else + { + MD_S8ListPush(arena, out, node->raw_string); + } + } + + //- rjf: children list + if(flags & MD_GenerateFlag_Children && !MD_NodeIsNil(node->first_child)) + { + if(node->string.size != 0) + { + MD_S8ListPush(arena, out, MD_S8Lit(":\n")); + } + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("{\n")); + for(MD_EachNode(child, node->first_child)) + { + MD_DebugDumpFromNode(arena, out, child, indent+1, indent_string, flags); + MD_S8ListPush(arena, out, MD_S8Lit(",\n")); + } + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("}")); + } + + //- rjf: next-comment + if(flags & MD_GenerateFlag_Comments && node->next_comment.size != 0) + { + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("\n/*\n")); + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, node->next_comment); + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("*/\n")); + } + +#undef MD_PrintIndent +} + +MD_FUNCTION void +MD_ReconstructionFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, + int indent, MD_String8 indent_string) +{ + MD_CodeLoc code_loc = MD_CodeLocFromNode(node); + +#define MD_PrintIndent(_indent_level) do\ +{\ +for(int i = 0; i < (_indent_level); i += 1)\ +{\ +MD_S8ListPush(arena, out, indent_string);\ +}\ +}while(0) + + //- rjf: prev-comment + if(node->prev_comment.size != 0) + { + MD_String8 comment = MD_S8SkipWhitespace(MD_S8ChopWhitespace(node->prev_comment)); + MD_b32 requires_multiline = MD_S8FindSubstring(comment, MD_S8Lit("\n"), 0, 0) < comment.size; + MD_PrintIndent(indent); + if(requires_multiline) + { + MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); + } + else + { + MD_S8ListPush(arena, out, MD_S8Lit("// ")); + } + MD_S8ListPush(arena, out, comment); + if(requires_multiline) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n*/\n")); + } + else + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + } + } + + //- rjf: tags of node + MD_u32 tag_first_line = MD_CodeLocFromNode(node->first_tag).line; + MD_u32 tag_last_line = tag_first_line; + { + for(MD_EachNode(tag, node->first_tag)) + { + MD_u32 tag_line = MD_CodeLocFromNode(tag).line; + if(tag_line != tag_last_line) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + tag_last_line = tag_line; + } + else if(!MD_NodeIsNil(tag->prev)) + { + MD_S8ListPush(arena, out, MD_S8Lit(" ")); + } + + MD_PrintIndent(indent); + MD_S8ListPush(arena, out, MD_S8Lit("@")); + MD_S8ListPush(arena, out, tag->string); + if(!MD_NodeIsNil(tag->first_child)) + { + int tag_arg_indent = (int)(indent + 1 + tag->string.size + 1); + MD_S8ListPush(arena, out, MD_S8Lit("(")); + MD_u32 last_line = MD_CodeLocFromNode(tag).line; + for(MD_EachNode(child, tag->first_child)) + { + MD_CodeLoc child_loc = MD_CodeLocFromNode(child); + if(child_loc.line != last_line) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + MD_PrintIndent(indent); + } + last_line = child_loc.line; + + int child_indent = tag_arg_indent; + if(MD_NodeIsNil(child->prev)) + { + child_indent = 0; + } + MD_ReconstructionFromNode(arena, out, child, child_indent, MD_S8Lit(" ")); + if(!MD_NodeIsNil(child->next)) + { + MD_S8ListPush(arena, out, MD_S8Lit(",\n")); + } + } + MD_S8ListPush(arena, out, MD_S8Lit(")")); + } + } + } + + //- rjf: name of node + if(node->string.size != 0) + { + if(tag_first_line != tag_last_line) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + MD_PrintIndent(indent); + } + else if(!MD_NodeIsNil(node->first_tag) || !MD_NodeIsNil(node->prev)) + { + MD_S8ListPush(arena, out, MD_S8Lit(" ")); + } + if(node->kind == MD_NodeKind_File) + { + MD_S8ListPush(arena, out, MD_S8Lit("`")); + MD_S8ListPush(arena, out, node->string); + MD_S8ListPush(arena, out, MD_S8Lit("`")); + } + else + { + MD_S8ListPush(arena, out, node->raw_string); + } + } + + //- rjf: children list + if(!MD_NodeIsNil(node->first_child)) + { + if(node->string.size != 0) + { + MD_S8ListPush(arena, out, MD_S8Lit(":")); + } + + // rjf: figure out opener/closer symbols + MD_u8 opener_char = 0; + MD_u8 closer_char = 0; + if(node->flags & MD_NodeFlag_HasParenLeft) { opener_char = '('; } + else if(node->flags & MD_NodeFlag_HasBracketLeft) { opener_char = '['; } + else if(node->flags & MD_NodeFlag_HasBraceLeft) { opener_char = '{'; } + if(node->flags & MD_NodeFlag_HasParenRight) { closer_char = ')'; } + else if(node->flags & MD_NodeFlag_HasBracketRight){ closer_char = ']'; } + else if(node->flags & MD_NodeFlag_HasBraceRight) { closer_char = '}'; } + + MD_b32 multiline = 0; + for(MD_EachNode(child, node->first_child)) + { + MD_CodeLoc child_loc = MD_CodeLocFromNode(child); + if(child_loc.line != code_loc.line) + { + multiline = 1; + break; + } + } + + if(opener_char != 0) + { + if(multiline) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + MD_PrintIndent(indent); + } + else + { + MD_S8ListPush(arena, out, MD_S8Lit(" ")); + } + MD_S8ListPush(arena, out, MD_S8(&opener_char, 1)); + if(multiline) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + MD_PrintIndent(indent+1); + } + } + MD_u32 last_line = MD_CodeLocFromNode(node->first_child).line; + for(MD_EachNode(child, node->first_child)) + { + int child_indent = 0; + MD_CodeLoc child_loc = MD_CodeLocFromNode(child); + if(child_loc.line != last_line) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + MD_PrintIndent(indent); + child_indent = indent+1; + } + last_line = child_loc.line; + MD_ReconstructionFromNode(arena, out, child, child_indent, indent_string); + } + MD_PrintIndent(indent); + if(closer_char != 0) + { + if(last_line != code_loc.line) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + MD_PrintIndent(indent); + } + else + { + MD_S8ListPush(arena, out, MD_S8Lit(" ")); + } + MD_S8ListPush(arena, out, MD_S8(&closer_char, 1)); + } + } + + //- rjf: trailing separator symbols + if(node->flags & MD_NodeFlag_IsBeforeSemicolon) + { + MD_S8ListPush(arena, out, MD_S8Lit(";")); + } + else if(node->flags & MD_NodeFlag_IsBeforeComma) + { + MD_S8ListPush(arena, out, MD_S8Lit(",")); + } + + //- rjf: next-comment + // TODO(rjf): @node_comments + if(node->next_comment.size != 0) + { + MD_String8 comment = MD_S8SkipWhitespace(MD_S8ChopWhitespace(node->next_comment)); + MD_b32 requires_multiline = MD_S8FindSubstring(comment, MD_S8Lit("\n"), 0, 0) < comment.size; + MD_PrintIndent(indent); + if(requires_multiline) + { + MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); + } + else + { + MD_S8ListPush(arena, out, MD_S8Lit("// ")); + } + MD_S8ListPush(arena, out, comment); + if(requires_multiline) + { + MD_S8ListPush(arena, out, MD_S8Lit("\n*/\n")); + } + else + { + MD_S8ListPush(arena, out, MD_S8Lit("\n")); + } + } + +#undef MD_PrintIndent +} + + +#if !MD_DISABLE_PRINT_HELPERS +MD_FUNCTION void +MD_PrintDebugDumpFromNode(FILE *file, MD_Node *node, MD_GenerateFlags flags) +{ + MD_ArenaTemp scratch = MD_GetScratch(0, 0); + MD_String8List list = {0}; + MD_DebugDumpFromNode(scratch.arena, &list, node, + 0, MD_S8Lit(" "), flags); + MD_String8 string = MD_S8ListJoin(scratch.arena, list, 0); + fwrite(string.str, string.size, 1, file); + MD_ReleaseScratch(scratch); +} +#endif + + +//~ Command Line Argument Helper + +MD_FUNCTION MD_String8List +MD_StringListFromArgCV(MD_Arena *arena, int argument_count, char **arguments) +{ + MD_String8List options = MD_ZERO_STRUCT; + for(int i = 1; i < argument_count; i += 1) + { + MD_S8ListPush(arena, &options, MD_S8CString(arguments[i])); + } + return options; +} + +MD_FUNCTION MD_CmdLine +MD_MakeCmdLineFromOptions(MD_Arena *arena, MD_String8List options) +{ + MD_CmdLine cmdln = MD_ZERO_STRUCT; + MD_b32 parsing_only_inputs = 0; + + for(MD_String8Node *n = options.first, *next = 0; + n; n = next) + { + next = n->next; + + //- rjf: figure out whether or not this is an option by checking for `-` or `--` + // from the beginning of the string + MD_String8 option_name = MD_ZERO_STRUCT; + if(MD_S8Match(n->string, MD_S8Lit("--"), 0)) + { + parsing_only_inputs = 1; + } + else if(MD_S8Match(MD_S8Prefix(n->string, 2), MD_S8Lit("--"), 0)) + { + option_name = MD_S8Skip(n->string, 2); + } + else if(MD_S8Match(MD_S8Prefix(n->string, 1), MD_S8Lit("-"), 0)) + { + option_name = MD_S8Skip(n->string, 1); + } + + //- rjf: trim off anything after a `:` or `=`, use that as the first value string + MD_String8 first_value = MD_ZERO_STRUCT; + MD_b32 has_many_values = 0; + if(option_name.size != 0) + { + MD_u64 colon_signifier_pos = MD_S8FindSubstring(option_name, MD_S8Lit(":"), 0, 0); + MD_u64 equal_signifier_pos = MD_S8FindSubstring(option_name, MD_S8Lit("="), 0, 0); + MD_u64 signifier_pos = MD_Min(colon_signifier_pos, equal_signifier_pos); + if(signifier_pos < option_name.size) + { + first_value = MD_S8Skip(option_name, signifier_pos+1); + option_name = MD_S8Prefix(option_name, signifier_pos); + if(MD_S8Match(MD_S8Suffix(first_value, 1), MD_S8Lit(","), 0)) + { + has_many_values = 1; + } + } + } + + //- rjf: gather arguments + if(option_name.size != 0 && !parsing_only_inputs) + { + MD_String8List option_values = MD_ZERO_STRUCT; + + //- rjf: push first value + if(first_value.size != 0) + { + MD_S8ListPush(arena, &option_values, first_value); + } + + //- rjf: scan next string values, add them to option values until we hit a lack + // of a ',' between values + if(has_many_values) + { + for(MD_String8Node *v = next; v; v = v->next, next = v) + { + MD_String8 value_str = v->string; + MD_b32 next_has_arguments = MD_S8Match(MD_S8Suffix(value_str, 1), MD_S8Lit(","), 0); + MD_b32 in_quotes = 0; + MD_u64 start = 0; + for(MD_u64 i = 0; i <= value_str.size; i += 1) + { + if(i == value_str.size || (value_str.str[i] == ',' && in_quotes == 0)) + { + if(start != i) + { + MD_S8ListPush(arena, &option_values, MD_S8Substring(value_str, start, i)); + } + start = i+1; + } + else if(value_str.str[i] == '"') + { + in_quotes = !in_quotes; + } + } + if(next_has_arguments == 0) + { + break; + } + } + } + + //- rjf: insert the fully parsed option + { + MD_CmdLineOption *opt = MD_PushArrayZero(arena, MD_CmdLineOption, 1); + opt->name = option_name; + opt->values = option_values; + if(cmdln.last_option == 0) + { + cmdln.first_option = cmdln.last_option = opt; + } + else + { + cmdln.last_option->next = opt; + cmdln.last_option = cmdln.last_option->next; + } + } + } + + //- rjf: this argument is not an option, push it to regular inputs list. + else + { + MD_S8ListPush(arena, &cmdln.inputs, n->string); + } + } + + return cmdln; +} + +MD_FUNCTION MD_String8List +MD_CmdLineValuesFromString(MD_CmdLine cmdln, MD_String8 name) +{ + MD_String8List values = MD_ZERO_STRUCT; + for(MD_CmdLineOption *opt = cmdln.first_option; opt; opt = opt->next) + { + if(MD_S8Match(opt->name, name, 0)) + { + values = opt->values; + break; + } + } + return values; +} + +MD_FUNCTION MD_b32 +MD_CmdLineB32FromString(MD_CmdLine cmdln, MD_String8 name) +{ + MD_b32 result = 0; + for(MD_CmdLineOption *opt = cmdln.first_option; opt; opt = opt->next) + { + if(MD_S8Match(opt->name, name, 0)) + { + result = 1; + break; + } + } + return result; +} + +MD_FUNCTION MD_i64 +MD_CmdLineI64FromString(MD_CmdLine cmdln, MD_String8 name) +{ + MD_String8List values = MD_CmdLineValuesFromString(cmdln, name); + MD_ArenaTemp scratch = MD_GetScratch(0, 0); + MD_String8 value_str = MD_S8ListJoin(scratch.arena, values, 0); + MD_i64 result = MD_CStyleIntFromString(value_str); + MD_ReleaseScratch(scratch); + return(result); +} + +//~ File System + +MD_FUNCTION MD_String8 +MD_LoadEntireFile(MD_Arena *arena, MD_String8 filename) +{ + MD_String8 result = MD_ZERO_STRUCT; +#if defined(MD_IMPL_LoadEntireFile) + result = MD_IMPL_LoadEntireFile(arena, filename); +#endif + return(result); +} + +MD_FUNCTION MD_b32 +MD_FileIterBegin(MD_FileIter *it, MD_String8 path) +{ +#if !defined(MD_IMPL_FileIterBegin) + return(0); +#else + return(MD_IMPL_FileIterBegin(it, path)); +#endif +} + +MD_FUNCTION MD_FileInfo +MD_FileIterNext(MD_Arena *arena, MD_FileIter *it) +{ +#if !defined(MD_IMPL_FileIterNext) + MD_FileInfo result = {0}; + return(result); +#else + return(MD_IMPL_FileIterNext(arena, it)); +#endif +} + +MD_FUNCTION void +MD_FileIterEnd(MD_FileIter *it) +{ +#if defined(MD_IMPL_FileIterEnd) + MD_IMPL_FileIterEnd(it); +#endif +} + +#endif // MD_C + +/* +Copyright 2021 Dion Systems LLC + +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. +*/ diff --git a/External/metadesk/md.h b/External/metadesk/md.h new file mode 100644 index 0000000..ff44627 --- /dev/null +++ b/External/metadesk/md.h @@ -0,0 +1,1248 @@ +// LICENSE AT END OF FILE (MIT). + +/* +** Welcome to Metadesk! +** +** Metadesk is a data description language designed to look like a programming +** language, and this is the accompanying parser library. While you are free to +** use it however you see fit, here are a couple of the uses we have intended +** to support: +** + quickly writing a C or C++ metaprogram from scratch +** + building "low budget" domain specific languages, such as marked-up +** webpage content, or asset metadata +** + creating robust and flexible config systems for applications +** +** If it's your first time with Metadesk, check out the "How to Build" section +** below, and consider looking at the examples included with the library. The +** examples_directory.txt will help you find your way from the intro examples +** through all the more advanced aspects of the library you might like to +** learn about. +** +** Direct issues, questions, suggestions, requests, etc to: +** https://github.com/Dion-Systems/metadesk +** +** +** How To Build: +** +** The library is set up as a direct source-include library, so if you have a +** single unit build you can just #include "md.h" and "md.c". If you have a +** multiple unit build you can #include "md.h" where necessary and add "md.c" +** as a separate compilation unit (extra care has to be taken if you intend to +** use overrides in a multiple unit build). +** +** See `bin/compile_flags.txt` for the flags to build with. +** +** The tests and examples can be built with the bash scripts in bin. There are +** a few things to know to use these scripts: +** 1. First you should run `bld_init.sh` which will initialize your copy of +** Metadesk's build system. +** 2. On Linux the shell scripts should work as written. On Windows you will +** need to use a bash interpreter specifically. Generally the `bash.exe` +** that comes with an install of git on Windows works well for this. +** Add it to your path or setup a batch script that calls it and then +** pass the bash scripts to the interpreter to build. +** 3. You should be able to run the scripts: +** `build_tests.sh` +** `build_examples.sh` +** `run_tests.sh` +** `run_examples.sh` +** `type_metadata_example.sh` +*/ + +#ifndef MD_H +#define MD_H + +#define MD_VERSION_MAJ 1 +#define MD_VERSION_MIN 0 + +//~ Set default values for controls +#if !defined(MD_DEFAULT_BASIC_TYPES) +# define MD_DEFAULT_BASIC_TYPES 1 +#endif +#if !defined(MD_DEFAULT_MEMSET) +# define MD_DEFAULT_MEMSET 1 +#endif +#if !defined(MD_DEFAULT_FILE_LOAD) +# define MD_DEFAULT_FILE_LOAD 1 +#endif +#if !defined(MD_DEFAULT_FILE_ITER) +# define MD_DEFAULT_FILE_ITER 1 +#endif +#if !defined(MD_DEFAULT_MEMORY) +# define MD_DEFAULT_MEMORY 1 +#endif +#if !defined(MD_DEFAULT_ARENA) +# define MD_DEFAULT_ARENA 1 +#endif +#if !defined(MD_DEFAULT_SCRATCH) +# define MD_DEFAULT_SCRATCH 1 +#endif +#if !defined(MD_DEFAULT_SPRINTF) +# define MD_DEFAULT_SPRINTF 1 +#endif + +#if !defined(MD_DISABLE_PRINT_HELPERS) +# define MD_DISABLE_PRINT_HELPERS 0 +#endif + + +//~///////////////////////////////////////////////////////////////////////////// +////////////////////////////// Context Cracking //////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__clang__) + +# define MD_COMPILER_CLANG 1 + +# if defined(__APPLE__) && defined(__MACH__) +# define MD_OS_MAC 1 +# elif defined(__gnu_linux__) +# define MD_OS_LINUX 1 +# elif defined(_WIN32) +# define MD_OS_WINDOWS 1 +# else +# error This compiler/platform combo is not supported yet +# endif + +# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) +# define MD_ARCH_X64 1 +# elif defined(i386) || defined(__i386) || defined(__i386__) +# define MD_ARCH_X86 1 +# elif defined(__aarch64__) +# define MD_ARCH_ARM64 1 +# elif defined(__arm__) +# define MD_ARCH_ARM32 1 +# else +# error architecture not supported yet +# endif + +#elif defined(_MSC_VER) + +# define MD_COMPILER_CL 1 + +# if defined(_WIN32) +# define MD_OS_WINDOWS 1 +# else +# error This compiler/platform combo is not supported yet +# endif + +# if defined(_M_AMD64) +# define MD_ARCH_X64 1 +# elif defined(_M_IX86) +# define MD_ARCH_X86 1 +# elif defined(_M_ARM64) +# define MD_ARCH_ARM64 1 +# elif defined(_M_ARM) +# define MD_ARCH_ARM32 1 +# else +# error architecture not supported yet +# endif + +# if _MSC_VER >= 1920 +# define MD_COMPILER_CL_YEAR 2019 +# elif _MSC_VER >= 1910 +# define MD_COMPILER_CL_YEAR 2017 +# elif _MSC_VER >= 1900 +# define MD_COMPILER_CL_YEAR 2015 +# elif _MSC_VER >= 1800 +# define MD_COMPILER_CL_YEAR 2013 +# elif _MSC_VER >= 1700 +# define MD_COMPILER_CL_YEAR 2012 +# elif _MSC_VER >= 1600 +# define MD_COMPILER_CL_YEAR 2010 +# elif _MSC_VER >= 1500 +# define MD_COMPILER_CL_YEAR 2008 +# elif _MSC_VER >= 1400 +# define MD_COMPILER_CL_YEAR 2005 +# else +# define MD_COMPILER_CL_YEAR 0 +# endif + +#elif defined(__GNUC__) || defined(__GNUG__) + +# define MD_COMPILER_GCC 1 + +# if defined(__gnu_linux__) +# define MD_OS_LINUX 1 +# else +# error This compiler/platform combo is not supported yet +# endif + +# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) +# define MD_ARCH_X64 1 +# elif defined(i386) || defined(__i386) || defined(__i386__) +# define MD_ARCH_X86 1 +# elif defined(__aarch64__) +# define MD_ARCH_ARM64 1 +# elif defined(__arm__) +# define MD_ARCH_ARM32 1 +# else +# error architecture not supported yet +# endif + +#else +# error This compiler is not supported yet +#endif + +#if defined(MD_ARCH_X64) +# define MD_ARCH_64BIT 1 +#elif defined(MD_ARCH_X86) +# define MD_ARCH_32BIT 1 +#endif + +#if defined(__cplusplus) +# define MD_LANG_CPP 1 + +// We can't get this 100% correct thanks to Microsoft's compiler. +// So this check lets us pre-define MD_CPP_VERSION if we have to. +# if !defined(MD_CPP_VERSION) +# if defined(MD_COMPILER_CL) +// CL is annoying and didn't update __cplusplus over time +// If it is available _MSVC_LANG serves the same role +# if defined(_MSVC_LANG) +# if _MSVC_LANG <= 199711L +# define MD_CPP_VERSION 98 +# elif _MSVC_LANG <= 201103L +# define MD_CPP_VERSION 11 +# elif _MSVC_LANG <= 201402L +# define MD_CPP_VERSION 14 +# elif _MSVC_LANG <= 201703L +# define MD_CPP_VERSION 17 +# elif _MSVC_LANG <= 202002L +# define MD_CPP_VERSION 20 +# else +# define MD_CPP_VERSION 23 +# endif +// If we don't have _MSVC_LANG we can guess from the compiler version +# else +# if MD_COMPILER_CL_YEAR <= 2010 +# define MD_CPP_VERSION 98 +# elif MD_COMPILER_CL_YEAR <= 2015 +# define MD_CPP_VERSION 11 +# else +# define MD_CPP_VERSION 17 +# endif +# endif +# else +// Other compilers use __cplusplus correctly +# if __cplusplus <= 199711L +# define MD_CPP_VERSION 98 +# elif __cplusplus <= 201103L +# define MD_CPP_VERSION 11 +# elif __cplusplus <= 201402L +# define MD_CPP_VERSION 14 +# elif __cplusplus <= 201703L +# define MD_CPP_VERSION 17 +# elif __cplusplus <= 202002L +# define MD_CPP_VERSION 20 +# else +# define MD_CPP_VERSION 23 +# endif +# endif +# endif + +#else +# define MD_LANG_C 1 +#endif + +// zeroify + +#if !defined(MD_ARCH_32BIT) +# define MD_ARCH_32BIT 0 +#endif +#if !defined(MD_ARCH_64BIT) +# define MD_ARCH_64BIT 0 +#endif +#if !defined(MD_ARCH_X64) +# define MD_ARCH_X64 0 +#endif +#if !defined(MD_ARCH_X86) +# define MD_ARCH_X86 0 +#endif +#if !defined(MD_ARCH_ARM64) +# define MD_ARCH_ARM64 0 +#endif +#if !defined(MD_ARCH_ARM32) +# define MD_ARCH_ARM32 0 +#endif +#if !defined(MD_COMPILER_CL) +# define MD_COMPILER_CL 0 +#endif +#if !defined(MD_COMPILER_GCC) +# define MD_COMPILER_GCC 0 +#endif +#if !defined(MD_COMPILER_CLANG) +# define MD_COMPILER_CLANG 0 +#endif +#if !defined(MD_OS_WINDOWS) +# define MD_OS_WINDOWS 0 +#endif +#if !defined(MD_OS_LINUX) +# define MD_OS_LINUX 0 +#endif +#if !defined(MD_OS_MAC) +# define MD_OS_MAC 0 +#endif +#if !defined(MD_LANG_C) +# define MD_LANG_C 0 +#endif +#if !defined(MD_LANG_CPP) +# define MD_LANG_CPP 0 +#endif +#if !defined(MD_CPP_VERSION) +# define MD_CPP_VERSION 0 +#endif + +#if MD_LANG_CPP +# define MD_ZERO_STRUCT {} +#else +# define MD_ZERO_STRUCT {0} +#endif + +#if MD_LANG_C +# define MD_C_LINKAGE_BEGIN +# define MD_C_LINKAGE_END +#else +# define MD_C_LINKAGE_BEGIN extern "C"{ +# define MD_C_LINKAGE_END } +#endif + +#if MD_COMPILER_CL +# define MD_THREAD_LOCAL __declspec(thread) +#elif MD_COMPILER_GCC || MD_COMPILER_CLANG +# define MD_THREAD_LOCAL __thread +#endif + +//~///////////////////////////////////////////////////////////////////////////// +///////////////////////////// Helpers, Macros, Etc ///////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +//~ Linkage Wrappers + +#if !defined(MD_FUNCTION) +# define MD_FUNCTION +#endif + +#if !defined(MD_GLOBAL) +# define MD_GLOBAL static +#endif + +//~ Basic Utilities + +#define MD_Assert(c) if (!(c)) { *(volatile MD_u64 *)0 = 0; } +#define MD_StaticAssert(c,label) MD_u8 MD_static_assert_##label[(c)?(1):(-1)] +#define MD_ArrayCount(a) (sizeof(a) / sizeof((a)[0])) + +#define MD_Min(a,b) (((a)<(b))?(a):(b)) +#define MD_Max(a,b) (((a)>(b))?(a):(b)) +#define MD_ClampBot(a,b) MD_Max(a,b) +#define MD_ClampTop(a,b) MD_Min(a,b) + +#define MD_AlignPow2(x,b) (((x)+((b)-1))&(~((b)-1))) + +//~ Linked List Macros + +// terminator modes +#define MD_CheckNull(p) ((p)==0) +#define MD_SetNull(p) ((p)=0) +#define MD_CheckNil(p) (MD_NodeIsNil(p)) +#define MD_SetNil(p) ((p)=MD_NilNode()) + +// implementations +#define MD_QueuePush_NZ(f,l,n,next,zchk,zset) (zchk(f)?\ +(f)=(l)=(n):\ +((l)->next=(n),(l)=(n),zset((n)->next))) +#define MD_QueuePop_NZ(f,l,next,zset) ((f)==(l)?\ +(zset(f),zset(l)):\ +((f)=(f)->next)) +#define MD_StackPush_N(f,n,next) ((n)->next=(f),(f)=(n)) +#define MD_StackPop_NZ(f,next,zchk) (zchk(f)?0:(f)=(f)->next) + +#define MD_DblPushBack_NPZ(f,l,n,next,prev,zchk,zset) \ +(zchk(f)?\ +((f)=(l)=(n),zset((n)->next),zset((n)->prev)):\ +((n)->prev=(l),(l)->next=(n),(l)=(n),zset((n)->next))) +#define MD_DblRemove_NPZ(f,l,n,next,prev,zset) (((f)==(n)?\ +((f)=(f)->next,zset((f)->prev)):\ +(l)==(n)?\ +((l)=(l)->prev,zset((l)->next)):\ +((n)->next->prev=(n)->prev,\ +(n)->prev->next=(n)->next))) + +// compositions +#define MD_QueuePush(f,l,n) MD_QueuePush_NZ(f,l,n,next,MD_CheckNull,MD_SetNull) +#define MD_QueuePop(f,l) MD_QueuePop_NZ(f,l,next,MD_SetNull) +#define MD_StackPush(f,n) MD_StackPush_N(f,n,next) +#define MD_StackPop(f) MD_StackPop_NZ(f,next,MD_CheckNull) +#define MD_DblPushBack(f,l,n) MD_DblPushBack_NPZ(f,l,n,next,prev,MD_CheckNull,MD_SetNull) +#define MD_DblPushFront(f,l,n) MD_DblPushBack_NPZ(l,f,n,prev,next,MD_CheckNull,MD_SetNull) +#define MD_DblRemove(f,l,n) MD_DblRemove_NPZ(f,l,n,next,prev,MD_SetNull) + +#define MD_NodeDblPushBack(f,l,n) MD_DblPushBack_NPZ(f,l,n,next,prev,MD_CheckNil,MD_SetNil) +#define MD_NodeDblPushFront(f,l,n) MD_DblPushBack_NPZ(l,f,n,prev,next,MD_CheckNil,MD_SetNil) +#define MD_NodeDblRemove(f,l,n) MD_DblRemove_NPZ(f,l,n,next,prev,MD_SetNil) + + +//~ Memory Operations + +#define MD_MemorySet(p,v,z) (MD_IMPL_Memset(p,v,z)) +#define MD_MemoryZero(p,z) (MD_IMPL_Memset(p,0,z)) +#define MD_MemoryZeroStruct(p) (MD_IMPL_Memset(p,0,sizeof(*(p)))) +#define MD_MemoryCopy(d,s,z) (MD_IMPL_Memmove(d,s,z)) + +//~ sprintf +#if MD_DEFAULT_SPRINTF +#define STB_SPRINTF_DECORATE(name) md_stbsp_##name +#define MD_IMPL_Vsnprintf md_stbsp_vsnprintf +#include "md_stb_sprintf.h" +#endif + +//~///////////////////////////////////////////////////////////////////////////// +//////////////////////////////////// Types ///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +//~ Basic Types + +#include + +#if defined(MD_DEFAULT_BASIC_TYPES) + +#include +typedef int8_t MD_i8; +typedef int16_t MD_i16; +typedef int32_t MD_i32; +typedef int64_t MD_i64; +typedef uint8_t MD_u8; +typedef uint16_t MD_u16; +typedef uint32_t MD_u32; +typedef uint64_t MD_u64; +typedef float MD_f32; +typedef double MD_f64; + +#endif + +typedef MD_i8 MD_b8; +typedef MD_i16 MD_b16; +typedef MD_i32 MD_b32; +typedef MD_i64 MD_b64; + +//~ Default Arena + +#if MD_DEFAULT_ARENA + +typedef struct MD_ArenaDefault MD_ArenaDefault; +struct MD_ArenaDefault +{ + MD_ArenaDefault *prev; + MD_ArenaDefault *current; + MD_u64 base_pos; + MD_u64 pos; + MD_u64 cmt; + MD_u64 cap; + MD_u64 align; +}; +#define MD_IMPL_Arena MD_ArenaDefault + +#endif + +//~ Abstract Arena + +#if !defined(MD_IMPL_Arena) +# error Missing implementation for MD_IMPL_Arena +#endif + +typedef MD_IMPL_Arena MD_Arena; + +//~ Arena Helpers + +typedef struct MD_ArenaTemp MD_ArenaTemp; +struct MD_ArenaTemp +{ + MD_Arena *arena; + MD_u64 pos; +}; + +//~ Basic Unicode string types. + +typedef struct MD_String8 MD_String8; +struct MD_String8 +{ + MD_u8 *str; + MD_u64 size; +}; + +typedef struct MD_String16 MD_String16; +struct MD_String16 +{ + MD_u16 *str; + MD_u64 size; +}; + +typedef struct MD_String32 MD_String32; +struct MD_String32 +{ + MD_u32 *str; + MD_u64 size; +}; + +typedef struct MD_String8Node MD_String8Node; +struct MD_String8Node +{ + MD_String8Node *next; + MD_String8 string; +}; + +typedef struct MD_String8List MD_String8List; +struct MD_String8List +{ + MD_u64 node_count; + MD_u64 total_size; + MD_String8Node *first; + MD_String8Node *last; +}; + +typedef struct MD_StringJoin MD_StringJoin; +struct MD_StringJoin +{ + MD_String8 pre; + MD_String8 mid; + MD_String8 post; +}; + +// NOTE(rjf): @maintenance These three flag types must not overlap. +typedef MD_u32 MD_MatchFlags; +typedef MD_u32 MD_StringMatchFlags; +typedef MD_u32 MD_NodeMatchFlags; +enum +{ + MD_MatchFlag_FindLast = (1<<0), +}; +enum +{ + MD_StringMatchFlag_CaseInsensitive = (1<<4), + MD_StringMatchFlag_RightSideSloppy = (1<<5), + MD_StringMatchFlag_SlashInsensitive = (1<<6), +}; +enum +{ + MD_NodeMatchFlag_Tags = (1<<16), + MD_NodeMatchFlag_TagArguments = (1<<17), + MD_NodeMatchFlag_NodeFlags = (1<<18), +}; + +typedef struct MD_DecodedCodepoint MD_DecodedCodepoint; +struct MD_DecodedCodepoint +{ + MD_u32 codepoint; + MD_u32 advance; +}; + +typedef enum MD_IdentifierStyle +{ + MD_IdentifierStyle_UpperCamelCase, + MD_IdentifierStyle_LowerCamelCase, + MD_IdentifierStyle_UpperCase, + MD_IdentifierStyle_LowerCase, +} +MD_IdentifierStyle; + +//~ Node types that are used to build all ASTs. + +typedef enum MD_NodeKind +{ + // NOTE(rjf): @maintenance Must be kept in sync with MD_StringFromNodeKind. + + MD_NodeKind_Nil, + + // NOTE(rjf): Generated by parser + MD_NodeKind_File, + MD_NodeKind_ErrorMarker, + + // NOTE(rjf): Parsed from user Metadesk code + MD_NodeKind_Main, + MD_NodeKind_Tag, + + // NOTE(rjf): User-created data structures + MD_NodeKind_List, + MD_NodeKind_Reference, + + MD_NodeKind_COUNT, +} +MD_NodeKind; + +typedef MD_u64 MD_NodeFlags; +#define MD_NodeFlag_AfterFromBefore(f) ((f) << 1) +enum +{ + // NOTE(rjf): @maintenance Must be kept in sync with MD_StringListFromNodeFlags. + + // NOTE(rjf): @maintenance Because of MD_NodeFlag_AfterFromBefore, it is + // *required* that every single pair of "Before*" or "After*" flags be in + // the correct order which is that the Before* flag comes first, and the + // After* flag comes immediately after (After* being the more significant + // bit). + + MD_NodeFlag_HasParenLeft = (1<<0), + MD_NodeFlag_HasParenRight = (1<<1), + MD_NodeFlag_HasBracketLeft = (1<<2), + MD_NodeFlag_HasBracketRight = (1<<3), + MD_NodeFlag_HasBraceLeft = (1<<4), + MD_NodeFlag_HasBraceRight = (1<<5), + + MD_NodeFlag_MaskSetDelimiters = (0x3F<<0), + + MD_NodeFlag_IsBeforeSemicolon = (1<<6), + MD_NodeFlag_IsAfterSemicolon = (1<<7), + MD_NodeFlag_IsBeforeComma = (1<<8), + MD_NodeFlag_IsAfterComma = (1<<9), + + MD_NodeFlag_MaskSeperators = (0xF<<6), + + MD_NodeFlag_StringSingleQuote = (1<<10), + MD_NodeFlag_StringDoubleQuote = (1<<11), + MD_NodeFlag_StringTick = (1<<12), + MD_NodeFlag_StringTriplet = (1<<13), + + MD_NodeFlag_MaskStringDelimiters = (0xF<<10), + + MD_NodeFlag_Numeric = (1<<14), + MD_NodeFlag_Identifier = (1<<15), + MD_NodeFlag_StringLiteral = (1<<16), + MD_NodeFlag_Symbol = (1<<17), + + MD_NodeFlag_MaskLabelKind = (0xF<<14), +}; + +typedef struct MD_Node MD_Node; +struct MD_Node +{ + // Tree relationship data. + MD_Node *next; + MD_Node *prev; + MD_Node *parent; + MD_Node *first_child; + MD_Node *last_child; + + // Tag list. + MD_Node *first_tag; + MD_Node *last_tag; + + // Node info. + MD_NodeKind kind; + MD_NodeFlags flags; + MD_String8 string; + MD_String8 raw_string; + + // Source code location information. + MD_u64 offset; + + // Reference. + MD_Node *ref_target; + + // Comments. + // @usage prev_comment/next_comment should be considered "hidden". Rely on + // the functions MD_PrevCommentFromNode/MD_NextCommentFromNode to access + // these. Directly access to these is likely to break in a future version. + MD_String8 prev_comment; + MD_String8 next_comment; +}; + +//~ Code Location Info. + +typedef struct MD_CodeLoc MD_CodeLoc; +struct MD_CodeLoc +{ + MD_String8 filename; + MD_u32 line; + MD_u32 column; +}; + +//~ String-To-Ptr and Ptr-To-Ptr tables + +typedef struct MD_MapKey MD_MapKey; +struct MD_MapKey +{ + MD_u64 hash; + MD_u64 size; + void *ptr; +}; + +typedef struct MD_MapSlot MD_MapSlot; +struct MD_MapSlot +{ + MD_MapSlot *next; + MD_MapKey key; + void *val; +}; + +typedef struct MD_MapBucket MD_MapBucket; +struct MD_MapBucket +{ + MD_MapSlot *first; + MD_MapSlot *last; +}; + +typedef struct MD_Map MD_Map; +struct MD_Map +{ + MD_MapBucket *buckets; + MD_u64 bucket_count; +}; + +//~ Tokens + +typedef MD_u32 MD_TokenKind; +enum +{ + MD_TokenKind_Identifier = (1<<0), + MD_TokenKind_Numeric = (1<<1), + MD_TokenKind_StringLiteral = (1<<2), + MD_TokenKind_Symbol = (1<<3), + MD_TokenKind_Reserved = (1<<4), + MD_TokenKind_Comment = (1<<5), + MD_TokenKind_Whitespace = (1<<6), + MD_TokenKind_Newline = (1<<7), + MD_TokenKind_BrokenComment = (1<<8), + MD_TokenKind_BrokenStringLiteral = (1<<9), + MD_TokenKind_BadCharacter = (1<<10), +}; + +typedef MD_u32 MD_TokenGroups; +enum +{ + MD_TokenGroup_Comment = MD_TokenKind_Comment, + MD_TokenGroup_Whitespace = (MD_TokenKind_Whitespace| + MD_TokenKind_Newline), + MD_TokenGroup_Irregular = (MD_TokenGroup_Comment| + MD_TokenGroup_Whitespace), + MD_TokenGroup_Regular = ~MD_TokenGroup_Irregular, + MD_TokenGroup_Label = (MD_TokenKind_Identifier| + MD_TokenKind_Numeric| + MD_TokenKind_StringLiteral| + MD_TokenKind_Symbol), + MD_TokenGroup_Error = (MD_TokenKind_BrokenComment| + MD_TokenKind_BrokenStringLiteral| + MD_TokenKind_BadCharacter), +}; + +typedef struct MD_Token MD_Token; +struct MD_Token +{ + MD_TokenKind kind; + MD_NodeFlags node_flags; + MD_String8 string; + MD_String8 raw_string; +}; + +//~ Parsing State + +typedef enum MD_MessageKind +{ + // NOTE(rjf): @maintenance This enum needs to be sorted in order of + // severity. + MD_MessageKind_Null, + MD_MessageKind_Note, + MD_MessageKind_Warning, + MD_MessageKind_Error, + MD_MessageKind_FatalError, +} +MD_MessageKind; + +typedef struct MD_Message MD_Message; +struct MD_Message +{ + MD_Message *next; + MD_Node *node; + MD_MessageKind kind; + MD_String8 string; + void *user_ptr; +}; + +typedef struct MD_MessageList MD_MessageList; +struct MD_MessageList +{ + MD_MessageKind max_message_kind; + // TODO(allen): rename + MD_u64 node_count; + MD_Message *first; + MD_Message *last; +}; + +typedef enum MD_ParseSetRule +{ + MD_ParseSetRule_EndOnDelimiter, + MD_ParseSetRule_Global, +} MD_ParseSetRule; + +typedef struct MD_ParseResult MD_ParseResult; +struct MD_ParseResult +{ + MD_Node *node; + MD_u64 string_advance; + MD_MessageList errors; +}; + +//~ Expression Parsing + +typedef enum MD_ExprOprKind +{ + MD_ExprOprKind_Null, + MD_ExprOprKind_Prefix, + MD_ExprOprKind_Postfix, + MD_ExprOprKind_Binary, + MD_ExprOprKind_BinaryRightAssociative, + MD_ExprOprKind_COUNT, +} MD_ExprOprKind; + +typedef struct MD_ExprOpr MD_ExprOpr; +struct MD_ExprOpr +{ + struct MD_ExprOpr *next; + MD_u32 op_id; + MD_ExprOprKind kind; + MD_u32 precedence; + MD_String8 string; + void *op_ptr; +}; + +typedef struct MD_ExprOprList MD_ExprOprList; +struct MD_ExprOprList +{ + MD_ExprOpr *first; + MD_ExprOpr *last; + MD_u64 count; +}; + +typedef struct MD_ExprOprTable MD_ExprOprTable; +struct MD_ExprOprTable +{ + // TODO(mal): @upgrade_potential Hash? + MD_ExprOprList table[MD_ExprOprKind_COUNT]; +}; + +typedef struct MD_Expr MD_Expr; +struct MD_Expr +{ + struct MD_Expr *parent; + union + { + struct MD_Expr *left; + struct MD_Expr *unary_operand; + }; + struct MD_Expr *right; + MD_ExprOpr *op; + MD_Node *md_node; +}; + +typedef struct MD_ExprParseResult MD_ExprParseResult; +struct MD_ExprParseResult +{ + MD_Expr *expr; + MD_MessageList errors; +}; + +// TODO(allen): nil MD_Expr + +typedef struct MD_ExprParseCtx MD_ExprParseCtx; +struct MD_ExprParseCtx +{ + MD_ExprOprTable *op_table; + +#define MD_POSTFIX_SETLIKE_OP_COUNT 5 // (), [], {}, [), (] + struct + { + MD_ExprOpr *postfix_set_ops[MD_POSTFIX_SETLIKE_OP_COUNT]; + MD_NodeFlags postfix_set_flags[MD_POSTFIX_SETLIKE_OP_COUNT]; + } accel; +#undef MD_POSTFIX_SETLIKE_OP_COUNT + + MD_MessageList errors; +}; + +typedef void (*MD_BakeOperatorErrorHandler)(MD_MessageKind kind, MD_String8 s); + +//~ String Generation Types + +typedef MD_u32 MD_GenerateFlags; +enum +{ + MD_GenerateFlag_Tags = (1<<0), + MD_GenerateFlag_TagArguments = (1<<1), + MD_GenerateFlag_Children = (1<<2), + MD_GenerateFlag_Comments = (1<<3), + MD_GenerateFlag_NodeKind = (1<<4), + MD_GenerateFlag_NodeFlags = (1<<5), + MD_GenerateFlag_Location = (1<<6), + + MD_GenerateFlags_Tree = (MD_GenerateFlag_Tags | + MD_GenerateFlag_TagArguments | + MD_GenerateFlag_Children), + MD_GenerateFlags_All = 0xffffffff, +}; + +//~ Command line parsing helper types. + +typedef struct MD_CmdLineOption MD_CmdLineOption; +struct MD_CmdLineOption +{ + MD_CmdLineOption *next; + MD_String8 name; + MD_String8List values; +}; + +typedef struct MD_CmdLine MD_CmdLine; +struct MD_CmdLine +{ + MD_String8List inputs; + MD_CmdLineOption *first_option; + MD_CmdLineOption *last_option; +}; + +//~ File system access types. + +typedef MD_u32 MD_FileFlags; +enum +{ + MD_FileFlag_Directory = (1<<0), +}; + +typedef struct MD_FileInfo MD_FileInfo; +struct MD_FileInfo +{ + MD_FileFlags flags; + MD_String8 filename; + MD_u64 file_size; +}; + +typedef struct MD_FileIter MD_FileIter; +struct MD_FileIter +{ + // This is opaque state to store OS-specific file-system iteration data. + MD_u8 opaque[640]; +}; + +//~///////////////////////////////////////////////////////////////////////////// +////////////////////////////////// Functions /////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +//~ Arena + +MD_FUNCTION MD_Arena* MD_ArenaAlloc(void); +MD_FUNCTION void MD_ArenaRelease(MD_Arena *arena); + +MD_FUNCTION void* MD_ArenaPush(MD_Arena *arena, MD_u64 size); +MD_FUNCTION void MD_ArenaPutBack(MD_Arena *arena, MD_u64 size); +MD_FUNCTION void MD_ArenaSetAlign(MD_Arena *arena, MD_u64 boundary); +MD_FUNCTION void MD_ArenaPushAlign(MD_Arena *arena, MD_u64 boundary); +MD_FUNCTION void MD_ArenaClear(MD_Arena *arena); + +#define MD_PushArray(a,T,c) (T*)(MD_ArenaPush((a), sizeof(T)*(c))) +#define MD_PushArrayZero(a,T,c) (T*)(MD_MemoryZero(MD_PushArray(a,T,c), sizeof(T)*(c))) + +MD_FUNCTION MD_ArenaTemp MD_ArenaBeginTemp(MD_Arena *arena); +MD_FUNCTION void MD_ArenaEndTemp(MD_ArenaTemp temp); + +//~ Arena Scratch Pool + +MD_FUNCTION MD_ArenaTemp MD_GetScratch(MD_Arena **conflicts, MD_u64 count); + +#define MD_ReleaseScratch(scratch) MD_ArenaEndTemp(scratch) + +//~ Characters + +MD_FUNCTION MD_b32 MD_CharIsAlpha(MD_u8 c); +MD_FUNCTION MD_b32 MD_CharIsAlphaUpper(MD_u8 c); +MD_FUNCTION MD_b32 MD_CharIsAlphaLower(MD_u8 c); +MD_FUNCTION MD_b32 MD_CharIsDigit(MD_u8 c); +MD_FUNCTION MD_b32 MD_CharIsUnreservedSymbol(MD_u8 c); +MD_FUNCTION MD_b32 MD_CharIsReservedSymbol(MD_u8 c); +MD_FUNCTION MD_b32 MD_CharIsSpace(MD_u8 c); +MD_FUNCTION MD_u8 MD_CharToUpper(MD_u8 c); +MD_FUNCTION MD_u8 MD_CharToLower(MD_u8 c); +MD_FUNCTION MD_u8 MD_CharToForwardSlash(MD_u8 c); + +//~ Strings + +MD_FUNCTION MD_u64 MD_CalculateCStringLength(char *cstr); + +MD_FUNCTION MD_String8 MD_S8(MD_u8 *str, MD_u64 size); +#define MD_S8CString(s) MD_S8((MD_u8 *)(s), MD_CalculateCStringLength(s)) + +#if MD_LANG_C +# define MD_S8Lit(s) (MD_String8){(MD_u8 *)(s), sizeof(s)-1} +#elif MD_LANG_CPP +# define MD_S8Lit(s) MD_S8((MD_u8*)(s), sizeof(s) - 1) +#endif +#define MD_S8LitComp(s) {(MD_u8 *)(s), sizeof(s)-1} + +#if MD_CPP_VERSION != 0 && MD_CPP_VERSION != 98 // anything C++11 and up +static inline MD_String8 +operator "" _md(const char *s, size_t size) +{ + MD_String8 str = MD_S8((MD_u8 *)s, (MD_u64)size); + return str; +} +#endif + +MD_FUNCTION MD_String8 MD_S8Range(MD_u8 *first, MD_u8 *opl); + +MD_FUNCTION MD_String8 MD_S8Substring(MD_String8 str, MD_u64 min, MD_u64 max); +MD_FUNCTION MD_String8 MD_S8Skip(MD_String8 str, MD_u64 min); +MD_FUNCTION MD_String8 MD_S8Chop(MD_String8 str, MD_u64 nmax); +MD_FUNCTION MD_String8 MD_S8Prefix(MD_String8 str, MD_u64 size); +MD_FUNCTION MD_String8 MD_S8Suffix(MD_String8 str, MD_u64 size); + +MD_FUNCTION MD_b32 MD_S8Match(MD_String8 a, MD_String8 b, MD_MatchFlags flags); +MD_FUNCTION MD_u64 MD_S8FindSubstring(MD_String8 str, MD_String8 substring, + MD_u64 start_pos, MD_MatchFlags flags); + +MD_FUNCTION MD_String8 MD_S8Copy(MD_Arena *arena, MD_String8 string); +MD_FUNCTION MD_String8 MD_S8FmtV(MD_Arena *arena, char *fmt, va_list args); + +MD_FUNCTION MD_String8 MD_S8Fmt(MD_Arena *arena, char *fmt, ...); + +#define MD_S8VArg(s) (int)(s).size, (s).str + +MD_FUNCTION void MD_S8ListPush(MD_Arena *arena, MD_String8List *list, + MD_String8 string); +MD_FUNCTION void MD_S8ListPushFmt(MD_Arena *arena, MD_String8List *list, + char *fmt, ...); + +MD_FUNCTION void MD_S8ListConcat(MD_String8List *list, MD_String8List *to_push); +MD_FUNCTION MD_String8List MD_S8Split(MD_Arena *arena, MD_String8 string, int splitter_count, + MD_String8 *splitters); +MD_FUNCTION MD_String8 MD_S8ListJoin(MD_Arena *arena, MD_String8List list, + MD_StringJoin *join); +MD_FUNCTION MD_String8 MD_S8ListJoinMid(MD_Arena *arena, MD_String8List list, + MD_String8 mid_separator); + +MD_FUNCTION MD_String8 MD_S8Stylize(MD_Arena *arena, MD_String8 string, + MD_IdentifierStyle style, MD_String8 separator); + +//~ Unicode Conversions + +MD_FUNCTION MD_DecodedCodepoint MD_DecodeCodepointFromUtf8(MD_u8 *str, MD_u64 max); +MD_FUNCTION MD_DecodedCodepoint MD_DecodeCodepointFromUtf16(MD_u16 *str, MD_u64 max); +MD_FUNCTION MD_u32 MD_Utf8FromCodepoint(MD_u8 *out, MD_u32 codepoint); +MD_FUNCTION MD_u32 MD_Utf16FromCodepoint(MD_u16 *out, MD_u32 codepoint); +MD_FUNCTION MD_String8 MD_S8FromS16(MD_Arena *arena, MD_String16 str); +MD_FUNCTION MD_String16 MD_S16FromS8(MD_Arena *arena, MD_String8 str); +MD_FUNCTION MD_String8 MD_S8FromS32(MD_Arena *arena, MD_String32 str); +MD_FUNCTION MD_String32 MD_S32FromS8(MD_Arena *arena, MD_String8 str); + +//~ String Skipping/Chopping Helpers + +// This is intended for removing extensions. +MD_FUNCTION MD_String8 MD_PathChopLastPeriod(MD_String8 string); + +// This is intended for removing everything but the filename. +MD_FUNCTION MD_String8 MD_PathSkipLastSlash(MD_String8 string); + +// This is intended for getting an extension from a filename. +MD_FUNCTION MD_String8 MD_PathSkipLastPeriod(MD_String8 string); + +// This is intended for getting the folder string from a full path. +MD_FUNCTION MD_String8 MD_PathChopLastSlash(MD_String8 string); + +MD_FUNCTION MD_String8 MD_S8SkipWhitespace(MD_String8 string); +MD_FUNCTION MD_String8 MD_S8ChopWhitespace(MD_String8 string); + +//~ Numeric Strings + +MD_FUNCTION MD_b32 MD_StringIsU64(MD_String8 string, MD_u32 radix); +MD_FUNCTION MD_b32 MD_StringIsCStyleInt(MD_String8 string); + +MD_FUNCTION MD_u64 MD_U64FromString(MD_String8 string, MD_u32 radix); +MD_FUNCTION MD_i64 MD_CStyleIntFromString(MD_String8 string); +MD_FUNCTION MD_f64 MD_F64FromString(MD_String8 string); + +MD_FUNCTION MD_String8 MD_CStyleHexStringFromU64(MD_Arena *arena, MD_u64 x, MD_b32 caps); + +//~ Enum/Flag Strings + +MD_FUNCTION MD_String8 MD_StringFromNodeKind(MD_NodeKind kind); +MD_FUNCTION MD_String8List MD_StringListFromNodeFlags(MD_Arena *arena, MD_NodeFlags flags); + +//~ Map Table Data Structure + +MD_FUNCTION MD_u64 MD_HashStr(MD_String8 string); +MD_FUNCTION MD_u64 MD_HashPtr(void *p); + +MD_FUNCTION MD_Map MD_MapMakeBucketCount(MD_Arena *arena, MD_u64 bucket_count); +MD_FUNCTION MD_Map MD_MapMake(MD_Arena *arena); +MD_FUNCTION MD_MapKey MD_MapKeyStr(MD_String8 string); +MD_FUNCTION MD_MapKey MD_MapKeyPtr(void *ptr); +MD_FUNCTION MD_MapSlot* MD_MapLookup(MD_Map *map, MD_MapKey key); +MD_FUNCTION MD_MapSlot* MD_MapScan(MD_MapSlot *first_slot, MD_MapKey key); +MD_FUNCTION MD_MapSlot* MD_MapInsert(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val); +MD_FUNCTION MD_MapSlot* MD_MapOverwrite(MD_Arena *arena, MD_Map *map, MD_MapKey key, + void *val); + +//~ Parsing + +MD_FUNCTION MD_Token MD_TokenFromString(MD_String8 string); +MD_FUNCTION MD_u64 MD_LexAdvanceFromSkips(MD_String8 string, MD_TokenKind skip_kinds); +MD_FUNCTION MD_ParseResult MD_ParseResultZero(void); +MD_FUNCTION MD_ParseResult MD_ParseNodeSet(MD_Arena *arena, MD_String8 string, MD_u64 offset, MD_Node *parent, + MD_ParseSetRule rule); +MD_FUNCTION MD_ParseResult MD_ParseOneNode(MD_Arena *arena, MD_String8 string, MD_u64 offset); +MD_FUNCTION MD_ParseResult MD_ParseWholeString(MD_Arena *arena, MD_String8 filename, MD_String8 contents); + +MD_FUNCTION MD_ParseResult MD_ParseWholeFile(MD_Arena *arena, MD_String8 filename); + +//~ Messages (Errors/Warnings) + +MD_FUNCTION MD_Node* MD_MakeErrorMarkerNode(MD_Arena *arena, MD_String8 parse_contents, + MD_u64 offset); + +MD_FUNCTION MD_Message*MD_MakeNodeError(MD_Arena *arena, MD_Node *node, + MD_MessageKind kind, MD_String8 str); +MD_FUNCTION MD_Message*MD_MakeTokenError(MD_Arena *arena, MD_String8 parse_contents, + MD_Token token, MD_MessageKind kind, + MD_String8 str); + +MD_FUNCTION void MD_MessageListPush(MD_MessageList *list, MD_Message *message); +MD_FUNCTION void MD_MessageListConcat(MD_MessageList *list, MD_MessageList *to_push); + +//~ Location Conversion + +MD_FUNCTION MD_CodeLoc MD_CodeLocFromFileOffset(MD_String8 filename, MD_u8 *base, MD_u64 offset); +MD_FUNCTION MD_CodeLoc MD_CodeLocFromNode(MD_Node *node); + +//~ Tree/List Building + +MD_FUNCTION MD_b32 MD_NodeIsNil(MD_Node *node); +MD_FUNCTION MD_Node *MD_NilNode(void); +MD_FUNCTION MD_Node *MD_MakeNode(MD_Arena *arena, MD_NodeKind kind, MD_String8 string, + MD_String8 raw_string, MD_u64 offset); +MD_FUNCTION void MD_PushChild(MD_Node *parent, MD_Node *new_child); +MD_FUNCTION void MD_PushTag(MD_Node *node, MD_Node *tag); + +MD_FUNCTION MD_Node *MD_MakeList(MD_Arena *arena); +MD_FUNCTION void MD_ListConcatInPlace(MD_Node *list, MD_Node *to_push); +MD_FUNCTION MD_Node *MD_PushNewReference(MD_Arena *arena, MD_Node *list, MD_Node *target); + +//~ Introspection Helpers + +// These calls are for getting info from nodes, and introspecting +// on trees that are returned to you by the parser. + +MD_FUNCTION MD_Node * MD_FirstNodeWithString(MD_Node *first, MD_String8 string, MD_MatchFlags flags); +MD_FUNCTION MD_Node * MD_NodeAtIndex(MD_Node *first, int n); +MD_FUNCTION MD_Node * MD_FirstNodeWithFlags(MD_Node *first, MD_NodeFlags flags); +MD_FUNCTION int MD_IndexFromNode(MD_Node *node); +MD_FUNCTION MD_Node * MD_RootFromNode(MD_Node *node); +MD_FUNCTION MD_Node * MD_ChildFromString(MD_Node *node, MD_String8 child_string, MD_MatchFlags flags); +MD_FUNCTION MD_Node * MD_TagFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags); +MD_FUNCTION MD_Node * MD_ChildFromIndex(MD_Node *node, int n); +MD_FUNCTION MD_Node * MD_TagFromIndex(MD_Node *node, int n); +MD_FUNCTION MD_Node * MD_TagArgFromIndex(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags, int n); +MD_FUNCTION MD_Node * MD_TagArgFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags tag_str_flags, MD_String8 arg_string, MD_MatchFlags arg_str_flags); +MD_FUNCTION MD_b32 MD_NodeHasChild(MD_Node *node, MD_String8 string, MD_MatchFlags flags); +MD_FUNCTION MD_b32 MD_NodeHasTag(MD_Node *node, MD_String8 string, MD_MatchFlags flags); +MD_FUNCTION MD_i64 MD_ChildCountFromNode(MD_Node *node); +MD_FUNCTION MD_i64 MD_TagCountFromNode(MD_Node *node); +MD_FUNCTION MD_Node * MD_ResolveNodeFromReference(MD_Node *node); +MD_FUNCTION MD_Node* MD_NodeNextWithLimit(MD_Node *node, MD_Node *opl); + +MD_FUNCTION MD_String8 MD_PrevCommentFromNode(MD_Node *node); +MD_FUNCTION MD_String8 MD_NextCommentFromNode(MD_Node *node); + +// NOTE(rjf): For-Loop Helpers +#define MD_EachNode(it, first) MD_Node *it = (first); !MD_NodeIsNil(it); it = it->next + +//~ Error/Warning Helpers + +MD_FUNCTION MD_String8 MD_StringFromMessageKind(MD_MessageKind kind); + +#define MD_FmtCodeLoc "%.*s:%i:%i:" +#define MD_CodeLocVArg(loc) MD_S8VArg((loc).filename), (loc).line, (loc).column + +MD_FUNCTION MD_String8 MD_FormatMessage(MD_Arena *arena, MD_CodeLoc loc, MD_MessageKind kind, + MD_String8 string); + +#if !MD_DISABLE_PRINT_HELPERS +#include +MD_FUNCTION void MD_PrintMessage(FILE *file, MD_CodeLoc loc, MD_MessageKind kind, + MD_String8 string); +MD_FUNCTION void MD_PrintMessageFmt(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, + char *fmt, ...); + +#define MD_PrintGenNoteCComment(f) fprintf((f), "// generated by %s:%d\n", __FILE__, __LINE__) +#endif + +//~ Tree Comparison/Verification + +MD_FUNCTION MD_b32 MD_NodeMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags); +MD_FUNCTION MD_b32 MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags); + +//~ Expression Parsing + +MD_FUNCTION void MD_ExprOprPush(MD_Arena *arena, MD_ExprOprList *list, + MD_ExprOprKind kind, MD_u32 precedence, + MD_String8 op_string, + MD_u32 op_id, void *op_ptr); + +MD_FUNCTION MD_ExprOprTable MD_ExprBakeOprTableFromList(MD_Arena *arena, + MD_ExprOprList *list); +MD_FUNCTION MD_ExprOpr* MD_ExprOprFromKindString(MD_ExprOprTable *table, + MD_ExprOprKind kind, MD_String8 s); + +MD_FUNCTION MD_ExprParseResult MD_ExprParse(MD_Arena *arena, MD_ExprOprTable *op_table, + MD_Node *first, MD_Node *one_past_last); + +MD_FUNCTION MD_Expr* MD_Expr_NewLeaf(MD_Arena *arena, MD_Node *node); +MD_FUNCTION MD_Expr* MD_Expr_NewOpr(MD_Arena *arena, MD_ExprOpr *op, MD_Node *op_node, + MD_Expr *left, MD_Expr *right); + +MD_FUNCTION MD_ExprParseCtx MD_ExprParse_MakeContext(MD_ExprOprTable *table); + +MD_FUNCTION MD_Expr* MD_ExprParse_TopLevel(MD_Arena *arena, MD_ExprParseCtx *ctx, + MD_Node *first, MD_Node *opl); +MD_FUNCTION MD_b32 MD_ExprParse_OprConsume(MD_ExprParseCtx *ctx, + MD_Node **iter, MD_Node *opl, + MD_ExprOprKind kind, + MD_u32 min_precedence, + MD_ExprOpr **op_out); +MD_FUNCTION MD_Expr* MD_ExprParse_Atom(MD_Arena *arena, MD_ExprParseCtx *ctx, + MD_Node **iter, MD_Node *first, MD_Node *opl); +MD_FUNCTION MD_Expr* MD_ExprParse_MinPrecedence(MD_Arena *arena, MD_ExprParseCtx *ctx, + MD_Node **iter, MD_Node *first, MD_Node *opl, + MD_u32 min_precedence); + + +//~ String Generation + +MD_FUNCTION void MD_DebugDumpFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, + int indent, MD_String8 indent_string, + MD_GenerateFlags flags); +MD_FUNCTION void MD_ReconstructionFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, + int indent, MD_String8 indent_string); + +//~ Command Line Argument Helper + +MD_FUNCTION MD_String8List MD_StringListFromArgCV(MD_Arena *arena, int argument_count, + char **arguments); +MD_FUNCTION MD_CmdLine MD_MakeCmdLineFromOptions(MD_Arena *arena, MD_String8List options); +MD_FUNCTION MD_String8List MD_CmdLineValuesFromString(MD_CmdLine cmdln, MD_String8 name); +MD_FUNCTION MD_b32 MD_CmdLineB32FromString(MD_CmdLine cmdln, MD_String8 name); +MD_FUNCTION MD_i64 MD_CmdLineI64FromString(MD_CmdLine cmdln, MD_String8 name); + +//~ File System + +MD_FUNCTION MD_String8 MD_LoadEntireFile(MD_Arena *arena, MD_String8 filename); +MD_FUNCTION MD_b32 MD_FileIterBegin(MD_FileIter *it, MD_String8 path); +MD_FUNCTION MD_FileInfo MD_FileIterNext(MD_Arena *arena, MD_FileIter *it); +MD_FUNCTION void MD_FileIterEnd(MD_FileIter *it); + +#endif // MD_H + +/* +Copyright 2021 Dion Systems LLC + +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. +*/ diff --git a/External/metadesk/md_stb_sprintf.h b/External/metadesk/md_stb_sprintf.h new file mode 100644 index 0000000..4758435 --- /dev/null +++ b/External/metadesk/md_stb_sprintf.h @@ -0,0 +1,1905 @@ +// NOTE(rjf): This library has been modified for Metadesk. + +// stb_sprintf - v1.09 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) +#if defined(__has_feature) && defined(__has_attribute) +#if __has_feature(address_sanitizer) +#if __has_attribute(__no_sanitize__) +#define STBSP__ASAN __attribute__((__no_sanitize__("address"))) +#elif __has_attribute(__no_sanitize_address__) +#define STBSP__ASAN __attribute__((__no_sanitize_address__)) +#elif __has_attribute(__no_address_safety_analysis__) +#define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) +#endif +#endif +#endif +#elif __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) +#if __SANITIZE_ADDRESS__ +#define STBSP__ASAN __attribute__((__no_sanitize_address__)) +#endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#include // for va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...); +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...); + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + +#include // for va_arg() + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff +#define stbsp__chk_cb_bufL(bytes) \ +{ \ +int len = (int)(bf - buf); \ +if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ +tlen += len; \ +if (0 == (bf = buf = callback(buf, user, len))) \ +goto done; \ +} \ +} +#define stbsp__chk_cb_buf(bytes) \ +{ \ +if (callback) { \ +stbsp__chk_cb_bufL(bytes); \ +} \ +} +#define stbsp__flush_cb() \ +{ \ +stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ +} // flush if there is even one byte in the buffer +#define stbsp__cb_buf_clamp(cl, v) \ +cl = v; \ +if (callback) { \ +int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ +if (cl > lg) \ +cl = lg; \ +} + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; +#ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else +#endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { +#define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length + sn = s; + for (;;) { + if ((((stbsp__uintptr)sn) & 3) == 0) + break; + lchk: + if (sn[0] == 0) + goto ld; + ++sn; + } + n = 0xffffffff; + if (pr >= 0) { + n = (stbsp__uint32)(sn - s); + if (n >= (stbsp__uint32)pr) + goto ld; + n = ((stbsp__uint32)(pr - n)) >> 2; + } + while (n) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + if ((v - 0x01010101) & (~v) & 0x80808080UL) + goto lchk; + sn += 4; + --n; + } + goto lchk; + ld: + + l = (stbsp__uint32)(sn - s); + // clamp to precision + if (l > (stbsp__uint32)pr) + l = pr; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + //~ rjf: METADESK ADDITION: %S for MD_String8's + + case 'S': // MD_String8 + { + //- rjf: pull out string + MD_String8 str = va_arg(va, MD_String8); + + //- rjf: get string length + s = (char *)str.str; + sn = (const char *)(str.str + str.size); + l = (int)str.size; + + //- rjf: clamp to precision + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + + goto scopy; + }break; + + //~ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); + // add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = (((l >> 4) & 15)) << 24; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } + endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + + done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ +{ \ +int cn; \ +for (cn = 0; cn < 8; cn++) \ +((char *)&dest)[cn] = ((char *)&src)[cn]; \ +} + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ +{ \ +double ahi = 0, alo, bhi = 0, blo; \ +stbsp__int64 bt; \ +oh = xh * yh; \ +STBSP__COPYFP(bt, xh); \ +bt &= ((~(stbsp__uint64)0) << 27); \ +STBSP__COPYFP(ahi, bt); \ +alo = xh - ahi; \ +STBSP__COPYFP(bt, yh); \ +bt &= ((~(stbsp__uint64)0) << 27); \ +STBSP__COPYFP(bhi, bt); \ +blo = yh - bhi; \ +ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ +} + +#define stbsp__ddtoS64(ob, xh, xl) \ +{ \ +double ahi = 0, alo, vh, t; \ +ob = (stbsp__int64)ph; \ +vh = (double)ob; \ +ahi = (xh - vh); \ +t = (ahi - xh); \ +alo = (xh - (ahi - t)) - (vh + t); \ +ob += (stbsp__int64)(ahi + alo + xl); \ +} + +#define stbsp__ddrenorm(oh, ol) \ +{ \ +double s; \ +s = oh + ol; \ +ol = ol - (s - oh); \ +oh = s; \ +} + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ diff --git a/build.bat b/build.bat index 9e77f5c..c9ec3a9 100644 --- a/build.bat +++ b/build.bat @@ -13,7 +13,7 @@ pushd Build REM O2 Optimisation Level 2 REM Oi Use CPU Intrinsics REM Z7 Combine multi-debug files to one debug file - set common_flags=-D DQN_UNIT_TESTS_WITH_MAIN -D DQN_UNIT_TESTS_WITH_KECCAK -D DQN_IMPLEMENTATION -D DQN_USE_STD_PRINTF /Tp %script_dir%\dqn.h + set common_flags=-D DQN_UNIT_TESTS_WITH_MAIN -D DQN_UNIT_TESTS_WITH_KECCAK -D DQN_IMPLEMENTATION -D DQN_WITH_JSON -D DQN_USE_STD_PRINTF /Tp %script_dir%\dqn.h set msvc_driver_flags=%common_flags% -MT -EHa -GR- -Od -Oi -Z7 -wd4201 -W4 -nologo diff --git a/dqn.h b/dqn.h index 49b0a47..cfff121 100644 --- a/dqn.h +++ b/dqn.h @@ -188,32 +188,25 @@ // allow arbitrary querying of data definitions expressed in Excel-like tables // using text files encoded in Dion-System's Metadesk grammar. // -// This option requires the standalone 'dqn_cpp_file.h' to be included prior -// to 'dqn.h' as well as Metadesk's 'md.h' and similarly the implementation of -// these files to be defined before the implementation of 'dqn.h' is defined. +// This option automatically includes 'dqn_cpp_file.h' to assist with code +// generation and Metadesk's 'md.h' and its implementation library. // // #define DQN_WITH_CGEN // -// For example in your header file -// -// #include "metadesk/md.h" -// #include "dqn/Standalone/dqn_cpp_file.h" -// #define DQN_STB_SPRINTF_HEADER_ONLY // Metadesk includes 'stb_sprintf.h' already -// #define DQN_WITH_CGEN -// #include "dqn.h" -// -// Then in your implementation file -// -// #include "metadesk/md.c" -// #define DQN_CPP_FILE_IMPLEMENTATION -// #include "dqn/Standalone/dqn_cpp_file.h" -// #define DQN_IMPLEMENTATION -// #include "dqn.h" +// Optionally define 'DQN_NO_METADESK' to disable the inclusion of Metadesk +// in the library. This might be useful if you are including the librarin in +// your project yourself. This library must still be defined and visible +// before this header. // // - Enable 'Dqn_JSON' a json parser. This option requires Sheredom's 'json.h' // to be included prior to this file. // // #define DQN_WITH_JSON +// +// Optionally define 'DQN_NO_SHEREDOM_JSON' to prevent Sheredom's 'json.h' +// library from being included. This might be useful if you are including the +// library in your project yourself. The library must still be defined and +// visible before this header. #if defined(DQN_ONLY_VARRAY) || \ defined(DQN_ONLY_SARRAY) || \ @@ -294,6 +287,27 @@ #endif #endif +#if defined(DQN_WITH_CGEN) + #if !defined(DQN_NO_METADESK) + #if !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS + #define DQN_UNDO_CRT_SECURE_NO_WARNINGS + #endif + #include "External/metadesk/md.h" + #if defined(DQN_UNDO_CRT_SECURE_NO_WARNINGS) + #undef _CRT_SECURE_NO_WARNINGS + #endif + #endif + + // Metadesk includes 'stb_sprintf.h' already + #if !defined(DQN_STB_SPRINTF_HEADER_ONLY) + #define DQN_STB_SPRINTF_HEADER_ONLY + #endif + + // Metadesk includes Windows.h + #define DQN_NO_WIN32_MIN_HEADER +#endif + #include "dqn_base.h" #include "dqn_external.h" #if defined(DQN_PLATFORM_WIN32) @@ -317,11 +331,15 @@ #include "dqn_type_info.h" #if defined(DQN_WITH_CGEN) -#include "dqn_cgen.h" + #include "Standalone/dqn_cpp_file.h" + #include "dqn_cgen.h" #endif #if defined(DQN_WITH_JSON) -#include "dqn_json.h" + #if !defined(DQN_NO_SHEREDOM_JSON) + #include "External/json.h" + #endif + #include "dqn_json.h" #endif #endif // DQN_H @@ -341,6 +359,23 @@ // //////////////////////////////////////////////////////////////////////////////////////////////////// +#if defined(DQN_WITH_CGEN) + #if !defined(DQN_NO_METADESK) + DQN_MSVC_WARNING_PUSH + DQN_MSVC_WARNING_DISABLE(4505) // warning C4505: '': unreferenced function with internal linkage has been removed + #include "External/metadesk/md.c" + DQN_MSVC_WARNING_POP + #endif + #define DQN_CPP_FILE_IMPLEMENTATION + #include "Standalone/dqn_cpp_file.h" + #include "dqn_cgen.cpp" +#endif + +#if defined(DQN_WITH_JSON) + #define DQN_JSON_IMPLEMENTATION + #include "dqn_json.h" +#endif + #include "dqn_base.cpp" #include "dqn_thread_context.cpp" #include "dqn_external.cpp" @@ -362,15 +397,6 @@ #include "dqn_hash.cpp" #include "dqn_helpers.cpp" -#if defined(DQN_WITH_CGEN) -#include "dqn_cgen.cpp" -#endif - -#if defined(DQN_WITH_JSON) -#define DQN_JSON_IMPLEMENTATION -#include "dqn_json.h" -#endif - #include "dqn_unit_tests.cpp" #include "dqn_docs.cpp" #endif // DQN_IMPLEMENTATION