diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b3825c7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: + - "**" + - "!main" + 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 15da581..f65da79 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ The goal of this langauge is to allow for the build up highly nested types, and The primitves aim to be informative to ensure that there is no ambiguity on how the primitive will function. -| Keyword | Full Name | Size | -| ------- | ----------------------- | -------- | -| `uint8` | Unsigned Integer 8-bits | `1 byte` | +| Keyword | Full Name | Size (bytes) | +| -------- | ------------------------ | ------------ | +| `uint8` | Unsigned Integer 8-bits | `1` | +| `uint16` | Unsigned Integer 16-bits | `2` | +| `uint32` | Unsigned Integer 32-bits | `4` | +| `uint64` | Unsigned Integer 64-bits | `8` | # Syntax diff --git a/examples/integer_primitives.lang b/examples/integer_primitives.lang new file mode 100644 index 0000000..cf839ce --- /dev/null +++ b/examples/integer_primitives.lang @@ -0,0 +1,4 @@ +uint8 a = 255; +uint16 b = 65535; +uint32 c = 4294967295; +uint64 d = 18446744073709551615; \ No newline at end of file diff --git a/examples/print.lang b/examples/print.lang new file mode 100644 index 0000000..4acdaeb --- /dev/null +++ b/examples/print.lang @@ -0,0 +1,4 @@ +print 1; + +uint8 a = 2; +print a; \ No newline at end of file diff --git a/src/generation/builder/builder.hpp b/src/generation/builder/builder.hpp index 3f1139d..0e78906 100644 --- a/src/generation/builder/builder.hpp +++ b/src/generation/builder/builder.hpp @@ -3,6 +3,7 @@ #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" @@ -24,6 +25,9 @@ class Builder { } llvm::IntegerType* getUint8() const { return llvm::Type::getInt8Ty(context); } + llvm::PointerType* getUint8Ptr() const { + return llvm::PointerType::get(this->context, 0); + } llvm::ConstantInt* createConst8(uint8_t value) { return llvm::ConstantInt::get(this->getUint8(), value); @@ -45,6 +49,14 @@ class Builder { return llvm::ConstantInt::get(this->getUint32(), value); }; + llvm::IntegerType* getUint64() const { + return llvm::Type::getInt64Ty(context); + } + + llvm::ConstantInt* createConst64(uint64_t value) { + return llvm::ConstantInt::get(this->getUint64(), value); + }; + llvm::ReturnInst* createReturn(llvm::Value* value) { return this->irBuilder.CreateRet(value); }; @@ -57,15 +69,24 @@ class Builder { return this->irBuilder.CreateStore(value, out); }; - llvm::LoadInst* load(llvm::Type* type, llvm::Value* value) { - return this->irBuilder.CreateLoad(type, value); + llvm::LoadInst* load(llvm::Type* type, llvm::Value* value, std::string name) { + return this->irBuilder.CreateLoad(type, value, name); }; - llvm::Value* add(llvm::Value* lhs, llvm::Value* rhs) { - return this->irBuilder.CreateAdd(lhs, rhs); + llvm::Value* add(llvm::Value* lhs, llvm::Value* rhs, std::string name) { + return this->irBuilder.CreateAdd(lhs, rhs, name); }; llvm::Value* zext(llvm::Value* in, llvm::Type* outType) { return this->irBuilder.CreateZExt(in, outType); }; + + llvm::Value* createGlobalStringPtr(std::string str) { + return this->irBuilder.CreateGlobalString(str); + }; + + llvm::CallInst* createCall(llvm::Function* function, + std::vector args) { + return this->irBuilder.CreateCall(function, args); + } }; \ No newline at end of file diff --git a/src/generation/generator.hpp b/src/generation/generator.hpp index 0d90b1f..49e42a4 100644 --- a/src/generation/generator.hpp +++ b/src/generation/generator.hpp @@ -1,5 +1,9 @@ #include "../syntax_analyser/program/program.hpp" #include "generation/builder/builder.hpp" +#include "generation/primitives/primitive.hpp" +#include "generation/primitives/uint16.hpp" +#include "generation/primitives/uint32.hpp" +#include "generation/primitives/uint64.hpp" #include "generation/primitives/uint8.hpp" #include "syntax_analyser/statement/addition/addition.hpp" #include "syntax_analyser/statement/assignment/assignment.hpp" @@ -8,11 +12,13 @@ #include "syntax_analyser/statement/assignment/number/number.hpp" #include "syntax_analyser/statement/initialisation/initialisation.hpp" #include "syntax_analyser/statement/primitives/primitive_type.hpp" +#include "syntax_analyser/statement/print/print.hpp" #include "syntax_analyser/statement/return/return.hpp" #include "syntax_analyser/statement/statement.hpp" #include "syntax_analyser/statement/value/identifier/identifier.hpp" #include "syntax_analyser/statement/value/number/number.hpp" #include "syntax_analyser/statement/value/value.hpp" +#include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/IR/Constants.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/LegacyPassManager.h" @@ -39,6 +45,7 @@ #include #include #include +#include class Generator { private: @@ -72,7 +79,20 @@ class Generator { llvm::BasicBlock::Create(context, "entry", mainFunc); builder.setInsertPoint(mainEntry); - std::map> symbols; + // Initialise print + auto* charPtrType = builder.getUint8Ptr(); + + std::vector PrintfArgsTypes = {charPtrType}; + + llvm::FunctionType* PrintfType = + llvm::FunctionType::get(builder.getUint32(), PrintfArgsTypes, true); + + llvm::Function* PrintfFunc = llvm::Function::Create( + PrintfType, llvm::Function::ExternalLinkage, "printf", *module); + + // End print init + + std::map> symbols; bool hasMainReturn = false; @@ -84,14 +104,30 @@ class Generator { const InitialisationStatement& initialisationStatement = static_cast(statement); + std::string identifierName = initialisationStatement.identifier->name; + switch (initialisationStatement.type) { case StatementPrimitiveType::UINT8: { - std::string identifierName = - initialisationStatement.identifier->name; - symbols.emplace(identifierName, std::make_unique( builder, identifierName)); + break; + } + case StatementPrimitiveType::UINT16: { + symbols.emplace(identifierName, std::make_unique( + builder, identifierName)); + break; + } + + case StatementPrimitiveType::UINT32: { + symbols.emplace(identifierName, std::make_unique( + builder, identifierName)); + break; + } + + case StatementPrimitiveType::UINT64: { + symbols.emplace(identifierName, std::make_unique( + builder, identifierName)); break; } } @@ -108,10 +144,53 @@ class Generator { static_cast( assignmentStatement); - std::unique_ptr& uint8 = + std::unique_ptr& prim = symbols.at(assignmentNumberStatement.identifier.name); - uint8->storeValue(assignmentNumberStatement.value.value); + switch (prim->getType()) { + case BuilderPrimitiveType::UINT: { + BuilderUintPrimitive& uintPrim = + static_cast(*prim); + + switch (uintPrim.getUintType()) { + case BuilderUintType::UINT8: { + BuilderUint8& uint8Prim = + static_cast(uintPrim); + + uint8Prim.storeValue( + assignmentNumberStatement.value.value); + break; + } + + case BuilderUintType::UINT16: { + BuilderUint16& uint16Prim = + static_cast(uintPrim); + + uint16Prim.storeValue( + assignmentNumberStatement.value.value); + break; + } + + case BuilderUintType::UINT32: { + BuilderUint32& uint32Prim = + static_cast(uintPrim); + + uint32Prim.storeValue( + assignmentNumberStatement.value.value); + break; + } + + case BuilderUintType::UINT64: { + BuilderUint64& uint64Prim = + static_cast(uintPrim); + + uint64Prim.storeValue( + assignmentNumberStatement.value.value); + break; + } + } + } + } break; } @@ -121,13 +200,27 @@ class Generator { static_cast( assignmentStatement); - std::unique_ptr& in = + std::unique_ptr& inBase = symbols.at(assignmentIdentifierStatement.value.name); - std::unique_ptr& out = + if (inBase->getType() != BuilderPrimitiveType::UINT) { + throw std::runtime_error("Cannot add non-uint type"); + } + + BuilderUintPrimitive& in = + static_cast(*inBase); + + std::unique_ptr& outBase = symbols.at(assignmentIdentifierStatement.identifier.name); - out->assignValue(*in); + if (outBase->getType() != BuilderPrimitiveType::UINT) { + throw std::runtime_error("Cannot add non-uint type"); + } + + BuilderUintPrimitive& out = + static_cast(*outBase); + + out.assignValue(in); break; } @@ -155,11 +248,19 @@ class Generator { const IdentifierValue& identifierValue = static_cast(*returnStatement.value); - std::unique_ptr& returnBuilder = + std::unique_ptr& returnBuilder = symbols.at(identifierValue.name); - llvm::Value* rawOut = - builder.load(builder.getUint8(), returnBuilder->getAlloc()); + if (returnBuilder->getType() != BuilderPrimitiveType::UINT) { + throw std::runtime_error("Cannot return non-uint type"); + } + + BuilderUintPrimitive& returnBuilderUint = + static_cast(*returnBuilder); + + llvm::Value* rawOut = builder.load( + returnBuilderUint.getLlvmIntegerType(), + returnBuilderUint.getAlloc(), identifierValue.name + "_load"); returnValue = builder.zext(rawOut, builder.getUint32()); } @@ -176,22 +277,42 @@ class Generator { const AdditionStatement& additionStatement = static_cast(statement); - std::unique_ptr& out = + std::unique_ptr& out = symbols.at(additionStatement.identifier.name); + if (out->getType() != BuilderPrimitiveType::UINT) { + throw std::runtime_error("Cannot assign addition to non-uint type"); + } + + BuilderUintPrimitive& outUint = + static_cast(*out); + llvm::Value* lhs; llvm::Value* rhs; + std::string outName = "add_"; + switch (additionStatement.lhs->statementValueType) { case StatementValueType::IDENTIFIER: { const IdentifierValue& lhsIdentifierValue = static_cast( *additionStatement.lhs.get()); - std::unique_ptr& lhsValue = + std::unique_ptr& lhsValue = symbols.at(lhsIdentifierValue.name); - lhs = builder.load(builder.getUint8(), lhsValue->getAlloc()); + if (lhsValue->getType() != BuilderPrimitiveType::UINT) { + throw std::runtime_error("Cannot add non-uint types"); + } + + BuilderUintPrimitive& lhsUintValue = + static_cast(*lhsValue); + + lhs = builder.load(lhsUintValue.getLlvmIntegerType(), + lhsUintValue.getAlloc(), + lhsIdentifierValue.name + "_load"); + + outName += lhsIdentifierValue.name; break; } @@ -200,20 +321,34 @@ class Generator { static_cast(*additionStatement.lhs.get()); lhs = builder.createConst8(lhsNumberValue.value); + outName += "const"; break; } } + outName += "_and_"; + switch (additionStatement.rhs->statementValueType) { case StatementValueType::IDENTIFIER: { const IdentifierValue& rhsIdentifierValue = static_cast( *additionStatement.rhs.get()); - std::unique_ptr& rhsValue = + std::unique_ptr& rhsValue = symbols.at(rhsIdentifierValue.name); - rhs = builder.load(builder.getUint8(), rhsValue->getAlloc()); + if (rhsValue->getType() != BuilderPrimitiveType::UINT) { + throw std::runtime_error("Cannot add non-uint types"); + } + + BuilderUintPrimitive& rhsValueUint = + static_cast(*rhsValue); + + rhs = builder.load(rhsValueUint.getLlvmIntegerType(), + rhsValueUint.getAlloc(), + rhsIdentifierValue.name + "_load"); + + outName += rhsIdentifierValue.name; break; } @@ -222,11 +357,53 @@ class Generator { static_cast(*additionStatement.rhs.get()); rhs = builder.createConst8(rhsNumberValue.value); + outName += "const"; + + break; + } + } + + builder.store(builder.add(lhs, rhs, outName), outUint.getAlloc()); + } + case StatementType::PRINT: { + const PrintStatement& printStatement = + static_cast(statement); + + llvm::Value* out; + + switch (printStatement.value->statementValueType) { + case StatementValueType::NUMBER: { + const NumberValue& numberValue = + static_cast(*printStatement.value); + + out = builder.createConst32(numberValue.value); break; } + + case StatementValueType::IDENTIFIER: { + const IdentifierValue& identifierValue = + static_cast(*printStatement.value); + + std::unique_ptr& printBuilder = + symbols.at(identifierValue.name); + + if (printBuilder->getType() != BuilderPrimitiveType::UINT) { + throw std::runtime_error("Cannot print non-uint type"); + } + + BuilderUintPrimitive& printBuilderUint = + static_cast(*printBuilder); + + out = builder.load(printBuilderUint.getLlvmIntegerType(), + printBuilderUint.getAlloc(), "temp"); + } } - builder.store(builder.add(lhs, rhs), out->getAlloc()); + llvm::Value* FormatStr = builder.createGlobalStringPtr("%llu\n"); + + std::vector Args = {FormatStr, out}; + + builder.createCall(PrintfFunc, Args); } } } diff --git a/src/generation/primitives/primitive.hpp b/src/generation/primitives/primitive.hpp new file mode 100644 index 0000000..d53ddae --- /dev/null +++ b/src/generation/primitives/primitive.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "generation/builder/builder.hpp" +#include "generation/primitives/primitive_type.hpp" + +class BuilderPrimitive { +protected: + BuilderPrimitiveType type; + + Builder& builder; + +public: + BuilderPrimitive(BuilderPrimitiveType type, Builder& builder) + : type(type), builder(builder) {}; + + BuilderPrimitiveType getType() { return this->type; } +}; \ No newline at end of file diff --git a/src/generation/primitives/primitive_type.hpp b/src/generation/primitives/primitive_type.hpp new file mode 100644 index 0000000..fb53b7f --- /dev/null +++ b/src/generation/primitives/primitive_type.hpp @@ -0,0 +1 @@ +enum class BuilderPrimitiveType { UINT = 1 }; \ No newline at end of file diff --git a/src/generation/primitives/uint.hpp b/src/generation/primitives/uint.hpp new file mode 100644 index 0000000..596a028 --- /dev/null +++ b/src/generation/primitives/uint.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "generation/builder/builder.hpp" +#include "generation/primitives/primitive.hpp" +#include "generation/primitives/uint_type.hpp" +#include "llvm/IR/DerivedTypes.h" + +class BuilderUintPrimitive : public BuilderPrimitive { +protected: + BuilderUintType uintType; + llvm::IntegerType* llvmIntegerType; + llvm::AllocaInst* alloc; + +public: + BuilderUintPrimitive(BuilderUintType uintType, Builder& builder, + llvm::IntegerType* llvmIntegerType, std::string name) + : BuilderPrimitive(BuilderPrimitiveType::UINT, builder), + llvmIntegerType(llvmIntegerType), uintType(uintType), + alloc(builder.allocate(llvmIntegerType, name)) {}; + + void assignValue(BuilderUintPrimitive& other) { + builder.store(builder.load(this->llvmIntegerType, other.alloc, "load"), + this->alloc); + }; + + llvm::IntegerType* getLlvmIntegerType() { return this->llvmIntegerType; }; + llvm::AllocaInst* getAlloc() { return this->alloc; }; + + BuilderUintType getUintType() { return this->uintType; } +}; \ No newline at end of file diff --git a/src/generation/primitives/uint16.hpp b/src/generation/primitives/uint16.hpp new file mode 100644 index 0000000..4dff024 --- /dev/null +++ b/src/generation/primitives/uint16.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "generation/builder/builder.hpp" +#include "generation/primitives/uint.hpp" + +#include + +class BuilderUint16 : public BuilderUintPrimitive { +private: +public: + BuilderUint16(Builder& builder, std::string name) + : BuilderUintPrimitive(BuilderUintType::UINT16, builder, + builder.getUint16(), name) {}; + + void storeValue(uint16_t value) { + this->builder.store(builder.createConst16(value), this->alloc); + }; + + void addValue(uint16_t value) { + builder.add(this->alloc, builder.createConst16(value), "add_out"); + }; +}; \ No newline at end of file diff --git a/src/generation/primitives/uint32.hpp b/src/generation/primitives/uint32.hpp new file mode 100644 index 0000000..9a6b018 --- /dev/null +++ b/src/generation/primitives/uint32.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "generation/builder/builder.hpp" +#include "generation/primitives/uint.hpp" + +#include + +class BuilderUint32 : public BuilderUintPrimitive { +private: +public: + BuilderUint32(Builder& builder, std::string name) + : BuilderUintPrimitive(BuilderUintType::UINT32, builder, + builder.getUint32(), name) {}; + + void storeValue(uint32_t value) { + this->builder.store(builder.createConst32(value), this->alloc); + }; + + void addValue(uint32_t value) { + builder.add(this->alloc, builder.createConst32(value), "add_out"); + }; +}; \ No newline at end of file diff --git a/src/generation/primitives/uint64.hpp b/src/generation/primitives/uint64.hpp new file mode 100644 index 0000000..89397f3 --- /dev/null +++ b/src/generation/primitives/uint64.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "generation/builder/builder.hpp" +#include "generation/primitives/uint.hpp" + +#include + +class BuilderUint64 : public BuilderUintPrimitive { +private: +public: + BuilderUint64(Builder& builder, std::string name) + : BuilderUintPrimitive(BuilderUintType::UINT64, builder, + builder.getUint64(), name) {}; + + void storeValue(uint64_t value) { + this->builder.store(builder.createConst64(value), this->alloc); + }; + + void addValue(uint64_t value) { + builder.add(this->alloc, builder.createConst64(value), "add_out"); + }; +}; \ No newline at end of file diff --git a/src/generation/primitives/uint8.hpp b/src/generation/primitives/uint8.hpp index 1301627..e2d43dd 100644 --- a/src/generation/primitives/uint8.hpp +++ b/src/generation/primitives/uint8.hpp @@ -1,31 +1,22 @@ #pragma once #include "generation/builder/builder.hpp" -#include "llvm/IR/Instructions.h" +#include "generation/primitives/uint.hpp" + #include -class BuilderUint8 { +class BuilderUint8 : public BuilderUintPrimitive { private: - Builder& builder; - - llvm::AllocaInst* alloc; - public: BuilderUint8(Builder& builder, std::string name) - : builder(builder), - alloc(this->builder.allocate(builder.getUint8(), name)) {}; + : BuilderUintPrimitive(BuilderUintType::UINT8, builder, + builder.getUint8(), name) {}; void storeValue(uint8_t value) { this->builder.store(builder.createConst8(value), this->alloc); }; - void assignValue(BuilderUint8& other) { - builder.store(builder.load(builder.getUint8(), other.alloc), this->alloc); - }; - void addValue(uint8_t value) { - builder.add(this->alloc, builder.createConst8(value)); + builder.add(this->alloc, builder.createConst8(value), "add_out"); }; - - llvm::AllocaInst* getAlloc() { return this->alloc; }; }; \ No newline at end of file diff --git a/src/generation/primitives/uint_type.hpp b/src/generation/primitives/uint_type.hpp new file mode 100644 index 0000000..8f445a9 --- /dev/null +++ b/src/generation/primitives/uint_type.hpp @@ -0,0 +1 @@ +enum class BuilderUintType { UINT8 = 1, UINT16 = 2, UINT32 = 3, UINT64 = 4 }; \ No newline at end of file diff --git a/src/lexer/lexer.hpp b/src/lexer/lexer.hpp index 268fba6..b2d6088 100644 --- a/src/lexer/lexer.hpp +++ b/src/lexer/lexer.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "./string_converter.hpp" @@ -7,9 +8,13 @@ #include "./tokens/end_of_line/end_of_line.hpp" #include "./tokens/identifier/identifier.hpp" #include "./tokens/operators/assignment/assignment.hpp" -#include "./tokens/primitives/uint8/uint8.hpp" +#include "./tokens/primitives/uint32/uint32.hpp" +#include "./tokens/primitives/uint64/uint64.hpp" #include "./tokens/operators/addition/addition.hpp" +#include "lexer/tokens/primitives/uint16/uint16.hpp" +#include "lexer/tokens/primitives/uint32/uint32.hpp" +#include "lexer/tokens/print/print.hpp" #include "lexer/tokens/return/return.hpp" #include "matcher.hpp" @@ -38,14 +43,22 @@ class Lexer { if (c == ' ' || c == ';') { if (buffer == "uint8") { tokens.addUint8(Uint8Token()); + } else if (buffer == "uint16") { + tokens.addUint16(Uint16Token()); + } else if (buffer == "uint32") { + tokens.addUint32(Uint32Token()); + } else if (buffer == "uint64") { + tokens.addUint64(Uint64Token()); } else if (buffer == "=") { tokens.addAssignment(AssignmentToken()); } else if (Matcher::isInt(buffer)) { - tokens.addNumber(StringConverter::toInt(buffer)); + tokens.addNumber(StringConverter::toUnsignedLongLong(buffer)); } else if (buffer == "+") { tokens.addAddition(AdditionToken()); } else if (buffer == "return") { tokens.addReturn(ReturnToken()); + } else if (buffer == "print") { + tokens.addPrint(PrintToken()); } else { tokens.addIdentifier(IdentifierToken(buffer)); } diff --git a/src/lexer/string_converter.hpp b/src/lexer/string_converter.hpp index d35c7f2..d05b804 100644 --- a/src/lexer/string_converter.hpp +++ b/src/lexer/string_converter.hpp @@ -2,5 +2,7 @@ class StringConverter { public: - static int toInt(std::string string) { return std::stoi(string); } + static unsigned long long toUnsignedLongLong(std::string string) { + return std::stoull(string); + } }; \ No newline at end of file diff --git a/src/lexer/token_container/token_container.cpp b/src/lexer/token_container/token_container.cpp index 68ed8de..a6b73b9 100644 --- a/src/lexer/token_container/token_container.cpp +++ b/src/lexer/token_container/token_container.cpp @@ -38,6 +38,21 @@ void TokenContainer::print() const { std::cout << "PRIMITIVE(UINT8)\n"; break; } + + case PrimitiveType::UINT16: { + std::cout << "PRIMITIVE(UINT16)\n"; + break; + } + + case PrimitiveType::UINT32: { + std::cout << "PRIMITIVE(UINT32)\n"; + break; + } + + case PrimitiveType::UINT64: { + std::cout << "PRIMITIVE(UINT64)\n"; + break; + } } break; } @@ -77,6 +92,10 @@ void TokenContainer::print() const { case RETURN: { std::cout << "RETURN\n"; } + + case PRINT: { + std::cout << "PRINT\n"; + } } } } \ No newline at end of file diff --git a/src/lexer/token_container/token_container.hpp b/src/lexer/token_container/token_container.hpp index 9a151f3..974d400 100644 --- a/src/lexer/token_container/token_container.hpp +++ b/src/lexer/token_container/token_container.hpp @@ -8,7 +8,11 @@ #include "lexer/tokens/number/number.hpp" #include "lexer/tokens/operators/addition/addition.hpp" #include "lexer/tokens/operators/assignment/assignment.hpp" +#include "lexer/tokens/primitives/uint16/uint16.hpp" +#include "lexer/tokens/primitives/uint32/uint32.hpp" +#include "lexer/tokens/primitives/uint64/uint64.hpp" #include "lexer/tokens/primitives/uint8/uint8.hpp" +#include "lexer/tokens/print/print.hpp" #include "lexer/tokens/return/return.hpp" #include "lexer/tokens/token.hpp" @@ -43,10 +47,26 @@ class TokenContainer { tokens.push_back(std::make_unique(token)); } + void addUint16(const Uint16Token& token) { + tokens.push_back(std::make_unique(token)); + } + + void addUint32(const Uint32Token& token) { + tokens.push_back(std::make_unique(token)); + } + + void addUint64(const Uint64Token& token) { + tokens.push_back(std::make_unique(token)); + } + void addReturn(const ReturnToken& token) { tokens.push_back(std::make_unique(token)); } + void addPrint(const PrintToken& token) { + tokens.push_back(std::make_unique(token)); + } + const Token& view(size_t index) const; size_t getCount() const; diff --git a/src/lexer/tokens/number/number.cpp b/src/lexer/tokens/number/number.cpp index 1b639f5..0bfcfe0 100644 --- a/src/lexer/tokens/number/number.cpp +++ b/src/lexer/tokens/number/number.cpp @@ -1,4 +1,4 @@ #include "./number.hpp" -NumberToken::NumberToken(const int value) +NumberToken::NumberToken(const unsigned long long value) : Token(TokenType::NUMBER), value(value) {}; \ No newline at end of file diff --git a/src/lexer/tokens/number/number.hpp b/src/lexer/tokens/number/number.hpp index 47c4cd5..41c9440 100644 --- a/src/lexer/tokens/number/number.hpp +++ b/src/lexer/tokens/number/number.hpp @@ -4,6 +4,6 @@ class NumberToken : public Token { public: - const int value; - NumberToken(const int value); + const unsigned long long value; + NumberToken(const unsigned long long value); }; \ No newline at end of file diff --git a/src/lexer/tokens/primitives/primitive_type.hpp b/src/lexer/tokens/primitives/primitive_type.hpp index 8559ff8..5f23073 100644 --- a/src/lexer/tokens/primitives/primitive_type.hpp +++ b/src/lexer/tokens/primitives/primitive_type.hpp @@ -1,3 +1,3 @@ #pragma once -enum class PrimitiveType { UINT8 = 1 }; +enum class PrimitiveType { UINT8 = 1, UINT16 = 2, UINT32 = 3, UINT64 = 4 }; diff --git a/src/lexer/tokens/primitives/uint16/uint16.cpp b/src/lexer/tokens/primitives/uint16/uint16.cpp new file mode 100644 index 0000000..efb36fb --- /dev/null +++ b/src/lexer/tokens/primitives/uint16/uint16.cpp @@ -0,0 +1,3 @@ +#include "./uint16.hpp" + +Uint16Token::Uint16Token() : PrimitiveToken(PrimitiveType::UINT16) {} \ No newline at end of file diff --git a/src/lexer/tokens/primitives/uint16/uint16.hpp b/src/lexer/tokens/primitives/uint16/uint16.hpp new file mode 100644 index 0000000..b9c6c6d --- /dev/null +++ b/src/lexer/tokens/primitives/uint16/uint16.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "../primitive.hpp" + +class Uint16Token : public PrimitiveToken { + +public: + Uint16Token(); +}; \ No newline at end of file diff --git a/src/lexer/tokens/primitives/uint32/uint32.cpp b/src/lexer/tokens/primitives/uint32/uint32.cpp new file mode 100644 index 0000000..dc46733 --- /dev/null +++ b/src/lexer/tokens/primitives/uint32/uint32.cpp @@ -0,0 +1,3 @@ +#include "./uint32.hpp" + +Uint32Token::Uint32Token() : PrimitiveToken(PrimitiveType::UINT32) {} \ No newline at end of file diff --git a/src/lexer/tokens/primitives/uint32/uint32.hpp b/src/lexer/tokens/primitives/uint32/uint32.hpp new file mode 100644 index 0000000..8a83f27 --- /dev/null +++ b/src/lexer/tokens/primitives/uint32/uint32.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "../primitive.hpp" + +class Uint32Token : public PrimitiveToken { + +public: + Uint32Token(); +}; \ No newline at end of file diff --git a/src/lexer/tokens/primitives/uint64/uint64.cpp b/src/lexer/tokens/primitives/uint64/uint64.cpp new file mode 100644 index 0000000..7937111 --- /dev/null +++ b/src/lexer/tokens/primitives/uint64/uint64.cpp @@ -0,0 +1,3 @@ +#include "./uint64.hpp" + +Uint64Token::Uint64Token() : PrimitiveToken(PrimitiveType::UINT64) {} \ No newline at end of file diff --git a/src/lexer/tokens/primitives/uint64/uint64.hpp b/src/lexer/tokens/primitives/uint64/uint64.hpp new file mode 100644 index 0000000..6d0aea4 --- /dev/null +++ b/src/lexer/tokens/primitives/uint64/uint64.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "../primitive.hpp" + +class Uint64Token : public PrimitiveToken { + +public: + Uint64Token(); +}; \ No newline at end of file diff --git a/src/lexer/tokens/print/print.cpp b/src/lexer/tokens/print/print.cpp new file mode 100644 index 0000000..c5d7a39 --- /dev/null +++ b/src/lexer/tokens/print/print.cpp @@ -0,0 +1,3 @@ +#include "./print.hpp" + +PrintToken::PrintToken() : Token(TokenType::PRINT) {}; \ No newline at end of file diff --git a/src/lexer/tokens/print/print.hpp b/src/lexer/tokens/print/print.hpp new file mode 100644 index 0000000..56d732d --- /dev/null +++ b/src/lexer/tokens/print/print.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "../token.hpp" + +class PrintToken : public Token { +public: + PrintToken(); +}; \ No newline at end of file diff --git a/src/lexer/tokens/token_type.hpp b/src/lexer/tokens/token_type.hpp index cc9cf15..4dc2a63 100644 --- a/src/lexer/tokens/token_type.hpp +++ b/src/lexer/tokens/token_type.hpp @@ -6,5 +6,6 @@ enum TokenType { OPERATOR = 3, IDENTIFIER = 4, NUMBER = 5, - RETURN = 6 + RETURN = 6, + PRINT = 7 }; diff --git a/src/syntax_analyser/abstract_syntax_tree.cpp b/src/syntax_analyser/abstract_syntax_tree.cpp index 2c4e64e..afcce01 100644 --- a/src/syntax_analyser/abstract_syntax_tree.cpp +++ b/src/syntax_analyser/abstract_syntax_tree.cpp @@ -13,11 +13,12 @@ #include "syntax_analyser/statement/assignment/number/number.hpp" #include "syntax_analyser/statement/initialisation/initialisation.hpp" #include "syntax_analyser/statement/primitives/primitive_type.hpp" +#include "syntax_analyser/statement/print/print.hpp" #include "syntax_analyser/statement/return/return.hpp" #include "syntax_analyser/statement/statement.hpp" #include "syntax_analyser/statement/value/identifier/identifier.hpp" #include "syntax_analyser/statement/value/number/number.hpp" -#include "syntax_analyser/statement/value/value.hpp" + #include #include #include @@ -34,6 +35,18 @@ AbstractSyntaxTree::getStatementPrimitiveTypeFromPrimitiveType( case PrimitiveType::UINT8: { return StatementPrimitiveType::UINT8; } + + case PrimitiveType::UINT16: { + return StatementPrimitiveType::UINT16; + } + + case PrimitiveType::UINT32: { + return StatementPrimitiveType::UINT32; + } + + case PrimitiveType::UINT64: { + return StatementPrimitiveType::UINT64; + } } } @@ -147,8 +160,10 @@ 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) { @@ -208,6 +223,26 @@ Program AbstractSyntaxTree::parse() { program.addStatement(std::move(returnStatement)); } + + if (buffer[0].get().tokenType == TokenType::PRINT) { + std::unique_ptr printStatement; + if (buffer[1].get().tokenType == TokenType::NUMBER) { + const NumberToken& number = + static_cast(buffer[1].get()); + + printStatement = std::make_unique( + (std::make_unique(number.value))); + } else if (buffer[1].get().tokenType == TokenType::IDENTIFIER) { + const IdentifierToken& identifier = + static_cast(buffer[1].get()); + + printStatement = std::make_unique( + (std::make_unique(identifier.name))); + } + + program.addStatement(std::move(printStatement)); + } + buffer.clear(); } } diff --git a/src/syntax_analyser/program/program.hpp b/src/syntax_analyser/program/program.hpp index 8ec3430..6291983 100644 --- a/src/syntax_analyser/program/program.hpp +++ b/src/syntax_analyser/program/program.hpp @@ -6,6 +6,7 @@ #include "syntax_analyser/statement/assignment/number/number.hpp" #include "syntax_analyser/statement/initialisation/initialisation.hpp" #include "syntax_analyser/statement/primitives/primitive_type.hpp" +#include "syntax_analyser/statement/print/print.hpp" #include "syntax_analyser/statement/return/return.hpp" #include "syntax_analyser/statement/statement.hpp" #include "syntax_analyser/statement/value/identifier/identifier.hpp" @@ -38,12 +39,27 @@ class Program { const InitialisationStatement& initialisationStatement) const { switch (initialisationStatement.type) { case StatementPrimitiveType::UINT8: { - std::cout << "UINT8 "; + std::cout << "UINT8"; + break; + } + + case StatementPrimitiveType::UINT16: { + std::cout << "UINT16"; + break; + } + + case StatementPrimitiveType::UINT32: { + std::cout << "UINT32"; + break; + } + + case StatementPrimitiveType::UINT64: { + std::cout << "UINT64"; break; } } - std::cout << initialisationStatement.identifier->name << ";\n"; + std::cout << " " << initialisationStatement.identifier->name << ";\n"; }; void printReturnStatement(const ReturnStatement& returnStatement) const { @@ -93,6 +109,14 @@ class Program { << rhs << ";\n"; } + void printPrintStatement(const PrintStatement& printStatement) const { + std::string value = this->getStatementValueString(*printStatement.value); + + std::cout << "print(" << value + << ")" + ";\n"; + } + public: Program(); @@ -168,6 +192,15 @@ class Program { break; } + + case StatementType::PRINT: { + const PrintStatement& printStatement = + static_cast(statement); + + this->printPrintStatement(printStatement); + + break; + } } } } 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/primitives/primitive_type.hpp b/src/syntax_analyser/statement/primitives/primitive_type.hpp index a42d3ec..957dad4 100644 --- a/src/syntax_analyser/statement/primitives/primitive_type.hpp +++ b/src/syntax_analyser/statement/primitives/primitive_type.hpp @@ -1,3 +1,8 @@ #pragma once -enum class StatementPrimitiveType { UINT8 = 1 }; +enum class StatementPrimitiveType { + UINT8 = 1, + UINT16 = 2, + UINT32 = 3, + UINT64 = 4 +}; diff --git a/src/syntax_analyser/statement/print/print.cpp b/src/syntax_analyser/statement/print/print.cpp new file mode 100644 index 0000000..be3beb0 --- /dev/null +++ b/src/syntax_analyser/statement/print/print.cpp @@ -0,0 +1,4 @@ +#include "syntax_analyser/statement/print/print.hpp" +#include "syntax_analyser/statement/value/value.hpp" +PrintStatement::PrintStatement(std::unique_ptr value) + : Statement(StatementType::PRINT), value(std::move(value)) {} \ No newline at end of file diff --git a/src/syntax_analyser/statement/print/print.hpp b/src/syntax_analyser/statement/print/print.hpp new file mode 100644 index 0000000..6478fcc --- /dev/null +++ b/src/syntax_analyser/statement/print/print.hpp @@ -0,0 +1,12 @@ +#pragma once +#include + +#include "syntax_analyser/statement/statement.hpp" +#include "syntax_analyser/statement/value/value.hpp" + +class PrintStatement : public Statement { +public: + const std::unique_ptr value; + + PrintStatement(std::unique_ptr value); +}; \ No newline at end of file diff --git a/src/syntax_analyser/statement/statement_type.hpp b/src/syntax_analyser/statement/statement_type.hpp index cfa6ffa..3f8fce2 100644 --- a/src/syntax_analyser/statement/statement_type.hpp +++ b/src/syntax_analyser/statement/statement_type.hpp @@ -2,5 +2,6 @@ enum class StatementType { INITIALISATION = 1, ASSIGNMENT = 2, RETURN = 3, - ADDITION = 4 + ADDITION = 4, + PRINT = 5 }; \ No newline at end of file diff --git a/tests/lexer.cpp b/tests/lexer.cpp new file mode 100644 index 0000000..876c9e2 --- /dev/null +++ b/tests/lexer.cpp @@ -0,0 +1,122 @@ +#include "../src/lexer/lexer.hpp" +#include "lexer/token_container/token_container.hpp" +#include "lexer/tokens/identifier/identifier.hpp" +#include "lexer/tokens/primitives/primitive.hpp" +#include "lexer/tokens/primitives/primitive_type.hpp" +#include "lexer/tokens/token_type.hpp" +#include + +TEST_CASE("Empty program", "[lexer]") { + Lexer lexer = Lexer(""); + + TokenContainer tokenContainer = lexer.makeTokenList(); + + REQUIRE(tokenContainer.getCount() == 0); +}; + +TEST_CASE("Empty line", "[lexer]") { + Lexer lexer = Lexer(";"); + + TokenContainer tokenContainer = lexer.makeTokenList(); + + REQUIRE(tokenContainer.getCount() == 1); + REQUIRE(tokenContainer.view(0).tokenType == TokenType::END_OF_LINE); +}; + +void test_primitive_variable_declaration(std::string string, + PrimitiveType primitive) { + std::string IDENTIFIER = "a"; + + Lexer lexer = Lexer(string + " " + IDENTIFIER + ";"); + + TokenContainer tokenContainer = lexer.makeTokenList(); + + REQUIRE(tokenContainer.getCount() == 3); + REQUIRE(tokenContainer.view(0).tokenType == TokenType::PRIMITIVE); + REQUIRE(static_cast(tokenContainer.view(0)) + .primitiveType == primitive); + + REQUIRE(tokenContainer.view(1).tokenType == TokenType::IDENTIFIER); + REQUIRE(static_cast(tokenContainer.view(1)).name == + IDENTIFIER); + + REQUIRE(tokenContainer.view(2).tokenType == TokenType::END_OF_LINE); +} + +TEST_CASE("Singular uint8 variable declaration", "[lexer]") { + test_primitive_variable_declaration("uint8", PrimitiveType::UINT8); +}; + +TEST_CASE("Singular uint16 variable declaration", "[lexer]") { + test_primitive_variable_declaration("uint16", PrimitiveType::UINT16); +}; + +TEST_CASE("Singular uint32 variable declaration", "[lexer]") { + test_primitive_variable_declaration("uint32", PrimitiveType::UINT32); +}; + +TEST_CASE("Singular uint64 variable declaration", "[lexer]") { + test_primitive_variable_declaration("uint64", PrimitiveType::UINT64); +}; + +TEST_CASE("Return number", "[lexer]") { + Lexer lexer = Lexer("return 0;"); + + TokenContainer tokenContainer = lexer.makeTokenList(); + + REQUIRE(tokenContainer.getCount() == 3); + REQUIRE(tokenContainer.view(0).tokenType == TokenType::RETURN); + + REQUIRE(tokenContainer.view(1).tokenType == TokenType::NUMBER); + REQUIRE(static_cast(tokenContainer.view(1)).value == 0); + + REQUIRE(tokenContainer.view(2).tokenType == TokenType::END_OF_LINE); +} + +TEST_CASE("Return identifier", "[lexer]") { + std::string IDENTIFIER = "a"; + + Lexer lexer = Lexer("return " + IDENTIFIER + ";"); + + TokenContainer tokenContainer = lexer.makeTokenList(); + + REQUIRE(tokenContainer.getCount() == 3); + REQUIRE(tokenContainer.view(0).tokenType == TokenType::RETURN); + + REQUIRE(tokenContainer.view(1).tokenType == TokenType::IDENTIFIER); + REQUIRE(static_cast(tokenContainer.view(1)).name == + IDENTIFIER); + + REQUIRE(tokenContainer.view(2).tokenType == TokenType::END_OF_LINE); +} + +TEST_CASE("Print number", "[lexer]") { + Lexer lexer = Lexer("print 0;"); + + TokenContainer tokenContainer = lexer.makeTokenList(); + + REQUIRE(tokenContainer.getCount() == 3); + REQUIRE(tokenContainer.view(0).tokenType == TokenType::PRINT); + + REQUIRE(tokenContainer.view(1).tokenType == TokenType::NUMBER); + REQUIRE(static_cast(tokenContainer.view(1)).value == 0); + + REQUIRE(tokenContainer.view(2).tokenType == TokenType::END_OF_LINE); +} + +TEST_CASE("Print identifier", "[lexer]") { + std::string IDENTIFIER = "a"; + + Lexer lexer = Lexer("print " + IDENTIFIER + ";"); + + TokenContainer tokenContainer = lexer.makeTokenList(); + + REQUIRE(tokenContainer.getCount() == 3); + REQUIRE(tokenContainer.view(0).tokenType == TokenType::PRINT); + + REQUIRE(tokenContainer.view(1).tokenType == TokenType::IDENTIFIER); + REQUIRE(static_cast(tokenContainer.view(1)).name == + IDENTIFIER); + + REQUIRE(tokenContainer.view(2).tokenType == TokenType::END_OF_LINE); +} \ No newline at end of file