From 88a08ee48cbbae086ddbfeaff0679bfe4fe6ce47 Mon Sep 17 00:00:00 2001
From: Matthew Kosarek <mattkae@protonmail.com>
Date: Sat, 10 Apr 2021 21:54:43 -0400
Subject: Added a transpiler that will make it so that we no longer need any
 JavaScript of Jquery in our App

---
 frontend/transpiler/List.h         | 222 +++++++++++++++++++++
 frontend/transpiler/Logger.cpp     | 123 ++++++++++++
 frontend/transpiler/Logger.h       |  43 ++++
 frontend/transpiler/MathHelper.h   |  33 ++++
 frontend/transpiler/MyString.cpp   | 392 +++++++++++++++++++++++++++++++++++++
 frontend/transpiler/MyString.h     |  76 +++++++
 frontend/transpiler/build.sh       |   1 +
 frontend/transpiler/pages.txt      |  14 ++
 frontend/transpiler/transpiler     | Bin 0 -> 38744 bytes
 frontend/transpiler/transpiler.cpp | 249 +++++++++++++++++++++++
 10 files changed, 1153 insertions(+)
 create mode 100644 frontend/transpiler/List.h
 create mode 100644 frontend/transpiler/Logger.cpp
 create mode 100644 frontend/transpiler/Logger.h
 create mode 100644 frontend/transpiler/MathHelper.h
 create mode 100644 frontend/transpiler/MyString.cpp
 create mode 100644 frontend/transpiler/MyString.h
 create mode 100755 frontend/transpiler/build.sh
 create mode 100644 frontend/transpiler/pages.txt
 create mode 100755 frontend/transpiler/transpiler
 create mode 100644 frontend/transpiler/transpiler.cpp

(limited to 'frontend/transpiler')

