/*
 *  JoeyDev
 *  Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty.  In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  1. The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *  2. Altered source versions must be plainly marked as such, and must not be
 *     misrepresented as being the original software.
 *  3. This notice may not be removed or altered from any source distribution.
*/


#pragma clang diagnostic push
#pragma ide diagnostic ignored "EmptyDeclOrStmt"


#include <errno.h>
#include "common.h"
#include "vecparse.h"
#include "utils.h"


#define IS_NUMBER(n)  (n & 0x8000 ? FALSE : TRUE)


typedef struct KeywordsS {
	char           *command;
	ParserKeywordT  id;
} KeywordsT;

typedef struct OperatorsS {
	char           *operator;
	ParserMathT     id;
} OperatorsT;

typedef struct LabelS {
	char *key;
	int   value;
	int   line;
} LabelT;


static int       labelGetValue(int lineNumber, VecByteCodeT *bytecode, LabelT *labels, LabelT ***unresolved, char *label);
static void      outputByte(VecByteCodeT *bytecode, unsigned short word);
static void      outputWord(VecByteCodeT *bytecode, unsigned short word);
static char     *parserGetNextLine(char *str, char **savePtr);
static gboolean  parserGetNextValue(char *token, char **valueEnd, char ***variables, int *x);
static int       parserGetWord(char *wordList, char **tokenEnd);
static gboolean  parserGetX(char **tokenEnd, char ***variables, int *x);
static gboolean  parserGetXY(char **tokenEnd, char ***variables, int *x, int *y);
static gboolean  parserGetXYZ(char **tokenEnd, char ***variables, int *x, int *y, int *z);
static gboolean  variableCollect(char *value, char ***variables, int *result);


static int labelGetValue(int lineNumber, VecByteCodeT *bytecode, LabelT *labels, LabelT ***unresolved, char *label) {
	int     index;
	int     found = -1;
	LabelT *temp;

	// Do we know this label?  We search ourselves so it's case-insensitive.
	for (index = 0; index < hmlen(labels); index++) {
		if (strcasecmp(label, labels[index].key) == 0) {
			found = index;
			break;
		}
	}
	if (found < 0) {
		// We don't yet know this label, so it's a forward reference.
		// Record it with a negative location, so we can resolve it later.
		temp = NEW(LabelT);
		temp->key   = strdup(label);
		temp->value = bytecode->length;
		temp->line  = lineNumber;
		arrput(*unresolved, temp);
		index = 0 - ((int)arrlen(*unresolved) + 1);
	} else {
		index = labels[index].value;
	}

	return index;
}


static void outputByte(VecByteCodeT *bytecode, unsigned short word) {
	unsigned char byte = (unsigned char)word;

	utilEnsureBufferSize(&bytecode->bytes, &bytecode->bufferSize, bytecode->length + 1);

	// If the word passed in is a variable, set the MSb in the byte as well.
	if (word & 0x8000) {
		byte |= 0x80;
	}

	bytecode->bytes[bytecode->length++] = byte;
}


static void outputWord(VecByteCodeT *bytecode, unsigned short word) {
	utilEnsureBufferSize(&bytecode->bytes, &bytecode->bufferSize, bytecode->length + 2);
	bytecode->bytes[bytecode->length++] = (word & 0xFF00) >> 8;
	bytecode->bytes[bytecode->length++] = word & 0x00FF;
}


// Custom strtok_r because:
//   A sequence of two or more contiguous delimiter bytes in the
//   parsed string is considered to be a single delimiter.
// Which is exactly what we don't want for the line parser.
static char *parserGetNextLine(char *str, char **savePtr) {
	char *token = NULL;

	if (str) {
		*savePtr = str;
	}

	if (!*savePtr) {
		return NULL;
	}

	token = *savePtr;
	*savePtr = strpbrk(*savePtr, "\n");

	if (*savePtr) {
		**savePtr = 0;
		*savePtr = *savePtr + 1;
	}

	while (token != NULL && (token[0] == ' ' || token[0] == '\t')) token++;

	return token;
}


static gboolean parserGetNextValue(char *token, char **valueEnd, char ***variables, int *x) {
	char *value;

	// Return next value in a comma separated list.

	value = strtok_r(token, ",", valueEnd);
	if (value == NULL) return FALSE;

	return variableCollect(value, variables, x);;
}


