Skip to content

Commit 9f9934e

Browse files
committed
Implemented checking of keys in conditions
1 parent 77f4bf7 commit 9f9934e

File tree

1 file changed

+80
-31
lines changed

1 file changed

+80
-31
lines changed

src/project_parser.cpp

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -62,25 +62,37 @@ static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destin
6262
}
6363
}
6464

65+
// TODO: construct this from a helper class with state so all the checking can be done implicitly
6566
class TomlChecker {
6667
const TomlBasicValue &m_v;
6768
tsl::ordered_set<toml::key> m_visited;
68-
bool m_checked = false;
69+
tsl::ordered_set<toml::key> m_conditionVisited;
6970

7071
public:
7172
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {}
7273
TomlChecker(const TomlBasicValue &v) : m_v(v) {}
7374
TomlChecker(const TomlChecker &) = delete;
7475

75-
~TomlChecker() noexcept(false) {
76-
if (!m_checked) {
77-
throw std::runtime_error("TomlChecker::check() not called");
76+
~TomlChecker() noexcept(false) {}
77+
78+
// TOOD: check if the condition is valid during the parsing stage to print better errors!
79+
template <typename T>
80+
void optional(const toml::key &ky, Condition<T> &destination) {
81+
get_optional(m_v, ky, destination);
82+
for (const auto &itr : destination) {
83+
if (!itr.first.empty()) {
84+
m_conditionVisited.emplace(itr.first);
85+
}
7886
}
87+
visit(ky);
7988
}
8089

8190
template <typename T>
8291
void optional(const toml::key &ky, T &destination) {
83-
get_optional(m_v, ky, destination);
92+
// TODO: this currently doesn't allow you to get an optional map<string, X>
93+
if (m_v.contains(ky)) {
94+
destination = toml::find<T>(m_v, ky);
95+
}
8496
visit(ky);
8597
}
8698

@@ -92,13 +104,48 @@ class TomlChecker {
92104

93105
void visit(const toml::key &ky) { m_visited.insert(ky); }
94106

107+
std::string format_unknown_key(const toml::key &ky, const TomlBasicValue &value) {
108+
auto loc = value.location();
109+
auto line_number_str = std::to_string(loc.line());
110+
auto line_width = line_number_str.length();
111+
auto line_str = loc.line_str();
112+
113+
std::ostringstream oss;
114+
oss << "[error] Unknown key: " << ky << '\n';
115+
oss << " --> " << loc.file_name() << '\n';
116+
117+
oss << std::string(line_width + 2, ' ') << "|\n";
118+
oss << ' ' << line_number_str << " | " << line_str << '\n';
119+
120+
oss << std::string(line_width + 2, ' ') << '|';
121+
auto key_start = line_str.find_last_of(ky, loc.column());
122+
if (key_start != std::string::npos) {
123+
oss << std::string(key_start - ky.length() + 2, ' ') << std::string(ky.length(), '~');
124+
}
125+
oss << '\n';
126+
127+
return oss.str();
128+
}
129+
95130
void check() {
96-
m_checked = true;
97131
for (const auto &itr : m_v.as_table()) {
98132
const auto &ky = itr.first;
99-
if (m_visited.count(ky) == 0) {
100-
// TODO: nice error messages
101-
throw std::runtime_error("Unknown key '" + ky + "'");
133+
if (m_conditionVisited.count(ky)) {
134+
// TODO: check if condition (ky) exists
135+
for (const auto &jtr : itr.second.as_table()) {
136+
if (m_visited.count(jtr.first) == 0) {
137+
throw std::runtime_error(format_unknown_key(jtr.first, jtr.second));
138+
}
139+
}
140+
} else if (m_visited.count(ky) == 0) {
141+
if (itr.second.is_table()) {
142+
for (const auto &jtr : itr.second.as_table()) {
143+
if (m_visited.count(jtr.first) == 0) {
144+
throw std::runtime_error(format_unknown_key(jtr.first, jtr.second));
145+
}
146+
}
147+
}
148+
throw std::runtime_error(format_unknown_key(ky, itr.second));
102149
}
103150
}
104151
}
@@ -126,6 +173,30 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
126173
get_optional(cmake, "allow-in-tree", allow_in_tree);
127174
}
128175
} else {
176+
// Reasonable default conditions (you can override these if you desire)
177+
if (parent == nullptr) {
178+
conditions["windows"] = R"cmake(WIN32)cmake";
179+
conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake";
180+
conditions["unix"] = R"cmake(UNIX)cmake";
181+
conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake";
182+
conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake";
183+
conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake";
184+
conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake";
185+
conditions["msvc"] = R"cmake(MSVC)cmake";
186+
} else {
187+
conditions = parent->conditions;
188+
}
189+
190+
if (toml.contains("conditions")) {
191+
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
192+
for (const auto &cond : conds) {
193+
conditions[cond.first] = cond.second;
194+
}
195+
}
196+
197+
// TODO: make TomlCheckerFactory
198+
// .check() only once (at the end)
199+
129200
if (toml.contains("cmake")) {
130201
const auto &cmake = toml::find(toml, "cmake");
131202
cmake_version = toml::find(cmake, "version").as_string();
@@ -165,7 +236,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
165236

166237
TomlChecker sub(itr.second);
167238
sub.optional("condition", subdir.condition);
168-
sub.optional("condition", subdir.condition);
169239
sub.optional("cmake-before", subdir.cmake_before);
170240
sub.optional("cmake-after", subdir.cmake_after);
171241
sub.optional("include-before", subdir.include_before);
@@ -409,27 +479,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
409479
v.required("packages", vcpkg.packages);
410480
v.check();
411481
}
412-
413-
// Reasonable default conditions (you can override these if you desire)
414-
if (parent == nullptr) {
415-
conditions["windows"] = R"cmake(WIN32)cmake";
416-
conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake";
417-
conditions["unix"] = R"cmake(UNIX)cmake";
418-
conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake";
419-
conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake";
420-
conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake";
421-
conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake";
422-
conditions["msvc"] = R"cmake(MSVC)cmake";
423-
} else {
424-
conditions = parent->conditions;
425-
}
426-
427-
if (toml.contains("conditions")) {
428-
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
429-
for (const auto &cond : conds) {
430-
conditions[cond.first] = cond.second;
431-
}
432-
}
433482
}
434483
}
435484

0 commit comments

Comments
 (0)