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/// \addtogroup 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/// Write the one-character code for \a type.
107///
108/// +--------------------+--------+
109/// | `ast_node_type` | Output |
110/// +--------------------+--------+
111/// | `document_start` | `^` |
112/// | `document_end` | `$` |
113/// | `object_begin` | `{` |
114/// | `object_end` | `}` |
115/// | `array_begin` | `[` |
116/// | `array_end` | `]` |
117/// | `string_canonical` | `s` |
118/// | `string_escaped` | `S` |
119/// | `key_canonical` | `k` |
120/// | `key_escaped` | `K` |
121/// | `literal_true` | `t` |
122/// | `literal_false` | `f` |
123/// | `literal_null` | `n` |
124/// | `integer` | `i` |
125/// | `decimal` | `d` |
126/// | `error` | `!` |
127/// +--------------------+--------+
128///
129/// A value outside the enumeration is written as `Invalid type value: ` followed by its number.
130JSONV_PUBLIC std::ostream& operator<<(std::ostream&, const ast_node_type& type);
131
132/// Get what \c operator<< writes for \a type as a \c std::string.
134
135/// Error code encountered while building the AST.
136enum class ast_error : std::uint64_t
137{
138 none = 0,
139 expected_document,
140 expected_string,
141 expected_key_delimiter,
142 /// A value followed another inside an array or object with no `,` between them, as in `[1 2]`.
144 unexpected_token,
145 unexpected_comma,
146 unexpected_eof,
147 expected_eof,
148 depth_exceeded,
149 extra_close,
150 mismatched_close,
151 /// A `]` or `}` arrived where the structure still owed a value -- after a `,` in either kind of structure, or
152 /// after the `:` of an object member, as in `{"a":}`.
154 invalid_literal,
155 invalid_number,
156 invalid_string,
157 invalid_comment,
158 internal,
159};
160
161/// \{
162
163/// Get a description of the error \a code.
164JSONV_PUBLIC std::ostream& operator<<(std::ostream&, const ast_error& code);
166/// \}
167
168/// Represents an entry in a JSON AST.
169///
170/// \see parse_index
171class ast_node final
172{
173public:
174 template <typename TSelf, ast_node_type KIndexToken>
175 class base
176 {
177 public:
178 /// Get the \c ast_node_type type.
180 static constexpr ast_node_type type()
181 {
182 return KIndexToken;
183 }
184
185 /// \see ast_node::token_raw
187 std::string_view token_raw() const
188 {
189 return std::string_view(_token_begin, static_cast<const TSelf&>(*this).token_size());
190 }
191
192 /// Allow implicit conversion to the more generic \c ast_node.
194 operator ast_node() const;
195
196 protected:
197 explicit constexpr base(const char* token_begin) :
198 _token_begin(token_begin)
199 { }
200
201 private:
202 const char* _token_begin;
203 };
204
205 template <typename TSelf, ast_node_type KIndexToken, std::size_t KTokenSize>
206 class basic_fixed_size_token : public base<TSelf, KIndexToken>
207 {
208 public:
209 explicit constexpr basic_fixed_size_token(const char* token_begin) :
211 { }
212
214 constexpr std::size_t token_size() const
215 {
216 return KTokenSize;
217 }
218 };
219
220 template <typename TSelf, ast_node_type KIndexToken>
221 class basic_dynamic_size_token : public base<TSelf, KIndexToken>
222 {
223 public:
224 explicit constexpr basic_dynamic_size_token(const char* token_begin, std::size_t token_size) :
226 _token_size(token_size)
227 { }
228
230 constexpr std::size_t token_size() const
231 {
232 return _token_size;
233 }
234
235 private:
236 std::size_t _token_size;
237 };
238
239 /// The start of a document.
240 class document_start final : public basic_fixed_size_token<document_start, ast_node_type::document_start, 0U>
241 {
242 public:
243 using basic_fixed_size_token<document_start, ast_node_type::document_start, 0U>::basic_fixed_size_token;
244 };
245
246 /// The end of a document.
247 class document_end final : public basic_fixed_size_token<document_end, ast_node_type::document_end, 0U>
248 {
249 public:
250 using basic_fixed_size_token<document_end, ast_node_type::document_end, 0U>::basic_fixed_size_token;
251 };
252
253 /// The beginning of an \c kind::object (`{`).
254 class object_begin final : public basic_fixed_size_token<object_begin, ast_node_type::object_begin, 1U>
255 {
256 public:
257 explicit constexpr object_begin(const char* token_begin, std::size_t element_count) :
259 _element_count(element_count)
260 { }
261
262 /// Get the number of elements in the object this token starts. This is useful for reserving memory.
264 constexpr std::size_t element_count() const
265 {
266 return _element_count;
267 }
268
269 private:
270 std::size_t _element_count;
271 };
272
273 /// The end of an \c kind::object (`}`).
274 class object_end final : public basic_fixed_size_token<object_end, ast_node_type::object_end, 1U>
275 {
276 public:
277 using basic_fixed_size_token<object_end, ast_node_type::object_end, 1U>::basic_fixed_size_token;
278 };
279
280 /// The beginning of an \c kind::array (`[`).
281 class array_begin final : public basic_fixed_size_token<array_begin, ast_node_type::array_begin, 1U>
282 {
283 public:
284 explicit constexpr array_begin(const char* token_begin, std::size_t element_count) :
286 _element_count(element_count)
287 { }
288
289 /// Get the number of elements in the array this token starts. This is useful for reserving memory.
291 constexpr std::size_t element_count() const
292 {
293 return _element_count;
294 }
295
296 private:
297 std::size_t _element_count;
298 };
299
300 /// The end of an \c kind::array (`]`).
301 class array_end final : public basic_fixed_size_token<array_end, ast_node_type::array_end, 1U>
302 {
303 public:
304 using basic_fixed_size_token<array_end, ast_node_type::array_end, 1U>::basic_fixed_size_token;
305 };
306
307 template <typename TSelf, ast_node_type KIndexToken, bool KEscaped>
308 class basic_string_token : public basic_dynamic_size_token<TSelf, KIndexToken>
309 {
310 public:
311 /// The return type of \c value is based on if the string is \c canonical or \c escaped. Strings in canonical
312 /// representation can be returned directly from the text through a \c std::string_view.
313 using value_type = std::conditional_t<KEscaped, std::string, std::string_view>;
314
315 public:
316 explicit constexpr basic_string_token(const char* token_begin, std::size_t token_size) :
318 _token_size(token_size)
319 { }
320
321 /// Was the source JSON for this string encoded in the canonical UTF-8 representation? If this is \c true, there
322 /// is no need to translate JSON escape sequences to extract a \c std::string value. This is the opposite of
323 /// \c escaped.
325 constexpr bool canonical() const noexcept
326 {
327 return !KEscaped;
328 }
329
330 /// Did the source JSON for this string contain escape sequences? If this is \c true, JSON escape sequences must
331 /// be translated into their canonical UTF-8 representation on extraction. This is the opposite of \c canonical.
333 constexpr bool escaped() const noexcept
334 {
335 return KEscaped;
336 }
337
339 constexpr std::size_t token_size() const
340 {
341 return _token_size;
342 }
343
345 value_type value() const
346 {
347 return detail::string_from_token(this->token_raw(), std::integral_constant<bool, KEscaped>());
348 }
349
350 private:
351 std::size_t _token_size;
352 };
353
354 class string_canonical final : public basic_string_token<string_canonical, ast_node_type::string_canonical, false>
355 {
356 public:
357 using basic_string_token<string_canonical, ast_node_type::string_canonical, false>::basic_string_token;
358 };
359
360 class string_escaped final : public basic_string_token<string_escaped, ast_node_type::string_escaped, true>
361 {
362 public:
363 using basic_string_token<string_escaped, ast_node_type::string_escaped, true>::basic_string_token;
364 };
365
366 class key_canonical final : public basic_string_token<key_canonical, ast_node_type::key_canonical, false>
367 {
368 public:
369 using basic_string_token<key_canonical, ast_node_type::key_canonical, false>::basic_string_token;
370 };
371
372 class key_escaped final : public basic_string_token<key_escaped, ast_node_type::key_escaped, true>
373 {
374 public:
375 using basic_string_token<key_escaped, ast_node_type::key_escaped, true>::basic_string_token;
376 };
377
378 class literal_true final : public basic_fixed_size_token<literal_true, ast_node_type::literal_true, 4U>
379 {
380 public:
381 using basic_fixed_size_token<literal_true, ast_node_type::literal_true, 4U>::basic_fixed_size_token;
382
384 bool value() const noexcept
385 {
386 return true;
387 }
388 };
389
390 class literal_false final : public basic_fixed_size_token<literal_false, ast_node_type::literal_false, 5U>
391 {
392 public:
393 using basic_fixed_size_token<literal_false, ast_node_type::literal_false, 5U>::basic_fixed_size_token;
394
396 bool value() const noexcept
397 {
398 return false;
399 }
400 };
401
402 class literal_null final : public basic_fixed_size_token<literal_null, ast_node_type::literal_null, 4U>
403 {
404 public:
405 using basic_fixed_size_token<literal_null, ast_node_type::literal_null, 4U>::basic_fixed_size_token;
406
408 jsonv::value value() const;
409 };
410
411 class integer final : public basic_dynamic_size_token<integer, ast_node_type::integer>
412 {
413 public:
414 using basic_dynamic_size_token<integer, ast_node_type::integer>::basic_dynamic_size_token;
415
416 /// Get the literal as an \c std::int64_t. A literal from 2^63 through 2^64-1 is stored with its bits intact,
417 /// so it reads back negative -- the onus is on the caller to know a particular key is in that range.
418 ///
419 /// \throws std::invalid_argument if the literal is outside that range altogether, where there are no bits to
420 /// keep. \c parse reads such a literal as the nearest \c double instead.
422 std::int64_t value() const;
423 };
424
425 class decimal final : public basic_dynamic_size_token<decimal, ast_node_type::decimal>
426 {
427 public:
428 using basic_dynamic_size_token<decimal, ast_node_type::decimal>::basic_dynamic_size_token;
429
431 double value() const;
432 };
433
434 class error final : public basic_dynamic_size_token<error, ast_node_type::error>
435 {
436 public:
437 explicit constexpr error(const char* token_begin, std::size_t token_size, ast_error error_code) :
439 _error_code(error_code)
440 { }
441
443 constexpr ast_error error_code() const
444 {
445 return _error_code;
446 }
447
448 private:
449 ast_error _error_code;
450 };
451
452 using storage_type = std::variant<document_end,
457 array_end,
465 integer,
466 decimal,
467 error
468 >;
469
470public:
471 ast_node(const storage_type& value) :
472 _impl(value)
473 { }
474
475 template <typename T, typename... TArgs>
476 explicit ast_node(std::in_place_type_t<T> type, TArgs&&... args) :
477 _impl(type, std::forward<TArgs>(args)...)
478 { }
479
480 /// Get the \c std::variant that backs this type.
481 ///
482 /// \see visit
484 const storage_type& storage() const
485 {
486 return _impl;
487 }
488
489 /// Convenience function for calling \c std::visit on the underlying \c storage of this node.
490 template <typename FVisitor>
491 auto visit(FVisitor&& visitor) const
492 {
493 return std::visit(std::forward<FVisitor>(visitor), _impl);
494 }
495
496 /// Convenience function for calling \c std::visit on a key (see \c as_key).
497 template <typename FVisitor>
499 {
500 return std::visit(std::forward<FVisitor>(visitor), as_key());
501 }
502
503 /// Get the \c ast_node_type that tells the underlying type of this instance.
506 {
507 return visit([](const auto& x) { return x.type(); });
508 }
509
510 /// \{
511
512 /// Check that this node has the given \a type or is one of the expected \a types.
513 ///
514 /// A mismatch is returned rather than thrown, since which node types are acceptable is a question about the JSON
515 /// source and not about the correctness of the program asking. The caller decides whether it is an error.
516 ///
517 /// \returns Nothing if this instance has \a type or one of the given \a types; otherwise the \c ast_node_type this
518 /// instance actually has.
519 /// \throws std::invalid_argument if \a types is empty. Unlike a type mismatch, expecting nothing at all is a
520 /// mistake in the calling code.
522 JSONV_PUBLIC std::expected<void, ast_node_type> expect(ast_node_type type) const;
524 JSONV_PUBLIC std::expected<void, ast_node_type> expect(std::initializer_list<ast_node_type> types) const;
525 /// \}
526
527 /// Get a view of the raw token. For example, \c "true", \c "{", or \c "1234". Note that this includes the complete
528 /// source, so string types such as \c ast_node_type::string_canonical include the opening and closing quotations.
530 std::string_view token_raw() const
531 {
532 return visit([](const auto& x) { return x.token_raw(); });
533 }
534
535 /// Get the underlying data of this node as \c T.
536 ///
537 /// \throws std::bad_variant_access if the requested \c T is different from the \c type of this instance.
538 template <typename T>
540 const T& as() const
541 {
542 return std::get<T>(_impl);
543 }
544
545 /// Get the underlying data of this node as one of the key types: \c key_canonical or \c key_escaped.
546 ///
547 /// \throw std::bad_variant_access if the \c type of this instance is neither \c key_canonical nor \c key_escaped.
549 std::variant<key_canonical, key_escaped> as_key() const
550 {
551 if (type() == ast_node_type::key_canonical)
552 return as<key_canonical>();
553 else
554 return as<key_escaped>();
555 }
556
557private:
558 storage_type _impl;
559};
560
561/// \}
562
563template <typename TSelf, ast_node_type KIndexToken>
565{
566 return ast_node(ast_node::storage_type(static_cast<const TSelf&>(*this)));
567}
568
569}
The beginning of an kind::array ([).
Definition ast.hpp:282
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:291
The end of an kind::array (]).
Definition ast.hpp:302
static constexpr ast_node_type type()
Get the ast_node_type type.
Definition ast.hpp:180
std::string_view token_raw() const
Definition ast.hpp:187
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:333
constexpr bool canonical() const noexcept
Was the source JSON for this string encoded in the canonical UTF-8 representation?...
Definition ast.hpp:325
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:313
The end of a document.
Definition ast.hpp:248
The start of a document.
Definition ast.hpp:241
std::int64_t value() const
Get the literal as an std::int64_t.
The beginning of an kind::object ({).
Definition ast.hpp:255
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:264
The end of an kind::object (}).
Definition ast.hpp:275
Represents an entry in a JSON AST.
Definition ast.hpp:172
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:549
JSONV_PUBLIC std::expected< void, ast_node_type > expect(ast_node_type type) const
Check that this node has the given type or is one of the expected types.
ast_node_type type() const
Get the ast_node_type that tells the underlying type of this instance.
Definition ast.hpp:505
auto visit_key(FVisitor &&visitor) const
Convenience function for calling std::visit on a key (see as_key).
Definition ast.hpp:498
auto visit(FVisitor &&visitor) const
Convenience function for calling std::visit on the underlying storage of this node.
Definition ast.hpp:491
std::string_view token_raw() const
Get a view of the raw token.
Definition ast.hpp:530
JSONV_PUBLIC std::expected< void, ast_node_type > expect(std::initializer_list< ast_node_type > types) const
Check that this node has the given type or is one of the expected types.
const storage_type & storage() const
Get the std::variant that backs this type.
Definition ast.hpp:484
const T & as() const
Get the underlying data of this node as T.
Definition ast.hpp:540
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:131
#define JSONV_NODISCARD
Warn if the caller discards the result of this function.
Definition config.hpp:143
#define JSONV_PUBLIC
This function or class is part of the public API for JSON Voorhees.
Definition config.hpp:124
ast_error
Error code encountered while building the AST.
Definition ast.hpp:137
JSONV_PUBLIC std::string to_string(const ast_node_type &type)
Get what operator<< writes for type as a std::string.
ast_node_type
Marker type for an encountered token type.
Definition ast.hpp:87
@ close_after_comma
A ] or } arrived where the structure still owed a value – after a , in either kind of structure,...
@ expected_comma
A value followed another inside an array or object with no , between them, as in [1 2].
Copyright (c) 2019-2020 by Travis Gockel.
STL namespace.