/* Iodine Copyright (C) 2025 Maxwell Jeffress This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Interpreter.h" #include "common.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::initInterpreter(vector args) { List arguments; arguments.type = valtype::STR; for (int i = 0; i < args.size(); i++) { Value buf; buf.type = valtype::STR; buf.value = args[i]; arguments.value.push_back(buf); } Value buf; buf.type = valtype::LIST; buf.value = arguments; variables["args"] = buf; } valtype Interpreter::keywordToValtype(keywords in) { switch (in) { case keywords::INT: return valtype::INT; case keywords::DEC: return valtype::DEC; case keywords::BOOL: return valtype::BOOL; case keywords::STR: return valtype::STR; default: return valtype::KEYWORD; } return valtype::KEYWORD; } void Interpreter::interpret(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 || nextToken->keyword == keywords::OBRAC || nextToken->keyword == keywords::CBRAC) { consume(); // consume the semicolon break; } else if (nextToken->keyword == keywords::COMMENT) { break; } consume(); // consume the peeked token currentInstruction.push_back(nextToken.value()); lengthOfLine ++; } // Apply variables to tokens, as well as allow users to input strings // 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()) { Token newToken; newToken.keyword = keywords::VALUE; if (varIt->second.type == valtype::LIST) newToken.keyword = keywords::LISTOBJ; newToken.type = varIt->second.type; newToken.value = varIt->second; currentInstruction[i] = newToken; } } else if (currentInstruction[i].keyword == keywords::INPUT) { Token newToken; string buffer; getline(cin, buffer); newToken.value.value = buffer; newToken.keyword = keywords::VALUE; newToken.type = valtype::STR; newToken.value.type = valtype::STR; currentInstruction[i] = newToken; } } // Allow users to get elements from list objects for (int i = 0; i < currentInstruction.size(); i++) { if (currentInstruction[i].keyword == keywords::LISTOBJ || currentInstruction[i].value.type == valtype::LIST) { log.debug("Found a list object"); if (i + 3 < currentInstruction.size()) { log.debug("Instruction is large enough to have a list index"); if (currentInstruction[i + 1].keyword == keywords::OSQUA && currentInstruction[i + 3].keyword == keywords::CSQUA && currentInstruction[i + 2].value.type == valtype::INT) { log.debug("Finding list object item"); if (currentInstruction[i + 2].value.type != valtype::INT) syntaxError.generalError("List index requires an int"); if (get(currentInstruction[i].value.value).value.size() < get(currentInstruction[i + 2].value.value)) syntaxError.listOutOfBounds(); Token newToken; newToken.keyword = keywords::VALUE; newToken.type = get(currentInstruction[i].value.value).type; newToken.value.type = get(currentInstruction[i].value.value).type; log.debug("Writing type " + log.getTypeString(newToken.type)); newToken.value = get(currentInstruction[i].value.value).value[get(currentInstruction[i + 2].value.value)]; currentInstruction[i] = newToken; currentInstruction.erase(currentInstruction.begin() + i + 1); currentInstruction.erase(currentInstruction.begin() + i + 1); currentInstruction.erase(currentInstruction.begin() + i + 1); } } } } // 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; } } // Detect any comparisons and convert // Start at 1 to avoid having to do more crappy memory safety stuff for (int i = 1; i < currentInstruction.size(); i++) { if (currentInstruction[i].keyword == keywords::EQUAL) { Token newToken; newToken.keyword = keywords::VALUE; newToken.type = valtype::BOOL; newToken.value.type = valtype::BOOL; if (currentInstruction.size() < i + 1) syntaxError.mathTooFewArgs(); if (currentInstruction[i - 1].type != currentInstruction[i + 1].type) syntaxError.mathTypeMismatch(); log.debug("Comparing values of type: " + to_string(static_cast(currentInstruction[i - 1].type))); // Make sure both operands are values if (currentInstruction[i - 1].keyword != keywords::VALUE || currentInstruction[i + 1].keyword != keywords::VALUE) { syntaxError.generalError("Can only compare values"); return; } // Ensure value types are set correctly if (currentInstruction[i - 1].value.type == valtype::INT) { newToken.value.value = (get(currentInstruction[i - 1].value.value) == get(currentInstruction[i + 1].value.value)); } else if (currentInstruction[i - 1].value.type == valtype::DEC) { newToken.value.value = (get(currentInstruction[i - 1].value.value) == get(currentInstruction[i + 1].value.value)); } else if (currentInstruction[i - 1].value.type == valtype::STR) { newToken.value.value = (get(currentInstruction[i - 1].value.value) == get(currentInstruction[i + 1].value.value)); } else if (currentInstruction[i - 1].value.type == valtype::BOOL) { newToken.value.value = (get(currentInstruction[i - 1].value.value) == get(currentInstruction[i + 1].value.value)); } currentInstruction[i - 1] = newToken; currentInstruction.erase(currentInstruction.begin() + i); currentInstruction.erase(currentInstruction.begin() + i); i -= 1; } if (currentInstruction[i].keyword == keywords::INEQUAL) { Token newToken; newToken.keyword = keywords::VALUE; newToken.type = valtype::BOOL; newToken.value.type = valtype::BOOL; if (currentInstruction.size() < i + 1) syntaxError.mathTooFewArgs(); if (currentInstruction[i - 1].type != currentInstruction[i + 1].type) syntaxError.mathTypeMismatch(); log.debug("Comparing values of type: " + to_string(static_cast(currentInstruction[i - 1].type))); // Make sure both operands are values if (currentInstruction[i - 1].keyword != keywords::VALUE || currentInstruction[i + 1].keyword != keywords::VALUE) { syntaxError.generalError("Can only compare values of"); return; } // Ensure value types are set correctly if (currentInstruction[i - 1].value.type == valtype::INT) { newToken.value.value = (get(currentInstruction[i - 1].value.value) != get(currentInstruction[i + 1].value.value)); } else if (currentInstruction[i - 1].value.type == valtype::DEC) { newToken.value.value = (get(currentInstruction[i - 1].value.value) != get(currentInstruction[i + 1].value.value)); } else if (currentInstruction[i - 1].value.type == valtype::STR) { newToken.value.value = (get(currentInstruction[i - 1].value.value) != get(currentInstruction[i + 1].value.value)); } else if (currentInstruction[i - 1].value.type == valtype::BOOL) { newToken.value.value = (get(currentInstruction[i - 1].value.value) != get(currentInstruction[i + 1].value.value)); } currentInstruction[i - 1] = newToken; currentInstruction.erase(currentInstruction.begin() + i); currentInstruction.erase(currentInstruction.begin() + i); i -= 1; } } // Find lists and create list objects for (int i = 0; i < currentInstruction.size(); i++) { if (currentInstruction[i].keyword == keywords::OSQUA && currentInstruction[i + 1].keyword != keywords::CSQUA) { log.debug("Making a list"); int startIndex = i; currentInstruction.erase(currentInstruction.begin() + i); List buf; buf.type = currentInstruction[i].value.type; while (currentInstruction[i].keyword != keywords::CSQUA) { if (buf.type == currentInstruction[i].value.type) { buf.value.push_back(currentInstruction[i].value); currentInstruction.erase(currentInstruction.begin() + i); } else { syntaxError.listTypeMismatch(); } } currentInstruction.erase(currentInstruction.begin() + i); Value newValue; newValue.type = valtype::LIST; newValue.value = buf; Token newToken; newToken.type = valtype::LIST; newToken.value.type = valtype::LIST; newToken.value.value = buf; newToken.keyword = keywords::LISTOBJ; currentInstruction.insert(currentInstruction.begin() + startIndex, newToken); } } // Execute the instruction log.debug("Length of line is " + to_string(lengthOfLine)); executeCode(currentInstruction); lengthOfLine = 0; } } 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 (skipScope > 0) { int braceCount = 0; while (i < tokens.size()) { if (tokens[i].keyword == keywords::OBRAC) { braceCount ++; } else if (tokens[i].keyword == keywords::CBRAC) { if (braceCount == 0) { skipScope --; break; } else { braceCount --; } } i++; } } if (repeatScope > 0) { if (tokens[i].keyword == keywords::CBRAC) { tokenIndex = loop; repeatScope --; return; } } if (tokens[i].keyword == keywords::PRINTLN) { i++; if (tokens.size() <= i) syntaxError.fnNotSufficientArgs("println", 1, 1, 0); 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("println", validTypes, nextToken.type); } } else { syntaxError.fnTypeMismatch("println", {}, nextToken.type); } } else 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); break; case valtype::DEC: cout << get(nextToken.value.value); break; case valtype::STR: cout << get(nextToken.value.value); break; case valtype::BOOL: cout << (get(nextToken.value.value) ? "true" : "false"); 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::PRINTLIST) { i++; if (tokens.size() <= i) syntaxError.fnNotSufficientArgs("printlist", 1, 1, 0); Token nextToken = tokens[i]; log.debug("Printing a list. Type of next token is " + log.getTypeString(nextToken.value.type)); if (nextToken.keyword == keywords::LISTOBJ && nextToken.type == valtype::LIST) { List list = get(nextToken.value.value); log.debug("List obtained"); if (list.value[0].type == valtype::INT) { for (int j = 0; j < list.value.size(); j++) { cout << get(list.value[j].value) << ", "; } } else if (list.value[0].type == valtype::DEC) { for (int j = 0; j < list.value.size(); j++) { cout << get(list.value[j].value) << ", "; } } else if (list.value[0].type == valtype::STR) { for (int j = 0; j < list.value.size(); j++) { cout << get(list.value[j].value) << ", "; } } else if (list.value[0].type == valtype::BOOL) { for (int j = 0; j < list.value.size(); j++) { cout << (get(list.value[j].value) ? "true" : "false") << ", "; } } cout << endl; } else { syntaxError.fnTypeMismatch("printlist", {}, valtype::UNKNOWN); } break; } 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; if (valueToken.keyword == keywords::LISTOBJ) { if (typeToken.value.type == get(valueToken.value.value).type) { newValue.type = valtype::LIST; newValue.value = get(valueToken.value.value); } else { syntaxError.fnTypeMismatch("let (listobj)", {log.getTypeString(get(valueToken.value.value).type)}, valueToken.type, "Variable name is " + varName); } } // Check the type declaration matches the value else 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::IF) { i++; if (tokens.size() < i) syntaxError.fnNotSufficientArgs("if", 1, 1, 0); log.debug("IF statement token type: " + to_string(static_cast(tokens[i].type))); log.debug("IF statement token keyword: " + to_string(static_cast(tokens[i].keyword))); if (tokens[i].keyword != keywords::VALUE) syntaxError.generalError("if needs a value, not a keyword"); if (tokens[i].type != valtype::BOOL) syntaxError.comparisonTypeError("in an if statement"); if (!get(tokens[i].value.value)) { skipScope ++; return; } } else if (tokens[i].keyword == keywords::WHILE) { i++; if (tokens.size() < i) syntaxError.fnNotSufficientArgs("while", 1, 1, 0); if (tokens[i].keyword != keywords::VALUE) syntaxError.generalError("while needs a value, not a keyword"); if (tokens[i].type != valtype::BOOL) syntaxError.comparisonTypeError("in a while statement"); log.debug("This is a while loop"); if (get(tokens[i].value.value) == true) { loop = tokenIndex - 2 - lengthOfLine; repeatScope ++; log.debug("While loop condition is true, will skip back to " + to_string(loop)); } else if (!get(tokens[i].value.value)) { int braceLevel = 0; while (++i < tokens.size()) { if (tokens[i].keyword == keywords::OBRAC) braceLevel ++; else if (tokens[i].keyword == keywords::CBRAC) { if (braceLevel == 0) break; braceLevel --; } break; } } else { syntaxError.generalError("Achievement get: How did we get here?"); } } else if (tokens[i].keyword == keywords::FUN) { i++; Function buf; if (keywordToValtype(tokens[i].keyword) == valtype::KEYWORD) syntaxError.generalError("fun needs a defining value for arg 1, not a value. "); buf.type = keywordToValtype(tokens[i].keyword); i++; string valName; if (tokens[i].value.type == valtype::STR) valName = get(tokens[i].value.value); buf.startToken = tokenIndex; i++; if (tokens[i].keyword != keywords::OPARE) syntaxError.generalError("This is a placeholder"); i++; map valuetypes; while (tokens[i].keyword != keywords::CPARE) { // We should have a valtype, a name for the variable, and a comma if the thing hasn't ended already if (keywordToValtype(tokens[i].keyword) == valtype::KEYWORD) syntaxError.generalError("Another placeholder"); valtype valbuf = keywordToValtype(tokens[i].keyword); i++; if (tokens[i].type != valtype::STR) syntaxError.generalError("A third placeholder"); valuetypes[get(tokens[i].value.value)] = valbuf; i++; if (tokens[i].keyword == keywords::CPARE) break; if (tokens[i].keyword != keywords::COMMA) syntaxError.generalError("Yet another placeholder"); i++; } functions[valName] = buf; log.debug("Init a function"); } 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 + 2) { // Need at least 3 tokens: variable, operator, value syntaxError.mathTooFewArgs(); return; } string varName = get(tokens[i].value.value); i++; if (tokens[i].keyword == keywords::SET) { Token valueToken = tokens[i + 1]; if (valueToken.type != variables[varName].type) { vector validTypes; switch(variables[varName].type) { case valtype::INT: validTypes = {"int"}; break; case valtype::DEC: validTypes = {"dec"}; break; case valtype::STR: validTypes = {"str"}; break; case valtype::BOOL: validTypes = {"bool"}; break; default: validTypes = {"unknown"}; break; } syntaxError.fnTypeMismatch("assignment", validTypes, valueToken.type); return; } variables[varName].value = valueToken.value.value; i++; // Skip the value token since we've processed it } else if (tokens[i].keyword == keywords::INCREMENT) { if (variables[varName].type == valtype::INT) { variables[varName].value = get(variables[varName].value) + 1; } else { syntaxError.mathCannotDoOperationOnType("++", "non-int"); } } else if (tokens[i].keyword == keywords::DECREMENT) { if (variables[varName].type == valtype::INT) { variables[varName].value = get(variables[varName].value) - 1; } else { syntaxError.mathCannotDoOperationOnType("--", "non-int"); } } else if (tokens[i].keyword == keywords::ADDTO) { if (tokens.size() < i + 1) syntaxError.mathTooFewArgs(); if (tokens[i + 1].value.type != variables[varName].type) syntaxError.mathTypeMismatch("Expected same type when adding"); if (variables[varName].type == valtype::INT) { variables[varName].value = get(variables[varName].value) + get(tokens[i + 1].value.value); } else if (variables[varName].type == valtype::DEC) { variables[varName].value = get(variables[varName].value) + get(tokens[i + 1].value.value); } else { syntaxError.mathCannotDoOperationOnType("+=", "non-numeric"); } i++; } else { syntaxError.mathCannotDoOperationOnType("unknown", "any"); } } else { if (tokens[i].keyword != keywords::CBRAC) syntaxError.unknownFn(); } } } }