diff --git a/.gitignore b/.gitignore index 9728c09..862ccb0 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ tags *.out *.app +subprojects +builddir diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..71748e0 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +all: build test + +reset: + powershell -executionpolicy bypass .\scripts\reset_build.ps1 + +build: + meson compile -j 4 -C builddir + +test: build + ./builddir/runtests + +install: build test + powershell "cp ./builddir/subprojects/libgit2-1.8.1/liblibgit2package.dll ." + powershell "cp ./builddir/subprojects/efsw/libefsw.dll ." + powershell "cp builddir/escape_turings_tarpit.exe ." + +clean: + meson compile --clean -C builddir diff --git a/dbc.cpp b/dbc.cpp new file mode 100644 index 0000000..c25d32a --- /dev/null +++ b/dbc.cpp @@ -0,0 +1,40 @@ +#include "dbc.hpp" + +void dbc::log(const string &message) { + fmt::print("{}\n", message); +} + +void dbc::sentinel(const string &message) { + string err = fmt::format("[SENTINEL!] {}\n", message); + throw dbc::SentinelError{err}; +} + +void dbc::pre(const string &message, bool test) { + if(!test) { + string err = fmt::format("[PRE!] {}\n", message); + throw dbc::PreCondError{err}; + } +} + +void dbc::pre(const string &message, std::function tester) { + dbc::pre(message, tester()); +} + +void dbc::post(const string &message, bool test) { + if(!test) { + string err = fmt::format("[POST!] {}\n", message); + throw dbc::PostCondError{err}; + } +} + +void dbc::post(const string &message, std::function tester) { + dbc::post(message, tester()); +} + +void dbc::check(bool test, const string &message) { + if(!test) { + string err = fmt::format("[CHECK!] {}\n", message); + fmt::println("{}", err); + throw dbc::CheckError{err}; + } +} diff --git a/dbc.hpp b/dbc.hpp new file mode 100644 index 0000000..919d729 --- /dev/null +++ b/dbc.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +using std::string; + +namespace dbc { + class Error { + public: + const string message; + Error(string m) : message{m} {} + Error(const char *m) : message{m} {} + }; + + class CheckError : public Error {}; + class SentinelError : public Error {}; + class PreCondError : public Error {}; + class PostCondError : public Error {}; + + void log(const string &message); + void sentinel(const string &message); + void pre(const string &message, bool test); + void pre(const string &message, std::function tester); + void post(const string &message, bool test); + void post(const string &message, std::function tester); + void check(bool test, const string &message); +} diff --git a/fsm.hpp b/fsm.hpp new file mode 100644 index 0000000..5898ea4 --- /dev/null +++ b/fsm.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#ifndef FSM_DEBUG +#define FSM_STATE(C, S, E, ...) case C::S: S(E, ##__VA_ARGS__); break +#else +#define FSM_STATE(C, S, E, ...) case C::S: fmt::println(">> " #C " " #S " event={}, state={}", int(E), int(_state)); S(E, ##__VA_ARGS__); fmt::println("<< " #C " state={}", int(_state)); break +#endif + +template +class DeadSimpleFSM { +protected: + // BUG: don't put this in your class because state() won't work + S _state = S::START; + +public: + template + void event(E event, Types... args); + + void state(S next_state) { + _state = next_state; + } + + bool in_state(S state) { + return _state == state; + } +}; diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..8e409b7 --- /dev/null +++ b/meson.build @@ -0,0 +1,14 @@ +project('distributary', 'cpp', + default_options: ['cpp_std=c++20']) + +catch2 = dependency('catch2-with-main') +fmt = dependency('fmt') + +runtests = executable('runtests', [ + 'dbc.cpp', + 'tests/fsm.cpp', + 'tests/dbc.cpp', + ], + dependencies: [catch2, fmt]) + +test('tests', runtests) diff --git a/scripts/reset_build.ps1 b/scripts/reset_build.ps1 new file mode 100644 index 0000000..03ccae4 --- /dev/null +++ b/scripts/reset_build.ps1 @@ -0,0 +1,11 @@ +mv .\subprojects\packagecache . +rm -recurse -force .\subprojects\,.\builddir\ +mkdir subprojects +mv .\packagecache .\subprojects\ +cp *.wrap subprojects +mkdir builddir +meson wrap install fmt +meson wrap install catch2 +# $env:CC="clang" +# $env:CXX="clang++" +meson setup --default-library=static --prefer-static builddir diff --git a/scripts/reset_build.sh b/scripts/reset_build.sh new file mode 100644 index 0000000..a419b6c --- /dev/null +++ b/scripts/reset_build.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e + +mv -f ./subprojects/packagecache . +rm -rf subprojects builddir +mkdir subprojects +mv packagecache ./subprojects/ +mkdir builddir +cp *.wrap subprojects +meson wrap install fmt +meson wrap install catch2 +meson setup builddir diff --git a/tests/dbc.cpp b/tests/dbc.cpp new file mode 100644 index 0000000..fa45b86 --- /dev/null +++ b/tests/dbc.cpp @@ -0,0 +1,39 @@ +#include +#include "dbc.hpp" + +using namespace dbc; + +TEST_CASE("basic feature tests", "[utils]") { + log("Logging a message."); + + try { + sentinel("This shouldn't happen."); + } catch(SentinelError) { + log("Sentinel happened."); + } + + pre("confirm positive cases work", 1 == 1); + pre("confirm positive lambda", [&]{ return 1 == 1;}); + post("confirm positive post", 1 == 1); + post("confirm postitive post with lamdba", [&]{ return 1 == 1;}); + + check(1 == 1, "one equals 1"); + + try { + check(1 == 2, "this should fail"); + } catch(CheckError err) { + log("check fail worked"); + } + + try { + pre("failing pre", 1 == 3); + } catch(PreCondError err) { + log("pre fail worked"); + } + + try { + post("failing post", 1 == 4); + } catch(PostCondError err) { + log("post faile worked"); + } +} diff --git a/tests/fsm.cpp b/tests/fsm.cpp new file mode 100644 index 0000000..bd43bf6 --- /dev/null +++ b/tests/fsm.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include "../fsm.hpp" + +using namespace fmt; +using std::string; + +enum class MyState { + START, RUNNING, END +}; + +enum class MyEvent { + STARTED, PUSH, QUIT +}; + +class MyFSM : public DeadSimpleFSM { +public: + void event(MyEvent ev, string data="") { + switch(_state) { + FSM_STATE(MyState, START, ev); + FSM_STATE(MyState, RUNNING, ev, data); + FSM_STATE(MyState, END, ev); + } + } + + void START(MyEvent ev) { + println("<<< START"); + state(MyState::RUNNING); + } + + void RUNNING(MyEvent ev, string &data) { + if(ev == MyEvent::QUIT) { + println("<<< QUITTING {}", data); + state(MyState::END); + } else { + println("<<< RUN: {}", data); + state(MyState::RUNNING); + } + } + + void END(MyEvent ev) { + println("<<< STOP"); + state(MyState::END); + } +}; + +TEST_CASE("confirm fsm works with optional data", "[utils]") { + MyFSM fsm; + + REQUIRE(fsm.in_state(MyState::START)); + + fsm.event(MyEvent::STARTED); + REQUIRE(fsm.in_state(MyState::RUNNING)); + + fsm.event(MyEvent::PUSH); + REQUIRE(fsm.in_state(MyState::RUNNING)); + + fsm.event(MyEvent::PUSH); + REQUIRE(fsm.in_state(MyState::RUNNING)); + + fsm.event(MyEvent::PUSH); + REQUIRE(fsm.in_state(MyState::RUNNING)); + + fsm.event(MyEvent::QUIT, "DONE!"); + REQUIRE(fsm.in_state(MyState::END)); +}