--- a/src/parser.cxx Fri Jan 10 21:58:42 2014 +0200 +++ b/src/parser.cxx Sat Jan 11 22:36:31 2014 +0200 @@ -1,792 +1,943 @@ -#include "objwriter.h" -#include "scriptreader.h" +/* + Copyright (c) 2013-2014, Santeri 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: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * 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. + + * Neither the name of the <organization> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + 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 <COPYRIGHT HOLDER> 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. +*/ + +#include "object_writer.h" +#include "parser.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()); +#include "lexer.h" -#define MUST_NOT_TOPLEVEL if (g_CurMode == MODE_TOPLEVEL) \ - ParserError ("%s-statements may not be defined at top level!", token.chars()); - +#define TOKEN (string (m_lx->get_token()->string)) #define SCOPE(n) scopestack[g_ScopeCursor - n] +// TODO: make these static 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; +int g_ScopeCursor = 0; +data_buffer* g_IfExpression = null; bool g_CanElse = false; string* g_UndefinedLabels[MAX_MARKS]; -bool g_Neurosphere = false; // neurosphere-compat -list<constinfo_t> g_ConstInfo; +list<constant_info> g_ConstInfo; + +static botscript_parser* g_current_parser = null; + +// ============================================================================ +// +botscript_parser::botscript_parser() : + m_lx (new lexer) {} + +// ============================================================================ +// +botscript_parser::~botscript_parser() +{ + delete m_lx; +} + +// ============================================================================ +// +void botscript_parser::check_toplevel() +{ + if (g_CurMode != MODE_TOPLEVEL) + error ("%1-statements may only be defined at top level!", TOKEN.chars()); +} + +// ============================================================================ +// +void botscript_parser::check_not_toplevel() +{ + if (g_CurMode == MODE_TOPLEVEL) + error ("%1-statements must not be defined at top level!", TOKEN.chars()); +} // ============================================================================ // 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 +// 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) { +void botscript_parser::parse_botscript (string file_name, object_writer* w) +{ + // Lex and preprocess the file + m_lx->process_file (file_name); + // Zero the entire block stack first for (int i = 0; i < MAX_SCOPE; i++) - ZERO(scopestack[i]); - + ZERO (scopestack[i]); + for (int i = 0; i < MAX_MARKS; i++) g_UndefinedLabels[i] = null; - - while (Next()) { + + while (m_lx->get_next()) + { // Check if else is potentically valid - if (token == "else" && !g_CanElse) - ParserError ("else without preceding if"); - if (token != "else") + if (TOKEN == "else" && !g_CanElse) + error ("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); + + switch (m_lx->get_token()) + { + case tk_state: + { + check_toplevel(); + m_lx->must_get_next (tk_string); + + // State name must be a word. + if (TOKEN.first (" ") != -1) + error ("state name must be a single word, got `%1`", TOKEN); + + 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 + m_lx->must_get_next (tk_colon); + + // write the previous state's onenter and + // mainloop buffers to file now + if (g_CurState.len()) + w->write (Buffers(); + + w->write (DH_STATENAME); + w->write_string (statename); + w->write (DH_STATEIDX); + w->write (g_NumStates); + + g_NumStates++; + g_CurState = TOKEN; + g_GotMainLoop = false; } - - // 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 + break; + + // ============================================================ + // + case tk_event: + { + check_toplevel(); + + // Event definition + m_lx->must_get_next (tk_string); + + event_info* e = find_event_by_name (token_string()); + + if (!e) + error ("bad event, got `%1`\n", token_string()); + + m_lx->must_get_next (tk_brace_start); + g_CurMode = MODE_EVENT; + w->write (DH_EVENT); + w->write (e->number); + g_NumEvents++; + continue; + } break; + + // ============================================================ + // + case tk_mainloop: + { + check_toplevel(); + m_lx->must_get_next (tk_brace_start); + + // Mode must be set before dataheader is written here! + g_CurMode = MODE_MAINLOOP; + w->write (DH_MAINLOOP); + } + break; + + // ============================================================ // - // 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; - } + case tk_onenter: + case tk_onexit: + { + check_toplevel(); + bool onenter = (m_lx->get_token() == "onenter"); + m_lx->must_get_next (tk_brace_start); + + // 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); } - - // 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; + break; + + // ============================================================ + // + case tk_int: + case tk_str: + case tk_void: + { + // For now, only globals are supported + if (g_CurMode != MODE_TOPLEVEL || g_CurState.len()) + error ("variables must only be global for now"); + + type_e type = (TOKEN == "int") ? TYPE_INT : + (TOKEN == "str") ? TYPE_STRING : + TYPE_BOOL; + + m_lx->must_get_next(); + + // Var name must not be a number + if (TOKEN.is_numeric()) + error ("variable name must not be a number"); + + string varname = TOKEN; + script_variable* var = DeclareGlobalVariable (this, type, varname); + m_lx->must_get_next (tk_semicolon); } - - 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; + break; + + // ============================================================ + // + case tk_goto: + { + check_not_toplevel(); + + // Get the name of the label + m_lx->must_get_next(); + + // Find the mark this goto statement points to + int m = w->find_byte_mark (TOKEN); + + // If not set, define it + if (m == MAX_MARKS) + { + m = w->add_mark (TOKEN); + g_UndefinedLabels[m] = new string (TOKEN); } - 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--; + + // Add a reference to the mark. + w->write (DH_GOTO); + w->add_reference (m); + m_lx->must_get_next (tk_semicolon); 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; + + // ============================================================ + // + case tk_if: + { + check_not_toplevel(); + push_scope(); + + // Condition + m_lx->must_get_next (tk_paren_start); + + // Read the expression and write it. + m_lx->must_get_next(); + data_buffer* c = parse_expression (TYPE_INT); + w->write_buffer (c); + + m_lx->must_get_next (tk_paren_end); + m_lx->must_get_next (tk_brace_start); + + // Add a mark - to here temporarily - and add a reference to it. + // Upon a closing brace, the mark will be adjusted. + int marknum = w->add_mark (""); + + // 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->add_reference (marknum); + + // Store it + SCOPE (0).mark1 = marknum; + SCOPE (0).type = SCOPETYPE_IF; + } break; + + // ============================================================ + // + case tk_else: + { + check_not_toplevel(); + m_lx->must_get_next (tk_brace_start); + + // Don't use PushScope as it resets the scope + g_ScopeCursor++; + + if (g_ScopeCursor >= MAX_SCOPE) + error ("too deep scope"); + + if (SCOPE (0).type != SCOPETYPE_IF) + error ("else without preceding if"); + + // write down to jump to the end of the else statement + // Otherwise we have fall-throughs + SCOPE (0).mark2 = w->add_mark (""); + + // Instruction to jump to the end after if block is complete + w->write (DH_GOTO); + w->add_reference (SCOPE (0).mark2); + + // Move the ifnot mark here and set type to else + w->move_mark (SCOPE (0).mark1); + SCOPE (0).type = SCOPETYPE_ELSE; + } + break; + + // ============================================================ + // + case tk_while: + { + check_not_toplevel(); + push_scope(); + + // 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. + int mark1 = w->add_mark (""); // start + int mark2 = w->add_mark (""); // end + + // Condition + m_lx->must_get_next (tk_paren_start); + m_lx->must_get_next(); + data_buffer* expr = parse_expression (TYPE_INT); + m_lx->must_get_next (tk_paren_end); + m_lx->must_get_next (tk_brace_start); + + // write condition + w->write_buffer (expr); + + // Instruction to go to the end if it fails + w->write (DH_IFNOTGOTO); + w->add_reference (mark2); + + // Store the needed stuff + SCOPE (0).mark1 = mark1; + SCOPE (0).mark2 = mark2; + SCOPE (0).type = SCOPETYPE_WHILE; + } + break; + + // ============================================================ + // + case tk_for: + { + check_not_toplevel(); + push_scope(); + + // Initializer + m_lx->must_get_next (tk_paren_start); + m_lx->must_get_next(); + data_buffer* init = parse_statement (w); + + if (!init) + error ("bad statement for initializer of for"); + + m_lx->must_get_next (tk_semicolon); + + // Condition + m_lx->must_get_next(); + data_buffer* cond = parse_expression (TYPE_INT); + + if (!cond) + error ("bad statement for condition of for"); + + m_lx->must_get_next (tk_semicolon); + + // Incrementor + m_lx->must_get_next(); + data_buffer* incr = parse_statement (w); + + if (!incr) + error ("bad statement for incrementor of for"); + + m_lx->must_get_next (tk_paren_end); + m_lx->must_get_next (tk_brace_start); + + // First, write out the initializer + w->write_buffer (init); + + // Init two marks + int mark1 = w->add_mark (""); + int mark2 = w->add_mark (""); + + // Add the condition + w->write_buffer (cond); + w->write (DH_IFNOTGOTO); + w->add_reference (mark2); + + // Store the marks and incrementor + SCOPE (0).mark1 = mark1; + SCOPE (0).mark2 = mark2; + SCOPE (0).buffer1 = incr; + SCOPE (0).type = SCOPETYPE_FOR; + } + break; + + // ============================================================ + // + case tk_do: + { + check_not_toplevel(); + push_scope(); + m_lx->must_get_next (tk_brace_start); + SCOPE (0).mark1 = w->add_mark (""); + SCOPE (0).type = SCOPETYPE_DO; + } + break; + + // ============================================================ + // + case tk_switch: + { + // This gets 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 + + check_not_toplevel(); + push_scope(); + m_lx->must_get_next (tk_paren_start); + m_lx->must_get_next(); + w->write_buffer (parse_expression (TYPE_INT)); + m_lx->must_get_next (tk_paren_end); + m_lx->must_get_next (tk_brace_start); + SCOPE (0).type = SCOPETYPE_SWITCH; + SCOPE (0).mark1 = w->add_mark (""); // end mark + SCOPE (0).buffer1 = null; // default header + } + break; + + // ============================================================ + // + case tk_case: + { + // case is only allowed inside switch + if (SCOPE (0).type != SCOPETYPE_SWITCH) + error ("case label outside switch"); + + // Get the literal (Zandronum does not support expressions here) + m_lx->must_get_next (tk_number); + int num = m_lx->get_token()->text.to_long(); + m_lx->must_get_next (tk_colon); + + for (int i = 0; i < MAX_CASE; i++) + if (SCOPE (0).casenumbers[i] == num) + error ("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); + add_switch_case (w, null); + SCOPE (0).casenumbers[SCOPE (0).casecursor] = num; + } + break; + + // ============================================================ + // + case tk_default: + { + if (SCOPE (0).type != SCOPETYPE_SWITCH) + error ("default label outside switch"); + + if (SCOPE (0).buffer1) + error ("multiple default labels in one switch"); + + m_lx->must_get_next (tk_colon); + + // 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. + data_buffer* b = new data_buffer; + SCOPE (0).buffer1 = b; + b->write (DH_DROP); + b->write (DH_GOTO); + add_switch_case (w, b); + } + break; + + // ============================================================ + // + case tk_break: + { + if (!g_ScopeCursor) + error ("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->add_reference (SCOPE (0).mark1); + } break; + + case SCOPETYPE_FOR: + case SCOPETYPE_WHILE: + { + w->add_reference (SCOPE (0).mark2); + } break; + + default: + { + error ("unexpected `break`"); + } break; + } + + m_lx->must_get_next (tk_semicolon); + } + break; + + // ============================================================ + // + case tk_continue: + { + m_lx->must_get_next (tk_semicolon); + + int curs; + bool found = false; + + // Fall 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->add_reference (scopestack[curs].mark1); + found = true; + } break; + + default: + break; + } + } + + // No loop blocks + if (!found) + error ("`continue`-statement not inside a loop"); + } + break; + + case tk_brace_end: + { + // 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->move_mark (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->move_mark (SCOPE (0).mark2); + break; + + case SCOPETYPE_FOR: + // write the incrementor at the end of the loop block + w->write_buffer (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->add_reference (SCOPE (0).mark1); + + // Move the closing mark here since we're at the end of the while loop + w->move_mark (SCOPE (0).mark2); + break; + + case SCOPETYPE_DO: + { + must_get_next (tk_while); + m_lx->must_get_next (tk_paren_start); + m_lx->must_get_next(); + data_buffer* expr = parse_expression (TYPE_INT); + m_lx->must_get_next (tk_paren_end); + m_lx->must_get_next (tk_semicolon); + + // If the condition runs true, go back to the start. + w->write_buffer (expr); + w->write (DH_IFGOTO); + w->add_reference (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->write_buffer (SCOPE (0).buffer1); + else + { + w->write (DH_DROP); + w->write (DH_GOTO); + w->add_reference (SCOPE (0).mark1); + } + + // Go through all of the buffers we + // recorded down and write them. + for (int u = 0; u < MAX_CASE; u++) + { + if (!SCOPE (0).casebuffers[u]) + continue; + + w->move_mark (SCOPE (0).casemarks[u]); + w->write_buffer (SCOPE (0).casebuffers[u]); + } + + // Move the closing mark here + w->move_mark (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) + error ("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() == ";") + m_lx->must_get_next (tk_semicolon); + } + break; + + // ============================================================ + case tk_const: + { + constant_info info; + + // Get the type + m_lx->must_get_next(); + info.type = GetTypeByName (TOKEN); + + if (info.type == TYPE_UNKNOWN || info.type == TYPE_VOID) + error ("unknown type `%s` for constant", TOKEN.c_str()); + + m_lx->must_get_next(); + info.name = TOKEN; + + m_lx->must_get_next (tk_assign); + + switch (info.type) + { + case TYPE_BOOL: + case TYPE_INT: + { + m_lx->must_get_next (tk_number); + info.val = m_lx->get_token()->text.to_long(); + } break; + + case TYPE_STRING: + { + m_lx->must_get_next (tk_string); + info.val = m_lx->get_token()->text; + } break; + + case TYPE_UNKNOWN: + case TYPE_VOID: + break; + } + + g_ConstInfo << info; + + m_lx->must_get_next (tk_semicolon); + continue; + } + + default: + { + // ============================================================ + // Label + lexer::token* next; + if (m_lx->get_token() == tk_symbol && + m_lx->peek_next (next) && + next->type == tk_colon) + { + check_not_toplevel(); + string label_name = token_string(); + + // want no conflicts.. + if (FindCommand (label_name)) + error ("label name `%s` conflicts with command name\n", label_name); + + if (FindGlobalVariable (label_name)) + error ("label name `%s` conflicts with variable\n", label_name); + + // 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] == label_name) + { + mark = i; + w->move_mark (i); + + // No longer undefinde + delete g_UndefinedLabels[i]; + g_UndefinedLabels[i] = null; + } + } + + // Not found in unmarked lists, define it now + if (mark == -1) + w->add_mark (label_name); + + m_lx->must_get_next (tk_colon); + continue; + } + + // Check if it's a command + CommandDef* comm = FindCommand (TOKEN); + + if (comm) + { + w->get_current_buffer()->merge (ParseCommand (comm)); + m_lx->must_get_next (tk_semicolon); + continue; + } + + // ============================================================ + // If nothing else, parse it as a statement + data_buffer* b = parse_statement (w); + + if (!b) + error ("unknown TOKEN `%s`", TOKEN.chars()); + + w->write_buffer (b); + m_lx->must_get_next (tk_semicolon); + } + break; } - - // 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 `}`?"); - + error ("script did not end at top level; a `}` is missing somewhere"); + // stateSpawn must be defined! if (!g_stateSpawnDefined) - ParserError ("script must have a state named `stateSpawn`!"); - + error ("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()); - + error ("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 (); - + w->write_member_buffers(); + // String table - w->WriteStringTable (); + w->write_string_table(); } // ============================================================================ // Parses a command call -DataBuffer* ScriptReader::ParseCommand (CommandDef* comm) { - DataBuffer* r = new DataBuffer(64); +data_buffer* botscript_parser::ParseCommand (CommandDef* comm) +{ + data_buffer* r = new data_buffer (64); + if (g_CurMode == MODE_TOPLEVEL) - ParserError ("command call at top level"); - - MustNext ("("); - MustNext (); - + error ("command call at top level"); + + m_lx->must_get_next (tk_paren_start); + m_lx->must_get_next(); + int curarg = 0; - while (1) { - if (token == ")") { + + while (1) + { + if (m_lx->get_token() == tk_paren_end) + { if (curarg < comm->numargs) - ParserError ("too few arguments passed to %s\n\tprototype: %s", + error ("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", + error ("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) { + + r->merge (parse_expression (comm->argtypes[curarg])); + m_lx->must_get_next(); + + if (curarg < comm->numargs - 1) + { + m_lx->must_be (tk_comma); + m_lx->must_get_next(); + } + else if (curarg < comm->maxargs - 1) + { // Can continue, but can terminate as well. - if (token == ")") { + if (m_lx->get_token() == tk_paren_end) + { curarg++; break; - } else { - MustThis (","); - MustNext (); + } + else + { + m_lx->must_be (tk_comma); + m_lx->must_get_next(); } } - + curarg++; } - + // If the script skipped any optional arguments, fill in defaults. - while (curarg < comm->maxargs) { - r->Write (DH_PUSHNUMBER); - r->Write (comm->defvals[curarg]); + 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); - + + 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; +static bool is_assignment_operator (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)) { +static word get_data_header_by_operator (script_variable* var, int oper) +{ + if (is_assignment_operator (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_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) { + + switch (oper) + { case OPER_ADD: return DH_ADD; case OPER_SUBTRACT: return DH_SUBTRACT; case OPER_MULTIPLY: return DH_MULTIPLY; @@ -806,100 +957,109 @@ 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); - +data_buffer* botscript_parser::parse_expression (type_e reqtype) +{ + data_buffer* retbuf = new data_buffer (64); + // Parse first operand - retbuf->Merge (ParseExprValue (reqtype)); - + retbuf->merge (parse_expr_value (reqtype)); + // Parse any and all operators we get int oper; - while ((oper = ParseOperator (true)) != -1) { + + while ( (oper = parse_operator (true)) != -1) + { // We peeked the operator, move forward now - Next (); - + Next(); + // Can't be an assignement operator, those belong in assignments. - if (IsAssignmentOperator (oper)) - ParserError ("assignment operator inside expression"); - + if (is_assignment_operator (oper)) + error ("assignment operator inside expression"); + // Parse the right operand. - MustNext (); - DataBuffer* rb = ParseExprValue (reqtype); - - if (oper == OPER_TERNARY) { + m_lx->must_get_next(); + data_buffer* rb = parse_expr_value (reqtype); + + if (oper == OPER_TERNARY) + { // Ternary operator requires - naturally - a third operand. - MustNext (":"); - MustNext (); - DataBuffer* tb = ParseExprValue (reqtype); - + m_lx->must_get_next (tk_colon); + m_lx->must_get_next(); + data_buffer* tb = parse_expr_value (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)); + int mark1 = retbuf->add_mark (""); // start of "else" case + int mark2 = retbuf->add_mark (""); // end of expression + retbuf->write (DH_IFNOTGOTO); // if the first operand (condition) + retbuf->add_reference (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->add_reference (mark2); // marked by mark2. + retbuf->move_mark (mark1); // move mark1 at the end of the true case + retbuf->merge (tb); // perform third operand (false case) + retbuf->move_mark (mark2); // move the ending mark2 here + } + else + { + // write to buffer + retbuf->merge (rb); + retbuf->write (get_data_header_by_operator (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) { +int botscript_parser::parse_operator (bool peek) +{ string oper; + if (peek) - oper += PeekNext (); + oper += PeekNext(); else - oper += token; - + 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) { + (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 : @@ -914,39 +1074,44 @@ (oper == "<<" && !equalsnext) ? OPER_LEFTSHIFT : (oper == ">>" && !equalsnext) ? OPER_RIGHTSHIFT : -1; - - if (o != -1) { - MustNext (); + + if (o != -1) + { + m_lx->must_get_next(); return o; } - + // Three-char opers oper += PeekNext (peek ? 2 : 1); o = oper == "<<=" ? OPER_ASSIGNLEFTSHIFT : oper == ">>=" ? OPER_ASSIGNRIGHTSHIFT : -1; - - if (o != -1) { - MustNext (); - MustNext (); + + if (o != -1) + { + m_lx->must_get_next(); + m_lx->must_get_next(); } - + return o; } // ============================================================================ -string ScriptReader::ParseFloat () { - MustNumber (true); - string floatstring = token; - +string botscript_parser::parse_float() +{ + m_lx->must_be (tk_number); + string floatstring = TOKEN; + lexer::token* tok; + // Go after the decimal point - if (PeekNext () == ".") { - Next ("."); - MustNumber (false); + if (m_lx->peek_next (tok) && tok->type == tk_dot) + { + m_lx->skip(); + m_lx->must_get_next (tk_number); floatstring += "."; - floatstring += token; + floatstring += token_string(); } - + return floatstring; } @@ -954,204 +1119,264 @@ // 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; - +data_buffer* botscript_parser::parse_expr_value (type_e reqtype) +{ + data_buffer* b = new data_buffer (16); + + script_variable* g; + // Prefixing "!" means negation. - bool negate = (token == "!"); + bool negate = (m_lx->get_token() == tk_exclamation_mark); + if (negate) // Jump past the "!" - Next (); - + m_lx->skip(); + // Handle strlen - if (token == "strlen") { - MustNext ("("); - MustNext (); - - // By this token we should get a string constant. - constinfo_t* constant = FindConstant (token); + if (TOKEN == "strlen") + { + m_lx->must_get_next (tk_paren_start); + m_lx->must_get_next(); + + // By this TOKEN we should get a string constant. + constant_info* constant = find_constant (TOKEN); + if (!constant || constant->type != TYPE_STRING) - ParserError ("strlen only works with const str"); - + error ("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 == "(") { + error ("strlen returns int but %s is expected\n", GetTypeName (reqtype).c_str()); + + b->write (DH_PUSHNUMBER); + b->write (constant->val.len()); + + m_lx->must_get_next (tk_paren_end); + } + else if (TOKEN == "(") + { // Expression - MustNext (); - DataBuffer* c = ParseExpression (reqtype); - b->Merge (c); - MustNext (")"); - } else if (CommandDef* comm = FindCommand (token)) { + m_lx->must_get_next(); + data_buffer* c = parse_expression (reqtype); + b->merge (c); + m_lx->must_get_next (tk_paren_end); + } + else if (CommandDef* comm = FindCommand (TOKEN)) + { delete b; - + // Command if (reqtype && comm->returnvalue != reqtype) - ParserError ("%s returns an incompatible data type", comm->name.chars()); + error ("%s returns an incompatible data type", comm->name.chars()); + b = ParseCommand (comm); - } else if (constinfo_t* constant = FindConstant (token)) { + } + else if (constant_info* constant = find_constant (TOKEN)) + { // Type check if (reqtype != constant->type) - ParserError ("constant `%s` is %s, expression requires %s\n", + error ("constant `%s` is %s, expression requires %s\n", constant->name.c_str(), GetTypeName (constant->type).c_str(), GetTypeName (reqtype).c_str()); - - switch (constant->type) { + + switch (constant->type) + { + case TYPE_BOOL: + case TYPE_INT: + b->write (DH_PUSHNUMBER); + b->write (atoi (constant->val)); + break; + + case TYPE_STRING: + b->write_string (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: + error ("unknown identifier `%s` (expected keyword, function or variable)", TOKEN.chars()); + break; + 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: + { + m_lx->must_be (tk_number); + + // 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; } - } 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); + b->write_string (TOKEN); break; } } - + // Negate it now if desired if (negate) - b->Write (DH_NEGATELOGICAL); - + 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 (); - +// TOKEN to be the name of the variable, and expects the variable to be given. +data_buffer* botscript_parser::ParseAssignment (script_variable* var) +{ + bool global = !var->statename.len(); + // Get an operator - MustNext (); - int oper = ParseOperator (); - if (!IsAssignmentOperator (oper)) - ParserError ("expected assignment operator"); - + m_lx->must_get_next(); + int oper = parse_operator(); + + if (!is_assignment_operator (oper)) + error ("expected assignment operator"); + if (g_CurMode == MODE_TOPLEVEL) // TODO: lift this restriction - ParserError ("can't alter variables at top level"); - + error ("can't alter variables at top level"); + // Parse the right operand - MustNext (); - DataBuffer* retbuf = new DataBuffer; - DataBuffer* expr = ParseExpression (var->type); - + m_lx->must_get_next(); + data_buffer* retbuf = new data_buffer; + data_buffer* expr = parse_expression (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); + 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 = get_data_header_by_operator (var, oper); + retbuf->write (dh); + retbuf->write (var->index); + } + return retbuf; } -void ScriptReader::PushScope () { +void botscript_parser::push_scope() +{ g_ScopeCursor++; + if (g_ScopeCursor >= MAX_SCOPE) - ParserError ("too deep scope"); - - ScopeInfo* info = &SCOPE(0); + error ("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++) { + + 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"); - +data_buffer* botscript_parser::parse_statement (object_writer* w) +{ + if (find_constant (TOKEN)) // There should not be constants here. + error ("invalid use for constant\n"); + // If it's a variable, expect assignment. - if (ScriptVar* var = FindGlobalVariable (token)) + if (script_variable* var = FindGlobalVariable (TOKEN)) return ParseAssignment (var); - + return null; } -void ScriptReader::AddSwitchCase (ObjWriter* w, DataBuffer* b) { - ScopeInfo* info = &SCOPE(0); - +void botscript_parser::add_switch_case (object_writer* w, data_buffer* b) +{ + ScopeInfo* info = &SCOPE (0); + info->casecursor++; + if (info->casecursor >= MAX_CASE) - ParserError ("too many cases in one switch"); - + error ("too many cases in one switch"); + // Init a mark for the case buffer - int m = w->AddMark (""); + int m = w->add_mark (""); 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); + b->add_reference (m); else - w->AddReference (m); - + w->add_reference (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; + info->casebuffers[info->casecursor] = w->SwitchBuffer = new data_buffer; +} + +constant_info* find_constant (string TOKEN) +{ + for (int i = 0; i < g_ConstInfo.size(); i++) + if (g_ConstInfo[i].name == TOKEN) + return &g_ConstInfo[i]; + + return null; } -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; +// ============================================================================ +// +bool botscript_parser::token_is (e_token a) +{ + return (m_lx->get_token() == a); +} + +// ============================================================================ +// +string botscript_parser::token_string() +{ + return m_lx->get_token()->text; +} + +// ============================================================================ +// +string botscript_parser::describe_position() const +{ + lexer::token* tok = m_lx->get_token(); + return tok->file + ":" + string (tok->line) + ":" + string (tok->column); } \ No newline at end of file