diff --git a/.gitignore b/.gitignore index ec2481e..53b21ec 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ tags # Persistent undo [._]*.un~ +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/main.cpp b/main.cpp new file mode 100644 index 0000000..dde31e8 --- /dev/null +++ b/main.cpp @@ -0,0 +1,39 @@ +#include + +#include +#include +#include + +int main() { + using namespace ftxui; + + auto summary = [&] { + auto content = vbox({ + hbox({text(L"- done: "), text(L"3") | bold}) | color(Color::Green), + hbox({text(L"- active: "), text(L"2") | bold}) | color(Color::RedLight), + hbox({text(L"- queue: "), text(L"9") | bold}) | color(Color::Red), + }); + return window(text(L" Summary "), content); + }; + + auto document = // + vbox({ + hbox({ + summary(), + summary(), + summary() | flex, + }), + summary(), + summary(), + }); + + // Limit the size of the document to 80 char. + document = document | size(WIDTH, LESS_THAN, 80); + + auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); + Render(screen, document); + + std::cout << screen.ToString() << '\0' << std::endl; + + return EXIT_SUCCESS; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..9c2f6a6 --- /dev/null +++ b/meson.build @@ -0,0 +1,28 @@ +project('lcthw-utilities', 'cpp', + default_options: ['cpp_std=c++20']) + +catch2 = dependency('catch2-with-main') +fmt = dependency('fmt') +json = dependency('nlohmann_json') +ftxui_screen = dependency('ftxui-screen') +ftxui_dom = dependency('ftxui-dom') +ftxui_component = dependency('ftxui-component') + +dependencies = [catch2, fmt, + ftxui_screen, ftxui_dom, ftxui_component, + json] + +runtests = executable('runtests', [ + 'dbc.cpp', + 'tests/fsm.cpp', + 'tests/dbc.cpp', + ], + dependencies: dependencies) + +roguish = executable('roguish', [ + 'dbc.cpp', + 'main.cpp' + ], + dependencies: dependencies) + +test('tests', runtests) diff --git a/scripts/reset_build.ps1 b/scripts/reset_build.ps1 new file mode 100644 index 0000000..8e5818b --- /dev/null +++ b/scripts/reset_build.ps1 @@ -0,0 +1,13 @@ +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 +meson wrap install ftxui +meson wrap install nlohmann_json +# $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..abaf744 --- /dev/null +++ b/scripts/reset_build.sh @@ -0,0 +1,14 @@ +#!/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 wrap install ftxui +meson wrap install nlohmann_json +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)); +}