diff --git a/CMakeLists.txt b/CMakeLists.txt index ff35240..bc72a73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,7 +86,6 @@ list(APPEND cmkr_SOURCES "include/arguments.hpp" "include/build.hpp" "include/cmake_generator.hpp" - "include/enum_helper.hpp" "include/error.hpp" "include/fs.hpp" "include/help.hpp" diff --git a/include/enum_helper.hpp b/include/enum_helper.hpp deleted file mode 100644 index 93d88d9..0000000 --- a/include/enum_helper.hpp +++ /dev/null @@ -1,70 +0,0 @@ -// https://codereview.stackexchange.com/a/14315 - -#include -#include -#include -#include -#include - -// This is the type that will hold all the strings. -// Each enumeration type will declare its own specialization. -// Any enum that does not have a specialization will generate a compiler error -// indicating that there is no definition of this variable (as there should be -// be no definition of a generic version). -template -struct enumStrings { - static std::vector data; -}; - -// This is a utility type. -// Created automatically. Should not be used directly. -template -struct enumRefHolder { - T &enumVal; - enumRefHolder(T &enumVal) : enumVal(enumVal) {} -}; -template -struct enumConstRefHolder { - T const &enumVal; - enumConstRefHolder(T const &enumVal) : enumVal(enumVal) {} -}; - -// The next two functions do the actual work of reading/writing an -// enum as a string. -template -std::ostream &operator<<(std::ostream &str, enumConstRefHolder const &data) { - return str << enumStrings::data[data.enumVal]; -} - -template -std::istream &operator>>(std::istream &str, enumRefHolder const &data) { - std::string value; - str >> value; - - // These two can be made easier to read in C++11 - // using std::begin() and std::end() - // - auto begin = std::begin(enumStrings::data); - auto end = std::end(enumStrings::data); - - auto find = std::find(begin, end, value); - if (find != end) { - data.enumVal = static_cast(std::distance(begin, find)); - } else { - throw std::invalid_argument(""); - } - return str; -} - -// This is the public interface: -// use the ability of function to deduce their template type without -// being explicitly told to create the correct type of enumRefHolder -template -enumConstRefHolder enumToString(T const &e) { - return enumConstRefHolder(e); -} - -template -enumRefHolder enumFromString(T &e) { - return enumRefHolder(e); -} \ No newline at end of file diff --git a/include/project_parser.hpp b/include/project_parser.hpp index bd2bafa..7c5ea11 100644 --- a/include/project_parser.hpp +++ b/include/project_parser.hpp @@ -57,13 +57,15 @@ enum TargetType { target_custom, target_object, target_template, - target_COUNT, + target_last, }; +extern const char *targetTypeNames[target_last]; + struct Target { std::string name; - TargetType type = {}; - std::string type_string; + TargetType type = target_last; + std::string type_name; ConditionVector headers; ConditionVector sources; diff --git a/src/cmake_generator.cpp b/src/cmake_generator.cpp index b9366b3..aea0a3b 100644 --- a/src/cmake_generator.cpp +++ b/src/cmake_generator.cpp @@ -780,7 +780,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { // Check if this target is using a template. if (target.type == parser::target_template) { for (const auto &t : project.templates) { - if (target.type_string == t.outline.name) { + if (target.type_name == t.outline.name) { tmplate = &t; tmplate_cs = std::unique_ptr(new ConditionScope(gen, tmplate->outline.condition)); } @@ -895,6 +895,8 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { if (tmplate->pass_sources_to_add_function) { cmd(add_command)(target.name, target_type_string, "${" + sources_var + "}"); } else { + cmd(add_command)(target.name, target_type_string).endl(); + // clang-format off cmd("if")(sources_var); cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}"); diff --git a/src/project_parser.cpp b/src/project_parser.cpp index b35c0d0..713d03c 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -1,40 +1,27 @@ #include "project_parser.hpp" -#include "enum_helper.hpp" #include "fs.hpp" #include #include #include #include -template <> -std::vector enumStrings::data{"executable", "library", "shared", "static", - "interface", "custom", "object", "template"}; - namespace cmkr { namespace parser { -using TomlBasicValue = toml::basic_value; +const char *targetTypeNames[target_last] = {"executable", "library", "shared", "static", "interface", "custom", "object", "template"}; -template -static EnumType to_enum(const std::string &str, const std::string &help_name) { - EnumType value; - try { - std::stringstream ss(str); - ss >> enumFromString(value); - } catch (std::invalid_argument &) { - std::string supported; - for (const auto &s : enumStrings::data) { - if (!supported.empty()) { - supported += ", "; - } - supported += s; +static TargetType parse_targetType(const std::string &name) { + for (int i = 0; i < target_last; i++) { + if (name == targetTypeNames[i]) { + return static_cast(i); } - throw std::runtime_error("Unknown " + help_name + "'" + str + "'! Supported types are: " + supported); } - return value; + return target_last; } +using TomlBasicValue = toml::basic_value; + static std::string format_key_error(const std::string &error, const toml::key &ky, const TomlBasicValue &value) { auto loc = value.location(); auto line_number_str = std::to_string(loc.line()); @@ -43,7 +30,7 @@ static std::string format_key_error(const std::string &error, const toml::key &k std::ostringstream oss; oss << "[error] " << error << '\n'; - oss << " --> " << loc.file_name() << '\n'; + oss << " --> " << loc.file_name() << ':' << loc.line() << '\n'; oss << std::string(line_width + 2, ' ') << "|\n"; oss << ' ' << line_number_str << " | " << line_str << '\n'; @@ -56,7 +43,6 @@ static std::string format_key_error(const std::string &error, const toml::key &k if (key_start != std::string::npos) { oss << std::string(key_start + 1, ' ') << std::string(ky.length(), '~'); } - oss << '\n'; return oss.str(); } @@ -242,6 +228,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) { conditions["x32"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 4)cmake"; } else { conditions = parent->conditions; + templates = parent->templates; } if (toml.contains("conditions")) { @@ -387,16 +374,43 @@ Project::Project(const Project *parent, const std::string &path, bool build) { throw std::runtime_error("[[bin]] has been renamed to [[target]]"); } - auto parse_target = [&](const std::string &name, TomlChecker& t) { + auto parse_target = [&](const std::string &name, TomlChecker &t, bool isTemplate) { Target target; target.name = name; - t.required("type", target.type_string); + t.required("type", target.type_name); + target.type = parse_targetType(target.type_name); - target.type = to_enum(target.type_string, "target type"); + // Users cannot set this target type + if (target.type == target_template) { + target.type = target_last; + } - if (target.type >= target_COUNT) { - target.type = target_template; + if (!isTemplate && target.type == target_last) { + for (const auto &tmplate : templates) { + if (target.type_name == tmplate.outline.name) { + target.type = target_template; + break; + } + } + } + + if (target.type == target_last) { + std::string error = "Unknown target type '" + target.type_name + "'\n"; + error += "Available types:\n"; + for (std::string type_name : targetTypeNames) { + if (type_name != "template") { + error += " - " + type_name + "\n"; + } + } + if (!isTemplate && !templates.empty()) { + error += "Available templates:\n"; + for (const auto &tmplate : templates) { + error += " - " + tmplate.outline.name + "\n"; + } + } + error.pop_back(); // Remove last newline + throw std::runtime_error(format_key_error(error, target.type_name, t.find("type"))); } t.optional("headers", target.headers); @@ -477,26 +491,37 @@ Project::Project(const Project *parent, const std::string &path, bool build) { if (toml.contains("template")) { const auto &ts = toml::find(toml, "template").as_table(); - for (const auto &itr : ts) { + auto &t = checker.create(itr.second); + const auto &name = itr.first; + + for (const auto &type_name : targetTypeNames) { + if (name == type_name) { + throw std::runtime_error(format_key_error("Reserved template name '" + name + "'", name, itr.second)); + } + } + + for (const auto &tmplate : templates) { + if (name == tmplate.outline.name) { + throw std::runtime_error(format_key_error("Template '" + name + "' already defined", name, itr.second)); + } + } + Template tmplate; - auto& t = checker.create(itr.second); - tmplate.outline = parse_target(itr.first, t); + tmplate.outline = parse_target(name, t, true); t.optional("add-function", tmplate.add_function); t.optional("pass-sources-to-add-function", tmplate.pass_sources_to_add_function); templates.push_back(tmplate); - enumStrings::data.push_back(tmplate.outline.name); } } if (toml.contains("target")) { const auto &ts = toml::find(toml, "target").as_table(); - for (const auto &itr : ts) { - auto& t = checker.create(itr.second); - targets.push_back(parse_target(itr.first, t)); + auto &t = checker.create(itr.second); + targets.push_back(parse_target(itr.first, t, false)); } }