src/Parser.cc

changeset 119
bdf8d46c145f
parent 118
e3361cf7cbf4
child 120
5ea0faefa82a
--- a/src/Parser.cc	Sun Mar 30 21:35:06 2014 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1417 +0,0 @@
-/*
-	Copyright 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:
-
-	1. Redistributions of source code must retain the above copyright
-	   notice, this list of conditions and the following disclaimer.
-	2. Redistributions in binary form must reproduce the above copyright
-	   notice, this list of conditions and the following disclaimer in the
-	   documentation and/or other materials provided with the distribution.
-	3. The name of the author may not be used to endorse or promote products
-	   derived from this software without specific prior written permission.
-
-	THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
-	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-	OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-	IN NO EVENT SHALL THE AUTHOR 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 "Parser.h"
-#include "Events.h"
-#include "Commands.h"
-#include "StringTable.h"
-#include "Containers.h"
-#include "Lexer.h"
-#include "DataBuffer.h"
-#include "Expression.h"
-
-#define SCOPE(n) (m_scopeStack[m_scopeCursor - n])
-
-static const StringList g_validZandronumVersions = {"1.2", "1.3", "2.0"};
-
-// ============================================================================
-//
-BotscriptParser::BotscriptParser() :
-	m_isReadOnly (false),
-	m_mainBuffer (new DataBuffer),
-	m_onenterBuffer (new DataBuffer),
-	m_mainLoopBuffer (new DataBuffer),
-	m_lexer (new Lexer),
-	m_numStates (0),
-	m_numEvents (0),
-	m_currentMode (PARSERMODE_TopLevel),
-	m_isStateSpawnDefined (false),
-	m_gotMainLoop (false),
-	m_scopeCursor (-1),
-	m_isElseAllowed (false),
-	m_highestGlobalVarIndex (0),
-	m_highestStateVarIndex (0),
-	m_zandronumVersion (10200), // 1.2
-	m_defaultZandronumVersion (true) {}
-
-// ============================================================================
-//
-BotscriptParser::~BotscriptParser()
-{
-	delete m_lexer;
-}
-
-// ============================================================================
-//
-void BotscriptParser::checkToplevel()
-{
-	if (m_currentMode != PARSERMODE_TopLevel)
-		error ("%1-statements may only be defined at top level!", getTokenString());
-}
-
-// ============================================================================
-//
-void BotscriptParser::checkNotToplevel()
-{
-	if (m_currentMode == PARSERMODE_TopLevel)
-		error ("%1-statements must not be defined at top level!", getTokenString());
-}
-
-// ============================================================================
-//
-// Main compiler 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 BotscriptParser::parseBotscript (String fileName)
-{
-	// Lex and preprocess the file
-	m_lexer->processFile (fileName);
-	pushScope();
-
-	while (m_lexer->next())
-	{
-		// Check if else is potentically valid
-		if (tokenIs (TK_Else) && m_isElseAllowed == false)
-			error ("else without preceding if");
-
-		if (tokenIs (TK_Else) == false)
-			m_isElseAllowed = false;
-
-		switch (m_lexer->token()->type)
-		{
-			case TK_State:
-				parseStateBlock();
-				break;
-
-			case TK_Event:
-				parseEventBlock();
-				break;
-
-			case TK_Mainloop:
-				parseMainloop();
-				break;
-
-			case TK_Onenter:
-			case TK_Onexit:
-				parseOnEnterExit();
-				break;
-
-			case TK_Var:
-				parseVar();
-				break;
-
-			case TK_If:
-				parseIf();
-				break;
-
-			case TK_Else:
-				parseElse();
-				break;
-
-			case TK_While:
-				parseWhileBlock();
-				break;
-
-			case TK_For:
-				parseForBlock();
-				break;
-
-			case TK_Do:
-				parseDoBlock();
-				break;
-
-			case TK_Switch:
-				parseSwitchBlock();
-				break;
-
-			case TK_Case:
-				parseSwitchCase();
-				break;
-
-			case TK_Default:
-				parseSwitchDefault();
-				break;
-
-			case TK_Break:
-				parseBreak();
-				break;
-
-			case TK_Continue:
-				parseContinue();
-				break;
-
-			case TK_BraceEnd:
-				parseBlockEnd();
-				break;
-
-			case TK_Eventdef:
-				parseEventdef();
-				break;
-
-			case TK_Funcdef:
-				parseFuncdef();
-				break;
-
-			case TK_Semicolon:
-				break;
-
-			case TK_Using:
-				parseUsing();
-				break;
-
-			default:
-			{
-				// Check if it's a command
-				CommandInfo* comm = findCommandByName (getTokenString());
-
-				if (comm)
-				{
-					currentBuffer()->mergeAndDestroy (parseCommand (comm));
-					m_lexer->mustGetNext (TK_Semicolon);
-					continue;
-				}
-
-				// If nothing else, parse it as a statement
-				m_lexer->skip (-1);
-				DataBuffer* b = parseStatement();
-
-				if (b == false)
-				{
-					m_lexer->next();
-					error ("unknown token `%1`", getTokenString());
-				}
-
-				currentBuffer()->mergeAndDestroy (b);
-				m_lexer->mustGetNext (TK_Semicolon);
-				break;
-			}
-		}
-	}
-
-	// ===============================================================================
-	// Script file ended. Do some last checks and write the last things to main buffer
-	if (m_currentMode != PARSERMODE_TopLevel)
-		error ("script did not end at top level; a `}` is missing somewhere");
-
-	if (isReadOnly() == false)
-	{
-		// stateSpawn must be defined!
-		if (m_isStateSpawnDefined == false)
-			error ("script must have a state named `stateSpawn`!");
-
-		if (m_defaultZandronumVersion)
-		{
-			print ("\n");
-			print ("note: use the 'using' directive to define a target Zandronum version\n");
-			print ("usage: using zandronum <version>, possible versions: %1\n", g_validZandronumVersions);
-			print ("\n");
-		}
-
-		// Dump the last state's onenter and mainloop
-		writeMemberBuffers();
-
-		// String table
-		writeStringTable();
-	}
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseStateBlock()
-{
-	checkToplevel();
-	m_lexer->mustGetNext (TK_String);
-	String statename = getTokenString();
-
-	// State name must be a word.
-	if (statename.firstIndexOf (" ") != -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.toLowercase() == "statespawn")
-		m_isStateSpawnDefined = true;
-
-	// Must end in a colon
-	m_lexer->mustGetNext (TK_Colon);
-
-	// write the previous state's onenter and
-	// mainloop buffers to file now
-	if (m_currentState.isEmpty() == false)
-		writeMemberBuffers();
-
-	currentBuffer()->writeDWord (DH_StateName);
-	currentBuffer()->writeString (statename);
-	currentBuffer()->writeDWord (DH_StateIndex);
-	currentBuffer()->writeDWord (m_numStates);
-
-	m_numStates++;
-	m_currentState = statename;
-	m_gotMainLoop = false;
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseEventBlock()
-{
-	checkToplevel();
-	m_lexer->mustGetNext (TK_String);
-
-	EventDefinition* e = findEventByName (getTokenString());
-
-	if (e == null)
-		error ("bad event, got `%1`\n", getTokenString());
-
-	m_lexer->mustGetNext (TK_BraceStart);
-	m_currentMode = PARSERMODE_Event;
-	currentBuffer()->writeDWord (DH_Event);
-	currentBuffer()->writeDWord (e->number);
-	m_numEvents++;
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseMainloop()
-{
-	checkToplevel();
-	m_lexer->mustGetNext (TK_BraceStart);
-
-	m_currentMode = PARSERMODE_MainLoop;
-	m_mainLoopBuffer->writeDWord (DH_MainLoop);
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseOnEnterExit()
-{
-	checkToplevel();
-	bool onenter = (tokenIs (TK_Onenter));
-	m_lexer->mustGetNext (TK_BraceStart);
-
-	m_currentMode = onenter ? PARSERMODE_Onenter : PARSERMODE_Onexit;
-	currentBuffer()->writeDWord (onenter ? DH_OnEnter : DH_OnExit);
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseVar()
-{
-	Variable* var = new Variable;
-	var->origin = m_lexer->describeCurrentPosition();
-	var->isarray = false;
-	const bool isconst = m_lexer->next (TK_Const);
-	m_lexer->mustGetAnyOf ({TK_Int,TK_Str,TK_Void});
-
-	DataType vartype =	(tokenIs (TK_Int)) ? TYPE_Int :
-					(tokenIs (TK_Str)) ? TYPE_String :
-					TYPE_Bool;
-
-	m_lexer->mustGetNext (TK_DollarSign);
-	m_lexer->mustGetNext (TK_Symbol);
-	String name = getTokenString();
-
-	if (m_lexer->next (TK_BracketStart))
-	{
-		m_lexer->mustGetNext (TK_BracketEnd);
-		var->isarray = true;
-
-		if (isconst)
-			error ("arrays cannot be const");
-	}
-
-	for (Variable* var : SCOPE(0).globalVariables + SCOPE(0).localVariables)
-	{
-		if (var->name == name)
-			error ("Variable $%1 is already declared on this scope; declared at %2",
-				var->name, var->origin);
-	}
-
-	var->name = name;
-	var->statename = "";
-	var->type = vartype;
-
-	if (isconst == false)
-	{
-		var->writelevel = WRITE_Mutable;
-	}
-	else
-	{
-		m_lexer->mustGetNext (TK_Assign);
-		Expression expr (this, m_lexer, vartype);
-
-		// If the expression was constexpr, we know its value and thus
-		// can store it in the variable.
-		if (expr.getResult()->isConstexpr())
-		{
-			var->writelevel = WRITE_Constexpr;
-			var->value = expr.getResult()->value();
-		}
-		else
-		{
-			// TODO: might need a VM-wise oninit for this...
-			error ("const variables must be constexpr");
-		}
-	}
-
-	// Assign an index for the variable if it is not constexpr. Constexpr
-	// variables can simply be substituted out for their value when used
-	// so they need no index.
-	if (var->writelevel != WRITE_Constexpr)
-	{
-		bool isglobal = isInGlobalState();
-		var->index = isglobal ? SCOPE(0).globalVarIndexBase++ : SCOPE(0).localVarIndexBase++;
-
-		if ((isglobal == true && var->index >= gMaxGlobalVars) ||
-			(isglobal == false && var->index >= gMaxStateVars))
-		{
-			error ("too many %1 variables", isglobal ? "global" : "state-local");
-		}
-	}
-
-	if (isInGlobalState())
-		SCOPE(0).globalVariables << var;
-	else
-		SCOPE(0).localVariables << var;
-
-	suggestHighestVarIndex (isInGlobalState(), var->index);
-	m_lexer->mustGetNext (TK_Semicolon);
-	print ("Declared %3 variable #%1 $%2\n", var->index, var->name, isInGlobalState() ? "global" : "state-local");
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseIf()
-{
-	checkNotToplevel();
-	pushScope();
-
-	// Condition
-	m_lexer->mustGetNext (TK_ParenStart);
-
-	// Read the expression and write it.
-	DataBuffer* c = parseExpression (TYPE_Int);
-	currentBuffer()->mergeAndDestroy (c);
-
-	m_lexer->mustGetNext (TK_ParenEnd);
-	m_lexer->mustGetNext (TK_BraceStart);
-
-	// Add a mark - to here temporarily - and add a reference to it.
-	// Upon a closing brace, the mark will be adjusted.
-	ByteMark* mark = currentBuffer()->addMark ("");
-
-	// Use DH_IfNotGoto - if the expression is not true, we goto the mark
-	// we just defined - and this mark will be at the end of the scope block.
-	currentBuffer()->writeDWord (DH_IfNotGoto);
-	currentBuffer()->addReference (mark);
-
-	// Store it
-	SCOPE (0).mark1 = mark;
-	SCOPE (0).type = SCOPE_If;
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseElse()
-{
-	checkNotToplevel();
-	m_lexer->mustGetNext (TK_BraceStart);
-	pushScope (eNoReset);
-
-	if (SCOPE (0).type != SCOPE_If)
-		error ("else without preceding if");
-
-	// write down to jump to the end of the else statement
-	// Otherwise we have fall-throughs
-	SCOPE (0).mark2 = currentBuffer()->addMark ("");
-
-	// Instruction to jump to the end after if block is complete
-	currentBuffer()->writeDWord (DH_Goto);
-	currentBuffer()->addReference (SCOPE (0).mark2);
-
-	// Move the ifnot mark here and set type to else
-	currentBuffer()->adjustMark (SCOPE (0).mark1);
-	SCOPE (0).type = SCOPE_Else;
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseWhileBlock()
-{
-	checkNotToplevel();
-	pushScope();
-
-	// While loops need two marks - one at the start of the loop and one at the
-	// end. The condition is checked at the very start of the loop, if it fails,
-	// we use goto to skip to the end of the loop. At the end, we loop back to
-	// the beginning with a go-to statement.
-	ByteMark* mark1 = currentBuffer()->addMark (""); // start
-	ByteMark* mark2 = currentBuffer()->addMark (""); // end
-
-	// Condition
-	m_lexer->mustGetNext (TK_ParenStart);
-	DataBuffer* expr = parseExpression (TYPE_Int);
-	m_lexer->mustGetNext (TK_ParenEnd);
-	m_lexer->mustGetNext (TK_BraceStart);
-
-	// write condition
-	currentBuffer()->mergeAndDestroy (expr);
-
-	// Instruction to go to the end if it fails
-	currentBuffer()->writeDWord (DH_IfNotGoto);
-	currentBuffer()->addReference (mark2);
-
-	// Store the needed stuff
-	SCOPE (0).mark1 = mark1;
-	SCOPE (0).mark2 = mark2;
-	SCOPE (0).type = SCOPE_While;
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseForBlock()
-{
-	checkNotToplevel();
-	pushScope();
-
-	// Initializer
-	m_lexer->mustGetNext (TK_ParenStart);
-	DataBuffer* init = parseStatement();
-
-	if (init == null)
-		error ("bad statement for initializer of for");
-
-	m_lexer->mustGetNext (TK_Semicolon);
-
-	// Condition
-	DataBuffer* cond = parseExpression (TYPE_Int);
-
-	if (cond == null)
-		error ("bad statement for condition of for");
-
-	m_lexer->mustGetNext (TK_Semicolon);
-
-	// Incrementor
-	DataBuffer* incr = parseStatement();
-
-	if (incr == null)
-		error ("bad statement for incrementor of for");
-
-	m_lexer->mustGetNext (TK_ParenEnd);
-	m_lexer->mustGetNext (TK_BraceStart);
-
-	// First, write out the initializer
-	currentBuffer()->mergeAndDestroy (init);
-
-	// Init two marks
-	ByteMark* mark1 = currentBuffer()->addMark ("");
-	ByteMark* mark2 = currentBuffer()->addMark ("");
-
-	// Add the condition
-	currentBuffer()->mergeAndDestroy (cond);
-	currentBuffer()->writeDWord (DH_IfNotGoto);
-	currentBuffer()->addReference (mark2);
-
-	// Store the marks and incrementor
-	SCOPE (0).mark1 = mark1;
-	SCOPE (0).mark2 = mark2;
-	SCOPE (0).buffer1 = incr;
-	SCOPE (0).type = SCOPE_For;
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseDoBlock()
-{
-	checkNotToplevel();
-	pushScope();
-	m_lexer->mustGetNext (TK_BraceStart);
-	SCOPE (0).mark1 = currentBuffer()->addMark ("");
-	SCOPE (0).type = SCOPE_Do;
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseSwitchBlock()
-{
-	// 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
-
-	checkNotToplevel();
-	pushScope();
-	m_lexer->mustGetNext (TK_ParenStart);
-	currentBuffer()->mergeAndDestroy (parseExpression (TYPE_Int));
-	m_lexer->mustGetNext (TK_ParenEnd);
-	m_lexer->mustGetNext (TK_BraceStart);
-	SCOPE (0).type = SCOPE_Switch;
-	SCOPE (0).mark1 = currentBuffer()->addMark (""); // end mark
-	SCOPE (0).buffer1 = null; // default header
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseSwitchCase()
-{
-	// case is only allowed inside switch
-	if (SCOPE (0).type != SCOPE_Switch)
-		error ("case label outside switch");
-
-	// Get a literal value for the case block. Zandronum does not support
-	// expressions here.
-	m_lexer->mustGetNext (TK_Number);
-	int num = m_lexer->token()->text.toLong();
-	m_lexer->mustGetNext (TK_Colon);
-
-	for (const CaseInfo& info : SCOPE(0).cases)
-		if (info.number == num)
-			error ("multiple case %1 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.
-	//
-	// We null the switch buffer for the case-go-to statement as
-	// we want it all under the switch, not into the case-buffers.
-	m_switchBuffer = null;
-	currentBuffer()->writeDWord (DH_CaseGoto);
-	currentBuffer()->writeDWord (num);
-	addSwitchCase (null);
-	SCOPE (0).casecursor->number = num;
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseSwitchDefault()
-{
-	if (SCOPE (0).type != SCOPE_Switch)
-		error ("default label outside switch");
-
-	if (SCOPE (0).buffer1 != null)
-		error ("multiple default labels in one switch");
-
-	m_lexer->mustGetNext (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.
-	DataBuffer* buf = new DataBuffer;
-	SCOPE (0).buffer1 = buf;
-	buf->writeDWord (DH_Drop);
-	buf->writeDWord (DH_Goto);
-	addSwitchCase (buf);
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseBreak()
-{
-	if (m_scopeCursor == 0)
-		error ("unexpected `break`");
-
-	currentBuffer()->writeDWord (DH_Goto);
-
-	// switch and if use mark1 for the closing point,
-	// for and while use mark2.
-	switch (SCOPE (0).type)
-	{
-		case SCOPE_If:
-		case SCOPE_Switch:
-		{
-			currentBuffer()->addReference (SCOPE (0).mark1);
-		} break;
-
-		case SCOPE_For:
-		case SCOPE_While:
-		{
-			currentBuffer()->addReference (SCOPE (0).mark2);
-		} break;
-
-		default:
-		{
-			error ("unexpected `break`");
-		} break;
-	}
-
-	m_lexer->mustGetNext (TK_Semicolon);
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseContinue()
-{
-	m_lexer->mustGetNext (TK_Semicolon);
-
-	int curs;
-	bool found = false;
-
-	// Fall through the scope until we find a loop block
-	for (curs = m_scopeCursor; curs > 0 && !found; curs--)
-	{
-		switch (m_scopeStack[curs].type)
-		{
-			case SCOPE_For:
-			case SCOPE_While:
-			case SCOPE_Do:
-			{
-				currentBuffer()->writeDWord (DH_Goto);
-				currentBuffer()->addReference (m_scopeStack[curs].mark1);
-				found = true;
-			} break;
-
-			default:
-				break;
-		}
-	}
-
-	// No loop blocks
-	if (found == false)
-		error ("`continue`-statement not inside a loop");
-}
-
-// ============================================================================
-//
-void BotscriptParser::parseBlockEnd()
-{
-	// Closing brace
-	// If we're in the block stack, we're descending down from it now
-	if (m_scopeCursor > 0)
-	{
-		switch (SCOPE (0).type)
-		{
-			case SCOPE_If:
-			{
-				// Adjust the closing mark.
-				currentBuffer()->adjustMark (SCOPE (0).mark1);
-
-				// We're returning from `if`, thus `else` follow
-				m_isElseAllowed = true;
-				break;
-			}
-
-			case SCOPE_Else:
-			{
-				// else instead uses mark1 for itself (so if expression
-				// fails, jump to else), mark2 means end of else
-				currentBuffer()->adjustMark (SCOPE (0).mark2);
-				break;
-			}
-
-			case SCOPE_For:
-			{	// write the incrementor at the end of the loop block
-				currentBuffer()->mergeAndDestroy (SCOPE (0).buffer1);
-			}
-			case SCOPE_While:
-			{	// write down the instruction to go back to the start of the loop
-				currentBuffer()->writeDWord (DH_Goto);
-				currentBuffer()->addReference (SCOPE (0).mark1);
-
-				// Move the closing mark here since we're at the end of the while loop
-				currentBuffer()->adjustMark (SCOPE (0).mark2);
-				break;
-			}
-
-			case SCOPE_Do:
-			{
-				m_lexer->mustGetNext (TK_While);
-				m_lexer->mustGetNext (TK_ParenStart);
-				DataBuffer* expr = parseExpression (TYPE_Int);
-				m_lexer->mustGetNext (TK_ParenEnd);
-				m_lexer->mustGetNext (TK_Semicolon);
-
-				// If the condition runs true, go back to the start.
-				currentBuffer()->mergeAndDestroy (expr);
-				currentBuffer()->writeDWord (DH_IfGoto);
-				currentBuffer()->addReference (SCOPE (0).mark1);
-				break;
-			}
-
-			case SCOPE_Switch:
-			{
-				// Switch closes. Move down to the record buffer of
-				// the lower block.
-				if (SCOPE (1).casecursor != SCOPE (1).cases.begin() - 1)
-					m_switchBuffer = SCOPE (1).casecursor->data;
-				else
-					m_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)
-					currentBuffer()->mergeAndDestroy (SCOPE (0).buffer1);
-				else
-				{
-					currentBuffer()->writeDWord (DH_Drop);
-					currentBuffer()->writeDWord (DH_Goto);
-					currentBuffer()->addReference (SCOPE (0).mark1);
-				}
-
-				// Go through all of the buffers we
-				// recorded down and write them.
-				for (CaseInfo& info : SCOPE (0).cases)
-				{
-					currentBuffer()->adjustMark (info.mark);
-					currentBuffer()->mergeAndDestroy (info.data);
-				}
-
-				// Move the closing mark here
-				currentBuffer()->adjustMark (SCOPE (0).mark1);
-				break;
-			}
-
-			case SCOPE_Unknown:
-				break;
-		}
-
-		// Descend down the stack
-		m_scopeCursor--;
-		return;
-	}
-
-	int dataheader =	(m_currentMode == PARSERMODE_Event) ? DH_EndEvent :
-						(m_currentMode == PARSERMODE_MainLoop) ? DH_EndMainLoop :
-						(m_currentMode == PARSERMODE_Onenter) ? DH_EndOnEnter :
-						(m_currentMode == PARSERMODE_Onexit) ? DH_EndOnExit : -1;
-
-	if (dataheader == -1)
-		error ("unexpected `}`");
-
-	// Data header must be written before mode is changed because
-	// onenter and mainloop go into special buffers, and we want
-	// the closing data headers into said buffers too.
-	currentBuffer()->writeDWord (dataheader);
-	m_currentMode = PARSERMODE_TopLevel;
-	m_lexer->next (TK_Semicolon);
-}
-
-// =============================================================================
-//
-void BotscriptParser::parseEventdef()
-{
-	EventDefinition* e = new EventDefinition;
-
-	m_lexer->mustGetNext (TK_Number);
-	e->number = getTokenString().toLong();
-	m_lexer->mustGetNext (TK_Colon);
-	m_lexer->mustGetNext (TK_Symbol);
-	e->name = m_lexer->token()->text;
-	m_lexer->mustGetNext (TK_ParenStart);
-	m_lexer->mustGetNext (TK_ParenEnd);
-	m_lexer->mustGetNext (TK_Semicolon);
-	addEvent (e);
-}
-
-// =============================================================================
-//
-void BotscriptParser::parseFuncdef()
-{
-	CommandInfo* comm = new CommandInfo;
-	comm->origin = m_lexer->describeCurrentPosition();
-
-	// Return value
-	m_lexer->mustGetAnyOf ({TK_Int,TK_Void,TK_Bool,TK_Str});
-	comm->returnvalue = getTypeByName (m_lexer->token()->text); // TODO
-	assert (comm->returnvalue != -1);
-
-	// Number
-	m_lexer->mustGetNext (TK_Number);
-	comm->number = m_lexer->token()->text.toLong();
-	m_lexer->mustGetNext (TK_Colon);
-
-	// Name
-	m_lexer->mustGetNext (TK_Symbol);
-	comm->name = m_lexer->token()->text;
-
-	// Arguments
-	m_lexer->mustGetNext (TK_ParenStart);
-	comm->minargs = 0;
-
-	while (m_lexer->peekNextType (TK_ParenEnd) == false)
-	{
-		if (comm->args.isEmpty() == false)
-			m_lexer->mustGetNext (TK_Comma);
-
-		CommandArgument arg;
-		m_lexer->mustGetAnyOf ({TK_Int,TK_Bool,TK_Str});
-		DataType type = getTypeByName (m_lexer->token()->text); // TODO
-		assert (type != -1 && type != TYPE_Void);
-		arg.type = type;
-
-		m_lexer->mustGetNext (TK_Symbol);
-		arg.name = m_lexer->token()->text;
-
-		// If this is an optional parameter, we need the default value.
-		if (comm->minargs < comm->args.size() || m_lexer->peekNextType (TK_Assign))
-		{
-			m_lexer->mustGetNext (TK_Assign);
-
-			switch (type)
-			{
-				case TYPE_Int:
-				case TYPE_Bool:
-					m_lexer->mustGetNext (TK_Number);
-					break;
-
-				case TYPE_String:
-					error ("string arguments cannot have default values");
-
-				case TYPE_Unknown:
-				case TYPE_Void:
-					break;
-			}
-
-			arg.defvalue = m_lexer->token()->text.toLong();
-		}
-		else
-			comm->minargs++;
-
-		comm->args << arg;
-	}
-
-	m_lexer->mustGetNext (TK_ParenEnd);
-	m_lexer->mustGetNext (TK_Semicolon);
-	addCommandDefinition (comm);
-}
-
-// ============================================================================
-//
-// Parses a using statement
-//
-void BotscriptParser::parseUsing()
-{
-	checkToplevel();
-	m_lexer->mustGetSymbol ("zandronum");
-	String versionText;
-
-	while (m_lexer->next() && (m_lexer->tokenType() == TK_Number || m_lexer->tokenType() == TK_Dot))
-		versionText += getTokenString();
-
-	// Note: at this point the lexer's pointing at the token after the version.
-	if (versionText.isEmpty())
-		error ("expected version string, got `%1`", getTokenString());
-	if (g_validZandronumVersions.contains (versionText) == false)
-		error ("unknown version string `%2`: valid versions: `%1`\n", g_validZandronumVersions, versionText);
-
-	StringList versionTokens = versionText.split (".");
-	m_zandronumVersion = versionTokens[0].toLong() * 10000 + versionTokens[1].toLong() * 100;
-	m_defaultZandronumVersion = false;
-	m_lexer->tokenMustBe (TK_Semicolon);
-}
-
-// ============================================================================/
-//
-// Parses a command call
-//
-DataBuffer* BotscriptParser::parseCommand (CommandInfo* comm)
-{
-	DataBuffer* r = new DataBuffer (64);
-
-	if (m_currentMode == PARSERMODE_TopLevel && comm->returnvalue == TYPE_Void)
-		error ("command call at top level");
-
-	m_lexer->mustGetNext (TK_ParenStart);
-	m_lexer->mustGetNext (TK_Any);
-
-	int curarg = 0;
-
-	for (;;)
-	{
-		if (tokenIs (TK_ParenEnd))
-		{
-			if (curarg < comm->minargs)
-				error ("too few arguments passed to %1\n\tusage is: %2",
-					comm->name, comm->signature());
-
-			break;
-		}
-
-		if (curarg >= comm->args.size())
-			error ("too many arguments (%3) passed to %1\n\tusage is: %2",
-				comm->name, comm->signature());
-
-		r->mergeAndDestroy (parseExpression (comm->args[curarg].type, true));
-		m_lexer->mustGetNext (TK_Any);
-
-		if (curarg < comm->minargs - 1)
-		{
-			m_lexer->tokenMustBe (TK_Comma);
-			m_lexer->mustGetNext (TK_Any);
-		}
-		else if (curarg < comm->args.size() - 1)
-		{
-			// Can continue, but can terminate as well.
-			if (tokenIs (TK_ParenEnd))
-			{
-				curarg++;
-				break;
-			}
-			else
-			{
-				m_lexer->tokenMustBe (TK_Comma);
-				m_lexer->mustGetNext (TK_Any);
-			}
-		}
-
-		curarg++;
-	}
-
-	// If the script skipped any optional arguments, fill in defaults.
-	while (curarg < comm->args.size())
-	{
-		r->writeDWord (DH_PushNumber);
-		r->writeDWord (comm->args[curarg].defvalue);
-		curarg++;
-	}
-
-	r->writeDWord (DH_Command);
-	r->writeDWord (comm->number);
-	r->writeDWord (comm->args.size());
-
-	return r;
-}
-
-// ============================================================================
-//
-String BotscriptParser::parseFloat()
-{
-	m_lexer->tokenMustBe (TK_Number);
-	String floatstring = getTokenString();
-	Lexer::TokenInfo tok;
-
-	// Go after the decimal point
-	if (m_lexer->peekNext (&tok) && tok.type ==TK_Dot)
-	{
-		m_lexer->skip();
-		m_lexer->mustGetNext (TK_Number);
-		floatstring += ".";
-		floatstring += getTokenString();
-	}
-
-	return floatstring;
-}
-
-// ============================================================================
-//
-// Parses an assignment operator.
-//
-AssignmentOperator BotscriptParser::parseAssignmentOperator()
-{
-	const List<ETokenType> tokens =
-	{
-		TK_Assign,
-		TK_AddAssign,
-		TK_SubAssign,
-		TK_MultiplyAssign,
-		TK_DivideAssign,
-		TK_ModulusAssign,
-		TK_DoublePlus,
-		TK_DoubleMinus,
-	};
-
-	m_lexer->mustGetAnyOf (tokens);
-
-	switch (m_lexer->tokenType())
-	{
-		case TK_Assign:			return ASSIGNOP_Assign;
-		case TK_AddAssign:		return ASSIGNOP_Add;
-		case TK_SubAssign:		return ASSIGNOP_Subtract;
-		case TK_MultiplyAssign:	return ASSIGNOP_Multiply;
-		case TK_DivideAssign:	return ASSIGNOP_Divide;
-		case TK_ModulusAssign:	return ASSIGNOP_Modulus;
-		case TK_DoublePlus:		return ASSIGNOP_Increase;
-		case TK_DoubleMinus:	return ASSIGNOP_Decrease;
-		default: break;
-	}
-
-	assert (false);
-	return (AssignmentOperator) 0;
-}
-
-// ============================================================================
-//
-struct AssignmentDataHeaderInfo
-{
-	AssignmentOperator	op;
-	DataHeader			local;
-	DataHeader			global;
-	DataHeader			array;
-};
-
-const AssignmentDataHeaderInfo gAssignmentDataHeaders[] =
-{
-	{ ASSIGNOP_Assign,		DH_AssignLocalVar,		DH_AssignGlobalVar,		DH_AssignGlobalArray },
-	{ ASSIGNOP_Add,			DH_AddLocalVar,			DH_AddGlobalVar,		DH_AddGlobalArray },
-	{ ASSIGNOP_Subtract,	DH_SubtractLocalVar,	DH_SubtractGlobalVar,	DH_SubtractGlobalArray },
-	{ ASSIGNOP_Multiply,	DH_MultiplyLocalVar,	DH_MultiplyGlobalVar,	DH_MultiplyGlobalArray },
-	{ ASSIGNOP_Divide,		DH_DivideLocalVar,		DH_DivideGlobalVar,		DH_DivideGlobalArray },
-	{ ASSIGNOP_Modulus,		DH_ModLocalVar,			DH_ModGlobalVar,		DH_ModGlobalArray },
-	{ ASSIGNOP_Increase,	DH_IncreaseLocalVar,	DH_IncreaseGlobalVar,	DH_IncreaseGlobalArray },
-	{ ASSIGNOP_Decrease,	DH_DecreaseLocalVar,	DH_DecreaseGlobalVar,	DH_DecreaseGlobalArray },
-};
-
-DataHeader BotscriptParser::getAssigmentDataHeader (AssignmentOperator op, Variable* var)
-{
-	for (const auto& a : gAssignmentDataHeaders)
-	{
-		if (a.op != op)
-			continue;
-
-		if (var->isarray)
-			return a.array;
-
-		if (var->IsGlobal())
-			return a.global;
-
-		return a.local;
-	}
-
-	error ("WTF: couldn't find data header for operator #%1", op);
-	return (DataHeader) 0;
-}
-
-// ============================================================================
-//
-// Parses an assignment. An assignment starts with a variable name, followed
-// by an assignment operator, followed by an expression value. Expects current
-// token to be the name of the variable, and expects the variable to be given.
-//
-DataBuffer* BotscriptParser::parseAssignment (Variable* var)
-{
-	DataBuffer* retbuf = new DataBuffer;
-	DataBuffer* arrayindex = null;
-
-	if (var->writelevel != WRITE_Mutable)
-		error ("cannot alter read-only variable $%1", var->name);
-
-	if (var->isarray)
-	{
-		m_lexer->mustGetNext (TK_BracketStart);
-		Expression expr (this, m_lexer, TYPE_Int);
-		expr.getResult()->convertToBuffer();
-		arrayindex = expr.getResult()->buffer()->clone();
-		m_lexer->mustGetNext (TK_BracketEnd);
-	}
-
-	// Get an operator
-	AssignmentOperator oper = parseAssignmentOperator();
-
-	if (m_currentMode == PARSERMODE_TopLevel)
-		error ("can't alter variables at top level");
-
-	if (var->isarray)
-		retbuf->mergeAndDestroy (arrayindex);
-
-	// Parse the right operand
-	if (oper != ASSIGNOP_Increase && oper != ASSIGNOP_Decrease)
-	{
-		DataBuffer* expr = parseExpression (var->type);
-		retbuf->mergeAndDestroy (expr);
-	}
-
-#if 0
-	// <<= and >>= do not have data headers. Solution: expand them.
-	// a <<= b -> a = a << b
-	// a >>= b -> a = a >> b
-	retbuf->WriteDWord (var->IsGlobal() ? DH_PushGlobalVar : DH_PushLocalVar);
-	retbuf->WriteDWord (var->index);
-	retbuf->MergeAndDestroy (expr);
-	retbuf->WriteDWord ((oper == OPER_ASSIGNLEFTSHIFT) ? DH_LeftShift : DH_RightShift);
-	retbuf->WriteDWord (var->IsGlobal() ? DH_AssignGlobalVar : DH_AssignLocalVar);
-	retbuf->WriteDWord (var->index);
-#endif
-
-	DataHeader dh = getAssigmentDataHeader (oper, var);
-	retbuf->writeDWord (dh);
-	retbuf->writeDWord (var->index);
-	return retbuf;
-}
-
-// ============================================================================
-//
-void BotscriptParser::pushScope (EReset reset)
-{
-	m_scopeCursor++;
-
-	if (m_scopeStack.size() < m_scopeCursor + 1)
-	{
-		ScopeInfo newscope;
-		m_scopeStack << newscope;
-		reset = SCOPE_Reset;
-	}
-
-	if (reset == SCOPE_Reset)
-	{
-		ScopeInfo* info = &SCOPE (0);
-		info->type = SCOPE_Unknown;
-		info->mark1 = null;
-		info->mark2 = null;
-		info->buffer1 = null;
-		info->cases.clear();
-		info->casecursor = info->cases.begin() - 1;
-	}
-
-	// Reset variable stuff in any case
-	SCOPE(0).globalVarIndexBase = (m_scopeCursor == 0) ? 0 : SCOPE(1).globalVarIndexBase;
-	SCOPE(0).localVarIndexBase = (m_scopeCursor == 0) ? 0 : SCOPE(1).localVarIndexBase;
-
-	for (Variable* var : SCOPE(0).globalVariables + SCOPE(0).localVariables)
-		delete var;
-
-	SCOPE(0).localVariables.clear();
-	SCOPE(0).globalVariables.clear();
-}
-
-// ============================================================================
-//
-DataBuffer* BotscriptParser::parseExpression (DataType reqtype, bool fromhere)
-{
-	// hehe
-	if (fromhere == true)
-		m_lexer->skip (-1);
-
-	Expression expr (this, m_lexer, reqtype);
-	expr.getResult()->convertToBuffer();
-
-	// The buffer will be destroyed once the function ends so we need to
-	// clone it now.
-	return expr.getResult()->buffer()->clone();
-}
-
-// ============================================================================
-//
-DataBuffer* BotscriptParser::parseStatement()
-{
-	// If it's a variable, expect assignment.
-	if (m_lexer->next (TK_DollarSign))
-	{
-		m_lexer->mustGetNext (TK_Symbol);
-		Variable* var = findVariable (getTokenString());
-
-		if (var == null)
-			error ("unknown variable $%1", var->name);
-
-		return parseAssignment (var);
-	}
-
-	return null;
-}
-
-// ============================================================================
-//
-void BotscriptParser::addSwitchCase (DataBuffer* casebuffer)
-{
-	ScopeInfo* info = &SCOPE (0);
-	CaseInfo casedata;
-
-	// Init a mark for the case buffer
-	ByteMark* casemark = currentBuffer()->addMark ("");
-	casedata.mark = casemark;
-
-	// Add a reference to the mark. "case" and "default" both
-	// add the necessary bytecode before the reference.
-	if (casebuffer != null)
-		casebuffer->addReference (casemark);
-	else
-		currentBuffer()->addReference (casemark);
-
-	// Init a buffer for the case block and tell the object
-	// writer to record all written data to it.
-	casedata.data = m_switchBuffer = new DataBuffer;
-	SCOPE(0).cases << casedata;
-	info->casecursor++;
-}
-
-// ============================================================================
-//
-bool BotscriptParser::tokenIs (ETokenType a)
-{
-	return (m_lexer->tokenType() == a);
-}
-
-// ============================================================================
-//
-String BotscriptParser::getTokenString()
-{
-	return m_lexer->token()->text;
-}
-
-// ============================================================================
-//
-String BotscriptParser::describePosition() const
-{
-	Lexer::TokenInfo* tok = m_lexer->token();
-	return tok->file + ":" + String (tok->line) + ":" + String (tok->column);
-}
-
-// ============================================================================
-//
-DataBuffer* BotscriptParser::currentBuffer()
-{
-	if (m_switchBuffer != null)
-		return m_switchBuffer;
-
-	if (m_currentMode == PARSERMODE_MainLoop)
-		return m_mainLoopBuffer;
-
-	if (m_currentMode == PARSERMODE_Onenter)
-		return m_onenterBuffer;
-
-	return m_mainBuffer;
-}
-
-// ============================================================================
-//
-void BotscriptParser::writeMemberBuffers()
-{
-	// If there was no mainloop defined, write a dummy one now.
-	if (m_gotMainLoop == false)
-	{
-		m_mainLoopBuffer->writeDWord (DH_MainLoop);
-		m_mainLoopBuffer->writeDWord (DH_EndMainLoop);
-	}
-
-	// Write the onenter and mainloop buffers, in that order in particular.
-	for (DataBuffer** bufp : List<DataBuffer**> ({&m_onenterBuffer, &m_mainLoopBuffer}))
-	{
-		currentBuffer()->mergeAndDestroy (*bufp);
-
-		// Clear the buffer afterwards for potential next state
-		*bufp = new DataBuffer;
-	}
-
-	// Next state definitely has no mainloop yet
-	m_gotMainLoop = false;
-}
-
-// ============================================================================
-//
-// Write string table
-//
-void BotscriptParser::writeStringTable()
-{
-	int stringcount = countStringsInTable();
-
-	if (stringcount == 0)
-		return;
-
-	// Write header
-	m_mainBuffer->writeDWord (DH_StringList);
-	m_mainBuffer->writeDWord (stringcount);
-
-	// Write all strings
-	for (int i = 0; i < stringcount; i++)
-		m_mainBuffer->writeString (getStringTable()[i]);
-}
-
-// ============================================================================
-//
-// Write the compiled bytecode to a file
-//
-void BotscriptParser::writeToFile (String outfile)
-{
-	FILE* fp = fopen (outfile, "wb");
-
-	if (fp == null)
-		error ("couldn't open %1 for writing: %2", outfile, strerror (errno));
-
-	// First, resolve references
-	for (MarkReference* ref : m_mainBuffer->references())
-		for (int i = 0; i < 4; ++i)
-			m_mainBuffer->buffer()[ref->pos + i] = (ref->target->pos >> (8 * i)) & 0xFF;
-
-	// Then, dump the main buffer to the file
-	fwrite (m_mainBuffer->buffer(), 1, m_mainBuffer->writtenSize(), fp);
-	print ("-- %1 byte%s1 written to %2\n", m_mainBuffer->writtenSize(), outfile);
-	fclose (fp);
-}
-
-// ============================================================================
-//
-// Attempt to find the variable by the given name. Looks from current scope
-// downwards.
-//
-Variable* BotscriptParser::findVariable (const String& name)
-{
-	for (int i = m_scopeCursor; i >= 0; --i)
-	{
-		for (Variable* var : m_scopeStack[i].globalVariables + m_scopeStack[i].localVariables)
-		{
-			if (var->name == name)
-				return var;
-		}
-	}
-
-	return null;
-}
-
-// ============================================================================
-//
-// Is the parser currently in global state (i.e. not in any specific state)?
-//
-bool BotscriptParser::isInGlobalState() const
-{
-	return m_currentState.isEmpty();
-}
-
-// ============================================================================
-//
-void BotscriptParser::suggestHighestVarIndex (bool global, int index)
-{
-	if (global)
-		m_highestGlobalVarIndex = max (m_highestGlobalVarIndex, index);
-	else
-		m_highestStateVarIndex = max (m_highestStateVarIndex, index);
-}
-
-// ============================================================================
-//
-int BotscriptParser::getHighestVarIndex (bool global)
-{
-	if (global)
-		return m_highestGlobalVarIndex;
-
-	return m_highestStateVarIndex;
-}

mercurial