diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 26992fd..65f1d5c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ image: gcc stages: - build + - test build: stage: build @@ -10,3 +11,9 @@ build: artifacts: paths: - main + stage: test + +unit-testing: + stage: test + script: + - make unit-tests diff --git a/Makefile b/Makefile index 70e2adb..87b974a 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,25 @@ MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)" CXXFLAGS=-std=c++20 -Wall -Wextra -O2 -Werror=switch -CPPFLAGS=-Ilib/expected/ -Ilib/ut/ +CPPFLAGS=-Ilib/expected/ -Ilib/ut/ -Isrc/ Obj=bin/lexer.o \ - bin/errors.o \ - bin/main.o + bin/errors.o + +all: bin/musique bin/unit-tests -all: bin/musique bin/%.o: src/%.cc src/*.hh g++ $(CXXFLAGS) $(CPPFLAGS) -o $@ $< -c -bin/musique: $(Obj) src/*.hh - g++ $(CXXFLAGS) $(CPPFLAGS) -o $@ $(Obj) +bin/musique: $(Obj) bin/main.o src/*.hh + g++ $(CXXFLAGS) $(CPPFLAGS) -o $@ $(Obj) bin/main.o + +.PHONY: unit-tests +unit-tests: bin/unit-tests + ./$< + +bin/unit-tests: src/tests/*.cc $(Obj) + g++ $(CXXFLAGS) $(CPPFLAGS) -o $@ $^ clean: rm -rf bin diff --git a/src/errors.cc b/src/errors.cc index 4ff2a1b..7971abe 100644 --- a/src/errors.cc +++ b/src/errors.cc @@ -1,4 +1,9 @@ -#include "musique.hh" +#include + +bool Error::operator==(errors::Type type) +{ + return this->type == type; +} std::ostream& operator<<(std::ostream& os, Error const&) { diff --git a/src/lexer.cc b/src/lexer.cc index c4892c1..3311889 100644 --- a/src/lexer.cc +++ b/src/lexer.cc @@ -1,4 +1,4 @@ -#include "musique.hh" +#include auto Lexer::next_token() -> Result { diff --git a/src/main.cc b/src/main.cc index 483a36d..a0c9835 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,5 @@ #include -#include "musique.hh" +#include std::string_view Source = R"musique( nums = [ 1 2 3 ] diff --git a/src/musique.hh b/src/musique.hh index 832248b..6314d16 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -15,10 +15,21 @@ using i16 = std::int16_t; using i32 = std::int32_t; using i64 = std::int64_t; + +namespace errors +{ + enum Type + { + End_Of_File + }; +} + struct Error { - std::string_view message; + errors::Type type; Error *child = nullptr; + + bool operator==(errors::Type); }; template @@ -59,6 +70,7 @@ struct Token Close_Paren }; + Type type; std::string_view source; }; diff --git a/src/tests/lex.cc b/src/tests/lex.cc new file mode 100644 index 0000000..636c455 --- /dev/null +++ b/src/tests/lex.cc @@ -0,0 +1,57 @@ +#include +#include + +using namespace boost::ut; + +auto under(auto enumeration) requires std::is_enum_v +{ + return static_cast>(enumeration); +} + +static void expect_token_type( + Token::Type expected_type, + std::string source, + reflection::source_location const& sl = reflection::source_location::current()) +{ + Lexer lexer{source}; + auto result = lexer.next_token(); + expect(result.has_value() >> fatal, sl) << "have not parsed any tokens"; + expect(eq(under(result->type), under(expected_type)), sl) << "different token type then expected"; +} + +static void expect_token_type_and_value( + Token::Type expected_type, + std::string_view source, + reflection::source_location const& sl = reflection::source_location::current()) +{ + Lexer lexer{source}; + auto result = lexer.next_token(); + expect(result.has_value() >> fatal, sl) << "have not parsed any tokens"; + expect(eq(under(result->type), under(expected_type)), sl) << "different token type then expected"; + expect(eq(result->source, source)) << "tokenized source is not equal to original"; +} + +suite lexer_test = [] { + "Empty file"_test = [] { + Lexer lexer{""}; + auto result = lexer.next_token(); + expect(!result.has_value() >> fatal) << "could not produce any tokens from empty file"; + expect(result.error() == errors::End_Of_File) << "could not produce any tokens from empty file"; + }; + + "Simple token types"_test = [] { + expect_token_type(Token::Type::Close_Block, "]"); + expect_token_type(Token::Type::Close_Paren, ")"); + expect_token_type(Token::Type::Open_Block, "["); + expect_token_type(Token::Type::Open_Paren, "("); + expect_token_type(Token::Type::Variable_Separator, "|"); + }; + + "Numeric tokens"_test = [] { + expect_token_type_and_value(Token::Type::Numeric, "0"); + expect_token_type_and_value(Token::Type::Numeric, "123456789"); + expect_token_type_and_value(Token::Type::Numeric, ".75"); + expect_token_type_and_value(Token::Type::Numeric, "0.75"); + expect_token_type_and_value(Token::Type::Numeric, "123456789.123456789"); + }; +}; diff --git a/src/tests/main.cc b/src/tests/main.cc new file mode 100644 index 0000000..784aabe --- /dev/null +++ b/src/tests/main.cc @@ -0,0 +1,12 @@ +#include + +int main() +{ + using namespace boost::ut; + + if (!isatty(STDOUT_FILENO)) { + cfg = options { + .colors = colors { .none = "", .pass = "", .fail = "" } + }; + } +}