--- a/parser.cxx Wed Jan 02 23:57:46 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1203 +0,0 @@ -/* - * botc source code - * Copyright (C) 2012 Santeri `Dusk` Piippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of the developer nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * 4. Redistributions in any form must be accompanied by information on how to - * obtain complete source code for the software and any accompanying - * software that uses the software. The source code must either be included - * in the distribution or be available for no more than the cost of - * distribution plus a nominal fee, and must be freely redistributable - * under reasonable conditions. For an executable file, complete source - * code means the source code for all modules it contains. It does not - * include source code for modules or files that typically accompany the - * major components of the operating system on which the executable file - * runs. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#define __PARSER_CXX__ - -#include <stdio.h> -#include <stdlib.h> -#include "common.h" -#include "str.h" -#include "objwriter.h" -#include "scriptreader.h" -#include "events.h" -#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()); - -#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; -str g_CurState = ""; -bool g_stateSpawnDefined = false; -bool g_GotMainLoop = false; -unsigned int g_ScopeCursor = 0; -DataBuffer* g_IfExpression = NULL; -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 -// 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()); - str 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.isnumber()) - ParserError ("variable name must not be a number"); - - str 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 str (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", (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_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(char) (!PeekNext (peek ? 1 : 0) == char) -int ScriptReader::ParseOperator (bool peek) { - str 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; -} - -// ============================================================================ -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 (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", (char*)GetTypeName (reqtype)); - - 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", - (char*)constant->name, (char*)GetTypeName (constant->type), - (char*)GetTypeName (reqtype)); - - 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 (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