Fri, 10 Jan 2014 21:58:42 +0200
- major refactoring begins
#include "objwriter.h" #include "scriptreader.h" #include "events.h" #include "commands.h" #include "stringtable.h" #include "variables.h" #include "containers.h" #define MUST_TOPLEVEL if (g_CurMode != MODE_TOPLEVEL) \ ParserError ("%s-statements may only be defined at top level!", token.chars()); #define MUST_NOT_TOPLEVEL if (g_CurMode == MODE_TOPLEVEL) \ ParserError ("%s-statements may not be defined at top level!", token.chars()); #define SCOPE(n) scopestack[g_ScopeCursor - n] int g_NumStates = 0; int g_NumEvents = 0; parsermode_e g_CurMode = MODE_TOPLEVEL; string g_CurState = ""; bool g_stateSpawnDefined = false; bool g_GotMainLoop = false; unsigned int g_ScopeCursor = 0; DataBuffer* g_IfExpression = null; bool g_CanElse = false; string* g_UndefinedLabels[MAX_MARKS]; bool g_Neurosphere = false; // neurosphere-compat list<constinfo_t> g_ConstInfo; // ============================================================================ // 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::ParseBotScript (ObjWriter* w) { // Zero the entire block stack first for (int i = 0; i < MAX_SCOPE; i++) ZERO(scopestack[i]); for (int i = 0; i < MAX_MARKS; i++) g_UndefinedLabels[i] = null; while (Next()) { // Check if else is potentically valid if (token == "else" && !g_CanElse) ParserError ("else without preceding if"); if (token != "else") g_CanElse = false; // ============================================================ if (token == "state") { MUST_TOPLEVEL MustString (); // State name must be a word. if (token.first (" ") != token.len()) ParserError ("state name must be a single word, got `%s`", token.chars()); string statename = token; // stateSpawn is special - it *must* be defined. If we // encountered it, then mark down that we have it. if (-token == "statespawn") g_stateSpawnDefined = true; // Must end in a colon MustNext (":"); // Write the previous state's onenter and // mainloop buffers to file now if (g_CurState.len()) w->WriteBuffers(); w->Write (DH_STATENAME); w->WriteString (statename); w->Write (DH_STATEIDX); w->Write (g_NumStates); g_NumStates++; g_CurState = token; g_GotMainLoop = false; continue; } // ============================================================ if (token == "event") { MUST_TOPLEVEL // Event definition MustString (); EventDef* e = FindEventByName (token); if (!e) ParserError ("bad event, got `%s`\n", token.chars()); MustNext ("{"); g_CurMode = MODE_EVENT; w->Write (DH_EVENT); w->Write (e->number); g_NumEvents++; continue; } // ============================================================ if (token == "mainloop") { MUST_TOPLEVEL MustNext ("{"); // Mode must be set before dataheader is written here! g_CurMode = MODE_MAINLOOP; w->Write (DH_MAINLOOP); continue; } // ============================================================ if (token == "onenter" || token == "onexit") { MUST_TOPLEVEL bool onenter = token == "onenter"; MustNext ("{"); // Mode must be set before dataheader is written here, // because onenter goes to a separate buffer. g_CurMode = onenter ? MODE_ONENTER : MODE_ONEXIT; w->Write (onenter ? DH_ONENTER : DH_ONEXIT); continue; } // ============================================================ if (token == "int" || token == "str" || token == "bool") { // For now, only globals are supported if (g_CurMode != MODE_TOPLEVEL || g_CurState.len()) ParserError ("variables must only be global for now"); type_e type = (token == "int") ? TYPE_INT : (token == "str") ? TYPE_STRING : TYPE_BOOL; MustNext (); // Var name must not be a number if (token.is_numeric()) ParserError ("variable name must not be a number"); string varname = token; ScriptVar* var = DeclareGlobalVariable (this, type, varname); if (!var) ParserError ("declaring %s variable %s failed", g_CurState.len() ? "state" : "global", varname.chars()); MustNext (";"); continue; } // ============================================================ // Goto if (token == "goto") { MUST_NOT_TOPLEVEL // Get the name of the label MustNext (); // Find the mark this goto statement points to unsigned int m = w->FindMark (token); // If not set, define it if (m == MAX_MARKS) { m = w->AddMark (token); g_UndefinedLabels[m] = new string (token); } // Add a reference to the mark. w->Write (DH_GOTO); w->AddReference (m); MustNext (";"); continue; } // ============================================================ // If if (token == "if") { MUST_NOT_TOPLEVEL PushScope (); // Condition MustNext ("("); // Read the expression and write it. MustNext (); DataBuffer* c = ParseExpression (TYPE_INT); w->WriteBuffer (c); MustNext (")"); MustNext ("{"); // Add a mark - to here temporarily - and add a reference to it. // Upon a closing brace, the mark will be adjusted. unsigned int marknum = w->AddMark (""); // Use DH_IFNOTGOTO - if the expression is not true, we goto the mark // we just defined - and this mark will be at the end of the scope block. w->Write (DH_IFNOTGOTO); w->AddReference (marknum); // Store it SCOPE(0).mark1 = marknum; SCOPE(0).type = SCOPETYPE_IF; continue; } if (token == "else") { MUST_NOT_TOPLEVEL MustNext ("{"); // Don't use PushScope as it resets the scope g_ScopeCursor++; if (g_ScopeCursor >= MAX_SCOPE) ParserError ("too deep scope"); if (SCOPE(0).type != SCOPETYPE_IF) ParserError ("else without preceding if"); // Write down to jump to the end of the else statement // Otherwise we have fall-throughs SCOPE(0).mark2 = w->AddMark (""); // Instruction to jump to the end after if block is complete w->Write (DH_GOTO); w->AddReference (SCOPE(0).mark2); // Move the ifnot mark here and set type to else w->MoveMark (SCOPE(0).mark1); SCOPE(0).type = SCOPETYPE_ELSE; continue; } // ============================================================ // While if (token == "while") { MUST_NOT_TOPLEVEL PushScope (); // While loops need two marks - one at the start of the loop and one at the // end. The condition is checked at the very start of the loop, if it fails, // we use goto to skip to the end of the loop. At the end, we loop back to // the beginning with a go-to statement. unsigned int mark1 = w->AddMark (""); // start unsigned int mark2 = w->AddMark (""); // end // Condition MustNext ("("); MustNext (); DataBuffer* expr = ParseExpression (TYPE_INT); MustNext (")"); MustNext ("{"); // Write condition w->WriteBuffer (expr); // Instruction to go to the end if it fails w->Write (DH_IFNOTGOTO); w->AddReference (mark2); // Store the needed stuff SCOPE(0).mark1 = mark1; SCOPE(0).mark2 = mark2; SCOPE(0).type = SCOPETYPE_WHILE; continue; } // ============================================================ // For loop if (token == "for") { MUST_NOT_TOPLEVEL PushScope (); // Initializer MustNext ("("); MustNext (); DataBuffer* init = ParseStatement (w); if (!init) ParserError ("bad statement for initializer of for"); MustNext (";"); // Condition MustNext (); DataBuffer* cond = ParseExpression (TYPE_INT); if (!cond) ParserError ("bad statement for condition of for"); MustNext (";"); // Incrementor MustNext (); DataBuffer* incr = ParseStatement (w); if (!incr) ParserError ("bad statement for incrementor of for"); MustNext (")"); MustNext ("{"); // First, write out the initializer w->WriteBuffer (init); // Init two marks int mark1 = w->AddMark (""); int mark2 = w->AddMark (""); // Add the condition w->WriteBuffer (cond); w->Write (DH_IFNOTGOTO); w->AddReference (mark2); // Store the marks and incrementor SCOPE(0).mark1 = mark1; SCOPE(0).mark2 = mark2; SCOPE(0).buffer1 = incr; SCOPE(0).type = SCOPETYPE_FOR; continue; } // ============================================================ // Do/while loop if (token == "do") { MUST_NOT_TOPLEVEL PushScope (); MustNext ("{"); SCOPE(0).mark1 = w->AddMark (""); SCOPE(0).type = SCOPETYPE_DO; continue; } // ============================================================ // Switch if (token == "switch") { /* This goes a bit tricky. switch is structured in the * bytecode followingly: * (expression) * case a: goto casemark1 * case b: goto casemark2 * case c: goto casemark3 * goto mark1 // jump to end if no matches * casemark1: ... * casemark2: ... * casemark3: ... * mark1: // end mark */ MUST_NOT_TOPLEVEL PushScope (); MustNext ("("); MustNext (); w->WriteBuffer (ParseExpression (TYPE_INT)); MustNext (")"); MustNext ("{"); SCOPE(0).type = SCOPETYPE_SWITCH; SCOPE(0).mark1 = w->AddMark (""); // end mark SCOPE(0).buffer1 = null; // default header continue; } // ============================================================ if (token == "case") { // case is only allowed inside switch if (SCOPE(0).type != SCOPETYPE_SWITCH) ParserError ("case label outside switch"); // Get the literal (Zandronum does not support expressions here) MustNumber (); int num = atoi (token.chars ()); MustNext (":"); for (int i = 0; i < MAX_CASE; i++) if (SCOPE(0).casenumbers[i] == num) ParserError ("multiple case %d labels in one switch", num); // Write down the expression and case-go-to. This builds // the case tree. The closing event will write the actual // blocks and move the marks appropriately. // AddSwitchCase will add the reference to the mark // for the case block that this heralds, and takes care // of buffering setup and stuff like that. // null the switch buffer for the case-go-to statement, // we want it all under the switch, not into the case-buffers. w->SwitchBuffer = null; w->Write (DH_CASEGOTO); w->Write (num); AddSwitchCase (w, null); SCOPE(0).casenumbers[SCOPE(0).casecursor] = num; continue; } if (token == "default") { if (SCOPE(0).type != SCOPETYPE_SWITCH) ParserError ("default label outside switch"); if (SCOPE(0).buffer1) ParserError ("multiple default labels in one switch"); MustNext (":"); // The default header is buffered into buffer1, since // it has to be the last of the case headers // // Since the expression is pushed into the switch // and is only popped when case succeeds, we have // to pop it with DH_DROP manually if we end up in // a default. DataBuffer* b = new DataBuffer; SCOPE(0).buffer1 = b; b->Write (DH_DROP); b->Write (DH_GOTO); AddSwitchCase (w, b); continue; } // ============================================================ // Break statement. if (token == "break") { if (!g_ScopeCursor) ParserError ("unexpected `break`"); w->Write (DH_GOTO); // switch and if use mark1 for the closing point, // for and while use mark2. switch (SCOPE(0).type) { case SCOPETYPE_IF: case SCOPETYPE_SWITCH: w->AddReference (SCOPE(0).mark1); break; case SCOPETYPE_FOR: case SCOPETYPE_WHILE: w->AddReference (SCOPE(0).mark2); break; default: ParserError ("unexpected `break`"); break; } MustNext (";"); continue; } // ============================================================ // Continue if (token == "continue") { MustNext (";"); int curs; bool found = false; // Drop through the scope until we find a loop block for (curs = g_ScopeCursor; curs > 0 && !found; curs--) { switch (scopestack[curs].type) { case SCOPETYPE_FOR: case SCOPETYPE_WHILE: case SCOPETYPE_DO: w->Write (DH_GOTO); w->AddReference (scopestack[curs].mark1); found = true; break; default: break; } } // No loop blocks if (!found) ParserError ("`continue`-statement not inside a loop"); continue; } // ============================================================ // Label if (PeekNext() == ":") { MUST_NOT_TOPLEVEL // want no conflicts.. if (IsKeyword (token)) ParserError ("label name `%s` conflicts with keyword\n", token.chars()); if (FindCommand (token)) ParserError ("label name `%s` conflicts with command name\n", token.chars()); if (FindGlobalVariable (token)) ParserError ("label name `%s` conflicts with variable\n", token.chars()); // See if a mark already exists for this label int mark = -1; for (int i = 0; i < MAX_MARKS; i++) { if (g_UndefinedLabels[i] && *g_UndefinedLabels[i] == token) { mark = i; w->MoveMark (i); // No longer undefinde delete g_UndefinedLabels[i]; g_UndefinedLabels[i] = null; } } // Not found in unmarked lists, define it now if (mark == -1) w->AddMark (token); MustNext (":"); continue; } // ============================================================ 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", token.c_str()); 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_UNKNOWN: case TYPE_VOID: break; } g_ConstInfo << info; MustNext (";"); continue; } // ============================================================ if (token == "}") { // Closing brace // If we're in the block stack, we're descending down from it now if (g_ScopeCursor > 0) { switch (SCOPE(0).type) { case SCOPETYPE_IF: // Adjust the closing mark. w->MoveMark (SCOPE(0).mark1); // We're returning from if, thus else can be next g_CanElse = true; break; case SCOPETYPE_ELSE: // else instead uses mark1 for itself (so if expression // fails, jump to else), mark2 means end of else w->MoveMark (SCOPE(0).mark2); break; case SCOPETYPE_FOR: // Write the incrementor at the end of the loop block w->WriteBuffer (SCOPE(0).buffer1); // fall-thru case SCOPETYPE_WHILE: // Write down the instruction to go back to the start of the loop w->Write (DH_GOTO); w->AddReference (SCOPE(0).mark1); // Move the closing mark here since we're at the end of the while loop w->MoveMark (SCOPE(0).mark2); break; case SCOPETYPE_DO: { MustNext ("while"); MustNext ("("); MustNext (); DataBuffer* expr = ParseExpression (TYPE_INT); MustNext (")"); MustNext (";"); // If the condition runs true, go back to the start. w->WriteBuffer (expr); w->Write (DH_IFGOTO); w->AddReference (SCOPE(0).mark1); break; } case SCOPETYPE_SWITCH: { // Switch closes. Move down to the record buffer of // the lower block. if (SCOPE(1).casecursor != -1) w->SwitchBuffer = SCOPE(1).casebuffers[SCOPE(1).casecursor]; else w->SwitchBuffer = null; // If there was a default in the switch, write its header down now. // If not, write instruction to jump to the end of switch after // the headers (thus won't fall-through if no case matched) if (SCOPE(0).buffer1) w->WriteBuffer (SCOPE(0).buffer1); else { w->Write (DH_DROP); w->Write (DH_GOTO); w->AddReference (SCOPE(0).mark1); } // Go through all of the buffers we // recorded down and write them. for (unsigned int u = 0; u < MAX_CASE; u++) { if (!SCOPE(0).casebuffers[u]) continue; w->MoveMark (SCOPE(0).casemarks[u]); w->WriteBuffer (SCOPE(0).casebuffers[u]); } // Move the closing mark here w->MoveMark (SCOPE(0).mark1); break; } case SCOPETYPE_UNKNOWN: break; } // Descend down the stack g_ScopeCursor--; continue; } int dataheader = (g_CurMode == MODE_EVENT) ? DH_ENDEVENT : (g_CurMode == MODE_MAINLOOP) ? DH_ENDMAINLOOP : (g_CurMode == MODE_ONENTER) ? DH_ENDONENTER : (g_CurMode == MODE_ONEXIT) ? DH_ENDONEXIT : -1; if (dataheader == -1) ParserError ("unexpected `}`"); // Data header must be written before mode is changed because // 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; if (PeekNext() == ";") MustNext (";"); continue; } // Check if it's a command CommandDef* comm = FindCommand (token); if (comm) { w->GetCurrentBuffer()->Merge (ParseCommand (comm)); MustNext (";"); continue; } // ============================================================ // If nothing else, parse it as a statement DataBuffer* b = ParseStatement (w); if (!b) ParserError ("unknown token `%s`", token.chars()); w->WriteBuffer (b); MustNext (";"); } // =============================================================================== // Script file ended. Do some last checks and write the last things to main buffer if (g_CurMode != MODE_TOPLEVEL) ParserError ("script did not end at top level; did you forget a `}`?"); // stateSpawn must be defined! if (!g_stateSpawnDefined) ParserError ("script must have a state named `stateSpawn`!"); for (int i = 0; i < MAX_MARKS; i++) if (g_UndefinedLabels[i]) ParserError ("label `%s` is referenced via `goto` but isn't defined\n", g_UndefinedLabels[i]->chars()); // Dump the last state's onenter and mainloop w->WriteBuffers (); // String table w->WriteStringTable (); } // ============================================================================ // Parses a command call DataBuffer* ScriptReader::ParseCommand (CommandDef* comm) { DataBuffer* r = new DataBuffer(64); if (g_CurMode == MODE_TOPLEVEL) ParserError ("command call at top level"); MustNext ("("); MustNext (); int curarg = 0; while (1) { if (token == ")") { if (curarg < comm->numargs) ParserError ("too few arguments passed to %s\n\tprototype: %s", comm->name.chars(), GetCommandPrototype (comm).chars()); break; curarg++; } if (curarg >= comm->maxargs) ParserError ("too many arguments passed to %s\n\tprototype: %s", comm->name.chars(), GetCommandPrototype (comm).chars()); r->Merge (ParseExpression (comm->argtypes[curarg])); MustNext (); if (curarg < comm->numargs - 1) { MustThis (","); MustNext (); } else if (curarg < comm->maxargs - 1) { // Can continue, but can terminate as well. if (token == ")") { curarg++; break; } else { MustThis (","); MustNext (); } } curarg++; } // If the script skipped any optional arguments, fill in defaults. while (curarg < comm->maxargs) { r->Write (DH_PUSHNUMBER); r->Write (comm->defvals[curarg]); curarg++; } r->Write (DH_COMMAND); r->Write (comm->number); r->Write (comm->maxargs); return r; } // ============================================================================ // Is the given operator an assignment operator? static bool IsAssignmentOperator (int oper) { switch (oper) { case OPER_ASSIGNADD: case OPER_ASSIGNSUB: case OPER_ASSIGNMUL: case OPER_ASSIGNDIV: case OPER_ASSIGNMOD: case OPER_ASSIGNLEFTSHIFT: case OPER_ASSIGNRIGHTSHIFT: case OPER_ASSIGN: return true; } return false; } // ============================================================================ // Finds an operator's corresponding dataheader static word DataHeaderByOperator (ScriptVar* var, int oper) { if (IsAssignmentOperator (oper)) { if (!var) error ("operator %d requires left operand to be a variable\n", oper); // TODO: At the moment, vars only are global // OPER_ASSIGNLEFTSHIFT and OPER_ASSIGNRIGHTSHIFT do not // have data headers, instead they are expanded out in // the operator parser switch (oper) { case OPER_ASSIGNADD: return DH_ADDGLOBALVAR; case OPER_ASSIGNSUB: return DH_SUBGLOBALVAR; case OPER_ASSIGNMUL: return DH_MULGLOBALVAR; case OPER_ASSIGNDIV: return DH_DIVGLOBALVAR; case OPER_ASSIGNMOD: return DH_MODGLOBALVAR; case OPER_ASSIGN: return DH_ASSIGNGLOBALVAR; default: error ("bad assignment operator!!\n"); } } switch (oper) { case OPER_ADD: return DH_ADD; case OPER_SUBTRACT: return DH_SUBTRACT; case OPER_MULTIPLY: return DH_MULTIPLY; case OPER_DIVIDE: return DH_DIVIDE; case OPER_MODULUS: return DH_MODULUS; case OPER_EQUALS: return DH_EQUALS; case OPER_NOTEQUALS: return DH_NOTEQUALS; case OPER_LESSTHAN: return DH_LESSTHAN; case OPER_GREATERTHAN: return DH_GREATERTHAN; case OPER_LESSTHANEQUALS: return DH_LESSTHANEQUALS; case OPER_GREATERTHANEQUALS: return DH_GREATERTHANEQUALS; case OPER_LEFTSHIFT: return DH_LSHIFT; case OPER_RIGHTSHIFT: return DH_RSHIFT; case OPER_OR: return DH_ORLOGICAL; case OPER_AND: return DH_ANDLOGICAL; case OPER_BITWISEOR: return DH_ORBITWISE; case OPER_BITWISEEOR: return DH_EORBITWISE; case OPER_BITWISEAND: return DH_ANDBITWISE; } error ("DataHeaderByOperator: couldn't find dataheader for operator %d!\n", oper); return 0; } // ============================================================================ // Parses an expression, potentially recursively DataBuffer* ScriptReader::ParseExpression (type_e reqtype) { DataBuffer* retbuf = new DataBuffer (64); // Parse first operand retbuf->Merge (ParseExprValue (reqtype)); // Parse any and all operators we get int oper; while ((oper = ParseOperator (true)) != -1) { // We peeked the operator, move forward now Next (); // Can't be an assignement operator, those belong in assignments. if (IsAssignmentOperator (oper)) ParserError ("assignment operator inside expression"); // Parse the right operand. MustNext (); DataBuffer* rb = ParseExprValue (reqtype); if (oper == OPER_TERNARY) { // Ternary operator requires - naturally - a third operand. MustNext (":"); MustNext (); DataBuffer* tb = ParseExprValue (reqtype); // It also is handled differently: there isn't a dataheader for ternary // operator. Instead, we abuse PUSHNUMBER and IFNOTGOTO for this. // Behold, big block of writing madness! :P int mark1 = retbuf->AddMark (""); // start of "else" case int mark2 = retbuf->AddMark (""); // end of expression retbuf->Write (DH_IFNOTGOTO); // if the first operand (condition) retbuf->AddMarkReference (mark1); // didn't eval true, jump into mark1 retbuf->Merge (rb); // otherwise, perform second operand (true case) retbuf->Write (DH_GOTO); // afterwards, jump to the end, which is retbuf->AddMarkReference (mark2); // marked by mark2. retbuf->MoveMark (mark1); // move mark1 at the end of the true case retbuf->Merge (tb); // perform third operand (false case) retbuf->MoveMark (mark2); // move the ending mark2 here } else { // Write to buffer retbuf->Merge (rb); retbuf->Write (DataHeaderByOperator (null, oper)); } } return retbuf; } // ============================================================================ // Parses an operator string. Returns the operator number code. #define ISNEXT(C) (PeekNext (peek ? 1 : 0) == C) int ScriptReader::ParseOperator (bool peek) { string oper; if (peek) oper += PeekNext (); else oper += token; if (-oper == "strlen") return OPER_STRLEN; // Check one-char operators bool equalsnext = ISNEXT ("="); int o = (oper == "=" && !equalsnext) ? OPER_ASSIGN : (oper == ">" && !equalsnext && !ISNEXT (">")) ? OPER_GREATERTHAN : (oper == "<" && !equalsnext && !ISNEXT ("<")) ? OPER_LESSTHAN : (oper == "&" && !ISNEXT ("&")) ? OPER_BITWISEAND : (oper == "|" && !ISNEXT ("|")) ? OPER_BITWISEOR : (oper == "+" && !equalsnext) ? OPER_ADD : (oper == "-" && !equalsnext) ? OPER_SUBTRACT : (oper == "*" && !equalsnext) ? OPER_MULTIPLY : (oper == "/" && !equalsnext) ? OPER_DIVIDE : (oper == "%" && !equalsnext) ? OPER_MODULUS : (oper == "^") ? OPER_BITWISEEOR : (oper == "?") ? OPER_TERNARY : -1; if (o != -1) { return o; } // Two-char operators oper += PeekNext (peek ? 1 : 0); equalsnext = PeekNext (peek ? 2 : 1) == ("="); o = (oper == "+=") ? OPER_ASSIGNADD : (oper == "-=") ? OPER_ASSIGNSUB : (oper == "*=") ? OPER_ASSIGNMUL : (oper == "/=") ? OPER_ASSIGNDIV : (oper == "%=") ? OPER_ASSIGNMOD : (oper == "==") ? OPER_EQUALS : (oper == "!=") ? OPER_NOTEQUALS : (oper == ">=") ? OPER_GREATERTHANEQUALS : (oper == "<=") ? OPER_LESSTHANEQUALS : (oper == "&&") ? OPER_AND : (oper == "||") ? OPER_OR : (oper == "<<" && !equalsnext) ? OPER_LEFTSHIFT : (oper == ">>" && !equalsnext) ? OPER_RIGHTSHIFT : -1; if (o != -1) { MustNext (); return o; } // Three-char opers oper += PeekNext (peek ? 2 : 1); o = oper == "<<=" ? OPER_ASSIGNLEFTSHIFT : oper == ">>=" ? OPER_ASSIGNRIGHTSHIFT : -1; if (o != -1) { MustNext (); MustNext (); } return o; } // ============================================================================ string ScriptReader::ParseFloat () { MustNumber (true); string 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 (type_e reqtype) { DataBuffer* b = new DataBuffer(16); ScriptVar* g; // Prefixing "!" means negation. bool negate = (token == "!"); if (negate) // Jump past the "!" Next (); // 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", GetTypeName (reqtype).c_str()); b->Write (DH_PUSHNUMBER); b->Write (constant->val.len ()); MustNext (")"); } else if (token == "(") { // Expression MustNext (); DataBuffer* c = ParseExpression (reqtype); b->Merge (c); MustNext (")"); } else if (CommandDef* comm = FindCommand (token)) { delete b; // Command 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", constant->name.c_str(), GetTypeName (constant->type).c_str(), GetTypeName (reqtype).c_str()); switch (constant->type) { case TYPE_BOOL: case TYPE_INT: b->Write (DH_PUSHNUMBER); b->Write (atoi (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 (DH_PUSHGLOBALVAR); b->Write (g->index); } else { // If nothing else, check for literal switch (reqtype) { case TYPE_VOID: case TYPE_UNKNOWN: ParserError ("unknown identifier `%s` (expected keyword, function or variable)", token.chars()); break; case TYPE_BOOL: case TYPE_INT: { MustNumber (true); // All values are written unsigned - thus we need to write the value's // absolute value, followed by an unary minus for negatives. b->Write (DH_PUSHNUMBER); long v = atol (token); b->Write (static_cast<word> (abs (v))); if (v < 0) b->Write (DH_UNARYMINUS); break; } case TYPE_STRING: // PushToStringTable either returns the string index of the // 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->WriteString (token); break; } } // Negate it now if desired if (negate) b->Write (DH_NEGATELOGICAL); 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) { bool global = !var->statename.len (); // Get an operator MustNext (); int oper = ParseOperator (); if (!IsAssignmentOperator (oper)) ParserError ("expected assignment operator"); if (g_CurMode == MODE_TOPLEVEL) // TODO: lift this restriction ParserError ("can't alter variables at top level"); // Parse the right operand MustNext (); DataBuffer* retbuf = new DataBuffer; DataBuffer* expr = ParseExpression (var->type); // <<= and >>= do not have data headers. Solution: expand them. // a <<= b -> a = a << b // a >>= b -> a = a >> b if (oper == OPER_ASSIGNLEFTSHIFT || oper == OPER_ASSIGNRIGHTSHIFT) { retbuf->Write (global ? DH_PUSHGLOBALVAR : DH_PUSHLOCALVAR); retbuf->Write (var->index); retbuf->Merge (expr); retbuf->Write ((oper == OPER_ASSIGNLEFTSHIFT) ? DH_LSHIFT : DH_RSHIFT); retbuf->Write (global ? DH_ASSIGNGLOBALVAR : DH_ASSIGNLOCALVAR); retbuf->Write (var->index); } else { retbuf->Merge (expr); long dh = DataHeaderByOperator (var, oper); retbuf->Write (dh); retbuf->Write (var->index); } return retbuf; } void ScriptReader::PushScope () { g_ScopeCursor++; if (g_ScopeCursor >= MAX_SCOPE) ParserError ("too deep scope"); ScopeInfo* info = &SCOPE(0); info->type = SCOPETYPE_UNKNOWN; info->mark1 = 0; info->mark2 = 0; info->buffer1 = null; info->casecursor = -1; for (int i = 0; i < MAX_CASE; i++) { info->casemarks[i] = MAX_MARKS; info->casebuffers[i] = null; info->casenumbers[i] = -1; } } 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)) return ParseAssignment (var); return null; } void ScriptReader::AddSwitchCase (ObjWriter* w, DataBuffer* b) { ScopeInfo* info = &SCOPE(0); info->casecursor++; if (info->casecursor >= MAX_CASE) ParserError ("too many cases in one switch"); // Init a mark for the case buffer int m = w->AddMark (""); info->casemarks[info->casecursor] = m; // Add a reference to the mark. "case" and "default" both // add the necessary bytecode before the reference. if (b) b->AddMarkReference (m); else w->AddReference (m); // 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 (string token) { for (uint i = 0; i < g_ConstInfo.size(); i++) if (g_ConstInfo[i].name == token) return &g_ConstInfo[i]; return null; }