diff --git a/Bobfile b/Bobfile index 0224e7f..761027e 100644 --- a/Bobfile +++ b/Bobfile @@ -1,4 +1,4 @@ compiler "g++"; binary "mx"; -source "src/main.cpp"; +source "src/*.cpp"; compile; diff --git a/mx b/mx index 81e508a..7a8be2b 100755 Binary files a/mx and b/mx differ diff --git a/src/ArgParser.cpp b/src/ArgParser.cpp new file mode 100644 index 0000000..27c6cbd --- /dev/null +++ b/src/ArgParser.cpp @@ -0,0 +1,38 @@ +#include "ArgParser.h" + +bool debugMode = false; + +ArgParser::ArgParser(int argc, char* argv[]) { + // First, collect all arguments + for (int i = 0; i < argc; i++) { + args.push_back(argv[i]); + } + + // Then process them + for (int i = 0; i < args.size(); i++) { + if (args[i] == "--debug") { + debugMode = true; + args.erase(args.begin() + i); + } else if (args[i] == "--help") { + cout << "mxlang interpreter" << endl; + cout << "Usage: mx [file]" << endl; + cout << "Options:" << endl; + cout << " --debug Enable debug mode" << endl; + cout << " --help Show this help message" << endl; + cout << "Issues? Send an email to max@maxwellj.xyz" << endl; + cout << "Report bugs at https://git.maxwellj.xyz/max/mx" << endl; + exit(0); + } else if (args[i] == "--version") { + cout << "mxlang, version 0.0.2" << endl; + exit(0); + } + + } +} + +string ArgParser::getArg(int index) { + if (index >= 0 && index < args.size()) { + return args[index]; + } + return ""; +} diff --git a/src/ArgParser.h b/src/ArgParser.h new file mode 100644 index 0000000..3ef7bee --- /dev/null +++ b/src/ArgParser.h @@ -0,0 +1,11 @@ +#pragma once + +#include "common.h" + +class ArgParser { +private: + vector args; +public: + ArgParser(int argc, char* argv[]); + string getArg(int index); +}; \ No newline at end of file diff --git a/src/Interpreter.cpp b/src/Interpreter.cpp new file mode 100644 index 0000000..0cfa023 --- /dev/null +++ b/src/Interpreter.cpp @@ -0,0 +1,263 @@ +#include "Interpreter.h" + +optional Interpreter::consume() { + tokenIndex++; + if (tokenIndex < tokens.size()) return tokens[tokenIndex]; + return {}; +} + +optional Interpreter::peek(int offset) { + int index = tokenIndex + offset; + if (index >= 0 && index < tokens.size()) return tokens[index]; + return {}; +} + +void Interpreter::convertToTokens(vector tokenList) { + if (debugMode) log.toggleDebugPrint(); + tokens = tokenList; + log.debug("Alright we got " + to_string(tokens.size()) + " tokens"); + + while (tokenIndex < static_cast(tokens.size() - 1)) { + auto currentToken = consume(); + if (!currentToken) break; + + vector currentInstruction; + currentInstruction.push_back(currentToken.value()); + + // Collect tokens until semicolon + while (auto nextToken = peek(1)) { + if (nextToken->keyword == keywords::SEMICOLON) { + consume(); // consume the semicolon + break; + } + consume(); // consume the peeked token + currentInstruction.push_back(nextToken.value()); + } + // Apply variables to tokens + // We start at 1 so we can reassign variables in the execution of code + for (int i = 1; i < currentInstruction.size(); i++) { + if (currentInstruction[i].type == valtype::STR) { + string potentialVarName = get(currentInstruction[i].value.value); + auto varIt = variables.find(potentialVarName); + + if (varIt != variables.end()) { + // Replace the token with the variable's value + Token newToken; + newToken.keyword = keywords::VALUE; + newToken.type = varIt->second.type; + newToken.value = varIt->second; + currentInstruction[i] = newToken; + } + } + } + // Do math + for (int i = 0; i < currentInstruction.size(); i++) { + if (currentInstruction[i].keyword == keywords::ADD || currentInstruction[i].keyword == keywords::SUBTRACT || currentInstruction[i].keyword == keywords::MULTIPLY || currentInstruction[i].keyword == keywords::DIVIDE) { + Token newToken; + newToken.keyword = keywords::VALUE; + if (currentInstruction.size() < i + 1) syntaxError.mathTooFewArgs(); + Token before = currentInstruction[i - 1]; + Token after = currentInstruction[i + 1]; + if (before.type != after.type) syntaxError.mathTypeMismatch(); + newToken.type = before.type; + if (currentInstruction[i].keyword == keywords::ADD) { + if (newToken.type == valtype::INT) { + newToken.value.value = get(before.value.value) + get(after.value.value); + } + else if (newToken.type == valtype::DEC) { + newToken.value.value = get(before.value.value) + get(after.value.value); + } + else if (newToken.type == valtype::STR) { + newToken.value.value = get(before.value.value) + get(after.value.value); + } else { + syntaxError.mathCannotDoOperationOnType("+", "bool"); + } + } + else if (currentInstruction[i].keyword == keywords::SUBTRACT) { + if (newToken.type == valtype::INT) { + newToken.value.value = get(before.value.value) - get(after.value.value); + } + else if (newToken.type == valtype::DEC) { + newToken.value.value = get(before.value.value) - get(after.value.value); + } else { + syntaxError.mathCannotDoOperationOnType("-", "bool or string"); + } + } + else if (currentInstruction[i].keyword == keywords::MULTIPLY) { + if (newToken.type == valtype::INT) { + newToken.value.value = get(before.value.value) * get(after.value.value); + } + else if (newToken.type == valtype::DEC) { + newToken.value.value = get(before.value.value) * get(after.value.value); + } else { + syntaxError.mathCannotDoOperationOnType("*", "bool or string"); + } + } + else if (currentInstruction[i].keyword == keywords::DIVIDE) { + if (newToken.type == valtype::INT) { + newToken.value.value = get(before.value.value) / get(after.value.value); + } + else if (newToken.type == valtype::DEC) { + newToken.value.value = get(before.value.value) / get(after.value.value); + } else { + syntaxError.mathCannotDoOperationOnType("/", "bool or string"); + } + } else { + // Something has gone terribly wrong + // We should never reach this point in the code + syntaxError.generalError("The math aint mathing"); + } + // Insert our cool new token and get rid of the boring old stuff + currentInstruction[i - 1] = newToken; + currentInstruction.erase(currentInstruction.begin() + i); + currentInstruction.erase(currentInstruction.begin() + i); + i -= 1; + } + } + // Execute the instruction + executeCode(currentInstruction); + } +} + +void Interpreter::executeCode(vector tokens) { + SyntaxError syntaxError; + log.debug("Token length for this expression is " + to_string(tokens.size())); + for (int i = 0; i < tokens.size(); i++) { + if (tokens[i].keyword == keywords::PRINT) { + i++; + if (tokens.size() <= i) break; + Token nextToken = tokens[i]; + // Handle different value types + if (nextToken.keyword == keywords::VALUE) { + switch (nextToken.type) { + case valtype::INT: + cout << get(nextToken.value.value) << endl; + break; + case valtype::DEC: + cout << get(nextToken.value.value) << endl; + break; + case valtype::STR: + cout << get(nextToken.value.value) << endl; + break; + case valtype::BOOL: + cout << (get(nextToken.value.value) ? "true" : "false") << endl; + break; + default: + vector validTypes = {"int", "dec", "str", "bool"}; + syntaxError.fnTypeMismatch("print", validTypes, nextToken.type); + } + } else { + syntaxError.fnNotSufficientArgs("print", 1, 1, 0); + } + } else if (tokens[i].keyword == keywords::EXIT) { + i++; + if (tokens.size() <= i) break; + Token nextToken = tokens[i]; + if (nextToken.keyword == keywords::VALUE) { + switch (nextToken.type) { + case valtype::INT: + exit(get(nextToken.value.value)); + break; + case valtype::DEC: + exit(get(nextToken.value.value)); + break; + default: + vector validTypes = {"int", "dec"}; + syntaxError.fnTypeMismatch("exit", validTypes, nextToken.type); + } + } + } else if (tokens[i].keyword == keywords::LET) { + i++; + if (tokens.size() <= i + 2) { + syntaxError.fnNotSufficientArgs("let", 3, 3, tokens.size() - i); + break; + } + + Token typeToken = tokens[i]; + Token nameToken = tokens[i + 1]; + Token valueToken = tokens[i + 2]; + + i += 2; + + // Validate that we have a valid variable name + if (nameToken.type != valtype::STR) { + vector validTypes = {"str"}; + syntaxError.fnTypeMismatch("let (variable name)", validTypes, nameToken.type); + continue; + } + + string varName = get(nameToken.value.value); + Value newValue; + + // Check the type declaration matches the value + if (typeToken.keyword == keywords::INT && valueToken.type == valtype::INT) { + newValue.type = valtype::INT; + newValue.value = get(valueToken.value.value); + } + else if (typeToken.keyword == keywords::DEC && valueToken.type == valtype::DEC) { + newValue.type = valtype::DEC; + newValue.value = get(valueToken.value.value); + } + else if (typeToken.keyword == keywords::STR && valueToken.type == valtype::STR) { + newValue.type = valtype::STR; + newValue.value = get(valueToken.value.value); + } + else if (typeToken.keyword == keywords::BOOL && valueToken.type == valtype::BOOL) { + newValue.type = valtype::BOOL; + newValue.value = get(valueToken.value.value); + } + else { + vector validTypes; + if (typeToken.keyword == keywords::INT) validTypes = {"int"}; + else if (typeToken.keyword == keywords::DEC) validTypes = {"dec"}; + else if (typeToken.keyword == keywords::STR) validTypes = {"str"}; + else if (typeToken.keyword == keywords::BOOL) validTypes = {"bool"}; + syntaxError.fnTypeMismatch("let", validTypes, valueToken.type, "Variable name is " + varName); + continue; + } + + // Store the variable + variables[varName] = newValue; + } else { + if (tokens[i].keyword == keywords::VALUE && tokens[i].value.type == valtype::STR && variables.find(get(tokens[i].value.value)) != variables.end()) { + log.debug("Manipulating variable..."); + if (tokens.size() < i + 1) { + syntaxError.mathTooFewArgs(); + } + string varName = get(tokens[i].value.value); + i++; + if (variables[varName].type == valtype::INT) { + if (tokens[i].keyword == keywords::INCREMENT) { + variables[varName].value = get(variables[varName].value) + 1; + } else if (tokens[i].keyword == keywords::DECREMENT) { + variables[varName].value = get(variables[varName].value) - 1; + } else if (tokens[i].keyword == keywords::ADDTO) { + if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); + if (tokens[i + 1].value.type != valtype::INT) syntaxError.mathTypeMismatch("Expected an int when adding to an int"); + variables[varName].value = get(variables[varName].value) + get(tokens[i + 1].value.value); + } else if (tokens[i].keyword == keywords::SUBTRACTFROM) { + if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); + if (tokens[i + 1].value.type != valtype::INT) syntaxError.mathTypeMismatch("Expected an int when subtracting from an int"); + variables[varName].value = get(variables[varName].value) - get(tokens[i + 1].value.value); + } + } else if (variables[varName].type == valtype::DEC) { + if (tokens[i].keyword == keywords::INCREMENT) { + variables[varName].value = get(variables[varName].value) + 1; + } else if (tokens[i].keyword == keywords::DECREMENT) { + variables[varName].value = get(variables[varName].value) - 1; + } else if (tokens[i].keyword == keywords::ADDTO) { + if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); + if (tokens[i + 1].value.type != valtype::DEC) syntaxError.mathTypeMismatch("Expected an dec when adding to an dec"); + variables[varName].value = get(variables[varName].value) + get(tokens[i + 1].value.value); + } else if (tokens[i].keyword == keywords::SUBTRACTFROM) { + if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); + if (tokens[i + 1].value.type != valtype::DEC) syntaxError.mathTypeMismatch("Expected an dec when subtracting from an dec"); + variables[varName].value = get(variables[varName].value) - get(tokens[i + 1].value.value); + } + } + } else { + syntaxError.unknownFn(); + } + } + } +} \ No newline at end of file diff --git a/src/Interpreter.h b/src/Interpreter.h new file mode 100644 index 0000000..fdc887a --- /dev/null +++ b/src/Interpreter.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common.h" +#include "SyntaxError.h" +#include "Logger.h" + +class Interpreter { +private: + SyntaxError syntaxError; + vector tokens; + map variables; + Logger log; + int tokenIndex = -1; + optional consume(); + optional peek(int offset = 1); + +public: + void convertToTokens(vector tokenList); + void executeCode(vector tokens); +}; \ No newline at end of file diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..e0cdbe0 --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,36 @@ +#include "Logger.h" + +string logList; + +void Logger::writeToLog(string type, string in) { + logList += type + ": " + in + "\n"; +} + +string Logger::getLog() { + return logList; +} + +void Logger::toggleDebugPrint() { + isDebug = !isDebug; +} + +void Logger::error(string in) { + cout << "Error: " + in + "\n"; + writeToLog("Error", in); +} + +void Logger::fatalError(string in, int code) { + cout << "Error: " + in + "\n"; + writeToLog("Error", in); + exit(code); +} + +void Logger::info(string in) { + cout << "Info: " + in + "\n"; + writeToLog("Info", in); +} + +void Logger::debug(string in) { + if (isDebug) cout << "Debug: " + in + "\n"; + writeToLog("Debug", in); +} \ No newline at end of file diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 0000000..d9fbff9 --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,17 @@ +#pragma once + +#include "common.h" + +class Logger { +private: + bool isDebug = false; + void writeToLog(string type, string in); + +public: + string getLog(); + void toggleDebugPrint(); + void error(string in); + void fatalError(string in, int code); + void info(string in); + void debug(string in); +}; \ No newline at end of file diff --git a/src/Parser.cpp b/src/Parser.cpp new file mode 100644 index 0000000..80e4153 --- /dev/null +++ b/src/Parser.cpp @@ -0,0 +1,164 @@ +#include "Parser.h" + +bool Parser::canInt(string in) { + try { + stoi(in); + return true; + } catch (...) { + return false; + } +} + +bool Parser::canDec(string in) { + try { + stod(in); + return true; + } catch (...) { + return false; + } +} + +void Parser::parseLines(string in, Logger log) { + log.debug("Parsing lines..."); + buffer.clear(); + termBuffer.clear(); + terms.clear(); + bool isString = false; + bool isEscaped = false; + + for (size_t i = 0; i < in.size(); i++) { + char c = in[i]; + + if (isEscaped) { + buffer += c; + isEscaped = false; + continue; + } + + if (c == '\\') { + isEscaped = true; + buffer += c; + continue; + } + + if (c == '"') { + buffer += c; + isString = !isString; + } else if (c == '\n' && !isString) { + // Skip newlines outside strings + continue; + } else if (c == ';' && !isString) { + if (!buffer.empty()) { + termBuffer.push_back(buffer); + buffer.clear(); + } + terms.push_back(termBuffer); + termBuffer.clear(); + } else if ((c == ',' || c == '{' || c == '}' || c == '(' || c == ')') && !isString) { + if (!buffer.empty()) { + termBuffer.push_back(buffer); + buffer.clear(); + } + termBuffer.push_back(string(1, c)); + } else if (c == ' ' && !isString) { + if (!buffer.empty()) { + termBuffer.push_back(buffer); + buffer.clear(); + } + } else { + buffer += c; + } + } + + // Handle any remaining buffer content + if (!buffer.empty()) { + termBuffer.push_back(buffer); + } + if (!termBuffer.empty()) { + terms.push_back(termBuffer); + } + + // Debug output + string debugString; + log.debug("Lines have been parsed."); + for (const auto& term : terms) { + for (const auto& t : term) { + debugString += t + ", "; + } + debugString += "\n"; + } + log.debug(debugString); +} + +void Parser::processLines() { + // Process vector> terms + for (int i = 0; i < terms.size(); i++) { + for (int j = 0; j < terms[i].size(); j++) { + Token token; + string ct = terms[i][j]; + if (ct == " ") continue; + // Oh boy here we go + else if (ct == "fun") token.keyword = keywords::FUN; + else if (ct == "let") token.keyword = keywords::LET; + else if (ct == "print") token.keyword = keywords::PRINT; + else if (ct == "return") token.keyword = keywords::RETURN; + else if (ct == "exit") token.keyword = keywords::EXIT; + else if (ct == "int") token.keyword = keywords::INT; + else if (ct == "dec") token.keyword = keywords::DEC; + else if (ct == "str") token.keyword = keywords::STR; + else if (ct == "bool") token.keyword = keywords::BOOL; + else if (ct == "{") token.keyword = keywords::OBRAC; + else if (ct == "}") token.keyword = keywords::CBRAC; + else if (ct == "(") token.keyword = keywords::OPARE; + else if (ct == ")") token.keyword = keywords::CPARE; + else if (ct == "+") token.keyword = keywords::ADD; + else if (ct == "-") token.keyword = keywords::SUBTRACT; + else if (ct == "*") token.keyword = keywords::MULTIPLY; + else if (ct == "/") token.keyword = keywords::DIVIDE; + else if (ct == "++") token.keyword = keywords::INCREMENT; + else if (ct == "--") token.keyword = keywords::DECREMENT; + else if (ct == "+=") token.keyword = keywords::ADDTO; + else if (ct == "-=") token.keyword = keywords::SUBTRACTFROM; + else if (ct == "*=") token.keyword = keywords::MULTIPLYTO; + else if (ct == "/=") token.keyword = keywords::DIVIDEFROM; + else { + token.keyword = keywords::VALUE; + // Convert the value based on its type + if (canDec(ct)) { + token.type = valtype::DEC; + token.value.type = valtype::DEC; + token.value.value = stod(ct); + } + else if (canInt(ct)) { + token.type = valtype::INT; + token.value.type = valtype::INT; + token.value.value = stoi(ct); + } + else if (ct == "true" || ct == "false") { + token.type = valtype::BOOL; + token.value.type = valtype::BOOL; + token.value.value = (ct == "true"); + } + else { + // Handle strings - remove quotes if present + token.type = valtype::STR; + token.value.type = valtype::STR; + if (ct.size() >= 2 && ct.front() == '"' && ct.back() == '"') { + // Remove the quotes + token.value.value = ct.substr(1, ct.size() - 2); + } else { + token.value.value = ct; + } + } + } + tokens.push_back(token); + } + Token semi; + semi.keyword = keywords::SEMICOLON; + tokens.push_back(semi); + } +} + +vector Parser::getTokens() { + return tokens; +} \ No newline at end of file diff --git a/src/Parser.h b/src/Parser.h new file mode 100644 index 0000000..28d2de0 --- /dev/null +++ b/src/Parser.h @@ -0,0 +1,21 @@ +#pragma once + +#include "common.h" +#include "Logger.h" + +class Parser { +private: + string buffer; + vector lines; + vector termBuffer; + vector tokens; + vector> terms; + vector> things; + vector>> stuff; + bool canInt(string in); + bool canDec(string in); +public: + void parseLines(string in, Logger log); + void processLines(); + vector getTokens(); +}; \ No newline at end of file diff --git a/src/SyntaxError.cpp b/src/SyntaxError.cpp new file mode 100644 index 0000000..cb4aa49 --- /dev/null +++ b/src/SyntaxError.cpp @@ -0,0 +1,54 @@ +#include "SyntaxError.h" + +void SyntaxError::fnTypeMismatch(string function, vector validTypes, valtype typeGiven, string notes) { + cerr << "TypeError: function type mismatch" << endl; + cerr << "Function '" << function << "' expected one of the following types: "; + for (int i = 0; i < validTypes.size(); i++) { + if (i != 0) cerr << ", "; + cerr << validTypes[i]; + } + cerr << endl << "Got type '"; + if (typeGiven == valtype::INT) cerr << "int"; + else if (typeGiven == valtype::DEC) cerr << "dec"; + else if (typeGiven == valtype::STR) cerr << "str"; + else if (typeGiven == valtype::BOOL) cerr << "bool"; + else cerr << "unknown"; + cerr << "' instead" << endl; + if (!notes.empty()) cerr << "Notes: " << notes << endl; + exit(1); +} + +void SyntaxError::fnNotSufficientArgs(string function, int minArgs, int maxArgs, int argsGiven) { + cerr << "TypeError: function not sufficient arguments" << endl; + cerr << "Function '" << function << "' expected between " << minArgs << " and " << maxArgs << " arguments, got " << argsGiven << endl; + exit(1); +} + +void SyntaxError::unknownFn() { + cerr << "TypeError: unknown function" << endl; + exit(1); +} + +void SyntaxError::mathTypeMismatch(string notes) { + cerr << "MathError: cannot add two different types together" << endl; + if (!notes.empty()) cerr << "Notes: " << notes << endl; + exit(1); +} + +void SyntaxError::mathTooFewArgs(string notes) { + cerr << "MathError: too few things to add together" << endl; + if (!notes.empty()) cerr << "Notes: " << notes << endl; + exit(1); +} + +void SyntaxError::mathCannotDoOperationOnType(string operatorUsed, string type, string notes) { + cerr << "MathError: cannot use operator '" + operatorUsed + " on type '" + type + "'" << endl; + if (!notes.empty()) cerr << "Notes: " << notes << endl; + exit(1); +} + +void SyntaxError::generalError(string notes) { + cerr << "GeneralError: Something went awfully wrong and we don't know why lmao" << endl; + if (!notes.empty()) cerr << "Notes: " << notes << endl; + exit(1); +} \ No newline at end of file diff --git a/src/SyntaxError.h b/src/SyntaxError.h new file mode 100644 index 0000000..c41bed7 --- /dev/null +++ b/src/SyntaxError.h @@ -0,0 +1,14 @@ +#pragma once + +#include "common.h" + +class SyntaxError { +public: + void fnTypeMismatch(string function, vector validTypes, valtype typeGiven, string notes = ""); + void fnNotSufficientArgs(string function, int minArgs, int maxArgs, int argsGiven); + void unknownFn(); + void mathTypeMismatch(string notes = ""); + void mathTooFewArgs(string notes = ""); + void mathCannotDoOperationOnType(string operatorUsed, string type, string notes = ""); + void generalError(string notes = ""); +}; \ No newline at end of file diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..eb402d8 --- /dev/null +++ b/src/common.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +using namespace std; + +enum class valtype { + INT, DEC, STR, BOOL, KEYWORD, UNKNOWN +}; + +enum class keywords { + IF, ELSE, WHILE, INT, DEC, STR, BOOL, FUN, RETURN, + OPARE, CPARE, OBRAC, CBRAC, + ADDTO, SUBTRACTFROM, MULTIPLYTO, DIVIDEFROM, + ADD, SUBTRACT, MULTIPLY, DIVIDE, + EQUAL, INEQUAL, LESS, GREATER, EQLESS, EQGREATER, + INCREMENT, DECREMENT, + PRINT, LET, INPUT, EXIT, + VALUE, SEMICOLON, VARIABLE +}; + +struct Value { + valtype type; + variant value; +}; + +struct Token { + keywords keyword; + Value value; + valtype type = valtype::KEYWORD; +}; + +struct var { + valtype type; + variant value; + + string toString() const { + if (type == valtype::INT) { + return to_string(get(value)); + } + else if (type == valtype::DEC) { + return to_string(get(value)); + } + else if (type == valtype::STR) { + return get(value); + } + else if (type == valtype::BOOL) { + return to_string(get(value)); + } else { + return "unknown"; + } + } +}; + +// Global variable +extern bool debugMode; +extern string logList; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c766958..3636562 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,626 +1,11 @@ -#include #include -#include #include -#include -#include -#include -#include -#include - -using namespace std; - -enum class valtype { - INT, DEC, STR, BOOL, KEYWORD, UNKNOWN -}; - -enum class keywords { - IF, ELSE, WHILE, INT, DEC, STR, BOOL, FUN, RETURN, - OPARE, CPARE, OBRAC, CBRAC, - ADDTO, SUBTRACTFROM, MULTIPLYTO, DIVIDEFROM, - ADD, SUBTRACT, MULTIPLY, DIVIDE, - EQUAL, INEQUAL, LESS, GREATER, EQLESS, EQGREATER, - INCREMENT, DECREMENT, - PRINT, LET, INPUT, EXIT, - VALUE, SEMICOLON, VARIABLE -}; - -struct Value { - valtype type; - variant value; -}; - -struct Token { - keywords keyword; - Value value; - valtype type = valtype::KEYWORD; -}; - -struct var { - valtype type; - variant value; - - string toString() const { - if (type == valtype::INT) { - return to_string(get(value)); - } - else if (type == valtype::DEC) { - return to_string(get(value)); - } - else if (type == valtype::STR) { - return get(value); - } - else if (type == valtype::BOOL) { - return to_string(get(value)); - } else { - return "unknown"; - } - } -}; - -bool debugMode = false; - -class ArgParser { - private: - vector args; - public: - ArgParser(int argc, char* argv[]) { - // First, collect all arguments - for (int i = 0; i < argc; i++) { - args.push_back(argv[i]); - } - - // Then process them - for (int i = 0; i < args.size(); i++) { - if (args[i] == "--debug") { - debugMode = true; - args.erase(args.begin() + i); - } else if (args[i] == "--help") { - cout << "mxlang interpreter" << endl; - cout << "Usage: mx [file]" << endl; - cout << "Options:" << endl; - cout << " --debug Enable debug mode" << endl; - cout << " --help Show this help message" << endl; - cout << "Issues? Send an email to max@maxwellj.xyz" << endl; - cout << "Report bugs at https://git.maxwellj.xyz/max/mx" << endl; - exit(0); - } - } - } - - string getArg(int index) { - if (index >= 0 && index < args.size()) { - return args[index]; - } - return ""; - } -}; - -string logList; - -class Logger { - private: - bool isDebug = false; - - void writeToLog(string type, string in) { - logList += type + ": " + in + "\n"; - } - - public: - string getLog() { - return logList; - } - void toggleDebugPrint() { - isDebug = !isDebug; - } - void error(string in) { - cout << "Error: " + in + "\n"; - writeToLog("Error", in); - } - void fatalError(string in, int code) { - cout << "Error: " + in + "\n"; - writeToLog("Error", in); - exit(code); - } - void info(string in) { - cout << "Info: " + in + "\n"; - writeToLog("Info", in); - } - void debug(string in) { - if (isDebug) cout << "Debug: " + in + "\n"; - writeToLog("Debug", in); - } -}; - -class SyntaxError { - private: - public: - void fnTypeMismatch(string function, vector validTypes, valtype typeGiven, string notes = "") { - cerr << "TypeError: function type mismatch" << endl; - cerr << "Function '" << function << "' expected one of the following types: "; - for (int i = 0; i < validTypes.size(); i++) { - if (i != 0) cerr << ", "; - cerr << validTypes[i]; - } - cerr << endl << "Got type '"; - if (typeGiven == valtype::INT) cerr << "int"; - else if (typeGiven == valtype::DEC) cerr << "dec"; - else if (typeGiven == valtype::STR) cerr << "str"; - else if (typeGiven == valtype::BOOL) cerr << "bool"; - else cerr << "unknown"; - cerr << "' instead" << endl; - if (!notes.empty()) cerr << "Notes: " << notes << endl; - exit(1); - } - void fnNotSufficientArgs(string function, int minArgs, int maxArgs, int argsGiven) { - cerr << "TypeError: function not sufficient arguments" << endl; - cerr << "Function '" << function << "' expected between " << minArgs << " and " << maxArgs << " arguments, got " << argsGiven << endl; - exit(1); - } - void unknownFn() { - cerr << "TypeError: unknown function" << endl; - exit(1); - } - void mathTypeMismatch(string notes = "") { - cerr << "MathError: cannot add two different types together" << endl; - if (!notes.empty()) cerr << "Notes: " << notes << endl; - exit(1); - } - void mathTooFewArgs(string notes = "") { - cerr << "MathError: too few things to add together" << endl; - if (!notes.empty()) cerr << "Notes: " << notes << endl; - exit(1); - } - void mathCannotDoOperationOnType(string operatorUsed, string type, string notes = "") { - cerr << "MathError: cannot use operator '" + operatorUsed + " on type '" + type + "'" << endl; - if (!notes.empty()) cerr << "Notes: " << notes << endl; - exit(1); - } - void generalError(string notes = "") { - cerr << "GeneralError: Something went awfully wrong and we don't know why lmao" << endl; - if (!notes.empty()) cerr << "Notes: " << notes << endl; - exit(1); - } -}; - -class Parser { - private: - string buffer; - vector lines; - vector termBuffer; - vector tokens; - vector> terms; - vector> things; - vector>> stuff; - bool canInt(string in) { - try { - stoi(in); - return true; - } catch (...) { - return false; - } - } - bool canDec(string in) { - try { - stod(in); - return true; - } catch (...) { - return false; - } - } - public: - void parseLines(string in, Logger log) { - log.debug("Parsing lines..."); - buffer.clear(); - termBuffer.clear(); - terms.clear(); - bool isString = false; - bool isEscaped = false; - - for (size_t i = 0; i < in.size(); i++) { - char c = in[i]; - - if (isEscaped) { - buffer += c; - isEscaped = false; - continue; - } - - if (c == '\\') { - isEscaped = true; - buffer += c; - continue; - } - - if (c == '"') { - buffer += c; - isString = !isString; - } else if (c == '\n' && !isString) { - // Skip newlines outside strings - continue; - } else if (c == ';' && !isString) { - if (!buffer.empty()) { - termBuffer.push_back(buffer); - buffer.clear(); - } - terms.push_back(termBuffer); - termBuffer.clear(); - } else if ((c == ',' || c == '{' || c == '}' || c == '(' || c == ')') && !isString) { - if (!buffer.empty()) { - termBuffer.push_back(buffer); - buffer.clear(); - } - termBuffer.push_back(string(1, c)); - } else if (c == ' ' && !isString) { - if (!buffer.empty()) { - termBuffer.push_back(buffer); - buffer.clear(); - } - } else { - buffer += c; - } - } - - // Handle any remaining buffer content - if (!buffer.empty()) { - termBuffer.push_back(buffer); - } - if (!termBuffer.empty()) { - terms.push_back(termBuffer); - } - - // Debug output - string debugString; - log.debug("Lines have been parsed."); - for (const auto& term : terms) { - for (const auto& t : term) { - debugString += t + ", "; - } - debugString += "\n"; - } - log.debug(debugString); - } - void processLines() { - // Process vector> terms - for (int i = 0; i < terms.size(); i++) { - for (int j = 0; j < terms[i].size(); j++) { - Token token; - string ct = terms[i][j]; - if (ct == " ") continue; - // Oh boy here we go - else if (ct == "fun") token.keyword = keywords::FUN; - else if (ct == "let") token.keyword = keywords::LET; - else if (ct == "print") token.keyword = keywords::PRINT; - else if (ct == "return") token.keyword = keywords::RETURN; - else if (ct == "exit") token.keyword = keywords::EXIT; - else if (ct == "int") token.keyword = keywords::INT; - else if (ct == "dec") token.keyword = keywords::DEC; - else if (ct == "str") token.keyword = keywords::STR; - else if (ct == "bool") token.keyword = keywords::BOOL; - else if (ct == "{") token.keyword = keywords::OBRAC; - else if (ct == "}") token.keyword = keywords::CBRAC; - else if (ct == "(") token.keyword = keywords::OPARE; - else if (ct == ")") token.keyword = keywords::CPARE; - else if (ct == "+") token.keyword = keywords::ADD; - else if (ct == "-") token.keyword = keywords::SUBTRACT; - else if (ct == "*") token.keyword = keywords::MULTIPLY; - else if (ct == "/") token.keyword = keywords::DIVIDE; - else if (ct == "++") token.keyword = keywords::INCREMENT; - else if (ct == "--") token.keyword = keywords::DECREMENT; - else if (ct == "+=") token.keyword = keywords::ADDTO; - else if (ct == "-=") token.keyword = keywords::SUBTRACTFROM; - else if (ct == "*=") token.keyword = keywords::MULTIPLYTO; - else if (ct == "/=") token.keyword = keywords::DIVIDEFROM; - else { - token.keyword = keywords::VALUE; - // Convert the value based on its type - if (canDec(ct)) { - token.type = valtype::DEC; - token.value.type = valtype::DEC; - token.value.value = stod(ct); - } - else if (canInt(ct)) { - token.type = valtype::INT; - token.value.type = valtype::INT; - token.value.value = stoi(ct); - } - else if (ct == "true" || ct == "false") { - token.type = valtype::BOOL; - token.value.type = valtype::BOOL; - token.value.value = (ct == "true"); - } - else { - // Handle strings - remove quotes if present - token.type = valtype::STR; - token.value.type = valtype::STR; - if (ct.size() >= 2 && ct.front() == '"' && ct.back() == '"') { - // Remove the quotes - token.value.value = ct.substr(1, ct.size() - 2); - } else { - token.value.value = ct; - } - } - } - tokens.push_back(token); - } - Token semi; - semi.keyword = keywords::SEMICOLON; - tokens.push_back(semi); - } -} - vector getTokens() { - return tokens; - } -}; - -class Interpreter { - private: - SyntaxError syntaxError; - vector tokens; - map variables; - Logger log; - int tokenIndex = -1; - optional consume() { - tokenIndex++; - if (tokenIndex < tokens.size()) return tokens[tokenIndex]; - return {}; - } - optional peek(int offset = 1) { - int index = tokenIndex + offset; - if (index >= 0 && index < tokens.size()) return tokens[index]; - return {}; - } - - public: - void convertToTokens(vector tokenList) { - if (debugMode) log.toggleDebugPrint(); - tokens = tokenList; - log.debug("Alright we got " + to_string(tokens.size()) + " tokens"); - - while (tokenIndex < static_cast(tokens.size() - 1)) { - auto currentToken = consume(); - if (!currentToken) break; - - vector currentInstruction; - currentInstruction.push_back(currentToken.value()); - - // Collect tokens until semicolon - while (auto nextToken = peek(1)) { - if (nextToken->keyword == keywords::SEMICOLON) { - consume(); // consume the semicolon - break; - } - consume(); // consume the peeked token - currentInstruction.push_back(nextToken.value()); - } - // Apply variables to tokens - // We start at 1 so we can reassign variables in the execution of code - for (int i = 1; i < currentInstruction.size(); i++) { - if (currentInstruction[i].type == valtype::STR) { - string potentialVarName = get(currentInstruction[i].value.value); - auto varIt = variables.find(potentialVarName); - - if (varIt != variables.end()) { - // Replace the token with the variable's value - Token newToken; - newToken.keyword = keywords::VALUE; - newToken.type = varIt->second.type; - newToken.value = varIt->second; - currentInstruction[i] = newToken; - } - } - } - // Do math - for (int i = 0; i < currentInstruction.size(); i++) { - if (currentInstruction[i].keyword == keywords::ADD || currentInstruction[i].keyword == keywords::SUBTRACT || currentInstruction[i].keyword == keywords::MULTIPLY || currentInstruction[i].keyword == keywords::DIVIDE) { - Token newToken; - newToken.keyword = keywords::VALUE; - if (currentInstruction.size() < i + 1) syntaxError.mathTooFewArgs(); - Token before = currentInstruction[i - 1]; - Token after = currentInstruction[i + 1]; - if (before.type != after.type) syntaxError.mathTypeMismatch(); - newToken.type = before.type; - if (currentInstruction[i].keyword == keywords::ADD) { - if (newToken.type == valtype::INT) { - newToken.value.value = get(before.value.value) + get(after.value.value); - } - else if (newToken.type == valtype::DEC) { - newToken.value.value = get(before.value.value) + get(after.value.value); - } - else if (newToken.type == valtype::STR) { - newToken.value.value = get(before.value.value) + get(after.value.value); - } else { - syntaxError.mathCannotDoOperationOnType("+", "bool"); - } - } - else if (currentInstruction[i].keyword == keywords::SUBTRACT) { - if (newToken.type == valtype::INT) { - newToken.value.value = get(before.value.value) - get(after.value.value); - } - else if (newToken.type == valtype::DEC) { - newToken.value.value = get(before.value.value) - get(after.value.value); - } else { - syntaxError.mathCannotDoOperationOnType("-", "bool or string"); - } - } - else if (currentInstruction[i].keyword == keywords::MULTIPLY) { - if (newToken.type == valtype::INT) { - newToken.value.value = get(before.value.value) * get(after.value.value); - } - else if (newToken.type == valtype::DEC) { - newToken.value.value = get(before.value.value) * get(after.value.value); - } else { - syntaxError.mathCannotDoOperationOnType("*", "bool or string"); - } - } - else if (currentInstruction[i].keyword == keywords::DIVIDE) { - if (newToken.type == valtype::INT) { - newToken.value.value = get(before.value.value) / get(after.value.value); - } - else if (newToken.type == valtype::DEC) { - newToken.value.value = get(before.value.value) / get(after.value.value); - } else { - syntaxError.mathCannotDoOperationOnType("/", "bool or string"); - } - } else { - // Something has gone terribly wrong - // We should never reach this point in the code - syntaxError.generalError("The math aint mathing"); - } - // Insert our cool new token and get rid of the boring old stuff - currentInstruction[i - 1] = newToken; - currentInstruction.erase(currentInstruction.begin() + i); - currentInstruction.erase(currentInstruction.begin() + i); - i -= 1; - } - } - // Execute the instruction - executeCode(currentInstruction); - } - } - void executeCode(vector tokens) { - SyntaxError syntaxError; - log.debug("Token length for this expression is " + to_string(tokens.size())); - for (int i = 0; i < tokens.size(); i++) { - if (tokens[i].keyword == keywords::PRINT) { - i++; - if (tokens.size() <= i) break; - Token nextToken = tokens[i]; - // Handle different value types - if (nextToken.keyword == keywords::VALUE) { - switch (nextToken.type) { - case valtype::INT: - cout << get(nextToken.value.value) << endl; - break; - case valtype::DEC: - cout << get(nextToken.value.value) << endl; - break; - case valtype::STR: - cout << get(nextToken.value.value) << endl; - break; - case valtype::BOOL: - cout << (get(nextToken.value.value) ? "true" : "false") << endl; - break; - default: - vector validTypes = {"int", "dec", "str", "bool"}; - syntaxError.fnTypeMismatch("print", validTypes, nextToken.type); - } - } else { - syntaxError.fnNotSufficientArgs("print", 1, 1, 0); - } - } else if (tokens[i].keyword == keywords::EXIT) { - i++; - if (tokens.size() <= i) break; - Token nextToken = tokens[i]; - if (nextToken.keyword == keywords::VALUE) { - switch (nextToken.type) { - case valtype::INT: - exit(get(nextToken.value.value)); - break; - case valtype::DEC: - exit(get(nextToken.value.value)); - break; - default: - vector validTypes = {"int", "dec"}; - syntaxError.fnTypeMismatch("exit", validTypes, nextToken.type); - } - } - } else if (tokens[i].keyword == keywords::LET) { - i++; - if (tokens.size() <= i + 2) { - syntaxError.fnNotSufficientArgs("let", 3, 3, tokens.size() - i); - break; - } - - Token typeToken = tokens[i]; - Token nameToken = tokens[i + 1]; - Token valueToken = tokens[i + 2]; - - i += 2; - - // Validate that we have a valid variable name - if (nameToken.type != valtype::STR) { - vector validTypes = {"str"}; - syntaxError.fnTypeMismatch("let (variable name)", validTypes, nameToken.type); - continue; - } - - string varName = get(nameToken.value.value); - Value newValue; - - // Check the type declaration matches the value - if (typeToken.keyword == keywords::INT && valueToken.type == valtype::INT) { - newValue.type = valtype::INT; - newValue.value = get(valueToken.value.value); - } - else if (typeToken.keyword == keywords::DEC && valueToken.type == valtype::DEC) { - newValue.type = valtype::DEC; - newValue.value = get(valueToken.value.value); - } - else if (typeToken.keyword == keywords::STR && valueToken.type == valtype::STR) { - newValue.type = valtype::STR; - newValue.value = get(valueToken.value.value); - } - else if (typeToken.keyword == keywords::BOOL && valueToken.type == valtype::BOOL) { - newValue.type = valtype::BOOL; - newValue.value = get(valueToken.value.value); - } - else { - vector validTypes; - if (typeToken.keyword == keywords::INT) validTypes = {"int"}; - else if (typeToken.keyword == keywords::DEC) validTypes = {"dec"}; - else if (typeToken.keyword == keywords::STR) validTypes = {"str"}; - else if (typeToken.keyword == keywords::BOOL) validTypes = {"bool"}; - syntaxError.fnTypeMismatch("let", validTypes, valueToken.type, "Variable name is " + varName); - continue; - } - - // Store the variable - variables[varName] = newValue; - } else { - if (tokens[i].keyword == keywords::VALUE && tokens[i].value.type == valtype::STR && variables.find(get(tokens[i].value.value)) != variables.end()) { - log.debug("Manipulating variable..."); - if (tokens.size() < i + 1) { - syntaxError.mathTooFewArgs(); - } - string varName = get(tokens[i].value.value); - i++; - if (variables[varName].type == valtype::INT) { - if (tokens[i].keyword == keywords::INCREMENT) { - variables[varName].value = get(variables[varName].value) + 1; - } else if (tokens[i].keyword == keywords::DECREMENT) { - variables[varName].value = get(variables[varName].value) - 1; - } else if (tokens[i].keyword == keywords::ADDTO) { - if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); - if (tokens[i + 1].value.type != valtype::INT) syntaxError.mathTypeMismatch("Expected an int when adding to an int"); - variables[varName].value = get(variables[varName].value) + get(tokens[i + 1].value.value); - } else if (tokens[i].keyword == keywords::SUBTRACTFROM) { - if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); - if (tokens[i + 1].value.type != valtype::INT) syntaxError.mathTypeMismatch("Expected an int when subtracting from an int"); - variables[varName].value = get(variables[varName].value) - get(tokens[i + 1].value.value); - } - } else if (variables[varName].type == valtype::DEC) { - if (tokens[i].keyword == keywords::INCREMENT) { - variables[varName].value = get(variables[varName].value) + 1; - } else if (tokens[i].keyword == keywords::DECREMENT) { - variables[varName].value = get(variables[varName].value) - 1; - } else if (tokens[i].keyword == keywords::ADDTO) { - if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); - if (tokens[i + 1].value.type != valtype::DEC) syntaxError.mathTypeMismatch("Expected an dec when adding to an dec"); - variables[varName].value = get(variables[varName].value) + get(tokens[i + 1].value.value); - } else if (tokens[i].keyword == keywords::SUBTRACTFROM) { - if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); - if (tokens[i + 1].value.type != valtype::DEC) syntaxError.mathTypeMismatch("Expected an dec when subtracting from an dec"); - variables[varName].value = get(variables[varName].value) - get(tokens[i + 1].value.value); - } - } - } else { - syntaxError.unknownFn(); - } - } - } - } -}; +#include +#include "common.h" +#include "ArgParser.h" +#include "Logger.h" +#include "Parser.h" +#include "Interpreter.h" int main(int argc, char* argv[]) { // Parse and act upon command line arguments