Files
ground_fork/src/main.cpp

2221 lines
90 KiB
C++
Raw Normal View History

2025-08-09 20:33:08 +10:00
/*
Ground
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/>.
*/
/*
With the licence out of the way, let's begin!
Ground is a programming language which takes some inspiration from
2025-08-11 08:57:45 +10:00
Assembly in its design, but has higher level features (more types,
simpler IO, easier variables, etc) which makes it easy to use.
2025-08-09 20:33:08 +10:00
Ground works even better if you write a programming language that
compiles to Ground code. Ground is designed to have a similar
purpose to the Java Virtual Machine, except easier for anyone to
write their own highly opinionated language on top of.
See documentation for building, maintaining and writing Ground code
in the docs folder in the repo.
Happy coding!
*/
#include <iostream>
#include <string>
#include <variant>
#include <vector>
#include <map>
#include <fstream>
2025-08-25 13:22:15 +10:00
#include <cstdlib>
#include <filesystem>
2025-08-09 20:33:08 +10:00
2025-08-25 17:35:16 +10:00
// Headers for external libraries
#ifdef _WIN32
// Note: Windows support is experiemental. Maybe try using a superior
// operating system? (cough cough, Linux?)
#include <windows.h>
#define DLOPEN(path) LoadLibrary(path)
#define DLSYM(handle, name) GetProcAddress(handle, name)
#define DLCLOSE(handle) FreeLibrary(handle)
#define DLERROR() "Windows DLL Error"
#else
#include <dlfcn.h>
#define DLOPEN(path) dlopen(path, RTLD_LAZY)
#define DLSYM(handle, name) dlsym(handle, name)
#define DLCLOSE(handle) dlclose(handle)
#define DLERROR() dlerror()
#endif
2025-08-09 20:33:08 +10:00
using namespace std;
/*
Instructions enum class
For each function keyword, an instruction is assigned. See also parser
function, interpreter function, Instruction struct
*/
enum class Instructions {
2025-08-10 13:31:28 +10:00
Jump, If,
Stdout, Stdin, Stdlnout,
Add, Subtract, Multiply, Divide,
2025-08-21 11:05:32 +10:00
Equal, Inequal, Greater, Lesser, Not,
2025-08-10 13:31:28 +10:00
End, Set, Empty,
2025-08-11 10:07:05 +10:00
Setlist, Getlistat, Setlistat, Getlistsize, Listappend, Listprepend,
Getstrcharat, Getstrsize,
2025-08-11 14:12:25 +10:00
Stoi, Stod, Tostring,
2025-08-12 09:45:00 +10:00
Fun, Return, Endfun, Pusharg, Call, Local,
2025-08-11 14:57:45 +10:00
Use, Extern
2025-08-09 20:33:08 +10:00
};
/*
Types enum class
Assists in type checking in the parser function. For example, the
following values correspond to the following types:
1 Int
3.14 Double
"Hello!" String
'e' Char
true Bool
$value Value
&var Direct
%10 Line
See also parser function
*/
enum class Types {
2025-08-12 09:45:00 +10:00
Int, Double, String, Char, Bool, Value, Direct, Line, ListRef, Label, Type, Function
2025-08-09 20:33:08 +10:00
};
/*
Literal struct
Contains literal values. For example, if the following line was written:
stdout "Hello world!"
The Literal struct in the instruction should look like this:
{
val = "Hello world!"; // I am ignoring the variant for simplicity
// of documenting the code
}
All value references are swapped out for their respective Literal they
point to. See also variables map, parser function, interpreter function
*/
struct Literal {
variant<int, double, bool, string, char> val;
};
2025-08-10 13:31:28 +10:00
/*
List struct
Contains literal values inside a vector. For example, if the following
program was written:
setlist #myNums 3 5 9 13
The List struct which would be created and stored should look like this:
{
val = {
Literal {
val = 3
},
Literal {
val = 5
},
Literal {
val = 9
},
Literal {
val = 13
},
}
}
All elements in the list must be of the same type. See also Literal struct.
*/
struct List {
vector<Literal> val;
};
2025-08-11 14:57:45 +10:00
/*
Direct struct
If the program being executed makes a direct reference, it is stored in a Direct
struct. For example, if the following line was written:
stdin &myVar
The Direct struct in the instruction should look like this:
{
varName = "myVar";
}
*/
struct Direct {
string varName;
};
2025-08-12 09:45:00 +10:00
struct TypeRef {
Types type;
};
struct FunctionRef {
string fnName;
};
2025-08-11 14:57:45 +10:00
2025-08-10 13:31:28 +10:00
/*
ListRef struct
Contains the name of a list referenced by the program. For example, if the
following program was written:
setlist #myNums 3 5 9 13
The ListRef struct should look like this:
{
listName = "myNums";
}
*/
struct ListRef {
string listName;
};
2025-08-09 20:33:08 +10:00
/*
variables map
Contains all variables made while running the program. See also Literal struct.
*/
map<string, Literal> variables;
2025-08-10 13:31:28 +10:00
/*
lists map
Contains all lists made while running the program. See also List struct.
*/
map<string, List> lists;
2025-08-10 15:42:52 +10:00
/*
labels map
Contains all labels made in the program, for ease of jumping around the code.
*/
map<string, int> labels;
2025-08-09 20:33:08 +10:00
/*
ValueRef struct
If the program being executed makes a value reference, it is stored in a ValueRef
struct. For example, if the following line was written:
stdin &myVar
The ValueRef struct in the instruction should look like this:
{
varName = "myVar";
}
*/
struct ValueRef {
string varName;
};
/*
Line struct
If the program being executed makes a line reference, it is stored in a Line
struct. For example, if the following line was written:
jump %10
The Line struct in the instruction should look like this:
{
lineNum = 10;
}
*/
struct Line {
int lineNum;
2025-08-10 15:42:52 +10:00
bool isLabel = false;
string label;
2025-08-09 20:33:08 +10:00
};
/*
Instruction struct
An instruction usually corresponds to a line in the program being interpreted.
For example, if the following line was written:
add 5 $myVar &outVar
The instruction should look like this:
{
inst = Instructions::Add;
args = {
Literal {
val = 5
},
ValueRef {
varName = "myVar"
},
Direct {
varName = "outVar"
}
};
}
inst starts as empty, so empty lines and commented out lines do not get in the
way of jump and if.
See also: Instructions enum class, Literal struct, ValueRef struct, Direct struct,
Line struct, exec function, parser function
*/
struct Instruction {
Instructions inst = Instructions::Empty;
2025-08-12 09:45:00 +10:00
vector<variant<Literal, ValueRef, ListRef, FunctionRef, TypeRef, Direct, Line>> args;
2025-08-09 20:33:08 +10:00
};
2025-08-22 13:52:26 +10:00
struct FnArg {
Direct ref;
Types type;
};
2025-08-13 09:40:03 +10:00
/*
Function struct
Contains information needed to run a Ground function.
*/
struct Function {
Types returnType;
2025-08-22 13:52:26 +10:00
vector<FnArg> args;
2025-08-13 09:40:03 +10:00
vector<Instruction> instructions;
2025-08-24 16:30:42 +10:00
map<string, int> localLabels;
2025-08-13 09:40:03 +10:00
};
2025-08-25 17:35:16 +10:00
// C-compatible enum and types for developing libraries for Ground in C
typedef enum {
GROUND_INT,
GROUND_DOUBLE,
GROUND_BOOL,
GROUND_STRING,
GROUND_CHAR
} GroundType;
typedef struct {
GroundType type;
union {
int int_val;
double double_val;
int bool_val;
char* string_val;
char char_val;
} data;
} GroundValue;
2025-08-13 09:40:03 +10:00
/*
functions map
Contains the code of functions and types of their values
*/
map<string, Function> functions;
2025-08-15 11:35:58 +10:00
/*
fnArgs vector
Containst the arguments to be passed to a function
*/
vector<Literal> fnArgs;
2025-08-13 18:31:54 +10:00
2025-08-25 17:35:16 +10:00
// External library functions and other things
// Handle to loaded libraries
map<string, void*> loadedLibraries;
// Map of function name to function pointer
map<string, void*> externalFunctions;
// Conversion functions
GroundValue literalToGroundValue(const Literal& lit) {
GroundValue gv;
if (holds_alternative<int>(lit.val)) {
gv.type = GROUND_INT;
gv.data.int_val = get<int>(lit.val);
} else if (holds_alternative<double>(lit.val)) {
gv.type = GROUND_DOUBLE;
gv.data.double_val = get<double>(lit.val);
} else if (holds_alternative<bool>(lit.val)) {
gv.type = GROUND_BOOL;
gv.data.bool_val = get<bool>(lit.val) ? 1 : 0;
} else if (holds_alternative<string>(lit.val)) {
gv.type = GROUND_STRING;
gv.data.string_val = const_cast<char*>(get<string>(lit.val).c_str());
} else if (holds_alternative<char>(lit.val)) {
gv.type = GROUND_CHAR;
gv.data.char_val = get<char>(lit.val);
}
return gv;
}
Literal groundValueToLiteral(const GroundValue& gv) {
Literal lit;
switch (gv.type) {
case GROUND_INT:
lit.val = gv.data.int_val;
break;
case GROUND_DOUBLE:
lit.val = gv.data.double_val;
break;
case GROUND_BOOL:
lit.val = (gv.data.bool_val != 0);
break;
case GROUND_STRING:
lit.val = string(gv.data.string_val);
break;
case GROUND_CHAR:
lit.val = gv.data.char_val;
break;
}
return lit;
}
2025-08-09 20:33:08 +10:00
/*
error function
Takes a string (which is a debug message) and prints it to the console, letting the
user know what went wrong with the program.
*/
void error(string in, int exitc = 1) {
cout << "\033[31mError: \033[39m" << in << endl;
exit(exitc);
}
/*
is* functions
These functions determine if a string value can be converted into a different type.
*/
bool isInt(string in) {
try {
stoi(in);
if (stod(in) != stoi(in)) return false;
2025-08-09 20:33:08 +10:00
return true;
} catch (...) {
return false;
}
}
bool isDouble(string in) {
try {
stod(in);
return true;
} catch (...) {
return false;
}
}
bool isBool(string in) {
if (in == "true" || in == "false") return true;
else return false;
}
bool isString(string in) {
if (in.size() >= 2 && in[0] == '"' && in[in.size() - 1] == '"') return true;
else return false;
}
bool isChar(string in) {
if (in.size() == 3 && in[0] == '\'' && in[in.size() - 1] == '\'') return true;
else return false;
}
bool isValue(string in) {
if (in.size() >= 1 && in[0] == '$') return true;
else return false;
}
bool isDirect(string in) {
if (in.size() >= 1 && in[0] == '&') return true;
else return false;
}
bool isLine(string in) {
2025-08-10 13:31:28 +10:00
if (in.size() >= 1 && in[0] == '%') return true;
else return false;
}
2025-08-10 15:42:52 +10:00
bool isLabel(string in) {
if (in.size() >= 1 && in[0] == '@') return true;
else return false;
}
2025-08-10 13:31:28 +10:00
bool isListRef(string in) {
if (in.size() >= 1 && in[0] == '*') return true;
2025-08-09 20:33:08 +10:00
else return false;
}
2025-08-12 09:45:00 +10:00
bool isType(string in) {
if (in.size() >= 1 && in[0] == '-') {
string type = in.substr(1);
if (type == "string" || type == "char" || type == "bool" || type == "double" || type == "int") return true;
else return false;
} else return false;
}
bool isFunction(string in) {
if (in.size() >= 1 && in[0] == '!') return true;
else return false;
}
2025-08-09 20:33:08 +10:00
/*
getType function
This function determines the type of a value inside a string based on the is*
functions above. Returns a type from the Types enum class.
*/
Types getType(string in) {
if (isInt(in)) return Types::Int;
if (isDouble(in)) return Types::Double;
if (isBool(in)) return Types::Bool;
if (isString(in)) return Types::String;
if (isChar(in)) return Types::Char;
if (isValue(in)) return Types::Value;
if (isDirect(in)) return Types::Direct;
if (isLine(in)) return Types::Line;
2025-08-10 15:42:52 +10:00
if (isLabel(in)) return Types::Label;
2025-08-10 13:31:28 +10:00
if (isListRef(in)) return Types::ListRef;
2025-08-12 09:45:00 +10:00
if (isType(in)) return Types::Type;
if (isFunction(in)) return Types::Function;
2025-08-09 20:33:08 +10:00
error("Could not determine type of \"" + in + "\"");
return Types::Int;
}
2025-08-22 13:52:26 +10:00
/*
getLitType function
This function determines the type of a value inside a Literal based on the
holds_alternative() function. Returns a type from the Types enum class.
*/
Types getLitType(Literal in) {
if (holds_alternative<int>(in.val)) return Types::Int;
if (holds_alternative<double>(in.val)) return Types::Double;
if (holds_alternative<bool>(in.val)) return Types::Bool;
if (holds_alternative<string>(in.val)) return Types::String;
if (holds_alternative<char>(in.val)) return Types::Char;
error("Literal for some reason has a weird type. This is not an issue with your program, but an issue with the Ground interpreter.");
return Types::Int;
}
2025-08-18 09:36:35 +10:00
bool processingFunction = false;
string procFnName = "";
bool inFunction = false;
2025-08-25 13:22:15 +10:00
// Forward declaration for the call instruction and use instruction
2025-08-18 09:36:35 +10:00
Literal exec(vector<Instruction> in);
2025-08-25 13:22:15 +10:00
// Forward declaration for the use instruction
vector<Instruction> parser(vector<vector<string>> in);
// Forward declaration for the use instruction
vector<vector<string>> lexer(string in);
2025-08-09 20:33:08 +10:00
/*
exec function
This function takes a list of instructions (see Instruction struct above and parser
function below) and acts upon their properties. This is the main interpreter
function for the program.
*/
2025-08-24 16:30:42 +10:00
Literal exec(vector<Instruction> in, bool executingFunction) {
map<string, int>* currentLabels = &labels;
for (auto& [fnName, fn] : functions) {
if (&fn.instructions == &in) {
currentLabels = &fn.localLabels;
break;
}
}
2025-08-09 20:33:08 +10:00
for (int i = 0; i < in.size(); i++) {
Instruction l = in[i];
2025-08-13 09:40:03 +10:00
if (processingFunction) {
if (l.inst == Instructions::Endfun) {
processingFunction = false;
procFnName = "";
continue;
} else {
functions[procFnName].instructions.push_back(l);
continue;
}
}
2025-08-10 15:42:52 +10:00
// Pre process value references and labels
2025-08-09 20:33:08 +10:00
for (int j = 0; j < l.args.size(); j++) {
if (holds_alternative<ValueRef>(l.args[j])) {
if (variables.find(get<ValueRef>(l.args[j]).varName) != variables.end()) {
l.args[j] = variables[get<ValueRef>(l.args[j]).varName];
} else {
error("Could not find variable " + get<ValueRef>(l.args[j]).varName);
}
2025-08-10 15:42:52 +10:00
} else if (holds_alternative<Line>(l.args[j])) {
Line ln = get<Line>(l.args[j]);
if (ln.isLabel) {
2025-08-24 16:30:42 +10:00
if (currentLabels->find(ln.label) != currentLabels->end()) {
2025-08-10 15:42:52 +10:00
Line newLine;
2025-08-24 16:30:42 +10:00
newLine.lineNum = (*currentLabels)[ln.label];
2025-08-10 15:42:52 +10:00
l.args[j] = newLine;
} else {
error("Could not find label " + ln.label);
}
}
2025-08-09 20:33:08 +10:00
}
}
switch (l.inst) {
// Ignore empty lines
case Instructions::Empty:
break;
/*
stdout instruction
This instruction prints characters to the console. It obtains the value
from the variant and prints it.
*/
case Instructions::Stdout:
if (l.args.size() < 1) {
error("Could not find argument for Stdout inbuilt");
}
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
cout << get<string>(get<Literal>(l.args[0]).val);
}
else if (holds_alternative<int>(get<Literal>(l.args[0]).val)) {
cout << get<int>(get<Literal>(l.args[0]).val);
}
else if (holds_alternative<double>(get<Literal>(l.args[0]).val)) {
cout << get<double>(get<Literal>(l.args[0]).val);
}
else if (holds_alternative<bool>(get<Literal>(l.args[0]).val)) {
if (get<bool>(get<Literal>(l.args[0]).val) == true) {
cout << "true";
} else {
cout << "false";
}
}
else if (holds_alternative<char>(get<Literal>(l.args[0]).val)) {
cout << get<char>(get<Literal>(l.args[0]).val);
}
else {
error("Couldn't print that");
}
} else {
error("Argument of stdlnout must be a value (literal or a value reference)");
}
break;
/*
stdlnout instruction
This instruction prints characters to the console. It obtains the value
from the variant and prints it, with a new line at the end.
*/
case Instructions::Stdlnout:
if (l.args.size() < 1) {
error("Could not find argument for Stdout inbuilt");
}
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
cout << get<string>(get<Literal>(l.args[0]).val) << endl;
}
else if (holds_alternative<int>(get<Literal>(l.args[0]).val)) {
cout << get<int>(get<Literal>(l.args[0]).val) << endl;
}
else if (holds_alternative<double>(get<Literal>(l.args[0]).val)) {
cout << get<double>(get<Literal>(l.args[0]).val) << endl;
}
else if (holds_alternative<bool>(get<Literal>(l.args[0]).val)) {
if (get<bool>(get<Literal>(l.args[0]).val) == true) {
cout << "true" << endl;
} else {
cout << "false" << endl;
}
}
else if (holds_alternative<char>(get<Literal>(l.args[0]).val)) {
cout << get<char>(get<Literal>(l.args[0]).val) << endl;
}
else {
error("Couldn't print that");
}
} else {
error("Argument of stdlnout must be a value (literal or a value reference)");
}
break;
/*
set instruction
This instruction sets a variable to a provided value.
*/
case Instructions::Set:
if (l.args.size() < 2) {
error("Could not find all arguments required for Set inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal varContents;
if (holds_alternative<Direct>(l.args[0])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[0]);
2025-08-09 20:33:08 +10:00
} else {
error("First argument of set must be a direct reference");
}
if (holds_alternative<Literal>(l.args[1])) {
varContents = get<Literal>(l.args[1]);
} else {
error("Second argument of set must be a value (literal or value reference)");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = varContents;
2025-08-09 20:33:08 +10:00
}
break;
2025-08-10 13:31:28 +10:00
/*
setlist instruction
This instruction takes a potentially infinite amount of arguments and
saves them to a list (vector) with name listName.
*/
case Instructions::Setlist:
if (l.args.size() < 2) {
error("Could not find all arguments required for Setlist inbuilt");
}
{
string listName;
List listContents;
if (holds_alternative<ListRef>(l.args[0])) {
listName = get<ListRef>(l.args[0]).listName;
} else {
error("First argument of setlist must be a list reference");
}
bool first = true;
2025-08-12 09:45:00 +10:00
for (variant<Literal, ValueRef, ListRef, FunctionRef, TypeRef, Direct, Line> k : l.args) {
2025-08-10 13:31:28 +10:00
if (holds_alternative<Literal>(k)) {
listContents.val.push_back(get<Literal>(k));
} else {
if (!first) error("All arguments after first in setlist must be values");
first = false;
}
}
lists[listName] = listContents;
}
break;
/*
getlistat instruction
This instruction gets a list item from a list and an index and saves the
value to a variable.
*/
case Instructions::Getlistat:
if (l.args.size() < 3) {
error("Could not find all arguments required for Getlistat inbuilt");
}
{
ListRef listref;
int ref;
Direct var;
if (holds_alternative<ListRef>(l.args[0])) {
listref = get<ListRef>(l.args[0]);
} else {
error("First argument of getlistat must be a list reference");
}
if (holds_alternative<Literal>(l.args[1])) {
if (holds_alternative<int>(get<Literal>(l.args[1]).val)) {
ref = get<int>(get<Literal>(l.args[1]).val);
} else {
error("Second argument of getlistat must be an integer literal");
}
} else {
error("Second argument of getlistat must be an integer literal");
}
if (holds_alternative<Direct>(l.args[2])) {
var = get<Direct>(l.args[2]);
} else {
error("Third argument of getlistat must be a direct reference");
}
if (lists.find(listref.listName) != lists.end()) {
if (lists[listref.listName].val.size() > ref) {
2025-08-24 15:08:07 +10:00
bool existed = variables.count(var.varName) > 0;
2025-08-10 13:31:28 +10:00
variables[var.varName] = lists[listref.listName].val[ref];
} else {
error("Index " + to_string(ref) + " out of range of list " + listref.listName);
}
} else {
error("Unknown list: " + listref.listName);
}
}
break;
2025-08-11 10:07:05 +10:00
/*
getstrcharat instruction
This instruction gets a character from a string at an index and saves it
to a variable.
*/
case Instructions::Getstrcharat:
if (l.args.size() < 3) {
error("Could not find all arguments required for Getstrcharat inbuilt");
}
{
string instr;
int ref;
Direct var;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
instr = get<string>(get<Literal>(l.args[0]).val);
} else {
2025-08-25 11:22:02 +10:00
error("First argument of getstrcharat must be a string literal");
2025-08-11 10:07:05 +10:00
}
} else {
2025-08-25 11:22:02 +10:00
error("First argument of getstrcharat must be a string literal");
2025-08-11 10:07:05 +10:00
}
if (holds_alternative<Literal>(l.args[1])) {
if (holds_alternative<int>(get<Literal>(l.args[1]).val)) {
ref = get<int>(get<Literal>(l.args[1]).val);
} else {
2025-08-25 11:22:02 +10:00
error("Second argument of getstrcharat must be an integer literal");
2025-08-11 10:07:05 +10:00
}
} else {
2025-08-25 11:22:02 +10:00
error("Second argument of getstrcharat must be an integer literal");
2025-08-11 10:07:05 +10:00
}
if (holds_alternative<Direct>(l.args[2])) {
var = get<Direct>(l.args[2]);
} else {
2025-08-25 11:22:02 +10:00
error("Third argument of getstrcharat must be a direct reference");
2025-08-11 10:07:05 +10:00
}
if (instr.size() > ref) {
Literal newLit;
newLit.val = instr[ref];
2025-08-24 15:08:07 +10:00
bool existed = variables.count(var.varName) > 0;
2025-08-11 10:07:05 +10:00
variables[var.varName] = newLit;
} else {
error("Index " + to_string(ref) + " out of range of string " + instr);
}
}
break;
2025-08-11 08:57:45 +10:00
/*
setlistat instruction
This instruction sets an item in a list to be a certain value.
*/
2025-08-10 16:08:56 +10:00
case Instructions::Setlistat:
if (l.args.size() < 3) {
error("Could not find all arguments required for Setlistat inbuilt");
}
{
ListRef listref;
int ref;
Literal value;
if (holds_alternative<ListRef>(l.args[0])) {
listref = get<ListRef>(l.args[0]);
} else {
error("First argument of setlistat must be a list reference");
}
if (holds_alternative<Literal>(l.args[1])) {
if (holds_alternative<int>(get<Literal>(l.args[1]).val)) {
ref = get<int>(get<Literal>(l.args[1]).val);
} else {
error("Second argument of setlistat must be an integer literal");
}
} else {
error("Second argument of setlistat must be an integer literal");
}
if (holds_alternative<Literal>(l.args[2])) {
value = get<Literal>(l.args[2]);
} else {
error("Third argument of setlistat must be a direct reference");
}
if (lists.find(listref.listName) != lists.end()) {
if (lists[listref.listName].val.size() > ref) {
lists[listref.listName].val[ref] = value;
} else {
error("Index " + to_string(ref) + " out of range of list " + listref.listName);
}
} else {
error("Unknown list: " + listref.listName);
}
}
break;
2025-08-11 08:57:45 +10:00
/*
listappend instruction
This instruction appends an item to a list.
*/
case Instructions::Listappend:
if (l.args.size() < 2) {
error("Could not find all arguments required for Listappend inbuilt");
}
{
ListRef listref;
Literal value;
if (holds_alternative<ListRef>(l.args[0])) {
listref = get<ListRef>(l.args[0]);
} else {
error("Second argument of listappend must be a list reference");
}
if (holds_alternative<Literal>(l.args[1])) {
value = get<Literal>(l.args[1]);
} else {
error("Second argument of listappend must be a direct reference");
}
if (lists.find(listref.listName) != lists.end()) {
lists[listref.listName].val.push_back(value);
} else {
error("Unknown list: " + listref.listName);
}
}
break;
2025-08-10 16:08:56 +10:00
/*
getlistsize instruction
This instruction saves the size of a list in a variable.
*/
2025-08-10 13:31:28 +10:00
case Instructions::Getlistsize:
if (l.args.size() < 2) {
error("Could not find all arguments required for Getlistsize inbuilt");
}
{
ListRef ref;
Direct var;
if (holds_alternative<ListRef>(l.args[0])) {
ref = get<ListRef>(l.args[0]);
} else {
error("First argument of getlistsize must be a list reference");
}
if (holds_alternative<Direct>(l.args[1])) {
var = get<Direct>(l.args[1]);
} else {
error("Second argument of getlistsize must be a direct reference");
}
Literal newLit;
if (lists.find(ref.listName) != lists.end()) {
newLit.val = int(lists[ref.listName].val.size());
2025-08-24 15:08:07 +10:00
bool existed = variables.count(var.varName) > 0;
2025-08-10 13:31:28 +10:00
variables[var.varName] = newLit;
} else {
error("Couldn't find the list " + ref.listName);
}
break;
}
2025-08-09 20:33:08 +10:00
/*
2025-08-11 10:07:05 +10:00
getstrsize instruction
This instruction saves the size of a string in a variable.
*/
case Instructions::Getstrsize:
if (l.args.size() < 2) {
error("Could not find all arguments required for Getstrsize inbuilt");
}
{
string ref;
Direct var;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
ref = get<string>(get<Literal>(l.args[0]).val);
} else {
error("First argument of getlistsize must be a string value");
}
} else {
error("First argument of getlistsize must be a string value");
}
if (holds_alternative<Direct>(l.args[1])) {
var = get<Direct>(l.args[1]);
} else {
error("Second argument of getlistsize must be a direct reference");
}
Literal newLit;
newLit.val = int(ref.size());
2025-08-24 15:08:07 +10:00
bool existed = variables.count(var.varName) > 0;
2025-08-11 10:07:05 +10:00
variables[var.varName] = newLit;
break;
}
2025-08-11 14:12:25 +10:00
/*
stoi instruction
This function converts a string to an int, and saves it in a variable.
If the string cannot be turned into an integer, there is an error.
*/
case Instructions::Stoi:
if (l.args.size() < 2) {
error("Could not find all arguments required for Stoi inbuilt");
}
{
string toConv;
Direct ref;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
toConv = get<string>(get<Literal>(l.args[0]).val);
} else {
error("First argument of stoi must be a string literal");
}
} else {
error("First argument of stoi must be a string literal");
}
if (holds_alternative<Direct>(l.args[1])) {
ref = get<Direct>(l.args[1]);
} else {
error("Second argument of stoi must be a direct reference");
}
if (isInt(toConv)) {
Literal newLit;
newLit.val = stoi(toConv);
2025-08-24 15:08:07 +10:00
bool existed = variables.count(ref.varName) > 0;
2025-08-11 14:12:25 +10:00
variables[ref.varName] = newLit;
} else {
error("Cannot convert the value " + toConv + " to an int");
}
}
break;
/*
stod instruction
This function converts a string to a decimal, and saves it in a variable.
If the string cannot be turned into a decimal, there is an error.
*/
case Instructions::Stod:
if (l.args.size() < 2) {
error("Could not find all arguments required for Stod inbuilt");
}
{
string toConv;
Direct ref;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
toConv = get<string>(get<Literal>(l.args[0]).val);
} else {
error("First argument of stod must be a string literal");
}
} else {
error("First argument of stod must be a string literal");
}
if (holds_alternative<Direct>(l.args[1])) {
ref = get<Direct>(l.args[1]);
} else {
error("Second argument of stod must be a direct reference");
}
if (isDouble(toConv) || isInt(toConv)) {
Literal newLit;
newLit.val = stod(toConv);
2025-08-24 15:08:07 +10:00
bool existed = variables.count(ref.varName) > 0;
2025-08-11 14:12:25 +10:00
variables[ref.varName] = newLit;
} else {
error("Cannot convert the value " + toConv + " to a decimal");
}
}
break;
/*
tostring instruction
This function converts any type to a string, and saves it in a variable.
*/
case Instructions::Tostring:
if (l.args.size() < 2) {
error("Could not find all arguments required for Tostring inbuilt");
}
{
Literal toConv;
Direct ref;
if (holds_alternative<Literal>(l.args[0])) {
toConv = get<Literal>(l.args[0]);
} else {
error("First argument of tostring must be a literal");
}
if (holds_alternative<Direct>(l.args[1])) {
ref = get<Direct>(l.args[1]);
} else {
error("Second argument of tostring must be a direct reference");
}
Literal newLit;
if (holds_alternative<int>(toConv.val)) {
newLit.val = to_string(get<int>(toConv.val));
} else if (holds_alternative<double>(toConv.val)) {
newLit.val = to_string(get<double>(toConv.val));
} else if (holds_alternative<string>(toConv.val)) {
newLit.val = get<string>(toConv.val);
} else if (holds_alternative<char>(toConv.val)) {
newLit.val = string().append(&get<char>(toConv.val));
} else if (holds_alternative<bool>(toConv.val)) {
if (get<bool>(toConv.val)) {
newLit.val = "true";
} else {
newLit.val = "false";
}
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(ref.varName) > 0;
2025-08-11 14:12:25 +10:00
variables[ref.varName] = newLit;
}
break;
2025-08-11 10:07:05 +10:00
/*
2025-08-09 20:33:08 +10:00
stdin instruction
This instruction takes input from the standard character input via
the C++ getline() function, and saves it to a variable.
*/
case Instructions::Stdin:
if (l.args.size() < 1) {
2025-08-10 13:31:28 +10:00
error("Could not find all arguments required for Stdin inbuilt");
2025-08-09 20:33:08 +10:00
}
if (holds_alternative<Direct>(l.args[0])) {
2025-08-24 15:08:07 +10:00
Direct varRef = get<Direct>(l.args[0]);
2025-08-09 20:33:08 +10:00
string userIn;
getline(cin, userIn);
Literal userLit;
userLit.val = userIn;
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = userLit;
2025-08-09 20:33:08 +10:00
} else {
error("First argument of stdin must be a direct reference");
}
break;
/*
add instruction
This instruction adds two values (can be numbers or strings) and outputs
it to the direct reference given.
*/
case Instructions::Add:
if (l.args.size() < 3) {
error("Could not find all arguments required for Add inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal left;
Literal right;
Literal final;
if (holds_alternative<Literal>(l.args[0])) {
left = get<Literal>(l.args[0]);
} else {
error("First argument of add must be a value (literal or value reference)");
}
if (holds_alternative<Literal>(l.args[1])) {
right = get<Literal>(l.args[1]);
} else {
error("Second argument of add must be a value (literal or value reference)");
}
if (holds_alternative<Direct>(l.args[2])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[2]);
2025-08-09 20:33:08 +10:00
} else {
error("Third argument of add must be a direct reference");
}
// Figure out types and compute values
if (holds_alternative<int>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<int>(left.val) + get<int>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<double>(right.val)) {
final.val = get<double>(left.val) + get<double>(right.val);
} else if (holds_alternative<int>(left.val) && holds_alternative<double>(right.val)) {
final.val = double(get<int>(left.val)) + get<double>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<double>(left.val) + double(get<int>(right.val));
} else if (holds_alternative<string>(left.val) && holds_alternative<string>(right.val)) {
final.val = get<string>(left.val) + get<string>(right.val);
2025-08-11 10:07:05 +10:00
} else if (holds_alternative<string>(left.val) && holds_alternative<char>(right.val)) {
final.val = get<string>(left.val).append(&get<char>(right.val));
} else if (holds_alternative<char>(left.val) && holds_alternative<string>(right.val)) {
final.val = string().append(&get<char>(left.val)) + get<string>(right.val);
} else if (holds_alternative<char>(left.val) && holds_alternative<char>(right.val)) {
final.val = string().append(&get<char>(left.val)).append(&get<char>(right.val));
2025-08-09 20:33:08 +10:00
} else {
error("Cannot add those two values");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = final;
2025-08-09 20:33:08 +10:00
}
break;
/*
subtract instruction
This instruction subtracts two values and outputs it to the
direct reference given.
*/
case Instructions::Subtract:
if (l.args.size() < 3) {
error("Could not find all arguments required for Subtract inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal left;
Literal right;
Literal final;
if (holds_alternative<Literal>(l.args[0])) {
left = get<Literal>(l.args[0]);
} else {
error("First argument of subtract must be a value (literal or value reference)");
}
if (holds_alternative<Literal>(l.args[1])) {
right = get<Literal>(l.args[1]);
} else {
error("Second argument of subtract must be a value (literal or value reference)");
}
if (holds_alternative<Direct>(l.args[2])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[2]);
2025-08-09 20:33:08 +10:00
} else {
error("Third argument of subtract must be a direct reference");
}
// Figure out types and compute values
if (holds_alternative<int>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<int>(left.val) - get<int>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<double>(right.val)) {
final.val = get<double>(left.val) - get<double>(right.val);
} else if (holds_alternative<int>(left.val) && holds_alternative<double>(right.val)) {
final.val = double(get<int>(left.val)) - get<double>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<double>(left.val) - double(get<int>(right.val));
} else {
error("Cannot subtract those two values");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = final;
2025-08-09 20:33:08 +10:00
}
break;
/*
multiply instruction
This instruction multiplies two numbers and outputs it to the
direct reference given.
*/
case Instructions::Multiply:
if (l.args.size() < 3) {
error("Could not find all arguments required for Multiply inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal left;
Literal right;
Literal final;
if (holds_alternative<Literal>(l.args[0])) {
left = get<Literal>(l.args[0]);
} else {
error("First argument of multiply must be a value (literal or value reference)");
}
if (holds_alternative<Literal>(l.args[1])) {
right = get<Literal>(l.args[1]);
} else {
error("Second argument of multiply must be a value (literal or value reference)");
}
if (holds_alternative<Direct>(l.args[2])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[2]);
2025-08-09 20:33:08 +10:00
} else {
error("Third argument of multiply must be a direct reference");
}
// Figure out types and compute values
if (holds_alternative<int>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<int>(left.val) * get<int>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<double>(right.val)) {
final.val = get<double>(left.val) * get<double>(right.val);
} else if (holds_alternative<int>(left.val) && holds_alternative<double>(right.val)) {
final.val = double(get<int>(left.val)) * get<double>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<double>(left.val) * double(get<int>(right.val));
} else {
error("Cannot multiply those two values");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = final;
2025-08-09 20:33:08 +10:00
}
break;
/*
divide instruction
This instruction divides two numbers and outputs it to the direct
reference given.
*/
case Instructions::Divide:
if (l.args.size() < 3) {
error("Could not find all arguments required for Divide inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal left;
Literal right;
Literal final;
if (holds_alternative<Literal>(l.args[0])) {
left = get<Literal>(l.args[0]);
} else {
error("First argument of divide must be a value (literal or value reference)");
}
if (holds_alternative<Literal>(l.args[1])) {
right = get<Literal>(l.args[1]);
} else {
error("Second argument of divide must be a value (literal or value reference)");
}
if (holds_alternative<Direct>(l.args[2])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[2]);
2025-08-09 20:33:08 +10:00
} else {
error("Third argument of divide must be a direct reference");
}
// Ensure we aren't dividing by zero
if (holds_alternative<int>(right.val)) {
if (get<int>(right.val) == 0) {
error("Division by zero is not allowed");
}
}
if (holds_alternative<double>(right.val)) {
if (get<double>(right.val) == 0) {
error("Division by zero is not allowed");
}
}
// Figure out types and compute values
if (holds_alternative<int>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<int>(left.val) / get<int>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<double>(right.val)) {
final.val = get<double>(left.val) / get<double>(right.val);
} else if (holds_alternative<int>(left.val) && holds_alternative<double>(right.val)) {
final.val = double(get<int>(left.val)) / get<double>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<double>(left.val) / double(get<int>(right.val));
} else {
error("Cannot divide those two values");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = final;
2025-08-09 20:33:08 +10:00
}
break;
/*
equal instruction
This instruction checks if two values are equal and outputs a boolean
to the direct reference given.
*/
case Instructions::Equal:
if (l.args.size() < 3) {
error("Could not find all arguments required for Equal inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal left;
Literal right;
Literal final;
if (holds_alternative<Literal>(l.args[0])) {
left = get<Literal>(l.args[0]);
} else {
error("First argument of equal must be a value (literal or value reference)");
}
if (holds_alternative<Literal>(l.args[1])) {
right = get<Literal>(l.args[1]);
} else {
error("Second argument of equal must be a value (literal or value reference)");
}
if (holds_alternative<Direct>(l.args[2])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[2]);
2025-08-09 20:33:08 +10:00
} else {
error("Third argument of equal must be a direct reference");
}
// Figure out types and compute values
if (holds_alternative<int>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<int>(left.val) == get<int>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<double>(right.val)) {
final.val = get<double>(left.val) == get<double>(right.val);
} else if (holds_alternative<int>(left.val) && holds_alternative<double>(right.val)) {
final.val = double(get<int>(left.val)) == get<double>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<double>(left.val) == double(get<int>(right.val));
} else if (holds_alternative<char>(left.val) && holds_alternative<char>(right.val)) {
final.val = get<char>(left.val) == get<char>(right.val);
} else if (holds_alternative<string>(left.val) && holds_alternative<string>(right.val)) {
final.val = get<string>(left.val) == get<string>(right.val);
} else if (holds_alternative<bool>(left.val) && holds_alternative<bool>(right.val)) {
final.val = get<bool>(left.val) == get<bool>(right.val);
} else {
error("Cannot equal those two values");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = final;
2025-08-09 20:33:08 +10:00
}
break;
/*
inequal instruction
This instruction checks if two values are not equal and outputs a boolean
to the direct reference given.
*/
case Instructions::Inequal:
if (l.args.size() < 3) {
error("Could not find all arguments required for Inequal inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal left;
Literal right;
Literal final;
if (holds_alternative<Literal>(l.args[0])) {
left = get<Literal>(l.args[0]);
} else {
error("First argument of inequal must be a value (literal or value reference)");
}
if (holds_alternative<Literal>(l.args[1])) {
right = get<Literal>(l.args[1]);
} else {
error("Second argument of inequal must be a value (literal or value reference)");
}
if (holds_alternative<Direct>(l.args[2])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[2]);
2025-08-09 20:33:08 +10:00
} else {
error("Third argument of inequal must be a direct reference");
}
// Figure out types and compute values
if (holds_alternative<int>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<int>(left.val) != get<int>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<double>(right.val)) {
final.val = get<double>(left.val) != get<double>(right.val);
} else if (holds_alternative<int>(left.val) && holds_alternative<double>(right.val)) {
final.val = double(get<int>(left.val)) != get<double>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<double>(left.val) != double(get<int>(right.val));
} else if (holds_alternative<char>(left.val) && holds_alternative<char>(right.val)) {
final.val = get<char>(left.val) != get<char>(right.val);
} else if (holds_alternative<string>(left.val) && holds_alternative<string>(right.val)) {
final.val = get<string>(left.val) != get<string>(right.val);
} else if (holds_alternative<bool>(left.val) && holds_alternative<bool>(right.val)) {
final.val = get<bool>(left.val) != get<bool>(right.val);
} else {
error("Cannot inequal those two values");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = final;
2025-08-09 20:33:08 +10:00
}
break;
2025-08-21 11:05:32 +10:00
/*
not instruction
Negates a boolean.
*/
case Instructions::Not:
if (l.args.size() < 2) {
error("Could not find all arguments required for Not inbuilt");
}
{
Literal boolean;
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-21 11:05:32 +10:00
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<bool>(get<Literal>(l.args[0]).val)) {
boolean.val = !(get<bool>(get<Literal>(l.args[0]).val));
} else {
error("First argument of not must be a boolean literal");
}
} else {
error("First argument of not must be a boolean literal");
}
if (holds_alternative<Direct>(l.args[1])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[1]);
2025-08-21 11:05:32 +10:00
} else {
error("Second argument of not must be a direct reference");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = boolean;
2025-08-21 11:05:32 +10:00
}
break;
2025-08-09 20:33:08 +10:00
/*
greater instruction
This instruction checks if the left value is greater than the right value
and outputs a boolean to the direct reference given.
*/
case Instructions::Greater:
if (l.args.size() < 3) {
error("Could not find all arguments required for Greater inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal left;
Literal right;
Literal final;
if (holds_alternative<Literal>(l.args[0])) {
left = get<Literal>(l.args[0]);
} else {
error("First argument of greater must be a value (literal or value reference)");
}
if (holds_alternative<Literal>(l.args[1])) {
right = get<Literal>(l.args[1]);
} else {
error("Second argument of greater must be a value (literal or value reference)");
}
if (holds_alternative<Direct>(l.args[2])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[2]);
2025-08-09 20:33:08 +10:00
} else {
error("Third argument of greater must be a direct reference");
}
// Figure out types and compute values
if (holds_alternative<int>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<int>(left.val) > get<int>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<double>(right.val)) {
final.val = get<double>(left.val) > get<double>(right.val);
} else if (holds_alternative<int>(left.val) && holds_alternative<double>(right.val)) {
final.val = double(get<int>(left.val)) > get<double>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<double>(left.val) > double(get<int>(right.val));
} else if (holds_alternative<char>(left.val) && holds_alternative<char>(right.val)) {
final.val = get<char>(left.val) > get<char>(right.val);
} else if (holds_alternative<string>(left.val) && holds_alternative<string>(right.val)) {
final.val = get<string>(left.val) > get<string>(right.val);
} else if (holds_alternative<bool>(left.val) && holds_alternative<bool>(right.val)) {
final.val = get<bool>(left.val) > get<bool>(right.val);
} else {
error("Cannot greater those two values");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = final;
2025-08-09 20:33:08 +10:00
}
break;
/*
lesser instruction
This instruction checks if the left value is lesser than the right value
and outputs a boolean to the direct reference given.
*/
case Instructions::Lesser:
if (l.args.size() < 3) {
error("Could not find all arguments required for Lesser inbuilt");
}
{
2025-08-24 15:08:07 +10:00
Direct varRef;
2025-08-09 20:33:08 +10:00
Literal left;
Literal right;
Literal final;
if (holds_alternative<Literal>(l.args[0])) {
left = get<Literal>(l.args[0]);
} else {
error("First argument of lesser must be a value (literal or value reference)");
}
if (holds_alternative<Literal>(l.args[1])) {
right = get<Literal>(l.args[1]);
} else {
error("Second argument of lesser must be a value (literal or value reference)");
}
if (holds_alternative<Direct>(l.args[2])) {
2025-08-24 15:08:07 +10:00
varRef = get<Direct>(l.args[2]);
2025-08-09 20:33:08 +10:00
} else {
error("Third argument of lesser must be a direct reference");
}
// Figure out types and compute values
if (holds_alternative<int>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<int>(left.val) < get<int>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<double>(right.val)) {
final.val = get<double>(left.val) < get<double>(right.val);
} else if (holds_alternative<int>(left.val) && holds_alternative<double>(right.val)) {
final.val = double(get<int>(left.val)) < get<double>(right.val);
} else if (holds_alternative<double>(left.val) && holds_alternative<int>(right.val)) {
final.val = get<double>(left.val) < double(get<int>(right.val));
} else if (holds_alternative<char>(left.val) && holds_alternative<char>(right.val)) {
final.val = get<char>(left.val) < get<char>(right.val);
} else if (holds_alternative<string>(left.val) && holds_alternative<string>(right.val)) {
final.val = get<string>(left.val) < get<string>(right.val);
} else if (holds_alternative<bool>(left.val) && holds_alternative<bool>(right.val)) {
final.val = get<bool>(left.val) < get<bool>(right.val);
} else {
error("Cannot lesser those two values");
}
2025-08-24 15:08:07 +10:00
bool existed = variables.count(varRef.varName) > 0;
variables[varRef.varName] = final;
2025-08-09 20:33:08 +10:00
}
break;
/*
jump instruction
Jumps to the line reference provided.
*/
case Instructions::Jump:
if (l.args.size() < 1) {
error("Could not find all arguments required for Jump inbuilt");
}
if (holds_alternative<Line>(l.args[0])) {
i = get<Line>(l.args[0]).lineNum - 2;
} else {
error("First argument of jump must be a line reference");
}
break;
/*
if instruction
Jumps to the line reference provided, if the boolean provided is true.
*/
case Instructions::If:
{
if (l.args.size() < 2) {
error("Could not find all arguments required for If inbuilt");
}
bool isTrue = false;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<bool>(get<Literal>(l.args[0]).val)) {
if (get<bool>(get<Literal>(l.args[0]).val)) isTrue = true;
} else {
error("First argument of if must be a bool value");
}
} else {
error("First argument of if must be a bool value");
}
if (isTrue) {
if (holds_alternative<Line>(l.args[1])) {
i = get<Line>(l.args[1]).lineNum - 2;
} else {
error("Second argument of if must be a line reference");
}
}
}
break;
2025-08-11 14:57:45 +10:00
/*
2025-08-13 09:40:03 +10:00
end instruction
2025-08-11 14:57:45 +10:00
Ends execution of the code, eith the status code provided.
*/
2025-08-09 20:33:08 +10:00
case Instructions::End:
if (l.args.size() < 1) {
error("Could not find all arguments required for End inbuilt");
}
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<int>(get<Literal>(l.args[0]).val)) {
exit(get<int>(get<Literal>(l.args[0]).val));
} else {
error("First argument of end must be an int value");
}
} else {
error("First argument of end must be an int value");
}
break;
2025-08-13 09:40:03 +10:00
/*
fun instruction
Allows functions to be defined.
*/
2025-08-11 14:57:45 +10:00
case Instructions::Fun:
2025-08-24 16:30:42 +10:00
if (l.args.size() < 2) {
2025-08-13 09:40:03 +10:00
error("Could not find all arguments required for Fun inbuilt");
}
{
Function newFunction;
2025-08-22 13:52:26 +10:00
2025-08-13 09:40:03 +10:00
if (holds_alternative<TypeRef>(l.args[0])) {
newFunction.returnType = get<TypeRef>(l.args[0]).type;
} else {
error("First argument of function must be a type reference");
}
string fnName;
2025-08-22 13:52:26 +10:00
2025-08-13 09:40:03 +10:00
if (holds_alternative<FunctionRef>(l.args[1])) {
fnName = get<FunctionRef>(l.args[1]).fnName;
} else {
error("Second argument of function must be a function reference");
}
2025-08-22 13:52:26 +10:00
// Parse function arguments (type-direct pairs)
if ((l.args.size() - 2) % 2 != 0) {
error("Function arguments must be in type-direct pairs");
}
for (int m = 2; m < l.args.size(); m += 2) {
FnArg newArg;
// Get type
if (holds_alternative<TypeRef>(l.args[m])) {
newArg.type = get<TypeRef>(l.args[m]).type;
2025-08-13 09:40:03 +10:00
} else {
2025-08-22 13:52:26 +10:00
error("Expected type reference in function argument definition");
2025-08-13 09:40:03 +10:00
}
2025-08-22 13:52:26 +10:00
// Get direct reference
if (m + 1 < l.args.size() && holds_alternative<Direct>(l.args[m + 1])) {
newArg.ref = get<Direct>(l.args[m + 1]);
} else {
error("Expected direct reference after type reference in function argument definition");
}
newFunction.args.push_back(newArg);
2025-08-13 09:40:03 +10:00
}
2025-08-22 13:52:26 +10:00
2025-08-13 09:40:03 +10:00
functions[fnName] = newFunction;
processingFunction = true;
procFnName = fnName;
}
2025-08-11 14:57:45 +10:00
break;
2025-08-13 09:40:03 +10:00
/*
return instruction
Exits a function.
*/
2025-08-11 14:57:45 +10:00
case Instructions::Return:
2025-08-13 18:31:54 +10:00
if (l.args.size() < 1) {
error("Could not find all arguments required for Return inbuilt");
}
if (holds_alternative<Literal>(l.args[0])) {
return get<Literal>(l.args[0]);
} else {
error("First argument of return must be a literal value/value reference");
}
2025-08-11 14:57:45 +10:00
break;
2025-08-13 09:40:03 +10:00
/*
endfun instruction
This should not be reached during normal execution.
*/
2025-08-11 14:57:45 +10:00
case Instructions::Endfun:
2025-08-13 09:40:03 +10:00
error("No function is being defined. Cannot end function declaration here");
2025-08-11 14:57:45 +10:00
break;
2025-08-18 09:36:35 +10:00
/*
pusharg instruction
This instruction makes new arguments avaliable for functions.
*/
2025-08-11 14:57:45 +10:00
case Instructions::Pusharg:
2025-08-15 11:35:58 +10:00
if (l.args.size() < 1) {
error("Could not find all arguments required for Endfun inbuilt");
}
if (holds_alternative<Literal>(l.args[0])) {
fnArgs.push_back(get<Literal>(l.args[0]));
} else {
error("First argument of pusharg must be a literal");
}
2025-08-11 14:57:45 +10:00
break;
2025-08-18 09:36:35 +10:00
/*
call instruction
This instruction calls a function, and makes the arguments avaliable for it.
*/
2025-08-11 14:57:45 +10:00
case Instructions::Call:
2025-08-18 09:36:35 +10:00
{
if (l.args.size() < 2) {
error("Could not find all arguments required for Call inbuilt");
}
2025-08-22 13:52:26 +10:00
2025-08-18 09:36:35 +10:00
FunctionRef ref;
Direct returnRef;
2025-08-22 13:52:26 +10:00
2025-08-18 09:36:35 +10:00
if (holds_alternative<FunctionRef>(l.args[0])) {
ref = get<FunctionRef>(l.args[0]);
} else {
error("First argument of call must be a function reference");
}
if (holds_alternative<Direct>(l.args[1])) {
returnRef = get<Direct>(l.args[1]);
} else {
error("Second argument of call must be a direct reference");
}
2025-08-25 17:35:16 +10:00
// Check for external function
if (externalFunctions.find(ref.fnName) != externalFunctions.end()) {
// Call external function
typedef GroundValue (*ExtFunc)(GroundValue*, int);
ExtFunc extFunc = (ExtFunc)externalFunctions[ref.fnName];
// Convert arguments
vector<GroundValue> gvArgs;
for (const Literal& arg : fnArgs) {
gvArgs.push_back(literalToGroundValue(arg));
}
// Call function
GroundValue result = extFunc(gvArgs.data(), gvArgs.size());
// Convert result back
Literal resultLit = groundValueToLiteral(result);
// Clear arguments and store result
fnArgs.clear();
variables[returnRef.varName] = resultLit;
break;
}
2025-08-22 13:52:26 +10:00
// Check if function exists
if (functions.find(ref.fnName) == functions.end()) {
error("Function " + ref.fnName + " not found");
}
// Check argument count
if (fnArgs.size() != functions[ref.fnName].args.size()) {
error("Function " + ref.fnName + " expects " +
to_string(functions[ref.fnName].args.size()) +
" arguments, got " + to_string(fnArgs.size()));
}
2025-08-25 13:35:22 +10:00
// Create backup of variables to be restored
map<string, Literal> scopeBackup = variables;
2025-08-22 13:52:26 +10:00
// Create function arguments with type checking
for (int m = 0; m < functions[ref.fnName].args.size(); m++) {
FnArg arg = functions[ref.fnName].args[m];
// Type checking - now with error reporting
if (arg.type != getLitType(fnArgs[m])) {
error("Function " + ref.fnName + " argument " + to_string(m + 1) +
" type mismatch. Expected type does not match provided argument type.");
}
// Create the variable
variables[arg.ref.varName] = fnArgs[m];
}
// Call the function
2025-08-24 16:30:42 +10:00
Literal retVal = exec(functions[ref.fnName].instructions, true);
2025-08-22 13:52:26 +10:00
2025-08-25 13:35:22 +10:00
// Restore scope
variables = scopeBackup;
2025-08-22 13:52:26 +10:00
// Clear function arguments for next call
fnArgs.clear();
2025-08-24 15:08:07 +10:00
// Now, assign the return value in the current scope.
bool existed = variables.count(returnRef.varName) > 0;
variables[returnRef.varName] = retVal;
2025-08-18 09:36:35 +10:00
}
2025-08-11 14:57:45 +10:00
break;
case Instructions::Use:
2025-08-25 13:22:15 +10:00
if (l.args.size() < 1) {
error("Could not find all arguments for Use inbuilt");
}
{
string useName;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
useName = get<string>(get<Literal>(l.args[0]).val) + ".grnd";
} else {
error("First argument for use requires a string literal");
}
} else {
error("First argument for use requires a string literal");
}
string groundLibsDir = getenv("GROUND_LIBS");
if (filesystem::exists(useName)) {
} else if (groundLibsDir != "" && filesystem::exists(groundLibsDir + useName)) {
useName = groundLibsDir + useName;
} else {
error("Could not find external Ground library in $GROUND_LIBS or current directory.");
}
ifstream file(useName);
string lns;
string in;
while (getline(file, lns)) {
in += lns += "\n";
}
Literal ret = exec(parser(lexer(in)), false); }
2025-08-11 14:57:45 +10:00
break;
case Instructions::Extern:
2025-08-25 17:35:16 +10:00
if (l.args.size() < 1) {
error("Could not find all arguments for Extern inbuilt");
}
{
string libName;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
libName = get<string>(get<Literal>(l.args[0]).val);
} else {
error("First argument for extern requires a string literal");
}
} else {
error("First argument for extern requires a string literal");
}
// Add appropriate extension
string fullLibName = libName;
#ifdef _WIN32
fullLibName += ".dll";
#elif __APPLE__
fullLibName += ".dylib";
#else
fullLibName += ".so";
#endif
// Check multiple locations for the library
string libPath;
bool found = false;
// Check GROUND_LIBS directory
const char* groundLibsDir = getenv("GROUND_LIBS");
if (groundLibsDir) {
string envPath = string(groundLibsDir);
// Add trailing slash if not present
if (!envPath.empty() && envPath.back() != '/' && envPath.back() != '\\') {
envPath += "/";
}
string fullEnvPath = envPath + fullLibName;
if (filesystem::exists(fullEnvPath)) {
libPath = fullEnvPath;
found = true;
}
}
if (!found) {
error("Could not find external library: " + fullLibName +
" (searched current directory and GROUND_LIBS)");
}
// Try to open the library
void* handle = DLOPEN(libPath.c_str());
if (!handle) {
error("Failed to load library " + libPath + ": " + string(DLERROR()));
}
// Store handle for cleanup later
loadedLibraries[libName] = handle;
// Get required functions
typedef const char** (*GetFunctionsFunc)();
typedef void* (*GetFunctionFunc)(const char*);
GetFunctionsFunc getFunctions = (GetFunctionsFunc)DLSYM(handle, "ground_get_functions");
GetFunctionFunc getFunction = (GetFunctionFunc)DLSYM(handle, "ground_get_function");
if (!getFunctions || !getFunction) {
error("Library " + libName + " is not Ground-compatible (missing required functions: ground_get_functions or ground_get_function)");
}
// Optional initialization
typedef void (*InitFunc)();
InitFunc initFunc = (InitFunc)DLSYM(handle, "ground_lib_init");
if (initFunc) {
initFunc();
}
// Register all functions
const char** functionNames = getFunctions();
if (!functionNames) {
error("Library " + libName + " returned null function list");
}
int functionCount = 0;
for (int i = 0; functionNames[i] != nullptr; i++) {
void* funcPtr = getFunction(functionNames[i]);
if (funcPtr) {
externalFunctions[string(functionNames[i])] = funcPtr;
functionCount++;
} else {
error("Failed to get function pointer for: " + string(functionNames[i]));
}
}
if (functionCount == 0) {
error("No functions were loaded from library: " + libName);
}
}
2025-08-11 14:57:45 +10:00
break;
2025-08-09 20:33:08 +10:00
default:
cout << "Still to be implemented" << endl;
break;
}
}
2025-08-13 18:31:54 +10:00
Literal retLiteral;
return retLiteral;
2025-08-09 20:33:08 +10:00
}
/*
lexer function
This function takes a string (the user's program) and splits it into smaller chunks
(keywords, values, words) for the parser to be able to read (see parser function
below).
*/
vector<vector<string>> lexer(string in) {
vector<vector<string>> out;
vector<string> line;
string buf;
bool procString = false;
bool procChar = false;
bool isComment = false;
for (char i : in) {
switch (i) {
case '"':
if (!isComment) {
2025-08-18 13:38:26 +10:00
if (procChar) {
buf.push_back(i);
} else {
procString = !procString;
buf.push_back(i);
}
2025-08-09 20:33:08 +10:00
}
break;
case '\'':
if (!isComment) {
2025-08-18 13:38:26 +10:00
if (procString) {
buf.push_back(i);
} else {
procChar = !procChar;
buf.push_back(i);
}
2025-08-09 20:33:08 +10:00
}
break;
case '\n':
if (!procString && !procChar) {
if (!buf.empty()) line.push_back(buf);
out.push_back(line);
buf.clear();
line.clear();
isComment = false;
} else {
if (!isComment) buf.push_back(i);
}
break;
case '#':
if (!procString && !procChar) {
isComment = true;
if (!buf.empty()) line.push_back(buf);
out.push_back(line);
buf.clear();
line.clear();
} else {
buf.push_back(i);
}
break;
case ' ':
if (!procString && !procChar) {
if (!buf.empty() && !isComment) line.push_back(buf);
buf.clear();
} else {
buf.push_back(i);
}
break;
default:
if (!isComment) buf.push_back(i);
break;
}
}
if (!buf.empty()) line.push_back(buf);
out.push_back(line);
return out;
}
/*
parser function
This takes multiple lines of code (in the form of vector<vector<string>>)
and turns that human-readable code into a list of instructions.
Refer to the Instruction struct above for more information.
*/
vector<Instruction> parser(vector<vector<string>> in) {
vector<Instruction> out;
2025-08-10 15:42:52 +10:00
int lineNum = 1;
2025-08-24 16:30:42 +10:00
int functionInstructionIndex = 0;
2025-08-09 20:33:08 +10:00
for (vector<string> lineTokens : in) {
2025-08-10 15:42:52 +10:00
lineNum ++;
2025-08-09 20:33:08 +10:00
Instruction newInst;
2025-08-10 15:42:52 +10:00
if (lineTokens.empty()) {
2025-08-24 16:30:42 +10:00
if (processingFunction) functionInstructionIndex ++;
2025-08-10 15:42:52 +10:00
out.push_back(newInst);
continue;
};
2025-08-09 20:33:08 +10:00
bool firstInst = true;
for (string i : lineTokens) {
if (firstInst) {
firstInst = false;
2025-08-10 15:42:52 +10:00
if (isLabel(i)) {
2025-08-24 16:30:42 +10:00
if (processingFunction) {
functions[procFnName].localLabels[i.substr(1)] = functionInstructionIndex;
} else {
2025-08-24 20:22:52 +10:00
labels[i.substr(1)] = lineNum - 1;
2025-08-24 16:30:42 +10:00
}
2025-08-10 15:42:52 +10:00
}
else if (i == "stdin") newInst.inst = Instructions::Stdin;
2025-08-09 20:33:08 +10:00
else if (i == "stdout") newInst.inst = Instructions::Stdout;
else if (i == "stdlnout") newInst.inst = Instructions::Stdlnout;
else if (i == "jump") newInst.inst = Instructions::Jump;
else if (i == "if") newInst.inst = Instructions::If;
else if (i == "add") newInst.inst = Instructions::Add;
else if (i == "subtract") newInst.inst = Instructions::Subtract;
else if (i == "multiply") newInst.inst = Instructions::Multiply;
else if (i == "divide") newInst.inst = Instructions::Divide;
else if (i == "greater") newInst.inst = Instructions::Greater;
else if (i == "lesser") newInst.inst = Instructions::Lesser;
else if (i == "equal") newInst.inst = Instructions::Equal;
else if (i == "inequal") newInst.inst = Instructions::Inequal;
2025-08-24 14:41:34 +10:00
else if (i == "not") newInst.inst = Instructions::Not;
2025-08-09 20:33:08 +10:00
else if (i == "end") newInst.inst = Instructions::End;
else if (i == "set") newInst.inst = Instructions::Set;
2025-08-10 13:31:28 +10:00
else if (i == "setlist") newInst.inst = Instructions::Setlist;
else if (i == "setlistat") newInst.inst = Instructions::Setlistat;
else if (i == "getlistat") newInst.inst = Instructions::Getlistat;
else if (i == "getlistsize") newInst.inst = Instructions::Getlistsize;
2025-08-11 10:07:05 +10:00
else if (i == "listappend") newInst.inst = Instructions::Listappend;
else if (i == "getstrsize") newInst.inst = Instructions::Getstrsize;
else if (i == "getstrcharat") newInst.inst = Instructions::Getstrcharat;
2025-08-11 14:12:25 +10:00
else if (i == "stoi") newInst.inst = Instructions::Stoi;
else if (i == "stod") newInst.inst = Instructions::Stod;
else if (i == "tostring") newInst.inst = Instructions::Tostring;
2025-08-24 16:30:42 +10:00
else if (i == "fun") {
newInst.inst = Instructions::Fun;
functionInstructionIndex = 0;
}
2025-08-11 14:57:45 +10:00
else if (i == "return") newInst.inst = Instructions::Return;
else if (i == "endfun") newInst.inst = Instructions::Endfun;
else if (i == "pusharg") newInst.inst = Instructions::Pusharg;
else if (i == "call") newInst.inst = Instructions::Call;
else if (i == "use") newInst.inst = Instructions::Use;
else if (i == "extern") newInst.inst = Instructions::Extern;
2025-08-09 20:33:08 +10:00
else error("Unexpected token: " + i);
} else {
Types type = getType(i);
switch (type) {
case Types::Value:
{
ValueRef newValueRef;
newValueRef.varName = i.substr(1);
newInst.args.push_back(newValueRef);
}
break;
case Types::Direct:
{
Direct newDirect;
newDirect.varName = i.substr(1);
newInst.args.push_back(newDirect);
}
break;
2025-08-10 15:42:52 +10:00
case Types::Label:
error("Label should be defined as first token");
break;
2025-08-12 09:45:00 +10:00
case Types::Type:
{
TypeRef newType;
string type = i.substr(1);
if (type == "string") newType.type = Types::String;
else if (type == "char") newType.type = Types::Char;
else if (type == "double") newType.type = Types::Double;
else if (type == "int") newType.type = Types::Int;
else if (type == "bool") newType.type = Types::Bool;
2025-08-13 09:40:03 +10:00
else error("Ground could not find type. This is an error with the interpreter, not your code. This line of code should never be reached.");
newInst.args.push_back(newType);
2025-08-12 09:45:00 +10:00
}
break;
case Types::Function:
{
FunctionRef newFunction;
newFunction.fnName = i.substr(1);
newInst.args.push_back(newFunction);
}
2025-08-13 09:40:03 +10:00
break;
2025-08-09 20:33:08 +10:00
case Types::Line:
{
Line newLine;
2025-08-10 15:42:52 +10:00
if (isInt(i.substr(1))) newLine.lineNum = stoi(i.substr(1));
else {
newLine.isLabel = true;
newLine.label = i.substr(1);
newLine.lineNum = 0;
}
2025-08-09 20:33:08 +10:00
newInst.args.push_back(newLine);
}
break;
2025-08-10 13:31:28 +10:00
case Types::ListRef:
{
ListRef newLR;
newLR.listName = i.substr(1);
newInst.args.push_back(newLR);
}
break;
2025-08-09 20:33:08 +10:00
case Types::String:
{
Literal newLiteral;
string str = i.substr(1, i.size() - 2);
newLiteral.val = str;
newInst.args.push_back(newLiteral);
}
break;
case Types::Char:
{
Literal newLiteral;
char ch = i[1];
newLiteral.val = ch;
newInst.args.push_back(newLiteral);
}
break;
case Types::Int:
{
Literal newLiteral;
int val = stoi(i);
newLiteral.val = val;
newInst.args.push_back(newLiteral);
}
break;
case Types::Double:
{
Literal newLiteral;
double val = stod(i);
newLiteral.val = val;
newInst.args.push_back(newLiteral);
}
break;
case Types::Bool:
{
Literal newLiteral;
bool val = (i == "true");
newLiteral.val = val;
newInst.args.push_back(newLiteral);
}
break;
}
}
}
2025-08-24 16:30:42 +10:00
if (processingFunction) functionInstructionIndex++;
2025-08-09 20:33:08 +10:00
out.push_back(newInst);
}
return out;
}
/*
main function
Takes input from the command line, reads the file specified by the user and
starts execution of the program. See also lexer function, parser function,
exec function.
*/
int main(int argc, char** argv) {
if (argc < 2) {
cout << "Usage: " << argv[0] << " (file)" << endl;
exit(0);
}
2025-08-25 19:13:00 +10:00
// Get args
List argsList;
for (int i = 2; i < argc; i++) {
Literal lit;
lit.val = argv[i];
argsList.val.push_back(lit);
}
lists["args"] = argsList;
2025-08-09 20:33:08 +10:00
ifstream file(argv[1]);
string lns;
string in;
while (getline(file, lns)) {
in += lns += "\n";
}
2025-08-24 16:30:42 +10:00
Literal ret = exec(parser(lexer(in)), false);
2025-08-13 18:31:54 +10:00
if (holds_alternative<int>(ret.val)) {
return get<int>(ret.val);
} else {
return 0;
}
2025-08-09 20:33:08 +10:00
return 0;
}