From 7ca0a9d688dedfa640e4d6cb713f90fd96735d74 Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Mon, 7 Apr 2025 18:35:16 +0300 Subject: [PATCH 01/10] nit --- include/bort/Parse/Parser.hpp | 9 +++++---- src/CLI/Main.cpp | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/bort/Parse/Parser.hpp b/include/bort/Parse/Parser.hpp index 35b10fb..f4d3d29 100644 --- a/include/bort/Parse/Parser.hpp +++ b/include/bort/Parse/Parser.hpp @@ -49,7 +49,7 @@ class Parser { auto parseParenExpr() -> Unique; /// identifier \n /// -> identifier - variable \n - /// -> identifier '(' expr, ... ')' - function call \n + /// -> functionCallExpr \n /// -> identifier indexationExpr \n /// -> identifier ('++' | '--') auto parseIdentifierExpr() -> Unique; @@ -68,7 +68,7 @@ class Parser { /// unaryOpExpr -> unaryOp valueExpression auto parseUnaryOpExpr() -> Unique; /// expression - /// -> valueExpression (binOp valueExpression ...) + /// -> valueExpression binOpRhs auto parseExpression() -> Unique; /// binOpRhs /// -> bipOp valueExpression (binOpRhs ...) @@ -80,7 +80,7 @@ class Parser { auto parseDeclspec() -> TypeRef; /// declaration -> declspec (varDecl | functionDecl) auto parseDeclarationStatement() -> Ref; - /// varDecl -> declspec identifier ';' + /// varDecl -> declspec identifier initializerExpr? ';' /// @todo declspec (identifier ('=' expr)?, ...) ';' auto parseVarDecl(TypeRef type, const Token& nameTok) -> Ref; @@ -93,13 +93,14 @@ class Parser { auto parseFunctionCallExpr(const Token& nameTok) -> Unique; /// indexationExpr -> nameTok '[' expr ']' - /// desugared into pointer arithmetic auto parseIndexationExpr(const Token& nameTok) -> Unique; /// statement \n /// -> expression ';' \n /// -> block \n /// -> ifStatement \n + /// -> whileStatement \n + /// -> returnStatement \n auto parseStatement() -> Ref; /// block /// -> '{' statement... '}' diff --git a/src/CLI/Main.cpp b/src/CLI/Main.cpp index d5cd83d..bdedcec 100644 --- a/src/CLI/Main.cpp +++ b/src/CLI/Main.cpp @@ -29,6 +29,8 @@ auto main(int argc, char* argv[]) -> int { // clang-format on cliParser.parse_positional("inputs"); + cliParser.positional_help("inputs"); + cliParser.show_positional_help(); auto result{ cliParser.parse(argc, argv) }; From b051f2f69464e0d2b3bbaf8acf656831dcc8043f Mon Sep 17 00:00:00 2001 From: Vladislav <67783125+Lolitron-0@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:42:40 +0300 Subject: [PATCH 02/10] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c8bfc24..e114125 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ bort uses cmake build system, so building pipeline is pretty standard. All depen ```bash git clone https://github.com/Lolitron-0/bort cd bort -cmake -S . -B build +cmake -S . -B build +сmake --build build ``` ## Usage From 62cd56dee9b313cf62a219665289979d3aaa452a Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Tue, 8 Apr 2025 16:27:48 +0300 Subject: [PATCH 03/10] optional tests in cmake --- CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c2b6ea..6f5e56e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ project(bort) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(BORT_BUILD_TESTS ON) + include_directories(include) set(SOURCES @@ -95,12 +97,14 @@ set(BOOST_INCLUDE_LIBRARIES functional;assert;stacktrace;range) FetchContent_Declare( Boost URL https://github.com/boostorg/boost/releases/download/boost-1.86.0/boost-1.86.0-cmake.tar.xz - DOWNLOAD_EXTRACT_TIMESTAMP ON -) + DOWNLOAD_EXTRACT_TIMESTAMP + ON) FetchContent_MakeAvailable(Boost) target_link_libraries(${PROJECT_NAME} PRIVATE Boost::functional Boost::assert Boost::stacktrace Boost::range) -add_subdirectory(tests) +if(${BORT_BUILD_TESTS}) + add_subdirectory(tests) +endif() From 28cce82b179570265d052ec54dd5a7da31de71cf Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Tue, 8 Apr 2025 16:30:40 +0300 Subject: [PATCH 04/10] optional tests --- .github/workflows/cmake-multi-platform.yml | 1 + CMakeLists.txt | 2 +- README.md | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 8f67436..7b60775 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -59,6 +59,7 @@ jobs: -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DBORT_BUILD_TESTS=ON -S ${{ github.workspace }} - name: Build diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f5e56e..0e2da50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(bort) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(BORT_BUILD_TESTS ON) +set(BORT_BUILD_TESTS OFF) include_directories(include) diff --git a/README.md b/README.md index e114125..c3556af 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ cmake -S . -B build сmake --build build ``` +To build bort with tests use `cmake -S . -B build -DBORT_BUILD_TESTS=ON` as configuration command (requires `python3` to be installed). + ## Usage After building, you can find binaries directly in build directory. You can start with: ```bash From d623d7e5aca01104f8ae267ddc401349c2708ec7 Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Mon, 7 Apr 2025 19:24:24 +0300 Subject: [PATCH 05/10] value categories --- dev.sh | 2 +- .../AST/Visitors/TypePropagationVisitor.hpp | 2 ++ include/bort/Parse/Parser.hpp | 9 +++--- src/AST/Visitors/TypePropagationVisitor.cpp | 22 +++++++++++++ src/Parse/Parser.cpp | 32 +++++++------------ 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/dev.sh b/dev.sh index f52469d..2b7357b 100755 --- a/dev.sh +++ b/dev.sh @@ -46,5 +46,5 @@ cp -f build/compile_commands.json . if [ $# -ne 0 ] && [ "$1" == "run" ]; then echo -e "----------------------------------\n" set -eux - ./build/bort --dump-ast --emit-ir --dump-codegen-info -o - ./tests/corpus/arrays.c + ./build/bort --dump-ast --emit-ir --dump-codegen-info -o - ./tests/corpus/expression.c fi diff --git a/include/bort/AST/Visitors/TypePropagationVisitor.hpp b/include/bort/AST/Visitors/TypePropagationVisitor.hpp index 8234f48..3ff065c 100644 --- a/include/bort/AST/Visitors/TypePropagationVisitor.hpp +++ b/include/bort/AST/Visitors/TypePropagationVisitor.hpp @@ -1,4 +1,5 @@ #pragma once +#include "bort/AST/ASTNode.hpp" #include "bort/AST/BinOpExpr.hpp" #include "bort/AST/IndexationExpr.hpp" #include "bort/AST/UnaryOpExpr.hpp" @@ -32,6 +33,7 @@ class TypePropagationVisitor : public StructureAwareASTVisitor { void visit(const Ref& indexationNode) override; void visit(const Ref& varDeclNode) override; + void assertLvalue(const Ref& node); void promoteAssignmentOperands(const TypeRef& lhsTy, TypeRef& rhsTy, const ASTDebugInfo& debugInfo); diff --git a/include/bort/Parse/Parser.hpp b/include/bort/Parse/Parser.hpp index f4d3d29..73540f2 100644 --- a/include/bort/Parse/Parser.hpp +++ b/include/bort/Parse/Parser.hpp @@ -48,7 +48,7 @@ class Parser { /// @todo type-casts -> '(' declspec ')' auto parseParenExpr() -> Unique; /// identifier \n - /// -> identifier - variable \n + /// -> varExpr \n /// -> functionCallExpr \n /// -> identifier indexationExpr \n /// -> identifier ('++' | '--') @@ -62,13 +62,12 @@ class Parser { auto parseValueExpression() -> Unique; /// sizeofExpr -> 'sizeof' (parenExpr | '(' declspec ')' ) auto parseSizeofExpr() -> Unique; - /// lvalue \n - /// -> identifier - auto tryParseLValue() -> std::optional>; + /// varExpr -> identifier + auto parseVarExpr() -> Unique; /// unaryOpExpr -> unaryOp valueExpression auto parseUnaryOpExpr() -> Unique; /// expression - /// -> valueExpression binOpRhs + /// -> valueExpression binOpRhs auto parseExpression() -> Unique; /// binOpRhs /// -> bipOp valueExpression (binOpRhs ...) diff --git a/src/AST/Visitors/TypePropagationVisitor.cpp b/src/AST/Visitors/TypePropagationVisitor.cpp index 5cfb406..92993ac 100644 --- a/src/AST/Visitors/TypePropagationVisitor.cpp +++ b/src/AST/Visitors/TypePropagationVisitor.cpp @@ -1,6 +1,8 @@ #include "bort/AST/Visitors/TypePropagationVisitor.hpp" #include "bort/AST/ASTDebugInfo.hpp" +#include "bort/AST/ASTNode.hpp" #include "bort/AST/NumberExpr.hpp" +#include "bort/AST/UnaryOpExpr.hpp" #include "bort/AST/Visitors/ASTVisitor.hpp" #include "bort/Basic/Casts.hpp" #include "bort/CLI/IO.hpp" @@ -126,6 +128,8 @@ void TypePropagationVisitor::visit(const Ref& binopNode) { } else if (binopNode->isLogical()) { binopNode->setType(IntType::get()); } else if (binopNode->getOp() == TokenKind::Assign) { + assertLvalue(binopNode->getLHS()); + auto lhsTy{ binopNode->getLHS()->getType() }; auto rhsTy{ binopNode->getRHS()->getType() }; @@ -141,6 +145,7 @@ void TypePropagationVisitor::visit(const Ref& unaryOpNode) { switch (unaryOpNode->getOp()) { case TokenKind::Amp: + assertLvalue(unaryOpNode->getOperand()); // for arrays it's address of first element if (auto arrOpType{ dynCastRef(type) }) { type = PointerType::get(arrOpType->getBaseType()); @@ -218,4 +223,21 @@ void TypePropagationVisitor::promoteAssignmentOperands( } } +void TypePropagationVisitor::assertLvalue(const Ref& node) { + if (node->getKind() == NodeKind::VariableExpr || + node->getKind() == NodeKind::IndexationExpr) { + return; + } + + if (auto unOpNode{ dynCastRef(node) }) { + if (unOpNode->getOp() == TokenKind::Star) { + return; + } + } + + Diagnostic::emitError(getASTRoot()->getNodeDebugInfo(node).token, + "Expected lvalue"); + throw FatalSemanticError(); +} + } // namespace bort::ast diff --git a/src/Parse/Parser.cpp b/src/Parse/Parser.cpp index 1c80018..eb9ffc1 100644 --- a/src/Parse/Parser.cpp +++ b/src/Parse/Parser.cpp @@ -119,21 +119,22 @@ auto Parser::parseIdentifierExpr() -> Unique { makeRef(std::string{ identifierTok.getStringView() })); } -auto Parser::tryParseLValue() - -> std::optional> { - if (curTok().is(TokenKind::Identifier)) { - return parseIdentifierExpr(); - } - return std::nullopt; +auto Parser::parseVarExpr() -> Unique { + bort_assert(curTok().is(TokenKind::Identifier), "Expected identifier"); + auto varTok{ curTok() }; + consumeToken(); + + return m_ASTRoot->registerNode( + ast::ASTDebugInfo{ varTok }, + makeRef(std::string{ varTok.getStringView() })); } auto Parser::parseValueExpression() -> std::unique_ptr { - if (auto lvalue{ tryParseLValue() }) { - return std::move(*lvalue); - } switch (curTok().getKind()) { + case TokenKind::Identifier: + return parseIdentifierExpr(); case TokenKind::LParen: return parseParenExpr(); case TokenKind::NumericLiteral: @@ -185,19 +186,8 @@ auto Parser::parseUnaryOpExpr() -> Unique { auto op{ curTok() }; consumeToken(); - Unique operand; - if (op.is(TokenKind::Amp)) { - auto lvalueOpt{ tryParseLValue() }; - if (!lvalueOpt) { - Diagnostic::emitError(curTok(), "Expected lvalue"); - return invalidNode(); - } - operand = std::move(*lvalueOpt); - } else { - operand = parseValueExpression(); - } return m_ASTRoot->registerNode( - ast::ASTDebugInfo{ op }, std::move(operand), op.getKind()); + ast::ASTDebugInfo{ op }, parseValueExpression(), op.getKind()); } auto Parser::parseExpression() -> std::unique_ptr { From 22d7da714f0e694eb5380d43c127cbf9d85774a2 Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Tue, 8 Apr 2025 16:25:28 +0300 Subject: [PATCH 06/10] WIP: for loops --- include/bort/Lex/Tokens.def | 1 + include/bort/Parse/Parser.hpp | 9 +++++--- src/Parse/Parser.cpp | 40 +++++++++++++++++++++++++++++------ tests/corpus/loops.c | 1 + 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/include/bort/Lex/Tokens.def b/include/bort/Lex/Tokens.def index 159ca19..c894d71 100644 --- a/include/bort/Lex/Tokens.def +++ b/include/bort/Lex/Tokens.def @@ -55,6 +55,7 @@ KEYWORD(const) KEYWORD(if) KEYWORD(else) KEYWORD(while) +KEYWORD(for) KEYWORD(return) KEYWORD(sizeof) diff --git a/include/bort/Parse/Parser.hpp b/include/bort/Parse/Parser.hpp index 73540f2..11e0568 100644 --- a/include/bort/Parse/Parser.hpp +++ b/include/bort/Parse/Parser.hpp @@ -77,10 +77,10 @@ class Parser { /// declspec -> ( 'int' | 'void' | 'char' ) ('*'...) /// @todo type qualifiers auto parseDeclspec() -> TypeRef; - /// declaration -> declspec (varDecl | functionDecl) + /// declaration -> declspec (varDecl ';' | functionDecl) auto parseDeclarationStatement() -> Ref; - /// varDecl -> declspec identifier initializerExpr? ';' - /// @todo declspec (identifier ('=' expr)?, ...) ';' + /// varDecl -> declspec identifier initializerExpr? + /// @todo declspec (identifier ('=' expr)?, ...) auto parseVarDecl(TypeRef type, const Token& nameTok) -> Ref; /// initializerList -> '{' number, ... '}' | stringLiteral @@ -96,6 +96,7 @@ class Parser { -> Unique; /// statement \n /// -> expression ';' \n + /// -> declarationStatement ';' \n /// -> block \n /// -> ifStatement \n /// -> whileStatement \n @@ -108,6 +109,8 @@ class Parser { auto parseIfStatement() -> Ref; /// whileStatement -> 'while' parenExpr block auto parseWhileStatement() -> Ref; + /// forStatement -> 'for' '(' varDecl ';' expr? ';' expr? ')' block + auto parseForStatement() -> Ref; /// returnStatement -> 'return' (expr)? ';' auto parseReturnStatement() -> Ref; diff --git a/src/Parse/Parser.cpp b/src/Parse/Parser.cpp index eb9ffc1..505c102 100644 --- a/src/Parse/Parser.cpp +++ b/src/Parse/Parser.cpp @@ -15,6 +15,7 @@ #include "bort/AST/VariableExpr.hpp" #include "bort/AST/WhileStmt.hpp" #include "bort/Basic/Assert.hpp" +#include "bort/Basic/Casts.hpp" #include "bort/Basic/Ref.hpp" #include "bort/CLI/IO.hpp" #include "bort/Frontend/Symbol.hpp" @@ -284,7 +285,15 @@ auto Parser::parseDeclarationStatement() -> Ref { return parseFunctionDecl(type, nameTok); } - return parseVarDecl(type, nameTok); + auto varDecl{ parseVarDecl(type, nameTok) }; + + if (curTok().isNot(TokenKind::Semicolon)) { + Diagnostic::emitError(curTok(), "Expected ';'"); + return invalidNode(); + } + consumeToken(); + + return varDecl; } auto Parser::parseVarDecl(TypeRef type, @@ -323,12 +332,6 @@ auto Parser::parseVarDecl(TypeRef type, } } - if (curTok().isNot(TokenKind::Semicolon)) { - Diagnostic::emitError(curTok(), "Expected ';'"); - return invalidNode(); - } - consumeToken(); - if (type->getKind() == TypeKind::Void) { Diagnostic::emitError(nameTok, "Variable of incomplete type 'void'"); return invalidNode(); @@ -522,6 +525,8 @@ auto Parser::parseStatement() -> Ref { return parseWhileStatement(); case TokenKind::KW_return: return parseReturnStatement(); + case TokenKind::KW_for: + return parseForStatement(); default: break; } @@ -629,4 +634,25 @@ auto Parser::parseReturnStatement() -> Ref { return m_ASTRoot->registerNode({ kwTok }, expr); } +auto Parser::parseForStatement() -> Ref { + bort_assert(curTok().is(TokenKind::KW_for), "Expected 'for'"); + consumeToken(); + + if (curTok().isNot(TokenKind::LParen)) { + Diagnostic::emitError(curTok(), "Expected '('"); + return invalidNode(); + } + consumeToken(); + + Ref init; + if (curTok().isNot(TokenKind::Semicolon)) { + // a little trick + init = dynCastRef(parseDeclarationStatement()); + if (!init) { + Diagnostic::emitError(curTok(), "Expected variable declaration"); + return invalidNode(); + } + } +} + } // namespace bort diff --git a/tests/corpus/loops.c b/tests/corpus/loops.c index 763d2be..b83b1dd 100644 --- a/tests/corpus/loops.c +++ b/tests/corpus/loops.c @@ -4,4 +4,5 @@ int main() { while (i < 1000000) { i = i + 1; } + } From 185a1975b163b4092867c82d9b6c48fe25e4cc0f Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Wed, 9 Apr 2025 00:36:28 +0300 Subject: [PATCH 07/10] language features: new operands, fixed logic expressions, for-loop --- include/bort/Codegen/RISCVCodegen.hpp | 10 ++ include/bort/IR/OpInst.hpp | 3 + include/bort/Lex/Tokens.def | 9 +- include/bort/Parse/Parser.hpp | 13 +- launch.json | 4 +- src/AST/Visitors/TypePropagationVisitor.cpp | 4 + src/CLI/Main.cpp | 19 +++ src/Codegen/RISCVCodegen.cpp | 54 ++++++- src/Codegen/RISCVPrinter.cpp | 15 +- src/IR/IRCodegen.cpp | 9 ++ src/IR/IRPrinter.cpp | 12 +- src/Parse/Parser.cpp | 167 ++++++++++++++------ tests/corpus/expression.c | 3 +- tests/corpus/loops.c | 5 +- 14 files changed, 258 insertions(+), 69 deletions(-) diff --git a/include/bort/Codegen/RISCVCodegen.hpp b/include/bort/Codegen/RISCVCodegen.hpp index e35f92b..1031618 100644 --- a/include/bort/Codegen/RISCVCodegen.hpp +++ b/include/bort/Codegen/RISCVCodegen.hpp @@ -86,6 +86,16 @@ struct RVInstInfo final : public ir::Metadata { std::string InstName; }; +struct RVOpAdditionalInfo final : public ir::Metadata { + explicit RVOpAdditionalInfo(bool isSingleOp) + : IsSingleOp{ isSingleOp } { + } + + [[nodiscard]] auto toString() const -> std::string override; + + bool IsSingleOp; +}; + struct RARSMacroDefinitions final : public ir::Metadata { [[nodiscard]] auto toString() const -> std::string override; diff --git a/include/bort/IR/OpInst.hpp b/include/bort/IR/OpInst.hpp index 14e8965..f1033a2 100644 --- a/include/bort/IR/OpInst.hpp +++ b/include/bort/IR/OpInst.hpp @@ -31,6 +31,9 @@ class OpInst final : public Instruction { void setSrc2(ValueRef value) { m_Operands[Src2Idx] = std::move(value); } + void setOp(TokenKind op) { + m_Op = op; + } static constexpr int Src1Idx{ 1 }; static constexpr int Src2Idx{ 2 }; diff --git a/include/bort/Lex/Tokens.def b/include/bort/Lex/Tokens.def index c894d71..713856a 100644 --- a/include/bort/Lex/Tokens.def +++ b/include/bort/Lex/Tokens.def @@ -39,10 +39,17 @@ PUNCT(Minus, "-") PUNCT(Star, "*") PUNCT(Div, "/") PUNCT(Pipe, "|") +PUNCT(Xor, "^") +PUNCT(Not, "!") +PUNCT(AmpAmp, "&&") +PUNCT(PipePipe, "||") PUNCT(PlusAssign, "+=") PUNCT(MinusAssign, "-=") -PUNCT(TimesAssign, "*=") +PUNCT(StarAssign, "*=") PUNCT(DivAssign, "/=") +PUNCT(AmpAssign, "&=") +PUNCT(XorAssign, "^=") +PUNCT(PipeAssign, "|=") PUNCT(Less, "<") PUNCT(Greater, ">") PUNCT(LessEqual, "<=") diff --git a/include/bort/Parse/Parser.hpp b/include/bort/Parse/Parser.hpp index 11e0568..0982ac2 100644 --- a/include/bort/Parse/Parser.hpp +++ b/include/bort/Parse/Parser.hpp @@ -46,12 +46,11 @@ class Parser { /// parenExpr \n /// -> '(' expression ')' \n /// @todo type-casts -> '(' declspec ')' - auto parseParenExpr() -> Unique; + auto parseParenExpr() -> Ref; /// identifier \n /// -> varExpr \n /// -> functionCallExpr \n - /// -> identifier indexationExpr \n - /// -> identifier ('++' | '--') + /// -> identifier indexationExpr auto parseIdentifierExpr() -> Unique; /// value expression \n /// -> number \n @@ -59,7 +58,7 @@ class Parser { /// -> sizeofExpr \n /// -> unaryOpExpr \n /// -> lvalue - auto parseValueExpression() -> Unique; + auto parseValueExpression() -> Ref; /// sizeofExpr -> 'sizeof' (parenExpr | '(' declspec ')' ) auto parseSizeofExpr() -> Unique; /// varExpr -> identifier @@ -68,12 +67,12 @@ class Parser { auto parseUnaryOpExpr() -> Unique; /// expression /// -> valueExpression binOpRhs - auto parseExpression() -> Unique; + auto parseExpression() -> Ref; /// binOpRhs /// -> bipOp valueExpression (binOpRhs ...) - auto parseBinOpRhs(Unique lhs, + auto parseBinOpRhs(Ref lhs, int32_t prevPrecedence = 0) - -> Unique; + -> Ref; /// declspec -> ( 'int' | 'void' | 'char' ) ('*'...) /// @todo type qualifiers auto parseDeclspec() -> TypeRef; diff --git a/launch.json b/launch.json index 66fa370..2b524ba 100644 --- a/launch.json +++ b/launch.json @@ -8,7 +8,9 @@ "program": "${workspaceFolder}/build/bort", "args": [ "--emit-ir", - "${workspaceFolder}/tests/corpus/functions.c" + "-o", + "-", + "${workspaceFolder}/tests/corpus/expression.c" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/src/AST/Visitors/TypePropagationVisitor.cpp b/src/AST/Visitors/TypePropagationVisitor.cpp index 92993ac..b0d371a 100644 --- a/src/AST/Visitors/TypePropagationVisitor.cpp +++ b/src/AST/Visitors/TypePropagationVisitor.cpp @@ -164,6 +164,10 @@ void TypePropagationVisitor::visit(const Ref& unaryOpNode) { throw FatalSemanticError(); } break; + case TokenKind::PlusPlus: + case TokenKind::MinusMinus: + assertLvalue(unaryOpNode->getOperand()); + break; default: break; } diff --git a/src/CLI/Main.cpp b/src/CLI/Main.cpp index bdedcec..a22937e 100644 --- a/src/CLI/Main.cpp +++ b/src/CLI/Main.cpp @@ -6,9 +6,28 @@ #include #include #include +#ifdef WIN32 +#include +#endif auto main(int argc, char* argv[]) -> int { +/// @todo: Make custom wrappers for fmt color formatters to make color +/// optional +#ifdef WIN32 + HANDLE hOut{ GetStdHandle(STD_OUTPUT_HANDLE) }; + HANDLE hErr{ GetStdHandle(STD_ERROR_HANDLE) }; + DWORD dwMode{}; + + GetConsoleMode(hOut, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, dwMode); + + GetConsoleMode(hErr, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hErr, dwMode); +#endif + bort::CLIOptions cliOptions; cxxopts::Options cliParser{ "bort", "Small-C to RISC-V compiler" }; diff --git a/src/Codegen/RISCVCodegen.cpp b/src/Codegen/RISCVCodegen.cpp index 2291ce2..864d410 100644 --- a/src/Codegen/RISCVCodegen.cpp +++ b/src/Codegen/RISCVCodegen.cpp @@ -49,6 +49,10 @@ auto RVInstInfo::toString() const -> std::string { return fmt::format("rv_ii .inst_name={}", InstName); } +auto RVOpAdditionalInfo::toString() const -> std::string { + return fmt::format("rv_oai .is_single_op={}", IsSingleOp); +} + auto RARSMacroDefinitions::toString() const -> std::string { return fmt::format("rars_macros .num_defined={}", Macros.size()); } @@ -80,11 +84,42 @@ class PreprocessPass : public InstructionVisitorBase { if (opInst->getOp() == TokenKind::Plus || opInst->getOp() == TokenKind::Amp || - opInst->getOp() == TokenKind::Pipe) { + opInst->getOp() == TokenKind::Pipe || + opInst->getOp() == TokenKind::Xor) { // add can be immediate break; } } + + // in RISC-V boolean stuff should be expressed through slt/sge/seqz + + auto addModInst{ [this](TokenKind op, auto&& inst) { + m_CurrentBBIter->insertAfter( + m_CurrentInstIter, + makeRef(op, inst->getDestination(), + inst->getDestination())); + } }; + if (opInst->getOp() == TokenKind::LessEqual) { + opInst->setOp(TokenKind::Greater); + addModInst(TokenKind::Not, opInst); + } else if (opInst->getOp() == TokenKind::GreaterEqual) { + opInst->setOp(TokenKind::Less); + addModInst(TokenKind::Not, opInst); + } else if (opInst->getOp() == TokenKind::Equals || + opInst->getOp() == TokenKind::NotEquals) { + if (opInst->getSrc2()) { + // c = a xor b + // c = (c ==/!= 0) + auto oldOp{ opInst->getOp() }; + opInst->setOp(TokenKind::Xor); + auto newInst{ makeRef(oldOp, opInst->getDestination(), + opInst->getDestination(), + nullptr) }; + newInst->addMDNode(RVOpAdditionalInfo{ true }); + m_CurrentBBIter->insertAfter(m_CurrentInstIter, + std::move(newInst)); + } + } } void visit(const Ref& unaryInst) override { @@ -195,12 +230,24 @@ class InstructionChoicePass : public InstructionVisitorBase { .Case(TokenKind::Div, "div") .Case(TokenKind::Less, "slt") .Case(TokenKind::Greater, "sgt") + .Case(TokenKind::Equals, "seqz") + .Case(TokenKind::NotEquals, "snez") .Case(TokenKind::Amp, "and") - .Case(TokenKind::Pipe, "or"); + .Case(TokenKind::Pipe, "or") + .Case(TokenKind::Xor, "xor"); } }; bort_assert(s_OpInstNames.Find(opInst->getOp()).has_value(), "Unknown op name"); + if (opInst->getOp() == TokenKind::Equals || + opInst->getOp() == TokenKind::NotEquals) { + if (auto src2Constant{ + dynCastRef(opInst->getSrc2()) }) { + bort_assert(src2Constant->getValue() == 0, + "Preprocess should leave only ==/!= 0"); + } + } + RVInstInfo info{ std::string{ s_OpInstNames.Find(opInst->getOp()).value() } }; if (isaRef(opInst->getSrc2())) { @@ -211,7 +258,8 @@ class InstructionChoicePass : public InstructionVisitorBase { void visit(const Ref& unaryInst) override { static constexpr cul::BiMap s_UnaryInstNames{ [](auto&& selector) { - return selector.Case(TokenKind::Minus, "neg"); + return selector.Case(TokenKind::Minus, "neg") + .Case(TokenKind::Not, "not"); } }; bort_assert(s_UnaryInstNames.Find(unaryInst->getOp()).has_value(), diff --git a/src/Codegen/RISCVPrinter.cpp b/src/Codegen/RISCVPrinter.cpp index ce3c688..d30ea6e 100644 --- a/src/Codegen/RISCVPrinter.cpp +++ b/src/Codegen/RISCVPrinter.cpp @@ -71,14 +71,21 @@ void Printer::run(ir::Module& module) { void Printer::visit(const Ref& opInst) { /// @todo MD node for immediatness, signedness, etc... auto* II{ opInst->getMDNode() }; + auto* OAI{ opInst->getMDNode() }; auto opName{ II->InstName }; auto src1Reg{ dynCastRef(opInst->getSrc()) }; auto src2Reg{ dynCastRef(opInst->getSrc2()) }; auto dstReg{ dynCastRef(opInst->getDestination()) }; - fmt::println(m_Stream, "{} {}, {}, {}", opName, - formatMachineValue(opInst->getDestination()), - formatMachineValue(opInst->getSrc()), - formatMachineValue(opInst->getSrc2())); + if (OAI && OAI->IsSingleOp) { + fmt::println(m_Stream, "{} {}, {}", opName, + formatMachineValue(opInst->getDestination()), + formatMachineValue(opInst->getSrc())); + } else { + fmt::println(m_Stream, "{} {}, {}, {}", opName, + formatMachineValue(opInst->getDestination()), + formatMachineValue(opInst->getSrc()), + formatMachineValue(opInst->getSrc2())); + } } void Printer::visit(const Ref& unaryInst) { diff --git a/src/IR/IRCodegen.cpp b/src/IR/IRCodegen.cpp index 6f29ad5..2f2e026 100644 --- a/src/IR/IRCodegen.cpp +++ b/src/IR/IRCodegen.cpp @@ -154,6 +154,15 @@ auto IRCodegen::visit(const Ref& unaryOpExpr) case TokenKind::Plus: result = addInstruction(makeRef(dst, operand)); break; + case TokenKind::PlusPlus: + result = addInstruction(makeRef( + TokenKind::Plus, operand, operand, IntegralConstant::getChar(1))); + break; + case TokenKind::MinusMinus: + result = + addInstruction(makeRef(TokenKind::Minus, operand, operand, + IntegralConstant::getChar(-1))); + break; default: result = addInstruction( makeRef(unaryOpExpr->getOp(), dst, operand)); diff --git a/src/IR/IRPrinter.cpp b/src/IR/IRPrinter.cpp index f173300..6b7b165 100644 --- a/src/IR/IRPrinter.cpp +++ b/src/IR/IRPrinter.cpp @@ -105,7 +105,10 @@ static auto styleInst(T&& name) { static constexpr cul::BiMap s_UnaryInstNames{ [](auto&& selector) { return selector.Case(TokenKind::Minus, "neg") .Case(TokenKind::Amp, "addr") - .Case(TokenKind::Star, "deref"); + .Case(TokenKind::Star, "deref") + .Case(TokenKind::PlusPlus, "inc") + .Case(TokenKind::MinusMinus, "dec") + .Case(TokenKind::Not, "not"); } }; static constexpr cul::BiMap s_OpInstNames{ [](auto&& selector) { @@ -115,8 +118,13 @@ static constexpr cul::BiMap s_OpInstNames{ [](auto&& selector) { .Case(TokenKind::Div, "div") .Case(TokenKind::Less, "slt") .Case(TokenKind::Greater, "sgt") + .Case(TokenKind::GreaterEqual, "sge") + .Case(TokenKind::LessEqual, "sle") + .Case(TokenKind::Equals, "seq") + .Case(TokenKind::NotEquals, "sne") .Case(TokenKind::Amp, "and") - .Case(TokenKind::Pipe, "or"); + .Case(TokenKind::Pipe, "or") + .Case(TokenKind::Xor, "xor"); } }; namespace bort { diff --git a/src/Parse/Parser.cpp b/src/Parse/Parser.cpp index 505c102..7bbbdf0 100644 --- a/src/Parse/Parser.cpp +++ b/src/Parse/Parser.cpp @@ -27,17 +27,18 @@ namespace bort { static constexpr auto s_BinopPrecedence{ frozen::make_unordered_map({ - { TokenKind::Plus, 20 }, - { TokenKind::Minus, 20 }, - { TokenKind::Star, 40 }, - { TokenKind::Div, 40 }, - { TokenKind::Less, 10 }, - { TokenKind::Greater, 10 }, - { TokenKind::LessEqual, 10 }, - { TokenKind::GreaterEqual, 10 }, - { TokenKind::Amp, 9 }, - { TokenKind::Pipe, 8 }, - { TokenKind::Assign, 5 }, + { TokenKind::Plus, 200 }, { TokenKind::Minus, 200 }, + { TokenKind::Star, 400 }, { TokenKind::Div, 400 }, + { TokenKind::Less, 100 }, { TokenKind::Greater, 100 }, + { TokenKind::LessEqual, 100 }, { TokenKind::GreaterEqual, 100 }, + { TokenKind::Equals, 95 }, { TokenKind::NotEquals, 95 }, + { TokenKind::Amp, 90 }, { TokenKind::Xor, 80 }, + { TokenKind::Pipe, 70 }, { TokenKind::AmpAmp, 65 }, + { TokenKind::PipePipe, 60 }, { TokenKind::Assign, 50 }, + { TokenKind::PlusAssign, 50 }, { TokenKind::MinusAssign, 50 }, + { TokenKind::StarAssign, 50 }, { TokenKind::DivAssign, 50 }, + { TokenKind::AmpAssign, 50 }, { TokenKind::XorAssign, 50 }, + { TokenKind::PipeAssign, 50 }, }) }; @@ -54,7 +55,7 @@ auto Parser::buildAST() -> Ref { break; } - auto node{ parseStatement() }; + auto node{ parseDeclarationStatement() }; if (!node) { break; } @@ -82,7 +83,7 @@ auto Parser::parseNumberExpr() -> Unique { ast::ASTDebugInfo{ token }, value, std::move(type)); } -auto Parser::parseParenExpr() -> Unique { +auto Parser::parseParenExpr() -> Ref { bort_assert(curTok().is(TokenKind::LParen), "Expected '('"); consumeToken(); @@ -130,8 +131,7 @@ auto Parser::parseVarExpr() -> Unique { makeRef(std::string{ varTok.getStringView() })); } -auto Parser::parseValueExpression() - -> std::unique_ptr { +auto Parser::parseValueExpression() -> Ref { switch (curTok().getKind()) { case TokenKind::Identifier: @@ -179,7 +179,8 @@ auto Parser::parseSizeofExpr() -> Unique { auto Parser::parseUnaryOpExpr() -> Unique { if (!curTok().isOneOf(TokenKind::Plus, TokenKind::Minus, TokenKind::Amp, - TokenKind::Star)) { + TokenKind::Star, TokenKind::PlusPlus, + TokenKind::MinusMinus, TokenKind::Not)) { Diagnostic::emitError(curTok(), "Expected unary operator in value expression"); return invalidNode(); @@ -191,7 +192,7 @@ auto Parser::parseUnaryOpExpr() -> Unique { ast::ASTDebugInfo{ op }, parseValueExpression(), op.getKind()); } -auto Parser::parseExpression() -> std::unique_ptr { +auto Parser::parseExpression() -> Ref { auto lhs{ parseValueExpression() }; if (!lhs) { return invalidNode(); @@ -206,9 +207,9 @@ static auto getTokPrecedence(const Token& tok) -> int32_t { return -1; } -auto Parser::parseBinOpRhs(std::unique_ptr lhs, +auto Parser::parseBinOpRhs(Ref lhs, int32_t prevPrecedence) - -> std::unique_ptr { + -> Ref { while (true) { // precedence or -1 if not a binop auto tokPrecedence{ getTokPrecedence(curTok()) }; @@ -218,7 +219,7 @@ auto Parser::parseBinOpRhs(std::unique_ptr lhs, auto binOp{ curTok() }; consumeToken(); - auto rhs{ parseValueExpression() }; + Ref rhs{ parseValueExpression() }; if (!rhs) { return invalidNode(); } @@ -235,9 +236,48 @@ auto Parser::parseBinOpRhs(std::unique_ptr lhs, } // now: (lhs binOp rhs) lookahead unparsed - lhs = m_ASTRoot->registerNode( - ast::ASTDebugInfo{ binOp }, std::move(lhs), std::move(rhs), - binOp.getKind()); + + // these are the same in IR + if (binOp.is(TokenKind::AmpAmp)) { + binOp.setKind(TokenKind::Amp); + } + if (binOp.is(TokenKind::PipePipe)) { + binOp.setKind(TokenKind::Pipe); + } + + auto getAssignTok{ [](TokenKind tok) -> std::optional { + switch (tok) { + case TokenKind::PlusAssign: + return TokenKind::Plus; + case TokenKind::MinusAssign: + return TokenKind::Minus; + case TokenKind::StarAssign: + return TokenKind::Star; + case TokenKind::DivAssign: + return TokenKind::Div; + case TokenKind::AmpAssign: + return TokenKind::Amp; + case TokenKind::XorAssign: + return TokenKind::Xor; + case TokenKind::PipeAssign: + return TokenKind::Div; + default: + return std::nullopt; + } + } }; + auto assignTok{ getAssignTok(binOp.getKind()) }; + + if (assignTok) { + auto actualOp{ m_ASTRoot->registerNode( + ast::ASTDebugInfo{ binOp }, lhs, std::move(rhs), *assignTok) }; + lhs = m_ASTRoot->registerNode( + ast::ASTDebugInfo{ binOp }, std::move(lhs), std::move(actualOp), + TokenKind::Assign); + } else { + lhs = m_ASTRoot->registerNode( + ast::ASTDebugInfo{ binOp }, std::move(lhs), std::move(rhs), + binOp.getKind()); + } } } @@ -478,32 +518,6 @@ auto Parser::parseIndexationExpr(const Token& nameTok) ast::ASTDebugInfo{ nameTok }, makeRef(std::string{ nameTok.getStringView() })) }; - // Ref varAddr{ - // m_ASTRoot->registerNode( - // ast::ASTDebugInfo{ indexationStartTok }, std::move(varExpr), - // TokenKind::Amp) - // }; - // - // auto varAddrDeref{ m_ASTRoot->registerNode( - // ast::ASTDebugInfo{ indexationStartTok }, varAddr, - // TokenKind::Star) }; - // - // auto sizeofExpr{ m_ASTRoot->registerNode( - // ast::ASTDebugInfo{ indexationStartTok }, std::move(varAddrDeref), - // TokenKind::KW_sizeof) }; - // - // auto indexMultiplication{ m_ASTRoot->registerNode( - // ast::ASTDebugInfo{ indexationStartTok }, std::move(sizeofExpr), - // std::move(expr), TokenKind::Star) }; - // - // auto pointerAddition{ m_ASTRoot->registerNode( - // ast::ASTDebugInfo{ indexationStartTok }, varAddr, - // std::move(indexMultiplication), TokenKind::Plus) }; - // - // auto dereference{ m_ASTRoot->registerNode( - // ast::ASTDebugInfo{ indexationStartTok }, - // std::move(pointerAddition), TokenKind::Star) }; - return m_ASTRoot->registerNode( ast::ASTDebugInfo{ indexationStartTok }, std::move(varExpr), std::move(idxExpr)); @@ -634,8 +648,16 @@ auto Parser::parseReturnStatement() -> Ref { return m_ASTRoot->registerNode({ kwTok }, expr); } +/// desugared into +/// { +/// init; +/// while (condition) { +/// ... +/// update; +/// } auto Parser::parseForStatement() -> Ref { bort_assert(curTok().is(TokenKind::KW_for), "Expected 'for'"); + auto forTok{ curTok() }; consumeToken(); if (curTok().isNot(TokenKind::LParen)) { @@ -653,6 +675,53 @@ auto Parser::parseForStatement() -> Ref { return invalidNode(); } } + + Ref condition; + if (curTok().isNot(TokenKind::Semicolon)) { + condition = parseExpression(); + if (curTok().isNot(TokenKind::Semicolon)) { + Diagnostic::emitError(curTok(), "Expected ';'"); + return invalidNode(); + } + consumeToken(); + } + + if (!condition) { + condition = m_ASTRoot->registerNode( + ast::ASTDebugInfo{ curTok() }, 1, IntType::get()); + } + + Ref update; + auto updateTok{ curTok() }; + if (curTok().isNot(TokenKind::Semicolon)) { + update = parseExpression(); + } + + if (curTok().isNot(TokenKind::RParen)) { + Diagnostic::emitError(curTok(), "Expected ')'"); + return invalidNode(); + } + consumeToken(); + + if (curTok().isNot(TokenKind::LBrace)) { + Diagnostic::emitError(curTok(), "Expected '{{'"); + return invalidNode(); + } + auto body{ parseBlock() }; + + if (update) { + body->pushChild(m_ASTRoot->registerNode( + ast::ASTDebugInfo{ updateTok }, std::move(update))); + } + + auto outerBlock{ m_ASTRoot->registerNode( + ast::ASTDebugInfo{ curTok() }) }; + outerBlock->pushChild(init); + outerBlock->pushChild(m_ASTRoot->registerNode( + ast::ASTDebugInfo{ forTok }, std::move(condition), + std::move(body))); + + return outerBlock; } } // namespace bort diff --git a/tests/corpus/expression.c b/tests/corpus/expression.c index 34c411e..41e5d73 100644 --- a/tests/corpus/expression.c +++ b/tests/corpus/expression.c @@ -3,7 +3,8 @@ int main() { var = 1; char cvar; cvar = 1; - if (var + cvar < 2) { + cvar ^= 1; + if (!(var + cvar == 2)) { var = 1; } else { var = 2; diff --git a/tests/corpus/loops.c b/tests/corpus/loops.c index b83b1dd..a167aa4 100644 --- a/tests/corpus/loops.c +++ b/tests/corpus/loops.c @@ -2,7 +2,10 @@ int main() { int i; i = 0; while (i < 1000000) { - i = i + 1; + i += 1; } + for (int j = 0; j < 1000000; ++j) { + i = i - 1; + } } From 67b8bb5750300aedf2a9a335a2efc629aa423203 Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Wed, 9 Apr 2025 00:37:18 +0300 Subject: [PATCH 08/10] removed todos --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c3556af..4596e68 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,8 @@ ## Global TODOs - Global variables -- for-loops - goto - switch -- compound assignment (`+=`, `*=`, ...) - compound variable declaration (`int a = 5, b, c = d;`) - testing From 5f4d56906ff4d19deaef2d1c1a5e33f453030c28 Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Wed, 9 Apr 2025 01:49:44 +0300 Subject: [PATCH 09/10] language feature: break/continue --- README.md | 2 +- dev.sh | 2 +- include/bort/AST/ASTNode.hpp | 2 + include/bort/AST/BinOpExpr.hpp | 3 + include/bort/AST/BreakStmt.hpp | 16 ++++ include/bort/AST/ContinueStmt.hpp | 16 ++++ include/bort/AST/Visitors/ASTPrinter.hpp | 4 + include/bort/AST/Visitors/ASTVisitor.hpp | 8 ++ include/bort/AST/Visitors/Utils.hpp | 10 +++ include/bort/IR/IRCodegen.hpp | 7 +- include/bort/IR/Metadata.hpp | 5 ++ include/bort/IR/Value.hpp | 5 ++ include/bort/Lex/Tokens.def | 8 ++ include/bort/Parse/Parser.hpp | 11 ++- launch.json | 2 +- src/AST/Visitors/ASTPrinter.cpp | 11 +++ src/Codegen/RISCVCodegen.cpp | 18 +++-- src/IR/IRCodegen.cpp | 55 ++++++++++++-- src/IR/IRPrinter.cpp | 5 +- src/Parse/Parser.cpp | 93 ++++++++++++++++++++---- tests/corpus/loops.c | 9 +++ 21 files changed, 258 insertions(+), 34 deletions(-) create mode 100644 include/bort/AST/BreakStmt.hpp create mode 100644 include/bort/AST/ContinueStmt.hpp diff --git a/README.md b/README.md index 4596e68..ac1d1a6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **bort** is a cross-platform Small-C language cross compiler for RISC-V architecture. ## Global TODOs -- Global variables +- global variables - goto - switch - compound variable declaration (`int a = 5, b, c = d;`) diff --git a/dev.sh b/dev.sh index 2b7357b..7b2889d 100755 --- a/dev.sh +++ b/dev.sh @@ -46,5 +46,5 @@ cp -f build/compile_commands.json . if [ $# -ne 0 ] && [ "$1" == "run" ]; then echo -e "----------------------------------\n" set -eux - ./build/bort --dump-ast --emit-ir --dump-codegen-info -o - ./tests/corpus/expression.c + ./build/bort --dump-ast --emit-ir --dump-codegen-info -o - ./tests/corpus/loops.c fi diff --git a/include/bort/AST/ASTNode.hpp b/include/bort/AST/ASTNode.hpp index 8c40d9a..4950a8a 100644 --- a/include/bort/AST/ASTNode.hpp +++ b/include/bort/AST/ASTNode.hpp @@ -32,6 +32,8 @@ enum class NodeKind { IfStmt, WhileStmt, ReturnStmt, + BreakStmt, + ContinueStmt, ASTRoot, NUM_NODES }; diff --git a/include/bort/AST/BinOpExpr.hpp b/include/bort/AST/BinOpExpr.hpp index e982b90..0ab24a7 100644 --- a/include/bort/AST/BinOpExpr.hpp +++ b/include/bort/AST/BinOpExpr.hpp @@ -16,6 +16,9 @@ class BinOpExpr final : public ExpressionNode { /// @todo bitwise operators return m_Op == TokenKind::Plus || m_Op == TokenKind::Minus || m_Op == TokenKind::Star || m_Op == TokenKind::Div || + m_Op == TokenKind::Mod || m_Op == TokenKind::LShift || + m_Op == TokenKind::RShift || m_Op == TokenKind::Amp || + m_Op == TokenKind::Pipe || m_Op == TokenKind::Xor || m_Op == TokenKind::Amp || m_Op == TokenKind::Pipe; } [[nodiscard]] constexpr auto isLogical() const -> bool { diff --git a/include/bort/AST/BreakStmt.hpp b/include/bort/AST/BreakStmt.hpp new file mode 100644 index 0000000..6010681 --- /dev/null +++ b/include/bort/AST/BreakStmt.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "bort/AST/ASTNode.hpp" + +namespace bort::ast { + +class BreakStmt final : public Statement { +private: + BreakStmt() + : Statement{ NodeKind::BreakStmt } { + } + +public: + friend class ASTRoot; +}; + +} // namespace bort::ast diff --git a/include/bort/AST/ContinueStmt.hpp b/include/bort/AST/ContinueStmt.hpp new file mode 100644 index 0000000..8fe3682 --- /dev/null +++ b/include/bort/AST/ContinueStmt.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "bort/AST/ASTNode.hpp" + +namespace bort::ast { + +class ContinueStmt final : public Statement { +private: + ContinueStmt() + : Statement{ NodeKind::ContinueStmt } { + } + +public: + friend class ASTRoot; +}; + +} // namespace bort::ast diff --git a/include/bort/AST/Visitors/ASTPrinter.hpp b/include/bort/AST/Visitors/ASTPrinter.hpp index cc1621b..d35936b 100644 --- a/include/bort/AST/Visitors/ASTPrinter.hpp +++ b/include/bort/AST/Visitors/ASTPrinter.hpp @@ -1,4 +1,6 @@ #pragma once +#include "bort/AST/BreakStmt.hpp" +#include "bort/AST/ContinueStmt.hpp" #include "bort/AST/IndexationExpr.hpp" #include "bort/AST/InitializerList.hpp" #include "bort/AST/UnaryOpExpr.hpp" @@ -32,6 +34,8 @@ class ASTPrinter : public StructureAwareASTVisitor { void visit(const Ref& ifStmtNode) override; void visit(const Ref& whileStmtNode) override; void visit(const Ref& returnStmtNode) override; + void visit(const Ref& breakStmtNode) override; + void visit(const Ref& continueStmtNode) override; void visit(const Ref& functionCallExpr) override; void push(); diff --git a/include/bort/AST/Visitors/ASTVisitor.hpp b/include/bort/AST/Visitors/ASTVisitor.hpp index c37a4e6..8c2eb5a 100644 --- a/include/bort/AST/Visitors/ASTVisitor.hpp +++ b/include/bort/AST/Visitors/ASTVisitor.hpp @@ -23,6 +23,8 @@ class ExpressionStmt; class IfStmt; class WhileStmt; class ReturnStmt; +class BreakStmt; +class ContinueStmt; class ASTVisitorBase { public: @@ -80,6 +82,12 @@ class StructureAwareASTVisitor : public ASTVisitorBase { virtual void visit(const Ref& /* charNode */) { // leaf } + virtual void visit(const Ref& /* breakNode */) { + // leaf + } + virtual void visit(const Ref& /* continueNode */) { + // leaf + } virtual void visit(const Ref& varDeclNode); virtual void visit(const Ref& initializerListNode); virtual void visit(const Ref& indexationExpr); diff --git a/include/bort/AST/Visitors/Utils.hpp b/include/bort/AST/Visitors/Utils.hpp index 2839219..d42dd57 100644 --- a/include/bort/AST/Visitors/Utils.hpp +++ b/include/bort/AST/Visitors/Utils.hpp @@ -2,6 +2,8 @@ #include "bort/AST/ASTNode.hpp" #include "bort/AST/BinOpExpr.hpp" +#include "bort/AST/BreakStmt.hpp" +#include "bort/AST/ContinueStmt.hpp" #include "bort/AST/ExpressionStmt.hpp" #include "bort/AST/FunctionCallExpr.hpp" #include "bort/AST/FunctionDecl.hpp" @@ -83,6 +85,14 @@ auto callHandler(const Ref& node, F&& visit) { bort_assert_nomsg(dynCastRef(node)); return visit(dynCastRef(node)); break; + case NodeKind::BreakStmt: + bort_assert_nomsg(dynCastRef(node)); + return visit(dynCastRef(node)); + break; + case NodeKind::ContinueStmt: + bort_assert_nomsg(dynCastRef(node)); + return visit(dynCastRef(node)); + break; case NodeKind::ASTRoot: bort_assert_nomsg(dynCastRef(node)); return visit(dynCastRef(node)); diff --git a/include/bort/IR/IRCodegen.hpp b/include/bort/IR/IRCodegen.hpp index c9db2c3..559d96e 100644 --- a/include/bort/IR/IRCodegen.hpp +++ b/include/bort/IR/IRCodegen.hpp @@ -1,6 +1,8 @@ #pragma once #include "bort/AST/ASTNode.hpp" #include "bort/AST/BinOpExpr.hpp" +#include "bort/AST/BreakStmt.hpp" +#include "bort/AST/ContinueStmt.hpp" #include "bort/AST/FunctionCallExpr.hpp" #include "bort/AST/IfStmt.hpp" #include "bort/AST/IndexationExpr.hpp" @@ -47,11 +49,14 @@ class IRCodegen { auto visit(const Ref& ifStmtNode) -> ValueRef; auto visit(const Ref& whileStmtNode) -> ValueRef; auto visit(const Ref& returnStmt) -> ValueRef; + auto visit(const Ref& breakStmt) -> ValueRef; + auto visit(const Ref& continueStmt) -> ValueRef; auto visit(const Ref& funcCallExpr) -> ValueRef; auto genBranchFromCondition(const Ref& cond, bool negate = false) -> Ref; - auto genArrayPtr(const ValueRef& arr) -> std::pair, ValueRef>; + auto genArrayPtr(const ValueRef& arr) + -> std::pair, ValueRef>; template requires std::is_base_of_v diff --git a/include/bort/IR/Metadata.hpp b/include/bort/IR/Metadata.hpp index 1fbdac7..8342dfe 100644 --- a/include/bort/IR/Metadata.hpp +++ b/include/bort/IR/Metadata.hpp @@ -40,6 +40,11 @@ class MDList { return node; } + template + void remove() { + m_Registry.erase(typeid(T)); + } + auto nodes() { return std::views::values(m_Registry); } diff --git a/include/bort/IR/Value.hpp b/include/bort/IR/Value.hpp index 9db2b21..969e32a 100644 --- a/include/bort/IR/Value.hpp +++ b/include/bort/IR/Value.hpp @@ -40,6 +40,11 @@ class Value { return m_MDList->get(); } + template + void removeMDNode() { + m_MDList->remove(); + } + template auto getMDNode() const -> const T* { return m_MDList->get(); diff --git a/include/bort/Lex/Tokens.def b/include/bort/Lex/Tokens.def index 713856a..9188807 100644 --- a/include/bort/Lex/Tokens.def +++ b/include/bort/Lex/Tokens.def @@ -38,9 +38,13 @@ PUNCT(Plus, "+") PUNCT(Minus, "-") PUNCT(Star, "*") PUNCT(Div, "/") +PUNCT(Mod, "%") PUNCT(Pipe, "|") PUNCT(Xor, "^") +PUNCT(LShift, "<<") +PUNCT(RShift, ">>") PUNCT(Not, "!") +PUNCT(Tilde, "~") PUNCT(AmpAmp, "&&") PUNCT(PipePipe, "||") PUNCT(PlusAssign, "+=") @@ -50,6 +54,8 @@ PUNCT(DivAssign, "/=") PUNCT(AmpAssign, "&=") PUNCT(XorAssign, "^=") PUNCT(PipeAssign, "|=") +PUNCT(LShiftAssign, "<<=") +PUNCT(RShiftAssign, ">>=") PUNCT(Less, "<") PUNCT(Greater, ">") PUNCT(LessEqual, "<=") @@ -61,6 +67,8 @@ KEYWORD(char) KEYWORD(const) KEYWORD(if) KEYWORD(else) +KEYWORD(break) +KEYWORD(continue) KEYWORD(while) KEYWORD(for) KEYWORD(return) diff --git a/include/bort/Parse/Parser.hpp b/include/bort/Parse/Parser.hpp index 0982ac2..8e772ac 100644 --- a/include/bort/Parse/Parser.hpp +++ b/include/bort/Parse/Parser.hpp @@ -1,5 +1,7 @@ #pragma once #include "bort/AST/ASTNode.hpp" +#include "bort/AST/BreakStmt.hpp" +#include "bort/AST/ContinueStmt.hpp" #include "bort/AST/ExpressionNode.hpp" #include "bort/AST/FunctionCallExpr.hpp" #include "bort/AST/FunctionDecl.hpp" @@ -50,7 +52,7 @@ class Parser { /// identifier \n /// -> varExpr \n /// -> functionCallExpr \n - /// -> identifier indexationExpr + /// -> identifier indexationExpr auto parseIdentifierExpr() -> Unique; /// value expression \n /// -> number \n @@ -100,7 +102,13 @@ class Parser { /// -> ifStatement \n /// -> whileStatement \n /// -> returnStatement \n + /// -> breakStatement \n + /// -> continueStatement auto parseStatement() -> Ref; + /// breakStatement -> 'break' ';' + auto parseBreakStatement() -> Ref; + /// continueStatement -> 'continue' ';' + auto parseContinueStatement() -> Ref; /// block /// -> '{' statement... '}' auto parseBlock() -> Unique; @@ -143,6 +151,7 @@ class Parser { Ref m_ASTRoot; bool m_ASTInvalid{ false }; bool m_DiagnosticSilenced{ false }; + bool m_InsideLoop{ false }; }; } // namespace bort diff --git a/launch.json b/launch.json index 2b524ba..aa2ed9f 100644 --- a/launch.json +++ b/launch.json @@ -10,7 +10,7 @@ "--emit-ir", "-o", "-", - "${workspaceFolder}/tests/corpus/expression.c" + "${workspaceFolder}/tests/corpus/loops.c" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/src/AST/Visitors/ASTPrinter.cpp b/src/AST/Visitors/ASTPrinter.cpp index 39c1d77..32ce250 100644 --- a/src/AST/Visitors/ASTPrinter.cpp +++ b/src/AST/Visitors/ASTPrinter.cpp @@ -36,6 +36,8 @@ static constexpr cul::BiMap s_NodeKindNames{ [](auto&& selector) { .Case(NodeKind::IfStmt, "IfStmt") .Case(NodeKind::WhileStmt, "WhileStmt") .Case(NodeKind::ReturnStmt, "ReturnStmt") + .Case(NodeKind::BreakStmt, "BreakStmt") + .Case(NodeKind::ContinueStmt, "ContinueStmt") .Case(NodeKind::Block, "Block") .Case(NodeKind::ASTRoot, "ASTRoot"); } }; @@ -191,4 +193,13 @@ void ASTPrinter::visit(const Ref& returnStmtNode) { dumpNodeInfo(returnStmtNode); dump("Expression", returnStmtNode->getExpression()); } + +void ASTPrinter::visit(const Ref& breakStmtNode) { + dumpNodeInfo(breakStmtNode); +} + +void ASTPrinter::visit(const Ref& continueStmtNode) { + dumpNodeInfo(continueStmtNode); +} + } // namespace bort::ast diff --git a/src/Codegen/RISCVCodegen.cpp b/src/Codegen/RISCVCodegen.cpp index 864d410..2c63fea 100644 --- a/src/Codegen/RISCVCodegen.cpp +++ b/src/Codegen/RISCVCodegen.cpp @@ -85,8 +85,10 @@ class PreprocessPass : public InstructionVisitorBase { if (opInst->getOp() == TokenKind::Plus || opInst->getOp() == TokenKind::Amp || opInst->getOp() == TokenKind::Pipe || - opInst->getOp() == TokenKind::Xor) { - // add can be immediate + opInst->getOp() == TokenKind::Xor || + opInst->getOp() == TokenKind::LShift || + opInst->getOp() == TokenKind::RShift) { + // these can be immediate break; } } @@ -228,24 +230,24 @@ class InstructionChoicePass : public InstructionVisitorBase { .Case(TokenKind::Minus, "sub") .Case(TokenKind::Star, "mul") .Case(TokenKind::Div, "div") + .Case(TokenKind::Mod, "rem") .Case(TokenKind::Less, "slt") .Case(TokenKind::Greater, "sgt") .Case(TokenKind::Equals, "seqz") .Case(TokenKind::NotEquals, "snez") .Case(TokenKind::Amp, "and") .Case(TokenKind::Pipe, "or") - .Case(TokenKind::Xor, "xor"); + .Case(TokenKind::Xor, "xor") + .Case(TokenKind::Xor, "sll") + .Case(TokenKind::Xor, "sra"); } }; bort_assert(s_OpInstNames.Find(opInst->getOp()).has_value(), "Unknown op name"); if (opInst->getOp() == TokenKind::Equals || opInst->getOp() == TokenKind::NotEquals) { - if (auto src2Constant{ - dynCastRef(opInst->getSrc2()) }) { - bort_assert(src2Constant->getValue() == 0, - "Preprocess should leave only ==/!= 0"); - } + bort_assert(!opInst->getSrc2(), + "Preprocess should leave only ==/!= nullptr"); } RVInstInfo info{ std::string{ diff --git a/src/IR/IRCodegen.cpp b/src/IR/IRCodegen.cpp index 2f2e026..aea4aa4 100644 --- a/src/IR/IRCodegen.cpp +++ b/src/IR/IRCodegen.cpp @@ -50,6 +50,18 @@ class StoreSync : public Metadata { ValueRef m_Loc; }; +struct BrToLoopEndMDTag : public MDTag { + explicit BrToLoopEndMDTag() + : MDTag{ "br_to_loop_end" } { + } +}; + +struct BrToLoopStartMDTag : public MDTag { + explicit BrToLoopStartMDTag() + : MDTag{ "br_to_loop_cond" } { + } +}; + auto IRCodegen::genBranchFromCondition( const Ref& cond, bool negate) -> Ref { @@ -300,17 +312,17 @@ auto IRCodegen::visit(const Ref& ifStmtNode) -> ValueRef { genBranchFromCondition(ifStmtNode->getCondition())) }; bort_assert_nomsg(thenBr); - pushBB("_false"); + pushBB("_false_if"); genericVisit(ifStmtNode->getElseBlock()); auto endBr{ addInstruction(makeRef()) }; bort_assert_nomsg(endBr); - pushBB("_true"); + pushBB("_true_if"); auto lastBBIt{ m_Module.getLastBBIt() }; thenBr->setTarget(&*lastBBIt); genericVisit(ifStmtNode->getThenBlock()); - pushBB("_end"); + pushBB("_end_if"); lastBBIt = m_Module.getLastBBIt(); endBr->setTarget(&*lastBBIt); return nullptr; @@ -328,19 +340,34 @@ void IRCodegen::pushBB(std::string postfix, std::string name) { auto IRCodegen::visit(const Ref& whileStmtNode) -> ValueRef { - pushBB("_cond"); + pushBB("_cond_loop"); auto& condBB{ *m_Module.getLastBBIt() }; auto endBr{ addInstruction( genBranchFromCondition(whileStmtNode->getCondition(), true)) }; - pushBB("_body"); + pushBB("_body_loop"); genericVisit(whileStmtNode->getBody()); auto loopBr{ addInstruction(makeRef()) }; loopBr->setTarget(&condBB); - pushBB("_end"); - endBr->setTarget(&*m_Module.getLastBBIt()); + pushBB("_end_loop"); + auto& endBB{ *m_Module.getLastBBIt() }; + endBr->setTarget(&endBB); + + for (auto&& bb : *m_Module.getLastFunctionIt()) { + for (auto&& inst : bb) { + if (auto br{ dynCastRef(inst) }) { + if (br->getMDNode()) { + br->setTarget(&endBB); + br->removeMDNode(); + } else if (br->getMDNode()) { + br->setTarget(&condBB); + br->removeMDNode(); + } + } + } + } return nullptr; } @@ -385,4 +412,18 @@ void IRCodegen::processNewInst(const Ref& instruction) { } } +auto IRCodegen::visit(const Ref& /*breakStmt*/) + -> ValueRef { + auto newInst{ addInstruction(makeRef()) }; + newInst->addMDNode(BrToLoopEndMDTag{}); + return nullptr; +} + +auto IRCodegen::visit(const Ref& /*continueStmt*/) + -> ValueRef { + auto newInst{ addInstruction(makeRef()) }; + newInst->addMDNode(BrToLoopStartMDTag{}); + return nullptr; +} + } // namespace bort::ir diff --git a/src/IR/IRPrinter.cpp b/src/IR/IRPrinter.cpp index 6b7b165..b640950 100644 --- a/src/IR/IRPrinter.cpp +++ b/src/IR/IRPrinter.cpp @@ -116,6 +116,7 @@ static constexpr cul::BiMap s_OpInstNames{ [](auto&& selector) { .Case(TokenKind::Minus, "sub") .Case(TokenKind::Star, "mul") .Case(TokenKind::Div, "div") + .Case(TokenKind::Mod, "rem") .Case(TokenKind::Less, "slt") .Case(TokenKind::Greater, "sgt") .Case(TokenKind::GreaterEqual, "sge") @@ -124,7 +125,9 @@ static constexpr cul::BiMap s_OpInstNames{ [](auto&& selector) { .Case(TokenKind::NotEquals, "sne") .Case(TokenKind::Amp, "and") .Case(TokenKind::Pipe, "or") - .Case(TokenKind::Xor, "xor"); + .Case(TokenKind::Xor, "xor") + .Case(TokenKind::LShift, "sl") + .Case(TokenKind::RShift, "sr"); } }; namespace bort { diff --git a/src/Parse/Parser.cpp b/src/Parse/Parser.cpp index 7bbbdf0..51710ce 100644 --- a/src/Parse/Parser.cpp +++ b/src/Parse/Parser.cpp @@ -3,6 +3,7 @@ #include "bort/AST/ASTNode.hpp" #include "bort/AST/BinOpExpr.hpp" #include "bort/AST/Block.hpp" +#include "bort/AST/ContinueStmt.hpp" #include "bort/AST/ExpressionNode.hpp" #include "bort/AST/ExpressionStmt.hpp" #include "bort/AST/FunctionCallExpr.hpp" @@ -27,18 +28,20 @@ namespace bort { static constexpr auto s_BinopPrecedence{ frozen::make_unordered_map({ - { TokenKind::Plus, 200 }, { TokenKind::Minus, 200 }, - { TokenKind::Star, 400 }, { TokenKind::Div, 400 }, - { TokenKind::Less, 100 }, { TokenKind::Greater, 100 }, - { TokenKind::LessEqual, 100 }, { TokenKind::GreaterEqual, 100 }, - { TokenKind::Equals, 95 }, { TokenKind::NotEquals, 95 }, - { TokenKind::Amp, 90 }, { TokenKind::Xor, 80 }, - { TokenKind::Pipe, 70 }, { TokenKind::AmpAmp, 65 }, - { TokenKind::PipePipe, 60 }, { TokenKind::Assign, 50 }, - { TokenKind::PlusAssign, 50 }, { TokenKind::MinusAssign, 50 }, - { TokenKind::StarAssign, 50 }, { TokenKind::DivAssign, 50 }, - { TokenKind::AmpAssign, 50 }, { TokenKind::XorAssign, 50 }, - { TokenKind::PipeAssign, 50 }, + { TokenKind::Star, 400 }, { TokenKind::Div, 400 }, + { TokenKind::Mod, 400 }, { TokenKind::Plus, 200 }, + { TokenKind::Minus, 200 }, { TokenKind::LShift, 150 }, + { TokenKind::RShift, 150 }, { TokenKind::Less, 100 }, + { TokenKind::Greater, 100 }, { TokenKind::LessEqual, 100 }, + { TokenKind::GreaterEqual, 100 }, { TokenKind::Equals, 95 }, + { TokenKind::NotEquals, 95 }, { TokenKind::Amp, 90 }, + { TokenKind::Xor, 80 }, { TokenKind::Pipe, 70 }, + { TokenKind::AmpAmp, 65 }, { TokenKind::PipePipe, 60 }, + { TokenKind::Assign, 50 }, { TokenKind::PlusAssign, 50 }, + { TokenKind::MinusAssign, 50 }, { TokenKind::StarAssign, 50 }, + { TokenKind::DivAssign, 50 }, { TokenKind::AmpAssign, 50 }, + { TokenKind::XorAssign, 50 }, { TokenKind::PipeAssign, 50 }, + { TokenKind::LShiftAssign, 50 }, { TokenKind::RShiftAssign, 50 }, }) }; @@ -180,7 +183,8 @@ auto Parser::parseSizeofExpr() -> Unique { auto Parser::parseUnaryOpExpr() -> Unique { if (!curTok().isOneOf(TokenKind::Plus, TokenKind::Minus, TokenKind::Amp, TokenKind::Star, TokenKind::PlusPlus, - TokenKind::MinusMinus, TokenKind::Not)) { + TokenKind::MinusMinus, TokenKind::Not, + TokenKind::Tilde)) { Diagnostic::emitError(curTok(), "Expected unary operator in value expression"); return invalidNode(); @@ -188,6 +192,12 @@ auto Parser::parseUnaryOpExpr() -> Unique { auto op{ curTok() }; consumeToken(); + + // same in IR + if (op.is(TokenKind::Tilde)) { + op.setKind(TokenKind::Not); + } + return m_ASTRoot->registerNode( ast::ASTDebugInfo{ op }, parseValueExpression(), op.getKind()); } @@ -245,6 +255,7 @@ auto Parser::parseBinOpRhs(Ref lhs, binOp.setKind(TokenKind::Pipe); } + // try to desugar compound assignment auto getAssignTok{ [](TokenKind tok) -> std::optional { switch (tok) { case TokenKind::PlusAssign: @@ -261,6 +272,10 @@ auto Parser::parseBinOpRhs(Ref lhs, return TokenKind::Xor; case TokenKind::PipeAssign: return TokenKind::Div; + case TokenKind::LShiftAssign: + return TokenKind::LShift; + case TokenKind::RShiftAssign: + return TokenKind::RShift; default: return std::nullopt; } @@ -541,6 +556,10 @@ auto Parser::parseStatement() -> Ref { return parseReturnStatement(); case TokenKind::KW_for: return parseForStatement(); + case TokenKind::KW_break: + return parseBreakStatement(); + case TokenKind::KW_continue: + return parseContinueStatement(); default: break; } @@ -557,6 +576,48 @@ auto Parser::parseStatement() -> Ref { ast::ASTDebugInfo{ stmtTok }, std::move(expression)); } +auto Parser::parseBreakStatement() -> Ref { + bort_assert(curTok().is(TokenKind::KW_break), "Expected 'break'"); + + if (!m_InsideLoop) { + Diagnostic::emitError(curTok(), "'break' outside of loop"); + return invalidNode(); + } + + auto breakTok{ curTok() }; + consumeToken(); + + if (curTok().isNot(TokenKind::Semicolon)) { + Diagnostic::emitError(curTok(), "Expected ';'"); + return invalidNode(); + } + consumeToken(); + + return m_ASTRoot->registerNode( + ast::ASTDebugInfo{ breakTok }); +} + +auto Parser::parseContinueStatement() -> Ref { + bort_assert(curTok().is(TokenKind::KW_continue), "Expected 'continue'"); + + if (!m_InsideLoop) { + Diagnostic::emitError(curTok(), "'break' outside of loop"); + return invalidNode(); + } + + auto continueTok{ curTok() }; + consumeToken(); + + if (curTok().isNot(TokenKind::Semicolon)) { + Diagnostic::emitError(curTok(), "Expected ';'"); + return invalidNode(); + } + consumeToken(); + + return m_ASTRoot->registerNode( + ast::ASTDebugInfo{ continueTok }); +} + auto Parser::parseBlock() -> Unique { bort_assert(curTok().is(TokenKind::LBrace), "Expected '{'"); auto block{ m_ASTRoot->registerNode( @@ -622,7 +683,10 @@ auto Parser::parseWhileStatement() -> Ref { return invalidNode(); } auto condition{ parseParenExpr() }; + + m_InsideLoop = true; auto body{ parseBlock() }; + m_InsideLoop = false; return m_ASTRoot->registerNode( ast::ASTDebugInfo{ whileTok }, std::move(condition), @@ -707,7 +771,10 @@ auto Parser::parseForStatement() -> Ref { Diagnostic::emitError(curTok(), "Expected '{{'"); return invalidNode(); } + + m_InsideLoop = true; auto body{ parseBlock() }; + m_InsideLoop = false; if (update) { body->pushChild(m_ASTRoot->registerNode( diff --git a/tests/corpus/loops.c b/tests/corpus/loops.c index a167aa4..e1f676e 100644 --- a/tests/corpus/loops.c +++ b/tests/corpus/loops.c @@ -3,9 +3,18 @@ int main() { i = 0; while (i < 1000000) { i += 1; + + if (i % 2374 == 0) { + break; + } } for (int j = 0; j < 1000000; ++j) { + if (i % 2 == 0) { + i -= 3; + continue; + } + i = i - 1; } } From 962f950bb319ee7c89f450adb6e22294f0a052a4 Mon Sep 17 00:00:00 2001 From: Lolitron-0 Date: Wed, 9 Apr 2025 02:02:48 +0300 Subject: [PATCH 10/10] optional tests fix --- CMakeLists.txt | 3 ++- dev.sh | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e2da50..4c3aad5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(bort) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(BORT_BUILD_TESTS OFF) +option(BORT_BUILD_TESTS "Enable testing (requires python3)") include_directories(include) @@ -106,5 +106,6 @@ target_link_libraries(${PROJECT_NAME} PRIVATE Boost::functional Boost::assert Boost::stacktrace Boost::range) if(${BORT_BUILD_TESTS}) + message(STATUS "Testing enabled") add_subdirectory(tests) endif() diff --git a/dev.sh b/dev.sh index 7b2889d..5e792c6 100755 --- a/dev.sh +++ b/dev.sh @@ -35,6 +35,7 @@ fi cmake -S . \ -B build \ -G Ninja \ + -DBORT_BUILD_TESTS=ON \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -Wall -Wextra -pedantic" \