From 85d0cbc81fe54af056f0253029bcaf3ca0604db5 Mon Sep 17 00:00:00 2001 From: sp <stefan.pranger@iaik.tugraz.at> Date: Thu, 17 Aug 2023 11:41:16 +0200 Subject: [PATCH] initial commit after modifications for Importance Driven Testing --- .gitignore | 34 + CMakeLists.txt | 22 + main.cpp | 171 +++++ util/CMakeLists.txt | 8 + util/Grid.cpp | 229 ++++++ util/Grid.h | 63 ++ util/MinigridGrammar.h | 102 +++ util/OptionParser.cpp | 32 + util/OptionParser.h | 13 + util/PrismModulesPrinter.cpp | 681 +++++++++++++++++ util/PrismModulesPrinter.h | 91 +++ util/PrismPrinter.h | 15 + util/cell.cpp | 83 +++ util/cell.h | 62 ++ util/popl.hpp | 1331 ++++++++++++++++++++++++++++++++++ 15 files changed, 2937 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 main.cpp create mode 100644 util/CMakeLists.txt create mode 100644 util/Grid.cpp create mode 100644 util/Grid.h create mode 100644 util/MinigridGrammar.h create mode 100644 util/OptionParser.cpp create mode 100644 util/OptionParser.h create mode 100644 util/PrismModulesPrinter.cpp create mode 100644 util/PrismModulesPrinter.h create mode 100644 util/PrismPrinter.h create mode 100644 util/cell.cpp create mode 100644 util/cell.h create mode 100644 util/popl.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce34176 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +build/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1ca1571 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +include(util/CMakeLists.txt) + +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'") +add_definitions(-DLOG_DEBUG) + +cmake_minimum_required(VERSION 3.0...3.22) + +set(CMAKE_BUILD_TYPE Debug) + +project( + Minigrid2PRISM + VERSION 0.1 + LANGUAGES CXX) + +add_executable(main + ${SRCS} + main.cpp + ) + +target_link_libraries(main pthread) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..58de07f --- /dev/null +++ b/main.cpp @@ -0,0 +1,171 @@ +#include "util/OptionParser.h" +#include "util/MinigridGrammar.h" +#include "util/Grid.h" + +#include <iostream> +#include <fstream> +#include <filesystem> +#include <sstream> + +std::vector<std::string> parseCommaSeparatedString(std::string const& str) { + std::vector<std::string> result; + std::stringstream stream(str); + while(stream.good()) { + std::string substr; + getline(stream, substr, ','); + substr.at(0) = std::toupper(substr.at(0)); + result.push_back(substr); + } + return result; +} + +struct printer { + typedef boost::spirit::utf8_string string; + + void element(string const& tag, string const& value, int depth) const { + for (int i = 0; i < (depth*4); ++i) std::cout << ' '; + + std::cout << "tag: " << tag; + if (value != "") std::cout << ", value: " << value; + std::cout << std::endl; + } +}; + +void print_info(boost::spirit::info const& what) { + using boost::spirit::basic_info_walker; + + printer pr; + basic_info_walker<printer> walker(pr, what.tag, 0); + boost::apply_visitor(walker, what.value); +} + +int main(int argc, char* argv[]) { + popl::OptionParser optionParser("Allowed options"); + + auto helpOption = optionParser.add<popl::Switch>("h", "help", "Print this help message."); + auto inputFilename = optionParser.add<popl::Value<std::string>>("i", "input-file", "Filename of the input file."); + auto outputFilename = optionParser.add<popl::Value<std::string>>("o", "output-file", "Filename for the output file."); + + auto agentsToBeConsidered = optionParser.add<popl::Value<std::string>, popl::Attribute::optional>("a", "agents", "Which parsed agents should be considered in the output. WIP."); + auto viewForAgents = optionParser.add<popl::Value<std::string>, popl::Attribute::optional>("v", "view", "Agents for which the 'view'('direction') variable should be included. WIP."); + + auto probabilisticBehaviourAgents = optionParser.add<popl::Value<std::string>, popl::Attribute::optional>("p", "prob-beh", "Agents for which we want to include probabilistic actions"); + auto probabilities = optionParser.add<popl::Value<std::string>, popl::Attribute::optional>("q", "probs", "The probabilities for which probabilistic actions should be added. WIP"); + + auto enforceOneWays = optionParser.add<popl::Switch>("f", "force-oneways", "Enforce encoding of oneways. This entails that slippery tiles do not allow turning and have prob 1 to shift the agent by one and makes turning impossible if in a one tile wide section of the grid."); + + try { + optionParser.parse(argc, argv); + + if(helpOption->count() > 0) { + std::cout << optionParser << std::endl; + return EXIT_SUCCESS; + } + } catch (const popl::invalid_option &e) { + return io::printPoplException(e); + } catch (const std::exception &e) { + std::cerr << "Exception: " << e.what() << "\n"; + return EXIT_FAILURE; + } + + GridOptions gridOptions = { {}, {} }; + if(agentsToBeConsidered->is_set()) { + gridOptions.agentsToBeConsidered = parseCommaSeparatedString(agentsToBeConsidered->value(0)); + } + if(viewForAgents->is_set()) { + gridOptions.agentsWithView = parseCommaSeparatedString(viewForAgents->value(0)); + } + if(enforceOneWays->is_set()) { + gridOptions.enforceOneWays = true; + } + if(probabilisticBehaviourAgents->is_set()) { + gridOptions.agentsWithProbabilisticBehaviour = parseCommaSeparatedString(probabilisticBehaviourAgents->value(0)); + for(auto const& a : gridOptions.agentsWithProbabilisticBehaviour) { + std::cout << a << std::endl; + } + if(probabilities->is_set()) { + std::vector<std::string> parsedStrings = parseCommaSeparatedString(probabilities->value(0)); + + std::transform(parsedStrings.begin(), parsedStrings.end(), std::back_inserter(gridOptions.probabilitiesForActions), [](const std::string& string) { + return std::stof(string); + }); + for(auto const& a : gridOptions.probabilitiesForActions) { + std::cout << a << std::endl; + } + } else { + throw std::logic_error{ "When adding agents with probabilistic behaviour, you also need to specify a list of probabilities via --probs." }; + } + } + + + std::fstream file {outputFilename->value(0), file.trunc | file.out}; + std::fstream infile {inputFilename->value(0), infile.in}; + std::string line, content, background, rewards; + std::cout << "\n"; + bool parsingBackground = false; + bool parsingStateRewards = false; + while (std::getline(infile, line) && !line.empty()) { + if(line.at(0) == '-' && line.at(line.size() - 1) == '-' && parsingBackground) { + parsingStateRewards = true; + parsingBackground = false; + continue; + } else if(line.at(0) == '-' && line.at(line.size() - 1) == '-') { + parsingBackground = true; + continue; + } + if(!parsingBackground && !parsingStateRewards) { + std::cout << "Reading :\t" << line << "\n"; + content += line + "\n"; + } else if (parsingBackground) { + std::cout << "Background:\t" << line << "\n"; + background += line + "\n"; + } else if(parsingStateRewards) { + rewards += line + "\n"; + } + } + std::cout << "\n"; + + + pos_iterator_t contentFirst(content.begin()); + pos_iterator_t contentIter = contentFirst; + pos_iterator_t contentLast(content.end()); + MinigridParser<pos_iterator_t> contentParser(contentFirst); + pos_iterator_t backgroundFirst(background.begin()); + pos_iterator_t backgroundIter = backgroundFirst; + pos_iterator_t backgroundLast(background.end()); + MinigridParser<pos_iterator_t> backgroundParser(backgroundFirst); + + cells contentCells; + cells backgroundCells; + std::map<coordinates, float> stateRewards; + try { + bool ok = phrase_parse(contentIter, contentLast, contentParser, qi::space, contentCells); + // TODO if(background is not empty) { + ok &= phrase_parse(backgroundIter, backgroundLast, backgroundParser, qi::space, backgroundCells); + // TODO } + + boost::escaped_list_separator<char> seps('\\', ';', '\n'); + Tokenizer csvParser(rewards, seps); + for(auto iter = csvParser.begin(); iter != csvParser.end(); ++iter) { + int x = std::stoi(*iter); + int y = std::stoi(*(++iter)); + float reward = std::stof(*(++iter)); + stateRewards[std::make_pair(x,y)] = reward; + } + if(ok) { + Grid grid(contentCells, backgroundCells, gridOptions, stateRewards); + //grid.printToPrism(std::cout, prism::ModelType::MDP); + grid.printToPrism(file, prism::ModelType::MDP); + } + } catch(qi::expectation_failure<pos_iterator_t> const& e) { + std::cout << "expected: "; print_info(e.what_); + std::cout << "got: \"" << std::string(e.first, e.last) << '"' << std::endl; + std::cout << "Expectation failure: " << e.what() << " at '" << std::string(e.first,e.last) << "'\n"; + } catch(const std::exception& e) { + std::cerr << "Exception '" << typeid(e).name() << "' caught:" << std::endl; + std::cerr << "\t" << e.what() << std::endl; + std::exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000..f5884fe --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND SRCS + ${CMAKE_CURRENT_LIST_DIR}/cell.cpp + ${CMAKE_CURRENT_LIST_DIR}/MinigridGrammar.h + ${CMAKE_CURRENT_LIST_DIR}/Grid.cpp + ${CMAKE_CURRENT_LIST_DIR}/PrismModulesPrinter.cpp + ${CMAKE_CURRENT_LIST_DIR}/popl.hpp + ${CMAKE_CURRENT_LIST_DIR}/OptionParser.cpp +) diff --git a/util/Grid.cpp b/util/Grid.cpp new file mode 100644 index 0000000..1184e1e --- /dev/null +++ b/util/Grid.cpp @@ -0,0 +1,229 @@ +#include "Grid.h" + +#include <algorithm> + +Grid::Grid(cells gridCells, cells background, const GridOptions &gridOptions, const std::map<coordinates, float> &stateRewards) + : allGridCells(gridCells), background(background), gridOptions(gridOptions), stateRewards(stateRewards) +{ + cell max = allGridCells.at(allGridCells.size() - 1); + maxBoundaries = std::make_pair(max.row - 1, max.column - 1); + std::copy_if(gridCells.begin(), gridCells.end(), std::back_inserter(walls), [](cell c) { + return c.type == Type::Wall; + }); + std::copy_if(gridCells.begin(), gridCells.end(), std::back_inserter(lava), [](cell c) { + return c.type == Type::Lava; + }); + std::copy_if(gridCells.begin(), gridCells.end(), std::back_inserter(floor), [](cell c) { + return c.type == Type::Floor; + }); + std::copy_if(background.begin(), background.end(), std::back_inserter(slipperyNorth), [](cell c) { + return c.type == Type::SlipperyNorth; + }); + std::copy_if(background.begin(), background.end(), std::back_inserter(slipperyEast), [](cell c) { + return c.type == Type::SlipperyEast; + }); + std::copy_if(background.begin(), background.end(), std::back_inserter(slipperySouth), [](cell c) { + return c.type == Type::SlipperySouth; + }); + std::copy_if(background.begin(), background.end(), std::back_inserter(slipperyWest), [](cell c) { + return c.type == Type::SlipperyWest; + }); + std::copy_if(gridCells.begin(), gridCells.end(), std::back_inserter(lockedDoors), [](cell c) { + return c.type == Type::LockedDoor; + }); + std::copy_if(gridCells.begin(), gridCells.end(), std::back_inserter(goals), [](cell c) { + return c.type == Type::Goal; + }); + std::copy_if(gridCells.begin(), gridCells.end(), std::back_inserter(keys), [](cell c) { + return c.type == Type::Key; + }); + std::copy_if(gridCells.begin(), gridCells.end(), std::back_inserter(boxes), [](cell c) { + return c.type == Type::Box; + }); + agent = *std::find_if(gridCells.begin(), gridCells.end(), [](cell c) { + return c.type == Type::Agent; + }); + std::copy_if(gridCells.begin(), gridCells.end(), std::back_inserter(adversaries), [](cell c) { + return c.type == Type::Adversary; + }); + agentNameAndPositionMap.insert({ "Agent", agent.getCoordinates() }); + for(auto const& adversary : adversaries) { + std::string color = adversary.getColor(); + color.at(0) = std::toupper(color.at(0)); + try { + if(gridOptions.agentsToBeConsidered.size() != 0 && std::find(gridOptions.agentsToBeConsidered.begin(), gridOptions.agentsToBeConsidered.end(), color) == gridOptions.agentsToBeConsidered.end()) continue; + auto success = agentNameAndPositionMap.insert({ color, adversary.getCoordinates() }); + if(!success.second) { + throw std::logic_error("Agent with " + color + " already present\n"); + } + } catch(const std::logic_error& e) { + std::cerr << "Expected agents colors to be different. Agent with color : '" << color << "' already present." << std::endl; + throw; + } + } + for(auto const& color : allColors) { + cells cellsOfColor; + std::copy_if(background.begin(), background.end(), std::back_inserter(cellsOfColor), [&color](cell c) { + return c.type == Type::Floor && c.color == color; + }); + if(cellsOfColor.size() > 0) { + backgroundTiles.emplace(color, cellsOfColor); + } + } +} + +std::ostream& operator<<(std::ostream& os, const Grid& grid) { + int lastRow = 1; + for(auto const& cell : grid.allGridCells) { + if(lastRow != cell.row) + os << std::endl; + os << static_cast<char>(cell.type) << static_cast<char>(cell.color); + lastRow = cell.row; + } + return os; +} + +cells Grid::getGridCells() { + return allGridCells; +} + +bool Grid::isBlocked(coordinates p) { + return isWall(p) || isLockedDoor(p) || isKey(p); +} + +bool Grid::isWall(coordinates p) { + return std::find_if(walls.begin(), walls.end(), + [p](cell cell) { + return cell.row == p.first && cell.column == p.second; + }) != walls.end(); +} + +bool Grid::isLockedDoor(coordinates p) { + return std::find_if(lockedDoors.begin(), lockedDoors.end(), + [p](cell cell) { + return cell.row == p.first && cell.column == p.second; + }) != lockedDoors.end(); +} + +bool Grid::isKey(coordinates p) { + return std::find_if(keys.begin(), keys.end(), + [p](cell cell) { + return cell.row == p.first && cell.column == p.second; + }) != keys.end(); +} + +bool Grid::isBox(coordinates p) { + return std::find_if(boxes.begin(), boxes.end(), + [p](cell cell) { + return cell.row == p.first && cell.column == p.second; + }) != boxes.end(); +} + +void Grid::printToPrism(std::ostream& os, const prism::ModelType& modelType) { + cells northRestriction; + cells eastRestriction; + cells southRestriction; + cells westRestriction; + cells walkable = floor; + walkable.insert(walkable.end(), goals.begin(), goals.end()); + walkable.insert(walkable.end(), boxes.begin(), boxes.end()); + walkable.push_back(agent); + walkable.insert(walkable.end(), adversaries.begin(), adversaries.end()); + walkable.insert(walkable.end(), lava.begin(), lava.end()); + for(auto const& c : walkable) { + if(isBlocked(c.getNorth())) northRestriction.push_back(c); + if(isBlocked(c.getEast())) eastRestriction.push_back(c); + if(isBlocked(c.getSouth())) southRestriction.push_back(c); + if(isBlocked(c.getWest())) westRestriction.push_back(c); + } + + prism::PrismModulesPrinter printer(modelType, agentNameAndPositionMap.size(), gridOptions.enforceOneWays); + printer.printModel(os, modelType); + if(modelType == prism::ModelType::SMG) { + printer.printGlobalMoveVariable(os, agentNameAndPositionMap.size()); + } + for(auto const &backgroundTilesOfColor : backgroundTiles) { + for(auto agentNameAndPosition = agentNameAndPositionMap.begin(); agentNameAndPosition != agentNameAndPositionMap.end(); ++agentNameAndPosition) { + printer.printBackgroundLabels(os, agentNameAndPosition->first, backgroundTilesOfColor); + } + } + cells noTurnFloor; + if(gridOptions.enforceOneWays) { + for(auto const& c : floor) { + cell east = c.getEast(allGridCells); + cell south = c.getSouth(allGridCells); + cell west = c.getWest(allGridCells); + cell north = c.getNorth(allGridCells); + if( (east.type == Type::Wall && west.type == Type::Wall) or + (north.type == Type::Wall && south.type == Type::Wall) ) { + noTurnFloor.push_back(c); + } + } + } + + for(auto agentNameAndPosition = agentNameAndPositionMap.begin(); agentNameAndPosition != agentNameAndPositionMap.end(); ++agentNameAndPosition) { + printer.printFormulas(os, agentNameAndPosition->first, northRestriction, eastRestriction, southRestriction, westRestriction, { slipperyNorth, slipperyEast, slipperySouth, slipperyWest }, lava, walls, noTurnFloor, slipperyNorth, slipperyEast, slipperySouth, slipperyWest); + printer.printGoalLabel(os, agentNameAndPosition->first, goals); + printer.printKeysLabels(os, agentNameAndPosition->first, keys); + } + + std::vector<AgentName> agentNames; + std::transform(agentNameAndPositionMap.begin(), + agentNameAndPositionMap.end(), + std::back_inserter(agentNames), + [](const std::map<AgentNameAndPosition::first_type,AgentNameAndPosition::second_type>::value_type &pair){return pair.first;}); + if(modelType == prism::ModelType::SMG) { + printer.printCrashLabel(os, agentNames); + } + size_t agentIndex = 0; + for(auto agentNameAndPosition = agentNameAndPositionMap.begin(); agentNameAndPosition != agentNameAndPositionMap.end(); ++agentNameAndPosition, agentIndex++) { + AgentName agentName = agentNameAndPosition->first; + bool agentWithView = std::find(gridOptions.agentsWithView.begin(), gridOptions.agentsWithView.end(), agentName) != gridOptions.agentsWithView.end(); + bool agentWithProbabilisticBehaviour = std::find(gridOptions.agentsWithProbabilisticBehaviour.begin(), gridOptions.agentsWithProbabilisticBehaviour.end(), agentName) != gridOptions.agentsWithProbabilisticBehaviour.end(); + std::set<std::string> slipperyActions; + printer.printInitStruct(os, agentName); + if(agentWithProbabilisticBehaviour) printer.printModule(os, agentName, agentIndex, maxBoundaries, agentNameAndPosition->second, keys, backgroundTiles, agentWithView, gridOptions.probabilitiesForActions); + else printer.printModule(os, agentName, agentIndex, maxBoundaries, agentNameAndPosition->second, keys, backgroundTiles, agentWithView); + for(auto const& c : slipperyNorth) { + printer.printSlipperyMove(os, agentName, agentIndex, c.getCoordinates(), slipperyActions, getWalkableDirOf8Neighborhood(c), prism::PrismModulesPrinter::SlipperyType::North); + if(!gridOptions.enforceOneWays) printer.printSlipperyTurn(os, agentName, agentIndex, c.getCoordinates(), slipperyActions, getWalkableDirOf8Neighborhood(c), prism::PrismModulesPrinter::SlipperyType::North); + + } + for(auto const& c : slipperyEast) { + printer.printSlipperyMove(os, agentName, agentIndex, c.getCoordinates(), slipperyActions, getWalkableDirOf8Neighborhood(c), prism::PrismModulesPrinter::SlipperyType::East); + if(!gridOptions.enforceOneWays) printer.printSlipperyTurn(os, agentName, agentIndex, c.getCoordinates(), slipperyActions, getWalkableDirOf8Neighborhood(c), prism::PrismModulesPrinter::SlipperyType::East); + } + for(auto const& c : slipperySouth) { + printer.printSlipperyMove(os, agentName, agentIndex, c.getCoordinates(), slipperyActions, getWalkableDirOf8Neighborhood(c), prism::PrismModulesPrinter::SlipperyType::South); + if(!gridOptions.enforceOneWays) printer.printSlipperyTurn(os, agentName, agentIndex, c.getCoordinates(), slipperyActions, getWalkableDirOf8Neighborhood(c), prism::PrismModulesPrinter::SlipperyType::South); + } + for(auto const& c : slipperyWest) { + printer.printSlipperyMove(os, agentName, agentIndex, c.getCoordinates(), slipperyActions, getWalkableDirOf8Neighborhood(c), prism::PrismModulesPrinter::SlipperyType::West); + if(!gridOptions.enforceOneWays) printer.printSlipperyTurn(os, agentName, agentIndex, c.getCoordinates(), slipperyActions, getWalkableDirOf8Neighborhood(c), prism::PrismModulesPrinter::SlipperyType::West); + } + + printer.printEndmodule(os); + if(modelType == prism::ModelType::SMG) { + if(agentWithProbabilisticBehaviour) printer.printPlayerStruct(os, agentNameAndPosition->first, agentWithView, gridOptions.probabilitiesForActions, slipperyActions); + else printer.printPlayerStruct(os, agentNameAndPosition->first, agentWithView, {}, slipperyActions); + } + //if(!stateRewards.empty()) { + printer.printRewards(os, agentName, stateRewards, lava, goals, backgroundTiles); + //} + } +} + + +std::array<bool, 8> Grid::getWalkableDirOf8Neighborhood(cell c) /* const */ { + return (std::array<bool, 8>) + { + !isBlocked(c.getNorth()), + !isBlocked(c.getNorth(allGridCells).getEast()), + !isBlocked(c.getEast()), + !isBlocked(c.getSouth(allGridCells).getEast()), + !isBlocked(c.getSouth()), + !isBlocked(c.getSouth(allGridCells).getWest()), + !isBlocked(c.getWest()), + !isBlocked(c.getNorth(allGridCells).getWest()) + }; +} diff --git a/util/Grid.h b/util/Grid.h new file mode 100644 index 0000000..84c2eb2 --- /dev/null +++ b/util/Grid.h @@ -0,0 +1,63 @@ +#pragma once + +#include <iostream> +#include <fstream> +#include <map> +#include <utility> + +#include "MinigridGrammar.h" +#include "PrismModulesPrinter.h" + +struct GridOptions { + std::vector<AgentName> agentsToBeConsidered; + std::vector<AgentName> agentsWithView; + std::vector<AgentName> agentsWithProbabilisticBehaviour; + std::vector<float> probabilitiesForActions; + bool enforceOneWays; +}; + +class Grid { + public: + Grid(cells gridCells, cells background, const GridOptions &gridOptions, const std::map<coordinates, float> &stateRewards = {}); + + cells getGridCells(); + + bool isBlocked(coordinates p); + bool isWall(coordinates p); + bool isLockedDoor(coordinates p); + bool isKey(coordinates p); + bool isBox(coordinates p); + void printToPrism(std::ostream &os, const prism::ModelType& modelType); + + std::array<bool, 8> getWalkableDirOf8Neighborhood(cell c); + + friend std::ostream& operator<<(std::ostream& os, const Grid &grid); + + private: + GridOptions gridOptions; + + cells allGridCells; + cells background; + coordinates maxBoundaries; + + cell agent; + cells adversaries; + AgentNameAndPositionMap agentNameAndPositionMap; + + cells walls; + cells floor; + cells slipperyNorth; + cells slipperyEast; + cells slipperySouth; + cells slipperyWest; + cells lockedDoors; + cells boxes; + cells lava; + + cells goals; + cells keys; + + std::map<Color, cells> backgroundTiles; + + std::map<coordinates, float> stateRewards; +}; diff --git a/util/MinigridGrammar.h b/util/MinigridGrammar.h new file mode 100644 index 0000000..79684a5 --- /dev/null +++ b/util/MinigridGrammar.h @@ -0,0 +1,102 @@ +#pragma once + +#include "cell.h" + +#include <vector> + +#include <boost/tokenizer.hpp> +#include <boost/fusion/adapted/struct.hpp> +#include <boost/spirit/include/qi.hpp> +#include <boost/spirit/include/phoenix.hpp> +#include <boost/spirit/include/phoenix_operator.hpp> +#include <boost/variant/recursive_wrapper.hpp> +#include <boost/spirit/include/support_line_pos_iterator.hpp> + +namespace qi = boost::spirit::qi; +namespace phoenix = boost::phoenix; + +typedef boost::spirit::line_pos_iterator<std::string::const_iterator> pos_iterator_t; + +typedef std::vector<cell> row; +typedef std::vector<cell> cells; + +BOOST_FUSION_ADAPT_STRUCT( + cell, + (Type, type) + (Color, color) +) + +template<typename It> +struct annotation_f { + typedef void result_type; + + annotation_f(It first) : first(first) {} + It const first; + + template<typename Val, typename First, typename Last> + void operator()(Val& v, First f, Last l) const { + do_annotate(v, f, l, first); + } + + private: + void static do_annotate(cell& c, It f, It l, It first) { + c.row = get_line(f) - 1; + c.column = get_column(first, f) / 2; + } + static void do_annotate(...) { std::cerr << "(not having LocationInfo)\n"; } +}; + +template <typename It> + struct MinigridParser : qi::grammar<It, row()> +{ + MinigridParser(It first) : MinigridParser::base_type(row_), annotate(first) + { + using namespace qi; + type_.add + ("W", Type::Wall) + (" ", Type::Floor) + ("D", Type::Door) + ("L", Type::LockedDoor) + ("K", Type::Key) + ("A", Type::Ball) + ("B", Type::Box) + ("G", Type::Goal) + ("V", Type::Lava) + ("n", Type::SlipperyNorth) + ("e", Type::SlipperyEast) + ("s", Type::SlipperySouth) + ("w", Type::SlipperyWest) + ("X", Type::Agent) + ("Z", Type::Adversary); + color_.add + ("R", Color::Red) + ("G", Color::Green) + ("B", Color::Blue) + ("P", Color::Purple) + ("Y", Color::Yellow) + (" ", Color::None); + + cell_ = type_ > color_; + + row_ = (cell_ % -qi::char_("\n")); + + auto set_location_info = annotate(_val, _1, _3); + on_success(cell_, set_location_info); + + BOOST_SPIRIT_DEBUG_NODE(type_); + BOOST_SPIRIT_DEBUG_NODE(color_); + BOOST_SPIRIT_DEBUG_NODE(cell_); + } + + private: + phoenix::function<annotation_f<It>> annotate; + + qi::symbols<char, Type> type_; + qi::symbols<char, Color> color_; + + qi::rule<It, cell()> cell_; + qi::rule<It, row()> row_; +}; + +typedef boost::tokenizer< boost::escaped_list_separator<char> , std::string::const_iterator, std::string> Tokenizer; +//std::ostream& operator<<(std::ostream& os, const row& r); diff --git a/util/OptionParser.cpp b/util/OptionParser.cpp new file mode 100644 index 0000000..715c8c4 --- /dev/null +++ b/util/OptionParser.cpp @@ -0,0 +1,32 @@ +#include <iostream> + +#include "popl.hpp" +#include "OptionParser.h" + +namespace io { + int printPoplException(const popl::invalid_option &e) { + std::cerr << "Invalid Option Exception: " << e.what() << "\n"; + std::cerr << "error: "; + if (e.error() == popl::invalid_option::Error::missing_argument) { + std::cerr << "missing_argument\n"; + } else if (e.error() == popl::invalid_option::Error::invalid_argument) { + std::cerr << "invalid_argument\n"; + } else if (e.error() == popl::invalid_option::Error::too_many_arguments) { + std::cerr << "too_many_arguments\n"; + } else if (e.error() == popl::invalid_option::Error::missing_option) { + std::cerr << "missing_option\n"; + } + + if (e.error() == popl::invalid_option::Error::missing_option) { + std::string option_name(e.option()->name(popl::OptionName::short_name, true)); + if (option_name.empty()) + option_name = e.option()->name(popl::OptionName::long_name, true); + std::cerr << "option: " << option_name << "\n"; + } + else { + std::cerr << "option: " << e.option()->name(e.what_name()) << "\n"; + std::cerr << "value: " << e.value() << "\n"; + } + return EXIT_FAILURE; + } +} diff --git a/util/OptionParser.h b/util/OptionParser.h new file mode 100644 index 0000000..68af918 --- /dev/null +++ b/util/OptionParser.h @@ -0,0 +1,13 @@ +#pragma once + +#include <iostream> + +#include "popl.hpp" + +#define INPUT_ERROR 1 +#define INPUT_OK 0 + +namespace io { + int printPoplException(const popl::invalid_option &e); + +} diff --git a/util/PrismModulesPrinter.cpp b/util/PrismModulesPrinter.cpp new file mode 100644 index 0000000..f6aca04 --- /dev/null +++ b/util/PrismModulesPrinter.cpp @@ -0,0 +1,681 @@ +#include "PrismModulesPrinter.h" + +#include <map> +#include <string> + +namespace prism { + + PrismModulesPrinter::PrismModulesPrinter(const ModelType &modelType, const size_t &numberOfPlayer, const bool enforceOneWays) + : modelType(modelType), numberOfPlayer(numberOfPlayer), enforceOneWays(enforceOneWays) { + } + + std::ostream& PrismModulesPrinter::printModel(std::ostream &os, const ModelType &modelType) { + switch(modelType) { + case(ModelType::MDP): + os << "mdp"; + break; + case(ModelType::SMG): + os << "smg"; + break; + } + os << "\n\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printBackgroundLabels(std::ostream &os, const AgentName &agentName, const std::pair<Color, cells> &backgroundTiles) { + if(backgroundTiles.second.size() == 0) return os; + + bool first = true; + std::string color = getColor(backgroundTiles.first); + color.at(0) = std::toupper(color.at(0)); + os << "formula " << agentName << "On" << color << " = "; + for(auto const& cell : backgroundTiles.second) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + os << "label \"" << agentName << "On" << color << "\" = " << agentName << "On" << color << ";\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printRestrictionFormula(std::ostream& os, const AgentName &agentName, const std::string &direction, const cells &cells) { + bool first = true; + os << "formula " << agentName << "CannotMove" << direction << " = " ; + for(auto const& cell : cells) { + if(first) first = false; + else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printIsOnSlipperyFormula(std::ostream& os, const AgentName &agentName, const std::vector<std::reference_wrapper<cells>> &slipperyCollection, const cells &slipperyNorth, const cells &slipperyEast, const cells &slipperySouth, const cells &slipperyWest) { + if(std::find_if(slipperyCollection.cbegin(), slipperyCollection.cend(), [=](const std::reference_wrapper<cells>& c) { return !c.get().empty(); }) == slipperyCollection.cend()) { + os << "formula " << agentName << "IsOnSlippery = false;\n"; + return os; + } + + bool first = true; + os << "formula " << agentName << "IsOnSlippery = "; + + for (const auto& slippery: slipperyCollection) { + for(const auto& cell : slippery.get()) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + } + os << ";\n"; + if(enforceOneWays) { + first = true; + os << "formula " << agentName << "IsOnSlipperyNorth = "; + + for (const auto& cell: slipperyNorth) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + first = true; + os << "formula " << agentName << "IsOnSlipperyEast = "; + + for (const auto& cell: slipperyEast) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + first = true; + os << "formula " << agentName << "IsOnSlipperySouth = "; + + for (const auto& cell: slipperySouth) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + first = true; + os << "formula " << agentName << "IsOnSlipperyWest = "; + for (const auto& cell: slipperyWest) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + } + return os; + } + + std::ostream& PrismModulesPrinter::printIsInLavaFormula(std::ostream& os, const AgentName &agentName, const cells &lava) { + if(lava.size() == 0) { + os << "formula " << agentName << "IsInLava = false;\n"; + return os; + } + bool first = true; + os << "formula " << agentName << "IsInLava = "; + for(auto const& cell : lava) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + os << "formula " << agentName << "IsInLavaAndNotDone = " << agentName << "IsInLava & !" << agentName << "Done;\n"; + os << "label \"" << agentName << "IsInLavaAndNotDone\" = " << agentName << "IsInLava & !" << agentName << "Done;\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printTurningNotAllowedFormulas(std::ostream& os, const AgentName &agentName, const cells &noTurnFloor) { + if( (!enforceOneWays or noTurnFloor.size() == 0) or (noTurnFloor.size() == 0) ) { + os << "formula " << agentName << "CannotTurn = false;\n"; + return os; + } + bool first = true; + os << "formula " << agentName << "CannotTurn = "; + for(auto const& cell : noTurnFloor) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << " | " << agentName << "IsOnSlippery;\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printIsFixedFormulas(std::ostream& os, const AgentName &agentName) { + os << "formula " << agentName << "IsFixed = false;\n"; + os << "formula " << agentName << "SlipperyTurnLeftAllowed = true;\n"; + os << "formula " << agentName << "SlipperyTurnRightAllowed = true;\n"; + os << "formula " << agentName << "SlipperyMoveForwardAllowed = true;\n"; + os << "label \"FixedStates\" = " << agentName << "IsFixed | !" << agentName << "SlipperyTurnRightAllowed | !" << agentName << "SlipperyTurnLeftAllowed | !" << agentName << "SlipperyMoveForwardAllowed | " << agentName << "IsInGoal | " << agentName << "IsInLava"; + if(enforceOneWays) { + os << " | " << agentName << "CannotTurn"; + } + os << ";\n"; + //os << "label \"FixedStates\" = " << agentName << "IsFixed | " << agentName << "IsOnSlippery | " << agentName << "IsInGoal | " << agentName << "IsInLava;\n"; + + return os; + } + + + std::ostream& PrismModulesPrinter::printWallFormula(std::ostream& os, const AgentName &agentName, const cells &walls) { + os << "formula " << agentName << "IsOnWall = "; + bool first = true; + for(auto const& cell : walls) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printFormulas(std::ostream& os, + const AgentName &agentName, + const cells &restrictionNorth, + const cells &restrictionEast, + const cells &restrictionSouth, + const cells &restrictionWest, + const std::vector<std::reference_wrapper<cells>> &slipperyCollection, + const cells &lava, + const cells &walls, + const cells &noTurnFloor, + const cells &slipperyNorth, + const cells &slipperyEast, + const cells &slipperySouth, + const cells &slipperyWest) { + printRestrictionFormula(os, agentName, "North", restrictionNorth); + printRestrictionFormula(os, agentName, "East ", restrictionEast); + printRestrictionFormula(os, agentName, "South", restrictionSouth); + printRestrictionFormula(os, agentName, "West ", restrictionWest); + printIsOnSlipperyFormula(os, agentName, slipperyCollection, slipperyNorth, slipperyEast, slipperySouth, slipperyWest); + printIsInLavaFormula(os, agentName, lava); + printWallFormula(os, agentName, walls); + printTurningNotAllowedFormulas(os, agentName, noTurnFloor); + printIsFixedFormulas(os, agentName); + os << "\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printGoalLabel(std::ostream& os, const AgentName &agentName, const cells &goals) { + if(goals.size() == 0) { + os << "formula " << agentName << "IsInGoal = false;\n"; + return os; + } + + bool first = true; + os << "formula " << agentName << "IsInGoal = "; + for(auto const& cell : goals) { + if(first) first = false; else os << " | "; + os << "(x" << agentName << "=" << cell.column << "&y" << agentName << "=" << cell.row << ")"; + } + os << ";\n"; + os << "formula " << agentName << "IsInGoalAndNotDone = " << agentName << "IsInGoal & !" << agentName << "Done;\n"; + os << "label \"" << agentName << "IsInGoalAndNotDone\" = " << agentName << "IsInGoal & !" << agentName << "Done;\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printCrashLabel(std::ostream &os, const std::vector<AgentName> agentNames) { + os << "label crash = "; + bool first = true; + for(auto const& agentName : agentNames) { + if(agentName == "Agent") continue; + if(first) first = false; else os << " | "; + os << "(xAgent=x" << agentName << ")&(yAgent=y" << agentName << ")"; + } + os << ";\n\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printAvoidanceLabel(std::ostream &os, const std::vector<AgentName> agentNames, const int &distance) { + os << "label avoidance = "; + bool first = true; + for(auto const& agentName : agentNames) { + if(agentName == "Agent") continue; + if(first) first = false; else os << " | "; + os << "max(xAgent-x" << agentName << ",x" << agentName << "-xAgent)+"; + os << "max(yAgent-y" << agentName << ",y" << agentName << "-yAgent) "; + } + os << ";\n\n"; + return os; + } + + // TODO this does not account for multiple agents yet, i.e. key can be picked up multiple times + std::ostream& PrismModulesPrinter::printKeysLabels(std::ostream& os, const AgentName &agentName, const cells &keys) { + if(keys.size() == 0) return os; + + for(auto const& key : keys) { + int xKey = key.getCoordinates().first; + int yKey = key.getCoordinates().second; + std::string keyColor = key.getColor(); + os << "label \"" << agentName << "PickedUp" << keyColor << "Key\" = " << agentName << "_has_" << keyColor << "_key = true;\n"; + os << "formula " << agentName << "CanPickUp" << keyColor << "Key = "; + os << "((x" << agentName << "-1 = " << xKey << "&y" << agentName << " = " << yKey << "&view" << agentName << " = 1) |"; + os << " (x" << agentName << "+1 = " << xKey << "&y" << agentName << " = " << yKey << "&view" << agentName << " = 3) |"; + os << " (x" << agentName << " = " << xKey << "&y" << agentName << "-1 = " << yKey << "&view" << agentName << " = 0) |"; + os << " (x" << agentName << " = " << xKey << "&y" << agentName << "+1 = " << yKey << "&view" << agentName << " = 2) ) &"; + os << "!" << agentName << "_has_" << keyColor << "_key;"; + } + os << "\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printBooleansForKeys(std::ostream &os, const AgentName &agentName, const cells &keys) { + for(auto const& key : keys) { + os << "\t" << agentName << "_has_"<< key.getColor() << "_key : bool init false;\n"; + } + os << "\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printBooleansForBackground(std::ostream &os, const AgentName &agentName, const std::map<Color, cells> &backgroundTiles) { + for(auto const& [color, cells] : backgroundTiles) { + if(cells.size() == 0) continue; + std::string c = getColor(color); + c.at(0) = std::toupper(c.at(0)); + os << "\t" << agentName << "_picked_up_" << c << " : bool init false;\n"; + } + os << "\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printActionsForKeys(std::ostream &os, const AgentName &agentName, const cells &keys) { + for(auto const& key : keys) { + std::string keyColor = key.getColor(); + os << "\t[pickup_" << keyColor << "_key] " << agentName << "CanPickUp" << keyColor << "Key -> "; + os << "(" << agentName << "_has_" << keyColor << "_key'=true);"; + } + os << "\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printActionsForBackground(std::ostream &os, const AgentName &agentName, const std::map<Color, cells> &backgroundTiles) { + for(auto const& [color, cells] : backgroundTiles) { + if(cells.size() == 0) continue; + std::string c = getColor(color); + c.at(0) = std::toupper(c.at(0)); + os << "\t[" << agentName << "_pickup_" << c << "] " << agentName << "On" << c << " & !" << agentName << "_picked_up_" << c << " -> "; + os << "(" << agentName << "_picked_up_" << c << "'=true);\n"; + } + os << "\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printInitStruct(std::ostream &os, const AgentName &agentName) { + os << "init\n"; + os << "\t(!AgentIsInGoal & !AgentIsInLava & !AgentDone & !AgentIsOnWall)"; + if(enforceOneWays) { + os << " & ( !AgentCannotTurn ) "; + } else { + os << " & ( !AgentIsOnSlippery ) "; + } + os << "\nendinit\n\n"; + + return os; + } + + std::ostream& PrismModulesPrinter::printModule(std::ostream &os, const AgentName &agentName, const size_t &agentIndex, const coordinates &boundaries, const coordinates& initialPosition, const cells &keys, const std::map<Color, cells> &backgroundTiles, const bool agentWithView, const std::vector<float> &probabilities) { + os << "module " << agentName << "\n"; + os << "\tx" << agentName << " : [1.." << boundaries.second << "];\n"; + os << "\ty" << agentName << " : [1.." << boundaries.first << "];\n"; + printBooleansForKeys(os, agentName, keys); + printBooleansForBackground(os, agentName, backgroundTiles); + os << "\t" << agentName << "Done : bool;\n"; + if(agentWithView) { + os << "\tview" << agentName << " : [0..3];\n"; + os << "\n"; + os << "\t[" << agentName << "_turn_right] " << moveGuard(agentIndex) << " !" << agentName << "CannotTurn & " << " !" << agentName << "IsFixed & " << " !" << agentName << "IsInGoal & !" << agentName << "IsInLava & !" << agentName << "IsOnSlippery -> (view" << agentName << "'=mod(view" << agentName << " + 1, 4)) " << moveUpdate(agentIndex) << ";\n"; + os << "\t[" << agentName << "_turn_left] " << moveGuard(agentIndex) << " !" << agentName << "CannotTurn & " << " !" << agentName << "IsFixed & " << " !" << agentName << "IsInGoal & !" << agentName << "IsInLava & !" << agentName << "IsOnSlippery & view" << agentName << ">0 -> (view" << agentName << "'=view" << agentName << " - 1) " << moveUpdate(agentIndex) << ";\n"; + os << "\t[" << agentName << "_turn_left] " << moveGuard(agentIndex) << " !" << agentName << "CannotTurn & " << " !" << agentName << "IsFixed & " << " !" << agentName << "IsInGoal & !" << agentName << "IsInLava & !" << agentName << "IsOnSlippery & view" << agentName << "=0 -> (view" << agentName << "'=3) " << moveUpdate(agentIndex) << ";\n"; + if(enforceOneWays) { + os << "\t[" << agentName << "_stuck] !" << agentName << "IsFixed & " << agentName << "CannotTurn & view" << agentName << " = 0 & " << agentName << "CannotMoveEast -> true;\n"; + os << "\t[" << agentName << "_stuck] !" << agentName << "IsFixed & " << agentName << "CannotTurn & view" << agentName << " = 1 & " << agentName << "CannotMoveSouth -> true;\n"; + os << "\t[" << agentName << "_stuck] !" << agentName << "IsFixed & " << agentName << "CannotTurn & view" << agentName << " = 2 & " << agentName << "CannotMoveWest -> true;\n"; + os << "\t[" << agentName << "_stuck] !" << agentName << "IsFixed & " << agentName << "CannotTurn & view" << agentName << " = 3 & " << agentName << "CannotMoveNorth -> true;\n"; + } + } else { + os << "\t[" << agentName << "_turns] " << " !" << agentName << "CannotTurn & " << " !" << agentName << "IsFixed & " << moveGuard(agentIndex) << " true -> (x" << agentName << "'=x" << agentName << ")" << moveUpdate(agentIndex) << ";\n"; + } + printActionsForKeys(os, agentName, keys); + printActionsForBackground(os, agentName, backgroundTiles); + os << "\n"; + printMovementActions(os, agentName, agentIndex, agentWithView); + for(auto const& probability : probabilities) { + printMovementActions(os, agentName, agentIndex, agentWithView, probability); + } + printDoneActions(os, agentName, agentIndex); + os << "\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printMovementActions(std::ostream &os, const AgentName &agentName, const size_t &agentIndex, const bool agentWithView, const float &probability) { + if(probability >= 1) { + os << "\t[" << agentName << "_move_north]" << moveGuard(agentIndex) << viewVariable(agentName, 3, agentWithView) << " !" << agentName << "IsFixed & " << " !" << agentName << "IsOnSlippery & !" << agentName << "IsInLava &!" << agentName << "IsInGoal & !" << agentName << "CannotMoveNorth -> (y" << agentName << "'=y" << agentName << "-1)" << moveUpdate(agentIndex) << ";\n"; + os << "\t[" << agentName << "_move_east] " << moveGuard(agentIndex) << viewVariable(agentName, 0, agentWithView) << " !" << agentName << "IsFixed & " << " !" << agentName << "IsOnSlippery & !" << agentName << "IsInLava &!" << agentName << "IsInGoal & !" << agentName << "CannotMoveEast -> (x" << agentName << "'=x" << agentName << "+1)" << moveUpdate(agentIndex) << ";\n"; + os << "\t[" << agentName << "_move_south]" << moveGuard(agentIndex) << viewVariable(agentName, 1, agentWithView) << " !" << agentName << "IsFixed & " << " !" << agentName << "IsOnSlippery & !" << agentName << "IsInLava &!" << agentName << "IsInGoal & !" << agentName << "CannotMoveSouth -> (y" << agentName << "'=y" << agentName << "+1)" << moveUpdate(agentIndex) << ";\n"; + os << "\t[" << agentName << "_move_west] " << moveGuard(agentIndex) << viewVariable(agentName, 2, agentWithView) << " !" << agentName << "IsFixed & " << " !" << agentName << "IsOnSlippery & !" << agentName << "IsInLava &!" << agentName << "IsInGoal & !" << agentName << "CannotMoveWest -> (x" << agentName << "'=x" << agentName << "-1)" << moveUpdate(agentIndex) << ";\n"; + } else { + std::string probabilityString = std::to_string(probability); + std::string percentageString = std::to_string((int)(100 * probability)); + std::string complementProbabilityString = std::to_string(1 - probability); + os << "\t[" << agentName << "_move_north_" << percentageString << "] "; + os << moveGuard(agentIndex) << viewVariable(agentName, 3, agentWithView) << " !" << agentName << "IsFixed & " << " !" << agentName << "IsOnSlippery & !" << agentName << "IsInLava & !" << agentName << "CannotMoveNorth -> "; + os << probabilityString << ": (y" << agentName << "'=y" << agentName << "-1)" << moveUpdate(agentIndex) << " + "; + os << complementProbabilityString << ": (y" << agentName << "'=y" << agentName << ") " << moveUpdate(agentIndex) << ";\n"; + + os << "\t[" << agentName << "_move_east_" << percentageString << "] "; + os << moveGuard(agentIndex) << viewVariable(agentName, 0, agentWithView) << " !" << agentName << "IsFixed & " << " !" << agentName << "IsOnSlippery & !" << agentName << "IsInLava & !" << agentName << "CannotMoveEast -> "; + os << probabilityString << ": (x" << agentName << "'=x" << agentName << "+1)" << moveUpdate(agentIndex) << " + "; + os << complementProbabilityString << ": (x" << agentName << "'=x" << agentName << ") " << moveUpdate(agentIndex) << ";\n"; + + os << "\t[" << agentName << "_move_south_" << percentageString << "] "; + os << moveGuard(agentIndex) << viewVariable(agentName, 1, agentWithView) << " !" << agentName << "IsFixed & " << " !" << agentName << "IsOnSlippery & !" << agentName << "IsInLava & !" << agentName << "CannotMoveSouth -> "; + os << probabilityString << ": (y" << agentName << "'=y" << agentName << "+1)" << moveUpdate(agentIndex) << " + "; + os << complementProbabilityString << ": (y" << agentName << "'=y" << agentName << ") " << moveUpdate(agentIndex) << ";\n"; + + os << "\t[" << agentName << "_move_west_" << percentageString << "] "; + os << moveGuard(agentIndex) << viewVariable(agentName, 2, agentWithView) << " !" << agentName << "IsFixed & " << " !" << agentName << "IsOnSlippery & !" << agentName << "IsInLava & !" << agentName << "CannotMoveWest -> "; + os << probabilityString << ": (x" << agentName << "'=x" << agentName << "-1)" << moveUpdate(agentIndex) << " + "; + os << complementProbabilityString << ": (x" << agentName << "'=x" << agentName << ") " << moveUpdate(agentIndex) << ";\n"; + } + return os; + } + + std::ostream& PrismModulesPrinter::printDoneActions(std::ostream &os, const AgentName &agentName, const size_t &agentIndex) { + os << "\t[" << agentName << "_done]" << moveGuard(agentIndex) << agentName << "IsInGoal | " << agentName << "IsInLava -> (" << agentName << "Done'=true);\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printSlipperyTurn(std::ostream &os, const AgentName &agentName, const size_t &agentIndex, const coordinates &c, std::set<std::string> &slipperyActions, const std::array<bool, 8>& neighborhood, SlipperyType orientation) { + constexpr std::size_t PROB_PIECES = 9, ALL_POSS_DIRECTIONS = 9; + + std::array<std::string, ALL_POSS_DIRECTIONS> positionTransition = { + /* north */ "(y" + agentName + "'=y" + agentName + "-1)", + /* north east */ "(x" + agentName + "'=x" + agentName + "+1) & (y" + agentName + "'=y" + agentName + "-1)", + /* east */ "(x" + agentName + "'=x" + agentName + "+1)", + /* east south */ "(x" + agentName + "'=x" + agentName + "+1) & (y" + agentName + "'=y" + agentName + "+1)", + /* south */ "(y" + agentName + "'=y" + agentName + "+1)", + /* south west */ "(x" + agentName + "'=x" + agentName + "-1) & (y" + agentName + "'=y" + agentName + "+1)", + /* west */ "(x" + agentName + "'=x" + agentName + "-1)", + /* north west */ "(x" + agentName + "'=x" + agentName + "-1) & (y" + agentName + "'=y" + agentName + "-1)", + /* own position */ "(x" + agentName + "'=x" + agentName + ") & (y" + agentName + "'=y" + agentName + ")" + }; + + // view transition appdx in form (guard, update part) + // IMPORTANT: No mod() usage for turn left due to bug in mod() function for decrement + + std::array<std::pair<std::string, std::string>, 3> viewTransition = { + /* turn to right */ std::make_pair(" & " + agentName + "SlipperyTurnRightAllowed ", " & (view" + agentName + "'=mod(view" + agentName + " + 1, 4))"), + /* turn to left */ std::make_pair(" & " + agentName + "SlipperyTurnLeftAllowed & view" + agentName + ">0", " & (view" + agentName + "'=view" + agentName + " - 1)"), + /* turn to left */ std::make_pair(" & " + agentName + "SlipperyTurnLeftAllowed & view" + agentName + "=0", " & (view" + agentName + "'=3)") + }; + + // direction specifics + + std::string actionName; + std::size_t remainPosIndex = 8; + std::array<std::size_t, ALL_POSS_DIRECTIONS> prob_piece_dir; // { n, ne, w, se, s, sw, w, nw, CURRENT POS } + + switch (orientation) + { + case SlipperyType::North: + actionName = "\t[" + agentName + "turn_at_slip_north]"; + prob_piece_dir = { 0, 0, 0, 1, 1, 1, 0, 0, 0 /* <- R */ }; + break; + + case SlipperyType::South: + actionName = "\t[" + agentName + "turn_at_slip_south]"; + prob_piece_dir = { 1, 1, 0, 0, 0, 0, 0, 1, 0 /* <- R */ }; + break; + + case SlipperyType::East: + actionName = "\t[" + agentName + "turn_at_slip_east]"; + prob_piece_dir = { 0, 0, 0, 0, 0, 1, 1, 1, 0 /* <- R */ }; + break; + + case SlipperyType::West: + actionName = "\t[" + agentName + "turn_at_slip_west]"; + prob_piece_dir = { 0, 1, 1, 1, 0, 0, 0, 0, 0 /* <- R */ }; + break; + } + + slipperyActions.insert(actionName); + + // override probability to 0 if corresp. direction is blocked + + for (std::size_t i = 0; i < ALL_POSS_DIRECTIONS - 1; i++) { + if (!neighborhood.at(i)) + prob_piece_dir.at(i) = 0; + } + + // determine residual probability (R) by replacing 0 with (1 - overall sum) + + prob_piece_dir.at(remainPosIndex) = PROB_PIECES - std::accumulate(prob_piece_dir.begin(), prob_piece_dir.end(), 0); + + // <DEBUG_AREA> + { + assert(prob_piece_dir.at(remainPosIndex) <= 9 && prob_piece_dir.at(remainPosIndex) >= 6 && "Value not in Range!"); + assert(std::accumulate(prob_piece_dir.begin(), prob_piece_dir.end(), 0) == PROB_PIECES && "Does not sum up to 1!"); + } + // </DEBUG_AREA> + + + // generic output (for every view transition) + for (std::size_t v = 0; v < viewTransition.size(); v++) { + + os << actionName << moveGuard(agentIndex) << " x" << agentName << "=" << c.second << " & y" << agentName << "=" << c.first << viewTransition.at(v).first; + for (std::size_t i = 0; i < ALL_POSS_DIRECTIONS; i++) { + os << (i == 0 ? " -> " : " + ") << prob_piece_dir.at(i) << "/" << PROB_PIECES << " : " << positionTransition.at(i) << viewTransition.at(v).second << moveUpdate(agentIndex) << (i == ALL_POSS_DIRECTIONS - 1 ? ";\n" : "\n"); + } + } + + return os; + } + + std::ostream& PrismModulesPrinter::printSlipperyMove(std::ostream &os, const AgentName &agentName, const size_t &agentIndex, const coordinates &c, std::set<std::string> &slipperyActions, const std::array<bool, 8>& neighborhood, SlipperyType orientation) { + constexpr std::size_t PROB_PIECES = 9, ALL_POSS_DIRECTIONS = 8; + + std::array<std::string, ALL_POSS_DIRECTIONS> positionTransition = { + /* north */ "(y" + agentName + "'=y" + agentName + "-1)", + /* north east */ "(x" + agentName + "'=x" + agentName + "+1) & (y" + agentName + "'=y" + agentName + "-1)", + /* east */ "(x" + agentName + "'=x" + agentName + "+1)", + /* east south */ "(x" + agentName + "'=x" + agentName + "+1) & (y" + agentName + "'=y" + agentName + "+1)", + /* south */ "(y" + agentName + "'=y" + agentName + "+1)", + /* south west */ "(x" + agentName + "'=x" + agentName + "-1) & (y" + agentName + "'=y" + agentName + "+1)", + /* west */ "(x" + agentName + "'=x" + agentName + "-1)", + /* north west */ "(x" + agentName + "'=x" + agentName + "-1) & (y" + agentName + "'=y" + agentName + "-1)" + }; + + // direction specifics + + std::size_t straightPosIndex; + std::string actionName, specialTransition; // if straight ahead is blocked + std::array<std::size_t, ALL_POSS_DIRECTIONS> prob_piece_dir; // { n, ne, w, se, s, sw, w, nw } + + switch (orientation) + { + case SlipperyType::North: + actionName = "\t[" + agentName + "move_on_slip_north]"; + prob_piece_dir = { 0, 0, 1, 2, 0 /* <- R */, 2, 1, 0 }; + straightPosIndex = 4; + specialTransition = "(y" + agentName + "'=y" + agentName + (!neighborhood.at(straightPosIndex) ? ")" : "+1)"); + break; + + case SlipperyType::South: + actionName = "\t[" + agentName + "move_on_slip_south]"; + prob_piece_dir = { 0 /* <- R */, 2, 1, 0, 0, 0, 1, 2 }; + straightPosIndex = 0; // always north + specialTransition = "(y" + agentName + "'=y" + agentName + (!neighborhood.at(straightPosIndex) ? ")" : "-1)"); + break; + + case SlipperyType::East: + actionName = "\t[" + agentName + "move_on_slip_east]"; + prob_piece_dir = { 1, 0, 0, 0, 1, 2, 0 /* <- R */, 2 }; + straightPosIndex = 6; + specialTransition = "(x" + agentName + "'=x" + agentName + (!neighborhood.at(straightPosIndex) ? ")" : "-1)"); + break; + + case SlipperyType::West: + actionName = "\t[" + agentName + "move_on_slip_west]"; + prob_piece_dir = { 1, 2, 0 /* <- R */, 2, 1, 0, 0, 0 }; + straightPosIndex = 2; + specialTransition = "(x" + agentName + "'=x" + agentName + (!neighborhood.at(straightPosIndex) ? ")" : "+1)"); + break; + } + + slipperyActions.insert(actionName); + + // override probability to 0 if corresp. direction is blocked + + for (std::size_t i = 0; i < ALL_POSS_DIRECTIONS; i++) { + if (!neighborhood.at(i)) + prob_piece_dir.at(i) = 0; + } + + // determine residual probability (R) by replacing 0 with (1 - overall sum) + if(enforceOneWays) { + prob_piece_dir = {0,0,0,0,0,0,0,0}; + } + prob_piece_dir.at(straightPosIndex) = PROB_PIECES - std::accumulate(prob_piece_dir.begin(), prob_piece_dir.end(), 0); + + // <DEBUG_AREA> + { + assert(prob_piece_dir.at(straightPosIndex) <= 9 && prob_piece_dir.at(straightPosIndex) >= 3 && "Value not in Range!"); + assert(std::accumulate(prob_piece_dir.begin(), prob_piece_dir.end(), 0) == PROB_PIECES && "Does not sum up to 1!"); + assert(orientation != SlipperyType::North || (prob_piece_dir.at(7) == 0 && prob_piece_dir.at(0) == 0 && prob_piece_dir.at(1) == 0 && "Slippery up should be impossible!")); + assert(orientation != SlipperyType::South || (prob_piece_dir.at(3) == 0 && prob_piece_dir.at(4) == 0 && prob_piece_dir.at(5) == 0 && "Slippery down should be impossible!")); + assert(orientation != SlipperyType::East || (prob_piece_dir.at(1) == 0 && prob_piece_dir.at(2) == 0 && prob_piece_dir.at(3) == 0 && "Slippery right should be impossible!")); + assert(orientation != SlipperyType::West || (prob_piece_dir.at(5) == 0 && prob_piece_dir.at(6) == 0 && prob_piece_dir.at(7) == 0 && "Slippery left should be impossible!")); + } + // </DEBUG_AREA> + + // special case: straight forward is blocked (then remain in same position) + + positionTransition.at(straightPosIndex) = specialTransition; + + // generic output (for every view and every possible view direction) + + os << actionName << moveGuard(agentIndex) << " x" << agentName << "=" << c.second << " & y" << agentName << "=" << c.first << " & " << agentName << "SlipperyMoveForwardAllowed "; + + for (std::size_t i = 0; i < ALL_POSS_DIRECTIONS; i++) { + os << (i == 0 ? " -> " : " + ") << prob_piece_dir.at(i) << "/" << PROB_PIECES << " : " << positionTransition.at(i) << moveUpdate(agentIndex) << (i == ALL_POSS_DIRECTIONS - 1 ? ";\n" : "\n"); + } + + return os; + } + + std::ostream& PrismModulesPrinter::printEndmodule(std::ostream &os) { + os << "endmodule\n"; + os << "\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printPlayerStruct(std::ostream &os, const AgentName &agentName, const bool agentWithView, const std::vector<float> &probabilities, const std::set<std::string> &slipperyActions) { + os << "player " << agentName << "\n\t"; + bool first = true; + std::list<std::string> allActions = { "_move_north", "_move_east", "_move_south", "_move_west" }; + std::list<std::string> movementActions = allActions; + for(auto const& probability : probabilities) { + std::string percentageString = std::to_string((int)(100 * probability)); + for(auto const& movement : movementActions) { + allActions.push_back(movement + "_" + percentageString); + } + } + if(agentWithView) { + allActions.push_back("_turn_left"); + allActions.push_back("_turn_right"); + } else { + allActions.push_back("_turns"); + } + + for(auto const& action : allActions) { + if(first) first = false; else os << ", "; + os << "[" << agentName << action << "]"; + } + for(auto const& action : slipperyActions) { + os << ", " << action; + } + os << "\nendplayer\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printGlobalMoveVariable(std::ostream &os, const size_t &numberOfPlayer) { + os << "\nglobal move : [0.." << std::to_string(numberOfPlayer - 1) << "] init 0;\n\n"; + return os; + } + + std::ostream& PrismModulesPrinter::printRewards(std::ostream &os, const AgentName &agentName, const std::map<coordinates, float> &stateRewards, const cells &lava, const cells &goals, const std::map<Color, cells> &backgroundTiles) { + if(lava.size() != 0) { + os << "rewards \"SafetyNoBFS\"\n"; + os << "\tAgentIsInLavaAndNotDone: -100;\n"; + os << "endrewards\n"; + } + + os << "rewards \"SafetyNoBFSAndGoal\"\n"; + if(goals.size() != 0) os << "\tAgentIsInGoalAndNotDone: 100;\n"; + if(lava.size() != 0) os << "\tAgentIsInLavaAndNotDone: -100;\n"; + os << "endrewards\n"; + + os << "rewards \"Time\"\n"; + os << "\t!AgentIsInGoal : -1;\n"; + if(goals.size() != 0) os << "\tAgentIsInGoalAndNotDone: 100;\n"; + if(lava.size() != 0) os << "\tAgentIsInLavaAndNotDone: -100;\n"; + os << "endrewards\n"; + + if(stateRewards.size() > 0) { + os << "rewards \"SafetyWithBFS\"\n"; + if(lava.size() != 0) os << "\tAgentIsInLavaAndNotDone: -100;\n"; + for(auto const [coordinates, reward] : stateRewards) { + os << "\txAgent=" << coordinates.first << "&yAgent=" << coordinates.second << " : " << reward << ";\n"; + } + os << "endrewards\n"; + } + if(stateRewards.size() > 0) { + os << "rewards \"SafetyWithBFSAndGoal\"\n"; + if(goals.size() != 0) os << "\tAgentIsInGoalAndNotDone: 100;\n"; + if(lava.size() != 0) os << "\tAgentIsInLavaAndNotDone: -100;\n"; + for(auto const [coordinates, reward] : stateRewards) { + os << "\txAgent=" << coordinates.first << "&yAgent=" << coordinates.second << " : " << reward << ";\n"; + } + os << "endrewards\n"; + } + + for(auto const entry : backgroundTiles) + { + std::cout << getColor(entry.first) << " "; + for(auto const cell : entry.second){ + std::cout << cell.getCoordinates().first << " " << cell.getCoordinates().second << std::endl; + } + } + if(backgroundTiles.size() > 0) { + os << "rewards \"TaxiReward\"\n"; + os << "\t!AgentIsInGoal : -1;\n"; + std::string allPassengersPickedUp = ""; + bool first = true; + for(auto const [color, cells] : backgroundTiles) { + if(cells.size() == 0) continue; + if(first) first = false; else allPassengersPickedUp += "&"; + std::string c = getColor(color); + c.at(0) = std::toupper(c.at(0)); + std::string visitedLabel = agentName + "_picked_up_" + c; + allPassengersPickedUp += visitedLabel; + os << "[" << agentName << "_pickup_" << c << "] true : 100;\n"; + } + if(goals.size() != 0) os << "\tAgentIsInGoalAndNotDone & " << allPassengersPickedUp << " : 100;\n"; + if(goals.size() != 0) os << "\tAgentIsInGoalAndNotDone & !(" << allPassengersPickedUp << ") : -100;\n"; + os << "endrewards"; + } + return os; + } + + std::string PrismModulesPrinter::moveGuard(const size_t &agentIndex) { + return isGame() ? " move=" + std::to_string(agentIndex) + " & " : " "; + } + + std::string PrismModulesPrinter::moveUpdate(const size_t &agentIndex) { + return isGame() ? + (agentIndex == numberOfPlayer - 1) ? + " & (move'=0) " : + " & (move'=" + std::to_string(agentIndex + 1) + ") " : + ""; + } + + std::string PrismModulesPrinter::viewVariable(const AgentName &agentName, const size_t &agentDirection, const bool agentWithView) { + return agentWithView ? " view" + agentName + "=" + std::to_string(agentDirection) + " & " : " "; + } + + bool PrismModulesPrinter::isGame() const { + return modelType == ModelType::SMG; + } +} diff --git a/util/PrismModulesPrinter.h b/util/PrismModulesPrinter.h new file mode 100644 index 0000000..7a196fc --- /dev/null +++ b/util/PrismModulesPrinter.h @@ -0,0 +1,91 @@ +#pragma once + +#include <iostream> +#include <functional> +#include "MinigridGrammar.h" +#include "PrismPrinter.h" + +namespace prism { + class PrismModulesPrinter { + public: + PrismModulesPrinter(const ModelType &modelType, const size_t &numberOfPlayer, const bool enforceOneWays = false); + + std::ostream& printRestrictionFormula(std::ostream& os, const AgentName &agentName, const std::string &direction, const cells &cells); + std::ostream& printIsOnSlipperyFormula(std::ostream& os, const AgentName &agentName, const std::vector<std::reference_wrapper<cells>> &slipperyCollection, const cells &slipperyNorth, const cells &slipperyEast, const cells &slipperySouth, const cells &slipperyWest); + std::ostream& printGoalLabel(std::ostream& os, const AgentName&agentName, const cells &goals); + std::ostream& printCrashLabel(std::ostream &os, const std::vector<AgentName> agentNames); + std::ostream& printAvoidanceLabel(std::ostream &os, const std::vector<AgentName> agentNames, const int &distance); + std::ostream& printKeysLabels(std::ostream& os, const AgentName&agentName, const cells &keys); + std::ostream& printBackgroundLabels(std::ostream &os, const AgentName &agentName, const std::pair<Color, cells> &backgroundTiles); + std::ostream& printIsInLavaFormula(std::ostream& os, const AgentName &agentName, const cells &lava); + std::ostream& printIsFixedFormulas(std::ostream& os, const AgentName &agentName); + std::ostream& printTurningNotAllowedFormulas(std::ostream& os, const AgentName &agentName, const cells &floor); + std::ostream& printWallFormula(std::ostream& os, const AgentName &agentName, const cells &walls); + std::ostream& printFormulas(std::ostream& os, + const AgentName&agentName, + const cells &restrictionNorth, + const cells &restrictionEast, + const cells &restrictionSouth, + const cells &restrictionWest, + const std::vector<std::reference_wrapper<cells>> &slipperyCollection, + const cells &lava, + const cells &walls, + const cells &noTurnFloor, + const cells &slipperyNorth, + const cells &slipperyEast, + const cells &slipperySouth, + const cells &slipperyWest); + + /* + * Representation for Slippery Tile. + * -) North: Slips from North to South + * -) East: Slips from East to West + * -) South: Slips from South to North + * -) West: Slips from West to East + */ + enum class SlipperyType { North, East, South, West }; + + /* + * Prints Slippery on move action. + * + * @param neighborhood: Information of wall-blocks in 8-neighborhood { n, nw, e, se, s, sw, w, nw }. If entry is false, then corresponding neighboorhood position is a wall. + * @param orientation: Information of slippery type (either north, south, east, west). + */ + std::ostream& printSlipperyMove(std::ostream &os, const AgentName &agentName, const size_t &agentIndex, const coordinates &c, std::set<std::string> &slipperyActions, const std::array<bool, 8>& neighborhood, SlipperyType orientation); + + /* + * Prints Slippery on turn action. + * + * @param neighborhood: Information of wall-blocks in 8-neighborhood { n, nw, e, se, s, sw, w, nw }. If entry is false, then corresponding neighboorhood position is a wall. + * @param orientation: Information of slippery type (either north, south, east, west). + */ + std::ostream& printSlipperyTurn(std::ostream &os, const AgentName &agentName, const size_t &agentIndex, const coordinates &c, std::set<std::string> &slipperyActions, const std::array<bool, 8>& neighborhood, SlipperyType orientation); + + std::ostream& printModel(std::ostream &os, const ModelType &modelType); + std::ostream& printBooleansForKeys(std::ostream &os, const AgentName &agentName, const cells &keys); + std::ostream& printActionsForKeys(std::ostream &os, const AgentName &agentName, const cells &keys); + std::ostream& printBooleansForBackground(std::ostream &os, const AgentName &agentName, const std::map<Color, cells> &backgroundTiles); + std::ostream& printActionsForBackground(std::ostream &os, const AgentName &agentName, const std::map<Color, cells> &backgroundTiles); + std::ostream& printInitStruct(std::ostream &os, const AgentName &agentName); + std::ostream& printModule(std::ostream &os, const AgentName &agentName, const size_t &agentIndex, const coordinates &boundaries, const coordinates& initialPosition, const cells &keys, const std::map<Color, cells> &backgroundTiles, const bool agentWithView, const std::vector<float> &probabilities = {}); + std::ostream& printMovementActions(std::ostream &os, const AgentName &agentName, const size_t &agentIndex, const bool agentWithView, const float &probability = 1.0); + std::ostream& printDoneActions(std::ostream &os, const AgentName &agentName, const size_t &agentIndex); + std::ostream& printEndmodule(std::ostream &os); + std::ostream& printPlayerStruct(std::ostream &os, const AgentName &agentName, const bool agentWithView, const std::vector<float> &probabilities = {}, const std::set<std::string> &slipperyActions = {}); + std::ostream& printGlobalMoveVariable(std::ostream &os, const size_t &numberOfPlayer); + + std::ostream& printRewards(std::ostream &os, const AgentName &agentName, const std::map<coordinates, float> &stateRewards, const cells &lava, const cells &goals, const std::map<Color, cells> &backgroundTiles); + + std::string moveGuard(const size_t &agentIndex); + std::string moveUpdate(const size_t &agentIndex); + + std::string viewVariable(const AgentName &agentName, const size_t &agentDirection, const bool agentWithView); + + bool isGame() const; + private: + + ModelType const& modelType; + const size_t numberOfPlayer; + bool enforceOneWays; + }; +} diff --git a/util/PrismPrinter.h b/util/PrismPrinter.h new file mode 100644 index 0000000..ed12126 --- /dev/null +++ b/util/PrismPrinter.h @@ -0,0 +1,15 @@ +#pragma once + +#include <string> + +#include "cell.h" + +typedef std::string AgentName; +typedef std::pair<std::string, coordinates> AgentNameAndPosition; +typedef std::map<AgentNameAndPosition::first_type, AgentNameAndPosition::second_type> AgentNameAndPositionMap; + +namespace prism { + enum class ModelType { + MDP, SMG + }; +} diff --git a/util/cell.cpp b/util/cell.cpp new file mode 100644 index 0000000..07d25f7 --- /dev/null +++ b/util/cell.cpp @@ -0,0 +1,83 @@ +#include "cell.h" + +#include <stdexcept> + +std::ostream &operator<<(std::ostream &os, const cell &c) { + os << static_cast<char>(c.type) << static_cast<char>(c.color); + os << " at (" << c.row << "," << c.column << ")"; + return os; +} + +cell cell::getNorth(const std::vector<cell> &grid) const { + auto north = std::find_if(grid.begin(), grid.end(), [this](const cell &c) { + return this->row - 1 == c.row && this->column == c.column; + }); + if(north == grid.end()) { + throw std::logic_error{ "Cannot get cell north of (" + std::to_string(row) + "," + std::to_string(column) + ")"}; + std::exit(EXIT_FAILURE); + } + return *north; +} + +cell cell::getEast(const std::vector<cell> &grid) const { + auto east = std::find_if(grid.begin(), grid.end(), [this](const cell &c) { + return this->row == c.row && this->column + 1 == c.column; + }); + if(east == grid.end()) { + throw std::logic_error{ "Cannot get cell east of (" + std::to_string(row) + "," + std::to_string(column) + ")"}; + std::exit(EXIT_FAILURE); + } + return *east; +} + +cell cell::getSouth(const std::vector<cell> &grid) const { + auto south = std::find_if(grid.begin(), grid.end(), [this](const cell &c) { + return this->row + 1 == c.row && this->column == c.column; + }); + if(south == grid.end()) { + throw std::logic_error{ "Cannot get cell south of (" + std::to_string(row) + "," + std::to_string(column) + ")"}; + std::exit(EXIT_FAILURE); + } + return *south; +} + +cell cell::getWest(const std::vector<cell> &grid) const { + auto west = std::find_if(grid.begin(), grid.end(), [this](const cell &c) { + return this->row == c.row && this->column - 1 == c.column; + }); + if(west == grid.end()) { + throw std::logic_error{ "Cannot get cell west of (" + std::to_string(row) + "," + std::to_string(column) + ")"}; + std::exit(EXIT_FAILURE); + } + return *west; +} + +coordinates cell::getCoordinates() const { + return std::make_pair(row, column); +} + +std::string cell::getColor() const { + switch(color) { + case Color::Red: return "red"; + case Color::Green: return "green"; + case Color::Blue: return "blue"; + case Color::Purple: return "purple"; + case Color::Yellow: return "yellow"; + case Color::None: return "transparent"; + default: return ""; + //case Color::Grey = 'G', + } +} + +std::string getColor(Color color) { + switch(color) { + case Color::Red: return "red"; + case Color::Green: return "green"; + case Color::Blue: return "blue"; + case Color::Purple: return "purple"; + case Color::Yellow: return "yellow"; + case Color::None: return "transparent"; + default: return ""; + //case Color::Grey = 'G', + } +} diff --git a/util/cell.h b/util/cell.h new file mode 100644 index 0000000..c369052 --- /dev/null +++ b/util/cell.h @@ -0,0 +1,62 @@ +#pragma once + +#include <iostream> +#include <utility> +#include <vector> + +typedef std::pair<int, int> coordinates; + +class Grid; + +enum class Type : char { + Wall = 'W', + Floor = ' ', + Door = 'D', + LockedDoor = 'L', + Key = 'K', + Ball = 'A', + Box = 'B', + Goal = 'G', + Lava = 'V', + Agent = 'X', + Adversary = 'Z', + SlipperyNorth = 'n', + SlipperySouth = 's', + SlipperyEast = 'e', + SlipperyWest = 'w' +}; +enum class Color : char { + Red = 'R', + Green = 'G', + Blue = 'B', + Purple = 'P', + Yellow = 'Y', + //Grey = 'G', + None = ' ' +}; + +constexpr std::initializer_list<Color> allColors = {Color::Red, Color::Green, Color::Blue, Color::Purple, Color::Yellow}; +std::string getColor(Color color); + +class cell { + public: + coordinates getNorth() const { return std::make_pair(row - 1, column); } + coordinates getSouth() const { return std::make_pair(row + 1, column); } + coordinates getEast() const { return std::make_pair(row, column + 1); } + coordinates getWest() const { return std::make_pair(row, column - 1); } + + cell getNorth(const std::vector<cell> &grid) const; + cell getEast(const std::vector<cell> &grid) const; + cell getSouth(const std::vector<cell> &grid) const; + cell getWest(const std::vector<cell> &grid) const; + + friend std::ostream& operator<<(std::ostream& os, const cell& cell); + + coordinates getCoordinates() const; + std::string getColor() const; + + int row; + int column; + Type type; + Color color; +}; diff --git a/util/popl.hpp b/util/popl.hpp new file mode 100644 index 0000000..8fd668b --- /dev/null +++ b/util/popl.hpp @@ -0,0 +1,1331 @@ +/*** + ____ __ ____ __ + ( _ \ / \( _ \( ) + ) __/( O )) __// (_/\ + (__) \__/(__) \____/ + version 1.3.0 + https://github.com/badaix/popl + + This file is part of popl (program options parser lib) + Copyright (C) 2015-2021 Johannes Pohl + + This software may be modified and distributed under the terms + of the MIT license. See the LICENSE file for details. +***/ + +/// checked with clang-tidy: +/// run-clang-tidy-3.8.py -header-filter='.*' +/// -checks='*,-misc-definitions-in-headers,-google-readability-braces-around-statements,-readability-braces-around-statements,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-build-using-namespace,-google-build-using-namespace' + +#ifndef POPL_HPP +#define POPL_HPP + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include <algorithm> +#include <cstdio> +#include <cstring> +#include <fstream> +#include <iostream> +#include <memory> +#include <sstream> +#include <stdexcept> +#include <vector> +#ifdef WINDOWS +#include <cctype> +#endif + + +namespace popl +{ + +#define POPL_VERSION "1.3.0" + + +/// Option's argument type +/** + * Switch has "no" argument + * Value has "required" argument + * Implicit has "optional" argument + */ +enum class Argument +{ + no = 0, // option never takes an argument + required, // option always requires an argument + optional // option may take an argument +}; + + +/// Option's attribute +/** + * inactive: Option is not set and will not be parsed + * hidden: Option is active, but will not show up in the help message + * required: Option must be set on the command line. Otherwise an exception will be thrown + * optional: Option must not be set. Default attribute. + * advanced: Option is advanced and will only show up in the advanced help message + * expoert: Option is expert and will only show up in the expert help message + */ +enum class Attribute +{ + inactive = 0, + hidden = 1, + required = 2, + optional = 3, + advanced = 4, + expert = 5 +}; + + +/// Option name type. Used in invalid_option exception. +/** + * unspecified: not specified + * short_name: The option's short name + * long_name: The option's long name + */ +enum class OptionName +{ + unspecified, + short_name, + long_name +}; + + +/// Abstract Base class for Options +/** + * Base class for Options + * holds just configuration data, no runtime data. + * Option is not bound to a special type "T" + */ +class Option +{ + friend class OptionParser; + +public: + /// Construct an Option + /// @param short_name the options's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + Option(const std::string& short_name, const std::string& long_name, std::string description); + + /// Destructor + virtual ~Option() = default; + + /// default copy constructor + Option(const Option&) = default; + + /// default move constructor + Option(Option&&) = default; + + /// default assignement operator + Option& operator=(const Option&) = default; + + /// default move assignement operator + Option& operator=(Option&&) = default; + + /// Get the Option's short name + /// @return character of the options's short name or 0 if no short name is defined + char short_name() const; + + /// Get the Option's long name + /// @return the long name of the Option. Empty string if no long name is defined + std::string long_name() const; + + /// Get the Option's long or short name + /// @param what_name the option's name to return + /// @param what_hyphen preced the returned name with (double-)hypen + /// @return the requested name of the Option. Empty string if not defined. + std::string name(OptionName what_name, bool with_hypen = false) const; + + /// Get the Option's description + /// @return the description + std::string description() const; + + /// Get the Option's default value + /// @param out stream to write the default value to + /// @return true if a default value is available, false if not + virtual bool get_default(std::ostream& out) const = 0; + + /// Set the Option's attribute + /// @param attribute + void set_attribute(const Attribute& attribute); + + /// Get the Option's attribute + /// @return the Options's attribute + Attribute attribute() const; + + /// Get the Option's argument type + /// @return argument type (no, required, optional) + virtual Argument argument_type() const = 0; + + /// Check how often the Option is set on command line + /// @return the Option's count on command line + virtual size_t count() const = 0; + + /// Check if the Option is set + /// @return true if set at least once + virtual bool is_set() const = 0; + + +protected: + /// Parse the command line option and fill the internal data structure + /// @param what_name short or long option name + /// @param value the value as given on command line + virtual void parse(OptionName what_name, const char* value) = 0; + + /// Clear the internal data structure + virtual void clear() = 0; + + std::string short_name_; + std::string long_name_; + std::string description_; + Attribute attribute_; +}; + + + +/// Value option with optional default value +/** + * Value option with optional default value + * If set, it requires an argument + */ +template <class T> +class Value : public Option +{ +public: + /// Construct an Value Option + /// @param short_name the option's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + Value(const std::string& short_name, const std::string& long_name, const std::string& description); + + /// Construct an Value Option + /// @param short_name the option's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + /// @param default_val the Option's default value + /// @param assign_to pointer to a variable to assign the parsed command line value to + Value(const std::string& short_name, const std::string& long_name, const std::string& description, const T& default_val, T* assign_to = nullptr); + + size_t count() const override; + bool is_set() const override; + + /// Assign the last parsed command line value to "var" + /// @param var pointer to the variable where is value is written to + void assign_to(T* var); + + /// Manually set the Option's value. Deletes current value(s) + /// @param value the new value of the option + void set_value(const T& value); + + /// Get the Option's value. Will throw if option at index idx is not available + /// @param idx the zero based index of the value (if set multiple times) + /// @return the Option's value at index "idx" + T value(size_t idx = 0) const; + + /// Get the Option's value, return default_value if not set. + /// @param default_value return value if value is not set + /// @param idx the zero based index of the value (if set multiple times) + /// @return the Option's value at index "idx" or the default value or default_value + T value_or(const T& default_value, size_t idx = 0) const; + + /// Set the Option's default value + /// @param value the default value if not specified on command line + void set_default(const T& value); + + /// Check if the Option has a default value + /// @return true if the Option has a default value + bool has_default() const; + + /// Get the Option's default value. Will throw if no default is set. + /// @return the Option's default value + T get_default() const; + bool get_default(std::ostream& out) const override; + + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; + std::unique_ptr<T> default_; + + virtual void update_reference(); + virtual void add_value(const T& value); + void clear() override; + + T* assign_to_; + std::vector<T> values_; +}; + + + +/// Value option with implicit default value +/** + * Value option with implicit default value + * If set, an argument is optional + * -without argument it carries the implicit default value + * -with argument it carries the explicit value + */ +template <class T> +class Implicit : public Value<T> +{ +public: + Implicit(const std::string& short_name, const std::string& long_name, const std::string& description, const T& implicit_val, T* assign_to = nullptr); + + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; +}; + + + +/// Value option without value +/** + * Value option without value + * Does not require an argument + * Can be either set or not set + */ +class Switch : public Value<bool> +{ +public: + Switch(const std::string& short_name, const std::string& long_name, const std::string& description, bool* assign_to = nullptr); + + void set_default(const bool& value) = delete; + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; +}; + + + +using Option_ptr = std::shared_ptr<Option>; + +/// OptionParser manages all Options +/** + * OptionParser manages all Options + * Add Options (Option_Type = Value<T>, Implicit<T> or Switch) with "add<Option_Type>(option params)" + * Call "parse(argc, argv)" to trigger parsing of the options and to + * fill "non_option_args" and "unknown_options" + */ +class OptionParser +{ +public: + /// Construct the OptionParser + /// @param description used for the help message + explicit OptionParser(std::string description = ""); + + /// Destructor + virtual ~OptionParser() = default; + + /// Add an Option e.g. 'add<Value<int>>("i", "int", "description for the -i option")' + /// @param T the option type (Value, Switch, Implicit) + /// @param attribute the Option's attribute (inactive, hidden, required, optional, ...) + /// @param Ts the Option's parameter + template <typename T, Attribute attribute, typename... Ts> + std::shared_ptr<T> add(Ts&&... params); + + /// Add an Option e.g. 'add<Value<int>>("i", "int", "description for the -i option")' + /// @param T the option type (Value, Switch, Implicit) + /// @param Ts the Option's parameter + template <typename T, typename... Ts> + std::shared_ptr<T> add(Ts&&... params); + + /// Parse an ini file into the added Options + /// @param ini_filename full path of the ini file + void parse(const std::string& ini_filename); + + /// Parse the command line into the added Options + /// @param argc command line argument count + /// @param argv command line arguments + void parse(int argc, const char* const argv[]); + + /// Delete all parsed options + void reset(); + + /// Produce a help message + /// @param max_attribute show options up to this level (optional, advanced, expert) + /// @return the help message + std::string help(const Attribute& max_attribute = Attribute::optional) const; + + /// Get the OptionParser's description + /// @return the description as given during construction + std::string description() const; + + /// Get all options that where added with "add" + /// @return a vector of the contained Options + const std::vector<Option_ptr>& options() const; + + /// Get command line arguments without option + /// e.g. "-i 5 hello" => hello + /// e.g. "-i 5 -- from here non option args" => "from", "here", "non", "option", "args" + /// @return vector to "stand-alone" command line arguments + const std::vector<std::string>& non_option_args() const; + + /// Get unknown command options + /// e.g. '--some_unknown_option="hello"' + /// @return vector to "stand-alone" command line arguments + const std::vector<std::string>& unknown_options() const; + + /// Get an Option by it's long name + /// @param the Option's long name + /// @return a pointer of type "Value, Switch, Implicit" to the Option or nullptr + template <typename T> + std::shared_ptr<T> get_option(const std::string& long_name) const; + + /// Get an Option by it's short name + /// @param the Option's short name + /// @return a pointer of type "Value, Switch, Implicit" to the Option or nullptr + template <typename T> + std::shared_ptr<T> get_option(char short_name) const; + +protected: + std::vector<Option_ptr> options_; + std::string description_; + std::vector<std::string> non_option_args_; + std::vector<std::string> unknown_options_; + + Option_ptr find_option(const std::string& long_name) const; + Option_ptr find_option(char short_name) const; +}; + + + +class invalid_option : public std::invalid_argument +{ +public: + enum class Error + { + missing_argument, + invalid_argument, + too_many_arguments, + missing_option + }; + + invalid_option(const Option* option, invalid_option::Error error, OptionName what_name, std::string value, const std::string& text) + : std::invalid_argument(text.c_str()), option_(option), error_(error), what_name_(what_name), value_(std::move(value)) + { + } + + invalid_option(const Option* option, invalid_option::Error error, const std::string& text) + : invalid_option(option, error, OptionName::unspecified, "", text) + { + } + + const Option* option() const + { + return option_; + } + + Error error() const + { + return error_; + } + + OptionName what_name() const + { + return what_name_; + } + + std::string value() const + { + return value_; + } + +private: + const Option* option_; + Error error_; + OptionName what_name_; + std::string value_; +}; + + + +/// Base class for an OptionPrinter +/** + * OptionPrinter creates a help message for a given OptionParser + */ +class OptionPrinter +{ +public: + /// Constructor + /// @param option_parser the OptionParser to create the help message from + explicit OptionPrinter(const OptionParser* option_parser) : option_parser_(option_parser) + { + } + + /// Destructor + virtual ~OptionPrinter() = default; + + /// Create a help message + /// @param max_attribute show options up to this level (optional, advanced, expert) + /// @return the help message + virtual std::string print(const Attribute& max_attribute = Attribute::optional) const = 0; + +protected: + const OptionParser* option_parser_; +}; + + + +/// Option printer for the console +/** + * Standard console option printer + * Creates a human readable help message + */ +class ConsoleOptionPrinter : public OptionPrinter +{ +public: + explicit ConsoleOptionPrinter(const OptionParser* option_parser); + ~ConsoleOptionPrinter() override = default; + + std::string print(const Attribute& max_attribute = Attribute::optional) const override; + +private: + std::string to_string(Option_ptr option) const; +}; + + + +/// Option printer for man pages +/** + * Creates help messages in groff format that can be used in man pages + */ +class GroffOptionPrinter : public OptionPrinter +{ +public: + explicit GroffOptionPrinter(const OptionParser* option_parser); + ~GroffOptionPrinter() override = default; + + std::string print(const Attribute& max_attribute = Attribute::optional) const override; + +private: + std::string to_string(Option_ptr option) const; +}; + + + +/// Option printer for bash completion +/** + * Creates a script with all options (short and long) that can be used for bash completion + */ +class BashCompletionOptionPrinter : public OptionPrinter +{ +public: + BashCompletionOptionPrinter(const OptionParser* option_parser, std::string program_name); + ~BashCompletionOptionPrinter() override = default; + + std::string print(const Attribute& max_attribute = Attribute::optional) const override; + +private: + std::string program_name_; +}; + + + +/// Option implementation ///////////////////////////////// + +inline Option::Option(const std::string& short_name, const std::string& long_name, std::string description) + : short_name_(short_name), long_name_(long_name), description_(std::move(description)), attribute_(Attribute::optional) +{ + if (short_name.size() > 1) + throw std::invalid_argument("length of short name must be <= 1: '" + short_name + "'"); + + if (short_name.empty() && long_name.empty()) + throw std::invalid_argument("short and long name are empty"); +} + + +inline char Option::short_name() const +{ + if (!short_name_.empty()) + return short_name_[0]; + return 0; +} + + +inline std::string Option::long_name() const +{ + return long_name_; +} + + +inline std::string Option::name(OptionName what_name, bool with_hypen) const +{ + if (what_name == OptionName::short_name) + return short_name_.empty() ? "" : ((with_hypen ? "-" : "") + short_name_); + if (what_name == OptionName::long_name) + return long_name_.empty() ? "" : ((with_hypen ? "--" : "") + long_name_); + return ""; +} + + +inline std::string Option::description() const +{ + return description_; +} + + +inline void Option::set_attribute(const Attribute& attribute) +{ + attribute_ = attribute; +} + + +inline Attribute Option::attribute() const +{ + return attribute_; +} + + + +/// Value implementation ///////////////////////////////// + +template <class T> +inline Value<T>::Value(const std::string& short_name, const std::string& long_name, const std::string& description) + : Option(short_name, long_name, description), assign_to_(nullptr) +{ +} + + +template <class T> +inline Value<T>::Value(const std::string& short_name, const std::string& long_name, const std::string& description, const T& default_val, T* assign_to) + : Value<T>(short_name, long_name, description) +{ + assign_to_ = assign_to; + set_default(default_val); +} + + +template <class T> +inline size_t Value<T>::count() const +{ + return values_.size(); +} + + +template <class T> +inline bool Value<T>::is_set() const +{ + return !values_.empty(); +} + + +template <class T> +inline void Value<T>::assign_to(T* var) +{ + assign_to_ = var; + update_reference(); +} + + +template <class T> +inline void Value<T>::set_value(const T& value) +{ + clear(); + add_value(value); +} + +template <class T> +inline T Value<T>::value_or(const T& default_value, size_t idx) const +{ + if (idx < values_.size()) + return values_[idx]; + else if (default_) + return *default_; + else + return default_value; +} + +template <class T> +inline T Value<T>::value(size_t idx) const +{ + if (!this->is_set() && default_) + return *default_; + + if (!is_set() || (idx >= count())) + { + std::stringstream optionStr; + if (!is_set()) + optionStr << "option not set: \""; + else + optionStr << "index out of range (" << idx << ") for \""; + + if (short_name() != 0) + optionStr << "-" << short_name(); + else + optionStr << "--" << long_name(); + + optionStr << "\""; + throw std::out_of_range(optionStr.str()); + } + + return values_[idx]; +} + + + +template <class T> +inline void Value<T>::set_default(const T& value) +{ + this->default_.reset(new T); + *this->default_ = value; + update_reference(); +} + + +template <class T> +inline bool Value<T>::has_default() const +{ + return (this->default_ != nullptr); +} + + +template <class T> +inline T Value<T>::get_default() const +{ + if (!has_default()) + throw std::runtime_error("no default value set"); + return *this->default_; +} + + +template <class T> +inline bool Value<T>::get_default(std::ostream& out) const +{ + if (!has_default()) + return false; + out << *this->default_; + return true; +} + + +template <class T> +inline Argument Value<T>::argument_type() const +{ + return Argument::required; +} + + +template <> +inline void Value<std::string>::parse(OptionName what_name, const char* value) +{ + if (strlen(value) == 0) + throw invalid_option(this, invalid_option::Error::missing_argument, what_name, value, "missing argument for " + name(what_name, true)); + + add_value(value); +} + + +template <> +inline void Value<bool>::parse(OptionName /*what_name*/, const char* value) +{ + bool val = + ((value != nullptr) && ((strcmp(value, "1") == 0) || (strcmp(value, "true") == 0) || (strcmp(value, "True") == 0) || (strcmp(value, "TRUE") == 0))); + add_value(val); +} + + +template <class T> +inline void Value<T>::parse(OptionName what_name, const char* value) +{ + T parsed_value; + std::string strValue; + if (value != nullptr) + strValue = value; + + std::istringstream is(strValue); + int valuesRead = 0; + while (is.good()) + { + if (is.peek() != EOF) + is >> parsed_value; + else + break; + + valuesRead++; + } + + if (is.fail()) + throw invalid_option(this, invalid_option::Error::invalid_argument, what_name, value, + "invalid argument for " + name(what_name, true) + ": '" + strValue + "'"); + + if (valuesRead > 1) + throw invalid_option(this, invalid_option::Error::too_many_arguments, what_name, value, + "too many arguments for " + name(what_name, true) + ": '" + strValue + "'"); + + if (strValue.empty()) + throw invalid_option(this, invalid_option::Error::missing_argument, what_name, "", "missing argument for " + name(what_name, true)); + + this->add_value(parsed_value); +} + + +template <class T> +inline void Value<T>::update_reference() +{ + if (this->assign_to_) + { + if (!this->is_set() && default_) + *this->assign_to_ = *default_; + else if (this->is_set()) + *this->assign_to_ = values_.back(); + } +} + + +template <class T> +inline void Value<T>::add_value(const T& value) +{ + values_.push_back(value); + update_reference(); +} + + +template <class T> +inline void Value<T>::clear() +{ + values_.clear(); + update_reference(); +} + + + +/// Implicit implementation ///////////////////////////////// + +template <class T> +inline Implicit<T>::Implicit(const std::string& short_name, const std::string& long_name, const std::string& description, const T& implicit_val, T* assign_to) + : Value<T>(short_name, long_name, description, implicit_val, assign_to) +{ +} + + +template <class T> +inline Argument Implicit<T>::argument_type() const +{ + return Argument::optional; +} + + +template <class T> +inline void Implicit<T>::parse(OptionName what_name, const char* value) +{ + if ((value != nullptr) && (strlen(value) > 0)) + Value<T>::parse(what_name, value); + else + this->add_value(*this->default_); +} + + + +/// Switch implementation ///////////////////////////////// + +inline Switch::Switch(const std::string& short_name, const std::string& long_name, const std::string& description, bool* assign_to) + : Value<bool>(short_name, long_name, description, false, assign_to) +{ +} + + +inline void Switch::parse(OptionName /*what_name*/, const char* /*value*/) +{ + add_value(true); +} + + +inline Argument Switch::argument_type() const +{ + return Argument::no; +} + + + +/// OptionParser implementation ///////////////////////////////// + +inline OptionParser::OptionParser(std::string description) : description_(std::move(description)) +{ +} + + +template <typename T, typename... Ts> +inline std::shared_ptr<T> OptionParser::add(Ts&&... params) +{ + return add<T, Attribute::optional>(std::forward<Ts>(params)...); +} + + +template <typename T, Attribute attribute, typename... Ts> +inline std::shared_ptr<T> OptionParser::add(Ts&&... params) +{ + static_assert(std::is_base_of<Option, typename std::decay<T>::type>::value, "type T must be Switch, Value or Implicit"); + std::shared_ptr<T> option = std::make_shared<T>(std::forward<Ts>(params)...); + + for (const auto& o : options_) + { + if ((option->short_name() != 0) && (option->short_name() == o->short_name())) + throw std::invalid_argument("duplicate short option name '-" + std::string(1, option->short_name()) + "'"); + if (!option->long_name().empty() && (option->long_name() == (o->long_name()))) + throw std::invalid_argument("duplicate long option name '--" + option->long_name() + "'"); + } + option->set_attribute(attribute); + options_.push_back(option); + return option; +} + + +inline std::string OptionParser::description() const +{ + return description_; +} + + +inline const std::vector<Option_ptr>& OptionParser::options() const +{ + return options_; +} + + +inline const std::vector<std::string>& OptionParser::non_option_args() const +{ + return non_option_args_; +} + + +inline const std::vector<std::string>& OptionParser::unknown_options() const +{ + return unknown_options_; +} + + +inline Option_ptr OptionParser::find_option(const std::string& long_name) const +{ + for (const auto& option : options_) + if (option->long_name() == long_name) + return option; + return nullptr; +} + + +inline Option_ptr OptionParser::find_option(char short_name) const +{ + for (const auto& option : options_) + if (option->short_name() == short_name) + return option; + return nullptr; +} + + +template <typename T> +inline std::shared_ptr<T> OptionParser::get_option(const std::string& long_name) const +{ + Option_ptr option = find_option(long_name); + if (!option) + throw std::invalid_argument("option not found: " + long_name); + auto result = std::dynamic_pointer_cast<T>(option); + if (!result) + throw std::invalid_argument("cannot cast option to T: " + long_name); + return result; +} + + +template <typename T> +inline std::shared_ptr<T> OptionParser::get_option(char short_name) const +{ + Option_ptr option = find_option(short_name); + if (!option) + throw std::invalid_argument("option not found: " + std::string(1, short_name)); + auto result = std::dynamic_pointer_cast<T>(option); + if (!result) + throw std::invalid_argument("cannot cast option to T: " + std::string(1, short_name)); + return result; +} + +inline void OptionParser::parse(const std::string& ini_filename) +{ + std::ifstream file(ini_filename.c_str()); + std::string line; + + auto trim = [](std::string& s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); + return s; + }; + + auto trim_copy = [trim](const std::string& s) { + std::string copy(s); + return trim(copy); + }; + + auto split = [trim_copy](const std::string& s) -> std::pair<std::string, std::string> { + size_t pos = s.find('='); + if (pos == std::string::npos) + return {"", ""}; + return {trim_copy(s.substr(0, pos)), trim_copy(s.substr(pos + 1, std::string::npos))}; + }; + + std::string section; + while (std::getline(file, line)) + { + trim(line); + if (line.empty()) + continue; + if (line.front() == '#') + continue; + + if ((line.front() == '[') && (line.back() == ']')) + { + section = trim_copy(line.substr(1, line.size() - 2)); + continue; + } + auto key_value = split(line); + if (key_value.first.empty()) + continue; + + std::string key = section.empty() ? key_value.first : section + "." + key_value.first; + Option_ptr option = find_option(key); + if (option && (option->attribute() == Attribute::inactive)) + option = nullptr; + + if (option) + option->parse(OptionName::long_name, key_value.second.c_str()); + else + unknown_options_.push_back(key); + } +} + +inline void OptionParser::parse(int argc, const char* const argv[]) +{ + for (int n = 1; n < argc; ++n) + { + const std::string arg(argv[n]); + if (arg == "--") + { + /// from here on only non opt args + for (int m = n + 1; m < argc; ++m) + non_option_args_.emplace_back(argv[m]); + } + else if (arg.find("--") == 0) + { + /// long option arg + std::string opt = arg.substr(2); + std::string optarg; + size_t equalIdx = opt.find('='); + if (equalIdx != std::string::npos) + { + optarg = opt.substr(equalIdx + 1); + opt.resize(equalIdx); + } + + Option_ptr option = find_option(opt); + if (option && (option->attribute() == Attribute::inactive)) + option = nullptr; + if (option) + { + if (option->argument_type() == Argument::no) + { + if (!optarg.empty()) + option = nullptr; + } + else if (option->argument_type() == Argument::required) + { + if (optarg.empty() && n < argc - 1) + optarg = argv[++n]; + } + } + + if (option) + option->parse(OptionName::long_name, optarg.c_str()); + else + unknown_options_.push_back(arg); + } + else if (arg.find('-') == 0) + { + /// short option arg + std::string opt = arg.substr(1); + bool unknown = false; + for (size_t m = 0; m < opt.size(); ++m) + { + char c = opt[m]; + std::string optarg; + + Option_ptr option = find_option(c); + if (option && (option->attribute() == Attribute::inactive)) + option = nullptr; + if (option) + { + if (option->argument_type() == Argument::required) + { + /// use the rest of the current argument as optarg + optarg = opt.substr(m + 1); + /// or the next arg + if (optarg.empty() && n < argc - 1) + optarg = argv[++n]; + m = opt.size(); + } + else if (option->argument_type() == Argument::optional) + { + /// use the rest of the current argument as optarg + optarg = opt.substr(m + 1); + m = opt.size(); + } + } + + if (option) + option->parse(OptionName::short_name, optarg.c_str()); + else + unknown = true; + } + if (unknown) + unknown_options_.push_back(arg); + } + else + { + non_option_args_.push_back(arg); + } + } + + for (auto& opt : options_) + { + if ((opt->attribute() == Attribute::required) && !opt->is_set()) + { + std::string option = opt->long_name().empty() ? std::string(1, opt->short_name()) : opt->long_name(); + throw invalid_option(opt.get(), invalid_option::Error::missing_option, "option \"" + option + "\" is required"); + } + } +} + + +inline void OptionParser::reset() +{ + unknown_options_.clear(); + non_option_args_.clear(); + for (auto& opt : options_) + opt->clear(); +} + + +inline std::string OptionParser::help(const Attribute& max_attribute) const +{ + ConsoleOptionPrinter option_printer(this); + return option_printer.print(max_attribute); +} + + + +/// ConsoleOptionPrinter implementation ///////////////////////////////// + +inline ConsoleOptionPrinter::ConsoleOptionPrinter(const OptionParser* option_parser) : OptionPrinter(option_parser) +{ +} + + +inline std::string ConsoleOptionPrinter::to_string(Option_ptr option) const +{ + std::stringstream line; + if (option->short_name() != 0) + { + line << " -" << option->short_name(); + if (!option->long_name().empty()) + line << ", "; + } + else + line << " "; + if (!option->long_name().empty()) + line << "--" << option->long_name(); + + if (option->argument_type() == Argument::required) + { + line << " arg"; + std::stringstream defaultStr; + if (option->get_default(defaultStr)) + { + if (!defaultStr.str().empty()) + line << " (=" << defaultStr.str() << ")"; + } + } + else if (option->argument_type() == Argument::optional) + { + std::stringstream defaultStr; + if (option->get_default(defaultStr)) + line << " [=arg(=" << defaultStr.str() << ")]"; + } + + return line.str(); +} + + +inline std::string ConsoleOptionPrinter::print(const Attribute& max_attribute) const +{ + if (option_parser_ == nullptr) + return ""; + + if (max_attribute < Attribute::optional) + throw std::invalid_argument("attribute must be 'optional', 'advanced', or 'default'"); + + std::stringstream s; + if (!option_parser_->description().empty()) + s << option_parser_->description() << ":\n"; + + size_t optionRightMargin(20); + const size_t maxDescriptionLeftMargin(40); + // const size_t descriptionRightMargin(80); + + for (const auto& option : option_parser_->options()) + optionRightMargin = std::max(optionRightMargin, to_string(option).size() + 2); + optionRightMargin = std::min(maxDescriptionLeftMargin - 2, optionRightMargin); + + for (const auto& option : option_parser_->options()) + { + if ((option->attribute() <= Attribute::hidden) || (option->attribute() > max_attribute)) + continue; + std::string optionStr = to_string(option); + if (optionStr.size() < optionRightMargin) + optionStr.resize(optionRightMargin, ' '); + else + optionStr += "\n" + std::string(optionRightMargin, ' '); + s << optionStr; + + std::string line; + std::vector<std::string> lines; + std::stringstream description(option->description()); + while (std::getline(description, line, '\n')) + lines.push_back(line); + + std::string empty(optionRightMargin, ' '); + for (size_t n = 0; n < lines.size(); ++n) + { + if (n > 0) + s << "\n" << empty; + s << lines[n]; + } + s << "\n"; + } + + return s.str(); +} + + + +/// GroffOptionPrinter implementation ///////////////////////////////// + +inline GroffOptionPrinter::GroffOptionPrinter(const OptionParser* option_parser) : OptionPrinter(option_parser) +{ +} + + +inline std::string GroffOptionPrinter::to_string(Option_ptr option) const +{ + std::stringstream line; + if (option->short_name() != 0) + { + line << "-" << option->short_name(); + if (!option->long_name().empty()) + line << ", "; + } + if (!option->long_name().empty()) + line << "--" << option->long_name(); + + if (option->argument_type() == Argument::required) + { + line << " arg"; + std::stringstream defaultStr; + if (option->get_default(defaultStr)) + { + if (!defaultStr.str().empty()) + line << " (=" << defaultStr.str() << ")"; + } + } + else if (option->argument_type() == Argument::optional) + { + std::stringstream defaultStr; + if (option->get_default(defaultStr)) + line << " [=arg(=" << defaultStr.str() << ")]"; + } + + return line.str(); +} + + +inline std::string GroffOptionPrinter::print(const Attribute& max_attribute) const +{ + if (option_parser_ == nullptr) + return ""; + + if (max_attribute < Attribute::optional) + throw std::invalid_argument("attribute must be 'optional', 'advanced', or 'default'"); + + std::stringstream s; + if (!option_parser_->description().empty()) + s << ".SS " << option_parser_->description() << ":\n"; + + for (const auto& option : option_parser_->options()) + { + if ((option->attribute() <= Attribute::hidden) || (option->attribute() > max_attribute)) + continue; + s << ".TP\n\\fB" << to_string(option) << "\\fR\n"; + if (!option->description().empty()) + s << option->description() << "\n"; + } + + return s.str(); +} + + + +/// BashCompletionOptionPrinter implementation ///////////////////////////////// + +inline BashCompletionOptionPrinter::BashCompletionOptionPrinter(const OptionParser* option_parser, std::string program_name) + : OptionPrinter(option_parser), program_name_(std::move(program_name)) +{ +} + + +inline std::string BashCompletionOptionPrinter::print(const Attribute& /*max_attribute*/) const +{ + if (option_parser_ == nullptr) + return ""; + + std::stringstream s; + s << "_" << program_name_ << "()\n"; + s << R"({ + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts=")"; + + for (const auto& option : option_parser_->options()) + { + if (option->attribute() > Attribute::hidden) + { + if (option->short_name() != 0) + s << "-" << option->short_name() << " "; + if (!option->long_name().empty()) + s << "--" << option->long_name() << " "; + } + } + + s << R"(" + if [[ ${cur} == -* ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi +} +complete -F )"; + s << "_" << program_name_ << " " << program_name_ << "\n"; + + return s.str(); +} + + + +static inline std::ostream& operator<<(std::ostream& out, const OptionParser& op) +{ + return out << op.help(); +} + + +} // namespace popl + + +#endif // POPL_HPP