diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a9a568b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + pull_request: + branches: + - "**" + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y wget lsb-release software-properties-common gnupg catch2 + + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 22 + + echo "LLVM_DIR=/usr/lib/llvm-22/lib/cmake/llvm" >> $GITHUB_ENV + + - name: Build and Test + run: | + cmake . + make + ./bin/tests diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..64c1a57 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 3.31.6) + +project(alpha) + + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +include_directories(${CMAKE_SOURCE_DIR}/src) + +find_package(LLVM REQUIRED CONFIG) + +add_definitions(${LLVM_DEFINITIONS}) +llvm_map_components_to_libnames(llvm_libs + core + support + irreader + codegen + asmprinter + target + native + TargetParser +) + + +file(GLOB_RECURSE ALL_SRC_CPP CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") +list(FILTER ALL_SRC_CPP EXCLUDE REGEX ".*main\\.cpp$") + +file(GLOB_RECURSE ALL_SRC_HPP CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp") + +add_library(alpha_lib STATIC ${ALL_SRC_CPP} ${ALL_SRC_HPP}) + +target_include_directories(alpha_lib PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${LLVM_INCLUDE_DIRS} +) +target_link_libraries(alpha_lib PUBLIC ${llvm_libs}) + +# Main compiler + +add_executable(main src/main.cpp) + +target_link_libraries(main PRIVATE alpha_lib) + +set_target_properties(main PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin +) + +# Tests +find_package(Catch2 3 REQUIRED) + +enable_testing() +file(GLOB_RECURSE ALL_TEST_CPP CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp") + + +add_executable(tests + ${ALL_TEST_CPP} +) + +target_link_libraries(tests PRIVATE + alpha_lib + Catch2::Catch2WithMain +) + +get_target_property(Catch2_INCLUDE_DIR Catch2::Catch2 INTERFACE_INCLUDE_DIRECTORIES) +target_include_directories(tests PRIVATE ${Catch2_INCLUDE_DIR}) + +include(Catch) +catch_discover_tests(tests) + +set_target_properties(tests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin +) \ No newline at end of file diff --git a/README.md b/README.md index f65da79..7caf99d 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,17 @@ uint8 b = a + 1; uint8 c = a + b + 1; ``` +## Printing + +Values can be printed to standard out using a print statement, proceeded by the value wanted to print. + +``` +print 1; + +uint8 a = 2; +print a; +``` + ## Examples Examples can be found in the `/examples` folder. @@ -79,6 +90,7 @@ String segments are converted to a list of tokens. Representing the smallest seg | Identifier | `IDENTIFIER` | Variable identifier | | Number | `NUMBER` | Integer | | Return | `RETURN` | Returns a value from a function | +| Print | `PRINT` | Prints a value | ## Syntax Analyser @@ -90,6 +102,7 @@ Combines tokens into statements. | Assignment | `ASSIGNMENT` | Sets a variable to a value | | Return | `RETURN` | Returns a value from a function | | Addition | `ADDITION` | Adds two values and assigns them to an identifier | +| Print | `PRINT` | Prints the value after it | ## `LLVM` Intermediate Representation diff --git a/src/generation/generator.hpp b/src/generation/generator.hpp index 49e42a4..75c95d8 100644 --- a/src/generation/generator.hpp +++ b/src/generation/generator.hpp @@ -21,20 +21,16 @@ #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/IR/Constants.h" #include "llvm/IR/Instruction.h" -#include "llvm/IR/LegacyPassManager.h" -#include "llvm/IR/Type.h" #include "llvm/IR/Value.h" #include "llvm/MC/TargetRegistry.h" -#include "llvm/Support/FileSystem.h" #include "llvm/Support/Program.h" -#include "llvm/Target/TargetMachine.h" -#include "llvm/Target/TargetOptions.h" #include #include #include #include #include #include +#include #include #include #include @@ -60,9 +56,7 @@ class Generator { llvm::InitializeNativeTargetAsmParser(); } - void compile() { - - llvm::LLVMContext context; + std::unique_ptr buildModule(llvm::LLVMContext& context) { std::unique_ptr module = std::make_unique("build", context); @@ -364,6 +358,7 @@ class Generator { } builder.store(builder.add(lhs, rhs, outName), outUint.getAlloc()); + break; } case StatementType::PRINT: { const PrintStatement& printStatement = @@ -412,8 +407,16 @@ class Generator { builder.createReturn(returnValue); } + return module; + } + + void print_module(std::unique_ptr module) { std::cout << "-- LLVM IR --" << std::endl; module->print(llvm::outs(), nullptr); + } + + void compile(llvm::LLVMContext& context, + std::unique_ptr module) { auto targetTriple = llvm::Triple(llvm::sys::getDefaultTargetTriple()); module->setTargetTriple(targetTriple); @@ -426,7 +429,7 @@ class Generator { auto CPU = "generic"; auto features = ""; llvm::TargetOptions opt; - std::optional RM = std::nullopt; + std::optional RM = llvm::Reloc::PIC_; llvm::TargetMachine* targetMachine = target->createTargetMachine(targetTriple, CPU, features, opt, RM); diff --git a/src/lexer/lexer.hpp b/src/lexer/lexer.hpp index b2d6088..20c99fb 100644 --- a/src/lexer/lexer.hpp +++ b/src/lexer/lexer.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include "./string_converter.hpp" @@ -59,7 +58,7 @@ class Lexer { tokens.addReturn(ReturnToken()); } else if (buffer == "print") { tokens.addPrint(PrintToken()); - } else { + } else if (buffer.size() != 0) { tokens.addIdentifier(IdentifierToken(buffer)); } diff --git a/src/main.cpp b/src/main.cpp index 8b85e2e..965be31 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "./io/file_reader.hpp" #include "./lexer/lexer.hpp" @@ -17,6 +18,14 @@ int main(const int argc, char* argv[]) { return 1; } + bool verbose = false; + + if (argc == 3) { + if (std::string(argv[2]) == "-v") { + verbose = true; + } + } + FileReader fileReader(argv[1]); if (!fileReader.isOpen()) { std::cerr << "Input file failed to open\n"; @@ -25,14 +34,26 @@ int main(const int argc, char* argv[]) { Lexer lexer(fileReader.readAll()); TokenContainer tokens = lexer.makeTokenList(); - tokens.print(); + if (verbose) { + tokens.print(); + } AbstractSyntaxTree ast(tokens); Program program = ast.parse(); - program.print(); + if (verbose) { + program.print(); + } Generator generator(program); generator.init(); - generator.compile(); + + llvm::LLVMContext context; + std::unique_ptr module = generator.buildModule(context); + + if (verbose) { + generator.print_module(std::move(module)); + } + + generator.compile(context, std::move(module)); } \ No newline at end of file diff --git a/src/syntax_analyser/abstract_syntax_tree.cpp b/src/syntax_analyser/abstract_syntax_tree.cpp index afcce01..319ced6 100644 --- a/src/syntax_analyser/abstract_syntax_tree.cpp +++ b/src/syntax_analyser/abstract_syntax_tree.cpp @@ -118,8 +118,6 @@ std::vector> AbstractSyntaxTree::evaluateOperations( const Token& secondToken = static_cast(tokens[tokensIndex + 1].get()); - std::cout << secondToken.tokenType << "\n"; - if (secondToken.tokenType != TokenType::NUMBER && secondToken.tokenType != TokenType::IDENTIFIER) { throw std::runtime_error( @@ -132,9 +130,6 @@ std::vector> AbstractSyntaxTree::evaluateOperations( std::make_unique(outputIdentifier), std::make_unique( (static_cast(secondToken)).value))); - - std::cout << outputIdentifier << " + " - << (static_cast(secondToken)).value << "\n"; } if (secondToken.tokenType == TokenType::IDENTIFIER) { @@ -160,10 +155,8 @@ Program AbstractSyntaxTree::parse() { const Token& token = this->tokenContainer.view(i); buffer.push_back(token); - std::cout << "Reading " << token.tokenType << std::endl; if (token.tokenType == TokenType::END_OF_LINE) { - std::cout << "Reading end of line\n"; if (buffer[0].get().tokenType == TokenType::PRIMITIVE && buffer[1].get().tokenType == TokenType::IDENTIFIER && buffer[2].get().tokenType == TokenType::OPERATOR) { @@ -246,6 +239,5 @@ Program AbstractSyntaxTree::parse() { buffer.clear(); } } - std::cout << "program size: " << program.size() << "\n"; return program; } \ No newline at end of file diff --git a/src/syntax_analyser/statement/addition/addition.hpp b/src/syntax_analyser/statement/addition/addition.hpp index e94c873..ef2bcf7 100644 --- a/src/syntax_analyser/statement/addition/addition.hpp +++ b/src/syntax_analyser/statement/addition/addition.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include "syntax_analyser/statement/statement.hpp" #include "syntax_analyser/statement/value/identifier/identifier.hpp" diff --git a/src/syntax_analyser/statement/print/print.hpp b/src/syntax_analyser/statement/print/print.hpp index 77944b0..6478fcc 100644 --- a/src/syntax_analyser/statement/print/print.hpp +++ b/src/syntax_analyser/statement/print/print.hpp @@ -1,8 +1,8 @@ #pragma once +#include -#include "syntax_analyser/statement/assignment/assignment_type.hpp" #include "syntax_analyser/statement/statement.hpp" -#include "syntax_analyser/statement/value/identifier/identifier.hpp" +#include "syntax_analyser/statement/value/value.hpp" class PrintStatement : public Statement { public: diff --git a/tests/system.cpp b/tests/system.cpp new file mode 100644 index 0000000..b231b0b --- /dev/null +++ b/tests/system.cpp @@ -0,0 +1,81 @@ +#include + +#include "generation/generator.hpp" +#include "io/file_reader.hpp" +#include +#include +#include +#include +#include +#include +#include + +class CompilerBuildListener : public Catch::EventListenerBase { +public: + using Catch::EventListenerBase::EventListenerBase; + void testRunStarting(const Catch::TestRunInfo& testRunInfo) override { + if (std::filesystem::exists("./bin/main")) { + return; + } + std::cerr << "Compiler not found"; + std::exit(1); + } +}; + +CATCH_REGISTER_LISTENER(CompilerBuildListener) + +std::string exec(std::string cmd) { + std::array buffer; + std::string result; + + std::unique_ptr pipe(popen(cmd.c_str(), "r"), + pclose); + + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + return result; +} + +void checkOutput(std::string filePath, std::string expectedOut) {} + +TEST_CASE("Run \"./examples/addition.lang\"", "[system]") { + std::system("./bin/main ./examples/addition.lang"); + int out = std::system("./main.out"); + REQUIRE(out != -1); + REQUIRE(WIFEXITED(out)); + REQUIRE(WEXITSTATUS(out) == 5); +}; + +TEST_CASE("Run \"./examples/integer_primitives.lang\"", "[system]") { + std::system("./bin/main ./examples/integer_primitives.lang"); + int out = std::system("./main.out"); + REQUIRE(out != -1); + REQUIRE(WIFEXITED(out)); + REQUIRE(WEXITSTATUS(out) == 0); +}; + +TEST_CASE("Run \"./examples/print.lang\"", "[system]") { + std::system("./bin/main ./examples/print.lang"); + std::string out = exec("./main.out"); + + REQUIRE(out == "1\n2\n"); +}; + +TEST_CASE("Run \"./examples/transitive_assignment.lang\"", "[system]") { + std::system("./bin/main ./examples/transitive_assignment.lang"); + int out = std::system("./main.out"); + + REQUIRE(out != -1); + REQUIRE(WIFEXITED(out)); + REQUIRE(WEXITSTATUS(out) == 1); +}; + +TEST_CASE("Run \"./examples/variable_declaration.lang\"", "[system]") { + std::system("./bin/main ./examples/variable_declaration.lang"); + int out = std::system("./main.out"); + + REQUIRE(out != -1); + REQUIRE(WIFEXITED(out)); + REQUIRE(WEXITSTATUS(out) == 1); +};