Initial commit

This commit is contained in:
2025-05-10 21:28:56 +10:00
commit a782c09f91
24 changed files with 2129 additions and 0 deletions

56
src/ArgParser.cpp Normal file
View File

@@ -0,0 +1,56 @@
/*
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 "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 << "Iodine programming language" << endl;
cout << "Usage: io [file]" << endl;
cout << "Options:" << endl;
cout << " --debug Enable debug mode" << endl;
cout << " --help Show this help message" << endl;
cout << " --version Get current installed version of Iodine" << endl;
cout << "Issues? Send an email to max@maxwellj.xyz" << endl;
cout << "Report bugs at https://git.maxwellj.xyz/iodine/iodine" << endl;
exit(0);
} else if (args[i] == "--version") {
cout << "Iodine, version 0.0.3" << endl;
exit(0);
}
}
}
string ArgParser::getArg(int index) {
if (index >= 0 && index < args.size()) {
return args[index];
}
return "";
}

29
src/ArgParser.h Normal file
View File

@@ -0,0 +1,29 @@
/*
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/>.
*/
#pragma once
#include "common.h"
class ArgParser {
private:
vector<string> args;
public:
ArgParser(int argc, char* argv[]);
string getArg(int index);
};

478
src/Interpreter.cpp Normal file
View File

@@ -0,0 +1,478 @@
/*
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 {};
}
void Interpreter::convertToTokens(vector<Token> tokenList) {
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()) {
// This is pretty crappy code but it exists for now. Lists are gonna break this so much it's not even gonna be funny
if (varIt->second.type == valtype::LIST) {
if (peek()->keyword == keywords::OSQUA) {
if (peek(2)->keyword == keywords::VALUE && peek(2)->type == valtype::INT && peek(2)->type == valtype::INT && peek(3)->keyword == keywords::CSQUA) {
Token newToken;
Value tokenVal = get<List>(varIt->second.value).value[get<int>(peek(2)->value.value)];
newToken.keyword = keywords::VALUE;
newToken.type = tokenVal.type;
newToken.value.value = tokenVal.value;
// Do this later
}
}
} else {
// 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;
}
}
} 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;
}
}
// 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;
}
}
// 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()) {
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) 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) << 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 {
syntaxError.fnNotSufficientArgs("println", 1, 1, 0);
}
} 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);
}
} 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;
// Check the type declaration matches the value
if (typeToken.keyword == keywords::INT && valueToken.type == valtype::INT) {
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));
} else {
log.debug("Condition is false, skipping scope");
skipScope ++;
return;
}
} 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;
}
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();
}
}
}
}

43
src/Interpreter.h Normal file
View File

@@ -0,0 +1,43 @@
/*
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/>.
*/
#pragma once
#include "common.h"
#include "SyntaxError.h"
#include "Logger.h"
class Interpreter {
private:
int skipScope = 0;
int repeatScope = 0;
vector<int> loops;
SyntaxError syntaxError;
vector<Token> tokens;
map<string, Value> variables;
Logger log;
int loop;
int lengthOfLine;
int tokenIndex = -1;
optional<Token> consume();
optional<Token> peek(int offset = 1);
public:
void convertToTokens(vector<Token> tokenList);
void executeCode(vector<Token> tokens);
};

54
src/Logger.cpp Normal file
View File

@@ -0,0 +1,54 @@
/*
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 "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);
}

35
src/Logger.h Normal file
View File

@@ -0,0 +1,35 @@
/*
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/>.
*/
#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);
};

192
src/Parser.cpp Normal file
View File

