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/utility.hpp"
8#include "../parser/parser.hpp"
9#include "../macros.hpp"
10#include <string>
11#include <variant>
12#include <string_view>
13#include <vector>
14#include <limits>
15#include <format>
16#include <coroutine>
17
18hi_export_module(hikogui.codec.jsonpath);
19
20hi_export namespace hi { inline namespace v1 {
21
22hi_export class jsonpath {
23public:
24 struct root {
25 [[nodiscard]] constexpr std::string string() const noexcept
26 {
27 return "$";
28 }
29
30 [[nodiscard]] constexpr bool is_singular() const noexcept
31 {
32 return true;
33 }
34 };
35
36 struct current {
37 [[nodiscard]] constexpr std::string string() const noexcept
38 {
39 return "@";
40 }
41
42 [[nodiscard]] constexpr bool is_singular() const noexcept
43 {
44 return true;
45 }
46 };
47
48 struct wildcard {
49 [[nodiscard]] constexpr std::string string() const noexcept
50 {
51 return "[*]";
52 }
53
54 [[nodiscard]] constexpr bool is_singular() const noexcept
55 {
56 return false;
57 }
58 };
59
60 struct descend {
61 [[nodiscard]] constexpr std::string string() const noexcept
62 {
63 return "..";
64 }
65
66 [[nodiscard]] constexpr bool is_singular() const noexcept
67 {
68 return false;
69 }
70 };
71
72 struct names : public std::vector<std::string> {
73 constexpr names(names const&) noexcept = default;
74 constexpr names(names&&) noexcept = default;
75 constexpr names& operator=(names const&) noexcept = default;
76 constexpr names& operator=(names&&) noexcept = default;
77 constexpr names() noexcept = default;
78
79 constexpr names(std::string other) : names()
80 {
82 }
83
84 [[nodiscard]] constexpr std::string string() const noexcept
85 {
86 auto r = std::string{"["};
87 auto first = true;
88 for (auto const& name : *this) {
89 if (not first) {
90 r += ',';
91 }
92
93 r += '\'';
94 r += name;
95 r += '\'';
96 first = false;
97 }
98 r += ']';
99 return r;
100 }
101
102 [[nodiscard]] constexpr bool is_singular() const noexcept
103 {
104 return size() == 1;
105 }
106 };
107
108 struct indices : public std::vector<ptrdiff_t> {
109 constexpr indices(indices const&) noexcept = default;
110 constexpr indices(indices&&) noexcept = default;
111 constexpr indices& operator=(indices const&) noexcept = default;
112 constexpr indices& operator=(indices&&) noexcept = default;
113 constexpr indices() noexcept = default;
114
115 [[nodiscard]] constexpr std::string string() const noexcept
116 {
117 auto r = std::string{"["};
118 auto first = true;
119 for (auto const index : *this) {
120 if (not first) {
121 r += ',';
122 }
123
124 r += hi::to_string(index);
125 first = false;
126 }
127 r += ']';
128 return r;
129 }
130
131 [[nodiscard]] generator<std::size_t> filter(std::size_t size) const noexcept
132 {
133 auto const size_ = narrow_cast<ptrdiff_t>(size);
134
135 for (auto const index : *this) {
136 auto const index_ = index >= 0 ? index : size_ + index;
137 if (index_ >= 0 and index_ < size_) {
138 co_yield narrow_cast<std::size_t>(index_);
139 }
140 }
141 }
142
143 [[nodiscard]] constexpr bool is_singular() const noexcept
144 {
145 return size() == 1;
146 }
147 };
148
149 struct slice {
150 ptrdiff_t first;
151 ptrdiff_t last;
152 ptrdiff_t step;
153
154 constexpr slice(slice const&) noexcept = default;
155 constexpr slice(slice&&) noexcept = default;
156 constexpr slice& operator=(slice const&) noexcept = default;
157 constexpr slice& operator=(slice&&) noexcept = default;
158
159 constexpr slice(ptrdiff_t first, ptrdiff_t last, ptrdiff_t step) noexcept : first(first), last(last), step(step) {}
160
161 [[nodiscard]] constexpr bool last_is_empty() const noexcept
162 {
164 }
165
171 [[nodiscard]] constexpr std::size_t begin(std::size_t size) const noexcept
172 {
173 auto const size_ = narrow_cast<ptrdiff_t>(size);
174 auto const begin = first >= 0 ? first : size_ + first;
175 return narrow_cast<std::size_t>(std::clamp(begin, 0_z, size_));
176 }
177
186 [[nodiscard]] constexpr std::size_t end(std::size_t size) const noexcept
187 {
188 if (last_is_empty()) {
189 return size;
190
191 } else {
192 auto const size_ = narrow_cast<ptrdiff_t>(size);
193 auto const end = last >= 0 ? last : size_ + last;
194 auto const last_ = std::clamp(end, 0_z, size_);
195
196 auto const first_ = begin(size);
197 auto const distance = last_ - first_;
198 auto const steps = distance / step;
199 return narrow_cast<std::size_t>(first_ + steps * step);
200 }
201 }
202
203 [[nodiscard]] constexpr std::string string() const noexcept
204 {
205 if (last_is_empty()) {
206 return std::format("[{}:e:{}]", first, step);
207 } else {
208 return std::format("[{}:{}:{}]", first, last, step);
209 }
210 }
211
212 [[nodiscard]] constexpr bool is_singular() const noexcept
213 {
214 return false;
215 }
216 };
217
218 using node = std::variant<root, current, wildcard, descend, names, indices, slice>;
219 using container_type = std::vector<node>;
220 using value_type = typename container_type::value_type;
221 using iterator = typename container_type::iterator;
222 using const_iterator = typename container_type::const_iterator;
223
224 template<std::input_iterator It, std::sentinel_for<It> ItEnd>
225 [[nodiscard]] constexpr jsonpath(It it, ItEnd last) : _nodes()
226 {
227 auto lexer_it = lexer<lexer_config::json_style()>.parse(it, last);
228 auto token_it = make_lookahead_iterator<4>(lexer_it);
229
230 while (token_it != std::default_sentinel) {
231 if (*token_it == '.') {
232 _nodes.emplace_back(parse_child_operator(++token_it, std::default_sentinel));
233
234 } else if (*token_it == '[') {
235 _nodes.emplace_back(parse_indexing_operator(++token_it, std::default_sentinel));
236
237 } else if (*token_it == '$') {
238 hi_check(_nodes.empty(), "Root node '$' not at start of path.");
239 _nodes.emplace_back(root{});
240 ++token_it;
241
242 } else if (*token_it == '@') {
243 hi_check(_nodes.empty(), "Current node '@' not at start of path.");
244 _nodes.emplace_back(current{});
245 ++token_it;
246
247 } else if (*token_it == token::id) {
248 hi_check(_nodes.empty(), "Unexpected child name {}.", *token_it);
249 _nodes.emplace_back(names{static_cast<std::string>(*token_it)});
250 ++token_it;
251
252 } else {
253 throw parse_error(std::format("Unexpected token {}.", *token_it));
254 }
255 }
256 }
257
258 [[nodiscard]] constexpr jsonpath(std::string_view rhs) : jsonpath(rhs.begin(), rhs.end()) {}
259
260 [[nodiscard]] constexpr bool empty() const noexcept
261 {
262 return _nodes.empty();
263 }
264
267 [[nodiscard]] constexpr bool is_singular() const noexcept
268 {
269 auto r = true;
270 for (auto const& node : _nodes) {
271 r &= std::visit(
272 [](auto const& node_) {
273 return node_.is_singular();
274 },
275 node);
276 }
277 return r;
278 }
279
280 [[nodiscard]] constexpr std::size_t size() const noexcept
281 {
282 return _nodes.size();
283 }
284
285 [[nodiscard]] constexpr iterator begin() noexcept
286 {
287 return _nodes.begin();
288 }
289
290 [[nodiscard]] constexpr const_iterator begin() const noexcept
291 {
292 return _nodes.begin();
293 }
294
295 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
296 {
297 return _nodes.cbegin();
298 }
299
300 [[nodiscard]] constexpr iterator end() noexcept
301 {
302 return _nodes.end();
303 }
304
305 [[nodiscard]] constexpr const_iterator end() const noexcept
306 {
307 return _nodes.end();
308 }
309
310 [[nodiscard]] constexpr const_iterator cend() const noexcept
311 {
312 return _nodes.cend();
313 }
314
315 [[nodiscard]] constexpr friend std::string to_string(jsonpath const& path) noexcept
316 {
317 auto r = std::string{};
318 for (auto const& node : path._nodes) {
319 r += std::visit(
320 [](auto const& node_) {
321 return node_.string();
322 },
323 node);
324 }
325 return r;
326 }
327
328private:
329 std::vector<node> _nodes;
330
331 template<std::input_iterator It, std::sentinel_for<It> ItEnd>
332 [[nodiscard]] constexpr static node parse_slicing_operator(It& it, ItEnd last)
333 {
334 auto start = 0_z;
336 auto step = 1_z;
337
338 if (it.size() >= 2 and it[0] == '-' and it[1] == token::integer) {
339 auto tmp = static_cast<size_t>(it[1]);
340 hi_check(can_narrow_cast<ptrdiff_t>(tmp), "Start-index out of range {}", tmp);
341 start = -narrow_cast<ptrdiff_t>(tmp);
342 it += 2;
343
344 } else if (*it == token::integer) {
345 auto tmp = static_cast<size_t>(*it);
346 hi_check(can_narrow_cast<ptrdiff_t>(tmp), "Start-index out of range {}", tmp);
347 start = narrow_cast<ptrdiff_t>(tmp);
348 ++it;
349
350 } else if (*it == ':') {
351 // Use the default.
352
353 } else {
354 throw parse_error(std::format("Unexpected token while parsing start-index of the slicing operator, got {}", *it));
355 }
356
357 hi_check(it != last, "Unexpected end-of-text after the start-index of the slicing operator.");
358 hi_check(*it == ':', "Expecting ':' adter the start-index of the slicing operator, got {}", *it);
359 ++it;
360
361 hi_check(it != last, "Unexpected end-of-text while parsing the end-index of the slicing operator.");
362 if (*it == ']') {
363 ++it;
364 return slice{start, end, step};
365
366 } else if (it.size() >= 2 and it[0] == '-' and it[1] == token::integer) {
367 auto tmp = static_cast<size_t>(it[1]);
368 hi_check(can_narrow_cast<ptrdiff_t>(tmp), "End-index out of range {}", tmp);
369 end = -narrow_cast<ptrdiff_t>(tmp);
370 it += 2;
371
372 } else if (*it == token::integer) {
373 auto tmp = static_cast<size_t>(*it);
374 hi_check(can_narrow_cast<ptrdiff_t>(tmp), "End-index out of range {}", tmp);
375 end = narrow_cast<ptrdiff_t>(tmp);
376 ++it;
377
378 } else if (*it == ':' or *it == ']') {
379 // Use the default.
380
381 } else {
382 throw parse_error(std::format("Unexpected token while parsing the end-index of the slicing operator, got {}", *it));
383 }
384
385 hi_check(it != last, "Unexpected end-of-text after the end-index of the slicing operator.");
386 if (*it == ']') {
387 ++it;
388 return slice{start, end, step};
389
390 } else if (it.size() >= 2 and it[0] == '-' and it[1] == token::integer) {
391 auto tmp = static_cast<size_t>(it[1]);
392 hi_check(can_narrow_cast<ptrdiff_t>(tmp), "Step-value out of range {}", tmp);
393 step = -narrow_cast<ptrdiff_t>(tmp);
394 it += 2;
395
396 } else if (*it == token::integer) {
397 auto tmp = static_cast<size_t>(*it);
398 hi_check(can_narrow_cast<ptrdiff_t>(tmp), "Step-value out of range {}", tmp);
399 step = narrow_cast<ptrdiff_t>(tmp);
400 ++it;
401
402 } else {
403 throw parse_error(std::format("Unexpected token while parsing step-value of the slicing operator, got {}", *it));
404 }
405
406 hi_check(it != last, "Unexpected end-of-text after the step-value of the slicing operator.");
407 hi_check(*it == ']', "Expecting '] after step-value of the slicing operator, got {}", *it);
408 ++it;
409 return slice{start, end, step};
410 }
411
412 template<std::input_iterator It, std::sentinel_for<It> ItEnd>
413 [[nodiscard]] constexpr static node parse_integer_indexing_operator(It& it, ItEnd last)
414 {
415 auto r = indices();
416
417 while (true) {
418 if (it.size() >= 2 and it[0] == '-' and it[1] == token::integer) {
419 auto tmp = static_cast<size_t>(it[1]);
420 hi_check(can_narrow_cast<ptrdiff_t>(tmp), "Index out of range {}", tmp);
421 r.push_back(-narrow_cast<ptrdiff_t>(tmp));
422 it += 2;
423
424 } else if (*it == token::integer) {
425 auto tmp = static_cast<size_t>(*it);
426 hi_check(can_narrow_cast<ptrdiff_t>(tmp), "Index out of range {}", tmp);
427 r.push_back(narrow_cast<ptrdiff_t>(tmp));
428 ++it;
429
430 } else {
431 throw parse_error(std::format("Expected an integer-index, got {}", *it));
432 }
433
434 if (it == last) {
435 throw parse_error("Unexpected end-of-text while parsing the index operator '['.");
436
437 } else if (*it == ']') {
438 ++it;
439 return r;
440
441 } else if (*it == ',') {
442 ++it;
443 continue;
444
445 } else {
446 throw parse_error(std::format("Unexpected token after a integer-index: {}.", *it));
447 }
448 }
449 }
450
451 template<std::input_iterator It, std::sentinel_for<It> ItEnd>
452 [[nodiscard]] constexpr static node parse_name_indexing_operator(It& it, ItEnd last)
453 {
454 auto r = names();
455
456 while (true) {
457 if (*it != token::id and *it != token::sstr and *it != token::dstr) {
458 throw parse_error(std::format("Expected a name-index, got {}", *it));
459 }
460
461 r.push_back(static_cast<std::string>(*it++));
462
463 if (it == last) {
464 throw parse_error("Unexpected end-of-text while parsing the index operator '['.");
465
466 } else if (*it == ']') {
467 ++it;
468 return r;
469
470 } else if (*it == ',') {
471 ++it;
472 continue;
473
474 } else {
475 throw parse_error(std::format("Unexpected token after a name-index: {}.", *it));
476 }
477 }
478 }
479
480 template<std::input_iterator It, std::sentinel_for<It> ItEnd>
481 [[nodiscard]] constexpr static node parse_indexing_operator(It& it, ItEnd last)
482 {
483 hi_check(it != last, "Unexpected end-of-text at index operator token '['.");
484
485 if (*it == '*') {
486 hi_check(it.size() >= 2 and it[1] == ']', "Expected end of wildcast-indexing operator '[*', got {}.", it[1]);
487 it += 2;
488 return wildcard{};
489
490 } else if (
491 *it == ':' or (it.size() >= 2 and it[0] == token::integer and it[1] == ':') or
492 (it.size() >= 3 and it[0] == '-' and it[1] == token::integer and it[2] == ':')) {
493 return parse_slicing_operator(it, last);
494
495 } else if (*it == token::integer or (it.size() >= 2 and it[0] == '-' and it[1] == token::integer)) {
496 return parse_integer_indexing_operator(it, last);
497
498 } else if (*it == token::id or *it == token::sstr or *it == token::dstr) {
499 return parse_name_indexing_operator(it, last);
500
501 } else {
502 throw parse_error(
503 std::format("Expected a integer index or name-index after indexing operator '[', got token {}.", *it));
504 }
505 }
506
507 template<std::input_iterator It, std::sentinel_for<It> ItEnd>
508 [[nodiscard]] constexpr static node parse_child_operator(It& it, ItEnd last)
509 {
510 hi_check(it != last, "Unexpected end-of-text at child operator token '.'.");
511
512 if (*it == '*') {
513 ++it;
514 return wildcard{};
515
516 } else if (*it == '.') {
517 if (it.size() >= 2 and it[1] == '[') {
518 // When the descend operator '..' is followed by an indexing operator.
519 // Then the full descend operator is consumed here.
520 ++it;
521 return descend{};
522
523 } else {
524 // The descend operator '..' is often followed by a name or '*' as-if the
525 // second dot in the descend operator is a child selector. Then don't consume
526 // the second dot and treat it as-if it is a child operator.
527 return descend{};
528 }
529
530 } else if (*it == token::id) {
531 auto name = static_cast<std::string>(*it);
532 ++it;
533 return names{std::move(name)};
534
535 } else {
536 throw parse_error(std::format("Expected a child name or wildcard, got token {}.", *it));
537 }
538 }
539};
540
541}} // namespace hi::v1
542
543// XXX #617 MSVC bug does not handle partial specialization in modules.
544hi_export template<>
545struct std::formatter<hi::jsonpath, char> : std::formatter<char const *, char> {
546 auto format(hi::jsonpath const& t, auto& fc) const
547 {
548 return std::formatter<std::string, char>{}.format(to_string(t), fc);
549 }
550};
@ current
Continue from the current position.
@ other
The gui_event does not have associated data.
The HikoGUI namespace.
Definition array_generic.hpp:20
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
Definition jsonpath.hpp:22
constexpr bool is_singular() const noexcept
The json-path will result in zero or one match.
Definition jsonpath.hpp:267
Definition jsonpath.hpp:24
Definition jsonpath.hpp:36
Definition jsonpath.hpp:48
Definition jsonpath.hpp:60
Definition jsonpath.hpp:72
Definition jsonpath.hpp:108
Definition jsonpath.hpp:149
constexpr std::size_t begin(std::size_t size) const noexcept
Get the start offset.
Definition jsonpath.hpp:171
constexpr std::size_t end(std::size_t size) const noexcept
Get the one-step beyond last offset.
Definition jsonpath.hpp:186
T begin(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T min(T... args)
T move(T... args)
std::string push_back(std::string ... args)
std::string size(std::string ... args)
T to_string(T... args)