elmcpp/main.cpp

134 lines
3.4 KiB
C++

#include <iostream>
#include <string>
#include <list>
#include <functional>
#include <tuple>
#include <variant>
#include <thread>
#include <sstream>
#include <optional>
using namespace std::chrono_literals;
template <typename Message> struct ConsoleIO {
static auto ReadlineS(std::istream& is, std::function<Message(std::optional<std::string>)> f) {
return [=,&is] {
if (std::string line; std::getline(is, line)) {
return f(std::make_optional(line));
} else {
return f(std::nullopt);
}
};
}
static std::function<Message()>
Readline(std::function<Message(std::optional<std::string>)> f) {
return ReadlineS (std::ref(std::cin), f);
}
static std::function<Message()>
Prompt(std::string prompt, std::function<Message(std::optional<std::string>)> f) {
return [=] {
std::cout << prompt << std::flush;
return Readline (f)();
};
}
};
template <typename ModelT, typename MessageT> struct AppT {
using Model = ModelT;
using Message = MessageT;
using Command = std::function<Message()>;
using InitResult = std::tuple<Model, std::list<Command>>;
using UpdateResult = InitResult;
using ViewResult = std::string;
using ReadLine = std::function<Message (std::string)>;
using Console = ConsoleIO<Message>;
using Init = std::function<InitResult ()>;
using Update = std::function<UpdateResult (Model, const Message&)>;
using View = std::function<ViewResult (const Model&)>;
using Done = std::function<bool (const Model&)>;
struct Config {
Init init;
Update update;
View view;
Done done;
};
int run (const Config & cfg) {
auto const& [init, update, view, done] = cfg;
auto [state, cmds] = init();
while (! done (state)) {
std::cout << view(state) << "\n";
if (!cmds.empty()) {
auto [newstate, newcmds] = update (state, cmds.front()());
cmds.erase (cmds.begin());
cmds.insert (cmds.end(), std::begin(newcmds), std::end (newcmds));
state = newstate;
}
std::this_thread::sleep_for (100ms);
}
std::cout << "app finished with " << cmds.size() << " pending commands and final state: \n" << view(state) << '\n';
return 0;
}
};
// user defined
struct Model {
bool done{false};
int value{0};
};
struct SetValue {
int s;
};
struct Stop {
};
using Message = std::variant<SetValue, Stop>;
auto userInputToMsg(const std::optional<std::string>& s) -> Message {
if (s) {
std::stringstream sstr(*s);
int r;
sstr >> r;
return SetValue { r };
} else {
return Stop{};
}
}
int main() {
using App = AppT<Model, Message>;
App app;
return app.run(App::Config{
[]() -> App::InitResult {
return {{false, 23}, {App::Console::Prompt("> ", userInputToMsg)}};
}, // init
[](App::Model state,
const App::Message &msg) -> App::UpdateResult {
if (auto stop = std::get_if<Stop>(&msg); stop) {
state.done = true;
return {state, {}};
} else {
state.value = std::get<SetValue>(msg).s;
return {state, {App::Console::Prompt("> ", userInputToMsg)}};
}
}, // update
[](const App::Model &state) -> App::ViewResult {
return "Model " + std::to_string(state.value);
}, // view
[](const App::Model &state) -> bool { return state.done; } // done
});
}