Initial commit
This commit is contained in:
249
src/runner/runner.cpp
Normal file
249
src/runner/runner.cpp
Normal 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
27
src/runner/runner.h
Normal 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 = {});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user