static int parserGetWord(char *wordList, char **tokenEnd) {
	char *token;
	char *word;
	char *wordEnd = NULL;
	char *mutable;
	int   index   = -1;

	// Returns -1 if the word is not found in the wordlist.
	// Returns a zero-based index of the position in the list
	// if one of the words is found.

	token = strtok_r(NULL, " ", tokenEnd);
	if (token == NULL) return index;

	mutable = strdup(wordList);

	word = strtok_r(mutable, " ", &wordEnd);
	while (word != NULL) {
		index++;
		if (strcasecmp(token, word) == 0) break;
		word = strtok_r(NULL, " ", &wordEnd);
	}

	DEL(mutable);

	return index;
}


static gboolean parserGetX(char **tokenEnd, char ***variables, int *x) {
	char *value;

	// Return single value.

	value = strtok_r(NULL, " ", tokenEnd);
	if (value == NULL) return FALSE;

	return variableCollect(value, variables, x);
}


static gboolean parserGetXY(char **tokenEnd, char ***variables, int *x, int *y) {
	char *token;
	char *valueEnd;

	// Return values of X,Y pair.

	token = strtok_r(NULL, " ", tokenEnd);
	if (token == NULL) return FALSE;

	if (!parserGetNextValue(token, &valueEnd, variables, x)) return FALSE;
	if (!parserGetNextValue(NULL, &valueEnd, variables, y)) return FALSE;

	return TRUE;
}


static gboolean parserGetXYZ(char **tokenEnd, char ***variables, int *x, int *y, int *z) {
	char *token;
	char *valueEnd;

	// Return values of X,Y pair.

	token = strtok_r(NULL, " ", tokenEnd);
	if (token == NULL) return FALSE;

	if (!parserGetNextValue(token, &valueEnd, variables, x)) return FALSE;
	if (!parserGetNextValue(NULL, &valueEnd, variables, y)) return FALSE;
	if (!parserGetNextValue(NULL, &valueEnd, variables, z)) return FALSE;

	return TRUE;
}


static gboolean variableCollect(char *value, char ***variables, int *result) {
	int    index;
	int    found;
	char  *endPtr = NULL;
	char **vars   = *variables;

	// Is it a variable or number?
	if (strlen(value) > 1 && value[0] == '%') {

		// It's a variable.  Do we know it?
		found = -1;
		for (index = 0; index < arrlen(vars); index++) {
			if (strcasecmp(vars[index], value) == 0) {
				found = index;  // Found.
				break;
			}
		}
		if (found < 0) {
			// Variable is not yet known.  Add it to list.
			found = arrlen(vars);
			arrput(vars, value);
		}
		// Set MSb to indicate it's a variable.
		found |= 0x8000;

	} else {

		// It's a number.
		errno = 0;  endPtr = NULL;
		found = (int)strtol(value, &endPtr, 10);
		if (errno != 0 || *endPtr != 0) return FALSE;
		// Is it negative?  If so, we need it in our format.
		if (found < 0) {
			found = -found;
			found |= 0x4000;
		}
		// Ensure MSb is cleared, so we know it's a number.
		found &= 0x7fff;

	}

	*result = found;

	return TRUE;
}


