2025-05-10 21:28:56 +10:00
|
|
|
/*
|
|
|
|
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 <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "Interpreter.h"
|
|
|
|
#include "common.h"
|
|
|
|
|
|
|
|
optional<Token> Interpreter::consume() {
|
|
|
|
tokenIndex++;
|
|
|
|
if (tokenIndex < tokens.size()) return tokens[tokenIndex];
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
optional<Token> Interpreter::peek(int offset) {
|
|
|
|
int index = tokenIndex + offset;
|
|
|
|
if (index >= 0 && index < tokens.size()) return tokens[index];
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2025-05-12 11:28:06 +10:00
|
|
|
void Interpreter::initInterpreter(vector<string> args) {
|
|
|
|
List arguments;
|
2025-05-13 11:06:40 +10:00
|
|
|
arguments.type = valtype::STR;
|
2025-05-12 11:28:06 +10:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-05-16 06:17:57 +10:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-05-12 11:28:06 +10:00
|
|
|
void Interpreter::interpret(vector<Token> tokenList) {
|
2025-05-10 21:28:56 +10:00
|
|
|
if (debugMode) log.toggleDebugPrint();
|
|
|
|
tokens = tokenList;
|
|
|
|
log.debug("Alright we got " + to_string(tokens.size()) + " tokens");
|
|
|
|
|
|
|
|
while (tokenIndex < static_cast<int>(tokens.size() - 1)) {
|
|
|
|
auto currentToken = consume();
|
|
|
|
if (!currentToken) break;
|
|
|
|
|
|
|
|
vector<Token> 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<string>(currentInstruction[i].value.value);
|
|
|
|
auto varIt = variables.find(potentialVarName);
|
|
|
|
if (varIt != variables.end()) {
|
2025-05-13 11:06:40 +10:00
|
|
|
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;
|
2025-05-10 21:28:56 +10:00
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
}
|
2025-05-13 11:06:40 +10:00
|
|
|
// 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<List>(currentInstruction[i].value.value).value.size() < get<int>(currentInstruction[i + 2].value.value)) syntaxError.listOutOfBounds();
|
|
|
|
Token newToken;
|
|
|
|
newToken.keyword = keywords::VALUE;
|
|
|
|
newToken.type = get<List>(currentInstruction[i].value.value).type;
|
|
|
|
newToken.value.type = get<List>(currentInstruction[i].value.value).type;
|
|
|
|
log.debug("Writing type " + log.getTypeString(newToken.type));
|
|
|
|
newToken.value = get<List>(currentInstruction[i].value.value).value[get<int>(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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-05-10 21:28:56 +10:00
|
|
|
// 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<int>(before.value.value) + get<int>(after.value.value);
|
|
|
|
}
|
|
|
|
else if (newToken.type == valtype::DEC) {
|
|
|
|
newToken.value.value = get<double>(before.value.value) + get<double>(after.value.value);
|
|
|
|
}
|
|
|
|
else if (newToken.type == valtype::STR) {
|
|
|
|
newToken.value.value = get<string>(before.value.value) + get<string>(after.value.value);
|
|
|
|
} else {
|
|
|
|
syntaxError.mathCannotDoOperationOnType("+", "bool");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (currentInstruction[i].keyword == keywords::SUBTRACT) {
|
|
|
|
if (newToken.type == valtype::INT) {
|
|
|
|
newToken.value.value = get<int>(before.value.value) - get<int>(after.value.value);
|
|
|
|
}
|
|
|
|
else if (newToken.type == valtype::DEC) {
|
|
|
|
newToken.value.value = get<double>(before.value.value) - get<double>(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<int>(before.value.value) * get<int>(after.value.value);
|
|
|
|
}
|
|
|
|
else if (newToken.type == valtype::DEC) {
|
|
|
|
newToken.value.value = get<double>(before.value.value) * get<double>(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<int>(before.value.value) / get<int>(after.value.value);
|
|
|
|
}
|
|
|
|
else if (newToken.type == valtype::DEC) {
|
|
|
|
newToken.value.value = get<double>(before.value.value) / get<double>(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<int>(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<int>(currentInstruction[i - 1].value.value) ==
|
|
|
|
get<int>(currentInstruction[i + 1].value.value));
|
|
|
|
} else if (currentInstruction[i - 1].value.type == valtype::DEC) {
|
|
|
|
newToken.value.value = (get<double>(currentInstruction[i - 1].value.value) ==
|
|
|
|
get<double>(currentInstruction[i + 1].value.value));
|
|
|
|
} else if (currentInstruction[i - 1].value.type == valtype::STR) {
|
|
|
|
newToken.value.value = (get<string>(currentInstruction[i - 1].value.value) ==
|
|
|
|
get<string>(currentInstruction[i + 1].value.value));
|
|
|
|
} else if (currentInstruction[i - 1].value.type == valtype::BOOL) {
|
|
|
|
newToken.value.value = (get<bool>(currentInstruction[i - 1].value.value) ==
|
|
|
|
get<bool>(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<int>(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<int>(currentInstruction[i - 1].value.value) !=
|
|
|
|
get<int>(currentInstruction[i + 1].value.value));
|
|
|
|
} else if (currentInstruction[i - 1].value.type == valtype::DEC) {
|
|
|
|
newToken.value.value = (get<double>(currentInstruction[i - 1].value.value) !=
|
|
|
|
get<double>(currentInstruction[i + 1].value.value));
|
|
|
|
} else if (currentInstruction[i - 1].value.type == valtype::STR) {
|
|
|
|
newToken.value.value = (get<string>(currentInstruction[i - 1].value.value) !=
|
|
|
|
get<string>(currentInstruction[i + 1].value.value));
|
|
|
|
} else if (currentInstruction[i - 1].value.type == valtype::BOOL) {
|
|
|
|
newToken.value.value = (get<bool>(currentInstruction[i - 1].value.value) !=
|
|
|
|
get<bool>(currentInstruction[i + 1].value.value));
|
|
|
|
}
|
|
|
|
|
|
|
|
currentInstruction[i - 1] = newToken;
|
|
|
|
currentInstruction.erase(currentInstruction.begin() + i);
|
|
|
|
currentInstruction.erase(currentInstruction.begin() + i);
|
|
|
|
i -= 1;
|
|
|
|
}
|
|
|
|
}
|
2025-05-12 20:04:38 +10:00
|
|
|
// Find lists and create list objects
|
|
|
|
for (int i = 0; i < currentInstruction.size(); i++) {
|
2025-05-13 14:05:29 +10:00
|
|
|
if (currentInstruction[i].keyword == keywords::OSQUA && currentInstruction[i + 1].keyword != keywords::CSQUA) {
|
2025-05-12 20:04:38 +10:00
|
|
|
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;
|
2025-05-13 09:07:35 +10:00
|
|
|
newToken.keyword = keywords::LISTOBJ;
|
2025-05-12 20:04:38 +10:00
|
|
|
currentInstruction.insert(currentInstruction.begin() + startIndex, newToken);
|
|
|
|
}
|
|
|
|
}
|
2025-05-10 21:28:56 +10:00
|
|
|
// Execute the instruction
|
|
|
|
log.debug("Length of line is " + to_string(lengthOfLine));
|
|
|
|
executeCode(currentInstruction);
|
|
|
|
lengthOfLine = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Interpreter::executeCode(vector<Token> 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()) {
|
2025-05-11 13:55:52 +10:00
|
|
|
if (tokens[i].keyword == keywords::OBRAC) {
|
|
|
|
braceCount ++;
|
|
|
|
} else if (tokens[i].keyword == keywords::CBRAC) {
|
|
|
|
if (braceCount == 0) {
|
|
|
|
skipScope --;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
braceCount --;
|
2025-05-10 21:28:56 +10:00
|
|
|
}
|
|
|
|
}
|
2025-05-11 13:55:52 +10:00
|
|
|
i++;
|
2025-05-10 21:28:56 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (repeatScope > 0) {
|
|
|
|
if (tokens[i].keyword == keywords::CBRAC) {
|
|
|
|
tokenIndex = loop;
|
|
|
|
repeatScope --;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (tokens[i].keyword == keywords::PRINTLN) {
|
|
|
|
i++;
|
2025-05-12 20:04:38 +10:00
|
|
|
if (tokens.size() <= i) syntaxError.fnNotSufficientArgs("println", 1, 1, 0);
|
2025-05-10 21:28:56 +10:00
|
|
|
Token nextToken = tokens[i];
|
|
|
|
// Handle different value types
|
|
|
|
if (nextToken.keyword == keywords::VALUE) {
|
|
|
|
switch (nextToken.type) {
|
|
|
|
case valtype::INT:
|
|
|
|
cout << get<int>(nextToken.value.value) << endl;
|
|
|
|
break;
|
|
|
|
case valtype::DEC:
|
|
|
|
cout << get<double>(nextToken.value.value) << endl;
|
|
|
|
break;
|
|
|
|
case valtype::STR:
|
|
|
|
cout << get<string>(nextToken.value.value) << endl;
|
|
|
|
break;
|
|
|
|
case valtype::BOOL:
|
|
|
|
cout << (get<bool>(nextToken.value.value) ? "true" : "false") << endl;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
vector<string> validTypes = {"int", "dec", "str", "bool"};
|
|
|
|
syntaxError.fnTypeMismatch("println", validTypes, nextToken.type);
|
|
|
|
}
|
|
|
|
} else {
|
2025-05-12 20:04:38 +10:00
|
|
|
syntaxError.fnTypeMismatch("println", {}, nextToken.type);
|
2025-05-10 21:28:56 +10:00
|
|
|
}
|
|
|
|
} 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<int>(nextToken.value.value);
|
|
|
|
break;
|
|
|
|
case valtype::DEC:
|
|
|
|
cout << get<double>(nextToken.value.value);
|
|
|
|
break;
|
|
|
|
case valtype::STR:
|
|
|
|
cout << get<string>(nextToken.value.value);
|
|
|
|
break;
|
|
|
|
case valtype::BOOL:
|
|
|
|
cout << (get<bool>(nextToken.value.value) ? "true" : "false");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
vector<string> validTypes = {"int", "dec", "str", "bool"};
|
|
|
|
syntaxError.fnTypeMismatch("print", validTypes, nextToken.type);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
syntaxError.fnNotSufficientArgs("print", 1, 1, 0);
|
|
|
|
}
|
2025-05-12 20:04:38 +10:00
|
|
|
} 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));
|
2025-05-13 09:07:35 +10:00
|
|
|
if (nextToken.keyword == keywords::LISTOBJ && nextToken.type == valtype::LIST) {
|
2025-05-12 20:04:38 +10:00
|
|
|
List list = get<List>(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<int>(list.value[j].value) << ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (list.value[0].type == valtype::DEC) {
|
|
|
|
for (int j = 0; j < list.value.size(); j++) {
|
|
|
|
cout << get<double>(list.value[j].value) << ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (list.value[0].type == valtype::STR) {
|
|
|
|
for (int j = 0; j < list.value.size(); j++) {
|
|
|
|
cout << get<string>(list.value[j].value) << ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (list.value[0].type == valtype::BOOL) {
|
|
|
|
for (int j = 0; j < list.value.size(); j++) {
|
|
|
|
cout << (get<bool>(list.value[j].value) ? "true" : "false") << ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cout << endl;
|
|
|
|
} else {
|
|
|
|
syntaxError.fnTypeMismatch("printlist", {}, valtype::UNKNOWN);
|
|
|
|
}
|
|
|
|
break;
|
2025-05-10 21:28:56 +10:00
|
|
|
} 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<int>(nextToken.value.value));
|
|
|
|
break;
|
|
|
|
case valtype::DEC:
|
|
|
|
exit(get<double>(nextToken.value.value));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
vector<string> 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<string> validTypes = {"str"};
|
|
|
|
syntaxError.fnTypeMismatch("let (variable name)", validTypes, nameToken.type);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
string varName = get<string>(nameToken.value.value);
|
|
|
|
Value newValue;
|
|
|
|
|
2025-05-13 14:05:29 +10:00
|
|
|
if (valueToken.keyword == keywords::LISTOBJ) {
|
|
|
|
if (typeToken.value.type == get<List>(valueToken.value.value).type) {
|
|
|
|
newValue.type = valtype::LIST;
|
|
|
|
newValue.value = get<List>(valueToken.value.value);
|
|
|
|
} else {
|
|
|
|
syntaxError.fnTypeMismatch("let (listobj)", {log.getTypeString(get<List>(valueToken.value.value).type)}, valueToken.type, "Variable name is " + varName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-10 21:28:56 +10:00
|
|
|
// Check the type declaration matches the value
|
2025-05-13 14:05:29 +10:00
|
|
|
else if (typeToken.keyword == keywords::INT && valueToken.type == valtype::INT) {
|
2025-05-10 21:28:56 +10:00
|
|
|
newValue.type = valtype::INT;
|
|
|
|
newValue.value = get<int>(valueToken.value.value);
|
|
|
|
}
|
|
|
|
else if (typeToken.keyword == keywords::DEC && valueToken.type == valtype::DEC) {
|
|
|
|
newValue.type = valtype::DEC;
|
|
|
|
newValue.value = get<double>(valueToken.value.value);
|
|
|
|
}
|
|
|
|
else if (typeToken.keyword == keywords::STR && valueToken.type == valtype::STR) {
|
|
|
|
newValue.type = valtype::STR;
|
|
|
|
newValue.value = get<string>(valueToken.value.value);
|
|
|
|
}
|
|
|
|
else if (typeToken.keyword == keywords::BOOL && valueToken.type == valtype::BOOL) {
|
|
|
|
newValue.type = valtype::BOOL;
|
|
|
|
newValue.value = get<bool>(valueToken.value.value);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
vector<string> 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<int>(tokens[i].type)));
|
|
|
|
log.debug("IF statement token keyword: " + to_string(static_cast<int>(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<bool>(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<bool>(tokens[i].value.value) == true) {
|
|
|
|
loop = tokenIndex - 2 - lengthOfLine;
|
|
|
|
repeatScope ++;
|
|
|
|
log.debug("While loop condition is true, will skip back to " + to_string(loop));
|
2025-05-11 13:55:52 +10:00
|
|
|
} else if (!get<bool>(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;
|
|
|
|
}
|
2025-05-10 21:28:56 +10:00
|
|
|
} else {
|
2025-05-11 13:55:52 +10:00
|
|
|
syntaxError.generalError("Achievement get: How did we get here?");
|
2025-05-10 21:28:56 +10:00
|
|
|
}
|
2025-05-18 14:27:04 +10:00
|
|
|
} else if (tokens[i].keyword == keywords::FUN) {
|
2025-05-16 06:17:57 +10:00
|
|
|
i++;
|
|
|
|
Function buf;
|
2025-05-18 14:27:04 +10:00
|
|
|
if (keywordToValtype(tokens[i].keyword) == valtype::KEYWORD) syntaxError.generalError("fun needs a defining value for arg 1, not a value. ");
|
2025-05-16 06:17:57 +10:00
|
|
|
buf.type = keywordToValtype(tokens[i].keyword);
|
|
|
|
i++;
|
|
|
|
string valName;
|
|
|
|
if (tokens[i].value.type == valtype::STR) valName = get<string>(tokens[i].value.value);
|
|
|
|
buf.startToken = tokenIndex;
|
|
|
|
i++;
|
2025-05-18 14:27:04 +10:00
|
|
|
if (tokens[i].keyword != keywords::OPARE) syntaxError.generalError("This is a placeholder");
|
|
|
|
i++;
|
|
|
|
map<string, valtype> 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<string>(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");
|
2025-05-10 21:28:56 +10:00
|
|
|
} else {
|
|
|
|
if (tokens[i].keyword == keywords::VALUE && tokens[i].value.type == valtype::STR && variables.find(get<string>(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<string>(tokens[i].value.value);
|
|
|
|
i++;
|
|
|
|
|
|
|
|
if (tokens[i].keyword == keywords::SET) {
|
|
|
|
Token valueToken = tokens[i + 1];
|
|
|
|
if (valueToken.type != variables[varName].type) {
|
|
|
|
vector<string> 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;
|
2025-05-18 14:27:04 +10:00
|
|
|
default: validTypes = {"unknown"}; break;
|
2025-05-10 21:28:56 +10:00
|
|
|
}
|
|
|
|
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<int>(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<int>(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<int>(variables[varName].value) + get<int>(tokens[i + 1].value.value);
|
|
|
|
} else if (variables[varName].type == valtype::DEC) {
|
|
|
|
variables[varName].value = get<double>(variables[varName].value) + get<double>(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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|