diff options
| author | navewindre <nw@moneybot.cc> | 2023-12-04 18:06:10 +0100 |
|---|---|---|
| committer | navewindre <nw@moneybot.cc> | 2023-12-04 18:06:10 +0100 |
| commit | aef0d1c1268ab7d4bc18996c9c6b4da16a40aadc (patch) | |
| tree | 43e766b51704f4ab8b383583bdc1871eeeb9c698 /sourcemod/scripting/include/json/helpers | |
| parent | 38f1140c11724da05a23a10385061200b907cf6e (diff) | |
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/include/json/helpers')
| -rw-r--r-- | sourcemod/scripting/include/json/helpers/decode.inc | 502 | ||||
| -rw-r--r-- | sourcemod/scripting/include/json/helpers/encode.inc | 200 | ||||
| -rw-r--r-- | sourcemod/scripting/include/json/helpers/string.inc | 133 |
3 files changed, 835 insertions, 0 deletions
diff --git a/sourcemod/scripting/include/json/helpers/decode.inc b/sourcemod/scripting/include/json/helpers/decode.inc new file mode 100644 index 0000000..f420222 --- /dev/null +++ b/sourcemod/scripting/include/json/helpers/decode.inc @@ -0,0 +1,502 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2019 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + */ + +#if defined _json_helpers_decode_included + #endinput +#endif +#define _json_helpers_decode_included + +#include <string> + +/** + * @section Determine Buffer Contents + */ + +/** + * Checks whether the character at the beginning + * of the buffer is whitespace. + * + * @param buffer String buffer of data. + * @returns True if the first character in the buffer + * is whitespace, false otherwise. + */ +stock bool json_is_whitespace(const char[] buffer) +{ + return buffer[0] == ' ' + || buffer[0] == '\t' + || buffer[0] == '\r' + || buffer[0] == '\n'; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the start of a string. + * + * @param buffer String buffer of data. + * @returns True if the first character in the buffer + * is the start of a string, false otherwise. + */ +stock bool json_is_string(const char[] buffer) +{ + return buffer[0] == '"'; +} + +/** + * Checks whether the buffer provided contains an int. + * + * @param buffer String buffer of data. + * @returns True if buffer contains an int, false otherwise. + */ +stock bool json_is_int(const char[] buffer) +{ + bool starts_with_zero = false; + bool has_digit_gt_zero = false; + + int length = strlen(buffer); + for (int i = 0; i < length; ++i) { + // allow minus as first character only + if (i == 0 && buffer[i] == '-') { + continue; + } + + if (IsCharNumeric(buffer[i])) { + if (buffer[i] == '0') { + if (starts_with_zero) { + // detect repeating leading zeros + return false; + } else if (! has_digit_gt_zero) { + starts_with_zero = true; + } + } else { + has_digit_gt_zero = true; + } + } else { + return false; + } + } + + // buffer must start with zero and have no other numerics before decimal + // OR not start with zero and have other numerics + return (starts_with_zero && ! has_digit_gt_zero) + || (! starts_with_zero && has_digit_gt_zero); +} + +/** + * Checks whether the buffer provided contains a float. + * + * @param buffer String buffer of data. + * @returns True if buffer contains a float, false otherwise. + */ +stock bool json_is_float(const char[] buffer) +{ + bool starts_with_zero = false; + bool has_digit_gt_zero = false; + bool after_decimal = false; + bool has_digit_after_decimal = false; + bool after_exponent = false; + bool has_digit_after_exponent = false; + + int length = strlen(buffer); + for (int i = 0; i < length; ++i) { + // allow minus as first character only + if (i == 0 && buffer[i] == '-') { + continue; + } + + // if we haven't encountered a decimal or exponent yet + if (! after_decimal && ! after_exponent) { + if (buffer[i] == '.') { + // if we encounter a decimal before any digits + if (! starts_with_zero && ! has_digit_gt_zero) { + return false; + } + + after_decimal = true; + } else if (buffer[i] == 'e' || buffer[i] == 'E') { + // if we encounter an exponent before any non-zero digits + if (starts_with_zero && ! has_digit_gt_zero) { + return false; + } + + after_exponent = true; + } else if (IsCharNumeric(buffer[i])) { + if (buffer[i] == '0') { + if (starts_with_zero) { + // detect repeating leading zeros + return false; + } else if (! has_digit_gt_zero) { + starts_with_zero = true; + } + } else { + has_digit_gt_zero = true; + } + } else { + return false; + } + } else if (after_decimal && ! after_exponent) { + // after decimal has been encountered, allow any numerics + if (IsCharNumeric(buffer[i])) { + has_digit_after_decimal = true; + } else if (buffer[i] == 'e' || buffer[i] == 'E') { + if (! has_digit_after_decimal) { + // detect exponents directly after decimal + return false; + } + + after_exponent = true; + } else { + return false; + } + } else if (after_exponent) { + if ( + (buffer[i] == '+' || buffer[i] == '-') + && (buffer[i - 1] == 'e' || buffer[i - 1] == 'E') + ) { + // allow + or - directly after exponent + continue; + } else if (IsCharNumeric(buffer[i])) { + has_digit_after_exponent = true; + } else { + return false; + } + } + } + + if (starts_with_zero && has_digit_gt_zero) { + /* if buffer starts with zero, there should + be no other digits before the decimal */ + return false; + } + + // if we have a decimal, there should be digit(s) after it + if (after_decimal) { + if (! has_digit_after_decimal) { + return false; + } + } + + // if we have an exponent, there should be digit(s) after it + if (after_exponent) { + if (! has_digit_after_exponent) { + return false; + } + } + + /* we should have reached an exponent, decimal, or both. + otherwise, this number can be handled by the int parser */ + return after_decimal || after_exponent; +} + +/** + * Checks whether the buffer provided contains a bool. + * + * @param buffer String buffer of data. + * @returns True if buffer contains a bool, false otherwise. + */ +stock bool json_is_bool(const char[] buffer) +{ + return StrEqual(buffer, "true") || StrEqual(buffer, "false"); +} + +/** + * Checks whether the buffer provided contains null. + * + * @param buffer String buffer of data. + * @returns True if buffer contains null, false otherwise. + */ +stock bool json_is_null(const char[] buffer) +{ + return StrEqual(buffer, "null"); +} + +/** + * Checks whether the character at the beginning + * of the buffer is the start of an object. + * + * @param buffer String buffer of data. + * @returns True if the first character in the buffer is + * the start of an object, false otherwise. + */ +stock bool json_is_object(const char[] buffer) +{ + return buffer[0] == '{'; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the end of an object. + * + * @param buffer String buffer of data. + * @returns True if the first character in the buffer is + * the end of an object, false otherwise. + */ +stock bool json_is_object_end(const char[] buffer) +{ + return buffer[0] == '}'; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the start of an array. + * + * @param buffer String buffer of data. + * @returns True if the first character in the buffer is + * the start of an array, false otherwise. + */ +stock bool json_is_array(const char[] buffer) +{ + return buffer[0] == '['; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the end of an array. + * + * @param buffer String buffer of data. + * @returns True if the first character in the buffer is + * the end of an array, false otherwise. + */ +stock bool json_is_array_end(const char[] buffer) +{ + return buffer[0] == ']'; +} + +/** + * Checks whether the character at the beginning of the buffer + * is considered a valid 'end point' for some data, such as a + * colon (indicating a key), a comma (indicating a new element), + * or the end of an object or array. + * + * @param buffer String buffer of data. + * @returns True if the first character in the buffer + * is a valid data end point, false otherwise. + */ +stock bool json_is_at_end(const char[] buffer, bool is_array) +{ + return buffer[0] == ',' + || (! is_array && buffer[0] == ':') + || json_is_object_end(buffer[0]) + || json_is_array_end(buffer[0]); +} + +/** + * @section Extract Contents from Buffer + */ + +/** + * Moves the position until it reaches a non-whitespace + * character or the end of the buffer's maximum size. + * + * @param buffer String buffer of data. + * @param max_size Maximum size of string buffer. + * @param pos Position to increment. + * @returns True if pos has not reached the end + * of the buffer, false otherwise. + */ +stock bool json_skip_whitespace(const char[] buffer, int max_size, int &pos) +{ + while (json_is_whitespace(buffer[pos]) && pos < max_size) { + ++pos; + } + + return pos < max_size; +} + +/** + * Extracts a JSON cell from the buffer until + * a valid end point is reached. + * + * @param buffer String buffer of data. + * @param max_size Maximum size of string buffer. + * @param pos Position to increment. + * @param output String buffer to store output. + * @param output_max_size Maximum size of output string buffer. + * @param is_array Whether the decoder is processing an array. + * @returns True if pos has not reached the end + * of the buffer, false otherwise. + */ +stock bool json_extract_until_end( + const char[] buffer, + int max_size, + int &pos, + char[] output, + int output_max_size, + bool is_array +) { + strcopy(output, output_max_size, ""); + + // set start to position of first character in cell + int start = pos; + + // while we haven't hit whitespace, an end point or the end of the buffer + while ( + ! json_is_whitespace(buffer[pos]) + && ! json_is_at_end(buffer[pos], is_array) + && pos < max_size + ) { + ++pos; + } + + // set end to the current position + int end = pos; + + // skip any following whitespace + json_skip_whitespace(buffer, max_size, pos); + + // if we aren't at a valid endpoint, extraction has failed + if (! json_is_at_end(buffer[pos], is_array)) { + return false; + } + + // copy only from start with length end - start + NULL terminator + strcopy(output, end - start + 1, buffer[start]); + + return pos < max_size; +} + +/** + * Extracts a JSON string from the buffer until + * a valid end point is reached. + * + * @param buffer String buffer of data. + * @param max_size Maximum size of string buffer. + * @param pos Position to increment. + * @param output String buffer to store output. + * @param output_max_size Maximum size of output string buffer. + * @param is_array Whether the decoder is processing an array. + * @returns True if pos has not reached the end + * of the buffer, false otherwise. + */ +stock bool json_extract_string( + const char[] buffer, + int max_size, + int &pos, + char[] output, + int output_max_size, + bool is_array +) { + strcopy(output, output_max_size, ""); + + // increment past opening quote + ++pos; + + // set start to position of first character in string + int start = pos; + + // while we haven't hit the end of the buffer + while (pos < max_size) { + // check for unescaped control characters + if ( + buffer[pos] == '\b' + || buffer[pos] == '\f' + || buffer[pos] == '\n' + || buffer[pos] == '\r' + || buffer[pos] == '\t' + ) { + return false; + } + + if (buffer[pos] == '"') { + // count preceding backslashes to check if quote is escaped + int search_pos = pos; + int preceding_backslashes = 0; + while (search_pos > 0 && buffer[--search_pos] == '\\') { + ++preceding_backslashes; + } + + // if we have an even number of backslashes, the quote is not escaped + if (preceding_backslashes % 2 == 0) { + break; + } + } + + // pass over the character as it is part of the string + ++pos; + } + + // set end to the current position + int end = pos; + + // increment past closing quote + ++pos; + + // skip trailing whitespace + if (! json_skip_whitespace(buffer, max_size, pos)) { + return false; + } + + // if we haven't reached an ending character at the end of the cell, + // there is likely junk data not encapsulated by a string + if (! json_is_at_end(buffer[pos], is_array)) { + return false; + } + + // copy only from start with length end - start + NULL terminator + strcopy(output, end - start + 1, buffer[start]); + json_unescape_string(output, max_size); + + return pos < max_size; +} + +/** + * Extracts an int from the buffer. + * + * @param buffer String buffer of data. + * @returns Int value of the buffer. + */ +stock int json_extract_int(const char[] buffer) +{ + return StringToInt(buffer); +} + +/** + * Extracts a float from the buffer. + * + * @param buffer String buffer of data. + * @returns Float value of the buffer. + */ +stock float json_extract_float(const char[] buffer) +{ + return StringToFloat(buffer); +} + +/** + * Extracts a bool from the buffer. + * + * @param buffer String buffer of data. + * @returns Bool value of the buffer. + */ +stock bool json_extract_bool(const char[] buffer) +{ + return StrEqual(buffer, "true"); +} diff --git a/sourcemod/scripting/include/json/helpers/encode.inc b/sourcemod/scripting/include/json/helpers/encode.inc new file mode 100644 index 0000000..ceae54d --- /dev/null +++ b/sourcemod/scripting/include/json/helpers/encode.inc @@ -0,0 +1,200 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2019 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + */ + +#if defined _json_helpers_encode_included + #endinput +#endif +#define _json_helpers_encode_included + +#include <string> + +/** + * @section Calculate Buffer Size for Value + */ + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of a string. + * + * @param length The length of the string. + * @returns Maximum buffer length. + */ +stock int json_cell_string_size(int length) +{ + // double for potential escaping, + 2 for outside quotes + NULL terminator + return (length * 2) + 3; +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of an int. + * + * @param input Value to calculate maximum buffer length for. + * @returns Maximum buffer length. + */ +stock int json_cell_int_size(int input) +{ + if (input == 0) { + // "0" + NULL terminator + return 2; + } + + int result = 0; + if (input < 0) { + // negative sign + result += 1; + } + + // calculate number of digits in number + result += RoundToFloor(Logarithm(FloatAbs(float(input)), 10.0)) + 1; + + // NULL terminator + result += 1; + + return result; +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of a float. + * + * @returns Maximum buffer length. + */ +stock int json_cell_float_size() +{ + return JSON_FLOAT_BUFFER_SIZE; +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of a bool. + * + * @returns Maximum buffer length. + */ +stock int json_cell_bool_size() +{ + // "true"|"false" + NULL terminator + return 6; +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of null. + * + * @returns Maximum buffer length. + */ +stock int json_cell_null_size() +{ + // "null" + NULL terminator + return 5; +} + +/** + * @section Convert Values to JSON Cells + */ + +/** + * Generates the JSON cell representation of a string. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + */ +stock void json_cell_string(const char[] input, char[] output, int max_size) +{ + // add dummy char that won't be escaped to replace with a quote later + strcopy(output, max_size, "?"); + + // add input string to output + StrCat(output, max_size, input); + + // escape the output string + json_escape_string(output, max_size); + + // surround string with quotations + output[0] = '"'; + StrCat(output, max_size, "\""); +} + +/** + * Generates the JSON cell representation of an int. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + */ +stock void json_cell_int(int input, char[] output, int max_size) +{ + IntToString(input, output, max_size); +} + +/** + * Generates the JSON cell representation of a float. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + */ +stock void json_cell_float(float input, char[] output, int max_size) +{ + FloatToString(input, output, max_size); + + // trim trailing 0s from float output up until decimal point + int last_char = strlen(output) - 1; + while (output[last_char] == '0' && output[last_char - 1] != '.') { + output[last_char--] = '\0'; + } +} + +/** + * Generates the JSON cell representation of a bool. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + */ +stock void json_cell_bool(bool input, char[] output, int max_size) +{ + strcopy(output, max_size, (input) ? "true" : "false"); +} + +/** + * Generates the JSON cell representation of null. + * + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + */ +stock void json_cell_null(char[] output, int max_size) +{ + strcopy(output, max_size, "null"); +} diff --git a/sourcemod/scripting/include/json/helpers/string.inc b/sourcemod/scripting/include/json/helpers/string.inc new file mode 100644 index 0000000..14fe38a --- /dev/null +++ b/sourcemod/scripting/include/json/helpers/string.inc @@ -0,0 +1,133 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2019 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + */ + +#if defined _json_helpers_string_included + #endinput +#endif +#define _json_helpers_string_included + +/** + * Mapping characters to their escaped form. + */ +char JSON_STRING_NORMAL[][] = { + "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" +}; +char JSON_STRING_ESCAPED[][] = { + "\\\\", "\\\"", "\\/", "\\b", "\\f", "\\n", "\\r", "\\t" +}; + +/** + * Escapes a string in-place in a buffer. + * + * @param buffer String buffer. + * @param max_size Maximum size of string buffer. + */ +stock void json_escape_string(char[] buffer, int max_size) +{ + for (int i = 0; i < sizeof(JSON_STRING_NORMAL); ++i) { + ReplaceString( + buffer, + max_size, + JSON_STRING_NORMAL[i], + JSON_STRING_ESCAPED[i] + ); + } +} + +/** + * Unescapes a string in-place in a buffer. + * + * @param buffer String buffer. + * @param max_size Maximum size of string buffer. + */ +stock void json_unescape_string(char[] buffer, int max_size) +{ + for (int i = 0; i < sizeof(JSON_STRING_NORMAL); ++i) { + ReplaceString( + buffer, + max_size, + JSON_STRING_ESCAPED[i], + JSON_STRING_NORMAL[i] + ); + } +} + +/** + * Checks if a string starts with another string. + * + * @param haystack String to check that starts with needle. + * @param max_size Maximum size of string buffer. + * @param needle String to check that haystack starts with. + * @returns True if haystack begins with needle, false otherwise. + */ +stock bool json_string_startswith(const char[] haystack, const char[] needle) +{ + int haystack_length = strlen(haystack); + int needle_length = strlen(needle); + if (needle_length > haystack_length) { + return false; + } + + for (int i = 0; i < needle_length; ++i) { + if (haystack[i] != needle[i]) { + return false; + } + } + + return true; +} + +/** + * Checks if a string ends with another string. + * + * @param haystack String to check that ends with needle. + * @param max_size Maximum size of string buffer. + * @param needle String to check that haystack ends with. + * @returns True if haystack ends with needle, false otherwise. + */ +stock bool json_string_endswith(const char[] haystack, const char[] needle) +{ + int haystack_length = strlen(haystack); + int needle_length = strlen(needle); + if (needle_length > haystack_length) { + return false; + } + + for (int i = 0; i < needle_length; ++i) { + if (haystack[haystack_length - needle_length + i] != needle[i]) { + return false; + } + } + + return true; +} |