diff --git a/frontend/transpiler/List.h b/frontend/transpiler/List.h
new file mode 100644
index 0000000..00a466a
--- /dev/null
+++ b/frontend/transpiler/List.h
@@ -0,0 +1,222 @@
+#pragma once
+#include <cstdlib>
+#include <cstring>
+#include "Logger.h"
+
+#define FOREACH(list)												\
+	for (size_t idx = 0; idx < list.numElements; idx++) \
+		if (auto value = list.getValue(idx)) \
+		
+
+template <typename T>
+struct List {
+	T* data = nullptr;
+	size_t capacity = 0;
+	size_t numElements = 0;
+	bool growDynamically = true;
+
+	void allocate(size_t size);
+	void add(T* element);
+	void add(T& element);
+	void add(T&& element);
+	bool grow(size_t newSize);
+	void set(T* value, size_t index);
+	void remove(size_t index);
+	void clear();
+	void deallocate();
+	bool isEmpty() {
+		return data == nullptr || numElements == 0;
+	}
+	T* getValue(int index) const;
+	T& operator[](int idx) const;
+	void binarySort(int (*f)(T *first, T* second));
+	void setFromArray(T* arry, int size) {
+	    allocate(size);
+		memcpy(data, arry, size * sizeof(T));
+		numElements = size;
+	}
+	void remove(int index) {
+		if (index >= numElements) {
+			logger_error("Cannot remove element at index: %d", index);
+			return;
+		}
+
+		
+		if (index == numElements - 1) {
+			numElements--;
+			return;
+		}
+		
+		memmove(data[index], data[index + 1], sizeof(T) * (numElements - index));
+		numElements--;
+	}
+};
+
+template <typename T>
+void List<T>::allocate(size_t size) {
+	if (size == 0 || size == capacity) {
+		numElements = 0;
+		return;
+	}
+
+	if (data != nullptr) {
+		deallocate();
+	}
+
+	data = static_cast<T*>(malloc(sizeof(T) * size));
+	capacity = size;
+	numElements = 0;
+}
+
+template <typename T>
+bool List<T>::grow(size_t newSize) {
+	if (!growDynamically) {
+		return false;
+	}
+
+	if (newSize == 0) {
+		return false;
+	}
+
+	T* newData = static_cast<T*>(malloc(sizeof(T) * newSize));
+
+	if (data != nullptr) {
+		memcpy(newData, data, numElements * sizeof(T));
+		delete data;
+	}
+
+	data = newData;
+	capacity = newSize;
+	return true;
+}
+
+template <typename T>
+void List<T>::set(T* value, size_t index) {
+	if (index >= capacity && !grow(index * 2)) {
+		return;
+	}
+
+	memcpy(&data[index], value, sizeof(T));
+}
+
+template <typename T>
+void List<T>::add(T* element) {
+	if (data == nullptr) {
+		allocate(2);
+	}
+
+	if (element == nullptr) {
+		logger_error("Element not defined");
+		return;
+	}
+
+	size_t newNumElements = numElements + 1;
+	if (newNumElements > capacity) {
+		if (!grow(2 * capacity)) {
+			logger_error("Trying to add to list but unable to grow the array");
+			return;
+		}
+	}
+
+	memcpy(&data[numElements], element, sizeof(T));
+	numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::add(T& element) {
+	if (data == nullptr) {
+		allocate(2);
+	}
+
+	size_t newNumElements = numElements + 1;
+	if (newNumElements > capacity) {
+		if (!grow(2 * capacity)) {
+			logger_error("Trying to add to list but unable to grow the array");
+			return;
+		}
+	}
+
+	memcpy(&data[numElements], &element, sizeof(T));
+	numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::add(T&& element) {
+	if (data == nullptr) {
+		allocate(2);
+	}
+
+	size_t newNumElements = numElements + 1;
+	if (newNumElements > capacity) {
+		if (!grow(2 * capacity)) {
+			logger_error("Trying to add to list but unable to grow the array");
+			return;
+		}
+	}
+
+	memcpy(&data[numElements], &element, sizeof(T));
+	numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::remove(size_t idx) {
+	if (idx >= numElements) {
+		logger_error("Index is outside of the list: %d >= %d", idx, numElements);
+		return;
+	}
+
+	for (; idx < numElements - 1; idx++) {
+		data[idx] = data[idx + 1];
+	}
+
+	numElements--;
+}
+
+template <typename T>
+void List<T>::deallocate() {
+	if (data != nullptr) {
+		free(data);
+		data = nullptr;
+	}
+
+	capacity = 0;
+	numElements = 0;
+}
+
+template <typename T>
+void List<T>::clear() {
+	numElements = 0;
+}
+
+template <typename T>
+T* List<T>::getValue(int idx) const {
+	return &data[idx];
+}
+
+template <typename T>
+T& List<T>::operator[](int idx) const {
+	return data[idx];
+}
+
+template <typename T>
+void List<T>::binarySort(int (*f)(T *first, T* second)) {
+	if (data == nullptr) {
+		return;
+	}
+
+	for (size_t idx = 0; idx < numElements - 1; idx++) {
+		int minIdx = idx;
+		T firstValue = data[idx];
+
+		for (int innerIdx = idx + 1; innerIdx < numElements; innerIdx++) {\
+			T secondValue = data[innerIdx];
+			if (f(&firstValue, &secondValue) > 0) {
+				minIdx= innerIdx;
+			}
+		}
+
+		T temp = data[minIdx];
+		memmove(&data[minIdx], &data[idx], sizeof(T));
+		memmove(&data[idx], &temp, sizeof(T));
+	}
+}
diff --git a/frontend/transpiler/Logger.cpp b/frontend/transpiler/Logger.cpp
new file mode 100644
index 0000000..1068d88
--- /dev/null
+++ b/frontend/transpiler/Logger.cpp
@@ -0,0 +1,123 @@
+#include "Logger.h"
+#include <chrono>
+#include <cstdarg>
+#include <cstdio>
+
+namespace Logger {
+    LogLevel gLogLevel = LogLevel_Debug;
+    FILE* gFilePointer = NULL;
+
+    void initialize(LoggerOptions options) {
+        setLevel(options.level);
+        if (options.logToFile) {
+#ifdef WIN32
+            fopen_s(&gFilePointer, options.filePath, "a");
+#else
+            gFilePointer = fopen(options.filePath, "a");
+#endif
+        }
+    }
+
+    void setLevel(LogLevel level) {
+        gLogLevel = level;
+    }
+
+    LogLevel getLevel() {
+        return gLogLevel;
+    }
+
+    void printHeader(const char* levelStr, const char* fileName, int lineNumber) {
+        time_t t = time(0);
+        tm now;
+#ifdef WIN32
+        localtime_s(&now, &t);
+#else
+        now = *localtime(&t);
+#endif
+
+        printf("%s:%d [%d-%d-%d %d:%d:%d] %s: ", fileName, lineNumber, (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr);
+        if (gFilePointer != NULL) {
+            fprintf(gFilePointer, "[%d-%d-%d %d:%d:%d] %s: ", (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr);
+        }
+    }
+
+    void logInternal(const char* file, int lineNumber,LogLevel level, const char* format, va_list args) {
+        if (level < gLogLevel) {
+            return;
+        }
+
+	
+        const char* levelStr;
+        switch (level) {
+        case LogLevel_Debug:
+            levelStr = "Debug";
+            break;
+        case LogLevel_Info:
+            levelStr = "Info";
+            break;
+        case LogLevel_Warn:
+            levelStr = "Warning";
+            break;
+        case LogLevel_Error:
+            levelStr = "Error";
+            break;
+        default:
+            levelStr = "Unknown";
+            break;
+        }
+
+	if (gFilePointer != NULL) {
+	    va_list fileArgs;
+	    va_copy(fileArgs, args);
+	    vfprintf(gFilePointer, format, fileArgs);
+	    fprintf(gFilePointer, "\n");
+	}
+
+        printHeader(levelStr, file, lineNumber);
+
+        vprintf(format, args);
+        printf("\n");
+    }
+
+    void doLog(const char* file, int lineNumber,LogLevel level, const char* format, ...) {
+        va_list args;
+        va_start(args, format);
+        logInternal(file, lineNumber, level, format, args);
+        va_end(args);
+    }
+
+    void doDebug(const char* file, int lineNumber,const char* format, ...) {
+        va_list args;
+        va_start(args, format);
+        logInternal(file, lineNumber, LogLevel_Debug, format, args);
+        va_end(args);
+    }
+
+    void doInfo(const char* file, int lineNumber,const char* format, ...) {
+        va_list args;
+        va_start(args, format);
+        logInternal(file, lineNumber, LogLevel_Info, format, args);
+        va_end(args);
+    }
+
+    void doWarning(const char* file, int lineNumber,const char* format, ...) {
+        va_list args;
+        va_start(args, format);
+        logInternal(file, lineNumber, LogLevel_Warn, format, args);
+        va_end(args);
+    }
+
+    void doError(const char* file, int lineNumber,const char* format, ...) {
+        va_list args;
+        va_start(args, format);
+        logInternal(file, lineNumber, LogLevel_Error, format, args);
+        va_end(args);
+    }
+
+    void free() {
+        if (gFilePointer) {
+            fclose(gFilePointer);
+	    gFilePointer = NULL;
+        }
+    }
+}
diff --git a/frontend/transpiler/Logger.h b/frontend/transpiler/Logger.h
new file mode 100644
index 0000000..7596b6f
--- /dev/null
+++ b/frontend/transpiler/Logger.h
@@ -0,0 +1,43 @@
+#ifndef LOGGER_H
+#define LOGGER_H
+
+#include <cstring>
+
+enum LogLevel {
+    LogLevel_Debug = 0,
+    LogLevel_Info = 1,
+    LogLevel_Warn = 2,
+    LogLevel_Error = 3
+};
+
+struct LoggerOptions {
+    LogLevel level = LogLevel_Debug;
+    bool logToFile = false;
+    const char* filePath = "debug.log";
+};
+
+namespace Logger {
+    void initialize(LoggerOptions options);
+    void setLevel(LogLevel level);
+    LogLevel getLevel();
+    void doLog(const char* file, int lineNumber, LogLevel level, const char* format, ...);
+    void doDebug(const char* file, int lineNumber, const char* format, ...);
+    void doInfo(const char* file, int lineNumber, const char* format, ...);
+    void doWarning(const char* file, int lineNumber, const char* format, ...);
+    void doError(const char* file, int lineNumber, const char* format, ...);
+    void free();
+};
+
+#if WIN32
+#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
+#else
+#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+#endif
+
+#define logger_log(level, format, ...) Logger::doLog(__FILENAME__, __LINE__, level, format, ## __VA_ARGS__)
+#define logger_debug(format, ...) Logger::doDebug(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_info(format, ...) Logger::doInfo(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_warning(format, ...) Logger::doWarning(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_error(format, ...) Logger::doError(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+
+#endif
diff --git a/frontend/transpiler/MathHelper.h b/frontend/transpiler/MathHelper.h
new file mode 100644
index 0000000..b9c9cb6
--- /dev/null
+++ b/frontend/transpiler/MathHelper.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#define PI 3.14159265358979323846f
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#define MAX(a,b) (((a)>(b))?(a):(b))
+#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower)))
+#define DTOR (PI / 180.0f)
+#define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)
+
+namespace MathHelper {
+	inline float radiansToDegrees(float radians) {
+		return radians * 180.f / PI;
+	}
+
+	inline float degreesToRadians(float degrees) {
+		return degrees * PI / 180.f;
+	}
+
+	inline int nearestPowerOfTwo(int n) {
+        int v = n; 
+
+        v--;
+        v |= v >> 1;
+        v |= v >> 2;
+        v |= v >> 4;
+        v |= v >> 8;
+        v |= v >> 16;
+        v++; // next power of 2
+
+        return v;
+    }
+}
diff --git a/frontend/transpiler/MyString.cpp b/frontend/transpiler/MyString.cpp
new file mode 100644
index 0000000..1cb3d43
--- /dev/null
+++ b/frontend/transpiler/MyString.cpp
@@ -0,0 +1,392 @@
+#include "MyString.h"
+#include "MathHelper.h"
+#include <cstdlib>
+#include <cstdio>
+#include <iostream>
+#include <cstdarg>
+#include <cmath>
+
+int StringBuffer::add(const char* str) {
+	int spaceNeeded = strlen(str);
+	int spaceAvailable = BUFFER_SIZE - pointer;
+
+	int amountToCopy = 0;
+	if (spaceAvailable >= spaceNeeded) {
+		amountToCopy = spaceNeeded;
+	} else {
+		amountToCopy = spaceAvailable;
+	}
+
+	memcpy(&buffer[pointer], str, sizeof(char) * amountToCopy);
+	pointer += amountToCopy;
+	buffer[pointer] = '\0';
+	return amountToCopy;
+}
+
+void StringBuffer::reset() {
+	buffer[0] = '\0';
+	pointer = 0;	
+}
+
+bool StringBuffer::isFull() {
+	return pointer == BUFFER_SIZE;
+}
+
+StringBuffer* StringBuilder::getCurrentBuffer() {
+	if (bufferPointer == 0) {
+		if (!defaultBuffer.isFull()) {
+			return &defaultBuffer;
+		}
+
+		StringBuffer newBuffer;
+		dynamicBuffer.allocate(2);
+		bufferPointer++;
+		dynamicBuffer.add(&newBuffer);
+	}
+
+	if (dynamicBuffer[bufferPointer - 1].isFull()) {
+		StringBuffer newBuffer;
+		dynamicBuffer.add(&newBuffer);
+		bufferPointer++;
+	}
+
+	return &dynamicBuffer[bufferPointer - 1];
+}
+
+StringBuffer* StringBuilder::getBufferAtIdx(int index) {
+	if (index == 0) {
+		return &defaultBuffer;
+	}
+
+	index = index - 1;
+	if (index < static_cast<int>(dynamicBuffer.numElements)) {
+		return &dynamicBuffer[index];
+	}
+
+	return nullptr;
+}
+
+const StringBuffer* StringBuilder::getBufferAtIdxConst(int index) const {
+	if (index == 0) {
+		return &defaultBuffer;
+	}
+
+	index = index - 1;
+	if (index < static_cast<int>(dynamicBuffer.numElements)) {
+		return &dynamicBuffer[index];
+	}
+
+	return nullptr;
+}
+
+String StringBuilder::toString() {
+	int strSize = (bufferPointer + 1) * StringBuffer::BUFFER_SIZE;
+	String retval;
+
+	if (strSize > String::SSO_SIZE) {
+		retval.dynamicBuffer = new char[strSize + 1];
+		memcpy(retval.dynamicBuffer, defaultBuffer.buffer, sizeof(char) * StringBuffer::BUFFER_SIZE);
+		FOREACH(dynamicBuffer) {
+			memcpy(&retval.dynamicBuffer[(idx + 1) * StringBuffer::BUFFER_SIZE], value->buffer, sizeof(char) * StringBuffer::BUFFER_SIZE);
+		}
+		retval.isSSO = false;
+	} else {
+		memcpy(retval.defaultBuffer, defaultBuffer.buffer, sizeof(char) * strSize);
+		retval.isSSO = true;
+	}
+
+	retval.capacity = strSize;
+	retval.length = length;
+	retval.getValue()[retval.length] = '\0';
+	return retval;
+}
+
+void StringBuilder::addStr(String* str) {
+	addStr(str->getValue());
+}
+
+void StringBuilder::addStr(const char* str) {
+	int amountLeft = strlen(str);
+	length += amountLeft;
+	int ptr = 0;
+
+	do {
+		StringBuffer* currentBuffer = getCurrentBuffer();
+		int amtAdded = currentBuffer->add(&str[ptr]);
+		amountLeft = amountLeft - amtAdded;
+		ptr += amtAdded;
+	} while (amountLeft > 0);
+}
+
+void StringBuilder::addChar(char c) {
+	char wrapper[2] = { c, '\0' };
+	addStr(wrapper);
+}
+
+const int DEFAULT_NUMBER_BUFFER_SIZE = 32;
+
+void StringBuilder::addInt(int value) {
+	char buffer[DEFAULT_NUMBER_BUFFER_SIZE];
+	sprintf(buffer, "%d", value);
+	addStr(buffer);
+}
+
+// Pulled from here: https://stackoverflow.com/questions/7228438/convert-double-float-to-string
+inline int n_tu(int number, int count) {
+    int result = 1;
+    while(count-- > 0)
+        result *= number;
+
+    return result;
+}
+
+void StringBuilder::addFloat(float value) {
+	char buffer[DEFAULT_NUMBER_BUFFER_SIZE];
+	sprintf(buffer, "%f", value);
+	addStr(buffer);
+}
+
+void StringBuilder::replace(const char* strToReplace, const char* replaceStr) {
+	
+}
+
+int StringBuilder::indexOf(const char* str) {
+	return -1;
+}
+
+void StringBuilder::removeAt(int index, int count) {
+	if (index >= length) {
+		printf("Index is larger than the length");
+		return;
+	}
+
+	if (count <= 0) {
+		printf("Count was <= zero");
+		return;
+	}
+
+	int newLength = length - count;
+	if (newLength == 0) {
+		clear();
+		return;
+	}
+
+	// Easiest way to do this is to move the entire string to the left, starting at index by count
+	int currentIndex = index;
+	int copyIndex = index + count;
+
+	while (currentIndex < newLength) {
+		int bufferPos = floor(static_cast<float>(currentIndex) / static_cast<float>(StringBuffer::BUFFER_SIZE));
+		int pointerIdx = currentIndex % StringBuffer::BUFFER_SIZE;
+		StringBuffer* buffer = getBufferAtIdx(bufferPos);
+
+		char newChar = '\0';
+		int copyBufferPos = floor(static_cast<float>(copyIndex) / static_cast<float>(StringBuffer::BUFFER_SIZE));
+		StringBuffer* copyBuffer = getBufferAtIdx(copyBufferPos);
+		if (copyBuffer != nullptr) {
+			int bufferPointerIdx = copyIndex % StringBuffer::BUFFER_SIZE;
+			if (bufferPointerIdx < copyBuffer->pointer) {
+				newChar = copyBuffer->buffer[bufferPointerIdx];
+			}
+		}
+
+		buffer->buffer[pointerIdx] = newChar;
+
+		currentIndex++;
+		copyIndex++;
+	}
+
+	// Brute force update the buffer that we're currently using
+	length = newLength;
+	int newNumBuffers = ceil(static_cast<float>(length) / static_cast<float>(StringBuffer::BUFFER_SIZE)); // This is how many buffers we should have
+	if (newNumBuffers <= 1) {
+		dynamicBuffer.numElements = 0;
+		bufferPointer = 0;
+	} else {
+		dynamicBuffer.numElements = newNumBuffers - 1;
+		bufferPointer = newNumBuffers - 1;
+	}
+
+	StringBuffer* lastBuffer = getBufferAtIdx(bufferPointer);
+	if (lastBuffer->pointer != StringBuffer::BUFFER_SIZE) {
+		lastBuffer->pointer = length % StringBuffer::BUFFER_SIZE;
+	}
+	lastBuffer->buffer[lastBuffer->pointer] = '\0';
+}
+
+void StringBuilder::format(const char* str, ...) {
+	va_list args;
+    va_start(args, str);
+
+	while (*str != '\0') {
+		if (*str == '%') {
+			str++;
+			switch (*str) {
+			case 'd':
+				addInt(va_arg(args, int));
+				break;
+			case 'f':
+				addFloat(static_cast<float>(va_arg(args, double)));
+				break;
+			case 's':
+				addStr(va_arg(args, char*));
+			default:
+				break;
+			}
+		} else {
+			addChar(*str);
+		}
+
+		str++;
+	}
+
+	va_end(args);
+}
+
+void StringBuilder::clear() {
+	bufferPointer = 0;
+	length = 0;
+	defaultBuffer.reset();
+
+	if (!dynamicBuffer.isEmpty()) {
+		FOREACH(dynamicBuffer) {
+			value->reset();
+		}
+	}
+
+	dynamicBuffer.clear();
+}
+
+void StringBuilder::free() {
+	dynamicBuffer.deallocate();
+}
+
+char StringBuilder::getCharAtIdx(int index) const {
+	int bufferPos = floor(static_cast<float>(index) / static_cast<float>(StringBuffer::BUFFER_SIZE));
+	int pointerIdx = index % StringBuffer::BUFFER_SIZE;
+	const StringBuffer* buffer = getBufferAtIdxConst(bufferPos);
+	if (buffer != nullptr && pointerIdx < buffer->pointer) {
+		return buffer->buffer[pointerIdx];
+	}
+
+	return '\0';
+}
+
+void StringBuilder::insert(char c, int index) {
+	if (index >= length) {
+		addChar(c);
+		return;
+	}
+
+	// Move all of the characters forward by one
+	char currentChar = c;
+	for (int cIdx = index; cIdx < length + 1; cIdx++) {
+		int bufferPos = floor(static_cast<float>(cIdx) / static_cast<float>(StringBuffer::BUFFER_SIZE));
+		int pointerIdx = cIdx % StringBuffer::BUFFER_SIZE;
+		StringBuffer* buffer = getBufferAtIdx(bufferPos);
+		if (buffer == nullptr) {
+			buffer = getCurrentBuffer(); // Get a new buffer if this one is filled up
+		}
+
+		char nextCurrent = buffer->buffer[pointerIdx];
+		buffer->buffer[pointerIdx] = currentChar;
+		currentChar = nextCurrent;
+	}
+
+	// Move the final buffer forward
+	StringBuffer* finalBuffer = getCurrentBuffer();
+	finalBuffer->pointer++;
+	finalBuffer->buffer[finalBuffer->pointer] = '\0';
+	length = length + 1;
+}
+
+String::String() {
+}
+
+String::String(const char* str) {
+	set(str);
+}
+
+void String::operator =(const char* str) {
+	set(str);
+}
+
+char* String::getValue() {
+	return isSSO ? defaultBuffer : dynamicBuffer;
+}
+
+const char* String::getValueConst() const {
+	return isSSO ? defaultBuffer : dynamicBuffer;
+}
+
+void String::set(const char* str) {
+	int inLen = strlen(str);
+
+	if (inLen <= SSO_SIZE) {
+		isSSO = true;
+		memcpy(defaultBuffer, str, inLen);
+		defaultBuffer[inLen] = '\0';
+	} else {
+		if (capacity >= inLen) {
+			memcpy(dynamicBuffer, str, inLen);
+		} else {
+			free();
+			capacity = MathHelper::nearestPowerOfTwo(inLen + 1);
+			dynamicBuffer = new char[capacity];
+			memcpy(dynamicBuffer, str, inLen);
+		}
+		isSSO = false;
+		dynamicBuffer[inLen] = '\0';
+	}
+
+	length = inLen;
+}
+
+void String::free() {
+	capacity = 0;
+	length = 0;
+	isSSO = true;
+	if (dynamicBuffer != nullptr) {
+		delete dynamicBuffer;
+	}
+
+	defaultBuffer[0] = '\0';
+}
+
+int String::toInteger() {
+	return atoi(getValue());
+}
+
+float String::toFloat() {
+	return static_cast<float>(atof(getValue()));
+}
+
+int String::indexOf(char c) {
+	char* buffer = getValue();
+	int idx = 0;
+	while (buffer[idx] != '\0') {
+		if (buffer[idx] == c) {
+			return idx;
+		}
+		idx++;
+	}
+
+	return -1;
+}
+
+StringView String::substring(int start, int end) {
+	StringView retval;
+	if (start >= length) {
+		retval.error = true;
+		return retval;
+	}
+
+	if (end >= length) {
+		end = length - 1;
+	}
+
+	char* value = getValue();
+	retval.value = &value[start];
+	retval.length = end - start;
+	return retval;
+}
diff --git a/frontend/transpiler/MyString.h b/frontend/transpiler/MyString.h
new file mode 100644
index 0000000..d05d84a
--- /dev/null
+++ b/frontend/transpiler/MyString.h
@@ -0,0 +1,76 @@
+#pragma once
+#include "List.h"
+
+struct StringView {
+	bool error = false;
+	char* value = nullptr;
+	size_t length = 0;
+};
+
+struct String {
+	const static int SSO_SIZE = 31;
+
+	char defaultBuffer[String::SSO_SIZE + 1] = { '\0' };
+	char* dynamicBuffer = nullptr;
+
+	int length = 0;
+	int capacity = 0;
+	bool isSSO = true;
+
+	String();
+	String(const char* str);
+	char* getValue();
+	const char* getValueConst() const;
+	void operator =(const char* str);
+	void set(const char* str);
+	void free();
+	int toInteger();
+	float toFloat();
+	int indexOf(char c);
+	inline bool equals(const String& other) { return strcmp(getValueConst(), other.getValueConst()) == 0; };
+	inline bool equalsCstr(const char* str) { return strcmp(getValueConst(), str) == 0; };
+	StringView substring(int start, int end);
+};
+
+struct StringBuffer {
+	const static int BUFFER_SIZE = 31;
+
+	int pointer = 0;
+	char buffer[StringBuffer::BUFFER_SIZE + 1]; // Leave space for trailing escape character
+
+	/* 
+	* Appends the string to the buffer
+	* @param str
+	* @returns number of characters copied
+	*/
+	int add(const char* str);
+	bool isFull();
+	void reset();
+};
+
+struct StringBuilder {
+	int bufferPointer = 0;
+	int length = 0;
+
+	StringBuffer defaultBuffer;
+	List<StringBuffer> dynamicBuffer;
+
+	StringBuffer* getCurrentBuffer();
+	StringBuffer* getBufferAtIdx(int index);
+	const StringBuffer* getBufferAtIdxConst(int index) const;
+	void addStr(String* str);
+	void addStr(const char* str);
+	void addChar(char c);
+	void format(const char* str, ...);
+	void addInt(int value);
+	void addFloat(float value);
+	void replace(const char* strToReplace, const char* replaceStr);
+	void removeAt(int index, int count);
+	int indexOf(char c);
+	int indexOf(const char* str);
+	String toString();
+	void clear();
+	char getCharAtIdx(int index) const;
+	void insert(char c, int index);
+	void free();
+};
\ No newline at end of file
diff --git a/frontend/transpiler/build.sh b/frontend/transpiler/build.sh
new file mode 100755
index 0000000..6ca478e
--- /dev/null
+++ b/frontend/transpiler/build.sh
@@ -0,0 +1 @@
+g++ -o transpiler transpiler.cpp Logger.cpp MyString.cpp
\ No newline at end of file
diff --git a/frontend/transpiler/pages.txt b/frontend/transpiler/pages.txt
new file mode 100644
index 0000000..caa420c
--- /dev/null
+++ b/frontend/transpiler/pages.txt
@@ -0,0 +1,14 @@
+&#127936; "2D"
+>"Rigidbody"
+>>/2d/_rigidbody/part_1.html "Linear Forces"
+>>/2d/_rigidbody/part_2.html "Rotational Forces"
+>>/2d/_rigidbody/part_3.html "Collision Forces"
+>"Collisions"
+>>/2d/_collisions/circle_line.html "Circle-Line"
+>>/2d/_collisions/rectangle_line.html "Rectangle-Line"
+>>/2d/_collisions/pill_line.html "Pill-Line"
+&#127776; "3D"
+&#128295; "WebAssembly"
+>>/intro/intro.html "Introduction"
+&#128712; "About"
+>>/roadmap.html "Roadmap"
diff --git a/frontend/transpiler/transpiler b/frontend/transpiler/transpiler
new file mode 100755
index 0000000..2f2d00b
Binary files /dev/null and b/frontend/transpiler/transpiler differ
diff --git a/frontend/transpiler/transpiler.cpp b/frontend/transpiler/transpiler.cpp
new file mode 100644
index 0000000..4771314
--- /dev/null
+++ b/frontend/transpiler/transpiler.cpp
@@ -0,0 +1,249 @@
+#include "MyString.h"
+#include "List.h"
+#include "Logger.h"
+#include <cstdio>
+#include <vector>
+#include <cstring>
+
+struct InnerCategory {
+    bool isLabel = false;
+    String label;
+    String path;
+
+    void toCode(StringBuilder& sb) {
+        if (isLabel) {
+            sb.format("\t\t\t\t\t<li><label>%s</label></li>\n", label.getValue());
+        } else {
+            sb.format("\t\t\t\t\t<li><a href=\"%s\">%s</a></li>\n", path.getValue(), label.getValue());
+        }
+    }
+
+    void free() {
+        label.free();
+        path.free();
+    }
+};
+
+struct OuterCategory {
+    String outerName;
+    String outerSymbol;
+    List<InnerCategory> items;
+
+    void toCode(StringBuilder& sb) {
+        sb.addStr("\t\t\t<li>\n");
+        sb.format("\t\t\t\t<span>%s<span>%s</span></span>\n", outerSymbol.getValue(), outerName.getValue());
+        sb.addStr("\t\t\t\t<ul class=\"inner-tree\">\n");
+
+        FOREACH(items) {
+            value->toCode(sb);
+        }
+        
+        sb.addStr("\t\t\t\t</ul>\n");
+        sb.addStr("\t\t\t</li>\n");
+    }
+
+    void reset() {
+        outerName.free();
+        outerSymbol.free(); 
+        items.clear();
+    }
+
+    void free() {
+        outerName.free();
+        outerSymbol.free();
+
+        FOREACH(items) {
+            value->free();
+        }
+        items.deallocate();
+    }
+};
+
+int main() {
+    logger_info("Running transpiler");
+    FILE* pagesFile = fopen("transpiler/pages.txt", "r+");
+
+    char * cLine = NULL;
+    size_t len = 0;
+    ssize_t read;
+
+    StringBuilder sb;
+    StringBuilder otherSb;
+
+    sb.addStr("\t\t<nav>\n\t\t<ul class=\"outer-tree\">\n");
+    sb.addStr("\t\t\t<li><a href=\"/\">Introduction</a></li>\n");
+
+    bool isFirst = true;
+    OuterCategory oc;
+    List<String> servedFiles;
+
+    servedFiles.add("./index.html");
+    while ((read = getline(&cLine, &len, pagesFile)) != -1) {
+        char* line = cLine;
+
+        int indent = 0;
+        while (line[0] == '>') { 
+            line++;
+            indent++;
+        }
+
+        switch (indent) {
+            case 0: {
+                if (!isFirst) {
+                    oc.toCode(sb);
+                }
+                oc.reset();
+
+                isFirst = false;
+                // Outer tree item
+                while (line[0] != ' ') {
+                    otherSb.addChar(line[0]);
+                    line++;
+                }
+                line++;
+
+                oc.outerSymbol = otherSb.toString();
+                otherSb.clear();
+
+                while (line[0] != '\n') {
+                    if (line[0] != '\"') {
+                        otherSb.addChar(line[0]);
+                    }
+                    line++;
+                }
+
+                oc.outerName = otherSb.toString();
+                logger_info(oc.outerName.getValue());
+                otherSb.clear();
+
+                break;
+            }
+            case 1: {
+                InnerCategory ic;
+                ic.isLabel = true;
+                while (line[0] != '\n') {
+                    if (line[0] != '\"') {
+                        otherSb.addChar(line[0]);
+                    }
+                    line++;
+                }
+                ic.label = otherSb.toString();
+                otherSb.clear();
+                oc.items.add(ic);
+
+                break;
+            }
+            case 2: {
+                InnerCategory ic;
+                ic.isLabel = false;
+
+                while (line[0] != ' ') {
+                    otherSb.addChar(line[0]);
+                    line++;
+                }
+                line++;
+
+                ic.path = otherSb.toString();
+                otherSb.insert('.', 0);
+                String pathCopy = otherSb.toString();
+                servedFiles.add(pathCopy);
+                otherSb.clear();
+
+
+                while (line[0] != '\n') {
+                    if (line[0] != '\"') {
+                        otherSb.addChar(line[0]);
+                    }
+                    line++;
+                }
+
+                ic.label = otherSb.toString();
+                otherSb.clear();
+                oc.items.add(ic);
+
+                break;
+            }
+        }
+    }
+
+    oc.toCode(sb);
+    sb.addStr("\t\t</ul>\n\t\t</nav>\n");
+    oc.reset();
+    fclose(pagesFile);
+
+    String navbarRetval = sb.toString();
+    sb.clear();
+
+    logger_info("Creating served files");
+
+    // Create the header
+    FOREACH(servedFiles) {
+        otherSb.clear();
+        otherSb.format("%s.content", value->getValue());
+        String contentFileName = otherSb.toString();
+        FILE* contentFile = fopen(contentFileName.getValue(), "r+");
+        if (contentFile == NULL) {
+            logger_warning("Could not output file, most likely unimplemented: %s", contentFileName.getValue());
+            continue;
+        }
+
+        logger_info("Outputting file: %s", contentFileName.getValue());
+
+        // Header
+        sb.addStr("<!DOCTYPE html>\n"
+            "<html lang=\"en\">\n"
+            "\t<head>\n"
+            "\t\t<meta charset=\"utf-8\">\n"
+            "\t\t<link rel=\"stylesheet\" href=\"/index.css\">\n"  
+            "\t\t<title>Physics for Games</title>\n"
+            "\t\t<link rel=\"shortcut icon\" href=\"favicon/favicon.ico\" type=\"image/x-icon\">\n"
+            "\t</head>\n"
+            "\t<body>\n"
+            "\t\t<header>\n"
+            "\t\t\t<h1>Physics for Games</h1>\n"
+            "\t\t</header>\n"
+            "\t\t<main>\n");
+
+        // Write navigation bar
+        sb.addStr(navbarRetval.getValue());
+
+        // Read body
+
+        fseek(contentFile, 0, SEEK_END);
+        long fsize = ftell(contentFile);
+        fseek(contentFile, 0, SEEK_SET);
+
+        char* bodyContent = new char[fsize + 1];
+        fread(bodyContent, 1, fsize, contentFile);
+        sb.addStr(bodyContent);
+        delete [] bodyContent;
+
+        contentFileName.free();
+        fclose(contentFile);
+
+        // Footer
+        sb.addStr("\t\t</main>\n"
+            "\t</body>\n"
+            "</html>\n");
+
+        FILE* outFile = fopen(value->getValue(), "w+");
+        String outStr = sb.toString();
+        fwrite(outStr.getValue(), 1, outStr.length, outFile);
+        outStr.free();
+        fclose(outFile);
+        sb.clear();
+    }
+
+    logger_info("Outputting files to their proper directories...");
+    sb.free();
+    navbarRetval.free();
+    otherSb.free();
+
+    FOREACH(servedFiles) {
+        value->free();
+    }
+
+    servedFiles.deallocate();
+
+    return 0;
+}   
\ No newline at end of file
-- 
cgit v1.2.1