--- a/src/parser.cxx Mon Jan 13 00:15:38 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1381 +0,0 @@ -/* - 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" -#include "lexer.h" - -#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; -int g_ScopeCursor = 0; -data_buffer* g_IfExpression = null; -bool g_CanElse = false; -string* g_UndefinedLabels[MAX_MARKS]; -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 -// of necessary buffering so stuff is written in the correct order. -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]); - - for (int i = 0; i < MAX_MARKS; i++) - g_UndefinedLabels[i] = null; - - while (m_lx->get_next()) - { - // Check if else is potentically valid - if (TOKEN == "else" && !g_CanElse) - error ("else without preceding if"); - - if (TOKEN != "else") - g_CanElse = false; - - 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.is_empty() == false) - w->write_member_buffers(); - - w->write (dh_state_name); - w->write_string (statename); - w->write (dh_state_index); - w->write (g_NumStates); - - g_NumStates++; - g_CurState = TOKEN; - g_GotMainLoop = false; - } - 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_main_loop); - } - break; - - // ============================================================ - // - 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_on_enter : dh_on_exit); - } - 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_is (tk_int)) ? TYPE_INT : - (token_is (tk_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 = declare_global_variable (this, type, varname); - m_lx->must_get_next (tk_semicolon); - } - 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); - } - - // Add a reference to the mark. - w->write (dh_goto); - w->add_reference (m); - m_lx->must_get_next (tk_semicolon); - 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_if_not_goto - 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_if_not_goto); - w->add_reference (marknum); - - // Store it - SCOPE (0).mark1 = marknum; - SCOPE (0).type = e_if_scope; - } 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 != e_if_scope) - 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 = e_else_scope; - } - 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_if_not_goto); - w->add_reference (mark2); - - // Store the needed stuff - SCOPE (0).mark1 = mark1; - SCOPE (0).mark2 = mark2; - SCOPE (0).type = e_while_scope; - } - 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_if_not_goto); - w->add_reference (mark2); - - // Store the marks and incrementor - SCOPE (0).mark1 = mark1; - SCOPE (0).mark2 = mark2; - SCOPE (0).buffer1 = incr; - SCOPE (0).type = e_for_scope; - } - 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 = e_do_scope; - } - 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 = e_switch_scope; - 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 != e_switch_scope) - 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_case_goto); - w->write (num); - add_switch_case (w, null); - SCOPE (0).casenumbers[SCOPE (0).casecursor] = num; - } - break; - - // ============================================================ - // - case tk_default: - { - if (SCOPE (0).type != e_switch_scope) - 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 e_if_scope: - case e_switch_scope: - { - w->add_reference (SCOPE (0).mark1); - } break; - - case e_for_scope: - case e_while_scope: - { - 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 e_for_scope: - case e_while_scope: - case e_do_scope: - { - 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 e_if_scope: - // 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 e_else_scope: - // 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 e_for_scope: - // write the incrementor at the end of the loop block - w->write_buffer (SCOPE (0).buffer1); - - // fall-thru - case e_while_scope: - // 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 e_do_scope: - { - m_lx->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_if_goto); - w->add_reference (SCOPE (0).mark1); - break; - } - - case e_switch_scope: - { - // 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 e_unknown_scope: - break; - } - - // Descend down the stack - g_ScopeCursor--; - continue; - } - - int dataheader = (g_CurMode == MODE_EVENT) ? dh_end_event : - (g_CurMode == MODE_MAINLOOP) ? dh_end_main_loop : - (g_CurMode == MODE_ONENTER) ? dh_end_on_enter : - (g_CurMode == MODE_ONEXIT) ? dh_end_on_exit : -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; - lexer::token* tok; - m_lx->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 (find_global_variable (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; - } - } - - // =============================================================================== - // Script file ended. Do some last checks and write the last things to main buffer - if (g_CurMode != MODE_TOPLEVEL) - error ("script did not end at top level; a `}` is missing somewhere"); - - // stateSpawn must be defined! - if (!g_stateSpawnDefined) - error ("script must have a state named `stateSpawn`!"); - - for (int i = 0; i < MAX_MARKS; i++) - if (g_UndefinedLabels[i]) - 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->write_member_buffers(); - - // String table - w->write_string_table(); -} - -// ============================================================================ -// Parses a command call -data_buffer* botscript_parser::ParseCommand (CommandDef* comm) -{ - data_buffer* r = new data_buffer (64); - - if (g_CurMode == MODE_TOPLEVEL) - 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 (m_lx->get_token() == tk_paren_end) - { - if (curarg < comm->numargs) - error ("too few arguments passed to %s\n\tprototype: %s", - comm->name.chars(), GetCommandPrototype (comm).chars()); - - break; - curarg++; - } - - if (curarg >= comm->maxargs) - error ("too many arguments passed to %s\n\tprototype: %s", - comm->name.chars(), GetCommandPrototype (comm).chars()); - - 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 (m_lx->get_token() == tk_paren_end) - { - curarg++; - break; - } - 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_push_number); - 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 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 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_add_global_var; - case OPER_ASSIGNSUB: return dh_subtract_global_var; - case OPER_ASSIGNMUL: return dh_multiply_global_var; - case OPER_ASSIGNDIV: return dh_divide_global_var; - case OPER_ASSIGNMOD: return dh_mod_global_var; - case OPER_ASSIGN: return dh_assign_global_var; - - 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_not_equals; - case OPER_LESSTHAN: return dh_less_than; - case OPER_GREATERTHAN: return dh_greater_than; - case OPER_LESSTHANEQUALS: return dh_at_most; - case OPER_GREATERTHANEQUALS: return dh_at_least; - case OPER_LEFTSHIFT: return dh_left_shift; - case OPER_RIGHTSHIFT: return dh_right_shift; - case OPER_OR: return dh_or_logical; - case OPER_AND: return dh_and_logical; - case OPER_BITWISEOR: return dh_or_bitwise; - case OPER_BITWISEEOR: return dh_eor_bitwise; - case OPER_BITWISEAND: return dh_and_bitwise; - } - - error ("DataHeaderByOperator: couldn't find dataheader for operator %d!\n", oper); - return 0; -} - -// ============================================================================ -// Parses an expression, potentially recursively -data_buffer* botscript_parser::parse_expression (type_e reqtype) -{ - data_buffer* retbuf = new data_buffer (64); - - // Parse first operand - retbuf->merge (parse_expr_value (reqtype)); - - // Parse any and all operators we get - int oper; - - while ( (oper = parse_operator (true)) != -1) - { - // We peeked the operator, move forward now - m_lx->skip(); - - // Can't be an assignement operator, those belong in assignments. - if (is_assignment_operator (oper)) - error ("assignment operator inside expression"); - - // Parse the right operand. - m_lx->must_get_next(); - data_buffer* rb = parse_expr_value (reqtype); - - if (oper == OPER_TERNARY) - { - // Ternary operator requires - naturally - a third operand. - 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->add_mark (""); // start of "else" case - int mark2 = retbuf->add_mark (""); // end of expression - retbuf->write (dh_if_not_goto); // 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 botscript_parser::parse_operator (bool peek) -{ - string oper; - - if (peek) - oper += m_lx->peek_next_string(); - 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 += m_lx->peek_next_string ( (peek ? 1 : 0); - equalsnext = m_lx->peek_next_string (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) - { - m_lx->must_get_next(); - return o; - } - - // Three-char opers - oper += m_lx->peek_next_string (peek ? 2 : 1); - o = oper == "<<=" ? OPER_ASSIGNLEFTSHIFT : - oper == ">>=" ? OPER_ASSIGNRIGHTSHIFT : - -1; - - if (o != -1) - { - m_lx->must_get_next(); - m_lx->must_get_next(); - } - - return o; -} - -// ============================================================================ -string botscript_parser::parse_float() -{ - m_lx->must_be (tk_number); - string floatstring = TOKEN; - lexer::token* tok; - - // Go after the decimal point - if (m_lx->peek_next (tok) && tok->type == tk_dot) - { - m_lx->skip(); - m_lx->must_get_next (tk_number); - floatstring += "."; - floatstring += token_string(); - } - - 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. -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 = (m_lx->get_token() == tk_exclamation_mark); - - if (negate) // Jump past the "!" - m_lx->skip(); - - // Handle strlen - 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) - error ("strlen only works with const str"); - - if (reqtype != TYPE_INT) - error ("strlen returns int but %s is expected\n", GetTypeName (reqtype).c_str()); - - b->write (dh_push_number); - b->write (constant->val.len()); - - m_lx->must_get_next (tk_paren_end); - } - else if (TOKEN == "(") - { - // Expression - 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) - error ("%s returns an incompatible data type", comm->name.chars()); - - b = ParseCommand (comm); - } - else if (constant_info* constant = find_constant (TOKEN)) - { - // Type check - if (reqtype != constant->type) - 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) - { - case TYPE_BOOL: - case TYPE_INT: - b->write (dh_push_number); - 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 = find_global_variable (TOKEN))) - { - // Global variable - b->write (dh_push_global_var); - 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: - { - 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_push_number); - - long v = atol (TOKEN); - b->write (static_cast<word> (abs (v))); - - if (v < 0) - b->write (dh_unary_minus); - - 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. - m_lx->must_be (tk_string); - b->write_string (TOKEN); - break; - } - } - - // Negate it now if desired - if (negate) - b->write (dh_negate_logical); - - 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. -data_buffer* botscript_parser::ParseAssignment (script_variable* var) -{ - bool global = !var->statename.len(); - - // Get an operator - m_lx->must_get_next(); - int oper = parse_operator(); - - if (!is_assignment_operator (oper)) - error ("expected assignment operator"); - - if (g_CurMode == MODE_TOPLEVEL) - error ("can't alter variables at top level"); - - // Parse the right operand - 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_push_global_var : dh_push_local_var); - retbuf->write (var->index); - retbuf->merge (expr); - retbuf->write ((oper == OPER_ASSIGNLEFTSHIFT) ? dh_left_shift : dh_right_shift); - retbuf->write (global ? dh_assign_global_var : dh_assign_local_var); - 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 botscript_parser::push_scope() -{ - g_ScopeCursor++; - - if (g_ScopeCursor >= MAX_SCOPE) - error ("too deep scope"); - - ScopeInfo* info = &SCOPE (0); - info->type = e_unknown_scope; - 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; - } -} - -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 (script_variable* var = find_global_variable (TOKEN)) - return ParseAssignment (var); - - return null; -} - -void botscript_parser::add_switch_case (object_writer* w, data_buffer* b) -{ - ScopeInfo* info = &SCOPE (0); - - info->casecursor++; - - if (info->casecursor >= MAX_CASE) - error ("too many cases in one switch"); - - // Init a mark for the case buffer - 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->add_reference (m); - else - 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 data_buffer; -} - -constant_info* find_constant (string tok) -{ - for (int i = 0; i < g_ConstInfo.size(); i++) - if (g_ConstInfo[i].name == tok) - 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