Initial commit

This commit is contained in:
2025-10-25 21:28:16 +11:00
commit 9e76fca977
9 changed files with 881 additions and 0 deletions

249
src/runner/runner.cpp Normal file
View File

@@ -0,0 +1,249 @@
#include "runner.h"
#include <iostream>
#include <ostream>
#include <utility>
#include "../parser/parser.h"
std::optional<ASTNode> Executor::consume() {
if (iterator < code.nodes.size()) {
return code.nodes[iterator++];
}
return {};
}
std::optional<ASTNode> Executor::peek(int ahead) {
if (iterator + ahead < code.nodes.size()) {
return code.nodes[iterator + ahead];
}
return {};
}
/**
* Constructs an `Executor` object.
*
* This constructor initializes the `Executor` by taking an abstract syntax tree (AST) code block as input,
* setting up the execution context, including variables, functions, and arguments, and continuously parsing
* and executing the AST nodes until the end of the block is reached.
*
* @param in The abstract syntax tree (AST) code block to execute.
* @param isInitCall A boolean flag to determine if this is the initial entry point to execution.
* If true, it runs the "main" function after setting up the context.
* @param scopeVals A map of variable names (strings) to their corresponding `ASTValue` objects used as
* the current scope of variables.
* @param scopeFns A map of function names (strings) to their corresponding `ASTFunction` objects used as
* the current set of functions within scope.
* @param args A vector of `ASTValue` objects passed as arguments for the context of this execution.
*
* @details
* - If arguments are provided, they are assigned to variables named `arg0`, `arg1`, etc., in the local scope.
* - Functions can be defined dynamically within the block and will be stored in the `functions` map.
* - Variable assignments and supported operators (e.g., '=', '==', '!=', etc.) are processed if encountered.
* - If a function call is encountered, the corresponding function body is executed in a new `Executor` context,
* passing along argument values and maintaining the state of variables and functions.
* - Special support for `import()` calls is provided when encountered in the root function.
* - If `isInitCall` is true, ensures the "main" function is executed after parsing and executing the AST.
* - The constructor uses recursive execution for nested function calls.
*
* @note Exits the process if critical execution errors occur (e.g., unexpected nodes or missing values).
*/
Executor::Executor(ASTCodeBlock in, bool isInitCall, std::map<std::string, ASTValue> scopeVals, std::map<std::string, ASTFunction> scopeFns, std::vector<ASTValue> args) : code(std::move(in)), variables(std::move(scopeVals)), functions(std::move(scopeFns)) {
for (size_t i = 0; i < args.size(); i++) {
variables["arg" + std::to_string(i)] = args[i];
}
while (true) {
std::optional<ASTNode> node = consume();
if (node.has_value()) {
// for if we see an identifier
if (std::holds_alternative<std::shared_ptr<ASTIdentifier>>(node.value())) {
std::optional<ASTNode> next = consume();
if (next.has_value()) {
// function assignment
// eg: main func { ... }
// eg: dingus func { ... }
if (std::holds_alternative<std::shared_ptr<ASTFunction>>(next.value())) {
functions[std::get<std::shared_ptr<ASTIdentifier>>(node.value())->name] = *std::get<std::shared_ptr<ASTFunction>>(next.value());
} else if (std::holds_alternative<std::shared_ptr<ASTIdentifier>>(next.value())) {
std::string id = std::get<std::shared_ptr<ASTIdentifier>>(next.value())->name;
if (id == "=") {
// setting a variable
std::optional<ASTNode> valueNode = consume();
if (valueNode.has_value()) {
ASTValue next;
if (std::holds_alternative<std::shared_ptr<ASTValue>>(valueNode.value())) {
variables[std::get<std::shared_ptr<ASTIdentifier>>(node.value())->name] = *std::get<std::shared_ptr<ASTValue>>(valueNode.value());
} else if (std::holds_alternative<std::shared_ptr<ASTFunction>>(valueNode.value())) {
functions[std::get<std::shared_ptr<ASTIdentifier>>(node.value())->name] = *std::get<std::shared_ptr<ASTFunction>>(valueNode.value());
} else {
std::cout << "Expected value or function after = sign" << std::endl;
exit(1);
}
} else {
std::cout << "Expected value after = sign" << std::endl;
exit(1);
}
} else if (id == "==") {
} else if (id == "!=") {
} else if (id == ">") {
} else if (id == ">=") {
} else if (id == "<") {
} else if (id == "<=") {
}
} else {
std::cout << "Expected function or operator after identifier" << std::endl;
exit(1);
}
}
}
// if we see a function call
// note: we only accept calls to import() in the root function
if (std::holds_alternative<std::shared_ptr<ASTFunctionCall>>(node.value())) {
std::string fnName = std::get<std::shared_ptr<ASTFunctionCall>>(node.value())->func;
std::vector<ASTNode> callArgNodes = std::get<std::shared_ptr<ASTFunctionCall>>(node.value())->args;
std::vector<ASTValue> callArgs;
for (auto &callArgNode : callArgNodes) {
if (std::holds_alternative<std::shared_ptr<ASTValue>>(callArgNode)) {
callArgs.push_back(*std::get<std::shared_ptr<ASTValue>>(callArgNode));
}
}
if (fnName == "import") {
// work on importing modules later
continue;
}
if (isInitCall) {
std::cout << "Function " << fnName << " not allowed in root function" << std::endl;
exit(1);
}
if (fnName == "print") {
for (ASTValue &arg : callArgs) {
if (arg.type == ValueType::String) {
std::optional<std::string> argString = arg.getString();
if (argString.has_value()) {
std::cout << argString.value();
} else {
std::cout << "Type mismatch - expecting string but got something else" << std::endl;
}
} else if (arg.type == ValueType::Int) {
std::optional<int> argInt = arg.getInt();
if (argInt.has_value()) {
std::cout << argInt.value();
} else {
std::cout << "Type mismatch - expecting int but got something else" << std::endl;
}
} else if (arg.type == ValueType::Float) {
std::optional<double> argFloat = arg.getFloat();
if (argFloat.has_value()) {
std::cout << argFloat.value();
} else {
std::cout << "Type mismatch - expecting float but got something else" << std::endl;
}
} else if (arg.type == ValueType::Bool) {
std::optional<bool> argBool = arg.getBool();
if (argBool.has_value()) {
std::cout << argBool.value();
} else {
std::cout << "Type mismatch - expecting bool but got something else" << std::endl;
}
} else {
std::cout << "Type mismatch - expecting string, int, float, or bool but got something else" << std::endl;
}
}
} else if (fnName == "println") {
for (ASTValue &arg : callArgs) {
if (arg.type == ValueType::String) {
std::optional<std::string> argString = arg.getString();
if (argString.has_value()) {
std::cout << argString.value() << std::endl;
} else {
std::cout << "Type mismatch - expecting string but got something else" << std::endl;
}
} else if (arg.type == ValueType::Int) {
std::optional<int> argInt = arg.getInt();
if (argInt.has_value()) {
std::cout << argInt.value() << std::endl;
} else {
std::cout << "Type mismatch - expecting int but got something else" << std::endl;
}
} else if (arg.type == ValueType::Float) {
std::optional<double> argFloat = arg.getFloat();
if (argFloat.has_value()) {
std::cout << argFloat.value() << std::endl;
} else {
std::cout << "Type mismatch - expecting float but got something else" << std::endl;
}
} else if (arg.type == ValueType::Bool) {
std::optional<bool> argBool = arg.getBool();
if (argBool.has_value()) {
std::cout << argBool.value() << std::endl;
} else {
std::cout << "Type mismatch - expecting bool but got something else" << std::endl;
}
} else {
std::cout << "Type mismatch - expecting string, int, float, or bool but got something else" << std::endl;
}
}
} else if (fnName == "if") {
if (callArgs.empty()) {
std::cout << "Expected at least one argument to if statement" << std::endl;
exit(1);
}
if (callArgs[0].type != ValueType::Bool) {
std::cout << "Expected first argument to if statement to be a boolean" << std::endl;
exit(1);
}
std::optional<ASTNode> block = consume();
if (!block.has_value()) {
std::cout << "If statement expects a body" << std::endl;
exit(1);
}
if (callArgs[0].getBool().value()) {
if (std::holds_alternative<std::shared_ptr<ASTCodeBlock>>(block.value())) {
Executor(*std::get<std::shared_ptr<ASTCodeBlock>>(block.value()), false, variables, functions);
}
}
} else if (fnName == "while") {
if (callArgs.empty()) {
std::cout << "Expected at least one argument to if statement" << std::endl;
exit(1);
}
if (callArgs[0].type != ValueType::Bool) {
std::cout << "Expected first argument to if statement to be a boolean" << std::endl;
exit(1);
}
std::optional<ASTNode> block = consume();
if (!block.has_value()) {
std::cout << "If statement expects a body" << std::endl;
exit(1);
}
if (callArgs[0].getBool().value()) {
while (callArgs[0].getBool().value()) {
if (std::holds_alternative<std::shared_ptr<ASTCodeBlock>>(block.value())) {
Executor(*std::get<std::shared_ptr<ASTCodeBlock>>(block.value()), false, variables, functions);
}
}
}
} else {
if (functions.find(fnName) != functions.end()) {
Executor(functions[fnName].body, false, variables, functions, callArgs);
} else {
std::cout << "Function " << fnName << " not found" << std::endl;
exit(1);
}
}
}
} else {
break;
}
}
if (isInitCall) {
Executor(functions["main"].body, false, variables, functions);
}
}

27
src/runner/runner.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <map>
#include "../parser/parser.h"
/**
* @class Executor
* @brief Responsible for executing a sequence of operations defined in an abstract syntax tree (AST).
*
* This class provides execution functionality for ASTCodeBlock objects.
* It maintains a mapping of variables and functions that can be used within the
* context of execution. The class implements mechanisms for traversing AST nodes
* and consuming or peeking at individual nodes.
*/
class Executor {
private:
std::map<std::string, ASTFunction> functions;
std::map<std::string, ASTValue> variables;
ASTCodeBlock code;
size_t iterator = 0;
std::optional<ASTNode> consume();
std::optional<ASTNode> peek(int ahead = 1);
public:
explicit Executor(ASTCodeBlock in, bool isInitCall = false, std::map<std::string, ASTValue> scopeVals = {}, std::map<std::string, ASTFunction> scopeFns = {}, std::vector<ASTValue> args = {});
};