diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b8af6b3 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index e524d79..096eb1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode/ build/ +.idea diff --git a/Makefile b/Makefile index 51b581f..fe63122 100644 --- a/Makefile +++ b/Makefile @@ -7,22 +7,26 @@ CC = g++ CFLAGS = -Wall -Wextra -std=c++11 # Source directory -SRC_DIR = ./src +SRC_DIR = ./source # Output directory BUILD_DIR = ./build -# Get all CPP files in the source directory -CPP_FILES := $(wildcard $(SRC_DIR)/*.cpp) +# Find all C++ files recursively in the source directory +CPP_FILES := $(shell find $(SRC_DIR) -type f -name '*.cpp') # Generate object file names by replacing the source directory with the build directory OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES)) +# Create directories for object files +$(shell mkdir -p $(dir $(OBJ_FILES))) + # Default target all: $(BUILD_DIR)/bob -# Rule to create the build directory if it doesn't exist -$(shell mkdir -p $(BUILD_DIR)) +# Rule to create necessary directories +$(DIRS): + mkdir -p $(patsubst $(SRC_DIR)/%, $(OUTPUT_DIR)/%, $@) # Rule to compile object files $(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp diff --git a/headers/Expression.h b/headers/Expression.h new file mode 100644 index 0000000..ef21c20 --- /dev/null +++ b/headers/Expression.h @@ -0,0 +1,49 @@ +// +// Created by Bobby Lucero on 5/21/23. +// + +#pragma once +#include "Lexer.h" +#include +struct Expr{ + virtual ~Expr() + { + + } +}; + +struct BinaryExpr : Expr +{ + const Expr left; + const Token oper; + const Expr right; + + BinaryExpr(Expr left, Token oper, Expr right) : left(left), oper(oper), right(right) + { + } +}; +struct GroupingExpr : Expr +{ + const Expr expression; + + GroupingExpr(Expr expression) : expression(expression) + { + } +}; +struct LiteralExpr : Expr +{ + const std::string value; + + LiteralExpr(std::string value) : value(value) + { + } +}; +struct UnaryExpr : Expr +{ + const Token oper; + const Expr right; + + UnaryExpr(Token oper, Expr right) : oper(oper), right(right) + { + } +}; diff --git a/headers/Lexer.h b/headers/Lexer.h index 770baa4..290c6e0 100644 --- a/headers/Lexer.h +++ b/headers/Lexer.h @@ -24,7 +24,21 @@ enum TokenType{ }; const std::map KEYWORDS { - + {"and", AND}, + {"or", OR}, + {"true", TRUE}, + {"false", FALSE}, + {"if", IF}, + {"else", ELSE}, + {"func", FUNCTION}, + {"for", FOR}, + {"while", WHILE}, + {"var", VAR}, + {"class", CLASS}, + {"super", SUPER}, + {"this", THIS}, + {"none", NONE}, + {"return", RETURN} }; struct Token @@ -41,6 +55,8 @@ public: std::vector Tokenize(std::string source); private: int line; + std::vector src; private: - bool matchOn(char expected, std::vector& src); + bool matchOn(char expected); + void advance(); }; diff --git a/headers/bob.h b/headers/bob.h new file mode 100644 index 0000000..2d9cb3d --- /dev/null +++ b/headers/bob.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include "../headers/Lexer.h" + +#define VERSION "0.0.1" + +class Bob +{ +public: + Lexer lexer; + +public: + void runFile(std::string path); + + void runPrompt(); + + void error(int line, std::string message); + + +private: + bool hadError = false; + +private: + void run(std::string source); + + void report(int line, std::string where, std::string message); +}; + diff --git a/headers/helperFunctions/HelperFunctions.h b/headers/helperFunctions/HelperFunctions.h new file mode 100644 index 0000000..e0aae20 --- /dev/null +++ b/headers/helperFunctions/HelperFunctions.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +std::vector splitString(const std::string& input, std::string delimiter) { + std::vector tokens; + std::string token; + size_t start = 0; + size_t end = input.find(delimiter); + + while (end != std::string::npos) { + token = input.substr(start, end - start); + tokens.push_back(token); + start = end + 1; + end = input.find(delimiter, start); + } + + // Add the last token (after the last delimiter) + token = input.substr(start, end); + tokens.push_back(token); + + return tokens; +} + +std::string trim(const std::string& str) { + // Find the first non-whitespace character + size_t start = str.find_first_not_of(" \t\n\r"); + + // If the string is all whitespace, return an empty string + if (start == std::string::npos) { + return ""; + } + + // Find the last non-whitespace character + size_t end = str.find_last_not_of(" \t\n\r"); + + // Extract the trimmed substring + return str.substr(start, end - start + 1); +} \ No newline at end of file diff --git a/source.bob b/source.bob index 76a517e..1e968a8 100644 --- a/source.bob +++ b/source.bob @@ -2,7 +2,15 @@ bob.test 10 11.1 -test = (11 + 2 "xs") +test = (11 + 2 "xs +hello +end") -//" -//11. \ No newline at end of file +// +//11. + +12//11 +11. +11.69 + 66.735293857293875 + 235982735987235.0 + 1 + +123a \ No newline at end of file diff --git a/source/.DS_Store b/source/.DS_Store new file mode 100644 index 0000000..cf37e4e Binary files /dev/null and b/source/.DS_Store differ diff --git a/source/Expression.cpp b/source/Expression.cpp new file mode 100644 index 0000000..c07d5b6 --- /dev/null +++ b/source/Expression.cpp @@ -0,0 +1,5 @@ +// +// Created by Bobby Lucero on 5/21/23. +// + +#include "../headers/Expression.h" diff --git a/src/Lexer.cpp b/source/Lexer.cpp similarity index 57% rename from src/Lexer.cpp rename to source/Lexer.cpp index 87301e6..cb52a9d 100644 --- a/src/Lexer.cpp +++ b/source/Lexer.cpp @@ -1,135 +1,142 @@ #include "../headers/Lexer.h" -#include #include - using namespace std; std::vector Lexer::Tokenize(std::string source){ std::vector tokens; - std::vector src{source.begin(), source.end()}; + src = std::vector{source.begin(), source.end()}; + line = 0; - while(src.size() > 0) + while(!src.empty()) { char t = src[0]; if(t == '(') { - tokens.push_back(Token{OPEN_PAREN, std::string(1, t)}); //brace initialization in case you forget - src.erase(src.begin()); + tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line}); //brace initialization in case you forget + advance(); } else if(t == ')') { - tokens.push_back(Token{CLOSE_PAREN, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line}); + advance(); } else if(t == '{') { - tokens.push_back(Token{OPEN_BRACE, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line}); + advance(); } else if(t == '}') { - tokens.push_back(Token{CLOSE_BRACE, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line}); + advance(); } else if(t == ',') { - tokens.push_back(Token{COMMA, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{COMMA, std::string(1, t), line}); + advance(); } else if(t == '.') { - tokens.push_back(Token{DOT, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{DOT, std::string(1, t), line}); + advance(); } else if(t == ';') { - tokens.push_back(Token{SEMICOLON, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{SEMICOLON, std::string(1, t), line}); + advance(); } else if(t == '+') { - tokens.push_back(Token{PLUS, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{PLUS, std::string(1, t), line}); + advance(); } else if(t == '-') { - tokens.push_back(Token{MINUS, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{MINUS, std::string(1, t), line}); + advance(); } - else if(t == '*') + else if(t == '*') { - tokens.push_back(Token{STAR, std::string(1, t)}); - src.erase(src.begin()); + tokens.push_back(Token{STAR, std::string(1, t), line}); + advance(); } else if(t == '=') { std::string token = std::string(1, t); - src.erase(src.begin()); - bool match = matchOn('=', src); + advance(); + bool match = matchOn('='); token += match ? "=" : ""; - tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token}); + tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token, line}); } else if(t == '!') { std::string token = std::string(1, t); - src.erase(src.begin()); - bool match = matchOn('=', src); + advance(); + bool match = matchOn('='); token += match ? "=" : ""; - tokens.push_back(Token{match ? BANG_EQUAL : BANG, token}); + tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line}); } else if(t == '<') { std::string token = std::string(1, t); - src.erase(src.begin()); - bool match = matchOn('=', src); + advance(); + bool match = matchOn('='); token += match ? "=" : ""; - tokens.push_back(Token{match ? LESS_EQUAL : LESS, token}); + tokens.push_back(Token{match ? LESS_EQUAL : LESS, token, line}); } else if(t == '>') { std::string token = std::string(1, t); - src.erase(src.begin()); - bool match = matchOn('=', src); + advance(); + bool match = matchOn('='); token += match ? "=" : ""; - tokens.push_back(Token{match ? GREATER_EQUAL : GREATER, token}); + tokens.push_back(Token{match ? GREATER_EQUAL : GREATER, token, line}); + } + else if(t == '&') + { + std::string token = std::string(1, t); + advance(); + bool match = matchOn('&'); + token += match ? "&" : ""; + if(match) tokens.push_back(Token{AND, token, line}); } else if(t == '/') { std::string token = std::string(1, t); - src.erase(src.begin()); - bool match = matchOn('/', src); + advance(); + bool match = matchOn('/'); if(match) { - while(src.size() > 0 && src[0] != '\n') + while(!src.empty() && src[0] != '\n') { - src.erase(src.begin()); + advance(); } } else { - tokens.push_back(Token{SLASH, std::string(1, t)}); + tokens.push_back(Token{SLASH, std::string(1, t), line}); } } else if(t == '"') { std::string str = std::string(1, src[0]); - src.erase(src.begin()); - while(src.size() > 0 && src[0] != '"') + advance(); + while(!src.empty() && src[0] != '"') { if(src[0] == '\n') line++; str += src[0]; - src.erase(src.begin()); + advance(); } - if(src.size() == 0) + if(src.empty()) { throw std::runtime_error("Unterminated string at line: " + std::to_string(this->line)); } else if(src[0] == '"') { str += '"'; - src.erase(src.begin()); - tokens.push_back(Token{STRING, str}); + advance(); + tokens.push_back(Token{STRING, str, line}); } @@ -137,30 +144,30 @@ std::vector Lexer::Tokenize(std::string source){ else if(t == '\n') { line++; - src.erase(src.begin()); + advance(); } else { //Multi char tokens if(std::isdigit(t)) { - std::string num = ""; - while(src.size() > 0 && std::isdigit(src[0])) + std::string num; + while(!src.empty() && std::isdigit(src[0])) { num += src[0]; - src.erase(src.begin()); + advance(); } - if(src.size() > 0 && src[0] == '.') + if(!src.empty() && src[0] == '.') { - src.erase(src.begin()); - if(src.size() > 0 && std::isdigit(src[0])) + advance(); + if(!src.empty() && std::isdigit(src[0])) { num += '.'; - while(src.size() > 0 && std::isdigit(src[0])) + while(!src.empty() && std::isdigit(src[0])) { num += src[0]; - src.erase(src.begin()); + advance(); } } else @@ -170,30 +177,30 @@ std::vector Lexer::Tokenize(std::string source){ } - tokens.push_back(Token{NUMBER, num}); + tokens.push_back(Token{NUMBER, num, line}); } else if(std::isalpha(t)) { - std::string ident = ""; - while(src.size() > 0 && std::isalpha(src[0])) + std::string ident; + while(!src.empty() && std::isalpha(src[0])) { ident += src[0]; - src.erase(src.begin()); + advance(); } if(KEYWORDS.find(ident) != KEYWORDS.end()) //identifier is a keyword { - tokens.push_back(Token{KEYWORDS.at(ident), ident}); + tokens.push_back(Token{KEYWORDS.at(ident), ident, line}); } else { - tokens.push_back(Token{IDENTIFIER, ident}); + tokens.push_back(Token{IDENTIFIER, ident, line}); } } - else if(t == ' ' || t == '\t' || t == '\n') + else if(t == ' ' || t == '\t') { - src.erase(src.begin()); //ignore t + advance(); } else { @@ -209,10 +216,15 @@ std::vector Lexer::Tokenize(std::string source){ return tokens; } -bool Lexer::matchOn(char expected, std::vector &src) +bool Lexer::matchOn(char expected) { - if(src.size() == 0) return false; + if(src.empty()) return false; if(src[0] != expected) return false; - src.erase(src.begin()); + advance(); return true; } + +void Lexer::advance() +{ + src.erase(src.begin()); +} diff --git a/source/bob.cpp b/source/bob.cpp new file mode 100644 index 0000000..52f7c1c --- /dev/null +++ b/source/bob.cpp @@ -0,0 +1,60 @@ +#include "../headers/bob.h" +using namespace std; + +void Bob::runFile(string path) +{ + ifstream file = ifstream(path); + + string source = ""; + + if(file.is_open()){ + source = string(istreambuf_iterator(file), istreambuf_iterator()); + } + else + { + cout << "File not found" << endl; + return; + } + + this->run(source); +} + +void Bob::runPrompt() +{ + cout << "Bob v" << VERSION << ", 2023" << endl; + for(;;) + { + string line; + cout << "-> "; + std::getline(std::cin, line); + + if(std::cin.eof()) + { + break; + } + + this->run(line); + hadError = false; + } +} + +void Bob::error(int line, string message) +{ + +} + +void Bob::run(string source) +{ + vector tokens = lexer.Tokenize(source); + + for(Token t : tokens){ + cout << "{type: " << t.type << ", value: " << t.lexeme << "}" << endl; + } +} + +void Bob::report(int line, string where, string message) +{ + hadError = true; +} + + diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..d49ca38 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,37 @@ +// +// Created by Bobby Lucero on 5/21/23. +// +#include "../headers/bob.h" +#include "../headers/Expression.h" +#include "../headers/Lexer.h" +int main(){ + + Bob bobLang; + + //bobLang.runFile("source.bob"); + + + Expr a; + Expr b; + Token t = {PLUS, "+", 1}; + Token t2 = {MINUS, "-", 1}; + BinaryExpr e = BinaryExpr(a, t, b); + + std::shared_ptr any = std::make_shared(a, t, b); + if(std::shared_ptr binexpr = std::dynamic_pointer_cast(any)) + { + std::cout << binexpr->oper.lexeme; + } + + any = std::make_shared(a, t2, b); + if(std::shared_ptr binexpr = std::dynamic_pointer_cast(any)) + { + std::cout << binexpr->oper.lexeme; + } + + std::cout << std::endl; + + bobLang.runPrompt(); + + return 0; +} diff --git a/src/test.cpp b/source/test.cpp similarity index 100% rename from src/test.cpp rename to source/test.cpp diff --git a/src/bob.cpp b/src/bob.cpp deleted file mode 100644 index 5d9dbeb..0000000 --- a/src/bob.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include -#include "../headers/Lexer.h" - -#define VERSION "0.0.1" - -using namespace std; - -class Bob -{ -public: - Lexer lexer; - -public: - void runFile(string path) - { - ifstream file = ifstream(path); - - string source = ""; - - if(file.is_open()){ - source = string(istreambuf_iterator(file), istreambuf_iterator()); - } - else - { - cout << "File not found" << endl; - return; - } - - this->run(source); - } - - void runPrompt() - { - cout << "Bob v" << VERSION << ", 2023" << endl; - for(;;) - { - string line; - cout << "-> "; - std::getline(std::cin, line); - - if(std::cin.eof()) - { - break; - } - - this->run(line); - hadError = false; - } - } - - void error(int line, string message) - { - - } - - -private: - bool hadError = false; - -private: - void run(string source) - { - vector tokens = lexer.Tokenize(source); - - for(Token t : tokens){ - cout << "{type: " << t.type << ", value: " << t.lexeme << "}" << endl; - } - } - - void report(int line, string where, string message) - { - hadError = true; - } -}; -int main(){ - - // string TokenTypeMappings[] = { - // "Identifier", - // "Number", - // "Equals", - // "OpenParen", - // "CloseParen", - // "BinaryOperator", - // "TestKeyword" - // }; - - Bob bobLang; - - bobLang.runFile("source.bob"); - //bobLang.runPrompt(); - - - return 0; -} diff --git a/testthing b/testthing new file mode 100644 index 0000000..c250188 --- /dev/null +++ b/testthing @@ -0,0 +1,35 @@ +struct BinaryExpr : Expr +{ + const Expr left; + const Token oper; + const Expr right; + + BinaryExpr(Expr left, Token oper, Expr right) : left(left), oper(oper), right(right) + { + } +}; +struct GroupingExpr : Expr +{ + const Expr expression; + + GroupingExpr(Expr expression) : expression(expression) + { + } +}; +struct LiteralExpr : Expr +{ + const std::string value; + + LiteralExpr(std::string value) : value(value) + { + } +}; +struct UnaryExpr : Expr +{ + const Token oper; + const Expr right; + + UnaryExpr(Token oper, Expr right) : oper(oper), right(right) + { + } +}; diff --git a/tools/GenerateAST b/tools/GenerateAST new file mode 100755 index 0000000..20ee1ae Binary files /dev/null and b/tools/GenerateAST differ diff --git a/tools/GenerateAST.cpp b/tools/GenerateAST.cpp new file mode 100644 index 0000000..ae78bb9 --- /dev/null +++ b/tools/GenerateAST.cpp @@ -0,0 +1,72 @@ +// +// Created by Bobby Lucero on 5/21/23. +// +#include +#include +#include +#include "../headers/helperFunctions/HelperFunctions.h" + +void defineType(std::ofstream &out, std::string baseName, std::string className, std::string fieldList) +{ + out << "struct " << className << "Expr : " << baseName << "\n{\n"; + std::vector fields = splitString(fieldList, ", "); + for(std::string field : fields) + { + out << " const " << trim(field) << ";\n"; + } + + out << "\n " << className << "Expr(" << fieldList << ") : "; + + std::string explicitDeclaration; + for(std::string field : fields) + { + std::string name = splitString(trim(field), " ")[1]; + + explicitDeclaration += trim(name) + "(" + trim(name) + "), "; + } + explicitDeclaration = trim(explicitDeclaration); + explicitDeclaration.pop_back(); + + out << explicitDeclaration; + + out << "\n {\n"; + out << " }\n"; + out << "};" << std::endl; +} + +void defineAst(std::string outputDir, std::string baseName, const std::vector& types) +{ + std::ofstream outFile(outputDir); + + if(outFile.is_open()) + { + for(std::string type : types) + { + std::vector type_split = splitString(type, "$"); + std::string className = trim(type_split[0]); + std::string fields = trim(type_split[1]); + defineType(outFile, baseName, className, fields); + } + } +} + + + +int main(int argc, char* argv[]){ + if(argc != 2) + { + std::cerr << "Usage " << argv[0] << " " << std::endl; + std::exit(64); + } + std::string outputDir = argv[1]; + + defineAst(outputDir, "Expr", { + "Binary $ Expr left, Token oper, Expr right", + "Grouping $ Expr expression", + "Literal $ std::string value", + "Unary $ Token oper, Expr right" + }); +} + + +