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