JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
ast.hpp
Go to the documentation of this file.
1/// \file jsonv/ast.hpp
2/// Utilities for directly dealing with a JSON AST. For most cases, it is more convenient to use \c jsonv::value.
3///
4/// Copyright (c) 2020 by Travis Gockel. All rights reserved.
5///
6/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
7/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
8/// version.
9///
10/// \author Travis Gockel (travis@gockelhut.com)
11#pragma once
12
13#include <jsonv/config.hpp>
14#include <jsonv/kind.hpp>
15#include <expected>
16#include <string_view>
17
18#include <cstdint>
19#include <initializer_list>
20#include <iosfwd>
21#include <utility>
22#include <variant>
23
24namespace jsonv
25{
26
27class value;
28
29namespace detail
30{
31
33inline std::string_view string_from_token(std::string_view token, std::false_type is_escaped JSONV_UNUSED)
34{
35 return std::string_view(token.data() + 1, token.size() - 2U);
36}
37
39std::string string_from_token(std::string_view token, std::true_type is_escaped);
40
41}
42
43/// \ingroup Value
44/// \{
45
46/// Marker type for an encountered token type.
47///
48/// - \c document_end
49/// The end of a document.
50/// - \c document_start
51/// The beginning of a document.
52/// - \c object_begin
53/// The beginning of an \c kind::object (`{`).
54/// - \c object_end
55/// The end of an \c kind::object (`}`).
56/// - \c array_begin
57/// The beginning of an \c kind::array (`[`).
58/// - \c array_end
59/// The end of an \c kind::array (`]`).
60/// - \c string_canonical
61/// A \c kind::string whose JSON-encoded format matches the canonical UTF-8 representation. There is no need to
62/// translate JSON escape sequences to extract a \c std::string value, so accessing the raw text is safe.
63/// - \c string_escaped
64/// A \c kind::string whose JSON-encoded format contains escape sequences, so it must be translated to extract a
65/// \c std::string value.
66/// - \c key_canonical
67/// The \c ast_node_type::string_canonical key of an \c kind::object.
68/// - \c key_escaped
69/// The \c ast_node_type::string_escaped key of an \c kind::object.
70/// - \c literal_true
71/// The \c kind::boolean literal \c true.
72/// - \c literal_false
73/// The \c kind::boolean literal \c false.
74/// - \c literal_null
75/// The \c kind::null literal \c null.
76/// - \c integer
77/// An \c kind::integer value. No decimals or exponent symbols were encountered during parsing. Note that integers are
78/// \e not bounds-checked by the AST -- values outside of \c std::int64_t are still \c integer values.
79/// - \c decimal
80/// A \c kind::decimal value.
81/// - \c error
82/// An AST parsing error.
83///
84/// \see parse_index
85/// \see ast_node
86enum class ast_node_type : std::uint8_t
87{
88 document_end = 0,
89 document_start = 1,
90 object_begin = 2,
91 object_end = 3,
92 array_begin = 4,
93 array_end = 5,
94 string_canonical = 6,
95 string_escaped = 7,
96 key_canonical = 8,
97 key_escaped = 9,
98 literal_true = 10,
99 literal_false = 11,
100 literal_null = 12,
101 integer = 13,
102 decimal = 14,
103 error = 15,
104};
105
106/// \{
107/// +--------------------+--------+
108/// | `ast_node_type` | Output |
109/// +--------------------+--------+
110/// | `document_start` | `^` |
111/// | `document_end` | `$` |
112/// | `object_begin` | `{` |
113/// | `object_end` | `}` |
114/// | `array_begin` | `[` |
115/// | `array_end` | `]` |
116/// | `string_canonical` | `s` |
117/// | `string_escaped` | `S` |
118/// | `key_canonical` | `k` |
119/// | `key_escaped` | `K` |
120/// | `literal_true` | `t` |
121/// | `literal_false` | `f` |
122/// | `literal_null` | `n` |
123/// | `integer` | `i` |
124/// | `decimal` | `d` |
125/// | `error` | `!` |
126/// +--------------------+--------+
127JSONV_PUBLIC std::ostream& operator<<(std::ostream&, const ast_node_type& type);
128JSONV_NODISCARD JSONV_PUBLIC std::string to_string(const ast_node_type& type);
129/// \}
130
131/// Error code encountered while building the AST.
132enum class ast_error : std::uint64_t
133{
134 none = 0,
135 expected_document,
136 expected_string,
137 expected_key_delimiter,
138 unexpected_token,
139 unexpected_comma,
140 unexpected_eof,
141 expected_eof,
142 depth_exceeded,
143 extra_close,
144 mismatched_close,
145 /// A `]` or `}` arrived where the structure still owed a value -- after a `,` in either kind of structure, or
146 /// after the `:` of an object member, as in `{"a":}`.
148 invalid_literal,
149 invalid_number,
150 invalid_string,
151 invalid_comment,
152 internal,
153};
154
155/// \{
156/// Get a description of the error \a code.
157JSONV_PUBLIC std::ostream& operator<<(std::ostream&, const ast_error& code);
158JSONV_NODISCARD JSONV_PUBLIC std::string to_string(const ast_error& code);
159/// \}
160
161/// Represents an entry in a JSON AST.
162///
163/// \see parse_index
165{
166public:
167 template <typename TSelf, ast_node_type KIndexToken>
168 class base
169 {
170 public:
171 /// Get the \c ast_node_type type.
173 static constexpr ast_node_type type()
174 {
175 return KIndexToken;
176 }
177
178 /// \see ast_node::token_raw
180 std::string_view token_raw() const
181 {
182 return std::string_view(_token_begin, static_cast<const TSelf&>(*this).token_size());
183 }
184
185 /// Allow implicit conversion to the more generic \c ast_node.
187 operator ast_node() const;
188
189 protected:
190 explicit constexpr base(const char* token_begin) :
191 _token_begin(token_begin)
192 { }
193
194 private:
195 const char* _token_begin;
196 };
197
198 template <typename TSelf, ast_node_type KIndexToken, std::size_t KTokenSize>
199 class basic_fixed_size_token : public base<TSelf, KIndexToken>
200 {
201 public:
202 explicit constexpr basic_fixed_size_token(const char* token_begin) :
204 { }
205
207 constexpr std::size_t token_size() const
208 {
209 return KTokenSize;
210 }
211 };
212
213 template <typename TSelf, ast_node_type KIndexToken>
214 class basic_dynamic_size_token : public base<TSelf, KIndexToken>
215 {
216 public:
217 explicit constexpr basic_dynamic_size_token(const char* token_begin, std::size_t token_size) :
219 _token_size(token_size)
220 { }
221
223 constexpr std::size_t token_size() const
224 {
225 return _token_size;
226 }
227
228 private:
229 std::size_t _token_size;
230 };
231
232 /// The start of a document.
233 class document_start final : public basic_fixed_size_token<document_start, ast_node_type::document_start, 0U>
234 {
235 public:
236 using basic_fixed_size_token<document_start, ast_node_type::document_start, 0U>::basic_fixed_size_token;
237 };
238
239 /// The end of a document.
240 class document_end final : public basic_fixed_size_token<document_end, ast_node_type::document_end, 0U>
241 {
242 public:
243 using basic_fixed_size_token<document_end, ast_node_type::document_end, 0U>::basic_fixed_size_token;
244 };
245
246 /// The beginning of an \c kind::object (`{`).
247 class object_begin final : public basic_fixed_size_token<object_begin, ast_node_type::object_begin, 1U>
248 {
249 public:
250 explicit constexpr object_begin(const char* token_begin, std::size_t element_count) :
252 _element_count(element_count)
253 { }
254
255 /// Get the number of elements in the object this token starts. This is useful for reserving memory.
257 constexpr std::size_t element_count() const
258 {
259 return _element_count;
260 }
261
262 private:
263 std::size_t _element_count;
264 };
265
266 /// The end of an \c kind::object (`}`).
267 class object_end final : public basic_fixed_size_token<object_end, ast_node_type::object_end, 1U>
268 {
269 public:
270 using basic_fixed_size_token<object_end, ast_node_type::object_end, 1U>::basic_fixed_size_token;
271 };
272
273 /// The beginning of an \c kind::array (`[`).
274 class array_begin final : public basic_fixed_size_token<array_begin, ast_node_type::array_begin, 1U>
275 {
276 public:
277 explicit constexpr array_begin(const char* token_begin, std::size_t element_count) :
279 _element_count(element_count)
280 { }
281
282 /// Get the number of elements in the array this token starts. This is useful for reserving memory.
284 constexpr std::size_t element_count() const
285 {
286 return _element_count;
287 }
288
289 private:
290 std::size_t _element_count;
291 };
292
293 /// The end of an \c kind::array (`]`).
294 class array_end final : public basic_fixed_size_token<array_end, ast_node_type::array_end, 1U>
295 {
296 public:
297 using basic_fixed_size_token<array_end, ast_node_type::array_end, 1U>::basic_fixed_size_token;
298 };
299
300 template <typename TSelf, ast_node_type KIndexToken, bool KEscaped>
301 class basic_string_token : public basic_dynamic_size_token<TSelf, KIndexToken>
302 {
303 public:
304 /// The return type of \c value is based on if the string is \c canonical or \c escaped. Strings in canonical
305 /// representation can be returned directly from the text through a \c std::string_view.
306 using value_type = std::conditional_t<KEscaped, std::string, std::string_view>;
307
308 public:
309 explicit constexpr basic_string_token(const char* token_begin, std::size_t token_size) :
311 _token_size(token_size)
312 { }
313
314 /// Was the source JSON for this string encoded in the canonical UTF-8 representation? If this is \c true, there
315 /// is no need to translate JSON escape sequences to extract a \c std::string value. This is the opposite of
316 /// \c escaped.
318 constexpr bool canonical() const noexcept
319 {
320 return !KEscaped;
321 }
322
323 /// Did the source JSON for this string contain escape sequences? If this is \c true, JSON escape sequences must
324 /// be translated into their canonical UTF-8 representation on extraction. This is the opposite of \c canonical.
326 constexpr bool escaped() const noexcept
327 {
328 return KEscaped;
329 }
330
332 constexpr std::size_t token_size() const
333 {
334 return _token_size;
335 }
336
338 value_type value() const
339 {
340 return detail::string_from_token(this->token_raw(), std::integral_constant<bool, KEscaped>());
341 }
342
343 private:
344 std::size_t _token_size;
345 };
346
347 class string_canonical final : public basic_string_token<string_canonical, ast_node_type::string_canonical, false>
348 {
349 public:
350 using basic_string_token<string_canonical, ast_node_type::string_canonical, false>::basic_string_token;
351 };
352
353 class string_escaped final : public basic_string_token<string_escaped, ast_node_type::string_escaped, true>
354 {
355 public:
356 using basic_string_token<string_escaped, ast_node_type::string_escaped, true>::basic_string_token;
357 };
358
359 class key_canonical final : public basic_string_token<key_canonical, ast_node_type::key_canonical, false>
360 {
361 public:
362 using basic_string_token<key_canonical, ast_node_type::key_canonical, false>::basic_string_token;
363 };
364
365 class key_escaped final : public basic_string_token<key_escaped, ast_node_type::key_escaped, true>
366 {
367 public:
368 using basic_string_token<key_escaped, ast_node_type::key_escaped, true>::basic_string_token;
369 };
370
371 class literal_true final : public basic_fixed_size_token<literal_true, ast_node_type::literal_true, 4U>
372 {
373 public:
374 using basic_fixed_size_token<literal_true, ast_node_type::literal_true, 4U>::basic_fixed_size_token;
375
377 bool value() const noexcept
378 {
379 return true;
380 }
381 };
382
383 class literal_false final : public basic_fixed_size_token<literal_false, ast_node_type::literal_false, 5U>
384 {
385 public:
386 using basic_fixed_size_token<literal_false, ast_node_type::literal_false, 5U>::basic_fixed_size_token;
387
389 bool value() const noexcept
390 {
391 return false;
392 }
393 };
394
395 class literal_null final : public basic_fixed_size_token<literal_null, ast_node_type::literal_null, 4U>
396 {
397 public:
398 using basic_fixed_size_token<literal_null, ast_node_type::literal_null, 4U>::basic_fixed_size_token;
399
401 jsonv::value value() const;
402 };
403
404 class integer final : public basic_dynamic_size_token<integer, ast_node_type::integer>
405 {
406 public:
407 using basic_dynamic_size_token<integer, ast_node_type::integer>::basic_dynamic_size_token;
408
410 std::int64_t value() const;
411 };
412
413 class decimal final : public basic_dynamic_size_token<decimal, ast_node_type::decimal>
414 {
415 public:
416 using basic_dynamic_size_token<decimal, ast_node_type::decimal>::basic_dynamic_size_token;
417
419 double value() const;
420 };
421
422 class error final : public basic_dynamic_size_token<error, ast_node_type::error>
423 {
424 public:
425 explicit constexpr error(const char* token_begin, std::size_t token_size, ast_error error_code) :
427 _error_code(error_code)
428 { }
429
431 constexpr ast_error error_code() const
432 {
433 return _error_code;
434 }
435
436 private:
437 ast_error _error_code;
438 };
439
440 using storage_type = std::variant<document_end,
445 array_end,
453 integer,
454 decimal,
455 error
456 >;
457
458public:
459 ast_node(const storage_type& value) :
460 _impl(value)
461 { }
462
463 template <typename T, typename... TArgs>
464 explicit ast_node(std::in_place_type_t<T> type, TArgs&&... args) :
465 _impl(type, std::forward<TArgs>(args)...)
466 { }
467
468 /// Get the \c std::variant that backs this type.
469 ///
470 /// \see visit
472 const storage_type& storage() const
473 {
474 return _impl;
475 }
476
477 /// Convenience function for calling \c std::visit on the underlying \c storage of this node.
478 template <typename FVisitor>
479 auto visit(FVisitor&& visitor) const
480 {
481 return std::visit(std::forward<FVisitor>(visitor), _impl);
482 }
483
484 /// Convenience function for calling \c std::visit on a key (see \c as_key).
485 template <typename FVisitor>
487 {
488 return std::visit(std::forward<FVisitor>(visitor), as_key());
489 }
490
491 /// Get the \c ast_node_type that tells the underlying type of this instance.
494 {
495 return visit([](const auto& x) { return x.type(); });
496 }
497
498 /// \{
499 /// Check that this node has the given \a type or is one of the expected \a types.
500 ///
501 /// A mismatch is returned rather than thrown, since which node types are acceptable is a question about the JSON
502 /// source and not about the correctness of the program asking. The caller decides whether it is an error.
503 ///
504 /// \returns Nothing if this instance has \a type or one of the given \a types; otherwise the \c ast_node_type this
505 /// instance actually has.
506 /// \throws std::invalid_argument if \a types is empty. Unlike a type mismatch, expecting nothing at all is a
507 /// mistake in the calling code.
509 JSONV_PUBLIC std::expected<void, ast_node_type> expect(ast_node_type type) const;
511 JSONV_PUBLIC std::expected<void, ast_node_type> expect(std::initializer_list<ast_node_type> types) const;
512 /// \}
513
514 /// Get a view of the raw token. For example, \c "true", \c "{", or \c "1234". Note that this includes the complete
515 /// source, so string types such as \c ast_node_type::string_canonical include the opening and closing quotations.
517 std::string_view token_raw() const
518 {
519 return visit([](const auto& x) { return x.token_raw(); });
520 }
521
522 /// Get the underlying data of this node as \c T.
523 ///
524 /// \throws std::bad_variant_access if the requested \c T is different from the \c type of this instance.
525 template <typename T>
527 const T& as() const
528 {
529 return std::get<T>(_impl);
530 }
531
532 /// Get the underlying data of this node as one of the key types: \c key_canonical or \c key_escaped.
533 ///
534 /// \throw std::bad_variant_access if the \c type of this instance is neither \c key_canonical nor \c key_escaped.
536 std::variant<key_canonical, key_escaped> as_key() const
537 {
538 if (type() == ast_node_type::key_canonical)
539 return as<key_canonical>();
540 else
541 return as<key_escaped>();
542 }
543
544private:
545 storage_type _impl;
546};
547
548template <typename TSelf, ast_node_type KIndexToken>
550{
551 return ast_node(ast_node::storage_type(static_cast<const TSelf&>(*this)));
552}
553
554/// \}
555
556}
ast_error
Error code encountered while building the AST.
Definition ast.hpp:133
@ close_after_comma
A ] or } arrived where the structure still owed a value – after a , in either kind of structure,...
The beginning of an kind::array ([).
Definition ast.hpp:275
constexpr std::size_t element_count() const
Get the number of elements in the array this token starts. This is useful for reserving memory.
Definition ast.hpp:284
The end of an kind::array (]).
Definition ast.hpp:295
static constexpr ast_node_type type()
Get the ast_node_type type.
Definition ast.hpp:173
std::string_view token_raw() const
Definition ast.hpp:180
constexpr bool escaped() const noexcept
Did the source JSON for this string contain escape sequences? If this is true, JSON escape sequences ...
Definition ast.hpp:326
constexpr bool canonical() const noexcept
Was the source JSON for this string encoded in the canonical UTF-8 representation?...
Definition ast.hpp:318
std::conditional_t< KEscaped, std::string, std::string_view > value_type
The return type of value is based on if the string is canonical or escaped.
Definition ast.hpp:306
The end of a document.
Definition ast.hpp:241
The start of a document.
Definition ast.hpp:234
The beginning of an kind::object ({).
Definition ast.hpp:248
constexpr std::size_t element_count() const
Get the number of elements in the object this token starts. This is useful for reserving memory.
Definition ast.hpp:257
The end of an kind::object (}).
Definition ast.hpp:268
Represents an entry in a JSON AST.
Definition ast.hpp:165
std::variant< key_canonical, key_escaped > as_key() const
Get the underlying data of this node as one of the key types: key_canonical or key_escaped.
Definition ast.hpp:536
JSONV_PUBLIC std::expected< void, ast_node_type > expect(ast_node_type type) const
ast_node_type type() const
Get the ast_node_type that tells the underlying type of this instance.
Definition ast.hpp:493
auto visit_key(FVisitor &&visitor) const
Convenience function for calling std::visit on a key (see as_key).
Definition ast.hpp:486
auto visit(FVisitor &&visitor) const
Convenience function for calling std::visit on the underlying storage of this node.
Definition ast.hpp:479
std::string_view token_raw() const
Get a view of the raw token.
Definition ast.hpp:517
const storage_type & storage() const
Get the std::variant that backs this type.
Definition ast.hpp:472
const T & as() const
Get the underlying data of this node as T.
Definition ast.hpp:527
An adapter for enumeration types.
Represents a single JSON value, which can be any one of a potential kind, each behaving slightly diff...
Definition value.hpp:113
Copyright (c) 2014-2020 by Travis Gockel.
#define JSONV_UNUSED
Note that you know the variable is unused, but make the compiler stop complaining about it.
Definition config.hpp:109
#define JSONV_NODISCARD
Warn if the caller discards the result of this function.
Definition config.hpp:121
#define JSONV_PUBLIC
This function or class is part of the public API for JSON Voorhees.
Definition config.hpp:102
ast_node_type
Marker type for an encountered token type.
Definition ast.hpp:87
Copyright (c) 2019-2020 by Travis Gockel.
STL namespace.