Mon, 13 Jan 2014 23:44:15 +0200
- renamed files to .cxx, restructured parser.cc
--- a/CMakeLists.txt Mon Jan 13 00:15:38 2014 +0200 +++ b/CMakeLists.txt Mon Jan 13 23:44:15 2014 +0200 @@ -1,15 +1,17 @@ add_executable (botc - src/commands.cxx - src/events.cxx + src/commands.cc + src/data_buffer.cc + src/events.cc src/format.cc - src/main.cxx - src/object_writer.cxx - src/parser.cxx - src/preprocessor.cxx - src/scriptreader.cxx - src/str.cxx - src/stringtable.cxx - src/variables.cxx + src/lexer.cc + src/lexer_scanner.cc + src/main.cc + src/object_writer.cc + src/parser.cc + src/str.cc + src/stringtable.cc + src/variables.cc ) -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -W -Wall") \ No newline at end of file +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -W -Wall") +set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/botstuff.h Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,167 @@ +// Macros and enums from zandronum bots.h + +#ifndef BOTC_BOTSTUFF_H +#define BOTC_BOTSTUFF_H + +static const int g_max_states = 256; +static const int g_max_events = 32; +static const int g_max_global_events = 32; +static const int g_max_global_vars = 128; +static const int g_max_global_arrays = 16; +static const int g_max_array_size = 65536; +static const int g_max_state_vars = 16; +static const int g_max_stringlist_size = 128; +static const int g_max_string_length = 256; +static const int g_max_reaction_time = 52; +static const int g_max_stored_events = 64; + +enum e_data_header +{ + dh_command, + dh_state_index, + dh_state_name, + dh_on_enter, + dh_main_loop, + dh_on_exit, + dh_event, + dh_end_on_enter, + dh_end_main_loop, + dh_end_on_exit, + dh_end_event, + dh_if_goto, + dh_if_not_goto, + dh_goto, + dh_or_logical, + dh_and_logical, + dh_or_bitwise, + dh_eor_bitwise, + dh_and_bitwise, + dh_equals, + dh_not_equals, + dh_less_than, + dh_at_most, + dh_greater_than, + dh_at_least, + dh_negate_logical, + dh_left_shift, + dh_right_shift, + dh_add, + dh_subtract, + dh_unary_minus, + dh_multiply, + dh_divide, + dh_modulus, + dh_push_number, + dh_push_string_index, + dh_push_global_var, + dh_push_local_var, + dh_drop_stack_position, + dh_script_var_list, + dh_string_list, + dh_increase_global_var, + dh_decrease_global_var, + dh_assign_global_var, + dh_add_global_var, + dh_subtract_global_var, + dh_multiply_global_var, + dh_divide_global_var, + dh_mod_global_var, + dh_increase_local_var, + dh_decrease_local_var, + dh_assign_local_var, + dh_add_local_var, + dh_subtract_local_var, + dh_multiply_local_var, + dh_divide_local_var, + dh_mod_local_var, + dh_case_goto, + dh_drop, + dh_increase_global_array, + dh_decrease_global_array, + dh_assign_global_array, + dh_add_global_array, + dh_subtract_global_array, + dh_multiply_global_array, + dh_divide_global_array, + dh_mod_global_array, + dh_push_global_array, + dh_swap, + dh_dup, + dh_array_set, + num_data_headers +}; + +//***************************************************************************** +// These are the different bot events that can be posted to a bot's state. +enum e_event +{ + ev_killed_by_enemy, + ev_killed_by_player, + ev_killed_by_self, + ev_killed_by_environment, + ev_reached_goal, + ev_goal_removed, + ev_damaged_by_player, + ev_player_say, + ev_enemy_killed, + ev_respawned, + ev_intermission, + ev_new_maps, + ev_enemy_used_fist, + ev_enemy_used_chainsaw, + ev_enemy_fired_pistol, + ev_enemy_fired_shotgun, + ev_enemy_fired_ssg, + ev_enemy_fired_chaingun, + ev_enemy_fired_minigun, + ev_enemy_fired_rocket, + ev_enemy_fired_grenade, + ev_enemy_fired_railgun, + ev_enemy_fired_plasma, + ev_enemy_fired_bfg, + ev_enemy_fired_bfg10k, + ev_player_used_fist, + ev_player_used_chainsaw, + ev_player_fired_pistol, + ev_player_fired_shotgun, + ev_player_fired_ssg, + ev_player_fired_chaingun, + ev_player_fired_minigun, + ev_player_fired_rocket, + ev_player_fired_grenade, + ev_player_fired_railgun, + ev_player_fired_plasma, + ev_player_fired_bfg, + ev_player_fired_bfg10k, + ev_used_fist, + ev_used_chainsaw, + ev_fired_pistol, + ev_fired_shotgun, + ev_fired_ssg, + ev_fired_chaingun, + ev_fired_minigun, + ev_fired_rocket, + ev_fired_grenade, + ev_fired_railgun, + ev_fired_plasma, + ev_fired_bfg, + ev_fired_bfg10k, + ev_player_joined_game, + ev_joined_game, + ev_duel_starting_countdown, + ev_duel_fight, + ev_duel_win_sequence, + ev_spectating, + ev_lms_starting_countdown, + ev_lms_fight, + ev_lms_win_sequence, + ev_weapon_change, + ev_enemy_bfg_explode, + ev_player_bfg_explode, + ev_bfg_explode, + ev_recieved_medal, + + num_bot_events +}; + +#endif // BOTC_BOTSTUFF_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/commands.cc Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,205 @@ +/* + Copyright (c) 2012-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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "main.h" +#include "scriptreader.h" +#include "str.h" +#include "commands.h" +#include "lexer.h" + +static list<command_info*> g_commands; + +// ============================================================================ +// Reads command definitions from commands.def and stores them to memory. +void init_commands () +{ + lexer lx; + lx.process_file ("commands.def"); + + while (lx.get_next()) + { + command_info* comm = new command_info; + + // Number + lx.must_be (tk_number); + comm->number = lx.get_token()->text.to_long(); + + lx.must_get_next (tk_colon); + + // Name + lx.must_get_next (tk_symbol); + comm->name = lx.get_token()->text; + + if (IsKeyword (comm->name)) + error ("command name `%1` conflicts with keyword", comm->name); + + lx.must_get_next (tk_colon); + + // Return value + lx.must_get_any_of ({tk_int, tk_void, tk_bool, tk_str}); + comm->returnvalue = GetTypeByName (lx.get_token()->text); // TODO + assert (comm->returnvalue != -1); + + lx.must_get_next (tk_colon); + + // Num args + lx.must_get_next (tk_number); + comm->numargs = lx.get_token()->text.to_long(); + + lx.must_get_next (tk_colon); + + // Max args + lx.must_get_next (tk_number); + comm->maxargs = lx.get_token()->text.to_long(); + + // Argument types + int curarg = 0; + + while (curarg < comm->maxargs) + { + command_argument arg; + lx.must_get_next (tk_colon); + lx.must_get_any_of ({tk_int, tk_bool, tk_str}); + type_e type = GetTypeByName (lx.get_token()->text); + assert (type != -1 && type != TYPE_VOID); + arg.type = type; + + lx.must_get_next (tk_paren_start); + lx.must_get_next (tk_symbol); + arg.name = lx.get_token()->text; + + // If this is an optional parameter, we need the default value. + if (curarg >= comm->numargs) + { + lx.must_get_next (tk_assign); + + switch (type) + { + case TYPE_INT: + case TYPE_BOOL: + lx.must_get_next (tk_number); + break; + + case TYPE_STRING: + lx.must_get_next (tk_string); + break; + + case TYPE_UNKNOWN: + case TYPE_VOID: + break; + } + + arg.defvalue = lx.get_token()->text.to_long(); + } + + lx.must_get_next (tk_paren_end); + comm->args << arg; + curarg++; + } + + g_commands << comm; + } + + if (g_commands.is_empty()) + error ("no commands defined!\n"); + + print ("%1 command definitions read.\n", g_commands.size()); +} + +// ============================================================================ +// Finds a command by name +command_info* find_command_by_name (string fname) +{ + for (command_info* comm : g_commands) + { + if (fname.to_uppercase() == comm->name.to_uppercase()) + return comm; + } + + return null; +} + +// ============================================================================ +// Returns the prototype of the command +string get_command_signature (command_info* comm) +{ + string text; + text += GetTypeName (comm->returnvalue); + text += ' '; + text += comm->name; + text += '('; + + bool hasoptionals = false; + + for (int i = 0; i < comm->maxargs; i++) + { + if (i == comm->numargs) + { + hasoptionals = true; + text += '['; + } + + if (i) + text += ", "; + + text += GetTypeName (comm->args[i].type); + text += ' '; + text += comm->args[i].name; + + if (i >= comm->numargs) + { + text += '='; + + bool is_string = comm->args[i].type == TYPE_STRING; + + if (is_string) + text += '"'; + + text += string::from_number (comm->args[i].defvalue); + + if (is_string) + text += '"'; + } + } + + if (hasoptionals) + text += ']'; + + text += ')'; + return text; +} + +const list<command_info*> get_commands() +{ + return g_commands; +}
--- a/src/commands.cxx Mon Jan 13 00:15:38 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,208 +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 <stdlib.h> -#include <stdio.h> -#include <string.h> -#include "main.h" -#include "scriptreader.h" -#include "str.h" -#include "commands.h" - -CommandDef* g_CommDef; - -// ============================================================================ -// Reads command definitions from commands.def and stores them to memory. -void ReadCommands () { - ScriptReader* r = new ScriptReader ("commands.def"); - g_CommDef = null; - CommandDef* curdef = g_CommDef; - int numCommDefs = 0; - - while (r->PeekNext().len()) { - CommandDef* comm = new CommandDef; - comm->next = null; - - // Number - r->MustNumber (); - comm->number = r->token.to_long(); - - r->MustNext (":"); - - // Name - r->MustNext (); - comm->name = r->token; - if (IsKeyword (comm->name)) - r->ParserError ("command name `%s` conflicts with keyword", comm->name.chars()); - - r->MustNext (":"); - - // Return value - r->MustNext (); - comm->returnvalue = GetTypeByName (r->token); - if (comm->returnvalue == -1) - r->ParserError ("bad return value type `%s` for command %s", r->token.chars(), comm->name.chars()); - - r->MustNext (":"); - - // Num args - r->MustNumber (); - comm->numargs = r->token.to_long(); - - r->MustNext (":"); - - // Max args - r->MustNumber (); - comm->maxargs = r->token.to_long(); - - if (comm->maxargs > MAX_MAXARGS) - r->ParserError ("maxargs (%d) greater than %d!", comm->maxargs, MAX_MAXARGS); - - // Argument types - int curarg = 0; - while (curarg < comm->maxargs) { - r->MustNext (":"); - r->MustNext (); - - type_e type = GetTypeByName (r->token); - if (type == -1) - r->ParserError ("bad argument %d type `%s`", curarg, r->token.chars()); - if (type == TYPE_VOID) - r->ParserError ("void is not a valid argument type!"); - comm->argtypes[curarg] = type; - - r->MustNext ("("); - r->MustNext (); - - // - 1 because of terminating null character - if (r->token.len() > MAX_ARGNAMELEN - 1) - r->ParserWarning ("argument name is too long (%d, max is %d)", - r->token.len(), MAX_ARGNAMELEN - 1); - - strncpy (comm->argnames[curarg], r->token.chars(), MAX_ARGNAMELEN); - comm->argnames[curarg][MAX_ARGNAMELEN-1] = 0; - - // If this is an optional parameter, we need the default value. - if (curarg >= comm->numargs) { - r->MustNext ("="); - switch (type) { - case TYPE_INT: - case TYPE_BOOL: - r->MustNumber(); - break; - case TYPE_STRING: - r->MustString(); - break; - case TYPE_UNKNOWN: - case TYPE_VOID: - break; - } - - comm->defvals[curarg] = r->token.to_long(); - } - - r->MustNext (")"); - curarg++; - } - - if (!g_CommDef) - g_CommDef = comm; - - if (!curdef) { - curdef = comm; - } else { - curdef->next = comm; - curdef = comm; - } - numCommDefs++; - } - - if (!numCommDefs) - r->ParserError ("no commands defined!\n"); - - r->CloseFile (); - delete r; - printf ("%d command definitions read.\n", numCommDefs); -} - -// ============================================================================ -// Finds a command by name -CommandDef* FindCommand (string fname) { - CommandDef* comm; - ITERATE_COMMANDS (comm) { - if (fname.to_uppercase() == comm->name.to_uppercase()) - return comm; - } - - return null; -} - -// ============================================================================ -// Returns the prototype of the command -string GetCommandPrototype (CommandDef* comm) { - string text; - text += GetTypeName (comm->returnvalue); - text += ' '; - text += comm->name; - text += '('; - - bool hasOptionalArguments = false; - for (int i = 0; i < comm->maxargs; i++) { - if (i == comm->numargs) { - hasOptionalArguments = true; - text += '['; - } - - if (i) - text += ", "; - - text += GetTypeName (comm->argtypes[i]); - text += ' '; - text += comm->argnames[i]; - - if (i >= comm->numargs) { - text += '='; - - bool isString = comm->argtypes[i] == TYPE_STRING; - if (isString) text += '"'; - - char defvalstring[8]; - sprintf (defvalstring, "%d", comm->defvals[i]); - text += defvalstring; - - if (isString) text += '"'; - } - } - - if (hasOptionalArguments) - text += ']'; - text += ')'; - return text; -} \ No newline at end of file
--- a/src/commands.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/commands.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,31 +31,29 @@ #ifndef BOTC_COMMANDS_H #define BOTC_COMMANDS_H -#define MAX_MAXARGS 8 -#define MAX_ARGNAMELEN 16 - #include "main.h" #include "str.h" -#define ITERATE_COMMANDS(comm) \ - for (comm = g_CommDef; comm->next != null; comm = comm->next) - -struct CommandDef { - string name; - int number; - int numargs; - int maxargs; - type_e returnvalue; - type_e argtypes[MAX_MAXARGS]; - char argnames[MAX_MAXARGS][MAX_ARGNAMELEN]; - int defvals[MAX_MAXARGS]; - CommandDef* next; +struct command_argument +{ + type_e type; + string name; + int defvalue; }; -void ReadCommands (); -CommandDef* FindCommand (string a); -string GetCommandPrototype (CommandDef* comm); +struct command_info +{ + string name; + int number; + int numargs; + int maxargs; + type_e returnvalue; + list<command_argument> args; +}; -extern CommandDef* g_CommDef; +void init_commands (); +command_info* find_command_by_name (string a); +string get_command_signature (command_info* comm); +const list<command_info*> get_commands(); -#endif // BOTC_COMMANDS_H \ No newline at end of file +#endif // BOTC_COMMANDS_H
--- a/src/containers.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/containers.h Mon Jan 13 23:44:15 2014 +0200 @@ -39,13 +39,13 @@ template<class T> class list { public: - typedef typename ::std::deque<T> list_type; - typedef typename list_type::iterator it; - typedef typename list_type::const_iterator c_it; - typedef typename list_type::reverse_iterator r_it; - typedef typename list_type::const_reverse_iterator cr_it; - typedef T element_type; - typedef list<T> self_type; + using list_type = typename ::std::deque<T>; + using iterator = typename list_type::iterator; + using const_iterator = typename list_type::const_iterator; + using reverse_iterator = typename list_type::reverse_iterator; + using const_reverse_iterator = typename list_type::const_reverse_iterator; + using element_type = T; + using self_type = list<T>; // ===================================================================== // @@ -65,56 +65,56 @@ // ===================================================================== // - it begin() + iterator begin() { return m_data.begin(); } // ===================================================================== // - c_it begin() const + const_iterator begin() const { return m_data.cbegin(); } // ===================================================================== // - it end() + iterator end() { return m_data.end(); } // ===================================================================== // - c_it end() const + const_iterator end() const { return m_data.cend(); } // ===================================================================== // - r_it rbegin() + reverse_iterator rbegin() { return m_data.rbegin(); } // ===================================================================== // - cr_it crbegin() const + const_reverse_iterator crbegin() const { return m_data.crbegin(); } // ===================================================================== // - r_it rend() + reverse_iterator rend() { return m_data.rend(); } // ===================================================================== // - cr_it crend() const + const_reverse_iterator crend() const { return m_data.crend(); } @@ -217,7 +217,7 @@ // Remove duplicate entries. For this to be effective, the vector must be // sorted first. sort(); - it pos = std::unique (begin(), end()); + iterator pos = std::unique (begin(), end()); resize (std::distance (begin(), pos)); } @@ -260,11 +260,11 @@ // ===================================================================== // - int find (const element_type& needle) + int find (const element_type& needle) const { int i = 0; - for (const element_type & hay : *this) + for (const element_type& hay : *this) { if (hay == needle) return i;
--- a/src/data_buffer.cc Mon Jan 13 00:15:38 2014 +0200 +++ b/src/data_buffer.cc Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -67,7 +67,7 @@ { // Same for references // TODO: add a g_NextRef system like here, akin to marks! - int r = AddMarkReference (other->refs[u]->num, false); + int r = add_reference (other->refs[u]->num, false); refs[r]->pos = other->refs[u]->pos + oldsize; } } @@ -109,7 +109,7 @@ // ============================================================================ // -int data_buffer::AddMarkReference (int marknum, bool placeholder) +int data_buffer::add_reference (int marknum, bool placeholder) { int u; @@ -117,6 +117,7 @@ if (!refs[u]) break; + // TODO: get rid of this if (u == MAX_MARKS) error ("mark reference quota exceeded, all goto-statements, if-structs and loops add refs\n"); @@ -127,7 +128,7 @@ // Write a dummy placeholder for the reference if (placeholder) - write (1234); + write (0xBEEFCAFE); return u; } @@ -205,18 +206,18 @@ // TODO: Casting float to word causes the decimal to be lost. // Find a way to store the number without such loss. float val = atof (floatstring); - write (DH_PUSHNUMBER); - write (static_cast<word> ((val > 0) ? val : -val)); + write (dh_push_number); + write (static_cast<word> (abs (val))); if (val < 0) - write (DH_UNARYMINUS); + write (dh_unary_minus); } // ============================================================================ // void data_buffer::write_string (string a) { - write (DH_PUSHSTRINGINDEX); + write (dh_push_string_index); write (get_string_table_index (a)); }
--- a/src/data_buffer.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/data_buffer.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -98,7 +98,7 @@ if (writesize >= allocsize) // should NEVER happen because resizing is done above error ("DataBuffer: written size exceeds allocated size!\n"); - buffer[writesize] = uni.b[x]; + buffer[writesize] = uni.as_bytes[x]; writesize++; } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/events.cc Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,90 @@ +/* + Copyright (c) 2012-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 <stdlib.h> +#include <stdio.h> +#include "main.h" +#include "scriptreader.h" +#include "str.h" +#include "events.h" +#include "lexer.h" + +static void unlink_events(); +static list<event_info*> g_events; + +// ============================================================================ +// Read event definitions from file +void init_events() +{ + lexer lx; + lx.process_file ("events.def"); + int num_events = 0; + + while (lx.get_next (tk_symbol)) + { + event_info* e = new event_info; + e->name = lx.get_token()->text; + e->number = num_events++; + g_events << e; + } + + printf ("%d event definitions read.\n", num_events); + atexit (&unlink_events); +} + +// ============================================================================ +// Delete event definitions recursively +static void unlink_events() +{ + print ("Freeing event information.\n"); + + for (event_info* e : g_events) + delete e; + + g_events.clear(); +} + +// ============================================================================ +// Finds an event definition by index +event_info* find_event_by_index (int idx) +{ + return g_events[idx]; +} + +// ============================================================================ +// Finds an event definition by name +event_info* find_event_by_name (string a) +{ + for (event_info* e : g_events) + if (a.to_uppercase() == e->name.to_uppercase()) + return e; + + return null; +}
--- a/src/events.cxx Mon Jan 13 00:15:38 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +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 <stdlib.h> -#include <stdio.h> -#include "main.h" -#include "scriptreader.h" -#include "str.h" -#include "events.h" -#include "lexer.h" - -static void unlink_events(); -static list<event_info*> g_events; - -// ============================================================================ -// Read event definitions from file -void init_events() -{ - lexer lx; - lx.process_file ("events.def"); - int num_events = 0; - - while (lx.get_next (tk_symbol)) - { - event_info* e = new event_info; - e->name = lx.get_token()->text; - e->number = num_events++; - g_events << e; - } - - printf ("%d event definitions read.\n", num_events); - atexit (&unlink_events); -} - -// ============================================================================ -// Delete event definitions recursively -static void unlink_events() -{ - print ("Freeing event information.\n"); - - for (event_info* e : g_events) - delete e; - - g_events.clear(); -} - -// ============================================================================ -// Finds an event definition by index -event_info* find_event_by_index (int idx) -{ - return g_events[idx]; -} - -// ============================================================================ -// Finds an event definition by name -event_info* find_event_by_name (string a) -{ - for (event_info* e : g_events) - if (a.to_uppercase() == e->name.to_uppercase()) - return e; - - return null; -}
--- a/src/events.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/events.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without
--- a/src/format.cc Mon Jan 13 00:15:38 2014 +0200 +++ b/src/format.cc Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without
--- a/src/format.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/format.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -38,8 +38,9 @@ { public: format_arg (const string& a) : m_string (a) {} - format_arg (int a) : m_string (a) {} - format_arg (long a) : m_string (a) {} + format_arg (char a) : m_string (a) {} + format_arg (int a) : m_string (string::from_number (a)) {} + format_arg (long a) : m_string (string::from_number (a)) {} format_arg (const char* a) : m_string (a) {} format_arg (void* a) @@ -99,6 +100,20 @@ return custom_format (a, "%d"); } +class script_error : public std::exception +{ + public: + script_error (const string& msg) : m_msg (msg) {} + + inline const char* what() const throw() + { + return m_msg.c_str(); + } + + private: + string m_msg; +}; + string format_args (const list<format_arg>& args); void print_args (FILE* fp, const list<format_arg>& args); void do_fatal (const list<format_arg>& args); @@ -107,10 +122,12 @@ # define format(...) format_args({ __VA_ARGS__ }) # define fprint(A, ...) print_args( A, { __VA_ARGS__ }) # define print(...) print_args( stdout, { __VA_ARGS__ }) +# define error(...) throw script_error (format (__VA_ARGS__)) #else string format (void, ...); void fprint (FILE* fp, ...); void print (void, ...); +void error (void, ...); #endif #ifndef IN_IDE_PARSER
--- a/src/lexer.cc Mon Jan 13 00:15:38 2014 +0200 +++ b/src/lexer.cc Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -57,8 +57,10 @@ lexer_scanner sc (fp); + devf ("Processing tokens...\n"); while (sc.get_next_token()) { + devf (".\n"); // Preprocessor commands: if (sc.get_token_type() == tk_hash) { @@ -93,7 +95,7 @@ } } - devf ("Lexer: File %1 processed.\n", file_name); + devf ("Lexer: File %1 processed (%2 tokens).\n", file_name, m_tokens.size()); m_token_position = m_tokens.begin() - 1; } @@ -104,6 +106,12 @@ iterator pos = m_token_position; devf ("Lexer: Requested next token, requirement: %1\n", describe_token_type (req)); + if (m_tokens.is_empty()) + { + devf ("Lexer: no tokens! Failed.\n"); + return false; + } + if (is_at_end()) { devf ("Lexer: at end of tokens. Failed.\n"); @@ -112,7 +120,7 @@ m_token_position++; - if (req != tk_any && get_token() != req) + if (req != tk_any && get_token_type() != req) { devf ("Lexer: Token %1 does not meet the requirement\n", describe_token (get_token())); m_token_position = pos; @@ -156,7 +164,7 @@ error ("unexpected EOF"); for (e_token tok : toks) - if (get_token() == tok) + if (get_token_type() == tok) return; string toknames; @@ -181,7 +189,7 @@ if (!get_next()) error ("unexpected EOF"); - if (get_token() == tk_symbol) + if (get_token_type() == tk_symbol) { for (int i = 0; i < syms.size(); ++i) { @@ -198,7 +206,8 @@ // void lexer::must_be (e_token tok) { - if (get_token() != tok) + print ("pos: %1", m_token_position - m_tokens.begin()); + if (get_token_type() != tok) error ("expected %1, got %2", describe_token_type (tok), describe_token (get_token())); } @@ -263,4 +272,5 @@ m_token_position += a; string result = get_token()->text; m_token_position = oldpos; + return result; }
--- a/src/lexer.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/lexer.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,11 +39,11 @@ types: struct token { - e_token type; + e_token type; string text; string file; - int line; - int column; + int line; + int column; }; using token_list = list<token>; @@ -55,16 +55,21 @@ void process_file (string file_name); bool get_next (e_token req = tk_any); - void must_get_next (e_token tok); + void must_get_next (e_token tok = tk_any); void must_get_any_of (const list<e_token>& toks); int get_one_symbol (const string_list& syms); void must_be (e_token tok); bool peek_next (token* tk = null); + inline bool has_valid_token() const + { + return (is_at_end() == false && m_token_position != m_tokens.begin()); + } + inline token* get_token() const { - assert (is_at_end() == false); - return & (*m_token_position); + assert (has_valid_token() == true); + return &(*m_token_position); } inline bool is_at_end() const @@ -72,7 +77,7 @@ return m_token_position == m_tokens.end(); } - inline token get_token_type() const + inline e_token get_token_type() const { return get_token()->type; } @@ -95,7 +100,7 @@ m_token_position += a; } - string peek_next_string (int a); + string peek_next_string (int a = 1); private: token_list m_tokens;
--- a/src/lexer_scanner.cc Mon Jan 13 00:15:38 2014 +0200 +++ b/src/lexer_scanner.cc Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ #include <cstring> #include <string> #include "lexer_scanner.h" +#include "lexer.h" static const string g_token_strings[] = { @@ -93,7 +94,7 @@ "return", }; -static_assert (countof (g_token_strings) == (int) last_named_token + 1, +static_assert (countof (g_token_strings) == (int) last_named_token, "Count of g_token_strings is not the same as the amount of named token identifiers."); // ============================================================================= @@ -130,7 +131,7 @@ r = false; // Advance the cursor unless we want to just peek - if (r && ! (flags & f_check_peek)) + if (r && !(flags & f_check_peek)) m_ptr += strlen (c); return r; @@ -153,6 +154,33 @@ m_ptr++; } + // Check for comments + if (strncmp (m_ptr, "//", 2) == 0) + { + m_ptr += 2; + + while (*(++m_ptr) != '\n') + ; + + return get_next_token(); + } + elif (strncmp (m_ptr, "/*", 2) == 0) + { + m_ptr += 2; + + while (strncmp (++m_ptr, "*/", 2) != 0) + { + if (*m_ptr == '\n') + { + m_line++; + m_line_break_pos = m_ptr; + } + } + + m_ptr += 2; // skip the */ + return get_next_token(); + } + if (*m_ptr == '\0') return false; @@ -162,7 +190,7 @@ if (check_string (g_token_strings[i], f_check_word)) { m_token_text = g_token_strings[i]; - m_e_token = (e_token) i; + m_token_type = (e_token) i; return true; } } @@ -196,19 +224,19 @@ m_token_text += *m_ptr++; } - m_e_token = tk_string; + m_token_type = tk_string; m_ptr++; // skip the final quote return true; } - m_e_token = tk_symbol; + m_token_type = tk_symbol; if (isdigit (*m_ptr)) { while (isdigit (*m_ptr)) m_token_text += *m_ptr++; - m_e_token = tk_number; + m_token_type = tk_number; return true; } @@ -239,11 +267,14 @@ return true; } + error ("unknown character \"%1\"", *m_ptr); return false; } +// ============================================================================= +// string lexer_scanner::get_token_string (e_token a) { - assert ((int) a <= (int) last_named_token); + assert ((int) a <= last_named_token); return g_token_strings[a]; }
--- a/src/lexer_scanner.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/lexer_scanner.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cc Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,281 @@ +/* + Copyright (c) 2012-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 "main.h" +#include "scriptreader.h" +#include "object_writer.h" +#include "events.h" +#include "commands.h" +#include "stringtable.h" +#include "variables.h" +#include "data_buffer.h" +#include "object_writer.h" +#include "parser.h" +#include "lexer.h" + +// List of keywords +const string_list g_Keywords = +{ + "bool", + "break", + "case", + "continue", + "const", + "default", + "do", + "else", + "event", + "for", + "goto", + "if", + "int", + "mainloop", + "onenter", + "onexit", + "state", + "switch", + "str" + "void", + "while", + + // These ones aren't implemented yet but I plan to do so, thus they are + // reserved. Also serves as a to-do list of sorts for me. >:F + "enum", // Would enum actually be useful? I think so. + "func", // Would function support need external support from zandronum? + "return", +}; + +// databuffer global variable +int g_NextMark = 0; + +int main (int argc, char** argv) +{ + try + { + // Intepret command-line parameters: + // -l: list commands + // I guess there should be a better way to do this. + if (argc == 2 && !strcmp (argv[1], "-l")) + { + command_info* comm; + init_commands(); + printf ("Begin list of commands:\n"); + printf ("------------------------------------------------------\n"); + + for (command_info* comm : get_commands()) + print ("%1\n", get_command_signature (comm)); + + printf ("------------------------------------------------------\n"); + printf ("End of command list\n"); + exit (0); + } + + // Print header + string header; + string headerline; + header = format ("%1 version %2.%3", APPNAME, VERSION_MAJOR, VERSION_MINOR); + + for (int i = 0; i < (header.len() / 2) - 1; ++i) + headerline += "-="; + + headerline += '-'; + print ("%1\n%2\n", header, headerline); + + if (argc < 2) + { + fprintf (stderr, "usage: %s <infile> [outfile] # compiles botscript\n", argv[0]); + fprintf (stderr, " %s -l # lists commands\n", argv[0]); + exit (1); + } + + string outfile; + + if (argc < 3) + outfile = ObjectFileName (argv[1]); + else + outfile = argv[2]; + + // If we'd end up writing into an existing file, + // ask the user if we want to overwrite it + if (fexists (outfile)) + { + // Additional warning if the paths are the same + string warning; +#ifdef FILE_CASEINSENSITIVE + + if (+outfile == +string (argv[1])) +#else + if (outfile == argv[1]) +#endif + { + warning = "\nWARNING: Output file is the same as the input file. "; + warning += "Answering yes here will destroy the source!\n"; + warning += "Continue nevertheless?"; + } + + printf ("output file `%s` already exists! overwrite?%s (y/n) ", outfile.chars(), warning.chars()); + + char ans; + fgets (&ans, 1, stdin); + + if (ans != 'y') + { + printf ("abort\n"); + exit (1); + } + } + + // Read definitions + printf ("Reading definitions...\n"); + init_events(); + init_commands(); + + // Prepare reader and writer + botscript_parser* r = new botscript_parser; + object_writer* w = new object_writer; + + // We're set, begin parsing :) + printf ("Parsing script...\n"); + r->parse_botscript (argv[1], w); + printf ("Script parsed successfully.\n"); + + // Parse done, print statistics and write to file + int globalcount = g_GlobalVariables.size(); + int stringcount = num_strings_in_table(); + int NumMarks = w->MainBuffer->count_marks(); + int NumRefs = w->MainBuffer->count_references(); + print ("%1 / %2 strings written\n", stringcount, g_max_stringlist_size); + print ("%1 / %2 global variables\n", globalcount, g_max_global_vars); + print ("%1 / %2 bytecode marks\n", NumMarks, MAX_MARKS); // TODO: nuke + print ("%1 / %2 bytecode references\n", NumRefs, MAX_MARKS); // TODO: nuke + print ("%1 / %2 events\n", g_NumEvents, g_max_events); + print ("%1 state%s1\n", g_NumStates); + + w->write_to_file (outfile); + + // Clear out the junk + delete r; + delete w; + + // Done! + exit (0); + } + catch (script_error& e) + { + lexer* lx = lexer::get_main_lexer(); + string fileinfo; + + if (lx != null && lx->has_valid_token()) + { + lexer::token* tk = lx->get_token(); + fileinfo = format ("%1:%2:%3: ", tk->file, tk->line, tk->column); + } + + fprint (stderr, "%1error: %2\n", fileinfo, e.what()); + } +} + +// ============================================================================ +// Utility functions + +// ============================================================================ +// Does the given file exist? +bool fexists (string path) +{ + if (FILE* test = fopen (path, "r")) + { + fclose (test); + return true; + } + + return false; +} + +// ============================================================================ +// Mutates given filename to an object filename +string ObjectFileName (string s) +{ + // Locate the extension and chop it out + int extdot = s.last ("."); + + if (extdot >= s.len() - 4) + s -= (s.len() - extdot); + + s += ".o"; + return s; +} + +// ============================================================================ +// Is the given argument a reserved keyword? +bool IsKeyword (string s) +{ + for (int u = 0; u < NumKeywords(); u++) + if (s.to_uppercase() == g_Keywords[u].to_uppercase()) + return true; + + return false; +} + +int NumKeywords() +{ + return sizeof (g_Keywords) / sizeof (const char*); +} + +// ============================================================================ +type_e GetTypeByName (string t) +{ + t = t.to_lowercase(); + return (t == "int") ? TYPE_INT : + (t == "str") ? TYPE_STRING : + (t == "void") ? TYPE_VOID : + (t == "bool") ? TYPE_BOOL : + TYPE_UNKNOWN; +} + + +// ============================================================================ +// Inverse operation - type name by value +string GetTypeName (type_e type) +{ + switch (type) + { + case TYPE_INT: return "int"; break; + + case TYPE_STRING: return "str"; break; + + case TYPE_VOID: return "void"; break; + + case TYPE_BOOL: return "bool"; break; + + case TYPE_UNKNOWN: return "???"; break; + } + + return ""; +}
--- a/src/main.cxx Mon Jan 13 00:15:38 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,278 +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 "main.h" -#include "scriptreader.h" -#include "object_writer.h" -#include "events.h" -#include "commands.h" -#include "stringtable.h" -#include "variables.h" -#include "containers.h" -#include "data_buffer.h" -#include "bots.h" -#include "object_writer.h" -#include "parser.h" - -// List of keywords -const string_list g_Keywords = -{ - "bool", - "break", - "case", - "continue", - "const", - "default", - "do", - "else", - "event", - "for", - "goto", - "if", - "int", - "mainloop", - "onenter", - "onexit", - "state", - "switch", - "str" - "void", - "while", - - // These ones aren't implemented yet but I plan to do so, thus they are - // reserved. Also serves as a to-do list of sorts for me. >:F - "enum", // Would enum actually be useful? I think so. - "func", // Would function support need external support from zandronum? - "return", -}; - -// databuffer global variable -int g_NextMark = 0; - -int main (int argc, char** argv) -{ - // Intepret command-line parameters: - // -l: list commands - // I guess there should be a better way to do this. - if (argc == 2 && !strcmp (argv[1], "-l")) - { - ReadCommands(); - printf ("Begin list of commands:\n"); - printf ("------------------------------------------------------\n"); - - CommandDef* comm; - ITERATE_COMMANDS (comm) - print ("%1\n", GetCommandPrototype (comm)); - - printf ("------------------------------------------------------\n"); - printf ("End of command list\n"); - exit (0); - } - - // Print header - string header; - string headerline; - header = format ("%1 version %2.%3", APPNAME, VERSION_MAJOR, VERSION_MINOR); - - for (int i = 0; i < (header.len() / 2) - 1; ++i) - headerline += "-="; - - headerline += '-'; - print ("%1\n%2\n", header, headerline); - - if (argc < 2) - { - fprintf (stderr, "usage: %s <infile> [outfile] # compiles botscript\n", argv[0]); - fprintf (stderr, " %s -l # lists commands\n", argv[0]); - exit (1); - } - - string outfile; - - if (argc < 3) - outfile = ObjectFileName (argv[1]); - else - outfile = argv[2]; - - // If we'd end up writing into an existing file, - // ask the user if we want to overwrite it - if (fexists (outfile)) - { - // Additional warning if the paths are the same - string warning; -#ifdef FILE_CASEINSENSITIVE - - if (+outfile == +string (argv[1])) -#else - if (outfile == argv[1]) -#endif - { - warning = "\nWARNING: Output file is the same as the input file. "; - warning += "Answering yes here will destroy the source!\n"; - warning += "Continue nevertheless?"; - } - - printf ("output file `%s` already exists! overwrite?%s (y/n) ", outfile.chars(), warning.chars()); - - char ans; - fgets (&ans, 1, stdin); - - if (ans != 'y') - { - printf ("abort\n"); - exit (1); - } - } - - // Read definitions - printf ("Reading definitions...\n"); - ReadEvents(); - ReadCommands(); - - // Prepare reader and writer - botscript_parser* r = new botscript_parser; - object_writer* w = new object_writer; - - // We're set, begin parsing :) - printf ("Parsing script...\n"); - r->parse_botscript (argv[1], w); - printf ("Script parsed successfully.\n"); - - // Parse done, print statistics and write to file - int globalcount = g_GlobalVariables.size(); - int stringcount = num_strings_in_table(); - int NumMarks = w->MainBuffer->count_marks(); - int NumRefs = w->MainBuffer->count_references(); - print ("%1 / %2 strings written\n", stringcount, MAX_LIST_STRINGS); - print ("%1 / %2 global variables\n", globalcount, MAX_SCRIPT_VARIABLES); - print ("%1 / %2 bytecode marks\n", NumMarks, MAX_MARKS); // TODO: nuke - print ("%1 / %2 bytecode references\n", NumRefs, MAX_MARKS); // TODO: nuke - print ("%1 / %2 events\n", g_NumEvents, MAX_NUM_EVENTS); - print ("%1 state%s1\n", g_NumStates); - - w->write_to_file (outfile); - - // Clear out the junk - delete r; - delete w; - - // Done! - exit (0); -} - -// ============================================================================ -// Utility functions - -// ============================================================================ -// Does the given file exist? -bool fexists (string path) -{ - if (FILE* test = fopen (path, "r")) - { - fclose (test); - return true; - } - - return false; -} - -// ============================================================================ -// Generic error -void error (const char* text, ...) -{ - va_list va; - va_start (va, text); - fprintf (stderr, "error: "); - vfprintf (stderr, text, va); - va_end (va); - exit (1); -} - -// ============================================================================ -// Mutates given filename to an object filename -string ObjectFileName (string s) -{ - // Locate the extension and chop it out - int extdot = s.last ("."); - - if (extdot >= s.len() - 4) - s -= (s.len() - extdot); - - s += ".o"; - return s; -} - -// ============================================================================ -// Is the given argument a reserved keyword? -bool IsKeyword (string s) -{ - for (int u = 0; u < NumKeywords(); u++) - if (s.to_uppercase() == g_Keywords[u].to_uppercase()) - return true; - - return false; -} - -int NumKeywords() -{ - return sizeof (g_Keywords) / sizeof (const char*); -} - -// ============================================================================ -type_e GetTypeByName (string t) -{ - t = t.to_lowercase(); - return (t == "int") ? TYPE_INT : - (t == "str") ? TYPE_STRING : - (t == "void") ? TYPE_VOID : - (t == "bool") ? TYPE_BOOL : - TYPE_UNKNOWN; -} - - -// ============================================================================ -// Inverse operation - type name by value -string GetTypeName (type_e type) -{ - switch (type) - { - case TYPE_INT: return "int"; break; - - case TYPE_STRING: return "str"; break; - - case TYPE_VOID: return "void"; break; - - case TYPE_BOOL: return "bool"; break; - - case TYPE_UNKNOWN: return "???"; break; - } - - return ""; -}
--- a/src/main.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/main.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,10 +39,10 @@ #include <cstdarg> #include <cstdint> #include "types.h" -#include "bots.h" +#include "containers.h" #include "str.h" -#include "containers.h" #include "format.h" +#include "botstuff.h" #include "tokens.h" // Application name and version @@ -52,11 +52,12 @@ // On Windows, files are case-insensitive #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__) - #define FILE_CASEINSENSITIVE +#define FILE_CASEINSENSITIVE #endif // Parser mode: where is the parser at? -enum parsermode_e { +enum parsermode_e +{ MODE_TOPLEVEL, // at top level MODE_EVENT, // inside event definition MODE_MAINLOOP, // inside mainloop @@ -64,7 +65,8 @@ MODE_ONEXIT, // inside onexit }; -enum type_e { +enum type_e +{ TYPE_UNKNOWN = 0, TYPE_VOID, TYPE_INT, @@ -99,52 +101,38 @@ extern parsermode_e g_CurMode; extern string g_CurState; -#define neurosphere if (g_Neurosphere) -#define twice for (int repeat_token = 0; repeat_token < 2; repeat_token++) - #ifndef __GNUC__ #define __attribute__(X) #endif #define deprecated __attribute__ ((deprecated)) -// Power function -template<class T> T pow (T a, int b) { - if (!b) - return 1; - - T r = a; - while (b > 1) { - b--; - r = r * a; - } - - return r; -} - // Byte datatype typedef int32_t word; typedef unsigned char byte; // Keywords -extern const char** g_Keywords; +extern const string_list g_Keywords; bool IsKeyword (string s); int NumKeywords (); // Script mark and reference -struct ScriptMark { +struct ScriptMark +{ string name; size_t pos; }; -struct ScriptMarkReference { +struct ScriptMarkReference +{ int num; size_t pos; }; // ==================================================================== // Generic union -template <class T> union generic_union { +template <class T> union generic_union +{ T as_t; byte as_bytes[sizeof (T)]; char as_chars[sizeof (T)]; @@ -156,10 +144,11 @@ // ==================================================================== // Finds a byte in the given value. -template <class T> inline unsigned char GetByteIndex (T a, int b) { - union_t<T> uni; - uni.val = a; - return uni.b[b]; +template <class T> inline unsigned char GetByteIndex (T a, int b) +{ + generic_union<T> uni; + uni.as_t = a; + return uni.as_bytes[b]; } #endif // BOTC_COMMON_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/object_writer.cc Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,191 @@ +/* + Copyright (c) 2012-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 "main.h" +#include "object_writer.h" +#include "data_buffer.h" +#include "stringtable.h" + +extern bool g_GotMainLoop; + +object_writer::object_writer() +{ + MainBuffer = new data_buffer; + MainLoopBuffer = new data_buffer; + OnEnterBuffer = new data_buffer; + SwitchBuffer = null; // created on demand + numWrittenBytes = 0; + numWrittenReferences = 0; +} + +void object_writer::write_string (string a) +{ + write (a.length()); + + for (int i = 0; i < a.length(); i++) + write (a[i]); +} + +void object_writer::write_buffer (data_buffer* buf) +{ + get_current_buffer()->merge (buf); +} + +void object_writer::write_member_buffers() +{ + // If there was no mainloop defined, write a dummy one now. + if (!g_GotMainLoop) + { + MainLoopBuffer->write (dh_main_loop); + MainLoopBuffer->write (dh_end_main_loop); + } + + // Write the onenter and mainloop buffers, IN THAT ORDER + for (int i = 0; i < 2; i++) + { + data_buffer** buf = (!i) ? &OnEnterBuffer : &MainLoopBuffer; + write_buffer (*buf); + + // Clear the buffer afterwards for potential next state + *buf = new data_buffer; + } + + // Next state definitely has no mainloop yet + g_GotMainLoop = false; +} + +// Write string table +void object_writer::write_string_table() +{ + int stringcount = num_strings_in_table(); + + if (!stringcount) + return; + + // Write header + write (dh_string_list); + write (stringcount); + + // Write all strings + for (int a = 0; a < stringcount; a++) + write_string (get_string_table()[a]); +} + +// Write main buffer to file +void object_writer::write_to_file (string filepath) +{ + fp = fopen (filepath, "w"); + CHECK_FILE (fp, filepath, "writing"); + + // First, resolve references + numWrittenReferences = 0; + + for (int u = 0; u < MAX_MARKS; u++) + { + ScriptMarkReference* ref = MainBuffer->refs[u]; + + if (!ref) + continue; + + // Substitute the placeholder with the mark position + generic_union<word> uni; + uni.as_word = static_cast<word> (MainBuffer->marks[ref->num]->pos); + + for (int v = 0; v < (int) sizeof (word); v++) + memset (MainBuffer->buffer + ref->pos + v, uni.as_bytes[v], 1); + + /* + printf ("reference %u at %d resolved to %u at %d\n", + u, ref->pos, ref->num, MainBuffer->marks[ref->num]->pos); + */ + numWrittenReferences++; + } + + // Then, dump the main buffer to the file + for (int x = 0; x < MainBuffer->writesize; x++) + write_data_to_file<byte> (*(MainBuffer->buffer + x)); + + print ("-- %1 byte%s1 written to %2\n", numWrittenBytes, filepath); + fclose (fp); +} + +data_buffer* object_writer::get_current_buffer() +{ + return SwitchBuffer ? SwitchBuffer : + (g_CurMode == MODE_MAINLOOP) ? MainLoopBuffer : + (g_CurMode == MODE_ONENTER) ? OnEnterBuffer : + MainBuffer; +} + +ScriptMark* g_ScriptMark = null; + +// Adds a mark +int object_writer::add_mark (string name) +{ + return get_current_buffer()->add_mark (name); +} + +// Adds a reference +int object_writer::add_reference (int mark) +{ + data_buffer* b = get_current_buffer(); + return b->add_reference (mark); +} + +// Finds a mark +int object_writer::find_byte_mark (string name) +{ + data_buffer* b = get_current_buffer(); + + for (int u = 0; u < MAX_MARKS; u++) + { + if (b->marks[u] && b->marks[u]->name.to_uppercase() == name.to_uppercase()) + return u; + } + + return MAX_MARKS; +} + +// Moves a mark to the current position +void object_writer::move_mark (int mark) +{ + get_current_buffer()->move_mark (mark); +} + +// Deletes a mark +void object_writer::delete_mark (int mark) +{ + get_current_buffer()->delete_mark (mark); +} + +void object_writer::offset_mark (int mark, int offset) +{ + get_current_buffer()->offset_mark (mark, offset); +}
--- a/src/object_writer.cxx Mon Jan 13 00:15:38 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,193 +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 "main.h" -#include "str.h" -#include "object_writer.h" -#include "data_buffer.h" -#include "stringtable.h" -#include "bots.h" - -extern bool g_GotMainLoop; - -object_writer::ObjectWriter (string path) -{ - MainBuffer = new data_buffer; - MainLoopBuffer = new data_buffer; - OnEnterBuffer = new data_buffer; - SwitchBuffer = null; // created on demand - numWrittenBytes = 0; - numWrittenReferences = 0; -} - -void object_writer::write_string (string a) -{ - write (a.length()); - - for (int i = 0; i < a.length(); i++) - write (a[i]); -} - -void object_writer::write_buffer (data_buffer* buf) -{ - get_current_buffer()->merge (buf); -} - -void object_writer::write_member_buffers() -{ - // If there was no mainloop defined, write a dummy one now. - if (!g_GotMainLoop) - { - MainLoopBuffer->write (DH_MAINLOOP); - MainLoopBuffer->write (DH_ENDMAINLOOP); - } - - // Write the onenter and mainloop buffers, IN THAT ORDER - for (int i = 0; i < 2; i++) - { - data_buffer** buf = (!i) ? &OnEnterBuffer : &MainLoopBuffer; - write_buffer (*buf); - - // Clear the buffer afterwards for potential next state - *buf = new data_buffer; - } - - // Next state definitely has no mainloop yet - g_GotMainLoop = false; -} - -// Write string table -void object_writer::write_string_table() -{ - int stringcount = num_strings_in_table(); - - if (!stringcount) - return; - - // Write header - write (DH_STRINGLIST); - write (stringcount); - - // Write all strings - for (int a = 0; a < stringcount; a++) - write_string (get_string_table() [a]); -} - -// Write main buffer to file -void object_writer::write_to_file (string filepath) -{ - fp = fopen (filepath, "w"); - CHECK_FILE (fp, filepath, "writing"); - - // First, resolve references - numWrittenReferences = 0; - - for (int u = 0; u < MAX_MARKS; u++) - { - ScriptMarkReference* ref = MainBuffer->refs[u]; - - if (!ref) - continue; - - // Substitute the placeholder with the mark position - generic_union<word> uni; - uni.as_word = static_cast<word> (MainBuffer->marks[ref->num]->pos); - - for (int v = 0; v < sizeof (word); v++) - memset (MainBuffer->buffer + ref->pos + v, uni.as_bytes[v], 1); - - /* - printf ("reference %u at %d resolved to %u at %d\n", - u, ref->pos, ref->num, MainBuffer->marks[ref->num]->pos); - */ - numWrittenReferences++; - } - - // Then, dump the main buffer to the file - for (int x = 0; x < MainBuffer->writesize; x++) - write_data_to_file<byte> (*(MainBuffer->buffer + x)); - - print ("-- %1 byte%s1 written to %2\n", numWrittenBytes, filepath); - fclose (fp); -} - -data_buffer* object_writer::get_current_buffer() -{ - return SwitchBuffer ? SwitchBuffer : - (g_CurMode == MODE_MAINLOOP) ? MainLoopBuffer : - (g_CurMode == MODE_ONENTER) ? OnEnterBuffer : - MainBuffer; -} - -ScriptMark* g_ScriptMark = null; - -// Adds a mark -int object_writer::add_mark (string name) -{ - return get_current_buffer()->add_mark (name); -} - -// Adds a reference -int object_writer::add_reference (int mark) -{ - data_buffer* b = get_current_buffer(); - return b->add_reference (mark); -} - -// Finds a mark -int object_writer::find_byte_mark (string name) -{ - data_buffer* b = get_current_buffer(); - - for (int u = 0; u < MAX_MARKS; u++) - { - if (b->marks[u] && b->marks[u]->name.to_uppercase() == name.to_uppercase()) - return u; - } - - return MAX_MARKS; -} - -// Moves a mark to the current position -void object_writer::move_mark (int mark) -{ - get_current_buffer()->move_mark (mark); -} - -// Deletes a mark -void object_writer::delete_mark (int mark) -{ - get_current_buffer()->delete_mark (mark); -} - -void object_writer::offset_mark (int mark, int offset) -{ - get_current_buffer()->offset_mark (mark, offset); -}
--- a/src/object_writer.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/object_writer.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/parser.cc Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,1450 @@ +/* + Copyright (c) 2012-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 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; +static string* g_undefined_labels[MAX_MARKS]; // TODO: make a list +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_string().chars()); +} + +// ============================================================================ +// +void botscript_parser::check_not_toplevel() +{ + if (g_CurMode == MODE_TOPLEVEL) + error ("%1-statements must not be defined at top level!", token_string().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) +{ + m_writer = w; + + // Lex and preprocess the file + m_lx->process_file (file_name); + + // Zero the entire block stack first + // TODO: this shouldn't be necessary + for (int i = 0; i < MAX_SCOPE; i++) + ZERO (scopestack[i]); + + for (int i = 0; i < MAX_MARKS; i++) + g_undefined_labels[i] = null; + + while (m_lx->get_next()) + { + // Check if else is potentically valid + if (token_is (tk_else) && !g_CanElse) + error ("else without preceding if"); + + if (!token_is (tk_else)) + g_CanElse = false; + + switch (m_lx->get_token()->type) + { + case tk_state: + parse_state_block(); + break; + + case tk_event: + parse_event_block(); + break; + + case tk_mainloop: + parse_mainloop(); + break; + + case tk_onenter: + case tk_onexit: + parse_on_enter_exit(); + break; + + case tk_int: + case tk_str: + case tk_void: + parse_variable_declaration(); + break; + + case tk_goto: + parse_goto(); + break; + + case tk_if: + parse_if(); + break; + + case tk_else: + parse_else(); + break; + + case tk_while: + parse_while_block(); + break; + + case tk_for: + parse_for_block(); + break; + + case tk_do: + parse_do_block(); + break; + + case tk_switch: + parse_switch_block(); + break; + + case tk_case: + parse_switch_case(); + break; + + case tk_default: + parse_switch_default(); + break; + + case tk_break: + parse_break(); + break; + + case tk_continue: + parse_continue(); + break; + + case tk_brace_end: + parse_block_end(); + break; + + case tk_const: + parse_const(); + break; + + default: + { + // Check for labels + lexer::token next; + + if (token_is (tk_symbol) && + m_lx->peek_next (&next) && + next.type == tk_colon) + { + parse_label(); + break; + } + + // Check if it's a command + command_info* comm = find_command_by_name (token_string()); + + if (comm) + { + m_writer->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 `%1`", token_string()); + + m_writer->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_undefined_labels[i]) + error ("label `%s` is referenced via `goto` but isn't defined\n", g_undefined_labels[i]->chars()); + + // Dump the last state's onenter and mainloop + m_writer->write_member_buffers(); + + // String table + m_writer->write_string_table(); +} + +// ============================================================================ +// +void botscript_parser::parse_state_block() +{ + check_toplevel(); + m_lx->must_get_next (tk_string); + string statename = token_string(); + + // State name must be a word. + if (statename.first (" ") != -1) + error ("state name must be a single word, got `%1`", statename); + + // stateSpawn is special - it *must* be defined. If we + // encountered it, then mark down that we have it. + if (-statename == "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) + m_writer->write_member_buffers(); + + m_writer->write (dh_state_name); + m_writer->write_string (statename); + m_writer->write (dh_state_index); + m_writer->write (g_NumStates); + + g_NumStates++; + g_CurState = statename; + g_GotMainLoop = false; +} + +// ============================================================================ +// +void botscript_parser::parse_event_block() +{ + check_toplevel(); + 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; + m_writer->write (dh_event); + m_writer->write (e->number); + g_NumEvents++; +} + +// ============================================================================ +// +void botscript_parser::parse_mainloop() +{ + check_toplevel(); + m_lx->must_get_next (tk_brace_start); + + // Mode must be set before dataheader is written here! + g_CurMode = MODE_MAINLOOP; + m_writer->write (dh_main_loop); +} + +// ============================================================================ +// +void botscript_parser::parse_on_enter_exit() +{ + check_toplevel(); + bool onenter = (token_is (tk_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; + m_writer->write (onenter ? dh_on_enter : dh_on_exit); +} + +// ============================================================================ +// +void botscript_parser::parse_variable_declaration() +{ + // For now, only globals are supported + if (g_CurMode != MODE_TOPLEVEL || g_CurState.is_empty() == false) + 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(); + string varname = token_string(); + + // Var name must not be a number + if (varname.is_numeric()) + error ("variable name must not be a number"); + + script_variable* var = declare_global_variable (type, varname); + (void) var; + m_lx->must_get_next (tk_semicolon); +} + +// ============================================================================ +// +void botscript_parser::parse_goto() +{ + check_not_toplevel(); + + // Get the name of the label + m_lx->must_get_next(); + + // Find the mark this goto statement points to + string target = token_string(); + int m = m_writer->find_byte_mark (target); + + // If not set, define it + if (m == MAX_MARKS) + { + m = m_writer->add_mark (target); + g_undefined_labels[m] = new string (target); + } + + // Add a reference to the mark. + m_writer->write (dh_goto); + m_writer->add_reference (m); + m_lx->must_get_next (tk_semicolon); + continue; +} + +// ============================================================================ +// +void botscript_parser::parse_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); + m_writer->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 = m_writer->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. + m_writer->write (dh_if_not_goto); + m_writer->add_reference (marknum); + + // Store it + SCOPE (0).mark1 = marknum; + SCOPE (0).type = e_if_scope; +} + +// ============================================================================ +// +void botscript_parser::parse_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 = m_writer->add_mark (""); + + // Instruction to jump to the end after if block is complete + m_writer->write (dh_goto); + m_writer->add_reference (SCOPE (0).mark2); + + // Move the ifnot mark here and set type to else + m_writer->move_mark (SCOPE (0).mark1); + SCOPE (0).type = e_else_scope; +} + +// ============================================================================ +// +void botscript_parser::parse_while_block() +{ + 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 = m_writer->add_mark (""); // start + int mark2 = m_writer->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 + m_writer->write_buffer (expr); + + // Instruction to go to the end if it fails + m_writer->write (dh_if_not_goto); + m_writer->add_reference (mark2); + + // Store the needed stuff + SCOPE (0).mark1 = mark1; + SCOPE (0).mark2 = mark2; + SCOPE (0).type = e_while_scope; +} + +// ============================================================================ +// +void botscript_parser::parse_for_block() +{ + 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 + m_writer->write_buffer (init); + + // Init two marks + int mark1 = m_writer->add_mark (""); + int mark2 = m_writer->add_mark (""); + + // Add the condition + m_writer->write_buffer (cond); + m_writer->write (dh_if_not_goto); + m_writer->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; +} + +// ============================================================================ +// +void botscript_parser::parse_do_block() +{ + check_not_toplevel(); + push_scope(); + m_lx->must_get_next (tk_brace_start); + SCOPE (0).mark1 = m_writer->add_mark (""); + SCOPE (0).type = e_do_scope; +} + +// ============================================================================ +// +void botscript_parser::parse_switch_block() +{ + // 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(); + m_writer->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 = m_writer->add_mark (""); // end mark + SCOPE (0).buffer1 = null; // default header +} + +// ============================================================================ +// +void botscript_parser::parse_switch_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. + m_writer->SwitchBuffer = null; + m_writer->write (dh_case_goto); + m_writer->write (num); + add_switch_case (m_writer, null); + SCOPE (0).casenumbers[SCOPE (0).casecursor] = num; +} + +// ============================================================================ +// +void botscript_parser::parse_switch_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 (m_writer, b); +} + +// ============================================================================ +// +void botscript_parser::parse_break() +{ + if (!g_ScopeCursor) + error ("unexpected `break`"); + + m_writer->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: + { + m_writer->add_reference (SCOPE (0).mark1); + } break; + + case e_for_scope: + case e_while_scope: + { + m_writer->add_reference (SCOPE (0).mark2); + } break; + + default: + { + error ("unexpected `break`"); + } break; + } + + m_lx->must_get_next (tk_semicolon); +} + +// ============================================================================ +// +void botscript_parser::parse_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: + { + m_writer->write (dh_goto); + m_writer->add_reference (scopestack[curs].mark1); + found = true; + } break; + + default: + break; + } + } + + // No loop blocks + if (!found) + error ("`continue`-statement not inside a loop"); +} + +// ============================================================================ +// +void botscript_parser::parse_block_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. + m_writer->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 + m_writer->move_mark (SCOPE (0).mark2); + break; + + case e_for_scope: + // write the incrementor at the end of the loop block + m_writer->write_buffer (SCOPE (0).buffer1); + + // fall-thru + case e_while_scope: + // write down the instruction to go back to the start of the loop + m_writer->write (dh_goto); + m_writer->add_reference (SCOPE (0).mark1); + + // Move the closing mark here since we're at the end of the while loop + m_writer->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. + m_writer->write_buffer (expr); + m_writer->write (dh_if_goto); + m_writer->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) + m_writer->SwitchBuffer = SCOPE (1).casebuffers[SCOPE (1).casecursor]; + else + m_writer->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) + m_writer->write_buffer (SCOPE (0).buffer1); + else + { + m_writer->write (dh_drop); + m_writer->write (dh_goto); + m_writer->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; + + m_writer->move_mark (SCOPE (0).casemarks[u]); + m_writer->write_buffer (SCOPE (0).casebuffers[u]); + } + + // Move the closing mark here + m_writer->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. + m_writer->write (dataheader); + g_CurMode = MODE_TOPLEVEL; + m_lx->get_next (tk_semicolon); +} + +// ============================================================================ +// +void botscript_parser::parse_const() +{ + constant_info info; + + // Get the type + m_lx->must_get_next(); + string typestring = token_string(); + info.type = GetTypeByName (typestring); + + if (info.type == TYPE_UNKNOWN || info.type == TYPE_VOID) + error ("unknown type `%1` for constant", typestring); + + m_lx->must_get_next(); + info.name = token_string(); + + m_lx->must_get_next (tk_assign); + + switch (info.type) + { + case TYPE_BOOL: + case TYPE_INT: + { + m_lx->must_get_next (tk_number); + } break; + + case TYPE_STRING: + { + m_lx->must_get_next (tk_string); + } break; + + case TYPE_UNKNOWN: + case TYPE_VOID: + break; + } + + info.val = m_lx->get_token()->text; + g_ConstInfo << info; + + m_lx->must_get_next (tk_semicolon); +} + +// ============================================================================ +// +void botscript_parser::parse_label() +{ + check_not_toplevel(); + string label_name = token_string(); + + // want no conflicts.. + if (find_command_by_name (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_undefined_labels[i] && *g_undefined_labels[i] == label_name) + { + mark = i; + m_writer->move_mark (i); + + // No longer undefinde + delete g_undefined_labels[i]; + g_undefined_labels[i] = null; + } + } + + // Not found in unmarked lists, define it now + if (mark == -1) + m_writer->add_mark (label_name); + + m_lx->must_get_next (tk_colon); +} + +// ============================================================================ +// Parses a command call +data_buffer* botscript_parser::ParseCommand (command_info* 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 (token_is (tk_paren_end)) + { + if (curarg < comm->numargs) + error ("too few arguments passed to %s\n\tprototype: %s", + comm->name.chars(), get_command_signature (comm).chars()); + + break; + curarg++; + } + + if (curarg >= comm->maxargs) + error ("too many arguments passed to %s\n\tprototype: %s", + comm->name.chars(), get_command_signature (comm).chars()); + + r->merge (parse_expression (comm->args[curarg].type)); + 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_is (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->args[curarg].defvalue); + 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) (m_lx->peek_next_string (peek ? 1 : 0) == C) +int botscript_parser::parse_operator (bool peek) +{ + string oper; + + if (peek) + oper += m_lx->peek_next_string(); + else + oper += token_string(); + + 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_string(); + 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 = token_is (tk_exclamation_mark); + + if (negate) // Jump past the "!" + m_lx->skip(); + + // Handle strlen + /* if (token_string() == "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_string()); + + 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_is (tk_paren_start)) + { + // 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 (command_info* comm = find_command_by_name (token_string())) + { + 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_string())) + { + // 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_string()))) + { + // 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 `%1` (expected keyword, function or variable)", token_string()); + 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 = token_string().to_long(); + 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_string()); + 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_string())) // 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_string())) + 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 = m_writer->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 + m_writer->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] = m_writer->SwitchBuffer = new data_buffer; +} + +// ============================================================================ +// +constant_info* find_constant (const 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_type() == 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); +}
--- 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
--- a/src/parser.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/parser.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -81,7 +81,7 @@ struct operator_info { operator_e opercode; - DATAHEADERS_e dataheader; + e_data_header dataheader; e_token token; }; @@ -165,7 +165,7 @@ botscript_parser(); ~botscript_parser(); void parse_botscript (string file_name, object_writer* w); - data_buffer* ParseCommand (CommandDef* comm); + data_buffer* ParseCommand (command_info* comm); data_buffer* parse_expression (type_e reqtype); data_buffer* ParseAssignment (script_variable* var); int parse_operator (bool peek = false); @@ -181,9 +181,30 @@ string describe_position() const; private: - lexer* m_lx; + lexer* m_lx; + object_writer* m_writer; + void parse_state_block(); + void parse_event_block(); + void parse_mainloop(); + void parse_on_enter_exit(); + void parse_variable_declaration(); + void parse_goto(); + void parse_if(); + void parse_else(); + void parse_while_block(); + void parse_for_block(); + void parse_do_block(); + void parse_switch_block(); + void parse_switch_case(); + void parse_switch_default(); + void parse_break(); + void parse_continue(); + void parse_continue(); + void parse_block_end(); + void parse_const(); + void parse_label(); }; -constant_info* find_constant_by_name (string token); +constant_info* find_constant (const string& tok); #endif // BOTC_PARSER_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/str.cc Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,482 @@ +/* + 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 <cstring> +#include "main.h" +#include "str.h" + +// ============================================================================= +// +int string::compare (const string& other) const +{ + return m_string.compare (other.std_string()); +} + +// ============================================================================= +// +void string::trim (string::length_type n) +{ + if (n > 0) + m_string = substring (0, length() - n).std_string(); + else + m_string = substring (n, -1).std_string(); +} + +// ============================================================================= +// +string string::strip (list<char> unwanted) +{ + string copy (m_string); + + for (char c : unwanted) + for (int i = 0; i < copy.length(); ++i) + if (copy[i] == c) + copy.erase (i); + + /* + while(( pos = copy.first( c )) != -1 ) + copy.erase( pos ); + */ + + return copy; +} + +// ============================================================================= +// +string string::to_uppercase() const +{ + string newstr = m_string; + + for (char& c : newstr) + if (c >= 'a' && c <= 'z') + c -= 'a' - 'A'; + + return newstr; +} + +// ============================================================================= +// +string string::to_lowercase() const +{ + string newstr = m_string; + + for (char & c : newstr) + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + return newstr; +} + +// ============================================================================= +// +string_list string::split (char del) const +{ + string delimstr; + delimstr += del; + return split (delimstr); +} + +// ============================================================================= +// +string_list string::split (string del) const +{ + string_list res; + long a = 0; + + // Find all separators and store the text left to them. + for (;;) + { + long b = first (del, a); + + if (b == -1) + break; + + string sub = substring (a, b); + + if (sub.length() > 0) + res.push_back (substring (a, b)); + + a = b + strlen (del); + } + + // Add the string at the right of the last separator + if (a < (int) length()) + res.push_back (substring (a, length())); + + return res; +} + +// ============================================================================= +// +void string::replace (const char* a, const char* b) +{ + long pos; + + while ( (pos = first (a)) != -1) + m_string = m_string.replace (pos, strlen (a), b); +} + +// ============================================================================= +// +int string::count (const char needle) const +{ + int needles = 0; + + for (const char & c : m_string) + if (c == needle) + needles++; + + return needles; +} + +// ============================================================================= +// +string string::substring (long a, long b) const +{ + if (b == -1) + b = length(); + + if (b == a) + return ""; + + if (b < a) + { + // Swap the variables + int c = a; + a = b; + b = c; + } + + char* newstr = new char[b - a + 1]; + strncpy (newstr, m_string.c_str() + a, b - a); + newstr[b - a] = '\0'; + + string other (newstr); + delete[] newstr; + return other; +} + +// ============================================================================= +// +string::length_type string::posof (int n) const +{ + int count = 0; + + for (int i = 0; i < length(); ++i) + { + if (m_string[i] != ' ') + continue; + + if (++count < n) + continue; + + return i; + } + + return -1; +} + +// ============================================================================= +// +int string::first (const char* c, string::length_type a) const +{ + for (; a < length(); a++) + if (m_string[a] == c[0] && strncmp (m_string.c_str() + a, c, strlen (c)) == 0) + return a; + + return -1; +} + +// ============================================================================= +// +int string::last (const char* c, string::length_type a) const +{ + if (a == -1 || a >= length()) + a = length() - 1; + + for (; a > 0; a--) + if (m_string[a] == c[0] && strncmp (m_string.c_str() + a, c, strlen (c)) == 0) + return a; + + return -1; +} + +// ============================================================================= +// +void string::dump() const +{ + print ("`%1`:\n", chars()); + int i = 0; + + for (char u : m_string) + print ("\t%1. [%d2] `%3`\n", i++, u, string (u)); +} + +// ============================================================================= +// +long string::to_long (bool* ok, int base) const +{ + errno = 0; + char* endptr; + long i = strtol (m_string.c_str(), &endptr, base); + *ok = (errno == 0 && *endptr == '\0'); + return i; +} + +// ============================================================================= +// +float string::to_float (bool* ok) const +{ + errno = 0; + char* endptr; + float i = strtof (m_string.c_str(), &endptr); + *ok = (errno == 0 && *endptr == '\0'); + return i; +} + +// ============================================================================= +// +double string::to_double (bool* ok) const +{ + errno = 0; + char* endptr; + double i = strtod (m_string.c_str(), &endptr); + *ok = (errno == 0 && *endptr == '\0'); + return i; +} + +// ============================================================================= +// +bool operator== (const char* a, const string& b) +{ + return b == a; +} + +// ============================================================================= +// +string operator+ (const char* a, const string& b) +{ + return string (a) + b; +} + +// ============================================================================= +// +string string::operator+ (const string data) const +{ + string newString = *this; + newString += data; + return newString; +} + +// ============================================================================= +// +string string::operator+ (const char* data) const +{ + string newstr = *this; + newstr += data; + return newstr; +} + +// ============================================================================= +// +string& string::operator+= (const string data) +{ + append (data); + return *this; +} + +// ============================================================================= +// +string& string::operator+= (const char* data) +{ + append (data); + return *this; +} + +// ============================================================================= +// +bool string::is_numeric() const +{ + bool gotDot = false; + + for (const char & c : m_string) + { + // Allow leading hyphen for negatives + if (&c == &m_string[0] && c == '-') + continue; + + // Check for decimal point + if (!gotDot && c == '.') + { + gotDot = true; + continue; + } + + if (c >= '0' && c <= '9') + continue; // Digit + + // If the above cases didn't catch this character, it was + // illegal and this is therefore not a number. + return false; + } + + return true; +} + +// ============================================================================= +// +bool string::ends_with (const string& other) +{ + if (length() < other.length()) + return false; + + const int ofs = length() - other.length(); + return strncmp (chars() + ofs, other.chars(), other.length()) == 0; +} + +// ============================================================================= +// +bool string::starts_with (const string& other) +{ + if (length() < other.length()) + return false; + + return strncmp (chars(), other.chars(), other.length()) == 0; +} + +// ============================================================================= +// +void string::sprintf (const char* fmtstr, ...) +{ + char* buf; + int bufsize = 256; + va_list va; + va_start (va, fmtstr); + + do + buf = new char[bufsize]; + + while (vsnprintf (buf, bufsize, fmtstr, va) >= bufsize); + + va_end (va); + m_string = buf; + delete[] buf; +} + +// ============================================================================= +// +void string::prepend (string a) +{ + m_string = (a + m_string).std_string(); +} + +// ============================================================================= +// +string string_list::join (const string& delim) +{ + string result; + + for (const string & it : std_deque()) + { + if (!result.is_empty()) + result += delim; + + result += it; + } + + return result; +} + +// ============================================================================= +// +bool string::mask (const string& pattern) const +{ + // Elevate to uppercase for case-insensitive matching + string pattern_upper = pattern.to_uppercase(); + string this_upper = to_uppercase(); + const char* maskstring = pattern_upper.chars(); + const char* mptr = &maskstring[0]; + + for (const char* sptr = this_upper.chars(); *sptr != '\0'; sptr++) + { + if (*mptr == '?') + { + if (* (sptr + 1) == '\0') + { + // ? demands that there's a character here and there wasn't. + // Therefore, mask matching fails + return false; + } + } + + elif (*mptr == '*') + { + char end = * (++mptr); + + // If '*' is the final character of the message, all of the remaining + // string matches against the '*'. We don't need to bother checking + // the string any further. + if (end == '\0') + return true; + + // Skip to the end character + while (*sptr != end && *sptr != '\0') + sptr++; + + // String ended while the mask still had stuff + if (*sptr == '\0') + return false; + } + elif (*sptr != *mptr) + return false; + + mptr++; + } + + return true; +} + +// ============================================================================= +// +string string::from_number (int a) +{ + char buf[32]; + ::sprintf (buf, "%d", a); + return string (buf); +} + +// ============================================================================= +// +string string::from_number (long a) +{ + char buf[32]; + ::sprintf (buf, "%ld", a); + return string (buf); +} \ No newline at end of file
--- a/src/str.cxx Mon Jan 13 00:15:38 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,464 +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 <cstring> -#include "main.h" -#include "str.h" - -// ============================================================================= -// -int string::compare (const string& other) const -{ - return m_string.compare (other.std_string()); -} - -// ============================================================================= -// -void string::trim (string::length_type n) -{ - if (n > 0) - m_string = substring (0, length() - n).std_string(); - else - m_string = substring (n, -1).std_string(); -} - -// ============================================================================= -// -string string::strip (list<char> unwanted) -{ - string copy (m_string); - - for (char c : unwanted) - for (int i = 0; i < copy.length(); ++i) - if (copy[i] == c) - copy.erase (i); - - /* - while(( pos = copy.first( c )) != -1 ) - copy.erase( pos ); - */ - - return copy; -} - -// ============================================================================= -// -string string::to_uppercase() const -{ - string newstr = m_string; - - for (char& c : newstr) - if (c >= 'a' && c <= 'z') - c -= 'a' - 'A'; - - return newstr; -} - -// ============================================================================= -// -string string::to_lowercase() const -{ - string newstr = m_string; - - for (char & c : newstr) - if (c >= 'A' && c <= 'Z') - c += 'a' - 'A'; - - return newstr; -} - -// ============================================================================= -// -string_list string::split (char del) const -{ - string delimstr; - delimstr += del; - return split (delimstr); -} - -// ============================================================================= -// -string_list string::split (string del) const -{ - string_list res; - long a = 0; - - // Find all separators and store the text left to them. - for (;;) - { - long b = first (del, a); - - if (b == -1) - break; - - string sub = substring (a, b); - - if (sub.length() > 0) - res.push_back (substring (a, b)); - - a = b + strlen (del); - } - - // Add the string at the right of the last separator - if (a < (int) length()) - res.push_back (substring (a, length())); - - return res; -} - -// ============================================================================= -// -void string::replace (const char* a, const char* b) -{ - long pos; - - while ( (pos = first (a)) != -1) - m_string = m_string.replace (pos, strlen (a), b); -} - -// ============================================================================= -// -int string::count (const char needle) const -{ - int numNeedles = 0; - - for (const char & c : m_string) - if (c == needle) - numNeedles++; - - return numNeedles; -} - -// ============================================================================= -// -string string::substring (long a, long b) const -{ - if (b == -1) - b = length(); - - if (b == a) - return ""; - - if (b < a) - { - // Swap the variables - int c = a; - a = b; - b = c; - } - - char* newString = new char[b - a + 1]; - strncpy (newString, m_string.c_str() + a, b - a); - newString[b - a] = '\0'; - - string other (newString); - delete[] newString; - return other; -} - -// ============================================================================= -// -string::length_type string::posof (int n) const -{ - int count = 0; - - for (int i = 0; i < length(); ++i) - { - if (m_string[i] != ' ') - continue; - - if (++count < n) - continue; - - return i; - } - - return -1; -} - -// ============================================================================= -// -int string::first (const char* c, string::length_type a) const -{ - for (; a < length(); a++) - if (m_string[a] == c[0] && strncmp (m_string.c_str() + a, c, strlen (c)) == 0) - return a; - - return -1; -} - -// ============================================================================= -// -int string::last (const char* c, string::length_type a) const -{ - if (a == -1 || a >= length()) - a = length() - 1; - - for (; a > 0; a--) - if (m_string[a] == c[0] && strncmp (m_string.c_str() + a, c, strlen (c)) == 0) - return a; - - return -1; -} - -// ============================================================================= -// -void string::dump() const -{ - print ("`%1`:\n", chars()); - int i = 0; - - for (char u : m_string) - print ("\t%1. [%d2] `%3`\n", i++, u, string (u)); -} - -// ============================================================================= -// -long string::to_long (bool* ok, int base) const -{ - errno = 0; - char* endptr; - long i = strtol (m_string.c_str(), &endptr, base); - *ok = (errno == 0 && *endptr == '\0'); - return i; -} - -// ============================================================================= -// -float string::to_float (bool* ok) const -{ - errno = 0; - char* endptr; - float i = strtof (m_string.c_str(), &endptr); - *ok = (errno == 0 && *endptr == '\0'); - return i; -} - -// ============================================================================= -// -double string::to_double (bool* ok) const -{ - errno = 0; - char* endptr; - double i = strtod (m_string.c_str(), &endptr); - *ok = (errno == 0 && *endptr == '\0'); - return i; -} - -// ============================================================================= -// -bool operator== (const char* a, const string& b) -{ - return b == a; -} - -// ============================================================================= -// -string operator+ (const char* a, const string& b) -{ - return string (a) + b; -} - -// ============================================================================= -// -string string::operator+ (const string data) const -{ - string newString = *this; - newString += data; - return newString; -} - -// ============================================================================= -// -string string::operator+ (const char* data) const -{ - string newString = *this; - newString += data; - return newString; -} - -// ============================================================================= -// -string& string::operator+= (const string data) -{ - append (data); - return *this; -} - -// ============================================================================= -// -string& string::operator+= (const char* data) -{ - append (data); - return *this; -} - -// ============================================================================= -// -bool string::is_numeric() const -{ - bool gotDot = false; - - for (const char & c : m_string) - { - // Allow leading hyphen for negatives - if (&c == &m_string[0] && c == '-') - continue; - - // Check for decimal point - if (!gotDot && c == '.') - { - gotDot = true; - continue; - } - - if (c >= '0' && c <= '9') - continue; // Digit - - // If the above cases didn't catch this character, it was - // illegal and this is therefore not a number. - return false; - } - - return true; -} - -// ============================================================================= -// -bool string::ends_with (const string& other) -{ - if (length() < other.length()) - return false; - - const int ofs = length() - other.length(); - return strncmp (chars() + ofs, other.chars(), other.length()) == 0; -} - -// ============================================================================= -// -bool string::starts_with (const string& other) -{ - if (length() < other.length()) - return false; - - return strncmp (chars(), other.chars(), other.length()) == 0; -} - -// ============================================================================= -// -void string::sprintf (const char* fmtstr, ...) -{ - char* buf; - int bufsize = 256; - va_list va; - va_start (va, fmtstr); - - do - buf = new char[bufsize]; - - while (vsnprintf (buf, bufsize, fmtstr, va) >= bufsize); - - va_end (va); - m_string = buf; - delete[] buf; -} - -// ============================================================================= -// -void string::prepend (string a) -{ - m_string = (a + m_string).std_string(); -} - -// ============================================================================= -// -string string_list::join (const string& delim) -{ - string result; - - for (const string & it : std_deque()) - { - if (!result.is_empty()) - result += delim; - - result += it; - } - - return result; -} - -// ============================================================================= -// -bool string::mask (const string& pattern) const -{ - // Elevate to uppercase for case-insensitive matching - string pattern_upper = pattern.to_uppercase(); - string this_upper = to_uppercase(); - const char* maskstring = pattern_upper.chars(); - const char* mptr = &maskstring[0]; - - for (const char* sptr = this_upper.chars(); *sptr != '\0'; sptr++) - { - if (*mptr == '?') - { - if (* (sptr + 1) == '\0') - { - // ? demands that there's a character here and there wasn't. - // Therefore, mask matching fails - return false; - } - } - - elif (*mptr == '*') - { - char end = * (++mptr); - - // If '*' is the final character of the message, all of the remaining - // string matches against the '*'. We don't need to bother checking - // the string any further. - if (end == '\0') - return true; - - // Skip to the end character - while (*sptr != end && *sptr != '\0') - sptr++; - - // String ended while the mask still had stuff - if (*sptr == '\0') - return false; - } - elif (*sptr != *mptr) - return false; - - mptr++; - } - - return true; -}
--- a/src/str.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/str.h Mon Jan 13 23:44:15 2014 +0200 @@ -51,7 +51,7 @@ string() {} - string (char a) + explicit string (char a) { m_string = &a; } @@ -95,6 +95,9 @@ string& operator+= (const string data); string& operator+= (const char* data); + static string from_number (int a); + static string from_number (long a); + inline bool is_empty() const { return m_string[0] == '\0';
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stringtable.cc Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,81 @@ +/* + Copyright (c) 2012-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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "stringtable.h" + +static string_list g_string_table; + +// ============================================================================ +// +const string_list& get_string_table() +{ + return g_string_table; +} + +// ============================================================================ +// Potentially adds a string to the table and returns the index of it. +// +int get_string_table_index (const string& a) +{ + // Must not be too long. + if (a.length() >= g_max_string_length) + error ("string `%1` too long (%2 characters, max is %3)\n", + a, a.length(), g_max_string_length); + + // Find a free slot in the table. + int idx; + + for (idx = 0; idx < g_string_table.size(); idx++) + { + // String is already in the table, thus return it. + if (g_string_table[idx] == a) + return idx; + } + + // Check if the table is already full + if (g_string_table.size() == g_max_stringlist_size - 1) + error ("too many strings!\n"); + + // Now, dump the string into the slot + g_string_table[idx] = a; + + return idx; +} + +// ============================================================================ +// Counts the amount of strings in the table. +// +int num_strings_in_table() +{ + return g_string_table.size(); +}
--- a/src/stringtable.cxx Mon Jan 13 00:15:38 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +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 <stdio.h> -#include <stdlib.h> -#include <string.h> -#include "main.h" -#include "bots.h" -#include "stringtable.h" - -static string_list g_string_table; - -// ============================================================================ -// -const string_list& get_string_table() -{ - return g_string_table; -} - -// ============================================================================ -// Potentially adds a string to the table and returns the index of it. -// -int get_string_table_index (const string& a) -{ - // Must not be too long. - if (strlen (a) >= MAX_STRING_LENGTH) - error ("string `%s` too long (%d characters, max is %d)\n", - a.c_str(), a.length(), MAX_STRING_LENGTH); - - // Find a free slot in the table. - int idx; - - for (idx = 0; idx < g_string_table.size(); idx++) - { - // String is already in the table, thus return it. - if (g_string_table[idx] == a) - return idx; - } - - // Check if the table is already full - if (g_string_table.size() == MAX_LIST_STRINGS - 1) - error ("too many strings!\n"); - - // Now, dump the string into the slot - g_string_table[idx] = a; - - return idx; -} - -// ============================================================================ -// Counts the amount of strings in the table. -// -int num_strings_in_table() -{ - return g_string_table.size(); -}
--- a/src/stringtable.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/stringtable.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,9 +32,8 @@ #define BOTC_STRINGTABLE_H #include "main.h" -#include "bots.h" -int get_string_table_index(const string& a); +int get_string_table_index (const string& a); const string_list& get_string_table(); int num_strings_in_table();
--- a/src/tokens.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/tokens.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,6 +31,8 @@ #ifndef TOKENS_H #define TOKENS_H +#include <climits> + // ======================================================= enum e_token {
--- a/src/types.h Mon Jan 13 00:15:38 2014 +0200 +++ b/src/types.h Mon Jan 13 23:44:15 2014 +0200 @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2014, Santeri Piippo + Copyright (c) 2012-2014, Santeri Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -33,30 +33,12 @@ #include <cstdlib> #include <stdexcept> -#include "str.h" static const std::nullptr_t null = nullptr; -class script_error : public std::exception +template<class T> inline T abs (T a) { - public: - script_error (const string& msg) : m_msg (msg) {} - - inline const char* what() const throw() - { - return m_msg.c_str(); - } - - private: - string m_msg; -}; - -#ifndef IN_IDE_PARSER -# define error(...) throw script_error (format (__VA_ARGS__)) -#else -// kdevelop stuff -using FILE = void; // blargh -void error (void, ...); -#endif + return (a >= 0) ? a : -a; +} #endif // BOTC_TYPES_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/variables.cc Mon Jan 13 23:44:15 2014 +0200 @@ -0,0 +1,89 @@ +/* + Copyright (c) 2012-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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "object_writer.h" +#include "stringtable.h" +#include "variables.h" +#include "parser.h" + +list<script_variable> g_GlobalVariables; +list<script_variable> g_LocalVariables; + +// ============================================================================ +// Tries to declare a new global-scope variable. Returns pointer +// to new global variable, null if declaration failed. +script_variable* declare_global_variable (type_e type, string name) +{ + // Unfortunately the VM does not support string variables so yeah. + if (type == TYPE_STRING) + error ("variables cannot be string\n"); + + // Check that the variable is valid + if (find_command_by_name (name)) + error ("name of variable-to-be `%s` conflicts with that of a command", name.chars()); + + if (IsKeyword (name)) + error ("name of variable-to-be `%s` is a keyword", name.chars()); + + if (g_GlobalVariables.size() >= g_max_global_vars) + error ("too many global variables!"); + + for (int i = 0; i < g_GlobalVariables.size(); i++) + if (g_GlobalVariables[i].name == name) + error ("attempted redeclaration of global variable `%s`", name.chars()); + + script_variable g; + g.index = g_GlobalVariables.size(); + g.name = name; + g.statename = ""; + g.value = 0; + g.type = type; + + g_GlobalVariables << g; + return &g_GlobalVariables[g.index]; +} + +// ============================================================================ +// Find a global variable by name +script_variable* find_global_variable (string name) +{ + for (int i = 0; i < g_GlobalVariables.size(); i++) + { + script_variable* g = &g_GlobalVariables[i]; + + if (g->name == name) + return g; + } + + return null; +} \ No newline at end of file
--- a/src/variables.cxx Mon Jan 13 00:15:38 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +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 <stdio.h> -#include <stdlib.h> -#include <string.h> -#include "main.h" -#include "containers.h" -#include "bots.h" -#include "object_writer.h" -#include "stringtable.h" -#include "variables.h" -#include "parser.h" - -list<script_variable> g_GlobalVariables; -list<script_variable> g_LocalVariables; - -// ============================================================================ -// Tries to declare a new global-scope variable. Returns pointer -// to new global variable, null if declaration failed. -script_variable* declare_global_variable (type_e type, string name) -{ - // Unfortunately the VM does not support string variables so yeah. - if (type == TYPE_STRING) - error ("variables cannot be string\n"); - - // Check that the variable is valid - if (FindCommand (name)) - error ("name of variable-to-be `%s` conflicts with that of a command", name.chars()); - - if (IsKeyword (name)) - error ("name of variable-to-be `%s` is a keyword", name.chars()); - - if (g_GlobalVariables.size() >= g_max_global_vars) - error ("too many global variables!"); - - for (int i = 0; i < g_GlobalVariables.size(); i++) - if (g_GlobalVariables[i].name == name) - error ("attempted redeclaration of global variable `%s`", name.chars()); - - script_variable g; - g.index = g_GlobalVariables.size(); - g.name = name; - g.statename = ""; - g.value = 0; - g.type = type; - - g_GlobalVariables << g; - return &g_GlobalVariables[g.index]; -} - -// ============================================================================ -// Find a global variable by name -script_variable* find_global_variable (string name) -{ - for (int i = 0; i < g_GlobalVariables.size(); i++) - { - script_variable* g = &g_GlobalVariables[i]; - - if (g->name == name) - return g; - } - - return null; -} \ No newline at end of file