@@ -0,0 +1,192 @@
/*
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 "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<vector<string>> 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 == "println") token.keyword = keywords::PRINTLN;
else if (ct == "input") token.keyword = keywords::INPUT;
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 == "if") token.keyword = keywords::IF;
else if (ct == "while") token.keyword = keywords::WHILE;
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::SET;
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 if (ct == "==") token.keyword = keywords::EQUAL;
else if (ct == "!=") token.keyword = keywords::INEQUAL;
else if (ct == "<") token.keyword = keywords::LESS;
else if (ct == ">") token.keyword = keywords::GREATER;
else if (ct == "//") token.keyword = keywords::COMMENT;
else if (ct == "[") token.keyword = keywords::OSQUA;
else if (ct == "]") token.keyword = keywords::CSQUA;
else {
token.keyword = keywords::VALUE;
// Convert the value based on its type
if (canInt(ct)) {
token.type = valtype::INT;
token.value.type = valtype::INT;
token.value.value = stoi(ct);
}else if (canDec(ct)) {
token.type = valtype::DEC;
token.value.type = valtype::DEC;
token.value.value = stod(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<Token> Parser::getTokens() {
return tokens;
}

39
src/Parser.h Normal file
View File

@@ -0,0 +1,39 @@
/*
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/>.
*/
#pragma once
#include "common.h"
#include "Logger.h"
class Parser {
private:
string buffer;
vector<string> lines;
vector<string> termBuffer;
vector<Token> tokens;
vector<vector<string>> terms;
vector<vector<keywords>> things;
vector<vector<variant<var, keywords>>> stuff;
bool canInt(string in);
bool canDec(string in);
public:
void parseLines(string in, Logger log);
void processLines();
vector<Token> getTokens();
};

81
src/SyntaxError.cpp Normal file
View File

@@ -0,0 +1,81 @@
/*
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 "SyntaxError.h"
void SyntaxError::fnTypeMismatch(string function, vector<string> 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 use 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);
}
void SyntaxError::cannotCompareDifferentTypes(string notes) {
cerr << "ComparisonError: cannot compare two different types" << endl;
if (!notes.empty()) cerr << "Notes: " << notes << endl;
exit(1);
}
void SyntaxError::comparisonTypeError(string notes) {
cerr << "ComparisonError: cannot use a non-bool type in an if or while statement" << endl;
if (!notes.empty()) cerr << "Notes: " << notes << endl;
}

34
src/SyntaxError.h Normal file
View File

@@ -0,0 +1,34 @@
/*
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/>.
*/
#pragma once
#include "common.h"
class SyntaxError {
public:
void fnTypeMismatch(string function, vector<string> 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 cannotCompareDifferentTypes(string notes = "");
void comparisonTypeError(string notes = "");
void generalError(string notes = "");
};

88
src/common.h Normal file
View File

@@ -0,0 +1,88 @@
/*
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/>.
*/
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <variant>
#include <map>
#include <optional>
using namespace std;
enum class valtype {
INT, DEC, STR, BOOL, LIST, KEYWORD, UNKNOWN
};
enum class keywords {
IF, ELSE, WHILE, INT, DEC, STR, BOOL, FUN, RETURN,
OPARE, CPARE, OBRAC, CBRAC, OSQUA, CSQUA, COMMA,
SET, ADDTO, SUBTRACTFROM, MULTIPLYTO, DIVIDEFROM,
ADD, SUBTRACT, MULTIPLY, DIVIDE,
EQUAL, INEQUAL, LESS, GREATER, EQLESS, EQGREATER,
INCREMENT, DECREMENT,
PRINT, PRINTLN, LET, INPUT, EXIT,
VALUE, SEMICOLON, VARIABLE,
COMMENT
};
struct Value;
struct List {
valtype type;
vector<Value> value;
};
struct Value {
valtype type;
variant<int, double, string, bool, List> value;
};
struct Token {
keywords keyword;
Value value;
valtype type = valtype::KEYWORD;
};
struct var {
valtype type;
variant<int, double, string, bool> value;
string toString() const {
if (type == valtype::INT) {
return to_string(get<int>(value));
}
else if (type == valtype::DEC) {
return to_string(get<double>(value));
}
else if (type == valtype::STR) {
return get<string>(value);
}
else if (type == valtype::BOOL) {
return to_string(get<bool>(value));
} else {
return "unknown";
}
}
};
// Global variable
extern bool debugMode;
extern string logList;

55
src/main.cpp Normal file
View File

@@ -0,0 +1,55 @@
/*
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 <iostream>
#include <fstream>
#include <csignal>
#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
ArgParser args(argc, argv);
// Initialise the logger
Logger log;
if (debugMode) log.toggleDebugPrint();
log.debug("Logger initialised!");
// Initialise the parser
Parser parser;
// Exit if file doesn't exist
if (argc < 2) log.fatalError("Please provide a file", 1);
// Read the file
ifstream inputFile(args.getArg(1));
string input;
string file;
while(getline(inputFile, input)) {
file += input;
}
inputFile.close();
// Parse the file
parser.parseLines(file, log);
parser.processLines();
// Initialise the interpreter
Interpreter interpreter;
// Convert to tokens and run the code
interpreter.convertToTokens(parser.getTokens());
return 0;
}