HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
jsonpath.hpp
1// Copyright Take Vos 2021.
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 "required.hpp"
8#include "tokenizer.hpp"
9#include "coroutine.hpp"
10#include "check.hpp"
11#include <string>
12#include <variant>
13#include <string_view>
14#include <vector>
15#include <limits>
16
17namespace tt {
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 constexpr jsonpath_names(jsonpath_names const &) noexcept = default;
71 constexpr jsonpath_names(jsonpath_names &&) noexcept = default;
72 constexpr jsonpath_names &operator=(jsonpath_names const &) noexcept = default;
73 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 (ttlet &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]] 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 std::begin(names);
111 }
112
113 [[nodiscard]] auto end() const noexcept
114 {
115 return std::end(names);
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 std::size(names) == 1;
126 }
127};
128
130 std::vector<ssize_t> indices;
131
132 constexpr jsonpath_indices(jsonpath_indices const &) noexcept = default;
133 constexpr jsonpath_indices(jsonpath_indices &&) noexcept = default;
134 constexpr jsonpath_indices &operator=(jsonpath_indices const &) noexcept = default;
135 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 (ttlet index : indices) {
147 if (not first) {
148 r += ',';
149 }
150
151 r += tt::to_string(index);
152 first = false;
153 }
154 r += ']';
155 return r;
156 }
157
158 [[nodiscard]] generator<size_t> filter(size_t size) const noexcept
159 {
160 ttlet size_ = static_cast<ssize_t>(size);
161
162 for (ttlet index : indices) {
163 ttlet index_ = index >= 0 ? index : size_ + index;
164 if (index_ >= 0 and index_ < size_) {
165 co_yield static_cast<size_t>(index_);
166 }
167 }
168 }
169
170 [[nodiscard]] 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 std::size(indices) == 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]] size_t begin(size_t size) const noexcept
209 {
210 ttlet size_ = static_cast<ssize_t>(size);
211 ttlet begin = first >= 0 ? first : size_ + first;
212 return static_cast<size_t>(std::clamp(begin, 0_z, size_));
213 }
214
223 [[nodiscard]] size_t end(size_t size) const noexcept
224 {
225 ttlet size_ = static_cast<ssize_t>(size);
226 ttlet 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 ttlet first_ = begin(size);
234 ttlet distance = last_ - first_;
235 ttlet steps = distance / step;
236 return static_cast<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 tt_parse_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 tt_parse_check(*it == tokenizer_name_t::Operator and *it == "]", "Expected end of slicing operator ']', got {}.", *it);
282
283 tt_parse_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 tt_parse_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 tt_parse_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 tt_parse_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 tt_parse_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 tt_parse_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 auto 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("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("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 ttlet it_end = std::cend(tokens);
390 for (auto it = std::cbegin(tokens); 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 tt_parse_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 tt_parse_check(_nodes.empty(), "Current node '@' not at start of path.");
404
405 } else if (*it == tokenizer_name_t::Name) {
406 tt_parse_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("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 (ttlet &node : _nodes) {
429 r &= std::visit(
430 [](ttlet &node_) {
431 return node_.is_singular();
432 },
433 node);
434 }
435 return r;
436 }
437
438 [[nodiscard]] 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 (ttlet &node : path._nodes) {
477 r += std::visit(
478 [](ttlet &node_) {
479 return node_.string();
480 },
481 node);
482 }
483 return r;
484 }
485
486private:
488};
489
490} // namespace tt
491
492namespace std {
493
494template<typename CharT>
495struct formatter<tt::jsonpath, CharT> : formatter<char const *, CharT> {
496 auto format(tt::jsonpath const &t, auto &fc)
497 {
498 return formatter<std::string, CharT>{}.format(to_string(t), fc);
499 }
500};
501
502} // namespace std
STL namespace.
A return value for a generator-function.
Definition coroutine.hpp:25
Exception thrown during parsing on an error.
Definition exception.hpp:26
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
size_t begin(size_t size) const noexcept
Get the start offset.
Definition jsonpath.hpp:208
size_t end(size_t size) const noexcept
Get the one-step beyond last offset.
Definition jsonpath.hpp:223
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 emplace_back(T... args)
T empty(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)