Sun, 29 Jul 2012 04:02:07 +0300
Parser can now read expressions 100% properly and can perform variable assignment. I'd call this a milestone!
commands.cxx | file | annotate | diff | comparison | revisions | |
common.h | file | annotate | diff | comparison | revisions | |
databuffer.h | file | annotate | diff | comparison | revisions | |
events.cxx | file | annotate | diff | comparison | revisions | |
main.cxx | file | annotate | diff | comparison | revisions | |
objwriter.h | file | annotate | diff | comparison | revisions | |
parser.cxx | file | annotate | diff | comparison | revisions | |
preprocessor.cxx | file | annotate | diff | comparison | revisions | |
scriptreader.cxx | file | annotate | diff | comparison | revisions | |
scriptreader.h | file | annotate | diff | comparison | revisions | |
stringtable.cxx | file | annotate | diff | comparison | revisions | |
variables.cxx | file | annotate | diff | comparison | revisions |
--- a/commands.cxx Sat Jul 28 17:57:37 2012 +0300 +++ b/commands.cxx Sun Jul 29 04:02:07 2012 +0300 @@ -47,6 +47,8 @@ #include "str.h" #include "commands.h" +// ============================================================================ +// Reads command definitions from commands.def and stores them to memory. void ReadCommands () { ScriptReader* r = new ScriptReader ("commands.def"); g_CommDef = NULL; @@ -66,6 +68,8 @@ // Name r->MustNext (); comm->name = r->token; + if (IsKeyword (comm->name)) + r->ParserError ("command name `%s` conflicts with keyword", comm->name.chars()); r->MustNext (":"); @@ -142,20 +146,21 @@ numCommDefs++; } + if (!numCommDefs) + r->ParserError ("no commands defined!\n"); + r->CloseFile (); delete r; - - if (!numCommDefs) - error ("no commands defined!\n"); printf ("%d command definitions read.\n", numCommDefs); } +// ============================================================================ +// Get command type by name int GetCommandType (str t) { // "float" is for now just int. // TODO: find out how BotScript floats work // (are they real floats or fixed? how are they - // stored?) and add proper floating point support. - // NOTE: Also, shouldn't use RETURNVAL for data types.. + // stored?) and add proper floating point number support. t = t.tolower(); return !t.compare ("int") ? TYPE_INT : !t.compare ("float") ? TYPE_INT : @@ -164,7 +169,8 @@ !t.compare ("bool") ? TYPE_INT : -1; } -// Inverse operation +// ============================================================================ +// Inverse operation - type name by value str GetReturnTypeName (int r) { switch (r) { case RETURNVAL_INT: return "int"; break; @@ -176,6 +182,8 @@ return ""; } +// ============================================================================ +// Finds a command by name CommandDef* FindCommand (str fname) { CommandDef* comm; ITERATE_COMMANDS (comm) { @@ -186,6 +194,8 @@ return NULL; } +// ============================================================================ +// Returns the prototype of the command str GetCommandPrototype (CommandDef* comm) { str text; text += GetReturnTypeName (comm->returnvalue);
--- a/common.h Sat Jul 28 17:57:37 2012 +0300 +++ b/common.h Sun Jul 29 04:02:07 2012 +0300 @@ -117,6 +117,13 @@ } // Byte datatype -typedef unsigned long int byte; +typedef unsigned long int word; + +// Keywords +#define NUM_KEYWORDS 20 +#ifndef __MAIN_CXX__ +extern const char** g_Keywords; +#endif +bool IsKeyword (str s); #endif // __COMMON_H__ \ No newline at end of file
--- a/databuffer.h Sat Jul 28 17:57:37 2012 +0300 +++ b/databuffer.h Sun Jul 29 04:02:07 2012 +0300 @@ -106,6 +106,9 @@ // Merge another data buffer into this one. void Merge (DataBuffer* other) { + if (!other) + return; + for (unsigned int x = 0; x < other->writesize; x++) { unsigned char c = *(other->buffer+x); Write<unsigned char> (c);
--- a/events.cxx Sat Jul 28 17:57:37 2012 +0300 +++ b/events.cxx Sun Jul 29 04:02:07 2012 +0300 @@ -47,6 +47,9 @@ #include "events.h" EventDef* g_EventDef; + +// ============================================================================ +// Read event definitions from file void ReadEvents () { ScriptReader* r = new ScriptReader ("events.def"); g_EventDef = NULL; @@ -75,12 +78,16 @@ printf ("%d event definitions read.\n", numEventDefs); } +// ============================================================================ +// Delete event definitions recursively void UnlinkEvents (EventDef* e) { if (e->next) UnlinkEvents (e->next); delete e; } +// ============================================================================ +// Finds an event definition by index EventDef* FindEventByIdx (unsigned int idx) { EventDef* e = g_EventDef; while (idx > 0) { @@ -92,6 +99,8 @@ return e; } +// ============================================================================ +// Finds an event definition by name EventDef* FindEventByName (str a) { EventDef* e; for (e = g_EventDef; e->next != NULL; e = e->next) {
--- a/main.cxx Sat Jul 28 17:57:37 2012 +0300 +++ b/main.cxx Sun Jul 29 04:02:07 2012 +0300 @@ -38,6 +38,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#define __MAIN_CXX__ + #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -54,13 +56,31 @@ #include "bots.h" #include "botcommands.h" -bool fexists (char* path) { - if (FILE* test = fopen (path, "r")) { - fclose (test); - return true; - } - return false; -} +const char* g_Keywords[NUM_KEYWORDS] = { + "state", + "event", + "mainloop", + "onenter", + "onexit", + "var", + + // These ones aren't implemented yet but I plan to do so, thus they are + // reserved. Also serves as a to-do list of sorts for me. >:F + "break", + "case", + "continue", + "default", + "do", + "else", + "enum", // Would enum actually be useful? I think so. + "for", + "func", // Would function support need external support from zandronum? + "goto", // Labels and goto must be done before if or any loops... + "if", + "return", + "switch", + "while" +}; int main (int argc, char** argv) { // Intepret command-line parameters: @@ -88,10 +108,17 @@ printf ("%s\n%s\n", header.chars(), headerline.chars()); if (argc < 2) { - fprintf (stderr, "usage: %s: <infile> [outfile]\n", argv[0]); + fprintf (stderr, "usage: %s <infile> [outfile]\n", argv[0]); + fprintf (stderr, " %s -l # lists commands\n", argv[0]); exit (1); } + // A word should always be exactly 4 bytes. The above list command + // doesn't need it, but the rest of the program does. + if (sizeof (word) != 4) + error ("%s expects a word (unsigned long int) to be 4 bytes in size, is %d\n", + APPNAME, sizeof (word)); + str outfile; if (argc < 3) outfile = ObjectFileName (argv[1]); @@ -142,7 +169,7 @@ // Parse done, print statistics and write to file unsigned int globalcount = CountGlobalVars (); - printf ("%u global variable%s\n", globalcount, PLURAL (globalcount)); + printf ("%u/%u global variable%s\n", globalcount, MAX_SCRIPT_VARIABLES, PLURAL (globalcount)); printf ("%d state%s written\n", g_NumStates, PLURAL (g_NumStates)); printf ("%d event%s written\n", g_NumEvents, PLURAL (g_NumEvents)); w->WriteToFile (); @@ -155,12 +182,29 @@ printf ("done!\n"); } +// ============================================================================ +// Utility functions + +// ============================================================================ +// Does the given file exist? +bool fexists (char* path) { + if (FILE* test = fopen (path, "r")) { + fclose (test); + return true; + } + return false; +} + +// ============================================================================ +// Generic error void error (const char* text, ...) { PERFORM_FORMAT (text, c); fprintf (stderr, "error: %s", c); exit (1); } +// ============================================================================ +// Mutates given filename to an object filename char* ObjectFileName (str s) { // Locate the extension and chop it out unsigned int extdot = s.last ("."); @@ -169,4 +213,13 @@ s += ".o"; return s.chars(); +} + +// ============================================================================ +// Is the given argument a reserved keyword? +bool IsKeyword (str s) { + for (unsigned int u = 0; u < NUM_KEYWORDS; u++) + if (!s.icompare (g_Keywords[u])) + return true; + return false; } \ No newline at end of file
--- a/objwriter.h Sat Jul 28 17:57:37 2012 +0300 +++ b/objwriter.h Sun Jul 29 04:02:07 2012 +0300 @@ -82,7 +82,7 @@ } // Cannot use default arguments in function templates.. - void Write (byte stuff) {Write<byte> (stuff);} + void Write (word stuff) {Write<word> (stuff);} }; #endif // __OBJWRITER_H__ \ No newline at end of file
--- a/parser.cxx Sat Jul 28 17:57:37 2012 +0300 +++ b/parser.cxx Sun Jul 29 04:02:07 2012 +0300 @@ -62,9 +62,12 @@ bool g_GotMainLoop = false; // ============================================================================ -// Main parser +// Main parser code. Begins read of the script file, checks the syntax of it +// and writes the data to the object file via ObjWriter - which also takes care +// of necessary buffering so stuff is written in the correct order. void ScriptReader::BeginParse (ObjWriter* w) { while (Next()) { + printf ("BeginParse: token: `%s`\n", token.chars()); if (!token.icompare ("state")) { MUST_TOPLEVEL @@ -114,7 +117,7 @@ g_CurMode = MODE_EVENT; w->Write (DH_EVENT); - w->Write<byte> (e->number); + w->Write<word> (e->number); g_NumEvents++; continue; } @@ -164,6 +167,7 @@ } if (!token.compare ("}")) { + printf ("parse closing brace\n"); // Closing brace int dataheader = (g_CurMode == MODE_EVENT) ? DH_ENDEVENT : (g_CurMode == MODE_MAINLOOP) ? DH_ENDMAINLOOP : @@ -174,8 +178,8 @@ ParserError ("unexpected `}`"); // Data header must be written before mode is changed because - // onenter and mainloop go into buffers, and we want the closing - // data headers into said buffers too. + // onenter and mainloop go into special buffers, and we want + // the closing data headers into said buffers too. w->Write (dataheader); g_CurMode = MODE_TOPLEVEL; @@ -184,12 +188,23 @@ continue; } + // If it's a variable, expect assignment. + if (ScriptVar* var = FindGlobalVariable (token)) { + DataBuffer* b = ParseAssignment (var); + printf ("current token after assignment: `%s`\n", token.chars()); + MustNext (";"); + w->WriteBuffer (b); + delete b; + continue; + } + // If it's not a keyword, parse it as an expression. + printf ("token length: %d, first char: %c [%d]\n", token.len(), token.chars()[0], token.chars()[0]); DataBuffer* b = ParseExpression (TYPE_VOID); w->WriteBuffer (b); delete b; - printf ("expression done!\n"); - MustThis (";"); + printf ("expression done! current token is %s\n", token.chars()); + MustNext (";"); } if (g_CurMode != MODE_TOPLEVEL) @@ -207,18 +222,22 @@ } // ============================================================================ -// Parses a given command +// Parses a command call DataBuffer* ScriptReader::ParseCommand (CommandDef* comm) { DataBuffer* r = new DataBuffer(64); - // If this was defined at top-level, we stop right at square one! if (g_CurMode == MODE_TOPLEVEL) - ParserError ("no commands allowed at top level!"); + ParserError ("command call at top level"); + printf ("\n\n\n=====================================\nBEGIN PARSING COMMAND\n"); printf ("token: %s\n", token.chars()); MustNext ("("); + MustNext (); int curarg = 0; while (1) { + printf ("at argument %d\n", curarg); + printf ("next token: %s\n", token.chars()); + if (!token.compare (")")) { printf ("closing command with token `%s`\n", token.chars()); if (curarg < comm->numargs - 1) @@ -226,28 +245,26 @@ break; curarg++; } - printf ("prev token: %s\n", token.chars()); - MustNext (); - printf ("next token: %s\n", token.chars()); - - // jump back to start - if (!token.compare (")")) - continue; if (curarg >= comm->maxargs) ParserError ("too many arguments passed to %s\n", comm->name.chars()); r->Merge (ParseExpression (comm->argtypes[curarg])); + MustNext (); + printf ("after expression, token is `%s`\n", token.chars()); if (curarg < comm->numargs - 1) { MustThis (","); + MustNext (); } else if (curarg < comm->maxargs - 1) { // Can continue, but can terminate as well. if (!token.compare (")")) { curarg++; break; - } else + } else { MustThis (","); + MustNext (); + } } curarg++; @@ -255,19 +272,21 @@ // If the script skipped any optional arguments, fill in defaults. while (curarg < comm->maxargs) { - r->Write<byte> (DH_PUSHNUMBER); - r->Write<byte> (comm->defvals[curarg]); + r->Write<word> (DH_PUSHNUMBER); + r->Write<word> (comm->defvals[curarg]); curarg++; } - r->Write<byte> (DH_COMMAND); - r->Write<byte> (comm->number); - r->Write<byte> (comm->maxargs); + r->Write<word> (DH_COMMAND); + r->Write<word> (comm->number); + r->Write<word> (comm->maxargs); printf ("command complete\n"); return r; } +// ============================================================================ +// Is the given operator an assignment operator? static bool IsAssignmentOperator (int oper) { switch (oper) { case OPER_ASSIGNADD: @@ -282,6 +301,7 @@ } // ============================================================================ +// Finds an operator's corresponding dataheader static long DataHeaderByOperator (ScriptVar* var, int oper) { if (IsAssignmentOperator (oper)) { if (!var) @@ -312,7 +332,7 @@ } // ============================================================================ -// Parses an expression +// Parses an expression, potentially recursively DataBuffer* ScriptReader::ParseExpression (int reqtype) { printf ("begin parsing expression. this token is `%s`, next token is `%s`\n", token.chars(), PeekNext().chars()); @@ -320,20 +340,18 @@ DataBuffer* lb = NULL; + // If it's a variable, note it down - we need to do special checks with it later. ScriptVar* var = FindGlobalVariable (token); - if (var) { - MustNext (); - if (g_CurMode == MODE_TOPLEVEL) // TODO: lift this restriction - ParserError ("can't alter variables at top level"); - reqtype = TYPE_INT; - } else - lb = ParseExprValue (reqtype); + + lb = ParseExprValue (reqtype); printf ("done\n"); // Get an operator printf ("parse operator at token %s\n", token.chars()); - int oper = ParseOperator (); + int oper = ParseOperator (true); + printf ("operator parsed: token is now %s\n", token.chars()); printf ("got %d\n", oper); + // No operator found - stop here. if (oper == -1) { retbuf->Merge (lb); @@ -341,30 +359,37 @@ return retbuf; } + // We peeked the operator, move forward now + MustNext(); + + // Can't be an assignement operator, those belong in assignments. + if (IsAssignmentOperator (oper)) + ParserError ("assignment operator inside expressions"); + // Parse the right operand, printf ("parse right operand\n"); MustNext (); DataBuffer* rb = ParseExprValue (reqtype); printf ("done\n"); + retbuf->Merge (rb); retbuf->Merge (lb); - retbuf->Merge (rb); long dh = DataHeaderByOperator (var, oper); - retbuf->Write<byte> (dh); - - if (IsAssignmentOperator (oper)) - retbuf->Write<byte> (var->index); + retbuf->Write<word> (dh); printf ("expression complete\n"); return retbuf; } // ============================================================================ -// Parsess an operator from tokens and returns an identifier. -int ScriptReader::ParseOperator () { +// Parses an operator string. Returns the operator number code. +int ScriptReader::ParseOperator (bool peek) { str oper; - oper += PeekNext (); + if (peek) + oper += PeekNext (); + else + oper += token; // Check one-char operators int o = !oper.compare ("=") ? OPER_ASSIGN : @@ -376,13 +401,11 @@ -1; if (o != -1) { - MustNext (); return o; } // Two-char operators - MustNext (); - oper += PeekNext (1); + oper += PeekNext (peek ? 1 : 0); o = !oper.compare ("+=") ? OPER_ASSIGNADD : !oper.compare ("-=") ? OPER_ASSIGNSUB : @@ -391,10 +414,8 @@ !oper.compare ("%=") ? OPER_ASSIGNMOD : -1; - if (o != -1) { + if (o != -1) MustNext (); - MustNext (); - } return o; } @@ -423,12 +444,12 @@ // Command if (reqtype && comm->returnvalue != reqtype) ParserError ("%s returns an incompatible data type", comm->name.chars()); - return ParseCommand (comm); + b = ParseCommand (comm); } else if ((g = FindGlobalVariable (token)) && reqtype != TYPE_STRING) { printf ("value is a global var\n"); // Global variable - b->Write<byte> (DH_PUSHGLOBALVAR); - b->Write<byte> (g->index); + b->Write<word> (DH_PUSHGLOBALVAR); + b->Write<word> (g->index); } else { printf ("value is a literal\n"); // If nothing else, check for literal @@ -437,22 +458,15 @@ ParserError ("bad syntax"); break; case TYPE_INT: { - printf ("value is an integer literal\n"); - /* if (!token.isnumber ()) - ParserError ("expected an integer, got `%s`", token.chars()); - */ MustNumber (true); - printf ("literal is `%s` = %d\n", token.chars(), atoi (token.chars())); // All values are written unsigned - thus we need to write the value's // absolute value, followed by an unary minus if it was negative. - b->Write<byte> (DH_PUSHNUMBER); + b->Write<word> (DH_PUSHNUMBER); long v = atoi (token.chars ()); - b->Write<byte> (static_cast<byte> (abs (v))); - if (v < 0) { - printf ("%ld is negative, write abs value %d and unary minus\n", v, abs(v)); - b->Write<byte> (DH_UNARYMINUS); - } + b->Write<word> (static_cast<word> (abs (v))); + if (v < 0) + b->Write<word> (DH_UNARYMINUS); break; } case TYPE_STRING: @@ -461,11 +475,45 @@ // table and returns it index if it doesn't find it there. printf ("value is a string literal\n"); MustString (true); - b->Write<byte> (DH_PUSHSTRINGINDEX); - b->Write<byte> (PushToStringTable (token.chars())); + b->Write<word> (DH_PUSHSTRINGINDEX); + b->Write<word> (PushToStringTable (token.chars())); break; } } + printf ("value parsed: current token is `%s`\n", token.chars()); return b; +} + +// ============================================================================ +// Parses an assignment. An assignment starts with a variable name, followed +// by an assignment operator, followed by an expression value. Expects current +// token to be the name of the variable, and expects the variable to be given. +DataBuffer* ScriptReader::ParseAssignment (ScriptVar* var) { + printf ("ASSIGNMENT: this token is `%s`, next token is `%s`\n", + token.chars(), PeekNext().chars()); + + // Get an operator + printf ("parse assignment operator at token %s\n", token.chars()); + + MustNext (); + int oper = ParseOperator (); + if (!IsAssignmentOperator (oper)) + ParserError ("expected assignment operator"); + printf ("got %d\n", oper); + + if (g_CurMode == MODE_TOPLEVEL) // TODO: lift this restriction + ParserError ("can't alter variables at top level"); + + // Parse the right operand, + printf ("parse right operand\n"); + MustNext (); + DataBuffer* retbuf = ParseExprValue (TYPE_INT); + + long dh = DataHeaderByOperator (var, oper); + retbuf->Write<word> (dh); + retbuf->Write<word> (var->index); + + printf ("assignment complete\n"); + return retbuf; } \ No newline at end of file
--- a/preprocessor.cxx Sat Jul 28 17:57:37 2012 +0300 +++ b/preprocessor.cxx Sun Jul 29 04:02:07 2012 +0300 @@ -65,6 +65,7 @@ } } +// ============================================================================ // Reads a word until whitespace str ScriptReader::PPReadWord (char &term) { str word; @@ -79,13 +80,16 @@ return word; } +// ============================================================================ +// Preprocess any directives found in the script file void ScriptReader::PreprocessDirectives () { size_t spos = ftell (fp[fc]); if (!DoDirectivePreprocessing ()) fseek (fp[fc], spos, SEEK_SET); } -/* Returns true if the pre-processing was successful, false if not. +/* ============================================================================ + * Returns true if the pre-processing was successful, false if not. * If pre-processing was successful, the file pointer remains where * it was, if not, it's pushed back to where it was before preprocessing * took place and is parsed normally.
--- a/scriptreader.cxx Sat Jul 28 17:57:37 2012 +0300 +++ b/scriptreader.cxx Sun Jul 29 04:02:07 2012 +0300 @@ -59,7 +59,11 @@ } ScriptReader::~ScriptReader () { - FinalChecks (); + // If comment mode is 2 by the time the file ended, the + // comment was left unterminated. 1 is no problem, since + // it's terminated by newlines anyway. + if (commentmode == 2) + ParserError ("unterminated `/*`-style comment"); for (unsigned int u = 0; u < MAX_FILESTACK; u++) { if (fp[u]) { @@ -362,16 +366,19 @@ MustNext (); num += token; - // Cater for a possible minus sign, since it - // breaks the token, read a new one with the number. + // The number can possibly start off with a minus sign, which + // also breaks the token. If we encounter it, read another token + // and merge it to this one. if (!token.compare ("-")) { MustNext (); num += token; } + // Result must be a number. if (!num.isnumber()) ParserError ("expected a number, got `%s`", num.chars()); + // Save the number into the token. token = num; } @@ -396,13 +403,4 @@ case RETURNVAL_STRING: MustString (); break; case RETURNVAL_BOOLEAN: MustBool (); break; } -} - -// Checks to be performed at the end of file -void ScriptReader::FinalChecks () { - // If comment mode is 2 by the time the file ended, the - // comment was left unterminated. 1 is no problem, since - // it's terminated by newlines anyway. - if (commentmode == 2) - ParserError ("unterminated `/*`-style comment"); } \ No newline at end of file
--- a/scriptreader.h Sat Jul 28 17:57:37 2012 +0300 +++ b/scriptreader.h Sun Jul 29 04:02:07 2012 +0300 @@ -48,6 +48,8 @@ #define MAX_FILESTACK 8 +class ScriptVar; + class ScriptReader { public: // ==================================================================== @@ -89,13 +91,12 @@ void ParserError (const char* message, ...); void ParserWarning (const char* message, ...); - void FinalChecks (); - // parser.cxx: void BeginParse (ObjWriter* w); DataBuffer* ParseCommand (CommandDef* comm); DataBuffer* ParseExpression (int reqtype); - int ParseOperator (); + DataBuffer* ParseAssignment (ScriptVar* var); + int ParseOperator (bool peek = false); DataBuffer* ParseExprValue (int reqtype); // preprocessor.cxx:
--- a/stringtable.cxx Sat Jul 28 17:57:37 2012 +0300 +++ b/stringtable.cxx Sun Jul 29 04:02:07 2012 +0300 @@ -46,6 +46,8 @@ #include "bots.h" #include "stringtable.h" +// ============================================================================ +// Initializes the string table void InitStringTable() { // Zero out everything first. for (unsigned int a = 0; a < MAX_LIST_STRINGS; a++) @@ -53,10 +55,13 @@ g_StringTable[a][b] = 0; } +// ============================================================================ +// Potentially adds a string to the table and returns the index of it. unsigned int PushToStringTable (char* s) { // Find a free slot in the table. unsigned int a; for (a = 0; a < MAX_LIST_STRINGS; a++) { + // String is already in the table, thus return it. if (!strcmp (s, g_StringTable[a])) return a; @@ -81,6 +86,8 @@ return a; } +// ============================================================================ +// Counts the amount of strings in the table. unsigned int CountStringTable () { unsigned int count = 0; for (unsigned int a = 0; a < MAX_LIST_STRINGS; a++) {
--- a/variables.cxx Sat Jul 28 17:57:37 2012 +0300 +++ b/variables.cxx Sun Jul 29 04:02:07 2012 +0300 @@ -59,9 +59,17 @@ g_GlobalVariables[u] = NULL; } +// ============================================================================ // Tries to declare a new global-scope variable. Returns pointer // to new global variable, NULL if declaration failed. ScriptVar* DeclareGlobalVariable (ScriptReader* r, str name) { + // Check that the name is valid + if (FindCommand (name)) + r->ParserError ("name of variable-to-be `%s` conflicts with that of a command", name.chars()); + + if (IsKeyword (name)) + r->ParserError ("name of variable-to-be `%s` is a keyword", name.chars()); + // Find a NULL pointer to a global variable ScriptVar* g; unsigned int u = 0; @@ -71,7 +79,7 @@ break; if (!g_GlobalVariables[u]->name.icompare (name)) - r->ParserError ("tried to declare global scope variable %s twice", name.chars()); + r->ParserError ("attempted redeclaration of variable `%s`", name.chars()); } if (u == MAX_SCRIPT_VARIABLES) @@ -96,6 +104,7 @@ } */ +// ============================================================================ // Find a global variable by name ScriptVar* FindGlobalVariable (str name) { unsigned int u = 0; @@ -111,6 +120,8 @@ return NULL; } +// ============================================================================ +// Count all declared global variables unsigned int CountGlobalVars () { unsigned int count = 0; unsigned int u = 0;