#include "input_file.hpp" #include #include #include #include #include #include "shapes/circle.hpp" #include "shapes/line.hpp" #include "shapes/rectangle.hpp" #include "math/constants.hpp" InputFile::InputFile(const std::filesystem::path& path) : m_file(path), m_cmdsProcessed(0) { if (m_file.bad()) { throw std::runtime_error("Cannot open " + path.string() + " for reading"); } } // istream >> operator for math::Vector static std::istream& operator>>(std::istream& is, math::Vector& vec) { return is >> vec.x >> vec.y; } // command name -> command handler map using CommandMap = std::map>; /** * @brief Get the type name as a string * * @tparam T Type * @return Type name */ template static const char* GetTypeName() { return typeid(T).name(); } template <> const char* GetTypeName() { return "integer"; } template <> const char* GetTypeName() { return "float"; } template <> const char* GetTypeName() { return "vector"; } template <> const char* GetTypeName() { return "string"; } /** * @brief Read a value from an `istream` and check for errors * * @tparam T Type of the value to read * @param is Input stream * @return Read value */ template static T ReadVal(std::istream& is) { T v; is >> v; if (is.bad()) throw std::runtime_error(std::format("Could not parse {} from stream", GetTypeName())); return v; } /** * @brief Register a command in a command map * * @tparam TArgs Argument types * @tparam THandler Handler type * @param cmdMap Command map * @param cmd Command name * @param handler Command handler */ template static void RegisterCommand(CommandMap& cmdMap, const std::string& cmd, THandler&& handler) { cmdMap[cmd] = [handler = std::forward(handler)](std::istream& is) { std::apply(handler, std::tuple{ReadVal(is)...}); // tuple because of order of evaluation... }; } shapes::Group InputFile::Parse() { shapes::Group shapes; CommandMap cmds; /* SHAPES */ RegisterCommand(cmds, "line", [&shapes](int x1, int y1, int x2, int y2) { shapes.AddShape(math::Vector{x1, y1}, math::Vector{x2, y2}); }); RegisterCommand(cmds, "circle", [&shapes](int x, int y, int r) { if (r <= 0) throw std::runtime_error("Circle radius must be a positive integer"); shapes.AddShape(math::Vector{x, y}, static_cast(r)); }); RegisterCommand(cmds, "rect", [&shapes](int x, int y, int w, int h) { if (w <= 0 || h <= 0) throw std::runtime_error("Rect dimensions must be positive integers"); shapes.AddShape(math::Vector{x, y}, math::Vector{w, h}); }); /* TRANSFORMS */ RegisterCommand(cmds, "translate", [&shapes](int ox, int oy) { shapes.Translate(math::Vector{ox, oy}); }); RegisterCommand(cmds, "rotate", [&shapes](int cx, int cy, float deg) { shapes.Rotate(math::Vector{cx, cy}, deg * math::DEG_TO_RAD); }); RegisterCommand(cmds, "scale", [&shapes](int cx, int cy, float factor) { if (factor < std::numeric_limits().epsilon()) throw std::runtime_error("Scale factor must be a non-zero real number"); shapes.Scale(math::Vector{cx, cy}, factor); }); /* ---------------------------- */ std::string line; size_t lineNum = 0; try { while (std::getline(m_file, line)) { ++lineNum; if (line.empty() || line[0] == '#') continue; auto commentPos = line.find('#'); if (commentPos != std::string::npos) line = line.substr(0, commentPos); std::istringstream iss(line); std::string cmdName = ReadVal(iss); auto cmd = cmds.find(cmdName); if (cmd == cmds.end()) throw std::runtime_error("Unknown command: " + cmdName); cmd->second(iss); ++m_cmdsProcessed; } } catch (const std::exception& e) { throw std::runtime_error(std::format("Line {}: {}", lineNum, e.what())); } return shapes; }