elmcpp/main.cpp

114 lines
2.9 KiB
C++

#include <iostream>
#include <string>
#include <list>
#include <functional>
#include <tuple>
#include <variant>
#include <thread>
#include <sstream>
using namespace std::chrono_literals;
template <typename Message> struct ConsoleIO {
static auto ReadlineS(std::istream& is, std::function<Message(std::string)> f) {
return [=,&is] {
std::string line;
std::getline(is, line);
return f(line);
};
}
static std::function<Message()>
Readline(std::function<Message(std::string)> f) {
return ReadlineS (std::ref(std::cin), f);
}
static std::function<Message()>
Prompt(std::string prompt, std::function<Message(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
using Model = int;
struct SetState {
Model s;
};
using Message = std::variant<SetState>;
auto strToState (const std::string& s) -> Message {
std::stringstream sstr(s);
int r;
sstr >> r;
return SetState { r };
}
int main() {
using App = AppT<int, Message>;
App app;
return app.run(App::Config{
[]() -> App::InitResult {
return {23, {App::Console::Prompt("> ", strToState)}};
}, // init
[](const App::Model &state,
const App::Message &msg) -> App::UpdateResult {
return {std::get<SetState>(msg).s,
{App::Console::Prompt("> ", strToState)}};
}, // update
[](const App::Model &state) -> App::ViewResult {
return "Model " + std::to_string(state);
}, // view
[](const App::Model &state) -> bool { return state == 42; } // done
});
}