int vecparser(char *programIn, VecByteCodeT *bytecode) {
	int          keyword;
	char        *line;
	char        *lineEnd;
	gboolean     lineOkay;
	gboolean     isOkay;
	char        *token;
	char        *tokenEnd;
	int          lineNumber;
	int          x1;
	int          y1;
	int          x2;
	int          y2;
	PointT       p1;
	PointT       p2;
	PointT      *points     = NULL;  // Used to collect points for LINE
	char       **variables  = NULL;  // Array of known variables and their IDs
	LabelT      *labels     = NULL;  // Known labels and their byte offsets
	LabelT      *label      = NULL;  // Temp label for array management.
	LabelT     **unresolved = NULL;  // List of unresolved label uses and their byte offsets
	int          result     = -1;  // Returns -1 on success or line number of first error.
	KeywordsT    commands[] = {
			{ "BOX",       PARSE_BOX        },
			{ "CALL",      PARSE_CALL       },
			{ "CIRCLE",    PARSE_CIRCLE     },
			{ "CLEAR",     PARSE_CLEAR      },
			{ "COLOR",     PARSE_COLOR      },
			{ "//",        PARSE_COMMENT    },
			{ "ELLIPSE",   PARSE_ELLIPSE    },
			{ "GOTO",      PARSE_GOTO       },
			{ "IF",        PARSE_IF         },
			{ "FILL",      PARSE_FILL       },
			{ "LINE",      PARSE_LINE       },
			{ "PALETTE",   PARSE_PALETTE    },
			{ "PLOT",      PARSE_PLOT       },
			{ "RECTANGLE", PARSE_RECTANGLE  },
			{ "RETURN",    PARSE_RETURN     },
			{ NULL,        PARSE_NONE       }
	};
	OperatorsT   math[] = {
			{ "=",     MATH_ASSIGN   },
			{ "+",     MATH_ADD      },
			{ "-",     MATH_SUBTRACT },
			{ "*",     MATH_MULTIPLY },
			{ "/",     MATH_DIVIDE   },
			{ "RND",   MATH_RND      },
			{ "MOD",   MATH_MOD      },
			{ "POW",   MATH_POW      },
			{ "SQRT",  MATH_SQRT     },
			{ "ABS",   MATH_ABS      },
			{ "COS",   MATH_COS      },
			{ "SIN",   MATH_SIN      },
			{ "TAN",   MATH_TAN      },
			{ NULL,    MATH_NONE     }
	};

	// Parse code.
	lineNumber = 0;
	line = parserGetNextLine(programIn, &lineEnd);
	while (line != NULL) {

		// Get the first token on the line.
		token = strtok_r(line, " ", &tokenEnd);

		// Is this something we care about?  It'll be math, a label, or a keyword.
		lineOkay = FALSE;

		// Is this a blank line with no tokens?
		if (token == NULL) {

			// Yep - just move on.
			lineOkay = TRUE;

		} else {  // blank line

			debug("[%s]\n", token);

			// Is it math?
			if (strlen(token) > 1 && token[0] == '%') {

				// We're doing some kind of math.

				// Look up variable index in table, or add it if needed.
				// Variable index is stored in y1 for later.
				y1 = -1;
				for (y2 = 0; y2 < arrlen(variables); y2++) {
					if (strcasecmp(variables[y2], token) == 0) {
						y1 = y2;  // Found.
						break;
					}
				}
				if (y1 < 0) {
					// Variable is not yet known.  Add it to list.
					y1 = arrlen(variables);
					arrput(variables, token);
				}
				// Mark as variable.
				y1 |= 0x80;

				// Find the operator.
				token = strtok_r(NULL, " ", &tokenEnd);
				if (token != NULL) {
					keyword = 0;
					while (math[keyword].operator) {
						if (strcasecmp(math[keyword].operator, token) == 0) {
							// Yep!  Gather arguments and generate bytecode.
							if (!parserGetX(&tokenEnd, &variables, &x1)) break;
							outputByte(bytecode, PARSE_MATH);
							outputByte(bytecode, y1);
							outputByte(bytecode, math[keyword].id);
							outputWord(bytecode, x1);
							lineOkay = TRUE;
							break;
						}
						keyword++;
					}
				}

			} else {  // doing math

				// Is it a label?
				if (strlen(token) > 1 && token[strlen(token) - 1] == ':') {

					// It's a label.

					// Remove trailing colon.
					token[strlen(token) - 1] = 0;

					// Have we already used this label?
					x2 = -1;
					// We search ourselves so it's case-insensitive.
					for (x1 = 0; x1 < shlen(labels); x1++) {
						if (strcasecmp(token, labels[x1].key) == 0) {
							x2 = x1;
							break;
						}
					}
					if (x2 < 0) {
						// New label.  Add to hashmap.
						shput(labels, token, bytecode->length);
						lineOkay = TRUE;
					}

				} else {  // doing a label

					// It's a keyword.
					keyword = 0;
					while (commands[keyword].command) {
						if (strcasecmp(commands[keyword].command, token) == 0) {

							// Yep!  Gather arguments and generate bytecode.
							switch (commands[keyword].id) {

								case PARSE_NONE:
									// Won't happen, but silences an error.
									break;

								case PARSE_BOX:
									// Box (value),(value) to (value),(value)
									if (!parserGetXY(&tokenEnd, &variables, &x1, &y1)) break;
									if (parserGetWord("TO", &tokenEnd) < 0) break;
									if (!parserGetXY(&tokenEnd, &variables, &x2, &y2)) break;
									if (IS_NUMBER(x1) && IS_NUMBER(x2)) if (x2 < x1) break;
									if (IS_NUMBER(x1)) if (x1 < 0 || x1 > 319) break;
									if (IS_NUMBER(x2)) if (x2 < 0 || x2 > 319) break;
									if (IS_NUMBER(y1) && IS_NUMBER(y2)) if (y2 < y1) break;
									if (IS_NUMBER(y1)) if (y1 < 0 || y1 > 199) break;
									if (IS_NUMBER(y2)) if (y2 < 0 || y2 > 199) break;
									outputByte(bytecode, PARSE_BOX);
									outputWord(bytecode, x1);
									outputWord(bytecode, y1);
									outputWord(bytecode, x2);
									outputWord(bytecode, y2);
									lineOkay = TRUE;
									break;

								case PARSE_CALL:
									// Call (label)
									if (tokenEnd == NULL) break;
									outputByte(bytecode, PARSE_CALL);
									x1 = labelGetValue(lineNumber, bytecode, labels, &unresolved, tokenEnd);
									outputWord(bytecode, x1);
									lineOkay = TRUE;
									break;

								case PARSE_CIRCLE:
									// Circle (value),(value) radius (value)
									if (!parserGetXY(&tokenEnd, &variables, &x1, &y1)) break;
									if (parserGetWord("RADIUS", &tokenEnd) < 0) break;
									if (!parserGetX(&tokenEnd, &variables, &y2)) break;
									if (IS_NUMBER(x1)) if (x1 < 0 || x1 > 319) break;
									if (IS_NUMBER(y1)) if (y1 < 0 || y1 > 199) break;
									outputByte(bytecode, PARSE_CIRCLE);
									outputWord(bytecode, y2);
									outputWord(bytecode, x1);
									outputWord(bytecode, y1);
									lineOkay = TRUE;
									break;

								case PARSE_CLEAR:
									outputByte(bytecode, PARSE_CLEAR);
									lineOkay = TRUE;
									break;

								case PARSE_COLOR:
									// Color (short)
									if (!parserGetX(&tokenEnd, &variables, &x1)) break;
									if (IS_NUMBER(x1)) if (x1 < 0 || x1 > 15) break;
									outputByte(bytecode, PARSE_COLOR);
									outputByte(bytecode, x1);
									lineOkay = TRUE;
									break;

								case PARSE_COMMENT:
									// Eat the rest of the line.
									while (token != NULL) {
										token = strtok_r(NULL, " ", &tokenEnd);
									}
									lineOkay = TRUE;
									break;

								case PARSE_ELLIPSE:
									// Ellipse (value),(value) to (value),(value)
									if (!parserGetXY(&tokenEnd, &variables, &x1, &y1)) break;
									if (parserGetWord("TO", &tokenEnd) < 0) break;
									if (!parserGetXY(&tokenEnd, &variables, &x2, &y2)) break;
									if (IS_NUMBER(x1) && IS_NUMBER(x2)) if (x2 < x1) break;
									if (IS_NUMBER(x1)) if (x1 < 0 || x1 > 319) break;
									if (IS_NUMBER(x2)) if (x2 < 0 || x2 > 319) break;
									if (IS_NUMBER(y1) && IS_NUMBER(y2)) if (y2 < y1) break;
									if (IS_NUMBER(y1)) if (y1 < 0 || y1 > 199) break;
									if (IS_NUMBER(y2)) if (y2 < 0 || y2 > 199) break;
									outputByte(bytecode, PARSE_ELLIPSE);
									outputWord(bytecode, x1);
									outputWord(bytecode, y1);
									outputWord(bytecode, x2);
									outputWord(bytecode, y2);
									lineOkay = TRUE;
									break;

								case PARSE_FILL:
									// Fill (value),(value) {to (value}
									if (!parserGetXY(&tokenEnd, &variables, &x1, &y1)) break;
									if (IS_NUMBER(x1)) if (x1 < 0 || x1 > 319) break;
									if (IS_NUMBER(y1)) if (y1 < 0 || y1 > 199) break;
									// Do they want to fill to a certain color?  Or over the current color?
									x2 = 16;  // 16 == Fill, otherwise FillTo
									if (parserGetWord("TO", &tokenEnd) >= 0) {
										if (!parserGetX(&tokenEnd, &variables, &x2)) break;
										if (IS_NUMBER(x2)) if (x2 < 0 || x2 > 15) break;
									}
									outputByte(bytecode, PARSE_FILL);
									outputWord(bytecode, x1);
									outputWord(bytecode, y1);
									outputByte(bytecode, x2);
									lineOkay = TRUE;
									break;

								case PARSE_GOTO:
									// Goto (label)
									if (tokenEnd == NULL) break;
									outputByte(bytecode, PARSE_GOTO);
									x1 = labelGetValue(lineNumber, bytecode, labels, &unresolved, tokenEnd);
									outputWord(bytecode, x1);
									lineOkay = TRUE;
									break;

								case PARSE_IF:
									// If (value) (compare) (value) (goto|call) (label)
									if (!parserGetX(&tokenEnd, &variables, &x1)) break;
									y1 = parserGetWord("== != < > <= >=", &tokenEnd);
									if (y1 < 0) break;
									if (!parserGetX(&tokenEnd, &variables, &x2)) break;
									y2 = parserGetWord("goto call", &tokenEnd);
									if (y2 < 0) break;
									if (tokenEnd == NULL) break;
									outputByte(bytecode, PARSE_IF);
									outputWord(bytecode, x1);
									outputByte(bytecode, y1);
									outputWord(bytecode, x2);
									outputByte(bytecode, y2);
									x1 = labelGetValue(lineNumber, bytecode, labels, &unresolved, tokenEnd);
									outputWord(bytecode, x1);
									lineOkay = TRUE;
									break;

								case PARSE_LABEL:
									// Won't happen.
									break;

								case PARSE_LINE:
									// Line (value),(value) to (value),(value) [to ...]
									points = NULL;
									if (!parserGetXY(&tokenEnd, &variables, &p1.x, &p1.y)) break;
									if (parserGetWord("TO", &tokenEnd) < 0) break;
									if (!parserGetXY(&tokenEnd, &variables, &p2.x, &p2.y)) break;
									if (IS_NUMBER(p1.x)) if (p1.x < 0 || p1.x > 319) break;
									if (IS_NUMBER(p1.y)) if (p1.y < 0 || p1.y > 199) break;
									if (IS_NUMBER(p2.x)) if (p2.x < 0 || p2.x > 319) break;
									if (IS_NUMBER(p2.y)) if (p2.y < 0 || p2.y > 199) break;
									arrput(points, p1);
									arrput(points, p2);
									isOkay = TRUE;
									while (parserGetWord("TO", &tokenEnd) >= 0) {
										if (!parserGetXY(&tokenEnd, &variables, &p1.x, &p1.y)) {
											// Error.
											isOkay = FALSE;
											break;
										}
										if (IS_NUMBER(p1.x)) if (p1.x < 0 || p1.x > 319) { isOkay = FALSE; break; }
										if (IS_NUMBER(p1.y)) if (p1.y < 0 || p1.y > 199) { isOkay = FALSE; break; }
										arrput(points, p1);
									}
									if (isOkay) {
										outputByte(bytecode, PARSE_LINE);
										outputWord(bytecode, arrlen(points));
										for (x1 = 0; x1 < arrlen(points); x1++) {
											outputWord(bytecode, points[x1].x);
											outputWord(bytecode, points[x1].y);
										}
										lineOkay = TRUE;
									}
									break;

								case PARSE_MATH:
									// Won't happen.
									break;

								case PARSE_PALETTE:
									// Palette (short) AS (short),(short),(short)
									if (!parserGetX(&tokenEnd, &variables, &x1)) break;
									if (parserGetWord("AS", &tokenEnd) < 0) break;
									if (!parserGetXYZ(&tokenEnd, &variables, &x2, &y1, &y2)) break;
									if (IS_NUMBER(x1)) if (x1 < 0 || x1 > 15) break;
									if (IS_NUMBER(x2)) if (x2 < 0 || x2 > 15) break;
									if (IS_NUMBER(y1)) if (y1 < 0 || y1 > 15) break;
									if (IS_NUMBER(y2)) if (y2 < 0 || y2 > 15) break;
									outputByte(bytecode, PARSE_PALETTE);
									outputByte(bytecode, x1);
									outputByte(bytecode, x2);
									outputByte(bytecode, y1);
									outputByte(bytecode, y2);
									lineOkay = TRUE;
									break;

								case PARSE_PLOT:
									// Plot (value),(value)
									if (!parserGetXY(&tokenEnd, &variables, &x1, &y1)) break;
									if (IS_NUMBER(x1)) if (x1 < 0 || x1 > 319) break;
									if (IS_NUMBER(y1)) if (y1 < 0 || y1 > 199) break;
									outputByte(bytecode, PARSE_PLOT);
									outputWord(bytecode, x1);
									outputWord(bytecode, y1);
									lineOkay = TRUE;
									break;

								case PARSE_RECTANGLE:
									// Rectangle (value),(value) to (value),(value)
									if (!parserGetXY(&tokenEnd, &variables, &x1, &y1)) break;
									if (parserGetWord("TO", &tokenEnd) < 0) break;
									if (!parserGetXY(&tokenEnd, &variables, &x2, &y2)) break;
									if (IS_NUMBER(x1) && IS_NUMBER(x2)) if (x2 < x1) break;
									if (IS_NUMBER(x1)) if (x1 < 0 || x1 > 319) break;
									if (IS_NUMBER(x2)) if (x2 < 0 || x2 > 319) break;
									if (IS_NUMBER(y1) && IS_NUMBER(y2)) if (y2 < y1) break;
									if (IS_NUMBER(y1)) if (y1 < 0 || y1 > 199) break;
									if (IS_NUMBER(y2)) if (y2 < 0 || y2 > 199) break;
									outputByte(bytecode, PARSE_RECTANGLE);
									outputWord(bytecode, x1);
									outputWord(bytecode, y1);
									outputWord(bytecode, x2);
									outputWord(bytecode, y2);
									lineOkay = TRUE;
									break;

								case PARSE_RETURN:
									// Return
									outputByte(bytecode, PARSE_RETURN);
									lineOkay = TRUE;
									break;

							}  // switch

							// Unwind point array if needed.
							if (points != NULL) {
								while (arrlen(points) > 0) {
									arrdel(points, 0);
								}
							}

							// Stop looking for this keyword - we handled it.
							break;

						} else {
							// Keep looking until we find this keyword or run out of commands.
							keyword++;
						}  // keyword match

					}  // loop over commands

				}  // label

			} // math

		} // blank line

		// Is everything still okay?
		if (!lineOkay) {
			// Nope - error.
			result = lineNumber;
			break;
		}

		// Move to next line.
		line = parserGetNextLine(NULL, &lineEnd);
		lineNumber++;
	}  // read program line

	// Resolve forward label declarations and patch bytecode.
	if (lineOkay) {
#ifdef DEBUG_MODE
		for (y1 = 0; y1 < shlen(labels); y1++) {
			debug("Resolved - %s\n", labels[y1].key);
		}
		for (y1 = 0; y1 < arrlen(unresolved); y1++) {
			debug("Unresolved - %s\n", unresolved[y1]->key);
		}
#endif
		for (y1 = 0; y1 < arrlen(unresolved); y1++) {
			// Find offset of this unresolved label.  We search ourselves so it's case-insensitive.
			x2 = -1;
			for (x1 = 0; x1 < shlen(labels); x1++) {
				debug("Checking label %d of %d - %s == %s\n", y1, (int)arrlen(unresolved), unresolved[y1]->key,
						labels[x1].key);
				if (strcasecmp(unresolved[y1]->key, labels[x1].key) == 0) {
					x2 = x1;
					break;
				}
			}
			if (x2 < 0) {
				// Label not found!  Error!
				result = unresolved[y1]->line;
				break;
			} else {
				// Write this label offset into the unresolved offset.
				bytecode->bytes[unresolved[y1]->value++] = (labels[x2].value & 0xFF00) >> 8;
				bytecode->bytes[unresolved[y1]->value] = labels[x2].value & 0x00FF;
			}
		}
	}

	// Unwind variables array if needed.
	ARRFREE(variables);

	// Unwind unresolved array if needed.
	if (unresolved != NULL) {
		while (arrlen(unresolved) > 0) {
			label = unresolved[0];
			DEL(label->key);
			DEL(label);
			arrdel(unresolved, 0);
		}
	}

	// Unwind labels hashmap if needed.
	if (labels != NULL) {
		while (shlen(labels) > 0) {
			shdel(labels, labels[0].key);
		}
		shfree(labels);
	}

	/*
	 * (value) is a 16-bit integer.  Since we only need a fraction of the
	 * possible values provided by this, we steal a couple bits for our
	 * own use.  All values are stored without messing with 2's complement.
	 *
	 * Type  Negative  Value
	 *   \   /__________/_
	 *    \ //            \
	 *     tnvvvvvvvvvvvvvv
	 *
	 * So with this scheme we can store values from -16383 to 16383 (yes,
	 * zero is represented twice).
	 *
	 * The Type bit determines if the value stored is a literal value or a
	 * reference to a variable in the variable table.
	 *
	 * (short) is a simplified version used for colors.  It is always positive
	 * and has a range from 0 to 127 with Type being the MSb.  This effectively
	 * limits the number of available variables to 128.
	 *
	 */

	return result;
}


#pragma clang diagnostic pop
