Wed, 19 Dec 2012 13:44:18 +0200
Added constants, these are defined with const, take their value immediately and are replaced out with their value when used. The strlen operator can be used to get a string constant's length.
botcommands.h | file | annotate | diff | comparison | revisions | |
commands.cxx | file | annotate | diff | comparison | revisions | |
commands.h | file | annotate | diff | comparison | revisions | |
common.h | file | annotate | diff | comparison | revisions | |
databuffer.h | file | annotate | diff | comparison | revisions | |
main.cxx | file | annotate | diff | comparison | revisions | |
objwriter.cxx | file | annotate | diff | comparison | revisions | |
objwriter.h | file | annotate | diff | comparison | revisions | |
parser.cxx | file | annotate | diff | comparison | revisions | |
scriptreader.h | file | annotate | diff | comparison | revisions |
--- a/botcommands.h Wed Dec 19 04:20:02 2012 +0200 +++ b/botcommands.h Wed Dec 19 13:44:18 2012 +0200 @@ -170,14 +170,4 @@ } BOTCMD_e; -//***************************************************************************** -typedef enum -{ - RETURNVAL_VOID, - RETURNVAL_INT, - RETURNVAL_BOOLEAN, - RETURNVAL_STRING, - -} RETURNVAL_e; - #endif // __BOTCOMMANDS_H__ \ No newline at end of file
--- a/commands.cxx Wed Dec 19 04:20:02 2012 +0200 +++ b/commands.cxx Wed Dec 19 13:44:18 2012 +0200 @@ -75,7 +75,7 @@ // Return value r->MustNext (); - comm->returnvalue = GetCommandType (r->token); + comm->returnvalue = GetTypeByName (r->token); if (comm->returnvalue == -1) r->ParserError ("bad return value type `%s` for command %s", r->token.chars(), comm->name.chars()); @@ -100,10 +100,10 @@ r->MustNext (":"); r->MustNext (); - int type = GetCommandType (r->token); + type_e type = GetTypeByName (r->token); if (type == -1) r->ParserError ("bad argument %d type `%s`", curarg, r->token.chars()); - if (type == RETURNVAL_VOID) + if (type == TYPE_VOID) r->ParserError ("void is not a valid argument type!"); comm->argtypes[curarg] = type; @@ -122,10 +122,17 @@ if (curarg >= comm->numargs) { r->MustNext ("="); switch (type) { - case RETURNVAL_INT: - case RETURNVAL_BOOLEAN: - r->MustNumber(); break; - case RETURNVAL_STRING: r->MustString(); break; + case TYPE_INT: + case TYPE_BOOL: + r->MustNumber(); + break; + case TYPE_STRING: + r->MustString(); + break; + case TYPE_UNKNOWN: + case TYPE_FLOAT: + case TYPE_VOID: + break; } comm->defvals[curarg] = r->token; @@ -156,43 +163,6 @@ } // ============================================================================ -// Get command type by name -int GetCommandType (str t) { - t = t.tolower(); - return (t == "int") ? TYPE_INT : - (t == "float") ? TYPE_FLOAT : - (t == "str") ? TYPE_STRING : - (t == "void") ? TYPE_VOID : - (t == "bool") ? TYPE_BOOL : -1; -} - -// ============================================================================ -// Inverse operation - type name by value -str GetTypeName (int r) { - switch (r) { - case TYPE_INT: return "int"; break; - case TYPE_STRING: return "str"; break; - case TYPE_VOID: return "void"; break; - case TYPE_FLOAT: return "float"; break; - case TYPE_BOOL: return "bool"; break; - } - - return ""; -} - -// ============================================================================ -str GetReturnTypeName (int r) { - switch (r) { - case RETURNVAL_INT: return "int"; break; - case RETURNVAL_STRING: return "str"; break; - case RETURNVAL_VOID: return "void"; break; - case RETURNVAL_BOOLEAN: return "bool"; break; - } - - return ""; -} - -// ============================================================================ // Finds a command by name CommandDef* FindCommand (str fname) { CommandDef* comm; @@ -230,7 +200,7 @@ if (i >= comm->numargs) { text += '='; - bool isString = comm->argtypes[i] == RETURNVAL_STRING; + bool isString = comm->argtypes[i] == TYPE_STRING; if (isString) text += '"'; char defvalstring[8];
--- a/commands.h Wed Dec 19 04:20:02 2012 +0200 +++ b/commands.h Wed Dec 19 13:44:18 2012 +0200 @@ -44,6 +44,7 @@ #define MAX_MAXARGS 8 #define MAX_ARGNAMELEN 16 +#include "common.h" #include "str.h" #include "botcommands.h" @@ -55,8 +56,8 @@ int number; int numargs; int maxargs; - int returnvalue; - int argtypes[MAX_MAXARGS]; + type_e returnvalue; + type_e argtypes[MAX_MAXARGS]; char argnames[MAX_MAXARGS][MAX_ARGNAMELEN]; int defvals[MAX_MAXARGS]; CommandDef* next; @@ -64,8 +65,6 @@ void ReadCommands (); CommandDef* FindCommand (str a); -int GetCommandType (str token); -str GetReturnTypeName (int r); str GetCommandPrototype (CommandDef* comm); #ifndef __COMMANDS_CXX__
--- a/common.h Wed Dec 19 04:20:02 2012 +0200 +++ b/common.h Wed Dec 19 13:44:18 2012 +0200 @@ -67,6 +67,16 @@ MODE_ONEXIT, // inside onexit }; +enum type_e { + TYPE_UNKNOWN = 0, + TYPE_VOID, + TYPE_INT, + TYPE_STRING, + TYPE_FLOAT, + TYPE_BOOL, + +}; + #define CHECK_FILE(pointer,path,action) \ if (!pointer) { \ error ("couldn't open %s for %s!\n", (char*)path, action); \ @@ -89,6 +99,8 @@ void error (const char* text, ...); char* ObjectFileName (str s); bool fexists (char* path); +type_e GetTypeByName (str token); +str GetTypeName (type_e type); // Make the parser's variables globally available extern int g_NumStates;
--- a/databuffer.h Wed Dec 19 04:20:02 2012 +0200 +++ b/databuffer.h Wed Dec 19 13:44:18 2012 +0200 @@ -43,6 +43,7 @@ #include <stdio.h> #include <string.h> #include "common.h" +#include "stringtable.h" #define MAX_MARKS 512 @@ -279,6 +280,22 @@ count += !!refs[u]; return count; } + + // Write a float into the buffer + void WriteFloat (str floatstring) { + // TODO: Casting float to word causes the decimal to be lost. + // Find a way to store the number without such loss. + float val = atof (floatstring); + Write<word> (DH_PUSHNUMBER); + Write<word> (static_cast<word> ((val > 0) ? val : -val)); + if (val < 0) + Write<word> (DH_UNARYMINUS); + } + + void WriteString (str string) { + Write<word> (DH_PUSHSTRINGINDEX); + Write<word> (PushToStringTable (string)); + } }; #endif // __DATABUFFER_H__ \ No newline at end of file
--- a/main.cxx Wed Dec 19 04:20:02 2012 +0200 +++ b/main.cxx Wed Dec 19 13:44:18 2012 +0200 @@ -53,28 +53,34 @@ #include "stringtable.h" #include "variables.h" #include "array.h" +#include "databuffer.h" #include "bots.h" #include "botcommands.h" // List of keywords const char* g_Keywords[] = { + "bool", "break", "case", "continue", + "const", "default", "do", "else", "event", + "float", "for", "goto", "if", + "int", "mainloop", "onenter", "onexit", "state", "switch", - "var" + "str" + "void", "while", // These ones aren't implemented yet but I plan to do so, thus they are @@ -244,4 +250,32 @@ unsigned int NumKeywords () { return sizeof (g_Keywords) / sizeof (const char*); +} + + +// ============================================================================ +type_e GetTypeByName (str t) { + t = t.tolower(); + return (t == "int") ? TYPE_INT : + (t == "float") ? TYPE_FLOAT : + (t == "str") ? TYPE_STRING : + (t == "void") ? TYPE_VOID : + (t == "bool") ? TYPE_BOOL : + TYPE_UNKNOWN; +} + + +// ============================================================================ +// Inverse operation - type name by value +str GetTypeName (type_e type) { + switch (type) { + case TYPE_INT: return "int"; break; + case TYPE_STRING: return "str"; break; + case TYPE_VOID: return "void"; break; + case TYPE_FLOAT: return "float"; break; + case TYPE_BOOL: return "bool"; break; + case TYPE_UNKNOWN: return "???"; break; + } + + return ""; } \ No newline at end of file
--- a/objwriter.cxx Wed Dec 19 04:20:02 2012 +0200 +++ b/objwriter.cxx Wed Dec 19 13:44:18 2012 +0200 @@ -45,6 +45,7 @@ #include "common.h" #include "str.h" #include "objwriter.h" +#include "databuffer.h" #include "stringtable.h" #include "bots.h"
--- a/objwriter.h Wed Dec 19 04:20:02 2012 +0200 +++ b/objwriter.h Wed Dec 19 13:44:18 2012 +0200 @@ -45,8 +45,8 @@ #include <typeinfo> #include <string.h> #include "common.h" +#include "str.h" #include "databuffer.h" -#include "str.h" class ObjWriter { public: @@ -99,7 +99,6 @@ void MoveMark (unsigned int mark); void OffsetMark (unsigned int mark, int offset); void DeleteMark (unsigned int mark); - template <class T> void Write (T stuff) { GetCurrentBuffer ()->Write<T> (stuff); }
--- a/parser.cxx Wed Dec 19 04:20:02 2012 +0200 +++ b/parser.cxx Wed Dec 19 13:44:18 2012 +0200 @@ -50,6 +50,7 @@ #include "commands.h" #include "stringtable.h" #include "variables.h" +#include "array.h" #define MUST_TOPLEVEL if (g_CurMode != MODE_TOPLEVEL) \ ParserError ("%s-statements may only be defined at top level!", token.chars()); @@ -70,6 +71,7 @@ bool g_CanElse = false; str* g_UndefinedLabels[MAX_MARKS]; bool g_Neurosphere = false; // neurosphere-compat +array<constinfo_t> g_ConstInfo; // ============================================================================ // Main parser code. Begins read of the script file, checks the syntax of it @@ -258,7 +260,7 @@ MUST_NOT_TOPLEVEL MustNext ("{"); - // Don't use PushScope that will reset the scope. + // Don't use PushScope as it resets the scope g_ScopeCursor++; if (g_ScopeCursor >= MAX_SCOPE) ParserError ("too deep scope"); @@ -554,6 +556,47 @@ } // ============================================================ + if (token == "const") { + constinfo_t info; + + // Get the type + MustNext (); + info.type = GetTypeByName (token); + + if (info.type == TYPE_UNKNOWN || info.type == TYPE_VOID) + ParserError ("unknown type `%s` for constant", (char*)token); + + MustNext (); + info.name = token; + + MustNext ("="); + + switch (info.type) { + case TYPE_BOOL: + case TYPE_INT: + MustNumber (false); + info.val = token; + break; + case TYPE_STRING: + MustString (); + info.val = token; + break; + case TYPE_FLOAT: + MustNext (); + info.val = ParseFloat (); + break; + case TYPE_UNKNOWN: + case TYPE_VOID: + break; + } + + g_ConstInfo << info; + + MustNext (";"); + continue; + } + + // ============================================================ if (token == "}") { // Closing brace @@ -821,7 +864,7 @@ // ============================================================================ // Parses an expression, potentially recursively -DataBuffer* ScriptReader::ParseExpression (int reqtype) { +DataBuffer* ScriptReader::ParseExpression (type_e reqtype) { DataBuffer* retbuf = new DataBuffer (64); // Parse first operand @@ -880,6 +923,9 @@ else oper += token; + if (-oper == "strlen") + return OPER_STRLEN; + // Check one-char operators bool equalsnext = ISNEXT ("="); @@ -940,10 +986,26 @@ } // ============================================================================ +str ScriptReader::ParseFloat () { + MustNumber (true); + str floatstring = token; + + // Go after the decimal point + if (PeekNext () == ".") { + Next ("."); + MustNumber (false); + floatstring += "."; + floatstring += token; + } + + return floatstring; +} + +// ============================================================================ // Parses a value in the expression and returns the data needed to push // it, contained in a data buffer. A value can be either a variable, a command, // a literal or an expression. -DataBuffer* ScriptReader::ParseExprValue (int reqtype) { +DataBuffer* ScriptReader::ParseExprValue (type_e reqtype) { DataBuffer* b = new DataBuffer(16); ScriptVar* g; @@ -953,7 +1015,24 @@ if (negate) // Jump past the "!" Next (); - if (token == "(") { + // Handle strlen + if (token == "strlen") { + MustNext ("("); + MustNext (); + + // By this token we should get a string constant. + constinfo_t* constant = FindConstant (token); + if (!constant || constant->type != TYPE_STRING) + ParserError ("strlen only works with const str"); + + if (reqtype != TYPE_INT) + ParserError ("strlen returns int but %s is expected\n", (char*)GetTypeName (reqtype)); + + b->Write<word> (DH_PUSHNUMBER); + b->Write<word> (constant->val.len ()); + + MustNext (")"); + } else if (token == "(") { // Expression MustNext (); DataBuffer* c = ParseExpression (reqtype); @@ -966,15 +1045,38 @@ if (reqtype && comm->returnvalue != reqtype) ParserError ("%s returns an incompatible data type", comm->name.chars()); b = ParseCommand (comm); + } else if (constinfo_t* constant = FindConstant (token)) { + // Type check + if (reqtype != constant->type) + ParserError ("constant `%s` is %s, expression requires %s\n", + (char*)constant->name, (char*)GetTypeName (constant->type), + (char*)GetTypeName (reqtype)); + + switch (constant->type) { + case TYPE_BOOL: + case TYPE_INT: + b->Write<word> (DH_PUSHNUMBER); + b->Write<word> (atoi (constant->val)); + break; + case TYPE_FLOAT: + b->WriteFloat (constant->val); + break; + case TYPE_STRING: + b->WriteString (constant->val); + break; + case TYPE_VOID: + case TYPE_UNKNOWN: + break; + } } else if ((g = FindGlobalVariable (token))) { // Global variable b->Write<word> (DH_PUSHGLOBALVAR); b->Write<word> (g->index); } else { // If nothing else, check for literal - printf ("reqtype: %d\n", reqtype); switch (reqtype) { case TYPE_VOID: + case TYPE_UNKNOWN: ParserError ("unknown identifier `%s` (expected keyword, function or variable)", token.chars()); break; case TYPE_BOOL: @@ -996,37 +1098,21 @@ // string if it finds it in the table, or writes it to the // table and returns it index if it doesn't find it there. MustString (true); - b->Write<word> (DH_PUSHSTRINGINDEX); - b->Write<word> (PushToStringTable (token.chars())); + b->WriteString (token); break; case TYPE_FLOAT: { - str floatstring; - - MustNumber (true); - floatstring += token; - - // Go after the decimal point - if (PeekNext () == ".") { - MustNext ("."); - MustNumber (false); - floatstring += "."; - floatstring += token; - } - - // TODO: Casting float to word causes the decimal to be lost. - // Find a way to store the number without such loss. - float val = atof (floatstring); - b->Write<word> (DH_PUSHNUMBER); - b->Write<word> (static_cast<word> ((val > 0) ? val : -val)); - if (val < 0) - b->Write<word> (DH_UNARYMINUS); + str floatstring = ParseFloat (); // TODO: Keep this check after decimal loss is fixed, but make // it a real precision loss check. 55.5123 -> 55.512299, this // should probably be warned of. - float check = static_cast<float> (static_cast<word> (val)); - if (val != check) - ParserWarning ("floating point number %f loses precision (-> %f)", val, check); + float check = static_cast<float> (static_cast<word> (atof (floatstring))); + if (atof (floatstring) != check) + ParserWarning ("floating point number %f loses precision (-> %f)", + atof (floatstring), check); + + b->WriteFloat (floatstring); + break; } } } @@ -1048,7 +1134,6 @@ // Get an operator MustNext (); int oper = ParseOperator (); - printf ("got operator %d\n", oper); if (!IsAssignmentOperator (oper)) ParserError ("expected assignment operator"); @@ -1099,11 +1184,12 @@ } DataBuffer* ScriptReader::ParseStatement (ObjWriter* w) { + if (FindConstant (token)) // There should not be constants here. + ParserError ("invalid use for constant\n"); + // If it's a variable, expect assignment. - if (ScriptVar* var = FindGlobalVariable (token)) { - DataBuffer* b = ParseAssignment (var); - return b; - } + if (ScriptVar* var = FindGlobalVariable (token)) + return ParseAssignment (var); return NULL; } @@ -1129,4 +1215,11 @@ // Init a buffer for the case block and tell the object // writer to record all written data to it. info->casebuffers[info->casecursor] = w->SwitchBuffer = new DataBuffer; +} + +constinfo_t* FindConstant (str token) { + for (uint i = 0; i < g_ConstInfo.size(); i++) + if (g_ConstInfo[i].name == token) + return &g_ConstInfo[i]; + return NULL; } \ No newline at end of file
--- a/scriptreader.h Wed Dec 19 04:20:02 2012 +0200 +++ b/scriptreader.h Wed Dec 19 13:44:18 2012 +0200 @@ -43,8 +43,8 @@ #include <stdio.h> #include "str.h" +#include "commands.h" #include "objwriter.h" -#include "commands.h" #define MAX_FILESTACK 8 #define MAX_SCOPE 32 @@ -52,14 +52,6 @@ class ScriptVar; -enum type_e { - TYPE_VOID = 0, - TYPE_INT, - TYPE_STRING, - TYPE_FLOAT, - TYPE_BOOL -}; - // Operators enum operator_e { OPER_ADD, @@ -89,6 +81,7 @@ OPER_BITWISEAND, OPER_BITWISEEOR, OPER_TERNARY, + OPER_STRLEN, }; // Mark types @@ -135,6 +128,13 @@ }; // ============================================================================ +typedef struct { + str name; + type_e type; + str val; +} constinfo_t; + +// ============================================================================ // The script reader reads the script, parses it and tells the object writer // the bytecode it needs to write to file. class ScriptReader { @@ -181,10 +181,11 @@ // parser.cxx: void ParseBotScript (ObjWriter* w); DataBuffer* ParseCommand (CommandDef* comm); - DataBuffer* ParseExpression (int reqtype); + DataBuffer* ParseExpression (type_e reqtype); DataBuffer* ParseAssignment (ScriptVar* var); int ParseOperator (bool peek = false); - DataBuffer* ParseExprValue (int reqtype); + DataBuffer* ParseExprValue (type_e reqtype); + str ParseFloat (); void PushScope (); // preprocessor.cxx: @@ -204,6 +205,7 @@ str PPReadWord (char &term); }; +constinfo_t* FindConstant (str token); extern bool g_Neurosphere; #endif // __SCRIPTREADER_H__ \ No newline at end of file