diff options
Diffstat (limited to 'raylib/parser/raylib_parser.c')
-rw-r--r-- | raylib/parser/raylib_parser.c | 1308 |
1 files changed, 1308 insertions, 0 deletions
diff --git a/raylib/parser/raylib_parser.c b/raylib/parser/raylib_parser.c new file mode 100644 index 0000000..e7ee901 --- /dev/null +++ b/raylib/parser/raylib_parser.c @@ -0,0 +1,1308 @@ +/********************************************************************************************** + + raylib API parser + + This parser scans raylib.h to get API information about structs, enums, functions and defines. + All data is divided into pieces, usually as strings. The following types are used for data: + + - struct FunctionInfo + - struct StructInfo + - struct EnumInfo + - struct DefInfo + + 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. + + 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 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_FUNCS_TO_PARSE 512 // Maximum number of functions to parse +#define MAX_STRUCTS_TO_PARSE 64 // Maximum number of structures to parse +#define MAX_ENUMS_TO_PARSE 64 // Maximum number of enums to parse +#define MAX_DEFINES_TO_PARSE 2048 // Maximum number of defines to parse + +#define MAX_LINE_LENGTH 512 // Maximum length of one line (including comments) +#define MAX_STRUCT_LINE_LENGTH 2048 // Maximum length of one struct (multiple lines) + +#define MAX_FUNCTION_PARAMETERS 12 // Maximum number of function parameters +#define MAX_STRUCT_FIELDS 32 // Maximum number of struct fields +#define MAX_ENUM_VALUES 512 // Maximum number of enum values + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// 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; + +// 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; + +// 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; + +// Type of parsed define +typedef enum { UNKNOWN = 0, MACRO, GUARD, INT, LONG, FLOAT, DOUBLE, CHAR, STRING, COLOR } DefineType; + +// Define info data +typedef struct DefineInfo { + char name[64]; // Define name + DefineType type; // Define type + char value[256]; // Define value + char desc[128]; // Define description + bool isHex; // Define is hex number (for types INT, LONG) +} DefineInfo; + +// Output format for parsed data +typedef enum { DEFAULT = 0, JSON, XML, LUA } OutputFormat; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static int funcCount = 0; +static int structCount = 0; +static int enumCount = 0; +static int defineCount = 0; +static FunctionInfo *funcs = NULL; +static StructInfo *structs = NULL; +static EnumInfo *enums = NULL; +static DefineInfo *defines = NULL; +static char apiDefine[32] = "RLAPI\0"; + +// Command line variables +static char inFileName[512] = { 0 }; // Input file name (required in case of provided through CLI) +static char outFileName[512] = { 0 }; // Output file name (required for file save/export) +static int outputFormat = DEFAULT; + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +static void ShowCommandLineInfo(void); // Show command line usage info +static void ProcessCommandLine(int argc, char *argv[]); // Process command line input + +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 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 void MemoryCopy(void *dest, const void *src, unsigned int count); +static char *EscapeBackslashes(char *text); // Replace '\' by "\\" when exporting to JSON and XML + +static void ExportParsedData(const char *fileName, int format); // Export parsed data in desired format + +static const char *StrDefineType(DefineType type); // Get string of define type + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(int argc, char* argv[]) +{ + if (argc > 1) ProcessCommandLine(argc, argv); + + if (inFileName[0] == '\0') MemoryCopy(inFileName, "../src/raylib.h\0", 16); + + int length = 0; + char *buffer = LoadFileText(inFileName, &length); + + // Preprocess buffer to get separate lines + // NOTE: GetTextLines() also removes leading spaces/tabs + int linesCount = 0; + char **lines = GetTextLines(buffer, length, &linesCount); + + // Function lines pointers, selected from buffer "lines" + char **funcLines = (char **)malloc(MAX_FUNCS_TO_PARSE*sizeof(char *)); + + // Structs data (multiple lines), selected from "buffer" + int *structLines = (int *)malloc(MAX_STRUCTS_TO_PARSE*sizeof(int)); + + // Enums lines pointers, selected from buffer "lines" + int *enumLines = (int *)malloc(MAX_ENUMS_TO_PARSE*sizeof(int)); + + // Defines lines pointers, selected from buffer "lines" + int *defineLines = (int *)malloc(MAX_DEFINES_TO_PARSE*sizeof(int)); + + // Prepare required lines for parsing + //-------------------------------------------------------------------------------------------------- + + // Read function lines + for (int i = 0; i < linesCount; i++) + { + // Read function line (starting with `define`, i.e. for raylib.h "RLAPI") + if (IsTextEqual(lines[i], apiDefine, TextLength(apiDefine))) + { + // Keep a pointer to the function line + funcLines[funcCount] = lines[i]; + funcCount++; + } + } + + // Read struct lines + for (int i = 0; i < linesCount; i++) + { + // Find structs (starting with "typedef struct ... {", ending with '} ... ;') + if (IsTextEqual(lines[i], "typedef struct", 14)) + { + int j = 0; + bool validStruct = false; + + // WARNING: Typedefs between types: typedef Vector4 Quaternion; + // (maybe we could export these too?) + + for (int c = 0; c < MAX_LINE_LENGTH; c++) + { + char v = lines[i][c]; + if (v == '{') validStruct = true; + if (v == '{' || v == ';' || v == '\0') + { + // Not valid struct if it ends without '{': + // i.e typedef struct rAudioBuffer rAudioBuffer; -> Typedef and forward declaration + break; + } + } + if (!validStruct) continue; + structLines[structCount] = i; + while (lines[i][0] != '}') i++; + while (lines[i][0] != '\0') i++; + structCount++; + } + } + + // Read enum lines + for (int i = 0; i < linesCount; i++) + { + // Read enum line + if (IsTextEqual(lines[i], "typedef enum {", 14)) + { + // Keep the line position in the array of lines, + // so, we can scan that position and following lines + enumLines[enumCount] = i; + enumCount++; + } + } + + // Read const lines + for (int i = 0; i < linesCount; i++) + { + int j = 0; + while (lines[i][j] == ' ' || lines[i][j] == '\t') j++; // skip spaces and tabs in the begining + // Read define line + if (IsTextEqual(lines[i]+j, "#define ", 8)) + { + // Keep the line position in the array of lines, + // so, we can scan that position and following lines + defineLines[defineCount] = i; + defineCount++; + } + } + + // At this point we have all raylib structs, enums, functions, defines lines data to start parsing + + free(buffer); // Unload text buffer + + // Parsing raylib data + //-------------------------------------------------------------------------------------------------- + + // Structs info data + structs = (StructInfo *)calloc(MAX_STRUCTS_TO_PARSE, sizeof(StructInfo)); + + for (int i = 0; i < structCount; i++) + { + char **linesPtr = &lines[structLines[i]]; + + // Parse struct description + if (linesPtr[-1][0] == '/') + { + MemoryCopy(structs[i].desc, linesPtr[-1], TextLength(linesPtr[-1])); + } + + // Get struct name: typedef struct name { + const int TDS_LEN = 15; // length of "typedef struct " + for (int c = TDS_LEN; c < 64 + TDS_LEN; c++) + { + if (linesPtr[0][c] == '{') + { + MemoryCopy(structs[i].name, &linesPtr[0][TDS_LEN], c - TDS_LEN - 1); + break; + } + } + + // Get struct fields and count them -> fields finish with ; + int l = 1; + while (linesPtr[l][0] != '}') + { + // WARNING: Some structs have empty spaces and comments -> OK, processed + if ((linesPtr[l][0] != ' ') && (linesPtr[l][0] != '\0')) + { + // Scan one field line + char *fieldLine = linesPtr[l]; + int fieldEndPos = 0; + while (fieldLine[fieldEndPos] != ';') fieldEndPos++; + + if (fieldLine[0] != '/') // Field line is not a comment + { + //printf("Struct field: %s_\n", fieldLine); // OK! + + // Get struct field type and name + GetDataTypeAndName(fieldLine, fieldEndPos, structs[i].fieldType[structs[i].fieldCount], structs[i].fieldName[structs[i].fieldCount]); + + // Get the field description + // We start skipping spaces in front of description comment + int descStart = fieldEndPos; + while ((fieldLine[descStart] != '/') && (fieldLine[descStart] != '\0')) descStart++; + + int k = 0; + while ((fieldLine[descStart + k] != '\0') && (fieldLine[descStart + k] != '\n')) + { + structs[i].fieldDesc[structs[i].fieldCount][k] = fieldLine[descStart + k]; + k++; + } + + structs[i].fieldCount++; + } + } + + l++; + } + + } + + free(structLines); + + // Enum info data + enums = (EnumInfo *)calloc(MAX_ENUMS_TO_PARSE, sizeof(EnumInfo)); + + for (int i = 0; i < enumCount; i++) + { + + // Parse enum description + // NOTE: This is not necessarily from the line immediately before, + // some of the enums have extra lines between the "description" + // and the typedef enum + for (int j = enumLines[i] - 1; j > 0; j--) + { + char *linePtr = lines[j]; + if ((linePtr[0] != '/') || (linePtr[2] != ' ')) + { + MemoryCopy(enums[i].desc, &lines[j + 1][0], sizeof(enums[i].desc) - 1); + break; + } + } + + for (int j = 1; j < MAX_ENUM_VALUES*2; j++) // Maximum number of lines following enum first line + { + char *linePtr = lines[enumLines[i] + j]; + + if ((linePtr[0] >= 'A') && (linePtr[0] <= 'Z')) + { + // Parse enum value line, possible options: + //ENUM_VALUE_NAME, + //ENUM_VALUE_NAME + //ENUM_VALUE_NAME = 99 + //ENUM_VALUE_NAME = 99, + //ENUM_VALUE_NAME = 0x00000040, // Value description + + // We start reading the value name + int c = 0; + while ((linePtr[c] != ',') && + (linePtr[c] != ' ') && + (linePtr[c] != '=') && + (linePtr[c] != '\0')) { enums[i].valueName[enums[i].valueCount][c] = linePtr[c]; c++; } + + // After the name we can have: + // '=' -> value is provided + // ',' -> value is equal to previous + 1, there could be a description if not '\0' + // ' ' -> value is equal to previous + 1, there could be a description if not '\0' + // '\0' -> value is equal to previous + 1 + + // Let's start checking if the line is not finished + if ((linePtr[c] != ',') && (linePtr[c] != '\0')) + { + // Two options: + // '=' -> value is provided + // ' ' -> value is equal to previous + 1, there could be a description if not '\0' + bool foundValue = false; + while ((linePtr[c] != '\0') && (linePtr[c] != '/')) + { + if (linePtr[c] == '=') { foundValue = true; break; } + c++; + } + + if (foundValue) + { + if (linePtr[c + 1] == ' ') c += 2; + else c++; + + // Parse integer value + int n = 0; + char integer[16] = { 0 }; + + while ((linePtr[c] != ',') && (linePtr[c] != ' ') && (linePtr[c] != '\0')) + { + integer[n] = linePtr[c]; + c++; n++; + } + + if (integer[1] == 'x') enums[i].valueInteger[enums[i].valueCount] = (int)strtol(integer, NULL, 16); + else enums[i].valueInteger[enums[i].valueCount] = atoi(integer); + } + else enums[i].valueInteger[enums[i].valueCount] = (enums[i].valueInteger[enums[i].valueCount - 1] + 1); + } + else enums[i].valueInteger[enums[i].valueCount] = (enums[i].valueInteger[enums[i].valueCount - 1] + 1); + + // Look for description or end of line + while ((linePtr[c] != '/') && (linePtr[c] != '\0')) { c++; } + if (linePtr[c] == '/') + { + // Parse value description + MemoryCopy(enums[i].valueDesc[enums[i].valueCount], &linePtr[c], sizeof(enums[0].valueDesc[0]) - c - 1); + } + + enums[i].valueCount++; + } + else if (linePtr[0] == '}') + { + // Get enum name from typedef + int c = 0; + while (linePtr[2 + c] != ';') { enums[i].name[c] = linePtr[2 + c]; c++; } + + break; // Enum ended, break for() loop + } + } + } + free(enumLines); + + // Define info data + defines = (DefineInfo *)calloc(MAX_DEFINES_TO_PARSE, sizeof(DefineInfo)); + int defineIndex = 0; + + for (int i = 0; i < defineCount; i++) + { + char *linePtr = lines[defineLines[i]]; + int j = 0; + + while (linePtr[j] == ' ' || linePtr[j] == '\t') j++; // Skip spaces and tabs in the begining + j += 8; // Skip "#define " + while (linePtr[j] == ' ' || linePtr[j] == '\t') j++; // Skip spaces and tabs after "#define " + + // Extract name + int defineNameStart = j; + while (linePtr[j] != ' ' && linePtr[j] != '\t' && linePtr[j] != '\0') j++; + int defineNameEnd = j-1; + + // Skip duplicates + int nameLen = defineNameEnd - defineNameStart + 1; + bool isDuplicate = false; + for (int k = 0; k < defineIndex; k++) { + if (nameLen == TextLength(defines[k].name) && IsTextEqual(defines[k].name, linePtr + defineNameStart, nameLen)) { + isDuplicate = true; + break; + } + } + if (isDuplicate) continue; + + MemoryCopy(defines[defineIndex].name, linePtr + defineNameStart, nameLen); + + // Determine type + if (linePtr[defineNameEnd] == ')') defines[defineIndex].type = MACRO; + + while (linePtr[j] == ' ' || linePtr[j] == '\t') j++; // Skip spaces and tabs after name + + int defineValueStart = j; + if (linePtr[j] == '\0' || linePtr == "/") defines[defineIndex].type = GUARD; + if (linePtr[j] == '"') defines[defineIndex].type = STRING; + else if (linePtr[j] == '\'') defines[defineIndex].type = CHAR; + else if (IsTextEqual(linePtr+j, "CLITERAL(Color)", 15)) defines[defineIndex].type = COLOR; + else if (isdigit(linePtr[j])) { // Parsing numbers + bool isFloat = false, isNumber = true, isHex = false; + while (linePtr[j] != ' ' && linePtr[j] != '\t' && linePtr[j] != '\0') { + char ch = linePtr[j]; + if (ch == '.') isFloat = true; + if (ch == 'x') isHex = true; + if (!(isdigit(ch)||(ch >= 'a' && ch <= 'f')||(ch >= 'A' && ch <= 'F')||ch=='x'||ch=='L'||ch=='.'||ch=='+'||ch=='-')) isNumber = false; + j++; + } + if (isNumber) { + if (isFloat) { + defines[defineIndex].type = linePtr[j-1] == 'f' ? FLOAT : DOUBLE; + } else { + defines[defineIndex].type = linePtr[j-1] == 'L' ? LONG : INT; + defines[defineIndex].isHex = isHex; + } + } + } + + // Extracting value + while (linePtr[j] != '\\' && linePtr[j] != '\0' && !(linePtr[j] == '/' && linePtr[j+1] == '/')) j++; + int defineValueEnd = j-1; + while (linePtr[defineValueEnd] == ' ' || linePtr[defineValueEnd] == '\t') defineValueEnd--; // Remove trailing spaces and tabs + if (defines[defineIndex].type == LONG || defines[defineIndex].type == FLOAT) defineValueEnd--; // Remove number postfix + int valueLen = defineValueEnd - defineValueStart + 1; + if (valueLen > 255) valueLen = 255; + + if (valueLen > 0) MemoryCopy(defines[defineIndex].value, linePtr + defineValueStart, valueLen); + + // Extracting description + if (linePtr[j] == '/') { + int commentStart = j; + while (linePtr[j] != '\\' && linePtr[j] != '\0') j++; + int commentEnd = j-1; + int commentLen = commentEnd - commentStart + 1; + if (commentLen > 127) commentLen = 127; + + MemoryCopy(defines[defineIndex].desc, linePtr + commentStart, commentLen); + } + + defineIndex++; + } + defineCount = defineIndex; + free(defineLines); + + // Functions info data + funcs = (FunctionInfo *)calloc(MAX_FUNCS_TO_PARSE, sizeof(FunctionInfo)); + + for (int i = 0; i < funcCount; i++) + { + int funcParamsStart = 0; + int funcEnd = 0; + + // Get return type and function name from func line + for (int c = 0; (c < MAX_LINE_LENGTH) && (funcLines[i][c] != '\n'); c++) + { + if (funcLines[i][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(apiDefine) + 1; + int funcRetTypeNameLen = c - dc; // Substract `define` ("RLAPI " for raylib.h) + MemoryCopy(funcRetTypeName, &funcLines[i][dc], funcRetTypeNameLen); + + GetDataTypeAndName(funcRetTypeName, funcRetTypeNameLen, funcs[i].retType, funcs[i].name); + break; + } + } + + // Get parameters from func line + for (int c = funcParamsStart; c < MAX_LINE_LENGTH; c++) + { + if (funcLines[i][c] == ',') // Starts function parameters + { + // Get parameter type + name, extract info + char funcParamTypeName[128] = { 0 }; + int funcParamTypeNameLen = c - funcParamsStart; + MemoryCopy(funcParamTypeName, &funcLines[i][funcParamsStart], funcParamTypeNameLen); + + GetDataTypeAndName(funcParamTypeName, funcParamTypeNameLen, funcs[i].paramType[funcs[i].paramCount], funcs[i].paramName[funcs[i].paramCount]); + + funcParamsStart = c + 1; + if (funcLines[i][c + 1] == ' ') funcParamsStart += 1; + funcs[i].paramCount++; // Move to next parameter + } + else if (funcLines[i][c] == ')') + { + funcEnd = c + 2; + + // Check if previous word is void + if ((funcLines[i][c - 4] == 'v') && (funcLines[i][c - 3] == 'o') && (funcLines[i][c - 2] == 'i') && (funcLines[i][c - 1] == 'd')) break; + + // Get parameter type + name, extract info + char funcParamTypeName[128] = { 0 }; + int funcParamTypeNameLen = c - funcParamsStart; + MemoryCopy(funcParamTypeName, &funcLines[i][funcParamsStart], funcParamTypeNameLen); + + GetDataTypeAndName(funcParamTypeName, funcParamTypeNameLen, funcs[i].paramType[funcs[i].paramCount], funcs[i].paramName[funcs[i].paramCount]); + + funcs[i].paramCount++; // Move to next parameter + break; + } + } + + // Get function description + for (int c = funcEnd; c < MAX_LINE_LENGTH; c++) + { + if (funcLines[i][c] == '/') + { + MemoryCopy(funcs[i].desc, &funcLines[i][c], 127); // WARNING: Size could be too long for funcLines[i][c]? + break; + } + } + } + + for (int i = 0; i < linesCount; i++) free(lines[i]); + free(lines); + free(funcLines); + + // At this point, all raylib data has been parsed! + //----------------------------------------------------------------------------------------- + // structs[] -> We have all the structs decomposed into pieces for further analysis + // enums[] -> We have all the enums decomposed into pieces for further analysis + // funcs[] -> We have all the functions decomposed into pieces for further analysis + // defines[] -> We have all the defines decomposed into pieces for further analysis + + // Process input file to output + if (outFileName[0] == '\0') MemoryCopy(outFileName, "raylib_api.txt\0", 15); + + printf("\nInput file: %s", inFileName); + printf("\nOutput file: %s", outFileName); + if (outputFormat == DEFAULT) printf("\nOutput format: DEFAULT\n\n"); + else if (outputFormat == JSON) printf("\nOutput format: JSON\n\n"); + else if (outputFormat == XML) printf("\nOutput format: XML\n\n"); + else if (outputFormat == LUA) printf("\nOutput format: LUA\n\n"); + + ExportParsedData(outFileName, outputFormat); + + free(funcs); + free(structs); + free(enums); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Show command line usage info +static void ShowCommandLineInfo(void) +{ + printf("\n//////////////////////////////////////////////////////////////////////////////////\n"); + printf("// //\n"); + printf("// raylib API parser //\n"); + printf("// //\n"); + printf("// more info and bugs-report: github.com/raysan5/raylib/parser //\n"); + printf("// //\n"); + printf("// Copyright (c) 2021 Ramon Santamaria (@raysan5) //\n"); + printf("// //\n"); + printf("//////////////////////////////////////////////////////////////////////////////////\n\n"); + + printf("USAGE:\n\n"); + printf(" > raylib_parser [--help] [--input <filename.h>] [--output <filename.ext>] [--format <type>] [--define <DEF>]\n"); + + printf("\nOPTIONS:\n\n"); + printf(" -h, --help : Show tool version and command line usage help\n\n"); + printf(" -i, --input <filename.h> : Define input header file to parse.\n"); + printf(" NOTE: If not specified, defaults to: raylib.h\n\n"); + printf(" -o, --output <filename.ext> : Define output file and format.\n"); + printf(" Supported extensions: .txt, .json, .xml, .h\n"); + printf(" NOTE: If not specified, defaults to: raylib_api.txt\n\n"); + printf(" -f, --format <type> : Define output format for parser data.\n"); + printf(" Supported types: DEFAULT, JSON, XML, LUA\n\n"); + printf(" -d, --define <DEF> : Define functions define (i.e. RLAPI for raylib.h, RMDEF for raymath.h, etc\n"); + printf(" NOTE: If not specified, defaults to: RLAPI\n\n"); + + printf("\nEXAMPLES:\n\n"); + printf(" > raylib_parser --input raylib.h --output api.json\n"); + printf(" Process <raylib.h> to generate <api.json>\n\n"); + printf(" > raylib_parser --output raylib_data.info --format XML\n"); + printf(" Process <raylib.h> to generate <raylib_data.info> as XML text data\n\n"); + printf(" > raylib_parser --input raymath.h --output raymath_data.info --format XML\n"); + printf(" Process <raymath.h> to generate <raymath_data.info> as XML text data\n\n"); +} + +// Process command line arguments +static void ProcessCommandLine(int argc, char *argv[]) +{ + for (int i = 1; i < argc; i++) + { + if (IsTextEqual(argv[i], "-h", 2) || IsTextEqual(argv[i], "--help", 6)) + { + // Show info + ShowCommandLineInfo(); + } + else if (IsTextEqual(argv[i], "-i", 2) || IsTextEqual(argv[i], "--input", 7)) + { + // Check for valid argument and valid file extension + if (((i + 1) < argc) && (argv[i + 1][0] != '-')) + { + MemoryCopy(inFileName, argv[i + 1], TextLength(argv[i + 1])); // Read input filename + i++; + } + else printf("WARNING: No input file provided\n"); + } + else if (IsTextEqual(argv[i], "-o", 2) || IsTextEqual(argv[i], "--output", 8)) + { + if (((i + 1) < argc) && (argv[i + 1][0] != '-')) + { + MemoryCopy(outFileName, argv[i + 1], TextLength(argv[i + 1])); // Read output filename + i++; + } + else printf("WARNING: No output file provided\n"); + } + else if (IsTextEqual(argv[i], "-f", 2) || IsTextEqual(argv[i], "--format", 8)) + { + if (((i + 1) < argc) && (argv[i + 1][0] != '-')) + { + if (IsTextEqual(argv[i + 1], "DEFAULT\0", 8)) outputFormat = DEFAULT; + else if (IsTextEqual(argv[i + 1], "JSON\0", 5)) outputFormat = JSON; + else if (IsTextEqual(argv[i + 1], "XML\0", 4)) outputFormat = XML; + else if (IsTextEqual(argv[i + 1], "LUA\0", 4)) outputFormat = LUA; + } + else printf("WARNING: No format parameters provided\n"); + } + else if (IsTextEqual(argv[i], "-d", 2) || IsTextEqual(argv[i], "--define", 8)) + { + if (((i + 1) < argc) && (argv[i + 1][0] != '-')) + { + MemoryCopy(apiDefine, argv[i + 1], TextLength(argv[i + 1])); // Read functions define + apiDefine[TextLength(argv[i + 1])] = '\0'; + i++; + } + else printf("WARNING: No define key provided\n"); + } + } +} + +// 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 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; +} + +// 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]; +} + +// 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; +} + +// 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 LONG: return "LONG"; + case FLOAT: return "FLOAT"; + case DOUBLE: return "DOUBLE"; + case CHAR: return "CHAR"; + case STRING: return "STRING"; + case COLOR: return "COLOR"; + } + return ""; +} + +/* +// Replace text string +// REQUIRES: strlen(), strstr(), strncpy(), strcpy() -> TODO: Replace by custom implementations! +// WARNING: Returned buffer must be freed by the user (if return != NULL) +static char *TextReplace(char *text, const char *replace, const char *by) +{ + // Sanity checks and initialization + if (!text || !replace || !by) return NULL; + + char *result; + + char *insertPoint; // Next insert point + char *temp; // Temp pointer + int replaceLen; // Replace string length of (the string to remove) + int byLen; // Replacement length (the string to replace replace by) + int lastReplacePos; // Distance between replace and end of last replace + int count; // Number of replacements + + replaceLen = strlen(replace); + if (replaceLen == 0) return NULL; // Empty replace causes infinite loop during count + + byLen = strlen(by); + + // Count the number of replacements needed + insertPoint = text; + for (count = 0; (temp = strstr(insertPoint, replace)); count++) insertPoint = temp + replaceLen; + + // Allocate returning string and point temp to it + temp = result = (char *)malloc(strlen(text) + (byLen - replaceLen)*count + 1); + + if (!result) return NULL; // Memory could not be allocated + + // First time through the loop, all the variable are set correctly from here on, + // - 'temp' points to the end of the result string + // - 'insertPoint' points to the next occurrence of replace in text + // - 'text' points to the remainder of text after "end of replace" + while (count--) + { + insertPoint = strstr(text, replace); + lastReplacePos = (int)(insertPoint - text); + temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; + temp = strcpy(temp, by) + byLen; + text += lastReplacePos + replaceLen; // Move to next "end of replace" + } + + // Copy remaind text part after replacement to result (pointed by moving temp) + strcpy(temp, text); + + return result; +} +*/ + +// Export parsed data in desired format +static void ExportParsedData(const char *fileName, int format) +{ + FILE *outFile = fopen(fileName, "wt"); + + switch (format) + { + case DEFAULT: + { + // Print structs info + fprintf(outFile, "\nStructures found: %i\n\n", structCount); + for (int i = 0; i < structCount; i++) + { + fprintf(outFile, "Struct %02i: %s (%i fields)\n", i + 1, structs[i].name, structs[i].fieldCount); + fprintf(outFile, " Name: %s\n", structs[i].name); + fprintf(outFile, " Description: %s\n", structs[i].desc + 3); + for (int f = 0; f < structs[i].fieldCount; f++) fprintf(outFile, " Field[%i]: %s %s %s\n", f + 1, structs[i].fieldType[f], structs[i].fieldName[f], structs[i].fieldDesc[f]); + } + + // Print enums info + fprintf(outFile, "\nEnums found: %i\n\n", enumCount); + for (int i = 0; i < enumCount; i++) + { + fprintf(outFile, "Enum %02i: %s (%i values)\n", i + 1, enums[i].name, enums[i].valueCount); + fprintf(outFile, " Name: %s\n", enums[i].name); + fprintf(outFile, " Description: %s\n", enums[i].desc + 3); + for (int e = 0; e < enums[i].valueCount; e++) fprintf(outFile, " Value[%s]: %i\n", enums[i].valueName[e], enums[i].valueInteger[e]); + } + + // Print functions info + fprintf(outFile, "\nFunctions found: %i\n\n", funcCount); + for (int i = 0; i < funcCount; i++) + { + fprintf(outFile, "Function %03i: %s() (%i input parameters)\n", i + 1, funcs[i].name, funcs[i].paramCount); + fprintf(outFile, " Name: %s\n", funcs[i].name); + fprintf(outFile, " Return type: %s\n", funcs[i].retType); + fprintf(outFile, " Description: %s\n", funcs[i].desc + 3); + for (int p = 0; p < funcs[i].paramCount; p++) fprintf(outFile, " Param[%i]: %s (type: %s)\n", p + 1, funcs[i].paramName[p], funcs[i].paramType[p]); + if (funcs[i].paramCount == 0) fprintf(outFile, " No input parameters\n"); + } + + fprintf(outFile, "\nDefines found: %i\n\n", defineCount); + for (int i = 0; i < defineCount; i++) + { + fprintf(outFile, "Define %03i: %s\n", i + 1, defines[i].name); + fprintf(outFile, " Name: %s\n", defines[i].name); + fprintf(outFile, " Type: %s\n", StrDefineType(defines[i].type)); + fprintf(outFile, " Value: %s\n", defines[i].value); + fprintf(outFile, " Description: %s\n", defines[i].desc + 3); + } + } break; + case LUA: + { + fprintf(outFile, "return {\n"); + + // Print structs info + fprintf(outFile, " structs = {\n"); + for (int i = 0; i < structCount; i++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " name = \"%s\",\n", structs[i].name); + fprintf(outFile, " description = \"%s\",\n", EscapeBackslashes(structs[i].desc + 3)); + fprintf(outFile, " fields = {\n"); + for (int f = 0; f < structs[i].fieldCount; f++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " type = \"%s\",\n", structs[i].fieldType[f]); + fprintf(outFile, " name = \"%s\",\n", structs[i].fieldName[f]); + fprintf(outFile, " description = \"%s\"\n", EscapeBackslashes(structs[i].fieldDesc[f] + 3)); + fprintf(outFile, " }"); + if (f < structs[i].fieldCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " }\n"); + fprintf(outFile, " }"); + if (i < structCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " },\n"); + + // Print enums info + fprintf(outFile, " enums = {\n"); + for (int i = 0; i < enumCount; i++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " name = \"%s\",\n", enums[i].name); + fprintf(outFile, " description = \"%s\",\n", EscapeBackslashes(enums[i].desc + 3)); + fprintf(outFile, " values = {\n"); + for (int e = 0; e < enums[i].valueCount; e++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " name = \"%s\",\n", enums[i].valueName[e]); + fprintf(outFile, " value = %i,\n", enums[i].valueInteger[e]); + fprintf(outFile, " description = \"%s\"\n", EscapeBackslashes(enums[i].valueDesc[e] + 3)); + fprintf(outFile, " }"); + if (e < enums[i].valueCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " }\n"); + fprintf(outFile, " }"); + if (i < enumCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " },\n"); + + // Print defines info + fprintf(outFile, " defines = {\n"); + for (int i = 0; i < defineCount; i++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " name = \"%s\",\n", defines[i].name); + fprintf(outFile, " type = \"%s\",\n", StrDefineType(defines[i].type)); + if (defines[i].type == INT || defines[i].type == LONG || defines[i].type == FLOAT || defines[i].type == DOUBLE || defines[i].type == STRING) { + fprintf(outFile, " value = %s,\n", defines[i].value); + } else { + fprintf(outFile, " value = \"%s\",\n", defines[i].value); + } + fprintf(outFile, " description = \"%s\"\n", defines[i].desc + 3); + fprintf(outFile, " }"); + + if (i < defineCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " },\n"); + + // Print functions info + fprintf(outFile, " functions = {\n"); + for (int i = 0; i < funcCount; i++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " name = \"%s\",\n", funcs[i].name); + fprintf(outFile, " description = \"%s\",\n", EscapeBackslashes(funcs[i].desc + 3)); + fprintf(outFile, " returnType = \"%s\"", funcs[i].retType); + + if (funcs[i].paramCount == 0) fprintf(outFile, "\n"); + else + { + fprintf(outFile, ",\n params = {\n"); + for (int p = 0; p < funcs[i].paramCount; p++) + { + fprintf(outFile, " {type = \"%s\", name = \"%s\"}", funcs[i].paramType[p], funcs[i].paramName[p]); + if (p < funcs[i].paramCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " }\n"); + } + fprintf(outFile, " }"); + + if (i < funcCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " }\n"); + fprintf(outFile, "}\n"); + } break; + case JSON: + { + fprintf(outFile, "{\n"); + + // Print structs info + fprintf(outFile, " \"structs\": [\n"); + for (int i = 0; i < structCount; i++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " \"name\": \"%s\",\n", structs[i].name); + fprintf(outFile, " \"description\": \"%s\",\n", EscapeBackslashes(structs[i].desc + 3)); + fprintf(outFile, " \"fields\": [\n"); + for (int f = 0; f < structs[i].fieldCount; f++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " \"type\": \"%s\",\n", structs[i].fieldType[f]); + fprintf(outFile, " \"name\": \"%s\",\n", structs[i].fieldName[f]); + fprintf(outFile, " \"description\": \"%s\"\n", EscapeBackslashes(structs[i].fieldDesc[f] + 3)); + fprintf(outFile, " }"); + if (f < structs[i].fieldCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " ]\n"); + fprintf(outFile, " }"); + if (i < structCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " ],\n"); + + // Print enums info + fprintf(outFile, " \"enums\": [\n"); + for (int i = 0; i < enumCount; i++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " \"name\": \"%s\",\n", enums[i].name); + fprintf(outFile, " \"description\": \"%s\",\n", EscapeBackslashes(enums[i].desc + 3)); + fprintf(outFile, " \"values\": [\n"); + for (int e = 0; e < enums[i].valueCount; e++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " \"name\": \"%s\",\n", enums[i].valueName[e]); + fprintf(outFile, " \"value\": %i,\n", enums[i].valueInteger[e]); + fprintf(outFile, " \"description\": \"%s\"\n", EscapeBackslashes(enums[i].valueDesc[e] + 3)); + fprintf(outFile, " }"); + if (e < enums[i].valueCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " ]\n"); + fprintf(outFile, " }"); + if (i < enumCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " ],\n"); + + // Print defines info + fprintf(outFile, " \"defines\": [\n"); + for (int i = 0; i < defineCount; i++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " \"name\": \"%s\",\n", defines[i].name); + fprintf(outFile, " \"type\": \"%s\",\n", StrDefineType(defines[i].type)); + if (defines[i].isHex) { // INT or LONG + fprintf(outFile, " \"value\": %ld,\n", strtol(defines[i].value, NULL, 16)); + } else if (defines[i].type == INT || defines[i].type == LONG || defines[i].type == FLOAT || defines[i].type == DOUBLE || defines[i].type == STRING) { + fprintf(outFile, " \"value\": %s,\n", defines[i].value); + } else { + fprintf(outFile, " \"value\": \"%s\",\n", defines[i].value); + } + fprintf(outFile, " \"description\": \"%s\"\n", defines[i].desc + 3); + fprintf(outFile, " }"); + + if (i < defineCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " ],\n"); + + // Print functions info + fprintf(outFile, " \"functions\": [\n"); + for (int i = 0; i < funcCount; i++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " \"name\": \"%s\",\n", funcs[i].name); + fprintf(outFile, " \"description\": \"%s\",\n", EscapeBackslashes(funcs[i].desc + 3)); + fprintf(outFile, " \"returnType\": \"%s\"", funcs[i].retType); + + if (funcs[i].paramCount == 0) fprintf(outFile, "\n"); + else + { + fprintf(outFile, ",\n \"params\": [\n"); + for (int p = 0; p < funcs[i].paramCount; p++) + { + fprintf(outFile, " {\n"); + fprintf(outFile, " \"type\": \"%s\",\n", funcs[i].paramType[p]); + fprintf(outFile, " \"name\": \"%s\"\n", funcs[i].paramName[p]); + fprintf(outFile, " }"); + if (p < funcs[i].paramCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " ]\n"); + } + fprintf(outFile, " }"); + + if (i < funcCount - 1) fprintf(outFile, ",\n"); + else fprintf(outFile, "\n"); + } + fprintf(outFile, " ]\n"); + fprintf(outFile, "}\n"); + } break; + case XML: + { + // XML format to export data: + /* + <?xml version="1.0" encoding="Windows-1252" ?> + <raylibAPI> + <Structs count=""> + <Struct name="" fieldCount="" desc=""> + <Field type="" name="" desc=""> + <Field type="" name="" desc=""> + </Struct> + <Structs> + <Enums count=""> + <Enum name="" valueCount="" desc=""> + <Value name="" integer="" desc=""> + <Value name="" integer="" desc=""> + </Enum> + </Enums> + <Functions count=""> + <Function name="" retType="" paramCount="" desc=""> + <Param type="" name="" desc="" /> + <Param type="" name="" desc="" /> + </Function> + </Functions> + </raylibAPI> + */ + + fprintf(outFile, "<?xml version=\"1.0\" encoding=\"Windows-1252\" ?>\n"); + fprintf(outFile, "<raylibAPI>\n"); + + // Print structs info + fprintf(outFile, " <Structs count=\"%i\">\n", structCount); + for (int i = 0; i < structCount; i++) + { + fprintf(outFile, " <Struct name=\"%s\" fieldCount=\"%i\" desc=\"%s\">\n", structs[i].name, structs[i].fieldCount, structs[i].desc + 3); + for (int f = 0; f < structs[i].fieldCount; f++) + { + fprintf(outFile, " <Field type=\"%s\" name=\"%s\" desc=\"%s\" />\n", structs[i].fieldType[f], structs[i].fieldName[f], structs[i].fieldDesc[f] + 3); + } + fprintf(outFile, " </Struct>\n"); + } + fprintf(outFile, " </Structs>\n"); + + // Print enums info + fprintf(outFile, " <Enums count=\"%i\">\n", enumCount); + for (int i = 0; i < enumCount; i++) + { + fprintf(outFile, " <Enum name=\"%s\" valueCount=\"%i\" desc=\"%s\">\n", enums[i].name, enums[i].valueCount, enums[i].desc + 3); + for (int v = 0; v < enums[i].valueCount; v++) + { + fprintf(outFile, " <Value name=\"%s\" integer=\"%i\" desc=\"%s\" />\n", enums[i].valueName[v], enums[i].valueInteger[v], enums[i].valueDesc[v] + 3); + } + fprintf(outFile, " </Enum>\n"); + } + fprintf(outFile, " </Enums>\n"); + + // Print defines info + fprintf(outFile, " <Defines count=\"%i\">\n", defineCount); + for (int i = 0; i < defineCount; i++) + { + fprintf(outFile, " <Define name=\"%s\" type=\"%s\" value=\"%s\" desc=\"%s\" />\n", defines[i].name, StrDefineType(defines[i].type), defines[i].value, defines[i].desc); + } + fprintf(outFile, " </Defines>\n"); + + // Print functions info + fprintf(outFile, " <Functions count=\"%i\">\n", funcCount); + for (int i = 0; i < funcCount; i++) + { + fprintf(outFile, " <Function name=\"%s\" retType=\"%s\" paramCount=\"%i\" desc=\"%s\">\n", funcs[i].name, funcs[i].retType, funcs[i].paramCount, funcs[i].desc + 3); + for (int p = 0; p < funcs[i].paramCount; p++) + { + fprintf(outFile, " <Param type=\"%s\" name=\"%s\" desc=\"%s\" />\n", funcs[i].paramType[p], funcs[i].paramName[p], funcs[i].paramDesc[p] + 3); + } + fprintf(outFile, " </Function>\n"); + } + fprintf(outFile, " </Functions>\n"); + + fprintf(outFile, "</raylibAPI>\n"); + + } break; + default: break; + } + + fclose(outFile); +} |