Initial commit
This commit is contained in:
68
.vim/syntax/funk.vim
Normal file
68
.vim/syntax/funk.vim
Normal file
@@ -0,0 +1,68 @@
|
||||
" Vim syntax file
|
||||
" Language: Funk
|
||||
" Maintainer: Maxwell Jeffress
|
||||
" Latest Revision: 25 October 2025
|
||||
|
||||
if exists("b:current_syntax")
|
||||
finish
|
||||
endif
|
||||
|
||||
" Keywords
|
||||
syn keyword funkKeyword func import
|
||||
syn keyword funkConditional if while
|
||||
syn keyword funkBoolean true false
|
||||
|
||||
" Built-in functions
|
||||
syn keyword funkBuiltin print println
|
||||
|
||||
" Operators
|
||||
syn match funkOperator "\v\="
|
||||
syn match funkOperator "\v\=\="
|
||||
syn match funkOperator "\v!\="
|
||||
syn match funkOperator "\v\>"
|
||||
syn match funkOperator "\v\<"
|
||||
syn match funkOperator "\v\>\="
|
||||
syn match funkOperator "\v\<\="
|
||||
syn match funkOperator "\v\+"
|
||||
syn match funkOperator "\v-"
|
||||
syn match funkOperator "\v\*"
|
||||
syn match funkOperator "\v/"
|
||||
syn match funkOperator "\v\^"
|
||||
syn match funkOperator "\v\%"
|
||||
syn match funkOperator "\v\&\&"
|
||||
syn match funkOperator "\v\|\|"
|
||||
syn match funkOperator "\v!"
|
||||
|
||||
" Numbers
|
||||
syn match funkNumber "\v<\d+>"
|
||||
syn match funkNumber "\v<\d+\.\d+>"
|
||||
|
||||
" Strings
|
||||
syn region funkString start='"' end='"' skip='\\"'
|
||||
|
||||
" Comments
|
||||
syn match funkComment "\v//.*$"
|
||||
|
||||
" Identifiers (function/variable names)
|
||||
syn match funkIdentifier "\v<[a-zA-Z_][a-zA-Z0-9_]*>"
|
||||
|
||||
" Special variables (arg0, arg1, etc.)
|
||||
syn match funkSpecialVar "\varg\d+"
|
||||
|
||||
" Delimiters
|
||||
syn match funkDelimiter "\v[\(\)\{\}]"
|
||||
|
||||
" Highlighting
|
||||
hi def link funkKeyword Keyword
|
||||
hi def link funkConditional Conditional
|
||||
hi def link funkBoolean Boolean
|
||||
hi def link funkBuiltin Function
|
||||
hi def link funkOperator Operator
|
||||
hi def link funkNumber Number
|
||||
hi def link funkString String
|
||||
hi def link funkComment Comment
|
||||
hi def link funkIdentifier Identifier
|
||||
hi def link funkSpecialVar Special
|
||||
hi def link funkDelimiter Delimiter
|
||||
|
||||
let b:current_syntax = "funk"
|
||||
70
src/lexer/lexer.cpp
Normal file
70
src/lexer/lexer.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "lexer.h"
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
std::optional<char> Lexer::consume() {
|
||||
incrementor ++;
|
||||
if (incrementor < file.size()) {
|
||||
return file[incrementor];
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<char> Lexer::peek(int ahead) {
|
||||
if (incrementor + ahead < file.size()) {
|
||||
return file[incrementor + ahead];
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool Lexer::isDelimiter(char c) {
|
||||
if (std::find(delimiters.begin(), delimiters.end(), c) != delimiters.end()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs a Lexer object and tokenizes the provided input string.
|
||||
*
|
||||
* This constructor initializes the Lexer with the given input string and processes it
|
||||
* to generate a list of tokens. It supports handling strings encapsulated by double quotes
|
||||
* and uses specified delimiters to separate tokens.
|
||||
*
|
||||
* @param in The input string to be tokenized.
|
||||
* @return A constructed Lexer instance with tokenized content stored in the `content` member.
|
||||
*/
|
||||
Lexer::Lexer(std::string in) : file(std::move(in)) {
|
||||
std::string buf;
|
||||
bool instring = false;
|
||||
while (true) {
|
||||
std::optional<char> c = consume();
|
||||
if (c.has_value()) {
|
||||
if (c.value() == '"') {
|
||||
instring = !instring;
|
||||
if (!instring) {
|
||||
content.push_back(buf + '"');
|
||||
buf.clear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!instring && isDelimiter(c.value())) {
|
||||
if (!buf.empty()) content.push_back(buf);
|
||||
if (c.value() != ' ') content.emplace_back(1, c.value());
|
||||
buf.clear();
|
||||
} else {
|
||||
buf += c.value();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/lexer/lexer.h
Normal file
30
src/lexer/lexer.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* @class Lexer
|
||||
* @brief The Lexer class processes input strings to tokenize and parse contents.
|
||||
*
|
||||
* This class is designed to take a given string input, tokenize it based on
|
||||
* specific delimiters, and store the resulting tokens. It facilitates basic
|
||||
* operations like consuming, peeking at characters, and identifying delimiters.
|
||||
*/
|
||||
class Lexer {
|
||||
private:
|
||||
std::vector<char> delimiters = {
|
||||
'(', ')', '{', '}', '.', '\n', ' '
|
||||
};
|
||||
std::string file;
|
||||
size_t incrementor = -1;
|
||||
std::optional<char> consume();
|
||||
std::optional<char> peek(int ahead = 1);
|
||||
bool isDelimiter(char c);
|
||||
|
||||
public:
|
||||
std::vector<std::string> content;
|
||||
explicit Lexer(std::string in);
|
||||
};
|
||||
|
||||
28
src/main.cpp
Normal file
28
src/main.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include "lexer/lexer.h"
|
||||
#include "parser/parser.h"
|
||||
#include "runner/runner.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc <= 1) {
|
||||
std::cout << "Usage: " << argv[0] << " (file)" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
std::string fileContent;
|
||||
{
|
||||
std::ifstream file(argv[1]);
|
||||
if (file) {
|
||||
std::string buf;
|
||||
while (std::getline(file, buf)) {
|
||||
fileContent += buf + "\n";
|
||||
}
|
||||
} else {
|
||||
std::cout << "Could not open file" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
Lexer lexer(fileContent);
|
||||
ASTCodeBlock codeBlock(lexer.content);
|
||||
Executor executor(codeBlock, true);
|
||||
}
|
||||
292
src/parser/parser.cpp
Normal file
292
src/parser/parser.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
#include "parser.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <variant>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
ASTValue::ASTValue() : type(ValueType::None) {}
|
||||
ASTValue::ASTValue(std::string in) : type(ValueType::String), value(in) {}
|
||||
ASTValue::ASTValue(bool in) : type(ValueType::Bool), value(in) {}
|
||||
ASTValue::ASTValue(long long in) : type(ValueType::Int), value(in) {}
|
||||
ASTValue::ASTValue(double in) : type(ValueType::Float), value(in) {}
|
||||
|
||||
ValueType ASTValue::getValueType(std::string in) {
|
||||
if (in.size() < 1) {
|
||||
return ValueType::None;
|
||||
}
|
||||
if (in.front() == '"' && in.back() == '"') {
|
||||
return ValueType::String;
|
||||
}
|
||||
if (in == "true" || in == "false") {
|
||||
return ValueType::Bool;
|
||||
}
|
||||
bool isInt = true;
|
||||
bool isFloat = false;
|
||||
for (const char &c : in) {
|
||||
if (std::isdigit(c) == false) {
|
||||
if (c == '.' && isFloat == false) {
|
||||
isFloat = true;
|
||||
isInt = false;
|
||||
} else {
|
||||
isInt = false;
|
||||
isFloat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isInt) {
|
||||
return ValueType::Int;
|
||||
}
|
||||
if (isFloat) {
|
||||
if (in == ".") return ValueType::None;
|
||||
return ValueType::Float;
|
||||
}
|
||||
return ValueType::None;
|
||||
|
||||
}
|
||||
|
||||
ASTFunction::ASTFunction(ASTCodeBlock body) : body(std::move(body)) {}
|
||||
ASTFunction::ASTFunction() {}
|
||||
ASTFunctionCall::ASTFunctionCall(std::string func, std::vector<ASTNode> args) : func(std::move(func)), args(std::move(args)) {}
|
||||
ASTIdentifier::ASTIdentifier(std::string in) : name(std::move(in)) {}
|
||||
|
||||
|
||||
std::optional<std::string> ASTValue::getString() {
|
||||
if (type == ValueType::String && std::holds_alternative<std::string>(value)) {
|
||||
return std::get<std::string>(value);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int> ASTValue::getInt() {
|
||||
if (type == ValueType::Int && std::holds_alternative<long long>(value)) {
|
||||
return std::get<long long>(value);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<double> ASTValue::getFloat() {
|
||||
if (type == ValueType::Float && std::holds_alternative<double>(value)) {
|
||||
return std::get<double>(value);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<bool> ASTValue::getBool() {
|
||||
if (type == ValueType::Bool && std::holds_alternative<bool>(value)) {
|
||||
return std::get<bool>(value);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> ASTCodeBlock::consume() {
|
||||
if (iterator < content.size()) {
|
||||
return content[iterator++]; // Post-increment: returns current, then increments
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::string> ASTCodeBlock::peek(int ahead) {
|
||||
if (iterator + ahead < content.size()) {
|
||||
return content[iterator + ahead];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
TokenType ASTCodeBlock::getTokenType() {
|
||||
if (peek(0).has_value() == false) {
|
||||
return TokenType::None;
|
||||
}
|
||||
|
||||
std::optional<std::string> tokenv = peek(0);
|
||||
std::string token;
|
||||
if (tokenv.has_value()) {
|
||||
token = tokenv.value();
|
||||
} else {
|
||||
return TokenType::None;
|
||||
}
|
||||
|
||||
// Check for values first
|
||||
if (ASTValue().getValueType(token) != ValueType::None) {
|
||||
return TokenType::Value;
|
||||
}
|
||||
|
||||
// Check for special characters/keywords
|
||||
if (token == "{") {
|
||||
return TokenType::CodeBlockStart;
|
||||
}
|
||||
if (token == "}") {
|
||||
return TokenType::CodeBlockEnd;
|
||||
}
|
||||
if (token == "(") {
|
||||
return TokenType::OpenParen;
|
||||
}
|
||||
if (token == ")") {
|
||||
return TokenType::CloseParen;
|
||||
}
|
||||
if (token == "\n") {
|
||||
return TokenType::NewLine;
|
||||
}
|
||||
|
||||
// Check if current token is "func" followed by "{"
|
||||
if (token == "func" && peek(1).has_value() && peek(1).value() == "{") {
|
||||
return TokenType::Function;
|
||||
}
|
||||
|
||||
// Check if next token is "(" (function call)
|
||||
if (peek(1).has_value() && peek(1).value() == "(") {
|
||||
return TokenType::FunctionCallStart;
|
||||
}
|
||||
|
||||
// If none of the above, it's an identifier
|
||||
return TokenType::Identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses a block of code and creates an Abstract Syntax Tree (AST) representation.
|
||||
*
|
||||
* This method iterates through the tokens of a given block of code, evaluates the type
|
||||
* of each token, and adds the corresponding node to the AST.
|
||||
*
|
||||
* Supported token types include:
|
||||
* - Values (e.g., strings, booleans, integers, floats)
|
||||
* - Identifiers
|
||||
* - Functions with nested code blocks
|
||||
* - Function calls with arguments
|
||||
* - Nested code blocks
|
||||
*
|
||||
* The method utilizes a `switch` statement based on the `TokenType` to determine the
|
||||
* appropriate handling of each token. Each token is either consumed, parsed, and converted
|
||||
* to an appropriate AST node type or processed for special structures (e.g., functions,
|
||||
* code blocks, or function calls).
|
||||
*
|
||||
* @remarks This method assumes valid tokenized input. If an end-of-file condition
|
||||
* occurs while parsing functions or function calls, the program exits with an error.
|
||||
*/
|
||||
void ASTCodeBlock::parseBlock() {
|
||||
while (true) {
|
||||
std::optional<std::string> token = peek(0);
|
||||
if (token.has_value() == false) {
|
||||
return;
|
||||
}
|
||||
TokenType tokenType = getTokenType();
|
||||
ValueType valueType = ASTValue().getValueType(token.value());
|
||||
std::optional<std::string> currentToken = consume();
|
||||
|
||||
if (currentToken.has_value()) {
|
||||
switch (tokenType) {
|
||||
case TokenType::Value: {
|
||||
switch (valueType) {
|
||||
case ValueType::String:
|
||||
nodes.emplace_back(std::make_shared<ASTValue>(currentToken.value().substr(1, currentToken.value().size() - 2)));
|
||||
break;
|
||||
case ValueType::Bool:
|
||||
nodes.emplace_back(std::make_shared<ASTValue>(currentToken.value() == "true"));
|
||||
break;
|
||||
case ValueType::Int:
|
||||
nodes.emplace_back(std::make_shared<ASTValue>(std::stoll(currentToken.value())));
|
||||
break;
|
||||
case ValueType::Float:
|
||||
nodes.emplace_back(std::make_shared<ASTValue>(std::stod(currentToken.value())));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TokenType::Identifier:
|
||||
nodes.emplace_back(std::make_shared<ASTIdentifier>(currentToken.value()));
|
||||
break;
|
||||
case TokenType::Function: {
|
||||
std::vector<std::string> body;
|
||||
consume();
|
||||
int depth = 1;
|
||||
while (depth > 0) {
|
||||
std::optional<std::string> token = consume();
|
||||
if (token.has_value()) {
|
||||
if (token.value() == "{") {
|
||||
depth++;
|
||||
} else if (token.value() == "}") {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.push_back(token.value());
|
||||
} else {
|
||||
std::cout << "Reached end of file while parsing function" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
consume();
|
||||
nodes.emplace_back(std::make_shared<ASTFunction>(ASTCodeBlock(body)));
|
||||
break;
|
||||
}
|
||||
case TokenType::FunctionCallStart: {
|
||||
std::vector<std::string> args;
|
||||
std::optional<std::string> fnName = peek(-1);
|
||||
std::string fnNameStr;
|
||||
if (fnName.has_value()) {
|
||||
fnNameStr = fnName.value();
|
||||
} else {
|
||||
std::cout << "Reached end of file while parsing function call" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
while (getTokenType() != TokenType::CloseParen) {
|
||||
std::optional<std::string> token = consume();
|
||||
if (token.has_value()) {
|
||||
args.push_back(token.value());
|
||||
} else {
|
||||
std::cout << "Reached end of file while parsing function call" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
consume();
|
||||
nodes.emplace_back(std::make_shared<ASTFunctionCall>(fnNameStr, ASTCodeBlock(args).nodes));
|
||||
break;
|
||||
}
|
||||
case TokenType::CodeBlockStart: {
|
||||
std::vector<std::string> body;
|
||||
int depth = 1;
|
||||
while (depth > 0) {
|
||||
std::optional<std::string> token = consume();
|
||||
if (token.has_value()) {
|
||||
if (token.value() == "{") {
|
||||
depth++;
|
||||
} else if (token.value() == "}") {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.push_back(token.value());
|
||||
} else {
|
||||
std::cout << "Reached end of file while parsing code block" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
nodes.emplace_back(std::make_shared<ASTCodeBlock>(body));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASTCodeBlock::ASTCodeBlock() {}
|
||||
|
||||
ASTCodeBlock::ASTCodeBlock(std::vector<std::string> in) : content(std::move(in)) {
|
||||
parseBlock();
|
||||
}
|
||||
98
src/parser/parser.h
Normal file
98
src/parser/parser.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
class ASTValue;
|
||||
class ASTFunction;
|
||||
class ASTFunctionCall;
|
||||
class ASTCodeBlock;
|
||||
class ASTIdentifier;
|
||||
|
||||
typedef std::variant<std::shared_ptr<ASTValue>, std::shared_ptr<ASTFunction>, std::shared_ptr<ASTFunctionCall>, std::shared_ptr<ASTCodeBlock>, std::shared_ptr<ASTIdentifier>> ASTNode;
|
||||
typedef std::variant<long long, double, std::string, bool> RealValue;
|
||||
|
||||
enum class ValueType {
|
||||
Int, Float, String, Bool, None
|
||||
};
|
||||
|
||||
enum class TokenType {
|
||||
Identifier, Value, Function, FunctionCallStart, OpenParen, CloseParen, CodeBlockStart, CodeBlockEnd, NewLine, None
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ASTValue
|
||||
* @brief Represents a value in the Abstract Syntax Tree (AST).
|
||||
*
|
||||
* The ASTValue class encapsulates different types of values, including integers,
|
||||
* floating-point numbers, strings, and boolean. It provides methods for type identification
|
||||
* and value retrieval.
|
||||
*/
|
||||
class ASTValue {
|
||||
private:
|
||||
RealValue value;
|
||||
public:
|
||||
ValueType type;
|
||||
ValueType getValueType(std::string in);
|
||||
std::optional<std::string> getString();
|
||||
std::optional<int> getInt();
|
||||
std::optional<double> getFloat();
|
||||
std::optional<bool> getBool();
|
||||
explicit ASTValue(std::string in);
|
||||
explicit ASTValue(long long in);
|
||||
explicit ASTValue(double in);
|
||||
explicit ASTValue(bool in);
|
||||
ASTValue();
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ASTCodeBlock
|
||||
* @brief Represents a block of code in the Abstract Syntax Tree (AST).
|
||||
*
|
||||
* The ASTCodeBlock class is responsible for encapsulating and parsing a block
|
||||
* of code represented as a sequence of strings. It maintains the raw content
|
||||
* of the block, processes its elements, and classifies tokens to construct a
|
||||
* structured representation. The parsed elements are stored as AST nodes.
|
||||
*
|
||||
* Functions provided include utilities for token parsing, peeking into
|
||||
* upcoming tokens, identifying token types, and managing the iterator for
|
||||
* sequential token processing.
|
||||
*/
|
||||
class ASTCodeBlock {
|
||||
private:
|
||||
std::vector<std::string> content;
|
||||
size_t iterator = 0;
|
||||
void parseBlock();
|
||||
std::optional<std::string> consume();
|
||||
std::optional<std::string> peek(int ahead = 1);
|
||||
TokenType getTokenType();
|
||||
public:
|
||||
std::vector<ASTNode> nodes;
|
||||
explicit ASTCodeBlock(std::vector<std::string>);
|
||||
ASTCodeBlock();
|
||||
};
|
||||
|
||||
class ASTFunction {
|
||||
public:
|
||||
ASTCodeBlock body;
|
||||
explicit ASTFunction(ASTCodeBlock body);
|
||||
ASTFunction();
|
||||
};
|
||||
|
||||
class ASTFunctionCall {
|
||||
public:
|
||||
std::string func;
|
||||
std::vector<ASTNode> args;
|
||||
ASTFunctionCall(std::string func, std::vector<ASTNode> args);
|
||||
};
|
||||
|
||||
class ASTIdentifier {
|
||||
public:
|
||||
std::string name;
|
||||
explicit ASTIdentifier(std::string in);
|
||||
};
|
||||
|
||||
ASTNode parser(std::vector<std::string> 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 = {});
|
||||
};
|
||||
|
||||
19
tests/test.funk
Normal file
19
tests/test.funk
Normal file
@@ -0,0 +1,19 @@
|
||||
import("io")
|
||||
|
||||
dingus func {
|
||||
println("you got dingused")
|
||||
if (true) {
|
||||
println("yay")
|
||||
}
|
||||
println(321)
|
||||
}
|
||||
|
||||
main func {
|
||||
dingus()
|
||||
}
|
||||
|
||||
infinity func {
|
||||
while (true) {
|
||||
println("to infinity and beyond!")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user