306 lines
9.2 KiB
C
306 lines
9.2 KiB
C
#include <stdio.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <sys/param.h>
|
|
#include <dirent.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include "stb_c_lexer.h"
|
|
|
|
#include "raylib_parser.c"
|
|
|
|
#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 {
|
|
char filename[256];
|
|
// TODO: Track where function usage was found and display it?
|
|
} FunctionUsage;
|
|
|
|
typedef struct {
|
|
int from, to; // [from, to) - from inclusive, to exclusive
|
|
} LineRange;
|
|
|
|
static bool StartsWith(char *text, int textSize, char *prefix, int prefixSize)
|
|
{
|
|
return textSize >= prefixSize && !strncmp(text, prefix, prefixSize);
|
|
}
|
|
|
|
static bool EndsSith(char *text, int textSize, char *suffix, int suffixSize)
|
|
{
|
|
return textSize >= suffixSize && !strncmp(text+textSize-suffixSize, suffix, suffixSize);
|
|
}
|
|
|
|
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 GetFunctionFromIdentifier(char *id, FunctionInfo *functions, int functionCount)
|
|
{
|
|
int idSize = strlen(id);
|
|
|
|
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 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 fileSize = 0;
|
|
char *exampleCode = LoadFileText(fullPath, &fileSize);
|
|
if (exampleCode == NULL) {
|
|
return false;
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
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 filePath[PATH_MAX];
|
|
snprintf(filePath, sizeof(filePath), "%s/%s", dir, entry->d_name);
|
|
ParseFunctionUsagesFromFile(cwd, filePath, usages, usageCounts, functions, functionCount);
|
|
}
|
|
}
|
|
|
|
closedir(dirp);
|
|
}
|
|
|
|
// 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 int ParseFunctionsDefinitionsFromHeader(char *path, FunctionInfo *functions, int maxFunctions)
|
|
{
|
|
int fileSize;
|
|
char *contents = LoadFileText(path, &fileSize);
|
|
|
|
int count = 0;
|
|
|
|
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;
|
|
|
|
if (IsLineAPIFunction(line, lineSize)) {
|
|
ParseAPIFunctionInfo(line, lineSize, &functions[count]);
|
|
count++;
|
|
if (count == maxFunctions) break;
|
|
}
|
|
|
|
nextLineFrom = curr.to+1;
|
|
}
|
|
|
|
free(contents);
|
|
|
|
return count;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int count = 0;
|
|
struct dirent *entry;
|
|
while ((entry = readdir(dirp)) != NULL) {
|
|
if (entry->d_type != DT_REG) continue;
|
|
|
|
char *fileExtension = strrchr(entry->d_name, '.');
|
|
if (fileExtension == NULL) continue;
|
|
if (strcmp(fileExtension, ".h")) continue;
|
|
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name);
|
|
count += ParseFunctionsDefinitionsFromHeader(path, functions + count, maxFunctions - count);
|
|
}
|
|
closedir(dirp);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int GetUniqueFilenames(FunctionUsage *usages, int usageCount, char *uniqueFilenames[])
|
|
{
|
|
int count = 0;
|
|
|
|
for (int i = 0; i < usageCount; i++) {
|
|
FunctionUsage *usage = &usages[i];
|
|
|
|
bool found = false;
|
|
for (int j = 0; j < count; j++) {
|
|
if (!strcmp(uniqueFilenames[j], usage->filename)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
uniqueFilenames[count] = strdup(usage->filename);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|