610 lines
23 KiB
C++
610 lines
23 KiB
C++
#include <iostream>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
using namespace std;
|
|
|
|
enum class VarType {
|
|
INTEGER,
|
|
DECIMAL,
|
|
STRING,
|
|
UNKNOWN
|
|
};
|
|
|
|
struct Variable {
|
|
VarType type;
|
|
variant<int, double, string> value;
|
|
|
|
Variable() : type(VarType::UNKNOWN) {}
|
|
|
|
Variable(VarType t, const variant<int, double, string>& v) : type(t), value(v) {}
|
|
|
|
string toString() const {
|
|
switch(type) {
|
|
case VarType::INTEGER:
|
|
return to_string(get<int>(value));
|
|
case VarType::DECIMAL:
|
|
return to_string(get<double>(value));
|
|
case VarType::STRING:
|
|
return get<string>(value);
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
};
|
|
|
|
class Interpreter {
|
|
private:
|
|
map<string, Variable> variables;
|
|
bool isVerbose = false;
|
|
const vector<string> builtInFunctions = {
|
|
"exit", "log", "type", "run", "verbose", "str", "dec", "int"
|
|
};
|
|
|
|
const vector<string> operators = {
|
|
"+", "-", "*", "/"
|
|
};
|
|
|
|
const vector<string> incrementors = {
|
|
"++", "--"
|
|
};
|
|
|
|
const vector<string> modifiers = {
|
|
"+=", "-=", "*=", "/="
|
|
};
|
|
|
|
const vector<string> comparitors = {
|
|
"==", ">", ">=", "=<", "!=", "!<", "!>", "!=<", "!>="
|
|
};
|
|
|
|
bool checkForDec(const string& str) {
|
|
try {
|
|
return stoi(str) * 1.0 != stod(str);
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool checkForInt(const string& str) {
|
|
try {
|
|
return stoi(str) * 1.0 == stod(str);
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void error(const string& str) {
|
|
cout << "\u001b[31m Error running code: " << str << "\u001b[39m" << endl;
|
|
}
|
|
|
|
void verbose(const string& str) {
|
|
if (isVerbose) cout << "\u001b[36m Info: " << str << "\u001b[39m" << endl;
|
|
}
|
|
|
|
string interpolateVariables(const string& input) {
|
|
string result;
|
|
size_t pos = 0;
|
|
bool inString = false;
|
|
|
|
while (pos < input.length()) {
|
|
if (input[pos] == '"') {
|
|
inString = !inString;
|
|
result += input[pos];
|
|
pos++;
|
|
} else if (pos + 1 < input.length() && input[pos] == '$' && input[pos + 1] == '{') {
|
|
size_t end = input.find('}', pos);
|
|
if (end != string::npos) {
|
|
string varName = input.substr(pos + 2, end - (pos + 2));
|
|
auto it = variables.find(varName);
|
|
if (it != variables.end()) {
|
|
result += it->second.toString();
|
|
} else {
|
|
error("Variable " + varName + " not found");
|
|
result += "${" + varName + "}";
|
|
}
|
|
pos = end + 1;
|
|
} else {
|
|
result += input[pos];
|
|
pos++;
|
|
}
|
|
} else {
|
|
result += input[pos];
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
vector<pair<string, string>> preProcessTokens(const vector<pair<string, string>>& tokens) {
|
|
vector<pair<string, string>> processed;
|
|
for (const auto& [token, type] : tokens) {
|
|
if (type == "str") {
|
|
string processedStr = interpolateVariables(token);
|
|
processed.push_back({processedStr, "str"});
|
|
} else if (token[0] == '$' && token.length() > 1) {
|
|
string varName = token.substr(1);
|
|
auto it = variables.find(varName);
|
|
if (it != variables.end()) {
|
|
processed.push_back({it->second.toString(), getTokenType(it->second.toString())});
|
|
} else {
|
|
error("Variable " + varName + " not found");
|
|
processed.push_back({token, "unknown"});
|
|
}
|
|
} else {
|
|
processed.push_back({token, type});
|
|
}
|
|
}
|
|
return processed;
|
|
}
|
|
|
|
vector<pair<string, string>> tokenize(const string& input) {
|
|
vector<pair<string, string>> tokens;
|
|
string currentToken;
|
|
bool stringOpen = false;
|
|
|
|
for (char c : input) {
|
|
if (c == ' ' && !stringOpen) {
|
|
if (!currentToken.empty()) {
|
|
tokens.push_back({currentToken, getTokenType(currentToken)});
|
|
currentToken.clear();
|
|
}
|
|
} else {
|
|
currentToken += c;
|
|
}
|
|
if (c == '"') stringOpen = !stringOpen;
|
|
}
|
|
|
|
if (!currentToken.empty()) {
|
|
tokens.push_back({currentToken, getTokenType(currentToken)});
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
string getTokenType(const string& token) {
|
|
for (const auto& func : builtInFunctions) {
|
|
if (token == func) return "function";
|
|
}
|
|
for (const auto& inc : incrementors) {
|
|
if (token == inc) return "incrementor";
|
|
}
|
|
for (const auto& mod : modifiers) {
|
|
if (token == mod) return "modifier";
|
|
}
|
|
for (const auto& op : operators) {
|
|
if (token == op) return "operator";
|
|
}
|
|
for (const auto& comp : comparitors) {
|
|
if (token == comp) return "comparitor";
|
|
}
|
|
if (variables.find(token) != variables.end()) return "variable";
|
|
|
|
if (token == "=") return "equals";
|
|
if (token[0] == '"') return "str";
|
|
if (checkForInt(token)) return "int";
|
|
if (checkForDec(token)) return "dec";
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
variant<int, string, double> handleVariable(const string variable) {
|
|
auto& var = variables[variable];
|
|
if (var.type == VarType::INTEGER) {
|
|
return get<int>(var.value);
|
|
} else if (var.type == VarType::STRING) {
|
|
return get<string>(var.value);
|
|
} else if (var.type == VarType::DECIMAL) {
|
|
return get<double>(var.value);
|
|
}
|
|
error("Unknown variable " + variable);
|
|
return "Error";
|
|
}
|
|
|
|
int doIntMath(const vector<pair<string, string>>& tokens) {
|
|
verbose("Var type is integer");
|
|
if (tokens.size() == 2 && tokens[1].second == "incrementor") {
|
|
verbose("Incrementing...");
|
|
auto var = handleVariable(tokens[0].first);
|
|
if (!std::holds_alternative<int>(var)) {
|
|
error("Only integers can be incremented");
|
|
return 0;
|
|
}
|
|
if (tokens[1].first == "++") return get<int>(var) + 1;
|
|
if (tokens[1].first == "--") return get<int>(var) - 1;
|
|
return 0;
|
|
}
|
|
if (tokens.size() == 5 && tokens[1].first == "=" && tokens[3].second == "operator") {
|
|
verbose("Detected an operator");
|
|
|
|
int left = (tokens[2].second == "variable") ?
|
|
get<int>(handleVariable(tokens[2].first)) :
|
|
stoi(tokens[2].first);
|
|
int right = (tokens[4].second == "variable") ?
|
|
get<int>(handleVariable(tokens[4].first)) :
|
|
stoi(tokens[4].first);
|
|
|
|
if (tokens[3].first == "+") return left + right;
|
|
if (tokens[3].first == "-") return left - right;
|
|
if (tokens[3].first == "*") return left * right;
|
|
if (tokens[3].first == "/") {
|
|
if (right == 0) {
|
|
error("Division by zero");
|
|
return 0;
|
|
}
|
|
return left / right;
|
|
}
|
|
}
|
|
|
|
error("Invalid operation");
|
|
return 0;
|
|
}
|
|
|
|
public:
|
|
auto executeCommand(const string& input) {
|
|
string processedInput = interpolateVariables(input);
|
|
auto tokens = tokenize(input);
|
|
if (tokens.empty()) return 0;
|
|
tokens = preProcessTokens(tokens);
|
|
|
|
if (tokens[0].first == "log") {
|
|
verbose("Log command run");
|
|
|
|
if (tokens.size() < 2) {
|
|
error("log requires an argument");
|
|
return 0;
|
|
}
|
|
const auto& [token, type] = tokens[1];
|
|
if (type == "str" || type == "int" || type == "dec") {
|
|
cout << token << endl;
|
|
}
|
|
else {
|
|
error("that type cannot be logged");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
else if (tokens[0].first == "exit") {
|
|
if (tokens.size() < 2) {
|
|
exit(0);
|
|
} else if (tokens.size() > 1) {
|
|
const auto& [token, type] = tokens[1];
|
|
if (type == "int") {
|
|
exit(stoi(token));
|
|
} else {
|
|
error("exit argument must be integer");
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
else if (tokens[0].first == "type") {
|
|
if (tokens.size() < 2) {
|
|
error("type requires an argument");
|
|
return 0;
|
|
}
|
|
const auto& [token, type] = tokens[1];
|
|
cout << type << endl;
|
|
return 0;
|
|
}
|
|
|
|
else if (tokens[0].first == "run") {
|
|
if (tokens.size() < 2) {
|
|
error("run requires an argument");
|
|
return 0;
|
|
}
|
|
string inputStr = tokens[1].first;
|
|
char input[inputStr.length() + 1];
|
|
strcpy(input, inputStr.c_str());
|
|
int returnCode = system(input);
|
|
return 0;
|
|
}
|
|
else if (tokens[0].first == "str") {
|
|
verbose("String command run");
|
|
if (tokens.size() < 2) {
|
|
error("variable must have a name");
|
|
return 0;
|
|
}
|
|
if (tokens.size() < 4) {
|
|
error("when defining a variable, set what the variable means");
|
|
return 0;
|
|
}
|
|
|
|
const auto& varName = tokens[1].first;
|
|
|
|
if (tokens[2].first != "=") {
|
|
error("when defining a variable, use '='");
|
|
return 0;
|
|
}
|
|
if (variables.find(varName) != variables.end()) {
|
|
error("variable is already initialized");
|
|
return 0;
|
|
}
|
|
if (tokens[3].second != "str") {
|
|
error("you've initialized a string, but set it's value to a different type");
|
|
return 0;
|
|
}
|
|
|
|
string value = tokens[3].first;
|
|
value = value.substr(1, value.length() - 2);
|
|
variables[varName] = Variable(VarType::STRING, value);
|
|
|
|
verbose("String variable " + varName + "defined as " + value);
|
|
return 0;
|
|
}
|
|
|
|
else if (tokens[0].first == "int") {
|
|
verbose("Integer command run");
|
|
if (tokens.size() < 2) {
|
|
error("variable must have a name");
|
|
return 0;
|
|
}
|
|
if (tokens.size() < 4) {
|
|
error("when defining a variable, set what the variable means");
|
|
return 0;
|
|
}
|
|
|
|
const auto& varName = tokens[1].first;
|
|
|
|
if (tokens[2].first != "=") {
|
|
error("when defining a variable, use '='");
|
|
return 0;
|
|
}
|
|
if (variables.find(varName) != variables.end()) {
|
|
error("variable is already initialized");
|
|
return 0;
|
|
}
|
|
if (tokens[3].second != "int") {
|
|
error("you've initialized an integer, but set it's value to a different type");
|
|
return 0;
|
|
}
|
|
|
|
int value = stoi(tokens[3].first);
|
|
variables[varName] = Variable(VarType::INTEGER, value);
|
|
|
|
verbose("Integer variable " + varName + "defined as " + to_string(value));
|
|
return 0;
|
|
}
|
|
|
|
else if (tokens[0].first == "dec") {
|
|
verbose("Decimal command run");
|
|
if (tokens.size() < 2) {
|
|
error("variable must have a name");
|
|
return 0;
|
|
}
|
|
if (tokens.size() < 4) {
|
|
error("when defining a variable, set what the variable means");
|
|
return 0;
|
|
}
|
|
|
|
const auto& varName = tokens[1].first;
|
|
|
|
if (tokens[2].first != "=") {
|
|
error("when defining a variable, use '='");
|
|
return 0;
|
|
}
|
|
if (variables.find(varName) != variables.end()) {
|
|
error("variable is already initialized");
|
|
return 0;
|
|
}
|
|
if (tokens[3].second != "dec") {
|
|
error("you've initialized a decimal, but set it's value to a different type");
|
|
return 0;
|
|
}
|
|
|
|
double value = stod(tokens[3].first);
|
|
variables[varName] = Variable(VarType::DECIMAL, value);
|
|
|
|
verbose("Decimal variable " + varName + "defined as " + to_string(value));
|
|
return 0;
|
|
}
|
|
|
|
else if (tokens[0].first == "verbose") {
|
|
isVerbose = !isVerbose;
|
|
return 0;
|
|
}
|
|
|
|
else if (tokens[0].first == "help") {
|
|
cout << "Oktolang Help\nBuilt In Functions:\n help: This current help function\n log: Log something to the command line\n type: Find the type of the input\n str: Define a string\n int: Define a whole number\n dec: Define a number with a decimal place\n";
|
|
return 0;
|
|
}
|
|
|
|
else if (variables.find(tokens[0].first) != variables.end()) {
|
|
verbose("Doing stuff with variables...");
|
|
if (tokens.size() < 2) {
|
|
error("Expected an operator");
|
|
return 0;
|
|
}
|
|
auto& var = variables[tokens[0].first];
|
|
if (var.type == VarType::INTEGER) {
|
|
verbose("Mathing...");
|
|
var.value = doIntMath(tokens);
|
|
} else if (var.type == VarType::DECIMAL) {
|
|
if (tokens[1].second == "incrementor") {
|
|
if (tokens[1].first == "++") {
|
|
double currentValue = get<double>(var.value);
|
|
var.value = currentValue + 1;
|
|
} else if (tokens[1].second == "--") {
|
|
double currentValue = get<double>(var.value);
|
|
var.value = currentValue - 1;
|
|
}
|
|
} else if (tokens[3].second == "operator" && tokens[1].second == "equals") {
|
|
verbose("Detected an operator");
|
|
if (tokens[3].first == "+") {
|
|
verbose("Adding...");
|
|
if (tokens[2].second == "variable") {
|
|
verbose("Editing variable in 2nd token");
|
|
auto& varAdd = variables[tokens[2].first];
|
|
if (varAdd.type == VarType::INTEGER || varAdd.type == VarType::DECIMAL) {
|
|
tokens[2].first = to_string(get<double>(varAdd.value));
|
|
tokens[2].second = "int";
|
|
} else {
|
|
error("not all the variables you're adding are integers");
|
|
return 0;
|
|
}
|
|
}
|
|
if (tokens[4].second == "variable") {
|
|
verbose("Editing variable in 4th token");
|
|
auto& varAdd = variables[tokens[4].first];
|
|
if (varAdd.type == VarType::INTEGER || varAdd.type == VarType::DECIMAL) {
|
|
tokens[4].first = to_string(get<double>(varAdd.value));
|
|
tokens[4].second = "int";
|
|
} else {
|
|
error("not all the variables you're adding are integers");
|
|
return 0;
|
|
}
|
|
}
|
|
if (tokens[2].second != "int" || tokens[4].second != "int") {
|
|
verbose("Detected types are " + tokens[2].second + " and " + tokens[4].second);
|
|
error("make sure you're adding integers and integers when setting an integer");
|
|
return 0;
|
|
}
|
|
verbose("Trying to add variables");
|
|
var.value = stod(tokens[2].first) + stoi(tokens[4].first);
|
|
return 0;
|
|
}
|
|
if (tokens[3].first == "-") {
|
|
verbose("Subtracting...");
|
|
if (tokens[2].second == "variable") {
|
|
verbose("Editing variable in 2nd token");
|
|
auto& varAdd = variables[tokens[2].first];
|
|
if (varAdd.type == VarType::INTEGER || varAdd.type == VarType::DECIMAL) {
|
|
tokens[2].first = to_string(get<double>(varAdd.value));
|
|
tokens[2].second = "int";
|
|
} else {
|
|
error("not all the variables you're adding are integers");
|
|
return 0;
|
|
}
|
|
}
|
|
if (tokens[4].second == "variable") {
|
|
verbose("Editing variable in 4th token");
|
|
auto& varAdd = variables[tokens[4].first];
|
|
if (varAdd.type == VarType::INTEGER || varAdd.type == VarType::DECIMAL) {
|
|
tokens[4].first = to_string(get<double>(varAdd.value));
|
|
tokens[4].second = "int";
|
|
} else {
|
|
error("not all the variables you're adding are integers");
|
|
return 0;
|
|
}
|
|
}
|
|
if (tokens[2].second != "int" || tokens[4].second != "int") {
|
|
verbose("Detected types are " + tokens[2].second + " and " + tokens[4].second);
|
|
error("make sure you're adding integers and integers when setting an integer");
|
|
return 0;
|
|
}
|
|
verbose("Trying to subtract variables");
|
|
var.value = stod(tokens[2].first) - stoi(tokens[4].first);
|
|
return 0;
|
|
}
|
|
if (tokens[3].first == "*") {
|
|
verbose("Multiplying...");
|
|
if (tokens[2].second == "variable") {
|
|
verbose("Editing variable in 2nd token");
|
|
auto& varAdd = variables[tokens[2].first];
|
|
if (varAdd.type == VarType::INTEGER || varAdd.type == VarType::DECIMAL) {
|
|
tokens[2].first = to_string(get<double>(varAdd.value));
|
|
tokens[2].second = "int";
|
|
} else {
|
|
error("not all the variables you're adding are integers");
|
|
return 0;
|
|
}
|
|
}
|
|
if (tokens[4].second == "variable") {
|
|
verbose("Editing variable in 4th token");
|
|
auto& varAdd = variables[tokens[4].first];
|
|
if (varAdd.type == VarType::INTEGER || varAdd.type == VarType::DECIMAL) {
|
|
tokens[4].first = to_string(get<double>(varAdd.value));
|
|
tokens[4].second = "int";
|
|
} else {
|
|
error("not all the variables you're adding are integers");
|
|
return 0;
|
|
}
|
|
}
|
|
if (tokens[2].second != "int" || tokens[4].second != "int") {
|
|
verbose("Detected types are " + tokens[2].second + " and " + tokens[4].second);
|
|
error("make sure you're adding integers and integers when setting an integer");
|
|
return 0;
|
|
}
|
|
verbose("Trying to multiply variables");
|
|
var.value = stod(tokens[2].first) * stoi(tokens[4].first);
|
|
return 0;
|
|
}
|
|
if (tokens[3].first == "/") {
|
|
verbose("Dividing...");
|
|
if (tokens[2].second == "variable") {
|
|
verbose("Editing variable in 2nd token");
|
|
auto& varAdd = variables[tokens[2].first];
|
|
if (varAdd.type == VarType::INTEGER || varAdd.type == VarType::DECIMAL) {
|
|
tokens[2].first = to_string(get<double>(varAdd.value));
|
|
tokens[2].second = "int";
|
|
} else {
|
|
error("not all the variables you're adding are integers");
|
|
return 0;
|
|
}
|
|
}
|
|
if (tokens[4].second == "variable") {
|
|
verbose("Editing variable in 4th token");
|
|
auto& varAdd = variables[tokens[4].first];
|
|
if (varAdd.type == VarType::INTEGER || varAdd.type == VarType::DECIMAL) {
|
|
tokens[4].first = to_string(get<double>(varAdd.value));
|
|
tokens[4].second = "int";
|
|
} else {
|
|
error("not all the variables you're adding are integers");
|
|
return 0;
|
|
}
|
|
}
|
|
if (tokens[2].second != "int" || tokens[4].second != "int") {
|
|
verbose("Detected types are " + tokens[2].second + " and " + tokens[4].second);
|
|
error("make sure you're adding integers and integers when setting an integer");
|
|
return 0;
|
|
}
|
|
if (tokens[2].first == "0" || tokens[4].first == "0") {
|
|
error("Don't divide by zero or the end of the universe will be upon us you idiot");
|
|
verbose("(please don't try any funny business i'm begging you)");
|
|
return 0;
|
|
}
|
|
verbose("Trying to divide variables");
|
|
var.value = stod(tokens[2].first) / stoi(tokens[4].first);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (!tokens[0].first.empty()) {
|
|
error("I don't know how that works");
|
|
return 0;
|
|
}
|
|
return 0;
|
|
};
|
|
};
|
|
|
|
int main(int argc, char *argv[]) {
|
|
cout << "Oktolang interpreter\n";
|
|
Interpreter interpreter;
|
|
|
|
if (argc > 1) {
|
|
ifstream inFile(argv[1]);
|
|
string line;
|
|
while (getline(inFile, line)) {
|
|
if (line[0] == '#') continue;
|
|
if (interpreter.executeCommand(line) != 0) return 1;
|
|
}
|
|
} else {
|
|
string input;
|
|
while (true) {
|
|
cout << "> ";
|
|
getline(cin, input);
|
|
if (interpreter.executeCommand(input) != 0) break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|