make json output smaller
This commit is contained in:
parent
1ff7adac56
commit
7664c7d2c3
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
output
|
||||
build
|
||||
|
28
Makefile
28
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)
|
||||
|
520
src/main.c
520
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 <raylib_api.txt> <examples-dir> <output-file>\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 <raylib-src-dir> <examples-dir> <output-file>\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;
|
||||
}
|
||||
|
502
src/raylib_parser.c
Normal file
502
src/raylib_parser.c
Normal file
@ -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:
|
||||
|
||||
<retType> <name>(<paramType[0]> <paramName[0]>, <paramType[1]> <paramName[1]>); <desc>
|
||||
|
||||
Be careful with functions broken into several lines, it breaks the process!
|
||||
|
||||
- Structures are expected as several lines with the following form:
|
||||
|
||||
<desc>
|
||||
typedef struct <name> {
|
||||
<fieldType[0]> <fieldName[0]>; <fieldDesc[0]>
|
||||
<fieldType[1]> <fieldName[1]>; <fieldDesc[1]>
|
||||
<fieldType[2]> <fieldName[2]>; <fieldDesc[2]>
|
||||
} <name>;
|
||||
|
||||
- Enums are expected as several lines with the following form:
|
||||
|
||||
<desc>
|
||||
typedef enum {
|
||||
<valueName[0]> = <valueInteger[0]>, <valueDesc[0]>
|
||||
<valueName[1]>,
|
||||
<valueName[2]>, <valueDesc[2]>
|
||||
<valueName[3]> <valueDesc[3]>
|
||||
} <name>;
|
||||
|
||||
NOTE: Multiple options are supported for enums:
|
||||
- If value is not provided, (<valueInteger[i -1]> + 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 <string.h> 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 <stdlib.h> // Required for: malloc(), calloc(), realloc(), free(), atoi(), strtol()
|
||||
#include <stdio.h> // Required for: printf(), fopen(), fseek(), ftell(), fread(), fclose()
|
||||
#include <stdbool.h> // Required for: bool
|
||||
#include <ctype.h> // 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 <string.h>
|
||||
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 "";
|
||||
}
|
Loading…
Reference in New Issue
Block a user