diff --git a/AOEmbree.cpp b/AOEmbree.cpp index d5462b2..519f1ce 100644 --- a/AOEmbree.cpp +++ b/AOEmbree.cpp @@ -9,7 +9,7 @@ #define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" -# define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 #include using namespace std::chrono; @@ -18,13 +18,11 @@ using namespace std::chrono; #define DEBUG 0 - -void errorFunction(void* userPtr, enum RTCError error, const char* str) +void errorFunction(void *userPtr, enum RTCError error, const char *str) { printf("error %d: %s\n", error, str); } - RTCDevice initializeDevice() { RTCDevice device = rtcNewDevice(NULL); @@ -36,7 +34,8 @@ RTCDevice initializeDevice() return device; } -static void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { +static void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) +{ float v10[3]; v10[0] = v1[0] - v0[0]; v10[1] = v1[1] - v0[1]; @@ -52,7 +51,8 @@ static void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { N[2] = v10[0] * v20[1] - v10[1] * v20[0]; float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2]; - if (len2 > 0.0f) { + if (len2 > 0.0f) + { float len = sqrtf(len2); N[0] /= len; @@ -61,26 +61,30 @@ static void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { } } -std::vector computeVertexNormals(const tinyobj::attrib_t& attrib, const tinyobj::shape_t& shape) { +std::vector computeVertexNormals(const tinyobj::attrib_t &attrib, const tinyobj::shape_t &shape) +{ std::vector result; result.reserve(attrib.vertices.size()); - for (int i = 0; i < attrib.vertices.size(); i++) { + for (int i = 0; i < attrib.vertices.size(); i++) + { result.push_back(0.0f); } - for (size_t f = 0; f < shape.mesh.indices.size() / 3; f++) { + for (size_t f = 0; f < shape.mesh.indices.size() / 3; f++) + { // Get the three indexes of the face (all faces are triangular) tinyobj::index_t idx0 = shape.mesh.indices[3 * f + 0]; tinyobj::index_t idx1 = shape.mesh.indices[3 * f + 1]; tinyobj::index_t idx2 = shape.mesh.indices[3 * f + 2]; // Get the three vertex indexes and coordinates - int vi[3]; // indexes - float v[3][3]; // coordinates + int vi[3]; // indexes + float v[3][3]; // coordinates - for (int k = 0; k < 3; k++) { + for (int k = 0; k < 3; k++) + { vi[0] = idx0.vertex_index; vi[1] = idx1.vertex_index; vi[2] = idx2.vertex_index; @@ -97,7 +101,8 @@ std::vector computeVertexNormals(const tinyobj::attrib_t& attrib, const t float normal[3]; CalcNormal(normal, v[0], v[1], v[2]); - for (size_t i = 0; i < 3; ++i) { + for (size_t i = 0; i < 3; ++i) + { vec3 n(result[vi[i] * 3] + normal[0], result[vi[i] * 3 + 1] + normal[1], result[vi[i] * 3 + 2] + normal[2]); n = glm::normalize(n); @@ -109,12 +114,12 @@ std::vector computeVertexNormals(const tinyobj::attrib_t& attrib, const t } return result; - } void computeAOPerVert(float *verts, float *norms, int *tris, float *result, int vcount, int icount, - int samplesAO, float maxDist) { + int samplesAO, float maxDist) +{ #if DEBUG auto timerstart = high_resolution_clock::now(); @@ -126,25 +131,26 @@ void computeAOPerVert(float *verts, float *norms, int *tris, float *result, RTCGeometry geom = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE); - float* vertices = (float*) rtcSetNewGeometryBuffer(geom, - RTC_BUFFER_TYPE_VERTEX, - 0, - RTC_FORMAT_FLOAT3, - 3 * sizeof(float), - vcount); - - unsigned* indices = (unsigned*) rtcSetNewGeometryBuffer(geom, - RTC_BUFFER_TYPE_INDEX, - 0, - RTC_FORMAT_UINT3, - 3 * sizeof(unsigned), - icount); - - - for (int i = 0; i < vcount * 3; i++) { + float *vertices = (float *)rtcSetNewGeometryBuffer(geom, + RTC_BUFFER_TYPE_VERTEX, + 0, + RTC_FORMAT_FLOAT3, + 3 * sizeof(float), + vcount); + + unsigned *indices = (unsigned *)rtcSetNewGeometryBuffer(geom, + RTC_BUFFER_TYPE_INDEX, + 0, + RTC_FORMAT_UINT3, + 3 * sizeof(unsigned), + icount); + + for (int i = 0; i < vcount * 3; i++) + { vertices[i] = verts[i]; } - for (int i = 0; i < icount * 3; i++) { + for (int i = 0; i < icount * 3; i++) + { indices[i] = tris[i]; } @@ -156,19 +162,21 @@ void computeAOPerVert(float *verts, float *norms, int *tris, float *result, std::vector rayDir; - //Compute semi-sphere ray directions + // Compute semi-sphere ray directions int samples = samplesAO * 2; float golden_angle = M_PI * (3 - sqrtf(5)); - float start = 1 - 1.0f / (int)samples; + float start = 1 - 1.0f / (int)samples; float end = 1.0f / (int)samples - 1; - for (int i = 0; i < (int)samples; i++) { + for (int i = 0; i < (int)samples; i++) + { float theta = golden_angle * i; float z = start + i * (end - start) / (int)samples; float radius = sqrtf(1 - z * z); float y = radius * sin(theta); - //Only keep the upper half of the sphere - if (z > 0.01f) { + // Only keep the upper half of the sphere + if (z > 0.01f) + { float x = radius * cos(theta); rayDir.push_back(normalize(vec3(x, y, z))); } @@ -179,50 +187,52 @@ void computeAOPerVert(float *verts, float *norms, int *tris, float *result, struct RTCIntersectContext context; rtcInitIntersectContext(&context); - tbb::parallel_for( tbb::blocked_range(0, vcount), - [&](tbb::blocked_range r) - { - RTCRay *rays = new RTCRay[rayDir.size()]; + tbb::parallel_for(tbb::blocked_range(0, vcount), + [&](tbb::blocked_range r) + { + RTCRay *rays = new RTCRay[rayDir.size()]; - for (int i = r.begin(); i < r.end(); ++i) - { - vec3 oriVec(0, 0, 1); + for (int i = r.begin(); i < r.end(); ++i) + { + vec3 oriVec(0, 0, 1); - vec3 normal(norms[i * 3], norms[i * 3 + 1], norms[i * 3 + 2]); + vec3 normal(norms[i * 3], norms[i * 3 + 1], norms[i * 3 + 2]); - quat q = glm::rotation(oriVec, normalize(normal)); + quat q = glm::rotation(oriVec, normalize(normal)); - for (int s = 0; s < rayDir.size(); s++) { - vec3 dir = rayDir[s]; + for (int s = 0; s < rayDir.size(); s++) + { + vec3 dir = rayDir[s]; - vec3 rotatedDir = q * dir; + vec3 rotatedDir = q * dir; - rays[s].org_x = vertices[i * 3]; - rays[s].org_y = vertices[i * 3 + 1]; - rays[s].org_z = vertices[i * 3 + 2]; - rays[s].dir_x = rotatedDir.x; - rays[s].dir_y = rotatedDir.y; - rays[s].dir_z = rotatedDir.z; - rays[s].tnear = 0.01f; - rays[s].tfar = maxDist; - rays[s].mask = 0; - rays[s].flags = 0; + rays[s].org_x = vertices[i * 3]; + rays[s].org_y = vertices[i * 3 + 1]; + rays[s].org_z = vertices[i * 3 + 2]; + rays[s].dir_x = rotatedDir.x; + rays[s].dir_y = rotatedDir.y; + rays[s].dir_z = rotatedDir.z; + rays[s].tnear = 0.01f; + rays[s].tfar = maxDist; + rays[s].mask = 0; + rays[s].flags = 0; + } - } + rtcOccluded1M(scene, &context, &rays[0], rayDir.size(), sizeof(RTCRay)); - rtcOccluded1M(scene, &context, &rays[0], rayDir.size(), sizeof(RTCRay)); + float totalAO = 0.0f; - float totalAO = 0.0f; + for (int s = 0; s < rayDir.size(); s++) + { + if (rays[s].tfar < 0.0f) + { // Hit + totalAO += 1.0f; + } + } - for (int s = 0; s < rayDir.size(); s++) { - if (rays[s].tfar < 0.0f) {//Hit - totalAO += 1.0f; - } - } - - result[i] = 1.0f - (totalAO / rayDir.size()); - } - }); + result[i] = 1.0f - (totalAO / rayDir.size()); + } + }); /* Though not strictly necessary in this example, you should * always make sure to release resources allocated through Embree. */ @@ -236,23 +246,23 @@ void computeAOPerVert(float *verts, float *norms, int *tris, float *result, #endif } - -int main(int argc, char **argv) { - - +int main(int argc, char **argv) +{ cxxopts::Options options("AOEmbree", "Compute per vertex ambient occlusion using embree"); bool only_AO = false; bool force_normals = false; + bool output_to_file = false; + options.add_options() ("i,input", "OBJ input file", cxxopts::value()) ("s,samples", "Number of ray sample for each vertex", cxxopts::value()->default_value("128")) ("d,dist", "Maximum ray distance", cxxopts::value()->default_value("20.0")) - ("a,ao", "Only output per vertex AO values", cxxopts::value(only_AO)) + ("a,ao", "Only output per vertex AO values", cxxopts::value(only_AO)->default_value("false")) + ("o,output", "Output file", cxxopts::value()) ("n,normals", "Recompute mesh normals", cxxopts::value(force_normals)) - ("h,help", "Print usage") - ; + ("h,help", "Print usage"); auto argsresult = options.parse(argc, argv); @@ -265,14 +275,23 @@ int main(int argc, char **argv) { int samplesAO = argsresult["samples"].as(); float maxDist = argsresult["dist"].as(); std::string path; + std::string outpath; + if (argsresult.count("input")) path = argsresult["input"].as(); - else { + else + { std::cout << options.help() << std::endl; exit(0); } + if (argsresult.count("output")) + { + output_to_file = true; + outpath = argsresult["output"].as(); + } + tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; @@ -282,12 +301,15 @@ int main(int argc, char **argv) { bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, path.c_str()); - if (!warn.empty()) { + if (!warn.empty()) + { std::cout << warn << std::endl; } - if (!err.empty()) { + if (!err.empty()) + { std::cerr << err << std::endl; + exit(-1); } std::vector verts; @@ -297,16 +319,20 @@ int main(int argc, char **argv) { verts = attrib.vertices; norms = attrib.normals; - for (int s = 0; s < shapes.size(); s++) { + for (int s = 0; s < shapes.size(); s++) + { size_t index_offset = 0; - if (attrib.normals.size() == 0 || force_normals) { + if (attrib.normals.size() == 0 || force_normals) + { norms = computeVertexNormals(attrib, shapes[s]); } - for (int f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { + for (int f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) + { int fv = shapes[s].mesh.num_face_vertices[f]; - for (size_t v = 0; v < fv; v++) { + for (size_t v = 0; v < fv; v++) + { tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v]; ids.push_back(idx.vertex_index); } @@ -320,21 +346,61 @@ int main(int argc, char **argv) { verts.size() / 3, ids.size() / 3, samplesAO, maxDist); - if (only_AO) { - for (int i = 0; i < verts.size() / 3; i++) { - printf("%.3f ", result[i]); + if (output_to_file) + { + FILE *foutput = fopen(outpath.c_str(), "w"); + + if (foutput != NULL) + { + if (only_AO) + { + for (int i = 0; i < verts.size() / 3; i++) + { + fprintf(foutput, "%.3f ", result[i]); + } + return 0; + } + + for (int i = 0; i < verts.size() / 3; i++) + { + fprintf(foutput, "v %.6f %.6f %.6f %.3f %.3f %.3f\n", + verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2], result[i], result[i], result[i]); + fprintf(foutput, "vn %.6f %.6f %.6f\n", norms[i * 3], norms[i * 3 + 1], norms[i * 3 + 2]); + } + + for (int i = 0; i < ids.size() / 3; i++) + { + fprintf(foutput, "f %d %d %d\n", ids[i * 3] + 1, ids[i * 3 + 1] + 1, ids[i * 3 + 2] + 1); + } + fclose(foutput); + } + else + { + std::cerr << "Could not open the file " << outpath << std::endl; } - return 0; } + else + { + if (only_AO) + { + for (int i = 0; i < verts.size() / 3; i++) + { + printf("%.3f ", result[i]); + } + return 0; + } - for (int i = 0; i < verts.size() / 3; i++) { - printf("v %.6f %.6f %.6f %.3f %.3f %.3f\n", - verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2], result[i], result[i], result[i]); - printf("vn %.6f %.6f %.6f\n", norms[i * 3], norms[i * 3 + 1], norms[i * 3 + 2]); - } + for (int i = 0; i < verts.size() / 3; i++) + { + printf("v %.6f %.6f %.6f %.3f %.3f %.3f\n", + verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2], result[i], result[i], result[i]); + printf("vn %.6f %.6f %.6f\n", norms[i * 3], norms[i * 3 + 1], norms[i * 3 + 2]); + } - for (int i = 0; i < ids.size() / 3; i++) { - printf("f %d %d %d\n", ids[i * 3] + 1, ids[i * 3 + 1] + 1, ids[i * 3 + 2] + 1); + for (int i = 0; i < ids.size() / 3; i++) + { + printf("f %d %d %d\n", ids[i * 3] + 1, ids[i * 3 + 1] + 1, ids[i * 3 + 2] + 1); + } } return 0; diff --git a/cxxopts.hpp b/cxxopts.hpp index ea06962..af11cc2 100644 --- a/cxxopts.hpp +++ b/cxxopts.hpp @@ -30,38 +30,64 @@ THE SOFTWARE. #include #include #include +#include #include #include -#include #include #include #include #include #include #include +#include -#ifdef __cpp_lib_optional -#include -#define CXXOPTS_HAS_OPTIONAL +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif + +#ifndef CXXOPTS_NO_REGEX +# include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD #endif #ifndef CXXOPTS_VECTOR_DELIMITER #define CXXOPTS_VECTOR_DELIMITER ',' #endif -#define CXXOPTS__VERSION_MAJOR 2 -#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 0 #define CXXOPTS__VERSION_PATCH 0 +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 +#define CXXOPTS_NULL_DEREF_IGNORE +#endif + namespace cxxopts { - static constexpr struct { - uint8_t major, minor, patch; - } version = { - CXXOPTS__VERSION_MAJOR, - CXXOPTS__VERSION_MINOR, - CXXOPTS__VERSION_PATCH - }; + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; } // namespace cxxopts //when we ask cxxopts to use Unicode, help strings are processed using ICU, @@ -75,134 +101,145 @@ namespace cxxopts namespace cxxopts { - typedef icu::UnicodeString String; - - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(std::move(s)); - } - - class UnicodeStringIterator : public - std::iterator - { - public: + using String = icu::UnicodeString; - UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) - : s(string) - , i(pos) + inline + String + toLocalString(std::string s) { + return icu::UnicodeString::fromUTF8(std::move(s)); } - value_type - operator*() const +#if defined(__GNUC__) + // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: + // warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Weffc++" +// This will be ignored under other compilers like LLVM clang. +#endif + class UnicodeStringIterator : public + std::iterator { - return s->char32At(i); - } + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif - bool - operator==(const UnicodeStringIterator& rhs) const + inline + String& + stringAppend(String& s, String a) { - return s == rhs.s && i == rhs.i; + return s.append(std::move(a)); } - bool - operator!=(const UnicodeStringIterator& rhs) const + inline + String& + stringAppend(String& s, size_t n, UChar32 c) { - return !(*this == rhs); + for (size_t i = 0; i != n; ++i) + { + s.append(c); + } + + return s; } - UnicodeStringIterator& - operator++() + template + String& + stringAppend(String& s, Iterator begin, Iterator end) { - ++i; - return *this; + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; } - UnicodeStringIterator - operator+(int32_t v) + inline + size_t + stringLength(const String& s) { - return UnicodeStringIterator(s, i + v); + return s.length(); } - private: - const icu::UnicodeString* s; - int32_t i; - }; - - inline - String& - stringAppend(String&s, String a) - { - return s.append(std::move(a)); - } - - inline - String& - stringAppend(String& s, int n, UChar32 c) - { - for (int i = 0; i != n; ++i) - { - s.append(c); - } + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); - return s; - } + return result; + } - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - while (begin != end) + inline + bool + empty(const String& s) { - s.append(*begin); - ++begin; + return s.isEmpty(); } - - return s; - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - std::string - toUTF8String(const String& s) - { - std::string result; - s.toUTF8String(result); - - return result; - } - - inline - bool - empty(const String& s) - { - return s.isEmpty(); - } } namespace std { - inline - cxxopts::UnicodeStringIterator - begin(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, 0); - } - - inline - cxxopts::UnicodeStringIterator - end(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, s.length()); - } + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } } //ifdef CXXOPTS_USE_UNICODE @@ -210,56 +247,56 @@ namespace std namespace cxxopts { - typedef std::string String; - - template - T - toLocalString(T&& t) - { - return std::forward(t); - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - String& - stringAppend(String&s, const String& a) - { - return s.append(a); - } - - inline - String& - stringAppend(String& s, size_t n, char c) - { - return s.append(n, c); - } - - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - return s.append(begin, end); - } - - template - std::string - toUTF8String(T&& t) - { - return std::forward(t); - } - - inline - bool - empty(const std::string& s) - { - return s.empty(); - } + using String = std::string; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String& s, const String& a) + { + return s.append(a); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } } // namespace cxxopts //ifdef CXXOPTS_USE_UNICODE @@ -267,1928 +304,2309 @@ namespace cxxopts namespace cxxopts { - namespace - { + namespace + { #ifdef _WIN32 - const std::string LQUOTE("\'"); - const std::string RQUOTE("\'"); + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); #else - const std::string LQUOTE("‘"); - const std::string RQUOTE("’"); + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); #endif - } // namespace + } // namespace - class Value : public std::enable_shared_from_this - { +#if defined(__GNUC__) +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Weffc++" +// This will be ignored under other compilers like LLVM clang. +#endif + class Value : public std::enable_shared_from_this + { public: - virtual ~Value() = default; - - virtual - std::shared_ptr - clone() const = 0; + virtual ~Value() = default; - virtual void - parse(const std::string& text) const = 0; + virtual + std::shared_ptr + clone() const = 0; - virtual void - parse() const = 0; + virtual void + parse(const std::string& text) const = 0; - virtual bool - has_default() const = 0; + virtual void + parse() const = 0; - virtual bool - is_container() const = 0; + virtual bool + has_default() const = 0; - virtual bool - has_implicit() const = 0; + virtual bool + is_container() const = 0; - virtual std::string - get_default_value() const = 0; + virtual bool + has_implicit() const = 0; - virtual std::string - get_implicit_value() const = 0; + virtual std::string + get_default_value() const = 0; - virtual std::shared_ptr - default_value(const std::string& value) = 0; + virtual std::string + get_implicit_value() const = 0; - virtual std::shared_ptr - implicit_value(const std::string& value) = 0; + virtual std::shared_ptr + default_value(const std::string& value) = 0; - virtual std::shared_ptr - no_implicit_value() = 0; + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; - virtual bool - is_boolean() const = 0; - }; + virtual std::shared_ptr + no_implicit_value() = 0; - class OptionException : public std::exception - { - public: - explicit OptionException(std::string message) - : m_message(std::move(message)) + virtual bool + is_boolean() const = 0; + }; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + class OptionException : public std::exception { - } + public: + explicit OptionException(std::string message) + : m_message(std::move(message)) + { + } - const char* - what() const noexcept override - { - return m_message.c_str(); - } + CXXOPTS_NODISCARD + const char* + what() const noexcept override + { + return m_message.c_str(); + } private: - std::string m_message; - }; + std::string m_message; + }; - class OptionSpecException : public OptionException - { + class OptionSpecException : public OptionException + { public: - explicit OptionSpecException(const std::string& message) - : OptionException(message) - { - } - }; + explicit OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; - class OptionParseException : public OptionException - { - public: - explicit OptionParseException(const std::string& message) - : OptionException(message) + class OptionParseException : public OptionException { - } - }; - - class option_exists_error : public OptionSpecException - { public: - explicit option_exists_error(const std::string& option) - : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") - { - } - }; + explicit OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; - class invalid_option_format_error : public OptionSpecException - { - public: - explicit invalid_option_format_error(const std::string& format) - : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + class option_exists_error : public OptionSpecException { - } - }; - - class option_syntax_exception : public OptionParseException { public: - explicit option_syntax_exception(const std::string& text) - : OptionParseException("Argument " + LQUOTE + text + RQUOTE + - " starts with a - but has incorrect syntax") - { - } - }; + explicit option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; - class option_not_exists_exception : public OptionParseException - { - public: - explicit option_not_exists_exception(const std::string& option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + class invalid_option_format_error : public OptionSpecException { - } - }; - - class missing_argument_exception : public OptionParseException - { public: - explicit missing_argument_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " is missing an argument" - ) - { - } - }; + explicit invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; - class option_requires_argument_exception : public OptionParseException - { + class option_syntax_exception : public OptionParseException { public: - explicit option_requires_argument_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " requires an argument" - ) - { - } - }; + explicit option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; - class option_not_has_argument_exception : public OptionParseException - { + class option_not_exists_exception : public OptionParseException + { public: - option_not_has_argument_exception - ( - const std::string& option, - const std::string& arg - ) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + - " does not take an argument, but argument " + - LQUOTE + arg + RQUOTE + " given" - ) + explicit option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException { - } - }; + public: + explicit missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; - class option_not_present_exception : public OptionParseException - { + class option_requires_argument_exception : public OptionParseException + { public: - explicit option_not_present_exception(const std::string& option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + explicit option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException { - } - }; + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; - class argument_incorrect_type : public OptionParseException - { + class option_not_present_exception : public OptionParseException + { public: - explicit argument_incorrect_type - ( - const std::string& arg - ) - : OptionParseException( - "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" - ) + explicit option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class option_has_no_value_exception : public OptionException { - } - }; + public: + explicit option_has_no_value_exception(const std::string& option) + : OptionException( + !option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } + }; - class option_required_exception : public OptionParseException - { + class argument_incorrect_type : public OptionParseException + { public: - explicit option_required_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " is required but not present" - ) + explicit argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException { - } - }; + public: + explicit option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; - template - void throw_or_mimic(const std::string& text) - { - static_assert(std::is_base_of::value, - "throw_or_mimic only works on std::exception and " - "deriving classes"); + template + void throw_or_mimic(const std::string& text) + { + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); #ifndef CXXOPTS_NO_EXCEPTIONS - // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw - throw T{text}; + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{ text }; #else - // Otherwise manually instantiate the exception, print what() to stderr, - // and abort - T exception{text}; - std::cerr << exception.what() << std::endl; - std::cerr << "Aborting (exceptions disabled)..." << std::endl; - std::abort(); + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{ text }; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); #endif - } + } - namespace values - { - namespace + namespace values { - std::basic_regex integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); - std::basic_regex truthy_pattern - ("(t|T)(rue)?|1"); - std::basic_regex falsy_pattern - ("(f|F)(alse)?|0"); - } // namespace + namespace parser_tool + { + struct IntegerDesc + { + std::string negative = ""; + std::string base = ""; + std::string value = ""; + }; + struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; + }; +#ifdef CXXOPTS_NO_REGEX + inline IntegerDesc SplitInteger(const std::string& text) + { + if (text.empty()) + { + throw_or_mimic(text); + } + IntegerDesc desc; + const char* pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic(text); + } + return desc; + } - namespace detail - { - template - struct SignedCheck; + inline bool IsTrueText(const std::string& text) + { + const char* pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; + } - template - struct SignedCheck - { - template - void - operator()(bool negative, U u, const std::string& text) - { - if (negative) - { - if (u > static_cast((std::numeric_limits::min)())) + inline bool IsFalseText(const std::string& text) { - throw_or_mimic(text); + const char* pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; } - } - else - { - if (u > static_cast((std::numeric_limits::max)())) + + inline std::pair SplitSwitchDef(const std::string& text) { - throw_or_mimic(text); + std::string short_sw, long_sw; + const char* pdata = text.c_str(); + if (isalnum(*pdata) && *(pdata + 1) == ',') { + short_sw = std::string(1, *pdata); + pdata += 2; + } + while (*pdata == ' ') { pdata += 1; } + if (isalnum(*pdata)) { + const char* store = pdata; + pdata += 1; + while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') { + pdata += 1; + } + if (*pdata == '\0') { + long_sw = std::string(store, pdata - store); + } + else { + throw_or_mimic(text); + } + } + return std::pair(short_sw, long_sw); } - } - } - }; - template - struct SignedCheck - { - template - void - operator()(bool, U, const std::string&) {} - }; - - template - void - check_signed_range(bool negative, U value, const std::string& text) - { - SignedCheck::is_signed>()(negative, value, text); - } - } // namespace detail - - template - R - checked_negate(T&& t, const std::string&, std::true_type) - { - // if we got to here, then `t` is a positive number that fits into - // `R`. So to avoid MSVC C4146, we first cast it to `R`. - // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - return static_cast(-static_cast(t-1)-1); - } + inline ArguDesc ParseArgument(const char* arg, bool& matched) + { + ArguDesc argu_desc; + const char* pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata)) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata)) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; + } - template - T - checked_negate(T&& t, const std::string& text, std::false_type) - { - throw_or_mimic(text); - return t; - } +#else // CXXOPTS_NO_REGEX - template - void - integer_parser(const std::string& text, T& value) - { - std::smatch match; - std::regex_match(text, match, integer_pattern); + namespace + { + + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + std::basic_regex option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + } // namespace - if (match.length() == 0) - { - throw_or_mimic(text); - } + inline IntegerDesc SplitInteger(const std::string& text) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; + } - if (match.length(4) > 0) - { - value = 0; - return; - } + inline bool IsTrueText(const std::string& text) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + return !result.empty(); + } - using US = typename std::make_unsigned::type; + inline bool IsFalseText(const std::string& text) + { + std::smatch result; + std::regex_match(text, result, falsy_pattern); + return !result.empty(); + } - constexpr bool is_signed = std::numeric_limits::is_signed; - const bool negative = match.length(1) > 0; - const uint8_t base = match.length(2) > 0 ? 16 : 10; + inline std::pair SplitSwitchDef(const std::string& text) + { + std::match_results result; + std::regex_match(text.c_str(), result, option_specifier); + if (result.empty()) + { + throw_or_mimic(text); + } - auto value_match = match[3]; + const std::string& short_sw = result[2]; + const std::string& long_sw = result[3]; - US result = 0; + return std::pair(short_sw, long_sw); + } - for (auto iter = value_match.first; iter != value_match.second; ++iter) - { - US digit = 0; + inline ArguDesc ParseArgument(const char* arg, bool& matched) + { + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; + } - if (*iter >= '0' && *iter <= '9') - { - digit = static_cast(*iter - '0'); +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX } - else if (base == 16 && *iter >= 'a' && *iter <= 'f') + + namespace detail { - digit = static_cast(*iter - 'a' + 10); - } - else if (base == 16 && *iter >= 'A' && *iter <= 'F') + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) const {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } // namespace detail + + template + void + checked_negate(R& r, T&& t, const std::string&, std::true_type) { - digit = static_cast(*iter - 'A' + 10); + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t - 1) - 1); } - else + + template + void + checked_negate(R&, T&&, const std::string& text, std::false_type) { - throw_or_mimic(text); + throw_or_mimic(text); } - const US next = static_cast(result * base + digit); - if (result > next) + template + void + integer_parser(const std::string& text, T& value) { - throw_or_mimic(text); - } + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); - result = next; - } + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::is_signed; - detail::check_signed_range(negative, result, text); + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string& value_match = int_desc.value; - if (negative) - { - value = checked_negate(result, - text, - std::integral_constant()); - } - else - { - value = static_cast(result); - } - } + US result = 0; - template - void stringstream_parser(const std::string& text, T& value) - { - std::stringstream in(text); - in >> value; - if (!in) { - throw_or_mimic(text); - } - } + for (char ch : value_match) + { + US digit = 0; + + if (ch >= '0' && ch <= '9') + { + digit = static_cast(ch - '0'); + } + else if (base == 16 && ch >= 'a' && ch <= 'f') + { + digit = static_cast(ch - 'a' + 10); + } + else if (base == 16 && ch >= 'A' && ch <= 'F') + { + digit = static_cast(ch - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + const US next = static_cast(result * base + digit); + if (result > next) + { + throw_or_mimic(text); + } + + result = next; + } - inline - void - parse_value(const std::string& text, uint8_t& value) - { - integer_parser(text, value); - } + detail::check_signed_range(negative, result, text); - inline - void - parse_value(const std::string& text, int8_t& value) - { - integer_parser(text, value); - } + if (negative) + { + checked_negate(value, result, text, std::integral_constant()); + } + else + { + value = static_cast(result); + } + } - inline - void - parse_value(const std::string& text, uint16_t& value) - { - integer_parser(text, value); - } + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } + } - inline - void - parse_value(const std::string& text, int16_t& value) - { - integer_parser(text, value); - } + template ::value>::type* = nullptr + > + void parse_value(const std::string& text, T& value) + { + integer_parser(text, value); + } - inline - void - parse_value(const std::string& text, uint32_t& value) - { - integer_parser(text, value); - } + inline + void + parse_value(const std::string& text, bool& value) + { + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } - inline - void - parse_value(const std::string& text, int32_t& value) - { - integer_parser(text, value); - } + if (parser_tool::IsFalseText(text)) + { + value = false; + return; + } - inline - void - parse_value(const std::string& text, uint64_t& value) - { - integer_parser(text, value); - } + throw_or_mimic(text); + } - inline - void - parse_value(const std::string& text, int64_t& value) - { - integer_parser(text, value); - } + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } - inline - void - parse_value(const std::string& text, bool& value) - { - std::smatch result; - std::regex_match(text, result, truthy_pattern); - - if (!result.empty()) - { - value = true; - return; - } - - std::regex_match(text, result, falsy_pattern); - if (!result.empty()) - { - value = false; - return; - } - - throw_or_mimic(text); - } + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template ::value>::type* = nullptr + > + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } - inline - void - parse_value(const std::string& text, std::string& value) - { - value = text; - } + template + void + parse_value(const std::string& text, std::vector& value) + { + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while (!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } - // The fallback parser. It uses the stringstream parser to parse all types - // that have not been overloaded explicitly. It has to be placed in the - // source code before all other more specialized templates. - template - void - parse_value(const std::string& text, T& value) { - stringstream_parser(text, value); - } +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + inline + void parse_value(const std::string& text, char& c) + { + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; + } + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } + + bool + is_container() const override + { + return type_is_container::value; + } + + void + parse() const override + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const override + { + return m_default; + } + + bool + has_implicit() const override + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const override + { + return m_default_value; + } + + std::string + get_implicit_value() const override + { + return m_implicit_value; + } + + bool + is_boolean() const override + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr m_result{}; + T* m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() override = default; + + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } // namespace values template - void - parse_value(const std::string& text, std::vector& value) - { - std::stringstream in(text); - std::string token; - while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { - T v; - parse_value(token, v); - value.emplace_back(std::move(v)); - } + std::shared_ptr + value() + { + return std::make_shared>(); } -#ifdef CXXOPTS_HAS_OPTIONAL template - void - parse_value(const std::string& text, std::optional& value) + std::shared_ptr + value(T& t) { - T result; - parse_value(text, result); - value = std::move(result); + return std::make_shared>(&t); } -#endif - inline - void parse_value(const std::string& text, char& c) + class OptionAdder; + + class OptionDetails { - if (text.length() != 1) - { - throw_or_mimic(text); - } + public: + OptionDetails + ( + std::string short_, + std::string long_, + String desc, + std::shared_ptr val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) + { + m_hash = std::hash{}(m_long + m_short); + } - c = text[0]; - } + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } - template - struct type_is_container - { - static constexpr bool value = false; + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } + + CXXOPTS_NODISCARD + const std::string& + long_name() const + { + return m_long; + } + + size_t + hash() const + { + return m_hash; + } + + private: + std::string m_short{}; + std::string m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; + int m_count; + + size_t m_hash{}; }; - template - struct type_is_container> + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails { - static constexpr bool value = true; + std::string name{}; + std::string description{}; + std::vector options{}; }; - template - class abstract_value : public Value + class OptionValue { - using Self = abstract_value; + public: + void + parse + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_name = &details->long_name(); + } - public: - abstract_value() - : m_result(std::make_shared()) - , m_store(m_result.get()) - { - } + void + parse_default(const std::shared_ptr& details) + { + ensure_value(details); + m_default = true; + m_long_name = &details->long_name(); + m_value->parse(); + } - explicit abstract_value(T* t) - : m_store(t) - { - } + void + parse_no_value(const std::shared_ptr& details) + { + m_long_name = &details->long_name(); + } - ~abstract_value() override = default; +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" +#endif - abstract_value(const abstract_value& rhs) - { - if (rhs.m_result) + CXXOPTS_NODISCARD + size_t + count() const noexcept { - m_result = std::make_shared(); - m_store = m_result.get(); + return m_count; } - else + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +#pragma GCC diagnostic pop +#endif + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const { - m_store = rhs.m_store; - } - - m_default = rhs.m_default; - m_implicit = rhs.m_implicit; - m_default_value = rhs.m_default_value; - m_implicit_value = rhs.m_implicit_value; - } - - void - parse(const std::string& text) const override - { - parse_value(text, *m_store); - } - - bool - is_container() const override - { - return type_is_container::value; - } - - void - parse() const override - { - parse_value(m_default_value, *m_store); - } - - bool - has_default() const override - { - return m_default; - } - - bool - has_implicit() const override - { - return m_implicit; - } - - std::shared_ptr - default_value(const std::string& value) override - { - m_default = true; - m_default_value = value; - return shared_from_this(); - } - - std::shared_ptr - implicit_value(const std::string& value) override - { - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } - - std::shared_ptr - no_implicit_value() override - { - m_implicit = false; - return shared_from_this(); - } - - std::string - get_default_value() const override - { - return m_default_value; - } - - std::string - get_implicit_value() const override - { - return m_implicit_value; - } - - bool - is_boolean() const override - { - return std::is_same::value; - } - - const T& - get() const - { - if (m_store == nullptr) - { - return *m_result; - } - return *m_store; - } - - protected: - std::shared_ptr m_result; - T* m_store; - - bool m_default = false; - bool m_implicit = false; - - std::string m_default_value; - std::string m_implicit_value; + if (m_value == nullptr) { + throw_or_mimic( + m_long_name == nullptr ? "" : *m_long_name); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(const std::shared_ptr& details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + + const std::string* m_long_name = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr m_value{}; + size_t m_count = 0; + bool m_default = false; }; - template - class standard_value : public abstract_value + class KeyValue { - public: - using abstract_value::abstract_value; + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } - std::shared_ptr - clone() const - { - return std::make_shared>(*this); - } - }; + CXXOPTS_NODISCARD + const std::string& + key() const + { + return m_key; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } - template <> - class standard_value : public abstract_value - { - public: - ~standard_value() override = default; - - standard_value() - { - set_default_and_implicit(); - } - - explicit standard_value(bool* b) - : abstract_value(b) - { - set_default_and_implicit(); - } - - std::shared_ptr - clone() const override - { - return std::make_shared>(*this); - } - - private: - - void - set_default_and_implicit() - { - m_default = true; - m_default_value = "false"; - m_implicit = true; - m_implicit_value = "true"; - } + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; }; - } // namespace values - - template - std::shared_ptr - value() - { - return std::make_shared>(); - } - - template - std::shared_ptr - value(T& t) - { - return std::make_shared>(&t); - } - - class OptionAdder; - - class OptionDetails - { - public: - OptionDetails - ( - std::string short_, - std::string long_, - String desc, - std::shared_ptr val - ) - : m_short(std::move(short_)) - , m_long(std::move(long_)) - , m_desc(std::move(desc)) - , m_value(std::move(val)) - , m_count(0) - { - } - OptionDetails(const OptionDetails& rhs) - : m_desc(rhs.m_desc) - , m_count(rhs.m_count) + using ParsedHashMap = std::unordered_map; + using NameHashMap = std::unordered_map; + + class ParseResult { - m_value = rhs.m_value->clone(); - } + public: - OptionDetails(OptionDetails&& rhs) = default; + ParseResult() = default; + ParseResult(const ParseResult&) = default; - const String& - description() const - { - return m_desc; - } + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_unmatched(std::move(unmatched_args)) + { + } - const Value& value() const { - return *m_value; - } + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; - std::shared_ptr - make_storage() const - { - return m_value->clone(); - } + size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) + { + return 0; + } - const std::string& - short_name() const - { - return m_short; - } + auto viter = m_values.find(iter->second); - const std::string& - long_name() const - { - return m_long; - } + if (viter == m_values.end()) + { + return 0; + } - private: - std::string m_short; - std::string m_long; - String m_desc; - std::shared_ptr m_value; - int m_count; - }; - - struct HelpOptionDetails - { - std::string s; - std::string l; - String desc; - bool has_default; - std::string default_value; - bool has_implicit; - std::string implicit_value; - std::string arg_help; - bool is_container; - bool is_boolean; - }; - - struct HelpGroupDetails - { - std::string name; - std::string description; - std::vector options; - }; - - class OptionValue - { - public: - void - parse - ( - const std::shared_ptr& details, - const std::string& text - ) - { - ensure_value(details); - ++m_count; - m_value->parse(text); - } + return viter->second.count(); + } - void - parse_default(const std::shared_ptr& details) - { - ensure_value(details); - m_default = true; - m_value->parse(); - } + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); - size_t - count() const noexcept - { - return m_count; - } + if (iter == m_keys.end()) + { + throw_or_mimic(option); + } - // TODO: maybe default options should count towards the number of arguments - bool - has_default() const noexcept - { - return m_default; - } + auto viter = m_values.find(iter->second); - template - const T& - as() const - { - if (m_value == nullptr) { - throw_or_mimic("No value"); - } + if (viter == m_values.end()) + { + throw_or_mimic(option); + } -#ifdef CXXOPTS_NO_RTTI - return static_cast&>(*m_value).get(); -#else - return dynamic_cast&>(*m_value).get(); -#endif - } + return viter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + const std::vector& + unmatched() const + { + return m_unmatched; + } private: - void - ensure_value(const std::shared_ptr& details) - { - if (m_value == nullptr) - { - m_value = details->make_storage(); - } - } + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_unmatched{}; + }; - std::shared_ptr m_value; - size_t m_count = 0; - bool m_default = false; - }; + struct Option + { + Option + ( + std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) + { + } - class KeyValue - { - public: - KeyValue(std::string key_, std::string value_) - : m_key(std::move(key_)) - , m_value(std::move(value_)) - { - } + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; + }; - const - std::string& - key() const - { - return m_key; - } + using OptionMap = std::unordered_map>; + using PositionalList = std::vector; + using PositionalListIterator = PositionalList::const_iterator; - const - std::string& - value() const + class OptionParser { - return m_value; - } + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } - template - T - as() const - { - T result; - values::parse_value(m_value, result); - return result; - } + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + void + add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + parse_no_value(const std::shared_ptr& details); private: - std::string m_key; - std::string m_value; - }; - class ParseResult - { - public: + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; - ParseResult( - std::shared_ptr< - std::unordered_map> - >, - std::vector, - bool allow_unrecognised, - int&, char**&); + std::vector m_sequential{}; + bool m_allow_unrecognised; - size_t - count(const std::string& o) const + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; + }; + + class Options { - auto iter = m_options->find(o); - if (iter == m_options->end()) - { - return 0; - } + public: - auto riter = m_results.find(iter->second); + explicit Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_width(76) + , m_tab_expansion(false) + , m_options(std::make_shared()) + { + } - return riter->second.count(); - } + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } - const OptionValue& - operator[](const std::string& option) const - { - auto iter = m_options->find(option); + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } - if (iter == m_options->end()) - { - throw_or_mimic(option); - } + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } - auto riter = m_results.find(iter->second); + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } - return riter->second; - } + Options& + set_width(size_t width) + { + m_width = width; + return *this; + } - const std::vector& - arguments() const - { - return m_sequential; - } + Options& + set_tab_expansion(bool expansion = true) + { + m_tab_expansion = expansion; + return *this; + } + + ParseResult + parse(int argc, const char* const* argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list