HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
jsonpath.hpp
1// Copyright Take Vos 2021-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "utility/module.hpp"
8#include "tokenizer.hpp"
9#include "generator.hpp"
10#include <string>
11#include <variant>
12#include <string_view>
13#include <vector>
14#include <limits>
15#include <format>
16
17namespace hi::inline v1 {
18
20 [[nodiscard]] std::string string() const noexcept
21 {
22 return "$";
23 }
24
25 [[nodiscard]] bool is_singular() const noexcept
26 {
27 return true;
28 }
29};
30
32 [[nodiscard]] std::string string() const noexcept
33 {
34 return "@";
35 }
36
37 [[nodiscard]] bool is_singular() const noexcept
38 {
39 return true;
40 }
41};
42
44 [[nodiscard]] std::string string() const noexcept
45 {
46 return "[*]";
47 }
48
49 [[nodiscard]] bool is_singular() const noexcept
50 {
51 return false;
52 }
53};
54
56 [[nodiscard]] std::string string() const noexcept
57 {
58 return "..";
59 }
60
61 [[nodiscard]] bool is_singular() const noexcept
62 {
63 return false;
64 }
65};
66
69
70 hi_constexpr jsonpath_names(jsonpath_names const &) noexcept = default;
71 hi_constexpr jsonpath_names(jsonpath_names &&) noexcept = default;
72 hi_constexpr jsonpath_names &operator=(jsonpath_names const &) noexcept = default;
73 hi_constexpr jsonpath_names &operator=(jsonpath_names &&) noexcept = default;
74
75 jsonpath_names(std::string other) noexcept : names()
76 {
77 names.push_back(std::move(other));
78 }
79
80 [[nodiscard]] std::string string() const noexcept
81 {
82 auto r = std::string{"["};
83 auto first = true;
84 for (hilet &name : names) {
85 if (not first) {
86 r += ',';
87 }
88
89 r += '\'';
90 r += name;
91 r += '\'';
92 first = false;
93 }
94 r += ']';
95 return r;
96 }
97
98 [[nodiscard]] std::size_t size() const noexcept
99 {
100 return names.size();
101 }
102
103 [[nodiscard]] std::string const &front() const noexcept
104 {
105 return names.front();
106 }
107
108 [[nodiscard]] auto begin() const noexcept
109 {
110 return names.begin();
111 }
112
113 [[nodiscard]] auto end() const noexcept
114 {
115 return names.end();
116 }
117
118 void push_back(std::string rhs) noexcept
119 {
120 names.push_back(std::move(rhs));
121 }
122
123 [[nodiscard]] bool is_singular() const noexcept
124 {
125 return names.size() == 1;
126 }
127};
128
130 std::vector<ssize_t> indices;
131
132 hi_constexpr jsonpath_indices(jsonpath_indices const &) noexcept = default;
133 hi_constexpr jsonpath_indices(jsonpath_indices &&) noexcept = default;
134 hi_constexpr jsonpath_indices &operator=(jsonpath_indices const &) noexcept = default;
135 hi_constexpr jsonpath_indices &operator=(jsonpath_indices &&) noexcept = default;
136
137 jsonpath_indices(ssize_t other) noexcept : indices()
138 {
139 indices.push_back(other);
140 }
141
142 [[nodiscard]] std::string string() const noexcept
143 {
144 auto r = std::string{"["};
145 auto first = true;
146 for (hilet index : indices) {
147 if (not first) {
148 r += ',';
149 }
150
151 r += hi::to_string(index);
152 first = false;
153 }
154 r += ']';
155 return r;
156 }
157
158 [[nodiscard]] generator<std::size_t> filter(std::size_t size) const noexcept
159 {
160 hilet size_ = narrow_cast<ssize_t>(size);
161
162 for (hilet index : indices) {
163 hilet index_ = index >= 0 ? index : size_ + index;
164 if (index_ >= 0 and index_ < size_) {
165 co_yield narrow_cast<std::size_t>(index_);
166 }
167 }
168 }
169
170 [[nodiscard]] std::size_t size() const noexcept
171 {
172 return indices.size();
173 }
174
175 [[nodiscard]] ssize_t const &front() const noexcept
176 {
177 return indices.front();
178 }
179
180 void push_back(ssize_t rhs) noexcept
181 {
182 indices.push_back(rhs);
183 }
184
185 [[nodiscard]] bool is_singular() const noexcept
186 {
187 return indices.size() == 1;
188 }
189};
190
192 ssize_t first;
193 ssize_t last;
194 ssize_t step;
195
196 constexpr jsonpath_slice(jsonpath_slice const &) noexcept = default;
197 constexpr jsonpath_slice(jsonpath_slice &&) noexcept = default;
198 constexpr jsonpath_slice &operator=(jsonpath_slice const &) noexcept = default;
199 constexpr jsonpath_slice &operator=(jsonpath_slice &&) noexcept = default;
200
201 constexpr jsonpath_slice(ssize_t first, ssize_t last, ssize_t step) noexcept : first(first), last(last), step(step) {}
202
208 [[nodiscard]] std::size_t begin(std::size_t size) const noexcept
209 {
210 hilet size_ = narrow_cast<ssize_t>(size);
211 hilet begin = first >= 0 ? first : size_ + first;
212 return narrow_cast<std::size_t>(std::clamp(begin, 0_z, size_));
213 }
214
223 [[nodiscard]] std::size_t end(std::size_t size) const noexcept
224 {
225 hilet size_ = narrow_cast<ssize_t>(size);
226 hilet last_ = std::clamp(
227 last == std::numeric_limits<ssize_t>::min() ? size_ :
228 last >= 0 ? last :
229 size_ + last,
230 0_z,
231 size_);
232
233 hilet first_ = begin(size);
234 hilet distance = last_ - first_;
235 hilet steps = distance / step;
236 return narrow_cast<std::size_t>(first_ + steps * step);
237 }
238
239 [[nodiscard]] bool last_is_empty() const noexcept
240 {
241 return last == std::numeric_limits<ssize_t>::min();
242 }
243
244 [[nodiscard]] std::string string() const noexcept
245 {
246 if (last_is_empty()) {
247 return std::format("[{}:e:{}]", first, step);
248 } else {
249 return std::format("[{}:{}:{}]", first, last, step);
250 }
251 }
252
253 [[nodiscard]] bool is_singular() const noexcept
254 {
255 return false;
256 }
257};
258
259// clang-format off
260using jsonpath_node = std::variant<
261 jsonpath_root, jsonpath_current, jsonpath_wildcard, jsonpath_descend, jsonpath_names, jsonpath_indices, jsonpath_slice>;
262// clang-format on
263
264[[nodiscard]] inline jsonpath_node parse_jsonpath_slicing_operator(auto &it, auto it_end, ssize_t first)
265{
266 ++it;
268 if (*it == tokenizer_name_t::IntegerLiteral) {
269 last = static_cast<ssize_t>(*it);
270 ++it;
271 }
272
273 auto step = 1_z;
274 if (*it == tokenizer_name_t::Operator and *it == ":") {
275 ++it;
276 hi_check(*it == tokenizer_name_t::IntegerLiteral, "Expect integer as third slice argument, got {}.", *it);
277 step = static_cast<ssize_t>(*it);
278 ++it;
279 }
280
281 hi_check(*it == tokenizer_name_t::Operator and *it == "]", "Expected end of slicing operator ']', got {}.", *it);
282
283 hi_check(step != 0, "Slicing operator's step must not be zero");
284 return jsonpath_slice{first, last, step};
285}
286
287[[nodiscard]] inline jsonpath_node parse_jsonpath_integer_indexing_operator(auto &it, auto it_end, ssize_t first)
288{
289 auto tmp = jsonpath_indices(first);
290
291 while (*it == tokenizer_name_t::Operator and *it == ",") {
292 ++it;
293 hi_check(*it == tokenizer_name_t::IntegerLiteral, "Expect integer literal after comma ',', got {}.", *it);
294 tmp.push_back(static_cast<ssize_t>(*it));
295 ++it;
296 }
297
298 hi_check(*it == tokenizer_name_t::Operator and *it == "]", "Expected end of slicing operator ']', got {}.", *it);
299 return tmp;
300}
301
302[[nodiscard]] inline jsonpath_node parse_jsonpath_name_indexing_operator(auto &it, auto it_end, std::string first)
303{
304 auto tmp = jsonpath_names(std::move(first));
305
306 while (*it == tokenizer_name_t::Operator and *it == ",") {
307 ++it;
308 hi_check(*it == tokenizer_name_t::StringLiteral, "Expect string literal after comma ',', got {}.", *it);
309 tmp.push_back(static_cast<std::string>(*it));
310 ++it;
311 }
312
313 hi_check(*it == tokenizer_name_t::Operator and *it == "]", "Expected end of indexing operator ']', got {}.", *it);
314 return tmp;
315}
316
317[[nodiscard]] inline jsonpath_node parse_jsonpath_indexing_operator(auto &it, auto it_end)
318{
319 ++it;
320
321 if (*it == tokenizer_name_t::Operator and *it == "*") {
322 ++it;
323 hi_check(*it == tokenizer_name_t::Operator and *it == "]", "Expected end of indexing operator ']', got {}.", *it);
324 return jsonpath_wildcard{};
325
326 } else if (*it == tokenizer_name_t::Operator and *it == ":") {
327 return parse_jsonpath_slicing_operator(it, it_end, 0);
328
329 } else if (*it == tokenizer_name_t::IntegerLiteral) {
330 hilet first = static_cast<ssize_t>(*it);
331
332 ++it;
333 if (*it == tokenizer_name_t::Operator and *it == ":") {
334 return parse_jsonpath_slicing_operator(it, it_end, first);
335 } else {
336 return parse_jsonpath_integer_indexing_operator(it, it_end, first);
337 }
338
339 } else if (*it == tokenizer_name_t::StringLiteral) {
340 auto first = static_cast<std::string>(*it);
341
342 ++it;
343 return parse_jsonpath_name_indexing_operator(it, it_end, first);
344
345 } else {
346 throw parse_error(std::format("Expected a integer index or child name after indexing operator '[', got token {}.", *it));
347 }
348}
349
350[[nodiscard]] inline jsonpath_node parse_jsonpath_child_operator(auto &it, auto it_end)
351{
352 ++it;
353
354 if (*it == tokenizer_name_t::Operator and *it == "*") {
355 return jsonpath_wildcard{};
356
357 } else if (*it == tokenizer_name_t::Operator and *it == ".") {
358 if (*(it + 1) == tokenizer_name_t::Operator and *(it + 1) == "[") {
359 // When the descend operator '..' is followed by an indexing operator.
360 // Then the full descend operator is consumed here.
361 return jsonpath_descend{};
362
363 } else {
364 // The descend operator '..' is often followed by a name or '*' as-if the
365 // second dot in the descend operator is a child selector. Rewind so that
366 // the parser will use the second dot as a child selector.
367 --it;
368 return jsonpath_descend{};
369 }
370
371 } else if (*it == tokenizer_name_t::Name) {
372 return jsonpath_names{static_cast<std::string>(*it)};
373
374 } else {
375 throw parse_error(std::format("Expected a child name or wildcard, got token {}.", *it));
376 }
377}
378
379class jsonpath {
380public:
382 using value_type = typename container_type::value_type;
383 using iterator = typename container_type::iterator;
384 using const_iterator = typename container_type::const_iterator;
385
386 [[nodiscard]] jsonpath(std::string_view rhs) : _nodes()
387 {
388 auto tokens = parseTokens(rhs);
389 hilet it_end = tokens.cend();
390 for (auto it = tokens.cbegin(); it != it_end; ++it) {
391 if (*it == tokenizer_name_t::Operator and *it == ".") {
392 _nodes.emplace_back(parse_jsonpath_child_operator(it, it_end));
393
394 } else if (*it == tokenizer_name_t::Operator and *it == "[") {
395 _nodes.emplace_back(parse_jsonpath_indexing_operator(it, it_end));
396
397 } else if (*it == tokenizer_name_t::Name and *it == "$") {
398 hi_check(_nodes.empty(), "Root node '$' not at start of path.");
399 _nodes.emplace_back(jsonpath_root{});
400
401 } else if (*it == tokenizer_name_t::Operator and *it == "@") {
402 hi_check(_nodes.empty(), "Current node '@' not at start of path.");
403 _nodes.emplace_back(jsonpath_current{});
404
405 } else if (*it == tokenizer_name_t::Name) {
406 hi_check(_nodes.empty(), "Unexpected child name {}.", *it);
407 _nodes.emplace_back(jsonpath_names{static_cast<std::string>(*it)});
408
409 } else if (*it == tokenizer_name_t::End) {
410 continue;
411
412 } else {
413 throw parse_error(std::format("Unexpected token {}.", *it));
414 }
415 }
416 }
417
418 [[nodiscard]] bool empty() const noexcept
419 {
420 return _nodes.empty();
421 }
422
425 [[nodiscard]] bool is_singular() const noexcept
426 {
427 auto r = true;
428 for (hilet &node : _nodes) {
429 r &= std::visit(
430 [](hilet &node_) {
431 return node_.is_singular();
432 },
433 node);
434 }
435 return r;
436 }
437
438 [[nodiscard]] std::size_t size() const noexcept
439 {
440 return _nodes.size();
441 }
442
443 [[nodiscard]] iterator begin() noexcept
444 {
445 return _nodes.begin();
446 }
447
448 [[nodiscard]] const_iterator begin() const noexcept
449 {
450 return _nodes.begin();
451 }
452
453 [[nodiscard]] const_iterator cbegin() const noexcept
454 {
455 return _nodes.cbegin();
456 }
457
458 [[nodiscard]] iterator end() noexcept
459 {
460 return _nodes.end();
461 }
462
463 [[nodiscard]] const_iterator end() const noexcept
464 {
465 return _nodes.end();
466 }
467
468 [[nodiscard]] const_iterator cend() const noexcept
469 {
470 return _nodes.cend();
471 }
472
473 [[nodiscard]] friend std::string to_string(jsonpath const &path) noexcept
474 {
475 auto r = std::string{};
476 for (hilet &node : path._nodes) {
477 r += std::visit(
478 [](hilet &node_) {
479 return node_.string();
480 },
481 node);
482 }
483 return r;
484 }
485
486private:
488};
489
490} // namespace hi::inline v1
491
492template<typename CharT>
493struct std::formatter<hi::jsonpath, CharT> : std::formatter<char const *, CharT> {
494 auto format(hi::jsonpath const &t, auto &fc)
495 {
496 return std::formatter<std::string, CharT>{}.format(to_string(t), fc);
497 }
498};
#define hi_check(expression, message,...)
Check if the expression is valid, or throw a parse_error.
Definition assert.hpp:110
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
constexpr std::string to_string(std::u32string_view rhs) noexcept
Conversion from UTF-32 to UTF-8.
Definition to_string.hpp:215
DOXYGEN BUG.
Definition algorithm.hpp:13
std::vector< token_t > parseTokens(std::string_view text) noexcept
geometry/margins.hpp
Definition cache.hpp:11
std::ptrdiff_t ssize_t
Signed size/index into an array.
Definition utility.hpp:189
A return value for a generator-function.
Definition generator.hpp:29
Definition jsonpath.hpp:19
Definition jsonpath.hpp:31
Definition jsonpath.hpp:43
Definition jsonpath.hpp:55
Definition jsonpath.hpp:67
Definition jsonpath.hpp:129
Definition jsonpath.hpp:191
std::size_t end(std::size_t size) const noexcept
Get the one-step beyond last offset.
Definition jsonpath.hpp:223
std::size_t begin(std::size_t size) const noexcept
Get the start offset.
Definition jsonpath.hpp:208
Definition jsonpath.hpp:379
bool is_singular() const noexcept
The json-path will result in zero or one match.
Definition jsonpath.hpp:425
T begin(T... args)
T end(T... args)
T front(T... args)
T min(T... args)
T move(T... args)
T push_back(T... args)
T size(T... args)
T to_string(T... args)