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