182 lines
4.4 KiB
C++
182 lines
4.4 KiB
C++
#include "input_file.hpp"
|
|
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <string>
|
|
#include <format>
|
|
|
|
#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<std::string, std::function<void(std::istream&)>>;
|
|
|
|
/**
|
|
* @brief Get the type name as a string
|
|
*
|
|
* @tparam T Type
|
|
* @return Type name
|
|
*/
|
|
template <class T>
|
|
static const char* GetTypeName()
|
|
{
|
|
return typeid(T).name();
|
|
}
|
|
|
|
template <>
|
|
const char* GetTypeName<int>()
|
|
{
|
|
return "integer";
|
|
}
|
|
|
|
template <>
|
|
const char* GetTypeName<float>()
|
|
{
|
|
return "float";
|
|
}
|
|
|
|
template <>
|
|
const char* GetTypeName<math::Vector>()
|
|
{
|
|
return "vector";
|
|
}
|
|
|
|
template <>
|
|
const char* GetTypeName<std::string>()
|
|
{
|
|
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 <class T>
|
|
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<T>()));
|
|
|
|
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 <class... TArgs, class THandler>
|
|
static void RegisterCommand(CommandMap& cmdMap, const std::string& cmd, THandler&& handler)
|
|
{
|
|
cmdMap[cmd] = [handler = std::forward<THandler>(handler)](std::istream& is) {
|
|
std::apply(handler, std::tuple<TArgs...>{ReadVal<TArgs>(is)...}); // tuple because of order of evaluation...
|
|
};
|
|
}
|
|
|
|
shapes::Group InputFile::Parse()
|
|
{
|
|
shapes::Group shapes;
|
|
|
|
CommandMap cmds;
|
|
|
|
/* SHAPES */
|
|
|
|
RegisterCommand<int, int, int, int>(cmds, "line", [&shapes](int x1, int y1, int x2, int y2) {
|
|
shapes.AddShape<shapes::Line>(math::Vector{x1, y1}, math::Vector{x2, y2});
|
|
});
|
|
|
|
RegisterCommand<int, int, int>(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<shapes::Circle>(math::Vector{x, y}, static_cast<float>(r));
|
|
});
|
|
|
|
RegisterCommand<int, int, int, int>(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<shapes::Rectangle>(math::Vector{x, y}, math::Vector{w, h});
|
|
});
|
|
|
|
/* TRANSFORMS */
|
|
|
|
RegisterCommand<int, int>(cmds, "translate", [&shapes](int ox, int oy) { shapes.Translate(math::Vector{ox, oy}); });
|
|
|
|
RegisterCommand<int, int, float>(cmds, "rotate", [&shapes](int cx, int cy, float deg) {
|
|
shapes.Rotate(math::Vector{cx, cy}, deg * math::DEG_TO_RAD);
|
|
});
|
|
|
|
RegisterCommand<int, int, float>(cmds, "scale", [&shapes](int cx, int cy, float factor) {
|
|
if (factor < std::numeric_limits<decltype(factor)>().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<std::string>(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;
|
|
}
|