diff --git a/.clang-format b/.clang-format index a8785e8..9560a2e 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,8 @@ --- -BasedOnStyle: Chromium AlignAfterOpenBracket: AlwaysBreak +AllowShortBlocksOnASingleLine: "Always" +AllowShortIfStatementsOnASingleLine: WithoutElse +BasedOnStyle: Chromium ColumnLimit: '100' ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' Cpp11BracedListStyle: 'true' @@ -38,6 +40,3 @@ SpaceBeforeCpp11BracedList: 'true' SpacesInContainerLiterals: 'true' Standard: Cpp11 TabWidth: '4' -AllowShortIfStatementsOnASingleLine: "WithoutElse" -AllowShortLoopsOnASingleLine: "true" -AllowShortBlocksOnASingleLine: "Always" diff --git a/.clangd b/.clangd index f63436f..32c4c6c 100644 --- a/.clangd +++ b/.clangd @@ -1,3 +1,2 @@ CompileFlags: Add: [-std=c++23, -Wall] - Compiler: /usr/bin/clang++ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98522dd..22a0cb4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.7 + rev: v21.1.8 hooks: - id: clang-format - repo: https://github.com/cmake-lint/cmake-lint @@ -18,10 +18,14 @@ repos: hooks: - id: cmakelint - repo: https://github.com/rhysd/actionlint - rev: v1.7.9 + rev: v1.7.10 hooks: - id: actionlint - repo: https://github.com/sco1/brie-commit rev: v1.1.0 hooks: - id: brie-commit + - repo: https://github.com/GideonBear/pre-comet + rev: v1.0.0 + hooks: + - id: pre-comet diff --git a/CMakeLists.txt b/CMakeLists.txt index d3d7b5a..9a2c307 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,4 @@ cmake_minimum_required(VERSION 3.30) -#use(CMAKE_CXX_COMPILER "/usr/bin/clang++") project(winter LANGUAGES CXX) option(RUN_TESTS "Run unit tests" OFF) @@ -12,18 +11,10 @@ add_compile_options(-Wall) add_compile_options(-Wextra) add_compile_options(-Wconversion) add_compile_options(-Wimplicit-fallthrough) -add_compile_options(-Wfatal-errors) -add_compile_options(-fsanitize=address) -add_link_options(-fsanitize=address) -# repl -add_subdirectory(src/winter) +add_subdirectory(src) add_executable(winter src/main.cpp) -target_link_libraries(winter PUBLIC winter-lang) - -# public-interface -add_executable(public src/main2.cpp) -target_link_libraries(winter PUBLIC winter-lang) +target_link_libraries(winter PUBLIC winter_src) if(RUN_TESTS) message(STATUS "UNIT TESTS ENABLED") diff --git a/milestones.md b/milestones.md deleted file mode 100644 index f8ff63b..0000000 --- a/milestones.md +++ /dev/null @@ -1,33 +0,0 @@ -] Milestones - -* [ ] Initial setup - * [ ] Lexer - * [ ] Parser - * [ ] Bytecode generation - * [ ] Executor - * [ ] Public interface for embedding - * [ ] REPL -* [ ] Return code for complete execution of a single program -* [ ] Function calls in the same file -* [ ] Simple maths - * [ ] plus, minus, mul, divide - * [ ] modulo ( % operator ) -* [ ] builtins - * [ ] print -* [ ] Hello world -* [ ] Variables -* [ ] If -* [ ] switch cases -* [ ] basic for loops (start, stop, step) -* [ ] functions - pass parameters -* [ ] functions - return values - * [ ] Fibonacci - * [ ] Fizzbuzz -* [ ] range-based for loops? -* [ ] Basic types - double, string, char, bool, nil - - To investigate, does a function need to be a type? - - Do we need to have separate lambdas? (I don't want to) -* [ ] Basic classes -* [ ] basic Methods -* [ ] inheritance -* [ ] importing files diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..15d6ede --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,5 @@ +include_directories(${PROJECT_SOURCE_DIR}/src) + +add_library(winter_src STATIC + t.cpp +) diff --git a/src/main.cpp b/src/main.cpp index d7a4f97..d273ea1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,41 +3,36 @@ #include #include -#include "winter/winter.h" +#include "winter.h" using namespace std::string_view_literals; -void startRepl() {} +void usage() {} int main(int argc, char* argv[]) { - const auto args = std::span(argv, argc); + bool enable_debug; + std::string filename = ""; + const auto args = std::span(argv, argc); if (args.size() == 1) { - startRepl(); - return 0; + usage(); + return 1; } - bool enable_debug; - std::string filename = ""; - for (auto&& elem : args) { std::string_view str = std::string_view(elem); if (str == "-D"sv) { enable_debug = true; } - if (str.ends_with(".wt"sv)) { filename = std::string(elem); } + if (str.ends_with(".wtx"sv)) { filename = std::string(elem); } } if (filename.empty()) { std::println("Error: No file provided"); + usage(); return 1; } - auto vm = Winter::VM(enable_debug); - Winter::retcode_t ret = vm.doFile(filename); - if (!ret.has_value()) { - std::println("ERROR: {}", ret.error().msg); - return 0; - }; + // TODO: here goes the lexing/parsing etc - return ret.value(); + return 0; } diff --git a/src/main2.cpp b/src/main2.cpp deleted file mode 100644 index d7a3bb8..0000000 --- a/src/main2.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include - -#include "winter/winter.h" - -int main() { - auto vm = Winter::VM(false); - - vm.push(Winter::Object(Winter::ObjType::Null, 5)); - vm.push(Winter::Object(Winter::ObjType::Null, 3)); - - vm.registerFunc("add", [](Winter::VM& vm) { - auto b = vm.pop()->unwrap(); - auto a = vm.pop()->unwrap(); - vm.push(Winter::Object(Winter::ObjType::Null, a + b)); - return 0; - }); - - Winter::retcode_t ret = vm.call("add"); - assert(ret.has_value()); - - const int value = vm.pop()->unwrap(); - assert(value == 8); - - std::println("Result: {}", value); - return 0; -} diff --git a/src/t.cpp b/src/t.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/winter.h b/src/winter.h new file mode 100644 index 0000000..e69de29 diff --git a/src/winter/CMakeLists.txt b/src/winter/CMakeLists.txt deleted file mode 100644 index 87e770f..0000000 --- a/src/winter/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_library(winter-lang STATIC) -set_target_properties(winter-lang PROPERTIES LINKER_LANGUAGE CXX) - -target_sources(winter-lang PUBLIC - bytecode.cpp - helpers.cpp - lexer.cpp - parser.cpp -) - -target_include_directories(winter-lang PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/src/winter/ast_nodes.h b/src/winter/ast_nodes.h deleted file mode 100644 index a606763..0000000 --- a/src/winter/ast_nodes.h +++ /dev/null @@ -1,145 +0,0 @@ -#ifndef WINTER_AST_NODES_H -#define WINTER_AST_NODES_H - -#include -#include -#include -#include -#include - -#include "lexer.h" - -// helper for std::visit -template -struct overloads : Ts... { - using Ts::operator()...; -}; - -namespace Winter { - using binding_t = std::size_t; - - enum class NodeType { BlockNode, ExprNode, FuncNode, RootNode, ReturnNode, ValueNode }; - - class ASTNode { - public: - virtual std::string display(const std::size_t offset) const = 0; - virtual std::string getNodeName() const = 0; - virtual NodeType getNodeType() const = 0; - virtual ~ASTNode() = default; - }; - - class BlockNode : public ASTNode { - public: - std::vector> stmts = {}; - - [[nodiscard]] inline std::string display(const std::size_t offset) const override { - std::string out = std::string(offset, ' ') + "BlockNode: {\n"; - for (auto&& stmt : stmts) { out += stmt->display(offset + 2); } - - out += std::string(offset, ' ') + "}\n"; - return out; - } - - [[nodiscard]] inline std::string getNodeName() const override { return "BlockNode"; } - [[nodiscard]] inline NodeType getNodeType() const override { return NodeType::BlockNode; } - }; - - class ExprNode : public ASTNode { - public: - const Token* op; - std::unique_ptr lhs = nullptr; - std::unique_ptr rhs = nullptr; - [[nodiscard]] inline std::string display(const std::size_t offset) const override { - std::string out = std::string(offset, ' ') + "ExprNode { \n"; - if (lhs != nullptr) { out += lhs->display(offset + 2) + "\n"; } - if (op != nullptr) { out += op->toString() + "\n"; } - if (rhs != nullptr) { out += rhs->display(offset + 2) + "\n"; } - - out += std::string(offset, ' ') + "}"; - return out; - } - - [[nodiscard]] inline std::string getNodeName() const override { return "ExprNode"; } - [[nodiscard]] inline NodeType getNodeType() const override { return NodeType::ExprNode; } - }; - - class FuncNode : public ASTNode { - public: - std::string name; - std::vector params; - std::unique_ptr body; - - explicit FuncNode() {} - [[nodiscard]] inline std::string display(const std::size_t offset) const override { - std::string out = std::string(offset, ' ') + "FuncNode {\n"; - out += std::string(offset + 2, ' ') + "Name: " + name + "\n"; - for (auto&& tok : params) { - out += std::string(offset + 2, ' ') + tok.toString() + "\n"; - } - - out += body->display(offset + 2); - out += std::string(offset, ' ') + "}"; - return out; - } - - [[nodiscard]] inline std::string getNodeName() const override { return "FuncNode"; } - [[nodiscard]] inline NodeType getNodeType() const override { return NodeType::FuncNode; } - }; - - class RootNode : public ASTNode { - public: - std::vector> children = {}; - - explicit RootNode() {} - [[nodiscard]] inline std::string display(const std::size_t offset) const override { - std::string out = std::string(offset, ' ') + "RootNode {\n"; - for (auto&& child : children) { out += child->display(offset + 2) + "\n"; } - out += "}"; - return out; - } - - [[nodiscard]] inline std::string getNodeName() const override { return "RootNode"; } - [[nodiscard]] inline NodeType getNodeType() const override { return NodeType::RootNode; } - }; - - class ReturnNode : public ASTNode { - public: - std::unique_ptr expr; - [[nodiscard]] inline std::string display(const std::size_t offset) const override { - std::string out = std::string(offset, ' ') + "ReturnNode {\n"; - out += expr->display(offset + 2) + "\n"; - out += std::string(offset, ' ') + "}\n"; - return out; - } - - [[nodiscard]] inline std::string getNodeName() const override { return "ReturnNode"; } - [[nodiscard]] inline NodeType getNodeType() const override { return NodeType::ReturnNode; } - }; - - class ValueNode : public ASTNode { - public: - // TODO: Could/should this be std::any? See object.h - std::variant value; - - ValueNode(double v) : value(v) {} - [[nodiscard]] inline std::string display(const std::size_t offset) const override { - std::string out = std::string(offset, ' ') + "ValueNode {"; - std::visit( - overloads { - [&out](double arg) { out += std::to_string(arg); }, - }, - value); - out += "}"; - return out; - } - - [[nodiscard]] inline std::string getNodeName() const override { return "ValueNode"; } - [[nodiscard]] inline NodeType getNodeType() const override { return NodeType::ValueNode; } - }; - - template - using expected_node_t = std::expected, Err>; - -}; // namespace Winter - -#endif // WINTER_AST_NODES_H diff --git a/src/winter/bytecode.cpp b/src/winter/bytecode.cpp deleted file mode 100644 index e596c6b..0000000 --- a/src/winter/bytecode.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "bytecode.h" - -#include - -namespace Winter { - [[nodiscard]] std::expected Generator::compileValue(ValueNode* node) { - Bytecode bc = std::visit( - overloads { - [](double arg) { return Bytecode(Opcode::STORE_CONST, arg); }, - }, - node->value); - - return bc; - } - - [[nodiscard]] std::optional Generator::compileTok(const Token* op) { - if (op == nullptr) { return std::nullopt; } - - switch (op->type) { - case TokenType::MINUS: - return Bytecode(Opcode::SUB); - break; - case TokenType::PLUS: - return Bytecode(Opcode::ADD); - break; - case TokenType::STAR: - return Bytecode(Opcode::MUL); - break; - case TokenType::SLASH: - return Bytecode(Opcode::DIV); - break; - default: - return Bytecode(Opcode::NIL); - } - } - - [[nodiscard]] expected_bytecode_t Generator::compileExpression(ExprNode* node) { - std::vector bytecode = {}; - - auto dispatch = [this, &bytecode](std::unique_ptr inner) -> result_t { - if (inner == nullptr) { return {}; } - - switch (inner->getNodeType()) { - case NodeType::ValueNode: { - ValueNode* val_node = static_cast(inner.get()); - std::expected ret = compileValue(val_node); - if (!ret.has_value()) { return std::unexpected(ret.error()); } - bytecode.push_back(ret.value()); - } break; - default: - return std::unexpected( - Err(ErrType::BytecodeGenerationError, "Incorrect node type")); - } - - return {}; - }; - - result_t elem_bc = dispatch(std::move(node->lhs)); - if (!elem_bc.has_value()) { return std::unexpected(elem_bc.error()); } - - std::optional op = compileTok(node->op); - if (op.has_value()) { - elem_bc = dispatch(std::move(node->rhs)); - if (!elem_bc.has_value()) { return std::unexpected(elem_bc.error()); } - - bytecode.push_back(op.value()); - } - - return bytecode; - } - - [[nodiscard]] expected_bytecode_t Generator::compileReturn(const ReturnNode* node) { - ExprNode* expr = static_cast(node->expr.get()); - expected_bytecode_t expr_bc = compileExpression(expr); - if (!expr_bc.has_value()) { return std::unexpected(expr_bc.error()); } - - expr_bc.value().push_back(Bytecode(Opcode::RET)); - return expr_bc.value(); - } - - [[nodiscard]] expected_chunk_t Generator::compileFunc(const FuncNode* node) { - Chunk chunk = Chunk(node->name); - - for (auto&& elem : node->body->stmts) { - switch (elem->getNodeType()) { - case NodeType::ReturnNode: { - ReturnNode* retNode = static_cast(elem.get()); - expected_bytecode_t bc = compileReturn(retNode); - if (!bc.has_value()) { return std::unexpected(bc.error()); } - chunk.extend(bc.value()); - } break; - } - } - - return chunk; - } - - [[nodiscard]] std::expected Generator::generate() { - Module mod = Module("main"); - - if (ast_node->getNodeType() != NodeType::RootNode) { - return std::unexpected( - Err(ErrType::BytecodeGenerationError, "Node provided not Root node")); - } - - for (auto&& child : ast_node->children) { - switch (child->getNodeType()) { - case (NodeType::FuncNode): - expected_chunk_t func = compileFunc(static_cast(child.get())); - if (!func.has_value()) { return std::unexpected(func.error()); } - mod.chunks.push_back(func.value()); - break; - } - }; - - return mod; - } - -} // namespace Winter diff --git a/src/winter/bytecode.h b/src/winter/bytecode.h deleted file mode 100644 index 9daf505..0000000 --- a/src/winter/bytecode.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef WINTER_BYTECODE_H -#define WINTER_BYTECODE_H - -#include -#include -#include -#include -#include - -#include "ast_nodes.h" -#include "error.h" -#include "lexer.h" - -namespace Winter { - enum class Opcode { - ADD, - DIV, - MUL, - NIL, - RET, - STORE_CONST, // Used to store doubles inline - STORE_STACK, // Used to store any other value in a local stack - SUB, - }; - - struct Bytecode { - Opcode op; - double operand; - - explicit Bytecode(Opcode code) : op(code), operand(0.0) {} - explicit Bytecode(Opcode code, double operand) : op(code), operand(operand) {} - - inline bool operator==(const Bytecode& other) const { - return op == other.op && operand == other.operand; - } - - inline bool operator!=(const Bytecode& other) const { - return op != other.op && operand != other.operand; - } - - [[nodiscard]] inline std::string_view getOpcodeName() const { - switch (op) { - case Opcode::ADD: - return "ADD"; - case Opcode::DIV: - return "DIV"; - case Opcode::MUL: - return "MUL"; - case Opcode::NIL: - return "NIL"; - case Opcode::RET: - return "RET"; - case Opcode::STORE_CONST: - return "STORE_CONST"; - case Opcode::STORE_STACK: - return "STORE_STACK"; - case Opcode::SUB: - return "SUB"; - }; - - std::unreachable(); - } - }; - - struct Chunk { - std::string name; - std::vector instructions = {}; - - explicit Chunk() : name("") {} - explicit Chunk(const std::string& inp) : name(inp) {} - - inline void extend(const std::vector& input) { - instructions.insert(instructions.end(), input.begin(), input.end()); - } - }; - - struct Module { - std::string name; - std::vector chunks = {}; - - explicit Module() : name("") {} - explicit Module(std::string inp) : name(inp) {} - }; - - using expected_bytecode_t = std::expected, Err>; - using expected_chunk_t = std::expected; - - struct Generator { - std::unique_ptr ast_node; - - explicit Generator(std::unique_ptr root) : ast_node(std::move(root)) {} - [[nodiscard]] std::expected compileValue(ValueNode*); - [[nodiscard]] std::optional compileTok(const Token*); - [[nodiscard]] expected_bytecode_t compileExpression(ExprNode*); - [[nodiscard]] expected_bytecode_t compileReturn(const ReturnNode*); - [[nodiscard]] expected_chunk_t compileFunc(const FuncNode*); - [[nodiscard]] std::expected generate(); - }; - -}; // namespace Winter - -template <> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } - auto format(const Winter::Chunk& chunk, std::format_context& ctx) const { - std::string out = "Chunk: " + chunk.name + "\n"; - for (auto&& bc : chunk.instructions) { - out += std::string(4, ' '); - out += bc.getOpcodeName(); - - if (bc.operand != 0.0) { out += ": " + std::to_string(bc.operand); } - out += "\n"; - } - return std::format_to(ctx.out(), "{}", out); - } -}; - -template <> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } - auto format(const Winter::Module& mod, std::format_context& ctx) const { - std::string out = " --- Module --- \n"; - for (auto&& chunk : mod.chunks) { out += std::format("{}\n", chunk); } - return std::format_to(ctx.out(), "{}", out); - } -}; - -#endif // WINTER_BYTECODE_H diff --git a/src/winter/error.h b/src/winter/error.h deleted file mode 100644 index 289b617..0000000 --- a/src/winter/error.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef WINTER_ERROR_H -#define WINTER_ERROR_H - -#include -#include -#include - -namespace Winter { - enum class ErrType { - BytecodeGenerationError, - NameError, - NotImplementedError, - ParsingError, - RuntimeError, - TokenizingError, - }; - - struct Err { - ErrType type; - std::string msg; - - explicit Err(ErrType _type, std::string _msg) : type(_type), msg(_msg) {} - - [[nodiscard]] std::string getErrType() const { - // clang-format off - switch (type) { - case ErrType::BytecodeGenerationError: return "BytecodeGenerationError"; - case ErrType::NameError: return "NameError"; - case ErrType::NotImplementedError: return "NotImplementedError"; - case ErrType::ParsingError: return "ParsingError"; - case ErrType::RuntimeError: return "RuntimeError"; - case ErrType::TokenizingError: return "TokenizingError"; - }; - // clang-format on - - std::unreachable(); - } - - [[nodiscard]] std::string display() const { return getErrType() + ": " + msg; } - - [[nodiscard]] static std::unexpected TODO() { - return std::unexpected(Err(ErrType::NotImplementedError, "TODO")); - } - }; - - using retcode_t = std::expected; - using result_t = std::expected; -} // namespace Winter - -#endif // WINTER_ERROR_H diff --git a/src/winter/helpers.cpp b/src/winter/helpers.cpp deleted file mode 100644 index 106ca8f..0000000 --- a/src/winter/helpers.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "helpers.h" - -#include -#include - -// https://sqlpey.com/c++/cpp-file-to-string-methods/ -[[nodiscard]] std::string openFile(std::string_view fileName) { - std::ifstream ifs(fileName.data()); - std::stringstream buf_stream; - buf_stream << ifs.rdbuf(); - return buf_stream.str(); -} diff --git a/src/winter/helpers.h b/src/winter/helpers.h deleted file mode 100644 index e00b3ff..0000000 --- a/src/winter/helpers.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef WINTER_HELPERS_H -#define WINTER_HELPERS_H - -#include -#include - -[[nodiscard]] std::string openFile(std::string_view); - -#endif // WINTER_HELPERS_H diff --git a/src/winter/lexer.cpp b/src/winter/lexer.cpp deleted file mode 100644 index 9a0c8b7..0000000 --- a/src/winter/lexer.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "lexer.h" - -#include -#include -#include -#include -#include -#include - -#include "error.h" - -namespace Winter { - [[nodiscard]] std::string Token::toString() const { - std::string tok = ""; - - // clang-format off - switch (type) { - case TokenType::LEFT_PAREN: tok = "TokenType::LEFT_PAREN"; break; - case TokenType::RIGHT_PAREN: tok = "TokenType::RIGHT_PAREN"; break; - case TokenType::LEFT_BRACE: tok = "TokenType::LEFT_BRACE"; break; - case TokenType::RIGHT_BRACE: tok = "TokenType::RIGHT_BRACE"; break; - case TokenType::COMMA: tok = "TokenType::COMMA"; break; - case TokenType::DOT: tok = "TokenType::DOT"; break; - case TokenType::SEMICOLON: tok = "TokenType::SEMICOLON"; break; - case TokenType::COLON: tok = "TokenType::COLON"; break; - case TokenType::MINUS: tok = "TokenType::MINUS"; break; - case TokenType::PLUS: tok = "TokenType::PLUS"; break; - case TokenType::STAR: tok = "TokenType::STAR"; break; - case TokenType::SLASH: tok = "TokenType::SLASH"; break; - case TokenType::BANG: tok = "TokenType::BANG"; break; - case TokenType::BANG_EQ: tok = "TokenType::BANG_EQ"; break; - case TokenType::EQUAL: tok = "TokenType::EQUAL"; break; - case TokenType::EQUAL_EQ: tok = "TokenType::EQUAL_EQ"; break; - case TokenType::GREATER: tok = "TokenType::GREATER"; break; - case TokenType::GREATER_EQ: tok = "TokenType::GREATER_EQ"; break; - case TokenType::LESS: tok = "TokenType::LESS"; break; - case TokenType::LESS_EQ: tok = "TokenType::LESS_EQ"; break; - case TokenType::ELLIPSIS: tok = "TokenType::ELLIPSIS"; break; - case TokenType::AND: tok = "TokenType::AND"; break; - case TokenType::OR: tok = "TokenType::OR"; break; - case TokenType::CASE: tok = "TokenType::CASE"; break; - case TokenType::CLASS: tok = "TokenType::CLASS"; break; - case TokenType::CONST: tok = "TokenType::CONST"; break; - case TokenType::DEFAULT: tok = "TokenType::DEFAULT"; break; - case TokenType::ELSE: tok = "TokenType::ELSE"; break; - case TokenType::ENUM: tok = "TokenType::ENUM"; break; - case TokenType::EXPORT: tok = "TokenType::EXPORT"; break; - case TokenType::FALSE: tok = "TokenType::FALSE"; break; - case TokenType::FOR: tok = "TokenType::FOR"; break; - case TokenType::FUNC: tok = "TokenType::FUNC"; break; - case TokenType::IF: tok = "TokenType::IF"; break; - case TokenType::IMPORT: tok = "TokenType::IMPORT"; break; - case TokenType::NIL: tok = "TokenType::NIL"; break; - case TokenType::RETURN: tok = "TokenType::RETURN"; break; - case TokenType::STATIC: tok = "TokenType::STATIC"; break; - case TokenType::SWITCH: tok = "TokenType::SWITCH"; break; - case TokenType::TRUE: tok = "TokenType::TRUE"; break; - case TokenType::VAR: tok = "TokenType::VAR"; break; - case TokenType::IDENT: tok = "TokenType::IDENT"; break; - case TokenType::STRING: tok = "TokenType::STRING"; break; - case TokenType::CHAR: tok = "TokenType::CHAR"; break; - case TokenType::NUMBER: tok = "TokenType::NUMBER"; break; - case TokenType::END: tok = "TokenType::END"; break; - } - - // clang-format on - return std::format("{:<22} Start: {}, Len: {}", tok, start, len); - } - - void Lexer::makeToken(TokenType type, long start, std::size_t len) { - tokens.emplace_back(type, start, len); - } - - [[nodiscard]] std::size_t Lexer::scanNumber(std::size_t start) { - auto sv = std::string_view(raw_text.begin() + start, raw_text.end()); - std::size_t pos = 0; - std::string buf = ""; - - while (std::isdigit(sv.at(pos))) { - pos++; - if (pos == sv.size()) { break; } - } - makeToken(TokenType::NUMBER, start, pos); - return pos; - } - - [[nodiscard]] std::size_t Lexer::scanStringLiteral(std::size_t start) { - auto sv = std::string_view(raw_text.begin() + start, raw_text.end()); - - // starting at +1 because 0 should be opening quote - std::size_t pos = 1; - std::string buf = ""; - - while (sv.at(pos) != '"') { pos++; } - pos++; // closing quote - makeToken(TokenType::STRING, start, pos); - return pos; - } - - [[nodiscard]] std::size_t Lexer::scanChar(std::size_t start) { - auto sv = std::string_view(raw_text.begin() + start, raw_text.end()); - assert(sv.at(2) == '\''); // We have an opening/closing pair of single quotes - - makeToken(TokenType::CHAR, start, 3); - return 3; - } - - [[nodiscard]] bool Lexer::validIdentChar(char c) const { - if (c == '_') { return true; } - if (std::isalnum(c)) { return true; } - return false; - } - - [[nodiscard]] std::optional Lexer::scanKeyword( - std::string_view sv, - std::size_t start) { - for (auto&& pair : keyword_regex) { - if (std::regex_search(sv.begin(), sv.end(), pair.regex)) { - makeToken(pair.type, start, pair.length); - return pair.length; - } - } - - return {}; - } - - [[nodiscard]] std::size_t Lexer::scanIdentifier(std::size_t start) { - auto sv = std::string_view(raw_text.begin() + start, raw_text.end()); - if (sv.empty()) { return 0; } - - auto kw_ret = scanKeyword(sv, start); - if (kw_ret.has_value()) { return kw_ret.value(); } - - std::size_t pos = 0; - while (validIdentChar(sv.at(pos))) { - pos++; - if (pos == sv.size()) { break; } - } - - if (!pos) { return 0; } - makeToken(TokenType::IDENT, start, pos); - return pos; - } - - [[nodiscard]] std::size_t Lexer::scanEllipsis(std::size_t start) { - auto sv = std::string_view(raw_text.begin() + start, raw_text.end()); - if (sv.empty()) { - // error? - return 0; - } - - assert(sv.at(0) == '.'); - if (sv.size() < 3) { - makeToken(TokenType::DOT, start, 1); - return 1; - } - - if (sv.at(1) == '.' && sv.at(2) == '.') { - makeToken(TokenType::ELLIPSIS, start, 3); - return 3; - } - - return 0; - } - - [[nodiscard]] std::expected Lexer::tokenize() { - std::size_t advance_count = 0; - - for (const auto&& [idx, c] : std::views::enumerate(raw_text)) { - if (advance_count) { - advance_count--; - continue; - } - - if (std::isspace(c)) { continue; } - - // clang-format off - switch (c) { - case '(': makeToken(TokenType::LEFT_PAREN, idx, 1); break; - case ')': makeToken(TokenType::RIGHT_PAREN, idx, 1); break; - case '{': makeToken(TokenType::LEFT_BRACE, idx, 1); break; - case '}': makeToken(TokenType::RIGHT_BRACE, idx, 1); break; - case ',': makeToken(TokenType::COMMA, idx, 1); break; - case '.': advance_count = scanEllipsis(idx); break; - case ';': makeToken(TokenType::SEMICOLON, idx, 1); break; - case ':': makeToken(TokenType::COLON, idx, 1); break; - case '-': makeToken(TokenType::MINUS, idx, 1); break; - case '+': makeToken(TokenType::PLUS, idx, 1); break; - case '*': makeToken(TokenType::STAR, idx, 1); break; - case '/': makeToken(TokenType::SLASH, idx, 1); break; - case '\'': advance_count = scanChar(idx); break; - case '"': advance_count = scanStringLiteral(idx); break; - case '=': { - if (std::string("!=<>").contains(raw_text.at(idx - 1))) { - switch (raw_text.at(idx - 1)) { - case '!': - makeToken(TokenType::BANG_EQ, idx - 1, 2); - break; - case '=': - makeToken(TokenType::EQUAL_EQ, idx - 1, 2); - break; - // case '<': makeToken(TokenType::LESS_EQ, idx - 1, 2); break; - // case '>': makeToken(TokenType::GREATER_EQ, idx - 1, 2); break; - } - } else if (raw_text.at(idx + 1) == '=') { - break; - } else { - makeToken(TokenType::EQUAL, idx, 1); - break; - } - - } break; - - case '&': { - if (raw_text.at(idx - 1) == '&') { break; } - if (raw_text.at(idx + 1) == '&') { - makeToken(TokenType::AND, idx, 2); - } else { - return std::unexpected( - Err(ErrType::TokenizingError, "Singleton ampersand char found")); - } - } break; - - case '|': { - if (raw_text.at(idx - 1) == '|') { break; } - if (raw_text.at(idx + 1) == '|') { - makeToken(TokenType::OR, idx, 2); - } else { - return std::unexpected( - Err(ErrType::TokenizingError, "Singleton pipe char found")); - } - } break; - - default: - break; - }; - - // clang-format on - - // numbers - if (std::isdigit(c)) { - advance_count = scanNumber(idx) - 1; - continue; - } - - if (std::isalnum(c)) { - advance_count = scanIdentifier(idx) - 1; - continue; - } - } - - makeToken(TokenType::END, raw_text.size(), 0); - return {}; - } - - [[nodiscard]] std::expected Lexer::advance(const TokenType& tok) { - if (playhead < tokens.size()) { playhead++; } - if (tokens.at(playhead).type != tok) { - return std::unexpected(Err(ErrType::ParsingError, "Wrong token found")); - } - - return {}; - } - - void Lexer::advance() { - if (playhead < tokens.size()) { playhead++; } - } - - [[nodiscard]] Token* Lexer::currToken() { - return &tokens.at(playhead); - } - - [[nodiscard]] bool Lexer::check(const TokenType& tok) { - return tokens.at(playhead).type == tok; - } - - [[nodiscard]] bool Lexer::checkNext(const TokenType& tok) { - if (playhead == tokens.size() - 1) { return false; } - return tokens.at(playhead + 1).type == tok; - } - - [[nodiscard]] bool Lexer::atEnd() const { - return tokens.at(playhead).type == TokenType::END; - } -} // namespace Winter diff --git a/src/winter/lexer.h b/src/winter/lexer.h deleted file mode 100644 index 17d44cb..0000000 --- a/src/winter/lexer.h +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef WINTER_LEXER_H -#define WINTER_LEXER_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "error.h" - -namespace Winter { - enum class TokenType { - // Single-character tokens. - LEFT_PAREN, - RIGHT_PAREN, - LEFT_BRACE, - RIGHT_BRACE, - COMMA, - DOT, - SEMICOLON, - COLON, - MINUS, - PLUS, - STAR, - SLASH, - - // One or two character tokens. - BANG, - BANG_EQ, - EQUAL, - EQUAL_EQ, - GREATER, - GREATER_EQ, - LESS, - LESS_EQ, - ELLIPSIS, - - // digraphs - AND, - OR, - - // Keywords. - CASE, - CLASS, - CONST, - DEFAULT, - ELSE, - ENUM, - EXPORT, - FALSE, - FOR, - FUNC, - IF, - IMPORT, - NIL, - RETURN, - STATIC, - SWITCH, - TRUE, - VAR, - - // Literals. - IDENT, - STRING, - CHAR, - NUMBER, - - END // End Of File -- EOF is a macro - }; - - struct Token { - TokenType type; - std::size_t start; - std::size_t len; - - explicit Token(TokenType typ, std::size_t s, std::size_t l) : type(typ), start(s), len(l) {} - - auto operator<=>(const Token&) const = default; - - [[nodiscard]] std::string toString() const; - }; - - struct RegexPair { - std::regex regex; - TokenType type; - std::size_t length; - explicit constexpr RegexPair(std::regex r, TokenType tok, std::size_t sz) - : regex(r), type(tok), length(sz) {} - }; - - struct Lexer { - std::string raw_text; - std::vector tokens; - const std::array keyword_regex = { - RegexPair(std::regex("^(case)(?![a-zA-Z0-9_])"), TokenType::CASE, 4), - RegexPair(std::regex("^(class)(?![a-zA-Z0-9_])"), TokenType::CLASS, 5), - RegexPair(std::regex("^(const)(?![a-zA-Z0-9_])"), TokenType::CONST, 5), - RegexPair(std::regex("^(default)(?![a-zA-Z0-9_])"), TokenType::DEFAULT, 7), - RegexPair(std::regex("^(else)(?![a-zA-Z0-9_])"), TokenType::ELSE, 4), - RegexPair(std::regex("^(enum)(?![a-zA-Z0-9_])"), TokenType::ENUM, 4), - RegexPair(std::regex("^(export)(?![a-zA-Z0-9_])"), TokenType::EXPORT, 6), - RegexPair(std::regex("^(false)(?![a-zA-Z0-9_])"), TokenType::FALSE, 5), - RegexPair(std::regex("^(for)(?![a-zA-Z0-9_])"), TokenType::FOR, 3), - RegexPair(std::regex("^(func)(?![a-zA-Z0-9_])"), TokenType::FUNC, 4), - RegexPair(std::regex("^(if)(?![a-zA-Z0-9_])"), TokenType::IF, 2), - RegexPair(std::regex("^(import)(?![a-zA-Z0-9_])"), TokenType::IMPORT, 6), - RegexPair(std::regex("^(nil)(?![a-zA-Z0-9_])"), TokenType::NIL, 3), - RegexPair(std::regex("^(return)(?![a-zA-Z0-9_])"), TokenType::RETURN, 6), - RegexPair(std::regex("^(static)(?![a-zA-Z0-9_])"), TokenType::STATIC, 6), - RegexPair(std::regex("^(switch)(?![a-zA-Z0-9_])"), TokenType::SWITCH, 6), - RegexPair(std::regex("^(true)(?![a-zA-Z0-9_])"), TokenType::TRUE, 4), - RegexPair(std::regex("^(var)(?![a-zA-Z0-9_])"), TokenType::VAR, 3), - }; - - std::size_t playhead = 0; - - explicit Lexer(std::string src) : raw_text(src), tokens({}) {} - - // move assignment op - Lexer& operator=(Lexer&& other) noexcept { - if (this != &other) { - raw_text = std::move(other.raw_text); - tokens = std::move(other.tokens); - } - return *this; - } - - void makeToken(TokenType, long, std::size_t); - [[nodiscard]] std::size_t scanNumber(std::size_t); - [[nodiscard]] std::size_t scanStringLiteral(std::size_t); - [[nodiscard]] std::size_t scanChar(std::size_t); - [[nodiscard]] bool validIdentChar(char) const; - [[nodiscard]] std::optional scanKeyword(std::string_view, std::size_t); - [[nodiscard]] std::size_t scanIdentifier(std::size_t); - [[nodiscard]] std::size_t scanEllipsis(std::size_t); - [[nodiscard]] std::expected tokenize(); - - [[nodiscard]] std::expected advance(const TokenType&); - void advance(); - [[nodiscard]] Token* currToken(); - [[nodiscard]] bool check(const TokenType&); - [[nodiscard]] bool checkNext(const TokenType&); - [[nodiscard]] bool atEnd() const; - }; -} // namespace Winter - -template <> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } - auto format(const Winter::Lexer& lexer, std::format_context& ctx) const { - std::string out = " --- Lexer --- \n"; - for (auto&& token : lexer.tokens) { out += token.toString() + "\n"; } - - return std::format_to(ctx.out(), "{}", out); - } -}; - -#endif // WINTER_LEXER_H diff --git a/src/winter/object.h b/src/winter/object.h deleted file mode 100644 index f70f397..0000000 --- a/src/winter/object.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef WINTER_OBJECT_H -#define WINTER_OBJECT_H - -#include -#include -#include - -namespace Winter { - enum class ObjType { - Null, - }; - - struct Object { - ObjType type; - std::any value; - - [[nodiscard]] explicit constexpr Object(ObjType _type, std::any val) - : type(_type), value(val) {} - - template - [[nodiscard]] constexpr T unwrap() const { - return std::any_cast(value); - } - }; -}; // namespace Winter - -#endif // WINTER_OBJECT_H diff --git a/src/winter/parser.cpp b/src/winter/parser.cpp deleted file mode 100644 index bda44fc..0000000 --- a/src/winter/parser.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "parser.h" - -#include -#include - -namespace Winter { - [[nodiscard]] binding_t Parser::prefixBindingPower(const TokenType& tok) const { - switch (tok) { - case TokenType::BANG: - [[fallthrough]]; - case TokenType::MINUS: - return 8; - default: - return 0; - }; - - return 0; - } - - [[nodiscard]] binding_t Parser::infixBindingPower(const TokenType& tok) const { - switch (tok) { - case TokenType::LEFT_PAREN: - [[fallthrough]]; - case TokenType::RIGHT_PAREN: - [[fallthrough]]; - case TokenType::DOT: - return 9; - - case TokenType::STAR: - [[fallthrough]]; - case TokenType::SLASH: - return 7; - - case TokenType::PLUS: - [[fallthrough]]; - case TokenType::MINUS: - return 6; - - case TokenType::GREATER_EQ: - [[fallthrough]]; - case TokenType::LESS_EQ: - [[fallthrough]]; - case TokenType::GREATER: - [[fallthrough]]; - case TokenType::LESS: - return 5; - - case TokenType::EQUAL_EQ: - [[fallthrough]]; - case TokenType::BANG_EQ: - return 4; - - case TokenType::AND: - return 3; - - case TokenType::OR: - return 2; - - case TokenType::EQUAL: - return 1; - default: - return 0; - } - - return 0; - } - - [[nodiscard]] expected_node_t Parser::parseBlock() { - assert(L->currToken()->type == TokenType::LEFT_BRACE); - - L->advance(); - auto block = std::make_unique(); - - while (!L->atEnd() && !L->check(TokenType::RIGHT_BRACE)) { - auto ret = parseStatement(); - if (!ret.has_value()) { return std::unexpected(ret.error()); } - - block->stmts.push_back(std::move(ret.value())); - } - - assert(L->currToken()->type == TokenType::RIGHT_BRACE); - L->advance(); - return block; - } - - [[nodiscard]] expected_node_t Parser::parseExpression(const binding_t min_bp) { - std::unique_ptr node = std::make_unique(); - const Token* tok = L->currToken(); - std::string_view source = L->raw_text; - - switch (tok->type) { - case TokenType::NUMBER: - auto num_sv = - std::string_view(source.begin() + tok->start, source.begin() + tok->len); - node->lhs = std::make_unique(std::atof(num_sv.data())); - break; - } - - if (L->checkNext(TokenType::END)) { - // TODO: return failure - return std::unexpected( - Err(ErrType::ParsingError, "Parser should have found end of file")); - } else if (L->checkNext(TokenType::SEMICOLON) || L->checkNext(TokenType::RIGHT_PAREN)) { - return node; - } - - while (true) { - L->advance(); - const Token* op_tkn = L->currToken(); - if (op_tkn->type == TokenType::SEMICOLON || op_tkn->type == TokenType::RIGHT_PAREN) { - break; - } - - const binding_t bp = infixBindingPower(L->currToken()->type); - if (bp < min_bp) { break; } - L->advance(); - - expected_node_t rhs = parseExpression(bp + 1); - if (!rhs.has_value()) { return std::unexpected(rhs.error()); } - - node->op = op_tkn; - node->rhs = std::move(rhs.value()); - } - - assert(L->currToken()->type == TokenType::SEMICOLON); - return node; - } - - [[nodiscard]] expected_node_t Parser::parseFunc() { - auto ret = L->advance(TokenType::IDENT); - if (!ret.has_value()) { return std::unexpected(ret.error()); } - - auto func = std::make_unique(); - func->name = L->raw_text.substr(L->currToken()->start, L->currToken()->len); - - // params - ret = L->advance(TokenType::LEFT_PAREN); - if (!ret.has_value()) { return std::unexpected(ret.error()); } - - while (true) { - L->advance(); - if (L->currToken()->type == TokenType::RIGHT_PAREN) { - break; - } else if (L->currToken()->type != TokenType::IDENT) { - return std::unexpected( - Err(ErrType::ParsingError, "Token IDENT not found in function params")); - } - - func->params.push_back(*L->currToken()); - L->advance(); - if (L->currToken()->type == TokenType::RIGHT_PAREN) { - break; - } else if (L->currToken()->type == TokenType::COMMA) { - continue; - } - } - - // body - ret = L->advance(TokenType::LEFT_BRACE); - if (!ret.has_value()) { return std::unexpected(ret.error()); } - - auto block = parseBlock(); - if (!block.has_value()) { return std::unexpected(block.error()); } - func->body = std::move(block.value()); - - return func; - } - - [[nodiscard]] expected_node_t Parser::parseReturn() { - assert(L->currToken()->type == TokenType::RETURN); - auto ret = std::make_unique(); - L->advance(); - auto expr = parseExpression(0); - if (!expr.has_value()) { return std::unexpected(expr.error()); } - ret->expr = std::move(expr.value()); - L->advance(); - return ret; - } - - [[nodiscard]] expected_node_t Parser::parseStatement() { - switch (L->currToken()->type) { - case TokenType::RETURN: { - auto ret = parseReturn(); - if (!ret.has_value()) { return std::unexpected(ret.error()); } - L->advance(); // Skip semicolon - return std::move(ret).value(); - } break; - } - - std::unreachable(); - } - - [[nodiscard]] result_t Parser::parse_tree() { - std::unique_ptr root_ptr = std::make_unique(); - - while (!L->atEnd() && L->playhead < L->tokens.size()) { - switch (L->currToken()->type) { - case TokenType::FUNC: { - expected_node_t result = parseFunc(); - if (!result.has_value()) { return std::unexpected(result.error()); }; - - root_ptr->children.push_back(std::move(result.value())); - } break; - } - } - - root = std::move(root_ptr); - return {}; - } - -}; // namespace Winter diff --git a/src/winter/parser.h b/src/winter/parser.h deleted file mode 100644 index 04bf33e..0000000 --- a/src/winter/parser.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef WINTER_PARSER_H -#define WINTER_PARSER_H - -#include -#include - -#include "ast_nodes.h" -#include "error.h" -#include "lexer.h" - -namespace Winter { - - struct Parser { - Lexer* L; - std::unique_ptr root = nullptr; - - [[nodiscard]] explicit Parser(Lexer* lexer_ptr) : L(lexer_ptr) {} - [[nodiscard]] binding_t prefixBindingPower(const TokenType&) const; - [[nodiscard]] binding_t infixBindingPower(const TokenType&) const; - [[nodiscard]] expected_node_t parseBlock(); - [[nodiscard]] expected_node_t parseExpression(const binding_t); - [[nodiscard]] expected_node_t parseFunc(); - [[nodiscard]] expected_node_t parseReturn(); - [[nodiscard]] expected_node_t parseStatement(); - [[nodiscard]] result_t parse_tree(); - }; - -}; // namespace Winter - -template <> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } - auto format(const Winter::Parser& parser, std::format_context& ctx) const { - std::string out = " --- Parser --- \n"; - if (parser.root == nullptr) { return std::format_to(ctx.out(), "{}", out); } - - out += parser.root->display(0); - out += "\n"; - return std::format_to(ctx.out(), "{}", out); - } -}; - -#endif // WINTER_PARSER_H diff --git a/src/winter/winter.h b/src/winter/winter.h deleted file mode 100644 index 1e4b8b2..0000000 --- a/src/winter/winter.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef WINTER_H -#define WINTER_H - -#include -#include -#include -#include -#include -#include -#include - -#include "bytecode.h" -#include "error.h" -#include "helpers.h" -#include "lexer.h" -#include "object.h" -#include "parser.h" - -namespace Winter { - - struct VM { - using WinterFn = std::function; - - bool debug; - std::stack stack; - std::unordered_map registeredFunctions = {}; - - explicit VM(bool dbg_mode) : debug(dbg_mode) {} - constexpr void push(Object obj) { stack.push(obj); } - - [[nodiscard]] constexpr std::expected pop() { - if (stack.empty()) { - return std::unexpected(Err(ErrType::RuntimeError, "Stack is empty")); - } - Object obj = stack.top(); - stack.pop(); - return obj; - } - - constexpr void registerFunc(const std::string& name, WinterFn func) { - registeredFunctions.insert({name, func}); - } - - [[nodiscard]] constexpr retcode_t doString(const std::string& code) { - Lexer l = Lexer(code); - result_t ret = l.tokenize(); - if (debug) { std::println("{}", l); } - - if (!ret.has_value()) { return std::unexpected(ret.error()); } - - Parser p = Parser(&l); - ret = p.parse_tree(); - if (debug) { std::println("{}", p); } - - if (p.root == nullptr) { return std::unexpected(ret.error()); } - - Generator gen = Generator(std::move(p.root)); - std::expected module = gen.generate(); - if (!module.has_value()) { return std::unexpected(module.error()); } - if (debug) { std::println("{}", module.value()); } - - return 0; - } - - [[nodiscard]] constexpr retcode_t doFile(std::string_view fileName) { - const std::string fText = openFile(fileName); - retcode_t retcode = doString(fText); - if (!retcode.has_value()) { return retcode; } - - // Enforcing that any full script contains the main function - retcode = call("main"); - return retcode; - } - - [[nodiscard]] constexpr retcode_t call(const std::string& funcName) { - if (registeredFunctions.contains(funcName)) { - return registeredFunctions.at(funcName)(*this); - } - - // TODO: use std::format to include funcName in error message - return std::unexpected( - Err(ErrType::NameError, "Function " + funcName + " is not defined")); - } - }; -} // namespace Winter - -#endif // WINTER_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f3d78a4..41d7177 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,13 +1,15 @@ -include_directories(${PROJECT_SOURCE_DIR}/src/winter) +include_directories(${PROJECT_SOURCE_DIR}/src) + +add_compile_options(-g) include(FetchContent) fetchcontent_declare( willow GIT_REPOSITORY https://github.com/Ttibsi/willow - GIT_TAG ccc88b8753a74b6ab9b66e67e81c2169780b4eed + GIT_TAG main ) fetchcontent_makeavailable(willow) add_executable(test_exe ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) target_include_directories(test_exe PRIVATE ${PROJECT_SOURCE_DIR}/src/winter) -target_link_libraries(test_exe PUBLIC winter-lang willow) +target_link_libraries(test_exe PUBLIC winter_src willow) diff --git a/tests/bytecode_test.h b/tests/bytecode_test.h deleted file mode 100644 index b1ad608..0000000 --- a/tests/bytecode_test.h +++ /dev/null @@ -1,172 +0,0 @@ -#include -#include - -#include - -#include "winter/bytecode.h" -#include "winter/lexer.h" -#include "winter/parser.h" - -constexpr int test_Chunk_extend([[maybe_unused]] Willow::Test* test) { - Winter::Chunk c = Winter::Chunk(); - if (c.instructions.size()) { return 1; } - - std::vector v = { - Winter::Bytecode(Winter::Opcode::NIL), - Winter::Bytecode(Winter::Opcode::ADD, 12.3), - }; - c.extend(v); - - if (c.instructions.size() != 2) { return 2; } - - return 0; -} - -constexpr int test_Generator_compileValue([[maybe_unused]] Willow::Test* test) { - Winter::Generator gen = Winter::Generator(nullptr); - Winter::ValueNode node = {5}; - auto ret = gen.compileValue(&node); - if (!ret.has_value()) { - test->alert(ret.error().msg); - return 1; - } - - if (ret.value() != Winter::Bytecode(Winter::Opcode::STORE_CONST, 5.0)) { return 2; } - - return 0; -} - -constexpr int test_Generator_compileTok([[maybe_unused]] Willow::Test* test) { - Winter::Generator gen = Winter::Generator(nullptr); - - Winter::Token tok = Winter::Token(Winter::TokenType::NIL, 0, 3); - auto ret = gen.compileTok(&tok); - if (!ret.has_value()) { return 1; } - if (ret.value() != Winter::Bytecode(Winter::Opcode::NIL)) { return 2; } - - tok = Winter::Token(Winter::TokenType::PLUS, 0, 3); - ret = gen.compileTok(&tok); - if (!ret.has_value()) { return 3; } - if (ret.value() != Winter::Bytecode(Winter::Opcode::ADD)) { return 4; } - - return 0; -} - -constexpr int test_Generator_compileExpression([[maybe_unused]] Willow::Test* test) { - Winter::Generator gen = Winter::Generator(nullptr); - Winter::Token tok = Winter::Token(Winter::TokenType::PLUS, 0, 3); - - Winter::ExprNode node = Winter::ExprNode(); - node.op = &tok; - node.lhs = std::unique_ptr(new Winter::ValueNode(5)); - node.rhs = std::unique_ptr(new Winter::ValueNode(3)); - - auto ret = gen.compileExpression(&node); - if (!ret.has_value()) { - test->alert(ret.error().msg); - return 1; - } - - if (ret.value().size() != 3) { - test->alert("Expr bytecode length: " + std::to_string(ret.value().size())); - return 2; - } - - return 0; -} - -constexpr int test_Generator_compileReturn([[maybe_unused]] Willow::Test* test) { - Winter::Generator gen = Winter::Generator(nullptr); - - Winter::ReturnNode ret_node = Winter::ReturnNode(); - ret_node.expr = std::make_unique(); - ret_node.expr->lhs = std::make_unique(5); - - auto ret = gen.compileReturn(&ret_node); - if (!ret.has_value()) { - test->alert(ret.error().msg); - return 1; - } - - if (ret.value().size() != 2) { - test->alert("Expr bytecode length: " + std::to_string(ret.value().size())); - return 2; - } - - if (ret.value().front() != Winter::Bytecode(Winter::Opcode::STORE_CONST)) { return 3; } - if (ret.value().back() != Winter::Bytecode(Winter::Opcode::RET)) { return 4; } - - return 0; -} - -constexpr int test_Generator_compileFunc([[maybe_unused]] Willow::Test* test) { - Winter::Generator gen = Winter::Generator(nullptr); - Winter::FuncNode node = Winter::FuncNode(); - node.name = "f"; - node.params = {Winter::Token(Winter::TokenType::IDENT, 5, 2)}; - - // Build node body for test data - auto expr_node = std::make_unique(); - expr_node->lhs = std::make_unique(5); - - auto ret_node = std::make_unique(); - ret_node->expr = std::move(expr_node); // Transfer ownership - - node.body = std::make_unique(); - node.body->stmts.push_back(std::move(ret_node)); - - auto ret = gen.compileFunc(&node); - if (!ret.has_value()) { - test->alert(ret.error().msg); - return 1; - } - - if (ret.value().name != "f") { - test->alert("Chunk name" + ret.value().name); - return 2; - } - - if (ret.value().instructions.size() != 2) { - test->alert("Chunk instruction len: " + std::to_string(ret.value().instructions.size())); - return 3; - } - - return 0; -} - -constexpr int test_Generator_generate([[maybe_unused]] Willow::Test* test) { - using namespace Winter; - - Lexer l = Lexer("func f() { return 4; }"); - result_t ret = l.tokenize(); - Parser p = Parser(&l); - ret = p.parse_tree(); - Generator gen = Generator(std::move(p.root)); - auto gen_ret = gen.generate(); - - if (!gen_ret.has_value()) { - test->alert(gen_ret.error().msg); - return 1; - } - - Module m = gen_ret.value(); - if (m.name != "main") { - test->alert("Module name: " + m.name); - return 2; - } - - if (m.chunks.size() != 1) { return 3; } - const Chunk c = m.chunks.at(0); - - if (c.name != "f") { - test->alert("Chunk name" + c.name); - return 4; - } - - if (c.instructions.size() != 2) { - test->alert("Chunk instruction len: " + std::to_string(c.instructions.size())); - return 5; - } - - return 0; -} diff --git a/tests/error_test.h b/tests/error_test.h deleted file mode 100644 index 1771154..0000000 --- a/tests/error_test.h +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include "error.h" - -constexpr int test_errConstructor([[maybe_unused]] Willow::Test* test) { - const auto type = Winter::ErrType::NotImplementedError; - const auto err = Winter::Err(type, "TODO: not implemented"); - - if (err.type != type) { return 1; } - if (err.msg != "TODO: not implemented") { return 2; } - - return 0; -} - -constexpr int test_getErrType([[maybe_unused]] Willow::Test* test) { - const auto type = Winter::ErrType::NotImplementedError; - const auto err = Winter::Err(type, "TODO: not implemented"); - - if (err.getErrType() != "NotImplementedError") { return 1; } - return 0; -} - -constexpr int test_display([[maybe_unused]] Willow::Test* test) { - const auto type = Winter::ErrType::NotImplementedError; - const auto err = Winter::Err(type, "TODO: not implemented"); - const std::string expected = "NotImplementedError: TODO: not implemented"; - - if (err.display() != expected) { - test->alert(err.display()); - return 1; - } - - return 0; -} diff --git a/tests/helpers_test.h b/tests/helpers_test.h deleted file mode 100644 index 5ce19b4..0000000 --- a/tests/helpers_test.h +++ /dev/null @@ -1,13 +0,0 @@ -#include - -#include "winter/helpers.h" - -constexpr int test_openFile([[maybe_unused]] Willow::Test* test) { - const std::string f = openFile("tests/fixture/test_file_1.txt"); - - // wc -c tests/fixture/test_file_1.txt - if (f.size() != 70) { return 1; } - if (f.at(0) != 'T') { return 2; } - - return 0; -} diff --git a/tests/lexer_test.h b/tests/lexer_test.h deleted file mode 100644 index 22b951c..0000000 --- a/tests/lexer_test.h +++ /dev/null @@ -1,218 +0,0 @@ -#include "winter/lexer.h" - -using namespace std::string_view_literals; - -constexpr int test_token_toString([[maybe_unused]] Willow::Test* test) { - Winter::Token tok = Winter::Token(Winter::TokenType::NIL, 0, 3); - auto str = tok.toString(); - - if (str.size() != 39) { - test->alert("String length: " + std::to_string(str.size())); - return 1; - } - - if (str.substr(11, 3) != "NIL") { - test->alert("substr: " + str.substr(11, 3)); - return 2; - } - - return 0; -} - -constexpr int test_makeToken([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer(""); - l.makeToken(Winter::TokenType::NIL, 0, 3); - - if (l.tokens.size() != 1) { return 1; } - if (l.tokens.at(0).type != Winter::TokenType::NIL) { return 2; } - if (l.tokens.at(0).start != 0) { return 3; } - if (l.tokens.at(0).len != 3) { return 4; } - return 0; -} - -constexpr int test_scanNumber([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("42"); - - if (l.scanNumber(0) != 2) { return 1; } - if (l.tokens.at(0).type != Winter::TokenType::NUMBER) { return 2; } - - return 0; -} - -constexpr int test_scanStringLiteral([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("\"hello world\""); - - if (l.scanStringLiteral(0) != 13) { return 1; } - if (l.tokens.at(0).type != Winter::TokenType::STRING) { return 2; } - return 0; -} - -constexpr int test_scanChar([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("char: 'x'"); - const std::size_t len = std::string("char: ").size(); - - if (l.scanChar(len) != 3) { return 1; } - return 0; -} - -constexpr int test_validIdentChar([[maybe_unused]] Willow::Test* test) { - const auto l = Winter::Lexer(""); - - // return true - if (!l.validIdentChar('_')) { return 1; } - if (!l.validIdentChar('e')) { return 2; } - if (!l.validIdentChar('J')) { return 3; } - if (!l.validIdentChar('4')) { return 4; } - - // return false - if (l.validIdentChar('!')) { return 5; } - if (l.validIdentChar(':')) { return 6; } - - return 0; -} - -constexpr int test_scanKeyword([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("nil"); - auto ret = l.scanKeyword("nil"sv, 0); - if (!ret.has_value()) { return 1; } - if (ret.value() != 3) { return 2; } - - ret = l.scanKeyword("nonsense"sv, 0); - if (ret.has_value()) { return 3; } - - return 0; -} - -constexpr int test_scanIdentifier([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("foo()"); - if (l.scanIdentifier(0) != 3) { return 1; } - if (l.tokens.at(0).type != Winter::TokenType::IDENT) { return 2; } - - l = Winter::Lexer("returnValue"); - if (l.scanIdentifier(0) != 11) { return 3; } - - return 0; -} - -constexpr int test_scanEllipsis([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("..."); - - if (l.scanEllipsis(0) != 3) { return 1; } - if (l.scanEllipsis(1) != 1) { - test->alert("Result = " + std::to_string(l.scanEllipsis(1))); - return 2; - } - return 0; -} - -constexpr int test_tokenize([[maybe_unused]] Willow::Test* test) { - struct Params { - std::string text; - std::size_t param_id; - std::vector expected_tokens; - }; - - auto test_logic = [](const Params& params) -> std::size_t { - auto l = Winter::Lexer(params.text); - auto ret = l.tokenize(); - if (!ret.has_value()) { return params.param_id; } - - if (l.tokens.size() != params.expected_tokens.size()) { return params.param_id + 50; } - - for (std::size_t i = 0; i < params.expected_tokens.size(); i++) { - if (l.tokens.at(i).type != params.expected_tokens.at(i)) { return i + params.param_id; } - } - - return 0; - }; - - std::array param_collection = { - Params("", 100, {Winter::TokenType::END}), - Params( - "func f() { return 5; }", 200, - {Winter::TokenType::FUNC, Winter::TokenType::IDENT, Winter::TokenType::LEFT_PAREN, - Winter::TokenType::RIGHT_PAREN, Winter::TokenType::LEFT_BRACE, - Winter::TokenType::RETURN, Winter::TokenType::NUMBER, Winter::TokenType::SEMICOLON, - Winter::TokenType::RIGHT_BRACE, Winter::TokenType::END})}; - - for (auto&& p : param_collection) { - int val = int32_t(test_logic(p)); - if (val) { return val; } - } - - return 0; -} - -constexpr int test_advanceTokOverload([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("func f() { return 5; }"); - std::ignore = l.tokenize(); - - auto ret = l.advance(Winter::TokenType::IDENT); - if (!ret.has_value()) { - test->alert(ret.error().display()); - return 1; - } - - return 0; -} - -constexpr int test_advance([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("return 5;"); - if (l.playhead != 0) { return 1; } - - // Advance without any tokens - l.advance(); - if (l.playhead != 0) { return 2; } - - std::ignore = l.tokenize(); - l.advance(); - if (l.playhead != 1) { return 3; } - - return 0; -} - -constexpr int test_currToken([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("func f(){return 5;}"); - std::ignore = l.tokenize(); - - if (l.currToken()->type != Winter::TokenType::FUNC) { return 1; } - l.advance(); - if (l.currToken()->type != Winter::TokenType::IDENT) { return 2; } - - return 0; -} - -constexpr int test_check([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("func f(){return 5;}"); - std::ignore = l.tokenize(); - - if (!l.check(Winter::TokenType::FUNC)) { return 1; } - l.advance(); - if (!l.check(Winter::TokenType::IDENT)) { return 2; } - - return 0; -} - -constexpr int test_checkNext([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("func f(){return 5;}"); - std::ignore = l.tokenize(); - - if (!l.checkNext(Winter::TokenType::IDENT)) { return 1; } - l.advance(); - if (!l.checkNext(Winter::TokenType::LEFT_PAREN)) { return 2; } - - return 0; -} - -constexpr int test_atEnd([[maybe_unused]] Willow::Test* test) { - auto l = Winter::Lexer("func f(){return 5;}"); - std::ignore = l.tokenize(); - - if (l.atEnd()) { return 1; } - l.advance(); - if (l.atEnd()) { return 2; } - l.playhead = l.tokens.size() - 1; - if (!l.atEnd()) { return 3; } - - return 0; -} diff --git a/tests/main.cpp b/tests/main.cpp index 72a61de..1ce393d 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,61 +1,10 @@ #include #include -#include "bytecode_test.h" -#include "error_test.h" -#include "helpers_test.h" -#include "lexer_test.h" -#include "object_test.h" -#include "parser_test.h" -#include "winter_test.h" - int main() { Willow::PreCommitReporter reporter = {}; - return Willow::runTests( - { - {"openFile", test_openFile}, - {"[Object] Constructor", test_objectConstructor}, - {"[Object] unwrap", test_objectUnwrap}, - {"[Err] Constructor", test_errConstructor}, - {"[Err] getErrType", test_getErrType}, - {"[Err] display", test_display}, - {"[VM] push", test_vmPush}, - {"[VM] pop", test_vmPop}, - {"[VM] doString", test_vmDoString, Willow::Status::Skip}, - {"[VM] doFile", test_vmDoFile, Willow::Status::Skip}, - {"[VM] call", test_vmCall, Willow::Status::Skip}, - {"[Token] toString", test_token_toString}, - {"[Lexer] makeToken", test_makeToken}, - {"[Lexer] scanNumber", test_scanNumber}, - {"[Lexer] scanStringLiteral", test_scanStringLiteral}, - {"[Lexer] scanChar", test_scanChar}, - {"[Lexer] validIdentChar", test_validIdentChar}, - {"[Lexer] scanKeyword", test_scanKeyword}, - {"[Lexer] scanIdentifier", test_scanIdentifier}, - {"[Lexer] scanEllipsis", test_scanEllipsis}, - {"[Lexer] tokenize", test_tokenize}, - {"[Lexer] advance (TokenType overload)", test_advanceTokOverload}, - {"[Lexer] advance", test_advance}, - {"[Lexer] currToken", test_currToken}, - {"[Lexer] check", test_check}, - {"[Lexer] checkNext", test_checkNext}, - {"[Lexer] atEnd", test_atEnd}, - {"[Parser] prefixBindingPower", test_prefixBindingPower}, - {"[Parser] infixBindingPower", test_infixBindingPower}, - {"[Parser] parseBlock", test_parseBlock}, - {"[Parser] parseExpression", test_parseExpression}, - {"[Parser] parseFunc", test_parseFunc}, - {"[Parser] parseReturn", test_parseReturn}, - {"[Parser] parseStatement", test_parseStatement}, - {"[Parser] parse_tree", test_parse_tree}, - {"[Chunk] extend", test_Chunk_extend}, - {"[Generator] compileValue", test_Generator_compileValue}, - {"[Generator] compileTok", test_Generator_compileTok}, - {"[Generator] compileExpression", test_Generator_compileExpression}, - {"[Generator] compileReturn", test_Generator_compileReturn}, - {"[Generator] compileFunc", test_Generator_compileFunc}, - {"[Generator] generate", test_Generator_generate}, - }, - reporter); + Willow::registerTests({}); + + return Willow::runTests(reporter); } diff --git a/tests/object_test.h b/tests/object_test.h deleted file mode 100644 index 9b8aea1..0000000 --- a/tests/object_test.h +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include - -#include "winter/object.h" -using namespace std::literals; - -constexpr int test_objectConstructor([[maybe_unused]] Willow::Test* test) { - const auto o = Winter::Object(Winter::ObjType::Null, 5); - - if (o.type != Winter::ObjType::Null) { return 1; } - - if (o.value.type().name() != "i"sv) { return 2; } - - if (std::any_cast(o.value) != 5) { return 3; } - - return 0; -} - -constexpr int test_objectUnwrap([[maybe_unused]] Willow::Test* test) { - auto o = Winter::Object(Winter::ObjType::Null, 5); - - if (o.value.type().name() != "i"sv) { return 1; } - - if (o.unwrap() != 5) { return 2; } - - return 0; -} diff --git a/tests/parser_test.h b/tests/parser_test.h deleted file mode 100644 index a33db26..0000000 --- a/tests/parser_test.h +++ /dev/null @@ -1,122 +0,0 @@ -#include - -#include "winter/parser.h" - -constexpr int test_prefixBindingPower([[maybe_unused]] Willow::Test* test) { - const auto p = Winter::Parser(nullptr); - if (p.prefixBindingPower(Winter::TokenType::MINUS) != 8) { return 1; } - if (p.prefixBindingPower(Winter::TokenType::DOT) != 0) { return 2; } - return 0; -} - -constexpr int test_infixBindingPower([[maybe_unused]] Willow::Test* test) { - const auto p = Winter::Parser(nullptr); - if (p.infixBindingPower(Winter::TokenType::MINUS) != 6) { return 1; } - if (p.infixBindingPower(Winter::TokenType::EQUAL) != 1) { return 2; } - if (p.infixBindingPower(Winter::TokenType::BANG) != 0) { return 3; } - return 0; -} - -constexpr int test_parseBlock([[maybe_unused]] Willow::Test* test) { - auto L = Winter::Lexer("{ return 4; }"); - std::ignore = L.tokenize(); - auto p = Winter::Parser(&L); - auto ret = p.parseBlock(); - - if (!ret.has_value()) { return 1; } - if (ret.value() == nullptr) { return 2; } - - std::unique_ptr node = std::move(ret.value()); - if (node->stmts.size() != 1) { return 3; } - - return 0; -} - -constexpr int test_parseExpression([[maybe_unused]] Willow::Test* test) { - auto L = Winter::Lexer("4 + 3;"); - std::ignore = L.tokenize(); - auto p = Winter::Parser(&L); - auto ret = p.parseExpression(0); - - if (!ret.has_value()) { return 1; } - if (ret.value() == nullptr) { return 2; } - - std::unique_ptr node = std::move(ret.value()); - if (node->op == nullptr) { return 3; } - if (node->op->type != Winter::TokenType::PLUS) { return 4; } - - if (node->lhs == nullptr) { return 5; } - if (node->rhs == nullptr) { return 6; } - - return 0; -} - -constexpr int test_parseFunc([[maybe_unused]] Willow::Test* test) { - auto L = Winter::Lexer("func foo(argc, argv) { return 4; }"); - std::ignore = L.tokenize(); - auto p = Winter::Parser(&L); - auto ret = p.parseFunc(); - - if (!ret.has_value()) { return 1; } - if (ret.value() == nullptr) { return 2; } - - std::unique_ptr node = std::move(ret.value()); - if (node->name != std::string("foo")) { return 3; } - if (node->params.size() != 2) { return 4; } - - if (node->params.at(0).type != Winter::TokenType::IDENT) { return 5; } - if (node->params.at(0).len != 4) { return 6; } - if (node->body == nullptr) { return 7; } - - return 0; -} - -constexpr int test_parseReturn([[maybe_unused]] Willow::Test* test) { - auto L = Winter::Lexer("return 4;"); - std::ignore = L.tokenize(); - auto p = Winter::Parser(&L); - auto ret = p.parseReturn(); - - if (!ret.has_value()) { return 1; } - if (ret.value() == nullptr) { return 2; } - - std::unique_ptr node = std::move(ret.value()); - if (node->expr == nullptr) { return 3; } - - return 0; -} - -constexpr int test_parseStatement([[maybe_unused]] Willow::Test* test) { - auto L = Winter::Lexer("return 4;"); - std::ignore = L.tokenize(); - auto p = Winter::Parser(&L); - auto ret = p.parseStatement(); - - if (!ret.has_value()) { return 1; } - if (ret.value() == nullptr) { return 2; } - - Winter::ReturnNode* node = static_cast(ret.value().get()); - if (node->expr == nullptr) { return 3; } - - return 0; -} - -constexpr int test_parse_tree([[maybe_unused]] Willow::Test* test) { - auto L = Winter::Lexer("func foo(argc, argv) { return 4; }"); - std::ignore = L.tokenize(); - auto p = Winter::Parser(&L); - auto ret = p.parse_tree(); - - if (!ret.has_value()) { return 1; } - if (p.root == nullptr) { return 2; } - if (p.root->children.size() != 1) { - test->alert("Children len: " + std::to_string(p.root->children.size())); - return 3; - } - - Winter::FuncNode* func = static_cast(p.root->children.at(0).get()); - if (func->name != "foo") { return 4; } - if (func->body == nullptr) { return 5; } - - return 0; -} diff --git a/tests/winter_test.h b/tests/winter_test.h deleted file mode 100644 index 7c9b420..0000000 --- a/tests/winter_test.h +++ /dev/null @@ -1,39 +0,0 @@ -#include "winter/winter.h" - -constexpr int test_vmPush([[maybe_unused]] Willow::Test* test) { - auto vm = Winter::VM(false); - vm.push(Winter::Object(Winter::ObjType::Null, 5)); - - if (vm.stack.top().type != Winter::ObjType::Null) { return 1; } - if (vm.stack.top().unwrap() != 5) { return 2; } - - return 0; -} - -constexpr int test_vmPop([[maybe_unused]] Willow::Test* test) { - auto vm = Winter::VM(false); - const auto val = vm.pop(); - - if (val.has_value()) { return 1; } - const auto rtErr = Winter::ErrType::RuntimeError; - if (val.error().type != rtErr) { return 2; } - - vm.push(Winter::Object(Winter::ObjType::Null, 5)); - const auto val2 = vm.pop(); - - if (!val2.has_value()) { return 3; } - if (val2.value().unwrap() != 5) { return 4; } - return 0; -} - -constexpr int test_vmDoString([[maybe_unused]] Willow::Test* test) { - return 0; -} - -constexpr int test_vmDoFile([[maybe_unused]] Willow::Test* test) { - return 0; -} - -constexpr int test_vmCall([[maybe_unused]] Willow::Test* test) { - return 0; -}