src/parser.cpp

changeset 119
bdf8d46c145f
child 125
85814c0918c5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/parser.cpp	Sun Mar 30 21:51:23 2014 +0300
@@ -0,0 +1,1417 @@
+/*
+	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 "list.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