From fa5d805eefbcc86e3de5d6e4044937d7f7670f0b Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Mon, 13 Oct 2025 09:16:28 +1100 Subject: [PATCH] Refactoring (MAY BE BUGGY) --- Bobfile | 5 - Makefile | 22 + README.md | 8 +- src/data/data.cpp | 244 +++ src/data/data.h | 66 + src/error/error.cpp | 34 + src/error/error.h | 6 + src/interpreter/interpreter.cpp | 2003 +++++++++++++++++++++ src/interpreter/interpreter.h | 7 + src/lexer/lexer.cpp | 81 + src/lexer/lexer.h | 6 + src/main.cpp | 2918 +------------------------------ src/main.h | 250 +++ src/parser/parser.cpp | 184 ++ src/parser/parser.h | 7 + tests/args.grnd | 4 +- tests/everything.grnd | 14 +- tests/exists.grnd | 7 +- tests/functions.grnd | 10 +- tests/lists.grnd | 6 +- 20 files changed, 2953 insertions(+), 2929 deletions(-) delete mode 100644 Bobfile create mode 100644 Makefile create mode 100644 src/data/data.cpp create mode 100644 src/data/data.h create mode 100644 src/error/error.cpp create mode 100644 src/error/error.h create mode 100644 src/interpreter/interpreter.cpp create mode 100644 src/interpreter/interpreter.h create mode 100644 src/lexer/lexer.cpp create mode 100644 src/lexer/lexer.h create mode 100644 src/main.h create mode 100644 src/parser/parser.cpp create mode 100644 src/parser/parser.h diff --git a/Bobfile b/Bobfile deleted file mode 100644 index 62187c9..0000000 --- a/Bobfile +++ /dev/null @@ -1,5 +0,0 @@ -compiler "g++"; -binary "ground"; -source "src/main.cpp"; -flag "Ofast"; -compile; diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a06dc3d --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +CXX = g++ +CXXFLAGS = -O3 -Isrc -Wall -Wextra -std=c++17 + +BUILD_DIR = build +SRCS = $(shell find src -name '*.cpp') +VPATH = $(sort $(dir $(SRCS))) +OBJS = $(addprefix $(BUILD_DIR)/, $(notdir $(SRCS:.cpp=.o))) +TARGET = ground + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) + +$(BUILD_DIR)/%.o: %.cpp + @mkdir -p $(BUILD_DIR) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -rf $(BUILD_DIR) $(TARGET) + +.PHONY: all clean diff --git a/README.md b/README.md index 7523c68..36514e3 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,7 @@ Ground is an interpreter which processes and interprets Ground instructions. It ## How do I get started? -Clone the repo and compile with your favourite C++ compiler: - -``` - g++ src/main.cpp -std=c++17 -O3 -o ground -``` - -(You can omit the -std flag on systems which default to the latest standard, and the -O3 flag if you're fine with a slightly slower interpreter.) +Clone the repo and compile with `make`. Run a Ground program: diff --git a/src/data/data.cpp b/src/data/data.cpp new file mode 100644 index 0000000..ee07f66 --- /dev/null +++ b/src/data/data.cpp @@ -0,0 +1,244 @@ +#include "data.h" +#include "../main.h" +#include "../error/error.h" +#include +#include +#include +#include + +/* + labelStack stack + Allows each function to hold it's own set of labels +*/ +std::stack> labelStack; + +/* + variables map + Contains all variables made while running the program. See also Literal struct. +*/ +std::map variables; + +/* + functions map + Contains the code of functions and types of their values +*/ +std::map functions; + +/* + structs map + Contains structs (see the Struct struct for more info) +*/ +std::map structs; + +/* + fnArgs vector + Containst the arguments to be passed to a function +*/ +std::vector fnArgs; + +// External library functions and other things + +// Handle to loaded libraries +std::map loadedLibraries; + +// Map of function name to function pointer +std::map externalFunctions; + +// Libraries currently imported +std::vector libraries; + +// Conversion functions +GroundValue literalToGroundValue(const Literal& lit) { + GroundValue gv; + if (std::holds_alternative(lit.val)) { + gv.type = GROUND_INT; + gv.data.int_val = std::get(lit.val); + } else if (std::holds_alternative(lit.val)) { + gv.type = GROUND_DOUBLE; + gv.data.double_val = std::get(lit.val); + } else if (std::holds_alternative(lit.val)) { + gv.type = GROUND_BOOL; + gv.data.bool_val = std::get(lit.val) ? 1 : 0; + } else if (std::holds_alternative(lit.val)) { + gv.type = GROUND_STRING; + gv.data.string_val = const_cast(std::get(lit.val).c_str()); + } else if (std::holds_alternative(lit.val)) { + gv.type = GROUND_CHAR; + gv.data.char_val = std::get(lit.val); + } + return gv; +} + +/* + catches stackmap + This stores all catches in a scope, to catch errors before they happen. +*/ +std::stack> catches; + +Literal groundValueToLiteral(const GroundValue& gv) { + Literal lit; + switch (gv.type) { + case GROUND_INT: + lit.val = gv.data.int_val; + break; + case GROUND_DOUBLE: + lit.val = gv.data.double_val; + break; + case GROUND_BOOL: + lit.val = (gv.data.bool_val != 0); + break; + case GROUND_STRING: + lit.val = std::string(gv.data.string_val); + break; + case GROUND_CHAR: + lit.val = gv.data.char_val; + break; + } + return lit; +} + +/* + is* functions + These functions determine if a string value can be converted into a different type. +*/ + +bool isInt(std::string in) { + try { + std::stoi(in); + if (std::stod(in) != std::stoi(in)) return false; + return true; + } catch (...) { + return false; + } +} + +bool isDouble(std::string in) { + try { + std::stod(in); + return true; + } catch (...) { + return false; + } +} + +bool isBool(std::string in) { + if (in == "true" || in == "false") return true; + else return false; +} + +bool isString(std::string in) { + if (in.size() >= 2 && in[0] == '"' && in[in.size() - 1] == '"') return true; + else return false; +} + +bool isChar(std::string in) { + if (in.size() == 3 && in[0] == '\'' && in[in.size() - 1] == '\'') return true; + else return false; +} + +bool isValue(std::string in) { + if (in.size() >= 1 && in[0] == '$') return true; + else return false; +} + +bool isDirect(std::string in) { + if (in.size() >= 1 && in[0] == '&') return true; + else return false; +} + +bool isLine(std::string in) { + if (in.size() >= 1 && in[0] == '%') return true; + else return false; +} + +bool isLabel(std::string in) { + if (in.size() >= 1 && in[0] == '@') return true; + else return false; +} + +bool isType(std::string in) { + if (in.size() > 1 && in[0] == '-') return true; + else return false; +} + +bool isFunction(std::string in) { + if (in.size() >= 1 && in[0] == '!') return true; + else return false; +} + +bool isReferencingStruct(std::string in) { + return in.find('.') != std::string::npos; +} + +/* + getType function + This function determines the type of a value inside a string based on the is* + functions above. Returns a type from the Types enum class. +*/ +Types getType(std::string in) { + if (isInt(in)) return Types::Int; + if (isDouble(in)) return Types::Double; + if (isBool(in)) return Types::Bool; + if (isString(in)) return Types::String; + if (isChar(in)) return Types::Char; + if (isValue(in)) return Types::Value; + if (isDirect(in)) return Types::Direct; + if (isLine(in)) return Types::Line; + if (isLabel(in)) return Types::Label; + if (isType(in)) return Types::Type; + if (isFunction(in)) return Types::Function; + error("Could not determine type of \"" + in + "\""); + return Types::Int; +} + +/* + getLitType function + This function determines the type of a value inside a Literal based on the + holds_alternative() function. Returns a type from the Types enum class. +*/ +Types getLitType(Literal in) { + if (std::holds_alternative(in.val)) return Types::Int; + if (std::holds_alternative(in.val)) return Types::Double; + if (std::holds_alternative(in.val)) return Types::Bool; + if (std::holds_alternative(in.val)) return Types::String; + if (std::holds_alternative(in.val)) return Types::Char; + if (std::holds_alternative(in.val)) return Types::List; + return Types::Other; +} + +/* + setVal function + This function sets the value of a variable (whether in a struct or not). +*/ +void setVal(std::string varName, Literal value) { + if (isReferencingStruct(varName)) { + std::string structName; + std::string varInStruct; + + size_t dotPos = varName.find('.'); // Use size_t + if (dotPos != std::string::npos) { + structName = varName.substr(0, dotPos); + varInStruct = varName.substr(dotPos + 1); + if (variables.find(structName) != variables.end()) { + if (std::holds_alternative(variables[structName].val)) { + Struct structVal = std::get(variables[structName].val); + if (structVal.values.find(varInStruct) != structVal.values.end()) { + structVal.values[varInStruct] = value; + variables[structName].val = structVal; // Write back the modified struct + } else { + error("Could not find property '" + varInStruct + "' in struct '" + structName + "'"); + } + } else { + error("Variable '" + structName + "' is not a struct"); + } + } else { + error("Could not find struct '" + structName + "'"); + } + } else { + error("Invalid struct member access syntax"); + } + } else { + // Handle regular variables (both existing and new) + variables[varName] = value; + } +} diff --git a/src/data/data.h b/src/data/data.h new file mode 100644 index 0000000..b624165 --- /dev/null +++ b/src/data/data.h @@ -0,0 +1,66 @@ +#pragma once +#include "../main.h" +#include +#include +#include +#include + +/* + functions map + Contains the code of functions and types of their values +*/ +extern std::map functions; + +/* + structs map + Contains structs (see the Struct struct for more info) +*/ +extern std::map structs; + +/* + fnArgs vector + Containst the arguments to be passed to a function +*/ +extern std::vector fnArgs; + +// External library functions and other things + +// Handle to loaded libraries +extern std::map loadedLibraries; + +// Map of function name to function pointer +extern std::map externalFunctions; + +// Libraries currently imported +extern std::vector libraries; + +// catches stackmap +extern std::stack> catches; + +// labelStack stack +extern std::stack> labelStack; + +// variables map +extern std::map variables; + +GroundValue literalToGroundValue(const Literal& lit); +Literal groundValueToLiteral(const GroundValue& gv); +void setVal(std::string varName, Literal value); + +bool isInt(std::string in); +bool isDouble(std::string in); +bool isBool(std::string in); +bool isString(std::string in); +bool isChar(std::string in); +bool isValue(std::string in); +bool isDirect(std::string in); +bool isLine(std::string in); +bool isLabel(std::string in); +bool isType(std::string in); +bool isFunction(std::string in); +bool isReferencingStruct(std::string in); + +Types getType(std::string in); +Types getLitType(Literal in); + +void setVal(std::string varName, Literal value); diff --git a/src/error/error.cpp b/src/error/error.cpp new file mode 100644 index 0000000..94a75fc --- /dev/null +++ b/src/error/error.cpp @@ -0,0 +1,34 @@ +#include +#include +#include "error.h" +#include "../data/data.h" +#include "../main.h" + +using namespace std; + +/* + error function + Takes a string (which is a debug message) and prints it to the console, letting the + user know what went wrong with the program. +*/ +Literal error(string in, string errCode, int exitc) { + Error retError; + retError.code = errCode; + retError.pops = 0; + while (catches.size() > 0) { + if (catches.top().find(errCode) != catches.top().end()) { + Literal tmpLit; + tmpLit.val = false; + setVal(catches.top()[errCode].varName, tmpLit); + retError.reporter = catches.top()[errCode].varName; + Literal tmpLit2; + tmpLit2.val = retError; + return tmpLit2; + } else { + catches.pop(); + retError.pops ++; + } + } + cout << "\033[31m" + errCode + ": \033[39m" << in << endl; + exit(exitc); +} diff --git a/src/error/error.h b/src/error/error.h new file mode 100644 index 0000000..68b16c0 --- /dev/null +++ b/src/error/error.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../main.h" +#include + +Literal error(std::string in, std::string errCode = "syntaxError", int exitc = 1); diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp new file mode 100644 index 0000000..c688e70 --- /dev/null +++ b/src/interpreter/interpreter.cpp @@ -0,0 +1,2003 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "interpreter.h" +#include "../main.h" +#include "../data/data.h" +#include "../error/error.h" +#include "../parser/parser.h" +#include "../lexer/lexer.h" + +using namespace std; + +bool processingFunction = false; +string procFnName = ""; + +bool processingStruct = false; +string procStructName = ""; + +bool inFunction = false; + + +// Stack of strings for keeping track of which thing we're importing +stack importing; + +void preProcessLabels(vector instructions) { + map labels; + int definingFunction = 0; + for (size_t i = 0; i < instructions.size(); i++) { + if (instructions[i].isLabel && definingFunction == 0) { + labels[instructions[i].label.id] = i; + } else if (instructions[i].inst == Instructions::Fun) { + definingFunction++; + } else if (instructions[i].inst == Instructions::Endfun) { + definingFunction--; + } + } + labelStack.push(labels); +} + +/* + exec function + This function takes a list of instructions (see Instruction struct above and parser + function below) and acts upon their properties. This is the main interpreter + function for the program. +*/ +Literal exec(vector in, bool executingFunction) { + { + map tmp; + catches.push(tmp); + } + for (size_t i = 0; i < in.size(); i++) { + Instruction l = in[i]; + if (processingFunction) { + if (l.inst == Instructions::Endfun) { + processingFunction = false; + procFnName = ""; + continue; + } else { + if (processingStruct) { + structs[procStructName].functions[procFnName].instructions.push_back(l); + } + functions[procFnName].instructions.push_back(l); + continue; + } + } else if (processingStruct) { + if (l.inst == Instructions::Endstruct) { + processingStruct = false; + procStructName = ""; + continue; + } else if (l.inst == Instructions::Fun) { + if (l.args.size() < 2) { + return error("Could not find all arguments required for Fun inbuilt"); + } + Function newFunction; + + if (holds_alternative(l.args[0])) { + newFunction.returnType = get(l.args[0]).type; + } else { + return error("First argument of function must be a type reference"); + } + + string fnName; + + if (holds_alternative(l.args[1])) { + fnName = get(l.args[1]).fnName; + } else { + return error("Second argument of function must be a function reference"); + } + + if (importing.size() > 0) { + fnName = importing.top() + ":" + fnName; + } + + // Parse function arguments (type-direct pairs) + if ((l.args.size() - 2) % 2 != 0) { + return error("Function arguments must be in type-direct pairs"); + } + + for (size_t m = 2; m < l.args.size(); m += 2) { + FnArg newArg; + + // Get type + if (holds_alternative(l.args[m])) { + newArg.type = get(l.args[m]).type; + } else { + return error("Expected type reference in function argument definition"); + } + + // Get direct reference + if (m + 1 < l.args.size() && holds_alternative(l.args[m + 1])) { + newArg.ref = get(l.args[m + 1]); + } else { + return error("Expected direct reference after type reference in function argument definition"); + } + + newFunction.args.push_back(newArg); + } + + structs[procStructName].functions[fnName] = newFunction; + processingFunction = true; + procFnName = fnName; + } else if (l.inst == Instructions::Init) { + if (l.args.size() < 2) { + return error("Could not find all arguments required for Init inbuilt"); + } + + Direct varRef; + TypeRef type; + if (holds_alternative(l.args[0])) { + varRef = get(l.args[0]); + } else { + return error("First argument of init must be a direct reference"); + } + if (holds_alternative(l.args[1])) { + type = get(l.args[1]); + } else { + return error("Second argument of init must be a type reference"); + } + + Literal newVal; + if (type.isCustomType) { + newVal.val = structs[type.customType]; + } else { + switch (type.type) { + case Types::Int: + newVal.val = 0; + break; + case Types::Double: + newVal.val = double(0.0); + break; + case Types::String: + newVal.val = ""; + break; + case Types::Char: + newVal.val = '\0'; + break; + case Types::Bool: + newVal.val = false; + break; + case Types::List: + { + List newList; + newVal.val = newList; + } + break; + default: + return error("You dingus you werent supposed to get here"); + } + } + structs[procStructName].values[varRef.varName] = newVal; + } + continue; + } + + // Pre process value references and labels + for (size_t j = 0; j < l.args.size(); j++) { + + if (holds_alternative(l.args[j])) { + string varName = get(l.args[j]).varName; + if (variables.find(varName) != variables.end()) { + l.args[j] = variables[varName]; + } else { + if (isReferencingStruct(varName)) { + string structName; + string varInStruct; + + int dotPos = varName.find('.'); + if (dotPos != string::npos) { + structName = varName.substr(0, dotPos); + varInStruct = varName.substr(dotPos + 1); + if (variables.find(structName) != variables.end()) { + if (holds_alternative(variables[structName].val)) { + Struct structVal = get(variables[structName].val); + if (structVal.values.find(varInStruct) != structVal.values.end()) { + l.args[j] = structVal.values[varInStruct]; + } else { + return error("Could not find property '" + varInStruct + "' in struct '" + structName + "'"); + } + } else { + return error("Variable '" + structName + "' is not a struct"); + } + } else { + return error("Could not find struct '" + structName + "'"); + } + } else { + return error("Invalid struct member access syntax"); + } + } else { + return error("Could not find variable " + varName); + } + } + } else if (holds_alternative(l.args[j])) { + Line ln = get(l.args[j]); + if (ln.isLabel) { + if (labelStack.top().find(ln.label) != labelStack.top().end()) { + Line newLine; + newLine.lineNum = labelStack.top()[ln.label]; + l.args[j] = newLine; + } else { + if (l.inst != Instructions::Exists) return error("Could not find label " + ln.label); + } + } + } + } + switch (l.inst) { + // Ignore empty lines + case Instructions::Empty: + break; + /* + stdout instruction + This instruction prints characters to the console. It obtains the value + from the variant and prints it. + */ + case Instructions::Stdout: + if (l.args.size() < 1) { + return error("Could not find argument for Stdout inbuilt"); + } + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + cout << get(get(l.args[0]).val); + } + else if (holds_alternative(get(l.args[0]).val)) { + cout << get(get(l.args[0]).val); + } + else if (holds_alternative(get(l.args[0]).val)) { + cout << get(get(l.args[0]).val); + } + else if (holds_alternative(get(l.args[0]).val)) { + if (get(get(l.args[0]).val) == true) { + cout << "true"; + } else { + cout << "false"; + } + } + else if (holds_alternative(get(l.args[0]).val)) { + cout << get(get(l.args[0]).val); + } + else if (holds_alternative(get(l.args[0]).val)) { + List list = get(get(l.args[0]).val); + cout << "["; + for (size_t l = 0; l < list.val.size(); l++) { + if (holds_alternative(list.val[l].val)) { + cout << '"' << get(list.val[l].val) << '"'; + } + else if (holds_alternative(list.val[l].val)) { + cout << get(list.val[l].val); + } + else if (holds_alternative(list.val[l].val)) { + cout << get(list.val[l].val); + } + else if (holds_alternative(list.val[l].val)) { + if (get(list.val[l].val) == true) { + cout << "true"; + } else { + cout << "false"; + } + } + else if (holds_alternative(list.val[l].val)) { + cout << '\'' << get(list.val[l].val) << '\''; + } + else { + return error("Couldn't print that", "printError"); + } + if (l != list.val.size() - 1) { + cout << ", "; + } + } + cout << "]" << endl; + } + else { + return error("Couldn't print that", "printError"); + } + } else { + return error("Argument of stdlnout must be a value (literal or a value reference)"); + } + break; + /* + stdlnout instruction + This instruction prints characters to the console. It obtains the value + from the variant and prints it, with a new line at the end. + */ + case Instructions::Stdlnout: + if (l.args.size() < 1) { + return error("Could not find argument for Stdout inbuilt"); + } + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + cout << get(get(l.args[0]).val) << endl; + } + else if (holds_alternative(get(l.args[0]).val)) { + cout << get(get(l.args[0]).val) << endl; + } + else if (holds_alternative(get(l.args[0]).val)) { + cout << get(get(l.args[0]).val) << endl; + } + else if (holds_alternative(get(l.args[0]).val)) { + if (get(get(l.args[0]).val) == true) { + cout << "true" << endl; + } else { + cout << "false" << endl; + } + } + else if (holds_alternative(get(l.args[0]).val)) { + cout << get(get(l.args[0]).val) << endl; + } + else if (holds_alternative(get(l.args[0]).val)) { + List list = get(get(l.args[0]).val); + cout << "["; + for (size_t l = 0; l < list.val.size(); l++) { + if (holds_alternative(list.val[l].val)) { + cout << '"' << get(list.val[l].val) << '"'; + } + else if (holds_alternative(list.val[l].val)) { + cout << get(list.val[l].val); + } + else if (holds_alternative(list.val[l].val)) { + cout << get(list.val[l].val); + } + else if (holds_alternative(list.val[l].val)) { + if (get(list.val[l].val) == true) { + cout << "true"; + } else { + cout << "false"; + } + } + else if (holds_alternative(list.val[l].val)) { + cout << '\'' << get(list.val[l].val) << '\''; + } + else { + return error("Couldn't print that", "printError"); + } + if (l != list.val.size() - 1) { + cout << ", "; + } + } + cout << "]" << endl; + } + else { + return error("Couldn't print that", "printError"); + } + } else { + return error("Argument of stdlnout must be a value (literal or a value reference) or a list reference"); + } + break; + /* + error instruction + This instruction outputs a custom error message. + */ + case Instructions::Error: + if (l.args.size() < 2) { + return error("Could not find arguments for Error inbuilt"); + } + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val) && holds_alternative(get(l.args[1]).val)) { + return error(get(get(l.args[0]).val), get(get(l.args[1]).val)); + } else { + return error("Argument of error must be a string"); + } + } else { + return error("Argument of error must be a string"); + } + break; + /* + catch instruction + This instruction ensures that errors in programs are caught and dealt with appropriately. + */ + case Instructions::Catch: + if (l.args.size() < 2) { + return error("Could not find all arguments for Catch inbuilt"); + } + + { + string errCode; + Direct varRef; + + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + errCode = get(get(l.args[0]).val); + } else { + return error("First argument of catch must be a string literal"); + } + } else { + return error("First argument of catch must be a string literal"); + } + + if (holds_alternative(l.args[1])) { + varRef = get(l.args[1]); + } else { + return error("Second argument of catch must be a direct reference"); + } + + catches.top()[errCode] = varRef; + Literal tmpLit; + tmpLit.val = true; + setVal(varRef.varName, tmpLit); + } + break; + /* + set instruction + This instruction sets a variable to a provided value. + */ + case Instructions::Set: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Set inbuilt"); + } + { + Direct varRef; + Literal varContents; + + if (holds_alternative(l.args[0])) { + varRef = get(l.args[0]); + } else { + return error("First argument of set must be a direct reference"); + } + + if (holds_alternative(l.args[1])) { + varContents = get(l.args[1]); + } else { + return error("Second argument of set must be a value (literal or value reference)"); + } + + setVal(varRef.varName, varContents); + } + break; + case Instructions::Init: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Init inbuilt"); + } + { + Direct varRef; + TypeRef type; + if (holds_alternative(l.args[0])) { + varRef = get(l.args[0]); + } else { + return error("First argument of init must be a direct reference"); + } + if (holds_alternative(l.args[1])) { + type = get(l.args[1]); + } else { + return error("Second argument of init must be a type reference"); + } + + Literal newVal; + if (type.isCustomType) { + if (structs.find(type.customType) == structs.end()) { + return error("Unknown struct type: " + type.customType); + } + + Struct newStructInstance = structs[type.customType]; + newVal.val = newStructInstance; + } else { + switch (type.type) { + case Types::Int: + newVal.val = 0; + break; + case Types::Double: + newVal.val = double(0.0); + break; + case Types::String: + newVal.val = ""; + break; + case Types::Char: + newVal.val = '\0'; + break; + case Types::Bool: + newVal.val = false; + break; + case Types::List: + { + List newList; + newVal.val = newList; + } + break; + default: + return error("You dingus you werent supposed to get here"); + } + } + variables[varRef.varName] = newVal; + } + break; + /* + setlist instruction + This instruction takes a potentially infinite amount of arguments and + saves them to a list (vector) with name listName. + */ + case Instructions::Setlist: + if (l.args.size() < 1) { + return error("Could not find all arguments required for Setlist inbuilt"); + } + { + string listName; + List listContents; + + if (holds_alternative(l.args[0])) { + listName = get(l.args[0]).varName; + } else { + return error("First argument of setlist must be a list reference"); + } + + bool first = true; + for (argument k : l.args) { + if (holds_alternative(k)) { + listContents.val.push_back(get(k)); + } else { + if (!first) return error("All arguments after first in setlist must be values"); + first = false; + } + } + Literal tmpLit; + tmpLit.val = listContents; + setVal(listName, tmpLit); + } + break; + /* + getlistat instruction + This instruction gets a list item from a list and an index and saves the + value to a variable. + */ + case Instructions::Getlistat: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Getlistat inbuilt"); + } + { + string listref; + int ref; + Direct var; + + if (holds_alternative(l.args[0])) { + listref = get(l.args[0]).varName; + } else { + return error("First argument of getlistat must be a list reference"); + } + + if (holds_alternative(l.args[1])) { + if (holds_alternative(get(l.args[1]).val)) { + ref = get(get(l.args[1]).val); + } else { + return error("Second argument of getlistat must be an integer literal"); + } + } else { + return error("Second argument of getlistat must be an integer literal"); + } + + if (holds_alternative(l.args[2])) { + var = get(l.args[2]); + } else { + return error("Third argument of getlistat must be a direct reference"); + } + + if (variables.find(listref) != variables.end()) { + if (holds_alternative(variables[listref].val)) { + if (get(variables[listref].val).val.size() > ref) { + setVal(var.varName, get(variables[listref].val).val[ref]); + } else { + return error("Index " + to_string(ref) + " out of range of list " + listref, "rangeError"); + } + } else { + return error("Variable " + listref + " is not a list"); + } + } else { + return error("Unknown list: " + listref); + } + } + break; + /* + getstrcharat instruction + This instruction gets a character from a string at an index and saves it + to a variable. + */ + case Instructions::Getstrcharat: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Getstrcharat inbuilt"); + } + { + string instr; + int ref; + Direct var; + + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + instr = get(get(l.args[0]).val); + } else { + return error("First argument of getstrcharat must be a string literal"); + } + } else { + return error("First argument of getstrcharat must be a string literal"); + } + + if (holds_alternative(l.args[1])) { + if (holds_alternative(get(l.args[1]).val)) { + ref = get(get(l.args[1]).val); + } else { + return error("Second argument of getstrcharat must be an integer literal"); + } + } else { + return error("Second argument of getstrcharat must be an integer literal"); + } + + if (holds_alternative(l.args[2])) { + var = get(l.args[2]); + } else { + return error("Third argument of getstrcharat must be a direct reference"); + } + + if (instr.size() > ref) { + Literal newLit; + newLit.val = instr[ref]; + setVal(var.varName, newLit); + } else { + return error("Index " + to_string(ref) + " out of range of string " + instr, "rangeError"); + } + } + break; + /* + setlistat instruction + This instruction sets an item in a list to be a certain value. + */ + case Instructions::Setlistat: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Setlistat inbuilt"); + } + { + string listref; + int ref; + Literal value; + + if (holds_alternative(l.args[0])) { + listref = get(l.args[0]).varName; + } else { + return error("First argument of setlistat must be a list reference"); + } + + if (holds_alternative(l.args[1])) { + if (holds_alternative(get(l.args[1]).val)) { + ref = get(get(l.args[1]).val); + } else { + return error("Second argument of setlistat must be an integer literal"); + } + } else { + return error("Second argument of setlistat must be an integer literal"); + } + + if (holds_alternative(l.args[2])) { + value = get(l.args[2]); + } else { + return error("Third argument of setlistat must be a direct reference"); + } + if (variables.find(listref) != variables.end()) { + if (holds_alternative(variables[listref].val)) { + if (get(variables[listref].val).val.size() > ref) { + List tmpList = get(variables[listref].val); + tmpList.val[ref] = value; + Literal tmpLit; + tmpLit.val = tmpList; + setVal(listref, tmpLit); + } else { + return error("Index " + to_string(ref) + " out of range of list " + listref, "rangeError"); + } + } else { + return error("Variable " + listref + " is not a list"); + } + } else { + return error("Unknown list: " + listref); + } + } + break; + /* + listappend instruction + This instruction appends an item to a list. + */ + case Instructions::Listappend: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Listappend inbuilt"); + } + { + string listref; + Literal value; + + if (holds_alternative(l.args[0])) { + listref = get(l.args[0]).varName; + } else { + return error("First argument of listappend must be a list reference"); + } + + if (holds_alternative(l.args[1])) { + value = get(l.args[1]); + } else { + return error("Second argument of listappend must be a direct reference"); + } + + if (variables.find(listref) != variables.end()) { + if (!holds_alternative(variables[listref].val)) { + return error("Variable " + listref + "is not a list"); + } + List tmpList = get(variables[listref].val); + tmpList.val.push_back(value); + Literal tmpLit; + tmpLit.val = tmpList; + setVal(listref, tmpLit); + } else { + return error("Unknown list: " + listref); + } + } + break; + /* + getlistsize instruction + This instruction saves the size of a list in a variable. + */ + case Instructions::Getlistsize: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Getlistsize inbuilt"); + } + { + string ref; + Direct var; + + if (holds_alternative(l.args[0])) { + ref = get(l.args[0]).varName; + } else { + return error("First argument of getlistsize must be a list reference"); + } + + if (holds_alternative(l.args[1])) { + var = get(l.args[1]); + } else { + return error("Second argument of getlistsize must be a direct reference"); + } + + Literal newLit; + if (variables.find(ref) != variables.end()) { + newLit.val = int(get(variables[ref].val).val.size()); + setVal(var.varName, newLit); + } else { + return error("Couldn't find the list " + ref); + } + + break; + } + /* + getstrsize instruction + This instruction saves the size of a string in a variable. + */ + case Instructions::Getstrsize: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Getstrsize inbuilt"); + } + { + string ref; + Direct var; + + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + ref = get(get(l.args[0]).val); + } else { + return error("First argument of getlistsize must be a string value"); + } + } else { + return error("First argument of getlistsize must be a string value"); + } + + if (holds_alternative(l.args[1])) { + var = get(l.args[1]); + } else { + return error("Second argument of getlistsize must be a direct reference"); + } + + Literal newLit; + newLit.val = int(ref.size()); + setVal(var.varName, newLit); + break; + } + /* + stoi instruction + This function converts a string to an int, and saves it in a variable. + If the string cannot be turned into an integer, there is an error. + */ + case Instructions::Stoi: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Stoi inbuilt"); + } + { + string toConv; + Direct ref; + + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + toConv = get(get(l.args[0]).val); + } else { + return error("First argument of stoi must be a string literal"); + } + } else { + return error("First argument of stoi must be a string literal"); + } + + if (holds_alternative(l.args[1])) { + ref = get(l.args[1]); + } else { + return error("Second argument of stoi must be a direct reference"); + } + + if (isInt(toConv)) { + Literal newLit; + newLit.val = stoi(toConv); + setVal(ref.varName, newLit); + } else { + return error("Cannot convert the value " + toConv + " to an int", "conversionError"); + } + } + break; + /* + stod instruction + This function converts a string to a decimal, and saves it in a variable. + If the string cannot be turned into a decimal, there is an error. + */ + case Instructions::Stod: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Stod inbuilt"); + } + { + string toConv; + Direct ref; + + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + toConv = get(get(l.args[0]).val); + } else { + return error("First argument of stod must be a string literal"); + } + } else { + return error("First argument of stod must be a string literal"); + } + + if (holds_alternative(l.args[1])) { + ref = get(l.args[1]); + } else { + return error("Second argument of stod must be a direct reference"); + } + + if (isDouble(toConv) || isInt(toConv)) { + Literal newLit; + newLit.val = stod(toConv); + setVal(ref.varName, newLit); + } else { + return error("Cannot convert the value " + toConv + " to a decimal", "conversionError"); + } + } + break; + /* + tostring instruction + This function converts any type to a string, and saves it in a variable. + */ + case Instructions::Tostring: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Tostring inbuilt"); + } + { + Literal toConv; + Direct ref; + + if (holds_alternative(l.args[0])) { + toConv = get(l.args[0]); + } else { + return error("First argument of tostring must be a literal"); + } + + if (holds_alternative(l.args[1])) { + ref = get(l.args[1]); + } else { + return error("Second argument of tostring must be a direct reference"); + } + + Literal newLit; + if (holds_alternative(toConv.val)) { + newLit.val = to_string(get(toConv.val)); + } else if (holds_alternative(toConv.val)) { + newLit.val = to_string(get(toConv.val)); + } else if (holds_alternative(toConv.val)) { + newLit.val = get(toConv.val); + } else if (holds_alternative(toConv.val)) { + newLit.val = string().append(&get(toConv.val)); + } else if (holds_alternative(toConv.val)) { + if (get(toConv.val)) { + newLit.val = "true"; + } else { + newLit.val = "false"; + } + } + + setVal(ref.varName, newLit); + + } + break; + /* + stdin instruction + This instruction takes input from the standard character input via + the C++ getline() function, and saves it to a variable. + */ + case Instructions::Stdin: + if (l.args.size() < 1) { + return error("Could not find all arguments required for Stdin inbuilt"); + } + if (holds_alternative(l.args[0])) { + Direct varRef = get(l.args[0]); + string userIn; + getline(cin, userIn); + Literal userLit; + userLit.val = userIn; + setVal(varRef.varName, userLit); + } else { + return error("First argument of stdin must be a direct reference"); + } + break; + /* + add instruction + This instruction adds two values (can be numbers or strings) and outputs + it to the direct reference given. + */ + case Instructions::Add: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Add inbuilt"); + } + { + Direct varRef; + Literal left; + Literal right; + Literal final; + + if (holds_alternative(l.args[0])) { + left = get(l.args[0]); + } else { + return error("First argument of add must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[1])) { + right = get(l.args[1]); + } else { + return error("Second argument of add must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[2])) { + varRef = get(l.args[2]); + } else { + return error("Third argument of add must be a direct reference"); + } + + // Figure out types and compute values + if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) + get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) + get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = double(get(left.val)) + get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) + double(get(right.val)); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) + get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val).append(&get(right.val)); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = string().append(&get(left.val)) + get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = string().append(&get(left.val)).append(&get(right.val)); + } else { + return error("Cannot add those two values", "mathError"); + } + + setVal(varRef.varName, final); + } + break; + /* + subtract instruction + This instruction subtracts two values and outputs it to the + direct reference given. + */ + case Instructions::Subtract: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Subtract inbuilt"); + } + { + Direct varRef; + Literal left; + Literal right; + Literal final; + + if (holds_alternative(l.args[0])) { + left = get(l.args[0]); + } else { + return error("First argument of subtract must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[1])) { + right = get(l.args[1]); + } else { + return error("Second argument of subtract must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[2])) { + varRef = get(l.args[2]); + } else { + return error("Third argument of subtract must be a direct reference"); + } + + // Figure out types and compute values + if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) - get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) - get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = double(get(left.val)) - get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) - double(get(right.val)); + } else { + return error("Cannot subtract those two values", "mathError"); + } + + setVal(varRef.varName, final); + } + break; + /* + multiply instruction + This instruction multiplies two numbers and outputs it to the + direct reference given. + */ + case Instructions::Multiply: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Multiply inbuilt"); + } + { + Direct varRef; + Literal left; + Literal right; + Literal final; + + if (holds_alternative(l.args[0])) { + left = get(l.args[0]); + } else { + return error("First argument of multiply must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[1])) { + right = get(l.args[1]); + } else { + return error("Second argument of multiply must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[2])) { + varRef = get(l.args[2]); + } else { + return error("Third argument of multiply must be a direct reference"); + } + + // Figure out types and compute values + if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) * get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) * get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = double(get(left.val)) * get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) * double(get(right.val)); + } else { + return error("Cannot multiply those two values", "mathError"); + } + + setVal(varRef.varName, final); + } + break; + /* + divide instruction + This instruction divides two numbers and outputs it to the direct + reference given. + */ + case Instructions::Divide: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Divide inbuilt"); + } + { + Direct varRef; + Literal left; + Literal right; + Literal final; + + if (holds_alternative(l.args[0])) { + left = get(l.args[0]); + } else { + return error("First argument of divide must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[1])) { + right = get(l.args[1]); + } else { + return error("Second argument of divide must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[2])) { + varRef = get(l.args[2]); + } else { + return error("Third argument of divide must be a direct reference"); + } + + // Ensure we aren't dividing by zero + if (holds_alternative(right.val)) { + if (get(right.val) == 0) { + return error("Division by zero is not allowed", "divisionByZeroError"); + } + } + if (holds_alternative(right.val)) { + if (get(right.val) == 0) { + return error("Division by zero is not allowed", "divisionByZeroError"); + } + } + + // Figure out types and compute values + if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) / get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) / get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = double(get(left.val)) / get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) / double(get(right.val)); + } else { + return error("Cannot divide those two values", "mathError"); + } + + setVal(varRef.varName, final); + } + break; + /* + equal instruction + This instruction checks if two values are equal and outputs a boolean + to the direct reference given. + */ + case Instructions::Equal: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Equal inbuilt"); + } + { + Direct varRef; + Literal left; + Literal right; + Literal final; + + if (holds_alternative(l.args[0])) { + left = get(l.args[0]); + } else { + return error("First argument of equal must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[1])) { + right = get(l.args[1]); + } else { + return error("Second argument of equal must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[2])) { + varRef = get(l.args[2]); + } else { + return error("Third argument of equal must be a direct reference"); + } + + // Figure out types and compute values + if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) == get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) == get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = double(get(left.val)) == get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) == double(get(right.val)); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) == get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) == get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) == get(right.val); + } else { + return error("Cannot equal those two values", "comparisonError"); + } + + setVal(varRef.varName, final); + } + break; + /* + inequal instruction + This instruction checks if two values are not equal and outputs a boolean + to the direct reference given. + */ + case Instructions::Inequal: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Inequal inbuilt"); + } + { + Direct varRef; + Literal left; + Literal right; + Literal final; + + if (holds_alternative(l.args[0])) { + left = get(l.args[0]); + } else { + return error("First argument of inequal must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[1])) { + right = get(l.args[1]); + } else { + return error("Second argument of inequal must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[2])) { + varRef = get(l.args[2]); + } else { + return error("Third argument of inequal must be a direct reference"); + } + + // Figure out types and compute values + if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) != get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) != get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = double(get(left.val)) != get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) != double(get(right.val)); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) != get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) != get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) != get(right.val); + } else { + return error("Cannot inequal those two values", "comparisonError"); + } + + setVal(varRef.varName, final); + } + break; + /* + greater instruction + This instruction checks if the left value is greater than the right value + and outputs a boolean to the direct reference given. + */ + case Instructions::Greater: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Greater inbuilt"); + } + { + Direct varRef; + Literal left; + Literal right; + Literal final; + + if (holds_alternative(l.args[0])) { + left = get(l.args[0]); + } else { + return error("First argument of greater must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[1])) { + right = get(l.args[1]); + } else { + return error("Second argument of greater must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[2])) { + varRef = get(l.args[2]); + } else { + return error("Third argument of greater must be a direct reference"); + } + + // Figure out types and compute values + if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) > get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) > get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = double(get(left.val)) > get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) > double(get(right.val)); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) > get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) > get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) > get(right.val); + } else { + return error("Cannot greater those two values", "comparisonError"); + } + + setVal(varRef.varName, final); + } + break; + /* + lesser instruction + This instruction checks if the left value is lesser than the right value + and outputs a boolean to the direct reference given. + */ + case Instructions::Lesser: + if (l.args.size() < 3) { + return error("Could not find all arguments required for Lesser inbuilt"); + } + { + Direct varRef; + Literal left; + Literal right; + Literal final; + + if (holds_alternative(l.args[0])) { + left = get(l.args[0]); + } else { + return error("First argument of lesser must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[1])) { + right = get(l.args[1]); + } else { + return error("Second argument of lesser must be a value (literal or value reference)"); + } + + if (holds_alternative(l.args[2])) { + varRef = get(l.args[2]); + } else { + return error("Third argument of lesser must be a direct reference"); + } + + // Figure out types and compute values + if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) < get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) < get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = double(get(left.val)) < get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) < double(get(right.val)); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) < get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) < get(right.val); + } else if (holds_alternative(left.val) && holds_alternative(right.val)) { + final.val = get(left.val) < get(right.val); + } else { + return error("Cannot lesser those two values", "comparisonError"); + } + + setVal(varRef.varName, final); + } + break; + + /* + not instruction + Negates a boolean + */ + case Instructions::Not: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Not inbuilt"); + } + { + Literal boolean; + Direct varRef; + + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + boolean.val = !get(get(l.args[0]).val); + } else { + return error("First argument of not must be a boolean literal"); + } + } else { + return error("First argument of not must be a boolean literal"); + } + + if (holds_alternative(l.args[1])) { + varRef = get(l.args[1]); + } else { + return error("Second argument of not must be a direct reference"); + } + + setVal(varRef.varName, boolean); + } + break; + /* + jump instruction + Jumps to the line reference provided. + */ + case Instructions::Jump: + if (l.args.size() < 1) { + return error("Could not find all arguments required for Jump inbuilt"); + } + if (holds_alternative(l.args[0])) { + i = get(l.args[0]).lineNum - 1; + } else { + return error("First argument of jump must be a line reference"); + } + break; + /* + if instruction + If the boolean is true, jumps to the line reference provided. + */ + case Instructions::If: + if (l.args.size() < 2) { + return error("Could not find all arguments required for If inbuilt"); + } + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + if (get(get(l.args[0]).val)) { + if (holds_alternative(l.args[1])) { + i = get(l.args[1]).lineNum - 1; + } else { + return error("Second argument of if must be a line reference"); + } + } + } else { + return error("First argument of if must be a boolean literal"); + } + } else { + return error("First argument of if must be a boolean literal"); + } + break; + /* + end instruction + Ends the program with the provided exit code. + */ + case Instructions::End: + if (l.args.size() < 1) { + exit(0); + } + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + exit(get(get(l.args[0]).val)); + } else { + return error("First argument of end must be an integer literal"); + } + } else { + return error("First argument of end must be an integer literal"); + } + break; + /* + fun instruction + This instruction is used to define a function. + */ + case Instructions::Fun: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Fun inbuilt"); + } + { + Function newFunction; + + if (holds_alternative(l.args[0])) { + newFunction.returnType = get(l.args[0]).type; + } else { + return error("First argument of function must be a type reference"); + } + + string fnName; + + if (holds_alternative(l.args[1])) { + fnName = get(l.args[1]).fnName; + } else { + return error("Second argument of function must be a function reference"); + } + + if (importing.size() > 0) { + fnName = importing.top() + ":" + fnName; + } + + // Parse function arguments (type-direct pairs) + if ((l.args.size() - 2) % 2 != 0) { + return error("Function arguments must be in type-direct pairs"); + } + + for (size_t m = 2; m < l.args.size(); m += 2) { + FnArg newArg; + + // Get type + if (holds_alternative(l.args[m])) { + newArg.type = get(l.args[m]).type; + } else { + return error("Expected type reference in function argument definition"); + } + + // Get direct reference + if (m + 1 < l.args.size() && holds_alternative(l.args[m + 1])) { + newArg.ref = get(l.args[m + 1]); + } else { + return error("Expected direct reference after type reference in function argument definition"); + } + + newFunction.args.push_back(newArg); + } + + functions[fnName] = newFunction; + processingFunction = true; + procFnName = fnName; + } + break; + /* + struct instruction + This instruction is used to define a struct. + */ + case Instructions::Struct: + if (l.args.size() < 1) { + return error("Could not find all arguments required for Struct inbuilt"); + } + { + Struct newStruct; + + string structName; + + if (holds_alternative(l.args[0])) { + if (get(l.args[0]).isCustomType) { + structName = get(l.args[0]).customType; + } else { + return error("Struct name cannot be a primitive type"); + } + } else { + return error("First argument of struct must be a type reference"); + } + + if (importing.size() > 0) { + structName = importing.top() + ":" + structName; + } + + structs[structName] = newStruct; + processingStruct = true; + procStructName = structName; + } + break; + /* + return instruction + This instruction is used to return from a function. + */ + case Instructions::Return: + if (executingFunction) { + if (l.args.size() > 0) { + if (holds_alternative(l.args[0])) { + return get(l.args[0]); + } else { + return error("Return argument must be a literal"); + } + } else { + Literal empty; + return empty; + } + } else { + return error("Cannot return from outside a function"); + } + break; + /* + pusharg instruction + This instruction is used to push an argument to the function argument stack. + */ + case Instructions::Pusharg: + if (l.args.size() < 1) { + return error("Could not find all arguments required for Pusharg inbuilt"); + } + for (argument arg : l.args) { + if (holds_alternative(arg)) { + fnArgs.push_back(std::get(arg)); + } else { + return error("Pusharg argument must be a literal"); + } + } + break; + /* + call instruction + This instruction is used to call a function. + */ + case Instructions::Call: + { + if (l.args.size() < 2) { + return error("Could not find all arguments required for Call inbuilt"); + } + + FunctionRef ref; + string returnRef; + + if (holds_alternative(l.args[0])) { + ref = get(l.args[0]); + } else { + return error("First argument of call must be a function reference"); + } + + if (holds_alternative(l.args[1])) { + returnRef = get(l.args[1]).varName; + } else { + return error("Second argument of call must be a direct reference"); + } + + // Check for external function + if (externalFunctions.find(ref.fnName) != externalFunctions.end()) { + // Call external function + typedef GroundValue (*ExtFunc)(GroundValue*, int); + ExtFunc extFunc = (ExtFunc)externalFunctions[ref.fnName]; + + // Convert arguments + vector gvArgs; + for (const Literal& arg : fnArgs) { + gvArgs.push_back(literalToGroundValue(arg)); + } + + // Call function + GroundValue result = extFunc(gvArgs.data(), gvArgs.size()); + + // Convert result back + Literal resultLit = groundValueToLiteral(result); + + // Clear arguments and store result + fnArgs.clear(); + variables[returnRef] = resultLit; + + break; + } + + // Create backup of variables to be restored + map scopeBackup = variables; + + Function fnToExec; + bool inStruct = false; + Struct structVal; + string structName; + + // Check if function exists + if (functions.find(ref.fnName) == functions.end()) { + if (isReferencingStruct(ref.fnName)) { + string fnInStruct; + + size_t dotPos = ref.fnName.find('.'); + if (dotPos != string::npos) { + structName = ref.fnName.substr(0, dotPos); + fnInStruct = ref.fnName.substr(dotPos + 1); + if (variables.find(structName) != variables.end()) { + if (holds_alternative(variables[structName].val)) { + structVal = get(variables[structName].val); + if (structVal.functions.find(fnInStruct) != structVal.functions.end()) { + fnToExec = structVal.functions[fnInStruct]; + inStruct = true; + } else { + return error("Could not find function '" + fnInStruct + "' in struct '" + structName + "'"); + } + for (auto &[key, value] : structVal.values) { + variables[key] = value; + } + } else { + return error("Variable '" + structName + "' is not a struct"); + } + } else { + return error("Could not find struct '" + structName + "'"); + } + } else { + return error("Invalid struct member access syntax"); + } + } else { + return error("Function " + ref.fnName + " not found"); + } + } else { + fnToExec = functions[ref.fnName]; + } + + // Check argument count + if (fnArgs.size() != fnToExec.args.size()) { + return error("Function " + ref.fnName + " expects " + + to_string(fnToExec.args.size()) + + " arguments, got " + to_string(fnArgs.size())); + } + + // Create function arguments with type checking + for (int m = 0; m < fnToExec.args.size(); m++) { + FnArg arg = fnToExec.args[m]; + + // Type checking - now with error reporting + if (arg.type != getLitType(fnArgs[m])) { + if (arg.type == Types::Double && getLitType(fnArgs[m]) == Types::Int) { + fnArgs[m].val = double(get(fnArgs[m].val)); + } else { + return error("Function " + ref.fnName + " argument " + to_string(m + 1) + + " type mismatch. Expected type does not match provided argument type.", "typeError"); + } + } + + // Create the variable + variables[arg.ref.varName] = fnArgs[m]; + } + + // Clear function arguments for next call + fnArgs.clear(); + + // Process labels + preProcessLabels(functions[ref.fnName].instructions); + + // Call the function + Literal retVal = exec(fnToExec.instructions, true); + + string errPreserveValue; + + if (holds_alternative(retVal.val)) { + Error err = get(retVal.val); + if (err.pops > 1) { + err.pops --; + Literal tmpLit; + tmpLit.val = err; + return tmpLit; + } + errPreserveValue = err.reporter; + } + + // If we were executing a function in a struct, add back values + if (inStruct) { + for (auto &[key, value] : structVal.values) { + structVal.values[key] = variables[key]; + } + } + + // Restore scope + variables = scopeBackup; + labelStack.pop(); + + // Add back the false for any errors + if (!errPreserveValue.empty()) { + Literal errLit; + errLit.val = false; + setVal(errPreserveValue, errLit); + } + + // Add back the struct values + variables[structName].val = structVal; + + setVal(returnRef, retVal); + } + break; + /* + use instruction + This instruction is used to import a library. + */ + case Instructions::Use: + if (l.args.size() < 1) { + return error("Could not find all arguments required for Use inbuilt"); + } + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + string libName = get(get(l.args[0]).val); + bool alreadyImported = false; + for (string importedLib : libraries) { + if (importedLib == libName) { + alreadyImported = true; + break; + } + } + if (alreadyImported) { + break; + } + ifstream libFile(libName); + if (libFile.good()) { + string libCode((istreambuf_iterator(libFile)), istreambuf_iterator()); + libFile.close(); + importing.push(libName); + vector> lexed = lexer(libCode); + vector parsed = parser(lexed); + preProcessLabels(parsed); + exec(parsed); + labelStack.pop(); + importing.pop(); + libraries.push_back(libName); + } else { + return error("Could not find library " + libName); + } + } else { + return error("Use argument must be a string literal"); + } + } else { + return error("Use argument must be a string literal"); + } + break; + /* + extern instruction + This instruction is used to load an external library. + */ + case Instructions::Extern: + if (l.args.size() < 1) { + return error("Could not find all arguments for Extern inbuilt"); + } + { + string libName; + + if (holds_alternative(l.args[0])) { + if (holds_alternative(get(l.args[0]).val)) { + libName = get(get(l.args[0]).val); + } else { + return error("First argument for extern requires a string literal"); + } + } else { + return error("First argument for extern requires a string literal"); + } + + bool imported = false; + for (string lib : libraries) { + if (lib == libName) { + imported = true; + break; + } + } + + if (imported) break; + + // Add appropriate extension + string fullLibName = libName; + #ifdef _WIN32 + fullLibName += ".dll"; + #elif __APPLE__ + fullLibName += ".dylib"; + #else + fullLibName += ".so"; + #endif + + // Check multiple locations for the library + string libPath; + bool found = false; + + // Check GROUND_LIBS directory + const char* groundLibsDir = getenv("GROUND_LIBS"); + if (groundLibsDir) { + string envPath = string(groundLibsDir); + // Add trailing slash if not present + if (!envPath.empty() && envPath.back() != '/' && envPath.back() != '\\') { + envPath += "/"; + } + string fullEnvPath = envPath + fullLibName; + if (filesystem::exists(fullEnvPath)) { + libPath = fullEnvPath; + found = true; + } + } + + if (!found) { + return error("Could not find external library: " + fullLibName + + " (searched current directory and GROUND_LIBS)", "libraryError"); + } + + // Try to open the library + void* handle = DLOPEN(libPath.c_str()); + if (!handle) { + return error("Failed to load library " + libPath + ": " + string(DLERROR()), "libraryError"); + } + + // Store handle for cleanup later + loadedLibraries[libName] = handle; + + // Get required functions + typedef const char** (*GetFunctionsFunc)(); + typedef void* (*GetFunctionFunc)(const char*); + + GetFunctionsFunc getFunctions = (GetFunctionsFunc)DLSYM(handle, "ground_get_functions"); + GetFunctionFunc getFunction = (GetFunctionFunc)DLSYM(handle, "ground_get_function"); + + if (!getFunctions || !getFunction) { + return error("Library " + libName + " is not Ground-compatible (missing required functions: ground_get_functions or ground_get_function)", "libraryError"); + } + + // Optional initialization + typedef void (*InitFunc)(); + InitFunc initFunc = (InitFunc)DLSYM(handle, "ground_lib_init"); + if (initFunc) { + initFunc(); + } + + // Register all functions + const char** functionNames = getFunctions(); + if (!functionNames) { + return error("Library " + libName + " returned null function list"); + } + + int functionCount = 0; + for (int i = 0; functionNames[i] != nullptr; i++) { + void* funcPtr = getFunction(functionNames[i]); + if (funcPtr) { + externalFunctions[libName + ":" + string(functionNames[i])] = funcPtr; + functionCount++; + } else { + return error("Failed to get function pointer for: " + string(functionNames[i]), "libraryError"); + } + } + + if (functionCount == 0) { + return error("No functions were loaded from library: " + libName, "libraryError"); + } + } + break; + case Instructions::Gettype: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Gettype inbuilt"); + } + { + Literal toCheck; + Direct outVar; + + if (holds_alternative(l.args[0])) { + toCheck = get(l.args[0]); + } else { + return error("First argument of gettype must be a literal"); + } + + if (holds_alternative(l.args[1])) { + outVar = get(l.args[1]); + } else { + return error("Second argument of gettype must be a direct reference"); + } + + Literal typeStr; + switch (getLitType(toCheck)) { + case Types::Int: + typeStr.val = "int"; + break; + case Types::Double: + typeStr.val = "double"; + break; + case Types::String: + typeStr.val = "string"; + break; + case Types::Char: + typeStr.val = "char"; + break; + case Types::Bool: + typeStr.val = "bool"; + break; + case Types::List: + typeStr.val = "list"; + break; + default: + typeStr.val = "other"; + break; + } + setVal(outVar.varName, typeStr); + } + break; + case Instructions::Exists: + if (l.args.size() < 2) { + return error("Could not find all arguments required for Exists inbuilt"); + } + { + string toCheck; + Direct outVar; + + if (holds_alternative(l.args[0])) { + toCheck = get(l.args[0]).varName; + } else if (holds_alternative(l.args[0])) { + toCheck = get(l.args[0]).label; + } else { + return error("First argument of exists must be a value reference or label"); + } + + if (holds_alternative(l.args[1])) { + outVar = get(l.args[1]); + } else { + return error("Second argument of exists must be a direct reference"); + } + + Literal exists; + if (holds_alternative(l.args[0])) { + exists.val = variables.find(toCheck) != variables.end(); + } else { + exists.val = labelStack.top().find(toCheck) != labelStack.top().end(); + } + setVal(outVar.varName, exists); + } + break; + } + } + if (!executingFunction) { + for (auto const& [key, val] : loadedLibraries) { + DLCLOSE(val); + } + } else { + catches.pop(); + } + Literal empty; + return empty; +} diff --git a/src/interpreter/interpreter.h b/src/interpreter/interpreter.h new file mode 100644 index 0000000..965ba8e --- /dev/null +++ b/src/interpreter/interpreter.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../main.h" + +void preProcessLabels(std::vector instructions); +Literal exec(std::vector in, bool executingFunction = false); diff --git a/src/lexer/lexer.cpp b/src/lexer/lexer.cpp new file mode 100644 index 0000000..9ce79de --- /dev/null +++ b/src/lexer/lexer.cpp @@ -0,0 +1,81 @@ +#include +#include +#include "lexer.h" + +using namespace std; + +/* + lexer function + This function takes the raw text from the file and splits it into a format + that the parser can understand. +*/ + +vector> lexer(string in) { + vector> out; + vector line; + string buf; + bool procString = false; + bool procChar = false; + bool isComment = false; + + for (char i : in) { + switch (i) { + case '"': + if (!isComment) { + if (procChar) { + buf.push_back(i); + } else { + procString = !procString; + buf.push_back(i); + } + } + break; + case '\'': + if (!isComment) { + if (procString) { + buf.push_back(i); + } else { + procChar = !procChar; + buf.push_back(i); + } + } + break; + case '\n': + if (!procString && !procChar) { + if (!buf.empty()) line.push_back(buf); + out.push_back(line); + buf.clear(); + line.clear(); + isComment = false; + } else { + if (!isComment) buf.push_back(i); + } + break; + case '#': + if (!procString && !procChar) { + isComment = true; + if (!buf.empty()) line.push_back(buf); + out.push_back(line); + buf.clear(); + line.clear(); + } else { + buf.push_back(i); + } + break; + case ' ': + if (!procString && !procChar) { + if (!buf.empty() && !isComment) line.push_back(buf); + buf.clear(); + } else { + buf.push_back(i); + } + break; + default: + if (!isComment) buf.push_back(i); + break; + } + } + if (!buf.empty()) line.push_back(buf); + out.push_back(line); + return out; +} diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h new file mode 100644 index 0000000..ca68717 --- /dev/null +++ b/src/lexer/lexer.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +std::vector> lexer(std::string in); diff --git a/src/main.cpp b/src/main.cpp index 6e58de9..2209ff6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,2912 +36,40 @@ #include #include -#include #include -#include -#include #include -#include -#include -// Headers for external libraries -#ifdef _WIN32 - // Note: Windows support is experiemental. Maybe try using a superior - // operating system? (cough cough, Linux?) - #include - #define DLOPEN(path) LoadLibrary(path) - #define DLSYM(handle, name) GetProcAddress(handle, name) - #define DLCLOSE(handle) FreeLibrary(handle) - #define DLERROR() "Windows DLL Error" -#else - #include - #define DLOPEN(path) dlopen(path, RTLD_LAZY) - #define DLSYM(handle, name) dlsym(handle, name) - #define DLCLOSE(handle) dlclose(handle) - #define DLERROR() dlerror() -#endif +#include "main.h" +#include "data/data.h" +#include "error/error.h" +#include "lexer/lexer.h" +#include "parser/parser.h" +#include "interpreter/interpreter.h" using namespace std; -/* - Instructions enum class - For each function keyword, an instruction is assigned. See also parser - function, interpreter function, Instruction struct -*/ -enum class Instructions { - Jump, If, - Stdout, Stdin, Stdlnout, - Add, Subtract, Multiply, Divide, - Equal, Inequal, Greater, Lesser, Not, - End, Set, Empty, Gettype, Exists, - Setlist, Getlistat, Setlistat, Getlistsize, Listappend, Listprepend, - Getstrcharat, Getstrsize, - Stoi, Stod, Tostring, - Fun, Return, Endfun, Pusharg, Call, Local, - Use, Extern, Error, Catch, Try, Exception, - Struct, Endstruct, Init -}; - -/* - Types enum class - Assists in type checking in the parser function. For example, the - following values correspond to the following types: - 1 Int - 3.14 Double - "Hello!" String - 'e' Char - true Bool - $value Value - &var Direct - %10 Line - See also parser function -*/ -enum class Types { - Int, Double, String, Char, Bool, Value, Direct, Line, List, ListRef, Label, Type, Function, Other -}; - -// Forward declaration of Literal for list -struct Literal; - -/* - List struct - Contains literal values inside a vector. For example, if the following - program was written: - setlist #myNums 3 5 9 13 - The List struct which would be created and stored should look like this: - { - val = { - Literal { - val = 3 - }, - Literal { - val = 5 - }, - Literal { - val = 9 - }, - Literal { - val = 13 - }, - } - } - All elements in the list must be of the same type. See also Literal struct. -*/ -struct List { - vector val; -}; - -// Forward definition of literal for the legendary struct struct -struct Literal; - -/* - Direct struct - If the program being executed makes a direct reference, it is stored in a Direct - struct. For example, if the following line was written: - stdin &myVar - The Direct struct in the instruction should look like this: - { - varName = "myVar"; - } -*/ -struct Direct { - string varName; -}; - -struct TypeRef { - bool isCustomType = false; - string customType; - Types type; -}; - -struct FunctionRef { - string fnName; -}; - -/* - Label struct - Contains information needed to register labels -*/ -struct Label { - string id; - int lineNum = -1; -}; - -/* - Line struct - Contains information needed to jump to lines -*/ -struct Line { - int lineNum = -1; - bool isLabel = false; - string label; -}; - -/* - labelStack stack - Allows each function to hold it's own set of labels -*/ -stack> labelStack; - -/* - ListRef struct - Contains the name of a list referenced by the program. For example, if the - following program was written: - setlist #myNums 3 5 9 13 - The ListRef struct should look like this: - { - listName = "myNums"; - } -*/ -struct ListRef { - string listName; -}; - -/* - variables map - Contains all variables made while running the program. See also Literal struct. -*/ -map variables; - -/* - ValueRef struct - If the program being executed makes a value reference, it is stored in a ValueRef - struct. For example, if the following line was written: - stdin &myVar - The ValueRef struct in the instruction should look like this: - { - varName = "myVar"; - } -*/ -struct ValueRef { - string varName; -}; - -/* - Instruction struct - An instruction usually corresponds to a line in the program being interpreted. - For example, if the following line was written: - add 5 $myVar &outVar - The instruction should look like this: - { - inst = Instructions::Add; - args = { - Literal { - val = 5 - }, - ValueRef { - varName = "myVar" - }, - Direct { - varName = "outVar" - } - }; - } - inst starts as empty, so empty lines and commented out lines do not get in the - way of jump and if. - See also: Instructions enum class, Literal struct, ValueRef struct, Direct struct, - Line struct, exec function, parser function -*/ - -typedef variant argument; -struct Instruction { - Instructions inst = Instructions::Empty; - vector args; - bool isLabel = false; - Label label; -}; - -struct FnArg { - Direct ref; - Types type; -}; - -/* - Function struct - Contains information needed to run a Ground function. -*/ -struct Function { - Types returnType; - vector args; - vector instructions; - vector