From 7664c7d2c300040936a8b61ca26de04b0e9c9837 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 26 Aug 2023 14:45:05 +0300 Subject: [PATCH] make json output smaller --- .gitignore | 2 +- Makefile | 28 ++- src/main.c | 520 +++++++++++++++++++++----------------------- src/raylib_parser.c | 502 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 769 insertions(+), 283 deletions(-) create mode 100644 src/raylib_parser.c diff --git a/.gitignore b/.gitignore index 53752db..378eac2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -output +build diff --git a/Makefile b/Makefile index 0641d5c..6cb6632 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,20 @@ -.DEFAULT_GOAL := run +BUILD_DIR := build + +.DEFAULT_GOAL := all .PHONY := raylib_example_indexer all clean -raylib_example_indexer: src/main.c - mkdir -p output - gcc src/main.c -o output/raylib_example_indexer -I./external -DSTB_C_LEXER_IMPLEMENTATION +example_indexer: src/main.c + mkdir -p $(BUILD_DIR) + gcc src/main.c -o $(BUILD_DIR)/example_indexer -I./external -DSTB_C_LEXER_IMPLEMENTATION -raylib_api: - mkdir -p output - curl https://raw.githubusercontent.com/raysan5/raylib/master/parser/output/raylib_api.txt -o output/raylib_api.txt +$(BUILD_DIR)/raylib: + mkdir -p $(BUILD_DIR) + git clone git@github.com:raysan5/raylib.git $(BUILD_DIR)/raylib -raylib: - mkdir -p output - git clone git@github.com:raysan5/raylib.git output/raylib +run: example_indexer + ./$(BUILD_DIR)/example_indexer $(BUILD_DIR)/raylib/src $(BUILD_DIR)/raylib/examples $(BUILD_DIR)/output.json -run: raylib_example_indexer - ./output/raylib_example_indexer output/raylib_api.txt output/raylib/examples output/output.json - -all: raylib_api raylib run +all: $(BUILD_DIR)/raylib example_indexer run clean: - rm -rf output + rm -rf $(BUILD_DIR) diff --git a/src/main.c b/src/main.c index 3a63eca..022cd88 100644 --- a/src/main.c +++ b/src/main.c @@ -9,311 +9,297 @@ #include "stb_c_lexer.h" -#define MAX_FUNCS_TO_PARSE 1024 // Maximum number of functions to parse -#define MAX_FUNC_USAGES 1024 // Maximum number of usages per function +#include "raylib_parser.c" -typedef struct function_info { - char name[64]; - int name_size; - int param_count; -} function_info; +#define MAX_FUNCS_TO_PARSE 1024 // Maximum number of functions to parse +#define MAX_FUNCS_PER_EXAMPLE 1024 // Maximum number of usages per function per file -typedef struct function_usage { - char filename[PATH_MAX]; - int line_number; - int line_offset; -} function_usage; +typedef struct { + char filename[256]; + // TODO: Track where function usage was found and display it? +} FunctionUsage; -typedef struct line_range { - int from, to; // [from, to) - from inclusive, to exclusive -} line_range; +typedef struct { + int from, to; // [from, to) - from inclusive, to exclusive +} LineRange; -static int get_file_size(FILE *file) { - fseek(file, 0, SEEK_END); - int size = ftell(file); - fseek(file, 0, SEEK_SET); - return size; +static bool StartsWith(char *text, int textSize, char *prefix, int prefixSize) +{ + return textSize >= prefixSize && !strncmp(text, prefix, prefixSize); } -static bool get_next_line(line_range *line, char *text, int text_size, int from) { - for (int i = from; i < text_size; i++) { - if (text[i] == '\n') { - line->from = from; - line->to = i; - return true; - } - } - return false; +static bool EndsSith(char *text, int textSize, char *suffix, int suffixSize) +{ + return textSize >= suffixSize && !strncmp(text+textSize-suffixSize, suffix, suffixSize); } -static int skip_next_lines(char *text, int text_size, int line_count, int from) { - int next_line_from = from; - line_range curr = { 0 }; - - for (int i = 0; i < line_count; i++) { - if (!get_next_line(&curr, text, text_size, next_line_from)) break; - next_line_from = curr.to+1; - } - - return next_line_from; +static bool GetNextLine(LineRange *line, char *text, int textSize, int from) +{ + for (int i = from; i < textSize; i++) { + if (text[i] == '\n') { + line->from = from; + line->to = i; + return true; + } + } + return false; } -static int find_start_of_function_block(char *raylib_api, int raylib_api_size) { - int next_line_from = 0; - line_range curr = { 0 }; - while (get_next_line(&curr, raylib_api, raylib_api_size, next_line_from)) { - int line_size = curr.to - curr.from; - char *line = &raylib_api[curr.from]; +static int GetFunctionFromIdentifier(char *id, FunctionInfo *functions, int functionCount) +{ + int idSize = strlen(id); - if (line_size >= sizeof("Functions found:") && !strncmp(line, "Functions found:", sizeof("Functions found:")-1)) { - line_range next_line; - if (get_next_line(&next_line, raylib_api, raylib_api_size, curr.to+1)) { - return next_line.to+1; - } else { - return -1; - } - } - - next_line_from = curr.to+1; - } - - return -1; + for (int i = 0; i < functionCount; i++) { + FunctionInfo *function = &functions[i]; + if (idSize > sizeof(function->name)) continue; + if (!strcmp(id, function->name)) { + return i; + } + } + return -1; } -static bool parse_function_info(char *line, int line_size, function_info *info) { - char *name = strchr(line, ':') + 2; - int name_size = strchr(line, '(') - name; - strncpy(info->name, name, name_size); - info->name_size = name_size; +static bool ParseFunctionUsagesFromFile(char *directory, char *filePath, FunctionUsage *usages[], int *usageCounts, FunctionInfo *functions, int functionCount) +{ + char fullPath[PATH_MAX] = { 0 }; + snprintf(fullPath, sizeof(fullPath), "%s/%s", directory, filePath); - int param_count = strtoul(name + name_size + 4, NULL, 10); - info->param_count = param_count; + int fileSize = 0; + char *exampleCode = LoadFileText(fullPath, &fileSize); + if (exampleCode == NULL) { + return false; + } - return true; + stb_lexer lexer; + char stringStore[512]; + stb_c_lexer_init(&lexer, exampleCode, exampleCode+fileSize, stringStore, sizeof(stringStore)); + + while (stb_c_lexer_get_token(&lexer)) { + if (lexer.token != CLEX_id) continue; + + int functionIndex = GetFunctionFromIdentifier(lexer.string, functions, functionCount); + if (functionIndex != -1) { + int *usageCount = &usageCounts[functionIndex]; + assert(*usageCount < MAX_FUNCS_PER_EXAMPLE); + FunctionUsage *usage = &usages[functionIndex][*usageCount]; + strncpy(usage->filename, filePath, strlen(filePath)); + (*usageCount)++; + } + } + + free(exampleCode); + + return true; } -static int parse_funcs_from_raylib_api(char *raylib_api, int raylib_api_size, function_info *funcs, int max_funcs) { - int start_of_functions = find_start_of_function_block(raylib_api, raylib_api_size); - if (start_of_functions == -1) { - return -1; - } +static void ParseFunctionsUsagesFromFolder(char *cwd, char *dir, FunctionUsage *usages[], int *usageCounts, FunctionInfo *functions, int functionCount) +{ + char dirPath[PATH_MAX]; + snprintf(dirPath, sizeof(dirPath), "%s/%s", cwd, dir); + DIR *dirp = opendir(dirPath); + if (dirp == NULL) { + fprintf(stderr, "Failed to open directory '%s'\n", dirPath); + return; + } - int count = 0; + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type != DT_REG) continue; - int next_line_from = start_of_functions; - line_range curr = { 0 }; - while (get_next_line(&curr, raylib_api, raylib_api_size, next_line_from)) { - int line_size = curr.to - curr.from; - char *line = &raylib_api[curr.from]; + char *extension = strrchr(entry->d_name, '.'); + if (!strcmp(extension, ".c")) { + char filePath[PATH_MAX]; + snprintf(filePath, sizeof(filePath), "%s/%s", dir, entry->d_name); + ParseFunctionUsagesFromFile(cwd, filePath, usages, usageCounts, functions, functionCount); + } + } - function_info *func_info = &funcs[count]; - if (!parse_function_info(line, line_size, func_info)) { - fprintf(stderr, "Failed to parse function line: %.*s\n", line_size, line); - return -1; - } - count++; - - int skip_count = 3 + MAX(func_info->param_count, 1); - next_line_from = skip_next_lines(raylib_api, raylib_api_size, skip_count, curr.to+1); - - if (max_funcs == count) break; - } - - return count; + closedir(dirp); } -static int get_func_from_identifier(char *id, function_info *funcs, int func_count) { - int id_size = strlen(id); - for (int i = 0; i < func_count; i++) { - function_info *func = &funcs[i]; - if (id_size != func->name_size) continue; - if (!strncmp(id, func->name, func->name_size)) { - return i; - } - } - return -1; +// Checks if the line is in the format "#if defined(*_IMPLEMENTATION)" +static bool IsLineImplementationIfdef(char *line, int line_size) { + char *prefix = "#if defined("; + char *suffix = "_IMPLEMENTATION)"; + return StartsWith(line, line_size, prefix, strlen(prefix)) && + EndsSith(line, line_size, suffix, strlen(suffix)); } -static bool collect_function_usages_from_file(char *directory, char *file_path, function_usage *usages[], int *usage_counts, function_info *funcs, int func_count) { - char full_path[PATH_MAX] = { 0 }; - snprintf(full_path, sizeof(full_path), "%s/%s", directory, file_path); - FILE *file = fopen(full_path, "r"); - if (file == NULL) { - fprintf(stderr, "Failed to open file '%s'\n", full_path); - return false; - } +static int ParseFunctionsDefinitionsFromHeader(char *path, FunctionInfo *functions, int maxFunctions) +{ + int fileSize; + char *contents = LoadFileText(path, &fileSize); - int file_size = get_file_size(file); - char *example_code = malloc(file_size); - fread(example_code, sizeof(char), file_size, file); + int count = 0; - stb_lexer lexer; - char string_store[512]; - stb_c_lexer_init(&lexer, example_code, example_code+file_size, string_store, sizeof(string_store)); - while (stb_c_lexer_get_token(&lexer)) { - if (lexer.token != CLEX_id) continue; + int nextLineFrom = 0; + LineRange curr = { 0 }; + while (GetNextLine(&curr, contents, fileSize, nextLineFrom)) { + int lineSize = curr.to - curr.from; + char line[512] = { 0 }; + strncpy(line, &contents[curr.from], lineSize); // `raylib_parser.c` expects lines to be null-terminated + if (IsLineImplementationIfdef(line, lineSize)) break; - int func_idx = get_func_from_identifier(lexer.string, funcs, func_count); - if (func_idx != -1) { - stb_lex_location loc; - stb_c_lexer_get_location(&lexer, lexer.where_firstchar, &loc); - int *usage_count = &usage_counts[func_idx]; - assert(*usage_count < MAX_FUNC_USAGES); - function_usage *usage = &usages[func_idx][*usage_count]; - usage->line_number = loc.line_number; - usage->line_offset = loc.line_offset; - strncpy(usage->filename, file_path, strlen(file_path)); - (*usage_count)++; - } - } + if (IsLineAPIFunction(line, lineSize)) { + ParseAPIFunctionInfo(line, lineSize, &functions[count]); + count++; + if (count == maxFunctions) break; + } - free(example_code); - fclose(file); + nextLineFrom = curr.to+1; + } - return true; + free(contents); + + return count; } -static void collect_function_usages_from_folder(char *cwd, char *dir, function_usage *usages[], int *usage_counts, function_info *funcs, int func_count) { - char dir_path[PATH_MAX]; - snprintf(dir_path, sizeof(dir_path), "%s/%s", cwd, dir); - DIR *dirp = opendir(dir_path); - if (dirp == NULL) { - fprintf(stderr, "Failed to open directory '%s'\n", dir_path); - return; - } +static int ParseFunctionsDefinitionsFromFolder(char *dir, FunctionInfo *functions, int maxFunctions) +{ + DIR *dirp = opendir(dir); + if (dirp == NULL) { + fprintf(stderr, "Failed to open directory '%s'\n", dir); + return -1; + } - struct dirent *entry; - while ((entry = readdir(dirp)) != NULL) { - if (entry->d_type != DT_REG) continue; + int count = 0; + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type != DT_REG) continue; - char *extension = strrchr(entry->d_name, '.'); - if (!strcmp(extension, ".c")) { - char file_path[PATH_MAX]; - snprintf(file_path, sizeof(file_path), "%s/%s", dir, entry->d_name); - collect_function_usages_from_file(cwd, file_path, usages, usage_counts, funcs, func_count); - } - } + char *fileExtension = strrchr(entry->d_name, '.'); + if (fileExtension == NULL) continue; + if (strcmp(fileExtension, ".h")) continue; - closedir(dirp); + char path[256]; + snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name); + count += ParseFunctionsDefinitionsFromHeader(path, functions + count, maxFunctions - count); + } + closedir(dirp); + + return count; } -int main(int argc, char **argv) { - if (argc != 4) { - printf("Usage: %s \n", argv[0]); - return -1; - } +static int GetUniqueFilenames(FunctionUsage *usages, int usageCount, char *uniqueFilenames[]) +{ + int count = 0; - char *raylib_api_path = argv[1]; - char *raylib_examples_path = argv[2]; - char *output_path = argv[3]; + for (int i = 0; i < usageCount; i++) { + FunctionUsage *usage = &usages[i]; - char *output_extension = strrchr(output_path, '.'); - if (output_extension == NULL) { - fprintf(stderr, "ERROR: Missing extension on output file\n"); - return -1; - } + bool found = false; + for (int j = 0; j < count; j++) { + if (!strcmp(uniqueFilenames[j], usage->filename)) { + found = true; + break; + } + } - function_info funcs[MAX_FUNCS_TO_PARSE]; - int funcs_count = 0; + if (!found) { + uniqueFilenames[count] = strdup(usage->filename); + count++; + } + } - { // Collect function definitions - FILE *raylib_api_file = fopen(raylib_api_path, "r"); - if (raylib_api_file == NULL) { - fprintf(stderr, "Failed to open file '%s'\n", raylib_api_path); - return -1; - } - int raylib_api_size = get_file_size(raylib_api_file); - char *raylib_api = malloc(raylib_api_size); - fread(raylib_api, sizeof(char), raylib_api_size, raylib_api_file); - fclose(raylib_api_file); - - funcs_count = parse_funcs_from_raylib_api(raylib_api, raylib_api_size, funcs, MAX_FUNCS_TO_PARSE); - - free(raylib_api); - } - - function_usage *usages[MAX_FUNCS_TO_PARSE] = { 0 }; - for (int i = 0; i < funcs_count; i++) { - usages[i] = malloc(MAX_FUNC_USAGES * sizeof(function_usage)); - } - int usage_counts[MAX_FUNCS_TO_PARSE] = { 0 }; - - { // Collect function usages - DIR *dirp = opendir(raylib_examples_path); - if (dirp == NULL) { - fprintf(stderr, "Failed to open directory '%s'\n", raylib_examples_path); - return -1; - } - struct dirent *entry; - while ((entry = readdir(dirp)) != NULL) { - if (entry->d_type != DT_DIR) continue; - if (entry->d_name[0] == '.') continue; - - collect_function_usages_from_folder(raylib_examples_path, entry->d_name, usages, usage_counts, funcs, funcs_count); - } - closedir(dirp); - } - - // Output function usages - FILE *output_file = fopen(output_path, "w"); - if (output_file == NULL) { - fprintf(stderr, "Failed to open file '%s\n'", output_path); - return -1; - } - - fwrite("{\n", sizeof(char), 2, output_file); - for (int func_idx = 0; func_idx < funcs_count; func_idx++) { - function_info *info = &funcs[func_idx]; - - fwrite("\t\"", sizeof(char), 2, output_file); - fwrite(info->name, sizeof(char), info->name_size, output_file); - fwrite("\": [", sizeof(char), 4, output_file); - - int usage_count = usage_counts[func_idx]; - if (usage_count > 0) { - fwrite("\n", sizeof(char), 1, output_file); - - for (int i = 0; i < usage_count; i++) { - function_usage *usage = &usages[func_idx][i]; - char *example_name = strchr(usage->filename, '/')+1; - int example_name_size = strchr(usage->filename, '.') - example_name; - - char *entry_format = "" - "\t\t{\n" - "\t\t\t\"exampleName\": \"%.*s\",\n" - "\t\t\t\"lineNumber\": %d,\n" - "\t\t\t\"lineOffset\": %d\n" - "\t\t}"; - char entry[1024]; - int entry_size = snprintf(entry, sizeof(entry), entry_format, - example_name_size, - example_name, - usage->line_number, - usage->line_offset); - - fwrite(entry, sizeof(char), entry_size, output_file); - if (i < usage_count-1) { - fwrite(",", sizeof(char), 1, output_file); - } - fwrite("\n", sizeof(char), 1, output_file); - } - - fwrite("\t", sizeof(char), 1, output_file); - } - - fwrite("]", sizeof(char), 1, output_file); - if (func_idx < funcs_count-1) { - fwrite(",", sizeof(char), 1, output_file); - } - fwrite("\n", sizeof(char), 1, output_file); - } - - fwrite("}", sizeof(char), 1, output_file); - fclose(output_file); - - for (int i = 0; i < funcs_count; i++) { - free(usages[i]); - } - - return 0; + return count; +} + +static int OutputFunctionUsagesJSON(char *output, FunctionInfo *functions, int functionCount, FunctionUsage **usages, int *usageCounts) +{ + FILE *outputFile = fopen(output, "w"); + if (outputFile == NULL) { + fprintf(stderr, "Failed to open file '%s\n'", output); + return -1; + } + + fwrite("{", sizeof(char), 1, outputFile); + for (int functionIndex = 0; functionIndex < functionCount; functionIndex++) { + FunctionInfo *info = &functions[functionIndex]; + + fwrite("\"", sizeof(char), 1, outputFile); + fwrite(info->name, sizeof(char), strlen(info->name), outputFile); + fwrite("\":[", sizeof(char), 3, outputFile); + + int usageCount = usageCounts[functionIndex]; + if (usageCount > 0) { + char *uniqueFilenames[usageCount]; + int uniqueCount = GetUniqueFilenames(usages[functionIndex], usageCount, uniqueFilenames); + + for (int i = 0; i < uniqueCount; i++) { + char *filename = uniqueFilenames[i]; + char *example_name = strchr(filename, '/')+1; + int example_name_size = strchr(filename, '.') - example_name; + + fwrite("\"", sizeof(char), 1, outputFile); + fwrite(example_name, sizeof(char), example_name_size, outputFile); + fwrite("\"", sizeof(char), 1, outputFile); + if (i < uniqueCount-1) { + fwrite(",", sizeof(char), 1, outputFile); + } + } + + for (int i = 0; i < uniqueCount; i++) { + free(uniqueFilenames[i]); + } + } + + fwrite("]", sizeof(char), 1, outputFile); + if (functionIndex < functionCount-1) { + fwrite(",", sizeof(char), 1, outputFile); + } + } + + fwrite("}", sizeof(char), 1, outputFile); + fclose(outputFile); + + return 0; +} + +int main(int argc, char **argv) +{ + if (argc != 4) { + printf("Usage: %s \n", argv[0]); + return -1; + } + + char *raylibSrc = argv[1]; + char *raylibExamplesPath = argv[2]; + char *outputPath = argv[3]; + + FunctionInfo functions[MAX_FUNCS_TO_PARSE]; + int functionCount = ParseFunctionsDefinitionsFromFolder(raylibSrc, functions, MAX_FUNCS_TO_PARSE); + if (functionCount < 0) { + return -1; + } + + FunctionUsage *usages[MAX_FUNCS_TO_PARSE] = { 0 }; + for (int i = 0; i < functionCount; i++) { + usages[i] = malloc(MAX_FUNCS_PER_EXAMPLE * sizeof(FunctionUsage)); + } + int usageCounts[MAX_FUNCS_TO_PARSE] = { 0 }; + + { // Collect function usages from examples + DIR *dirp = opendir(raylibExamplesPath); + if (dirp == NULL) { + fprintf(stderr, "Failed to open directory '%s'\n", raylibExamplesPath); + return -1; + } + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type != DT_DIR) continue; + if (entry->d_name[0] == '.') continue; + + ParseFunctionsUsagesFromFolder(raylibExamplesPath, entry->d_name, usages, usageCounts, functions, functionCount); + } + closedir(dirp); + } + + // Output function usages + OutputFunctionUsagesJSON(outputPath, functions, functionCount, usages, usageCounts); + + for (int i = 0; i < functionCount; i++) { + free(usages[i]); + } + + return 0; } diff --git a/src/raylib_parser.c b/src/raylib_parser.c new file mode 100644 index 0000000..d4b4568 --- /dev/null +++ b/src/raylib_parser.c @@ -0,0 +1,502 @@ +/********************************************************************************************** + + raylib API parser + + This parser scans raylib.h to get API information about defines, structs, aliases, enums, callbacks and functions. + All data is divided into pieces, usually as strings. The following types are used for data: + + - struct DefineInfo + - struct StructInfo + - struct AliasInfo + - struct EnumInfo + - struct FunctionInfo + + CONSTRAINTS: + + This parser is specifically designed to work with raylib.h, so, it has some constraints: + + - Functions are expected as a single line with the following structure: + + ( , ); + + Be careful with functions broken into several lines, it breaks the process! + + - Structures are expected as several lines with the following form: + + + typedef struct { + ; + ; + ; + } ; + + - Enums are expected as several lines with the following form: + + + typedef enum { + = , + , + , + + } ; + + NOTE: Multiple options are supported for enums: + - If value is not provided, ( + 1) is assigned + - Value description can be provided or not + + OTHER NOTES: + + - This parser could work with other C header files if mentioned constraints are followed. + - This parser does not require library, all data is parsed directly from char buffers. + - This is a modified/stripped down version of the original parser + Done so to make it usable as a library, and not a CLI tool. + This does not implement a function for every type to parse + + LICENSE: zlib/libpng + + raylib-parser is licensed under an unmodified zlib/libpng license, which is an OSI-certified, + BSD-like license that allows static linking with closed source software: + + Copyright (c) 2021-2023 Ramon Santamaria (@raysan5) + +**********************************************************************************************/ + + +#define _CRT_SECURE_NO_WARNINGS + +#include // Required for: malloc(), calloc(), realloc(), free(), atoi(), strtol() +#include // Required for: printf(), fopen(), fseek(), ftell(), fread(), fclose() +#include // Required for: bool +#include // Required for: isdigit() + +#define MAX_LINE_LENGTH 512 // Maximum length of one line (including comments) + +#define MAX_STRUCT_FIELDS 64 // Maximum number of struct fields +#define MAX_ENUM_VALUES 512 // Maximum number of enum values +#define MAX_FUNCTION_PARAMETERS 12 // Maximum number of function parameters + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Type of parsed define +typedef enum { + UNKNOWN = 0, + MACRO, + GUARD, + INT, + INT_MATH, + LONG, + LONG_MATH, + FLOAT, + FLOAT_MATH, + DOUBLE, + DOUBLE_MATH, + CHAR, + STRING, + COLOR +} DefineType; + +// Define info data +typedef struct DefineInfo { + char name[64]; // Define name + int type; // Define type + char value[256]; // Define value + char desc[128]; // Define description + bool isHex; // Define is hex number (for types INT, LONG) +} DefineInfo; + +// Struct info data +typedef struct StructInfo { + char name[64]; // Struct name + char desc[128]; // Struct type description + int fieldCount; // Number of fields in the struct + char fieldType[MAX_STRUCT_FIELDS][64]; // Field type + char fieldName[MAX_STRUCT_FIELDS][64]; // Field name + char fieldDesc[MAX_STRUCT_FIELDS][128]; // Field description +} StructInfo; + +// Alias info data +typedef struct AliasInfo { + char type[64]; // Alias type + char name[64]; // Alias name + char desc[128]; // Alias description +} AliasInfo; + +// Enum info data +typedef struct EnumInfo { + char name[64]; // Enum name + char desc[128]; // Enum description + int valueCount; // Number of values in enumerator + char valueName[MAX_ENUM_VALUES][64]; // Value name definition + int valueInteger[MAX_ENUM_VALUES]; // Value integer + char valueDesc[MAX_ENUM_VALUES][128]; // Value description +} EnumInfo; + +// Function info data +typedef struct FunctionInfo { + char name[64]; // Function name + char desc[128]; // Function description (comment at the end) + char retType[32]; // Return value type + int paramCount; // Number of function parameters + char paramType[MAX_FUNCTION_PARAMETERS][32]; // Parameters type + char paramName[MAX_FUNCTION_PARAMETERS][32]; // Parameters name + char paramDesc[MAX_FUNCTION_PARAMETERS][128]; // Parameters description +} FunctionInfo; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- + +#ifndef RAYLIB_API_DEFINE + #define RAYLIB_API_DEFINE "RLAPI" +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +static bool IsLineAPIFunction(char *line, int lineSize); +static void ParseAPIFunctionInfo(char *linePtr, int lineSize, FunctionInfo *functionInfo); + +static char *LoadFileText(const char *fileName, int *length); +static char **GetTextLines(const char *buffer, int length, int *linesCount); +static void GetDataTypeAndName(const char *typeName, int typeNameLen, char *type, char *name); +static void GetDescription(const char *source, char *description); +static void MoveArraySize(char *name, char *type); // Move array size from name to type +static unsigned int TextLength(const char *text); // Get text length in bytes, check for \0 character +static bool IsTextEqual(const char *text1, const char *text2, unsigned int count); +static int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +static void MemoryCopy(void *dest, const void *src, unsigned int count); +static char *EscapeBackslashes(char *text); // Replace '\' by "\\" when exporting to JSON and XML +static const char *StrDefineType(DefineType type); // Get string of define type + +//---------------------------------------------------------------------------------- +// Additional functions for use as a library +//---------------------------------------------------------------------------------- + +static bool IsLineAPIFunction(char *line, int lineSize) +{ + int apiDefineSize = TextLength(RAYLIB_API_DEFINE); + + // Read function line (starting with `define`, i.e. for raylib.h "RLAPI") + return lineSize >= apiDefineSize && IsTextEqual(line, RAYLIB_API_DEFINE, apiDefineSize); +} + +// Assumes that `func_info` is zero initialized +static void ParseAPIFunctionInfo(char *linePtr, int lineSize, FunctionInfo *functionInfo) +{ + int funcParamsStart = 0; + int funcEnd = 0; + + // Get return type and function name from func line + for (int c = 0; c < lineSize; c++) + { + if (linePtr[c] == '(') // Starts function parameters + { + funcParamsStart = c + 1; + + // At this point we have function return type and function name + char funcRetTypeName[128] = { 0 }; + int dc = TextLength(RAYLIB_API_DEFINE) + 1; + int funcRetTypeNameLen = c - dc; // Substract `define` ("RLAPI " for raylib.h) + MemoryCopy(funcRetTypeName, &linePtr[dc], funcRetTypeNameLen); + + GetDataTypeAndName(funcRetTypeName, funcRetTypeNameLen, functionInfo->retType, functionInfo->name); + break; + } + } + + // Get parameters from func line + for (int c = funcParamsStart; c < lineSize; c++) + { + int paramIndex = functionInfo->paramCount; + + if (linePtr[c] == ',') // Starts function parameters + { + // Get parameter type + name, extract info + char funcParamTypeName[128] = { 0 }; + int funcParamTypeNameLen = c - funcParamsStart; + MemoryCopy(funcParamTypeName, &linePtr[funcParamsStart], funcParamTypeNameLen); + + GetDataTypeAndName(funcParamTypeName, funcParamTypeNameLen, functionInfo->paramType[paramIndex], functionInfo->paramName[paramIndex]); + + funcParamsStart = c + 1; + if (linePtr[c + 1] == ' ') funcParamsStart += 1; + functionInfo->paramCount++; // Move to next parameter + } + else if (linePtr[c] == ')') + { + funcEnd = c + 2; + + // Check if previous word is void + if ((linePtr[c - 4] == 'v') && (linePtr[c - 3] == 'o') && (linePtr[c - 2] == 'i') && (linePtr[c - 1] == 'd')) break; + + // Get parameter type + name, extract info + char funcParamTypeName[128] = { 0 }; + int funcParamTypeNameLen = c - funcParamsStart; + MemoryCopy(funcParamTypeName, &linePtr[funcParamsStart], funcParamTypeNameLen); + + GetDataTypeAndName(funcParamTypeName, funcParamTypeNameLen, functionInfo->paramType[paramIndex], functionInfo->paramName[paramIndex]); + + functionInfo->paramCount++; // Move to next parameter + break; + } + } + + // Get function description + GetDescription(&linePtr[funcEnd], functionInfo->desc); + + // Move array sizes from name to type + for (int j = 0; j < functionInfo->paramCount; j++) + { + MoveArraySize(functionInfo->paramName[j], functionInfo->paramType[j]); + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Load text data from file, returns a '\0' terminated string +// NOTE: text chars array should be freed manually +static char *LoadFileText(const char *fileName, int *length) +{ + char *text = NULL; + + if (fileName != NULL) + { + FILE *file = fopen(fileName, "rt"); + + if (file != NULL) + { + // WARNING: When reading a file as 'text' file, + // text mode causes carriage return-linefeed translation... + // ...but using fseek() should return correct byte-offset + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + text = (char *)calloc((size + 1), sizeof(char)); + unsigned int count = (unsigned int)fread(text, sizeof(char), size, file); + + // WARNING: \r\n is converted to \n on reading, so, + // read bytes count gets reduced by the number of lines + if (count < (unsigned int)size) + { + text = realloc(text, count + 1); + *length = count; + } + else *length = size; + + // Zero-terminate the string + text[count] = '\0'; + } + + fclose(file); + } + } + + return text; +} + +// Get all lines from a text buffer (expecting lines ending with '\n') +static char **GetTextLines(const char *buffer, int length, int *linesCount) +{ + // Get the number of lines in the text + int count = 0; + for (int i = 0; i < length; i++) if (buffer[i] == '\n') count++; + + printf("Number of text lines in buffer: %i\n", count); + + // Allocate as many pointers as lines + char **lines = (char **)malloc(count*sizeof(char **)); + + char *bufferPtr = (char *)buffer; + + for (int i = 0; (i < count) || (bufferPtr[0] != '\0'); i++) + { + lines[i] = (char *)calloc(MAX_LINE_LENGTH, sizeof(char)); + + // Remove line leading spaces + // Find last index of space/tab character + int index = 0; + while ((bufferPtr[index] == ' ') || (bufferPtr[index] == '\t')) index++; + + int j = 0; + while (bufferPtr[index + j] != '\n') + { + lines[i][j] = bufferPtr[index + j]; + j++; + } + + bufferPtr += (index + j + 1); + } + + *linesCount = count; + return lines; +} + +// Get data type and name from a string containing both +// NOTE: Useful to parse function parameters and struct fields +static void GetDataTypeAndName(const char *typeName, int typeNameLen, char *type, char *name) +{ + for (int k = typeNameLen; k > 0; k--) + { + if ((typeName[k] == ' ') && (typeName[k - 1] != ',')) + { + // Function name starts at this point (and ret type finishes at this point) + MemoryCopy(type, typeName, k); + MemoryCopy(name, typeName + k + 1, typeNameLen - k - 1); + break; + } + else if (typeName[k] == '*') + { + MemoryCopy(type, typeName, k + 1); + MemoryCopy(name, typeName + k + 1, typeNameLen - k - 1); + break; + } + else if ((typeName[k] == '.') && (typeNameLen == 3)) // Handle varargs ...); + { + MemoryCopy(type, "...", 3); + MemoryCopy(name, "args", 4); + break; + } + } +} + +// Get comment from a line, do nothing if no comment in line +static void GetDescription(const char *line, char *description) +{ + int c = 0; + int descStart = -1; + int lastSlash = -2; + bool isValid = false; + while (line[c] != '\0') + { + if (isValid && (descStart == -1) && (line[c] != ' ')) descStart = c; + else if (line[c] == '/') + { + if (lastSlash == c - 1) isValid = true; + lastSlash = c; + } + c++; + } + if (descStart != -1) MemoryCopy(description, &line[descStart], c - descStart); +} + +// Move array size from name to type +static void MoveArraySize(char *name, char *type) +{ + int nameLength = TextLength(name); + if (name[nameLength - 1] == ']') + { + for (int k = nameLength; k > 0; k--) + { + if (name[k] == '[') + { + int sizeLength = nameLength - k; + MemoryCopy(&type[TextLength(type)], &name[k], sizeLength); + name[k] = '\0'; + } + } + } +} + +// Get text length in bytes, check for \0 character +static unsigned int TextLength(const char *text) +{ + unsigned int length = 0; + + if (text != NULL) while (*text++) length++; + + return length; +} + +// Compare two text strings, requires number of characters to compare +static bool IsTextEqual(const char *text1, const char *text2, unsigned int count) +{ + bool result = true; + + for (unsigned int i = 0; i < count; i++) + { + if (text1[i] != text2[i]) + { + result = false; + break; + } + } + + return result; +} + +// Find first text occurrence within a string +int TextFindIndex(const char *text, const char *find) +{ + int textLen = TextLength(text); + int findLen = TextLength(find); + + for (int i = 0; i <= textLen - findLen; i++) + { + if (IsTextEqual(&text[i], find, findLen)) return i; + } + + return -1; +} + +// Custom memcpy() to avoid +static void MemoryCopy(void *dest, const void *src, unsigned int count) +{ + char *srcPtr = (char *)src; + char *destPtr = (char *)dest; + + for (unsigned int i = 0; i < count; i++) destPtr[i] = srcPtr[i]; +} + +// Escape backslashes in a string, writing the escaped string into a static buffer +static char *EscapeBackslashes(char *text) +{ + static char buffer[256] = { 0 }; + + int count = 0; + + for (int i = 0; (text[i] != '\0') && (i < 255); i++, count++) + { + buffer[count] = text[i]; + + if (text[i] == '\\') + { + buffer[count + 1] = '\\'; + count++; + } + } + + buffer[count] = '\0'; + + return buffer; +} + +// Get string of define type +static const char *StrDefineType(DefineType type) +{ + switch (type) + { + case UNKNOWN: return "UNKNOWN"; + case GUARD: return "GUARD"; + case MACRO: return "MACRO"; + case INT: return "INT"; + case INT_MATH: return "INT_MATH"; + case LONG: return "LONG"; + case LONG_MATH: return "LONG_MATH"; + case FLOAT: return "FLOAT"; + case FLOAT_MATH: return "FLOAT_MATH"; + case DOUBLE: return "DOUBLE"; + case DOUBLE_MATH: return "DOUBLE_MATH"; + case CHAR: return "CHAR"; + case STRING: return "STRING"; + case COLOR: return "COLOR"; + } + return ""; +}