diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-05 17:37:54 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-05 17:37:54 +0300 |
| commit | 32f5f761bc8e960293b4f4feaf973dd0da26d0f8 (patch) | |
| tree | 8d0436fdd093d5255c3b75e45f9741882b22e2e4 /docs/handbook/tomlplusplus | |
| parent | 64f4ddfa97c19f371fe1847b20bd26803f0a25d5 (diff) | |
| download | Project-Tick-32f5f761bc8e960293b4f4feaf973dd0da26d0f8.tar.gz Project-Tick-32f5f761bc8e960293b4f4feaf973dd0da26d0f8.zip | |
NOISSUE Project Tick Handbook is Released!
Assisted-by: Claude:Opus-4.6-High
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'docs/handbook/tomlplusplus')
| -rw-r--r-- | docs/handbook/tomlplusplus/architecture.md | 920 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/arrays.md | 625 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/basic-usage.md | 705 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/building.md | 474 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/code-style.md | 277 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/formatting.md | 546 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/node-system.md | 625 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/overview.md | 474 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/parsing.md | 494 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/path-system.md | 412 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/tables.md | 551 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/testing.md | 226 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/unicode-handling.md | 335 | ||||
| -rw-r--r-- | docs/handbook/tomlplusplus/values.md | 547 |
14 files changed, 7211 insertions, 0 deletions
diff --git a/docs/handbook/tomlplusplus/architecture.md b/docs/handbook/tomlplusplus/architecture.md new file mode 100644 index 0000000000..8ba979d200 --- /dev/null +++ b/docs/handbook/tomlplusplus/architecture.md @@ -0,0 +1,920 @@ +# toml++ — Architecture + +## Overview + +toml++ implements a tree-based data model for TOML documents. A parsed TOML document becomes a tree of `toml::node` objects, with `toml::table` as the root. The architecture centers on: + +1. A polymorphic node hierarchy (`node` → `table`, `array`, `value<T>`) +2. A recursive-descent parser that builds trees +3. Formatter classes that serialize trees back to text +4. A path system for structured navigation + +All public types live in the `toml` namespace. Internal implementation details live in `toml::impl` (an ABI-namespaced detail namespace). + +--- + +## Class Hierarchy + +``` +toml::node (abstract base) +├── toml::table — ordered map of key → node* +├── toml::array — vector of node* +└── toml::value<T> — leaf node holding a value + ├── value<std::string> + ├── value<int64_t> + ├── value<double> + ├── value<bool> + ├── value<toml::date> + ├── value<toml::time> + └── value<toml::date_time> +``` + +Supporting types: +``` +toml::node_view<T> — non-owning optional reference to a node +toml::key — string + source_region metadata +toml::path — vector of path_component +toml::path_component — key string or array index +toml::source_position — line + column +toml::source_region — begin + end positions + path +toml::parse_error — error description + source_region +toml::parse_result — table | parse_error (no-exceptions mode) +``` + +Formatter hierarchy: +``` +impl::formatter (base, protected) +├── toml::toml_formatter — TOML output +├── toml::json_formatter — JSON output +└── toml::yaml_formatter — YAML output +``` + +--- + +## `toml::node` — The Abstract Base Class + +Defined in `include/toml++/impl/node.hpp`, `toml::node` is the polymorphic base of all TOML tree nodes. It is declared as `TOML_ABSTRACT_INTERFACE`, meaning it has pure virtual methods and cannot be instantiated directly. + +### Private Members + +```cpp +class node +{ + private: + source_region source_{}; + + template <typename T> + decltype(auto) get_value_exact() const noexcept(...); + + // ref_type_ and ref_type — template aliases for ref() return types + // do_ref() — static helper for ref() implementation +``` + +The `source_` member records where this node was defined in the original TOML document (line, column, file path). + +### Protected Members + +```cpp + protected: + node() noexcept; + node(const node&) noexcept; + node(node&&) noexcept; + node& operator=(const node&) noexcept; + node& operator=(node&&) noexcept; + + // ref_cast<T>() — unsafe downcast helpers (all four ref-qualifications) + template <typename T> ref_cast_type<T, node&> ref_cast() & noexcept; + template <typename T> ref_cast_type<T, node&&> ref_cast() && noexcept; + template <typename T> ref_cast_type<T, const node&> ref_cast() const& noexcept; + template <typename T> ref_cast_type<T, const node&&> ref_cast() const&& noexcept; +``` + +Constructors and assignment operators are `protected` to prevent direct instantiation. `ref_cast<T>()` performs `reinterpret_cast`-based downcasts, used internally by `ref<T>()`. + +### Public Interface — Type Checks + +Every `node` provides a complete set of virtual type-checking methods: + +```cpp + public: + virtual ~node() noexcept; + + // Homogeneity checks + virtual bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept = 0; + virtual bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept = 0; + virtual bool is_homogeneous(node_type ntype) const noexcept = 0; + template <typename ElemType = void> + bool is_homogeneous() const noexcept; + + // Type identity + virtual node_type type() const noexcept = 0; + virtual bool is_table() const noexcept = 0; + virtual bool is_array() const noexcept = 0; + virtual bool is_array_of_tables() const noexcept; + virtual bool is_value() const noexcept = 0; + virtual bool is_string() const noexcept = 0; + virtual bool is_integer() const noexcept = 0; + virtual bool is_floating_point() const noexcept = 0; + virtual bool is_number() const noexcept = 0; + virtual bool is_boolean() const noexcept = 0; + virtual bool is_date() const noexcept = 0; + virtual bool is_time() const noexcept = 0; + virtual bool is_date_time() const noexcept = 0; + + // Template type check + template <typename T> + bool is() const noexcept; +``` + +The `is<T>()` template dispatches to the appropriate virtual method using `if constexpr`: + +```cpp +template <typename T> +bool is() const noexcept +{ + using type = impl::remove_cvref<impl::unwrap_node<T>>; + if constexpr (std::is_same_v<type, table>) + return is_table(); + else if constexpr (std::is_same_v<type, array>) + return is_array(); + else if constexpr (std::is_same_v<type, std::string>) + return is_string(); + // ... etc for int64_t, double, bool, date, time, date_time +} +``` + +### Public Interface — Type Casts + +```cpp + // Downcasts — return nullptr if type doesn't match + virtual table* as_table() noexcept = 0; + virtual array* as_array() noexcept = 0; + virtual toml::value<std::string>* as_string() noexcept = 0; + virtual toml::value<int64_t>* as_integer() noexcept = 0; + virtual toml::value<double>* as_floating_point() noexcept = 0; + virtual toml::value<bool>* as_boolean() noexcept = 0; + virtual toml::value<date>* as_date() noexcept = 0; + virtual toml::value<time>* as_time() noexcept = 0; + virtual toml::value<date_time>* as_date_time() noexcept = 0; + // + const overloads for all of the above + + // Template downcast + template <typename T> + impl::wrap_node<T>* as() noexcept; + template <typename T> + const impl::wrap_node<T>* as() const noexcept; +``` + +`as<T>()` is the unified template that dispatches to `as_table()`, `as_string()`, etc. + +### Public Interface — Value Retrieval + +```cpp + // Exact-match value retrieval + template <typename T> + optional<T> value_exact() const noexcept(...); + + // Permissive value retrieval (allows conversions) + template <typename T> + optional<T> value() const noexcept(...); + + // Value with default + template <typename T> + auto value_or(T&& default_value) const noexcept(...); +``` + +`value_exact<T>()` only succeeds if the node contains exactly type `T`. `value<T>()` is more lenient, allowing integer-to-float conversions and the like. `value_or()` returns the value if present, otherwise the given default. + +### Public Interface — Reference Access + +```cpp + template <typename T> + decltype(auto) ref() & noexcept; + template <typename T> + decltype(auto) ref() && noexcept; + template <typename T> + decltype(auto) ref() const& noexcept; + template <typename T> + decltype(auto) ref() const&& noexcept; +``` + +`ref<T>()` provides direct reference access to the underlying value. It asserts the type matches and is UB if it doesn't. + +### Public Interface — Visitation + +```cpp + template <typename Func> + decltype(auto) visit(Func&& visitor) & noexcept(...); + // + &&, const&, const&& overloads +``` + +Calls the visitor with the concrete node type. The visitor receives the actual `table&`, `array&`, or `value<T>&`. + +### Source Region + +```cpp + const source_region& source() const noexcept; +``` + +Returns where this node was defined in the source document. + +--- + +## `toml::table` — TOML Tables + +Declared in `include/toml++/impl/table.hpp`, `toml::table` extends `node` and models an ordered map of keys to nodes. + +### Internal Storage + +```cpp +class table : public node +{ + private: + using map_type = std::map<toml::key, impl::node_ptr, std::less<>>; + map_type map_; + bool inline_ = false; +``` + +- The backing container is `std::map<toml::key, std::unique_ptr<node>, std::less<>>`. +- `std::less<>` enables heterogeneous lookup (search by `std::string_view` without constructing a `key`). +- `inline_` tracks whether the table should be serialized as an inline table `{ ... }`. +- Insertion order is maintained because `toml::key` provides comparison operators that match `std::map`'s ordered semantics. + +### Iterators + +The table uses custom `impl::table_iterator<IsConst>` which wraps the map iterator and produces `table_proxy_pair<IsConst>` references: + +```cpp +template <bool IsConst> +struct table_proxy_pair +{ + using value_type = std::conditional_t<IsConst, const node, node>; + const toml::key& first; + value_type& second; +}; +``` + +This means iterating a table yields `(const key&, node&)` pairs, not `(const key&, unique_ptr<node>&)`. The `unique_ptr` layer is hidden. + +Public type aliases: +```cpp +using table_iterator = impl::table_iterator<false>; +using const_table_iterator = impl::table_iterator<true>; +``` + +### Construction + +```cpp +table() noexcept; // default +table(const table&); // deep copy +table(table&& other) noexcept; // move +explicit table(std::initializer_list<impl::table_init_pair> kvps); +``` + +The initializer-list constructor accepts `table_init_pair` objects, each containing a key and a value: + +```cpp +struct table_init_pair +{ + mutable toml::key key; + mutable node_ptr value; // std::unique_ptr<node> + + template <typename K, typename V> + table_init_pair(K&& k, V&& v, value_flags flags = preserve_source_value_flags); +}; +``` + +This enables the idiomatic construction: +```cpp +auto tbl = toml::table{ + { "name", "toml++" }, + { "version", 3 }, + { "nested", toml::table{ { "key", true } } } +}; +``` + +### Key Operations + +| Method | Description | +|--------|-------------| +| `size()` | Number of key-value pairs | +| `empty()` | Whether the table is empty | +| `get(key)` | Get node pointer by key, or nullptr | +| `get_as<T>(key)` | Get typed node pointer, or nullptr | +| `contains(key)` | Check if key exists | +| `operator[](key)` | Returns `node_view` (safe, never null-deref) | +| `at(key)` | Returns node reference, throws if missing | +| `insert(key, val)` | Insert if not present | +| `insert_or_assign(key, val)` | Insert or replace | +| `emplace<T>(key, args...)` | Construct in place if not present | +| `erase(key)` | Remove by key | +| `erase(iterator)` | Remove by iterator | +| `clear()` | Remove all entries | + +### Metadata + +```cpp +bool is_inline() const noexcept; +void is_inline(bool val) noexcept; +``` + +Controls inline table formatting: `{ a = 1, b = 2 }` vs. multi-line. + +--- + +## `toml::array` — TOML Arrays + +Declared in `include/toml++/impl/array.hpp`, `toml::array` extends `node` and models a heterogeneous sequence. + +### Internal Storage + +```cpp +class array : public node +{ + private: + std::vector<impl::node_ptr> elems_; +``` + +Each element is a `std::unique_ptr<node>`. The array can contain any mix of value types, tables, and nested arrays. + +### Iterators + +`impl::array_iterator<IsConst>` wraps the vector iterator and dereferences the `unique_ptr`, yielding `node&` references: + +```cpp +using array_iterator = impl::array_iterator<false>; +using const_array_iterator = impl::array_iterator<true>; +``` + +It satisfies `RandomAccessIterator` requirements (unlike `table_iterator` which is `BidirectionalIterator`). + +### Key Operations + +| Method | Description | +|--------|-------------| +| `size()` | Number of elements | +| `empty()` | Whether the array is empty | +| `capacity()` | Reserved capacity | +| `reserve(n)` | Reserve capacity | +| `shrink_to_fit()` | Release excess capacity | +| `operator[](index)` | Returns `node&` (no bounds check) | +| `at(index)` | Returns `node&` (bounds-checked, throws) | +| `front()` / `back()` | First / last element | +| `get(index)` | Returns `node*` or nullptr | +| `get_as<T>(index)` | Returns typed pointer or nullptr | +| `push_back(val)` | Append element | +| `emplace_back<T>(args...)` | Construct at end | +| `insert(pos, val)` | Insert at position | +| `emplace(pos, args...)` | Construct at position | +| `erase(pos)` | Remove at position | +| `erase(first, last)` | Remove range | +| `pop_back()` | Remove last | +| `clear()` | Remove all | +| `resize(n)` | Resize (default-constructed elements) | +| `truncate(n)` | Remove elements beyond index n | +| `flatten()` | Flatten nested arrays | +| `prune()` | Remove empty tables and arrays recursively | + +### Homogeneity + +```cpp +bool is_homogeneous(node_type ntype) const noexcept; +bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept; +template <typename ElemType = void> +bool is_homogeneous() const noexcept; +``` + +Returns `true` if all elements are the same type. Returns `false` for empty arrays. + +### for_each + +```cpp +template <typename Func> +array& for_each(Func&& visitor) & noexcept(...); +``` + +Iterates elements, calling the visitor with each concrete element type. Supports early exit by returning `bool` from the visitor (on compilers without the GCC 7 bug). + +--- + +## `toml::value<T>` — Leaf Values + +Declared in `include/toml++/impl/value.hpp`, `toml::value<T>` is a class template holding a single TOML value. + +### Template Parameter Constraints + +`T` must be one of the native TOML value types: +- `std::string` +- `int64_t` +- `double` +- `bool` +- `toml::date` +- `toml::time` +- `toml::date_time` + +```cpp +template <typename ValueType> +class value : public node +{ + static_assert(impl::is_native<ValueType> && !impl::is_cvref<ValueType>); + + private: + ValueType val_; + value_flags flags_ = value_flags::none; +``` + +### Type Aliases + +```cpp +using value_type = ValueType; +using value_arg = /* conditional type */; +``` + +`value_arg` differs by value type: +- `int64_t`, `double`, `bool` → passed by value +- `std::string` → passed as `std::string_view` +- `date`, `time`, `date_time` → passed as `const value_type&` + +### Key Operations + +```cpp +// Access the underlying value +ValueType& get() & noexcept; +ValueType&& get() && noexcept; +const ValueType& get() const& noexcept; +const ValueType&& get() const&& noexcept; + +// Implicit conversion operator +operator ValueType&() noexcept; +operator const ValueType&() const noexcept; + +// Value flags (integer format: binary, octal, hex) +value_flags flags() const noexcept; +value<ValueType>& flags(value_flags new_flags) noexcept; +``` + +### Construction + +`value<T>` supports variadic construction via `impl::native_value_maker`: + +```cpp +template <typename... Args> +explicit value(Args&&... args); + +value(const value& other) noexcept; +value(const value& other, value_flags flags) noexcept; +value(value&& other) noexcept; +value(value&& other, value_flags flags) noexcept; +``` + +Special handling exists for: +- `char8_t` strings (C++20): converted via `reinterpret_cast` +- Wide strings (Windows): narrowed via `impl::narrow()` + +--- + +## Date/Time Types + +Defined in `include/toml++/impl/date_time.hpp`: + +### `toml::date` + +```cpp +struct date +{ + uint16_t year; + uint8_t month; // 1-12 + uint8_t day; // 1-31 + + constexpr date(Y y, M m, D d) noexcept; + // Comparison operators: ==, !=, <, <=, >, >= + // Stream output: YYYY-MM-DD +}; +``` + +### `toml::time` + +```cpp +struct time +{ + uint8_t hour; // 0-23 + uint8_t minute; // 0-59 + uint8_t second; // 0-59 + uint32_t nanosecond; // 0-999999999 + + constexpr time(H h, M m, S s = 0, NS ns = 0) noexcept; + // Comparison operators, stream output: HH:MM:SS.nnnnnnnnn +}; +``` + +### `toml::time_offset` + +```cpp +struct time_offset +{ + int16_t minutes; // -1440 to +1440 + + constexpr time_offset(H h, M m) noexcept; + // Comparison operators, stream output: +HH:MM or -HH:MM +}; +``` + +### `toml::date_time` + +```cpp +struct date_time +{ + toml::date date; + toml::time time; + optional<toml::time_offset> offset; + + // If offset is present, it's an offset date-time + // If offset is absent, it's a local date-time + bool is_local() const noexcept; +}; +``` + +--- + +## `toml::key` — Table Keys + +Defined in `include/toml++/impl/key.hpp`: + +```cpp +class key +{ + private: + std::string key_; + source_region source_; + + public: + explicit key(std::string_view k, source_region&& src = {}); + explicit key(std::string&& k, source_region&& src = {}) noexcept; + explicit key(const char* k, source_region&& src = {}); + + // String access + std::string_view str() const noexcept; + operator std::string_view() const noexcept; + bool empty() const noexcept; + + // Source tracking + const source_region& source() const noexcept; + + // Comparison — compares the string content, not source position + friend bool operator==(const key& lhs, const key& rhs) noexcept; + friend bool operator<(const key& lhs, const key& rhs) noexcept; + // + heterogeneous comparisons with std::string_view +}; +``` + +Keys carry source position metadata from parsing, but comparisons only consider the string content. + +--- + +## Parser Design + +The parser is a recursive-descent UTF-8 parser implemented in `impl::parser` (defined in `parser.inl`). It operates on a stream of UTF-8 codepoints. + +### Parse Entry Points + +```cpp +// Defined in parser.hpp +parse_result parse(std::string_view doc, std::string_view source_path = {}); +parse_result parse(std::string_view doc, std::string&& source_path); +parse_result parse_file(std::string_view file_path); + +// Stream overloads +parse_result parse(std::istream& doc, std::string_view source_path = {}); +``` + +### Return Type: `parse_result` + +When exceptions are enabled (`TOML_EXCEPTIONS=1`): +```cpp +using parse_result = table; // parse() throws on error +``` + +When exceptions are disabled (`TOML_EXCEPTIONS=0`): +```cpp +class parse_result // discriminated union: table or parse_error +{ + bool err_; + union { toml::table; parse_error; } storage_; + + public: + explicit operator bool() const noexcept; // true = success + table& table() &; + parse_error& error() &; + // + iterator accessors for safe ranged-for on failure +}; +``` + +### Error Type + +```cpp +class parse_error /* : public std::runtime_error (with exceptions) */ +{ + std::string_view description() const noexcept; + const source_region& source() const noexcept; +}; +``` + +### Parser Internals + +The `impl::parser` class stores: +- A UTF-8 byte stream reader +- A source position tracker +- The root table being built +- The current implicit table stack (for dotted keys and `[section.headers]`) + +Parsing proceeds top-down: +1. Skip BOM if present +2. Parse key-value pairs, table headers (`[table]`), and array-of-tables headers (`[[array]]`) +3. For each key-value pair, parse the key (bare or quoted), then the value +4. Values are parsed based on the leading character(s): strings, numbers, booleans, dates/times, arrays, inline tables + +Node allocation uses `impl::make_node()` which dispatches through `impl::make_node_impl_specialized()`: +```cpp +template <typename T> +auto* make_node_impl_specialized(T&& val, value_flags flags) +{ + using unwrapped_type = unwrap_node<remove_cvref<T>>; + if constexpr (is_one_of<unwrapped_type, array, table>) + return new unwrapped_type(static_cast<T&&>(val)); + else + { + using native_type = native_type_of<unwrapped_type>; + using value_type = value<native_type>; + return new value_type{ static_cast<T&&>(val) }; + } +} +``` + +--- + +## Formatter Design + +### Base Class: `impl::formatter` + +Defined in `include/toml++/impl/formatter.hpp`: + +```cpp +class formatter +{ + private: + const node* source_; + const parse_result* result_; // for no-exceptions mode + const formatter_constants* constants_; + formatter_config config_; + size_t indent_columns_; + format_flags int_format_mask_; + std::ostream* stream_; + int indent_; + bool naked_newline_; + + protected: + // Stream management + void attach(std::ostream& stream) noexcept; + void detach() noexcept; + + // Output primitives + void print_newline(bool force = false); + void print_indent(); + void print_unformatted(char); + void print_unformatted(std::string_view); + + // Typed printing + void print_string(std::string_view str, bool allow_multi_line, bool allow_bare, bool allow_literal_whitespace); + void print(const value<std::string>&); + void print(const value<int64_t>&); + void print(const value<double>&); + void print(const value<bool>&); + void print(const value<date>&); + void print(const value<time>&); + void print(const value<date_time>&); + void print_value(const node&, node_type); + + // Configuration queries + bool indent_array_elements() const noexcept; + bool indent_sub_tables() const noexcept; + bool literal_strings_allowed() const noexcept; + bool multi_line_strings_allowed() const noexcept; + bool unicode_strings_allowed() const noexcept; + bool terse_kvps() const noexcept; + bool force_multiline_arrays() const noexcept; +}; +``` + +### Formatter Constants + +Each concrete formatter defines its behavior via `formatter_constants`: + +```cpp +struct formatter_constants +{ + format_flags mandatory_flags; // always-on flags + format_flags ignored_flags; // always-off flags + std::string_view float_pos_inf; // e.g., "inf", "Infinity", ".inf" + std::string_view float_neg_inf; + std::string_view float_nan; + std::string_view bool_true; + std::string_view bool_false; +}; +``` + +| Formatter | pos_inf | neg_inf | nan | bool_true | bool_false | +|-----------|---------|---------|-----|-----------|------------| +| `toml_formatter` | `"inf"` | `"-inf"` | `"nan"` | `"true"` | `"false"` | +| `json_formatter` | `"Infinity"` | `"-Infinity"` | `"NaN"` | `"true"` | `"false"` | +| `yaml_formatter` | `".inf"` | `"-.inf"` | `".NAN"` | `"true"` | `"false"` | + +### `toml::toml_formatter` + +Inherits `impl::formatter`. Serializes to valid TOML format. + +Key behaviors: +- Tracks a `key_path_` vector for producing `[table.paths]` +- Manages `pending_table_separator_` for blank lines between sections +- Uses 4-space indentation by default (`" "sv`) +- Respects `is_inline()` on tables +- Default flags include all `allow_*` flags and `indentation` + +### `toml::json_formatter` + +Outputs valid JSON. Key differences from TOML formatter: +- Mandatory `quote_dates_and_times` (dates become quoted strings) +- Ignores `allow_literal_strings` and `allow_multi_line_strings` +- Default includes `quote_infinities_and_nans` +- Uses 4-space indentation + +### `toml::yaml_formatter` + +Outputs YAML. Key differences: +- Uses 2-space indentation (`" "sv`) +- Mandatory `quote_dates_and_times` and `indentation` +- Ignores `allow_multi_line_strings` +- Custom string printing via `print_yaml_string()` +- YAML-style inf/nan representation (`.inf`, `-.inf`, `.NAN`) + +### Streaming Pattern + +All formatters use the same attach/print/detach pattern: + +```cpp +friend std::ostream& operator<<(std::ostream& lhs, toml_formatter& rhs) +{ + rhs.attach(lhs); + rhs.key_path_.clear(); // (toml_formatter specific) + rhs.print(); + rhs.detach(); + return lhs; +} +``` + +--- + +## `toml::node_view<T>` — Safe Node References + +Defined in `include/toml++/impl/node_view.hpp`: + +```cpp +template <typename ViewedType> +class node_view +{ + static_assert(impl::is_one_of<ViewedType, toml::node, const toml::node>); + + private: + mutable viewed_type* node_ = nullptr; + + public: + using viewed_type = ViewedType; + + node_view() noexcept = default; + explicit node_view(viewed_type* node) noexcept; + explicit node_view(viewed_type& node) noexcept; + + explicit operator bool() const noexcept; + viewed_type* node() const noexcept; +``` + +`node_view` wraps a pointer to a `node` (or `const node`) and provides the same interface as `node` — type checks, casts, value retrieval — but safely handles null (returns empty optionals, false booleans, empty views on subscript). + +The key design feature is chainable subscript operators: + +```cpp +node_view operator[](std::string_view key) const noexcept; +node_view operator[](size_t index) const noexcept; +node_view operator[](const path& p) const noexcept; +``` + +These return empty views when the key/index doesn't exist, so you can chain deeply without null checks: + +```cpp +auto val = tbl["section"]["subsection"]["key"].value_or(42); +// Safe even if any intermediate node is missing +``` + +--- + +## Source Tracking + +### `toml::source_position` + +```cpp +struct source_position +{ + source_index line; // 1-based + source_index column; // 1-based + + explicit constexpr operator bool() const noexcept; + // Comparison operators +}; +``` + +### `toml::source_region` + +```cpp +struct source_region +{ + source_position begin; + source_position end; + source_path_ptr path; // std::shared_ptr<const std::string> +}; +``` + +Every node carries a `source_region` accessible via `node::source()`. For programmatically-constructed nodes, the source region is default (zeroed). For parsed nodes, it tracks the exact file location. + +--- + +## Ownership Model + +The ownership model is straightforward: +- `toml::table` owns its child nodes via `std::map<key, std::unique_ptr<node>>` +- `toml::array` owns its child nodes via `std::vector<std::unique_ptr<node>>` +- `toml::value<T>` owns its stored value directly (by value) +- `toml::node_view<T>` is non-owning (raw pointer) +- `toml::parse_result` owns either a `table` or a `parse_error` (union storage) + +Copying a `table` or `array` performs a deep copy of the entire subtree. Moving transfers ownership with no allocation. + +--- + +## Thread Safety + +toml++ does not provide internal synchronization. A parsed `table` tree can be read concurrently from multiple threads, but modification requires external synchronization. The parser itself is not re-entrant — each call to `parse()` creates a new internal parser instance. + +--- + +## Memory Allocation + +All heap allocation uses the global `operator new` (no custom allocators). Nodes are individually heap-allocated even in arrays. The `make_node.hpp` factory always calls `new`: + +```cpp +return new unwrapped_type(static_cast<T&&>(val)); +// or +return new value_type{ static_cast<T&&>(val) }; +``` + +This makes the library simple but means that large TOML documents with many small values will create many small allocations. + +--- + +## ABI Namespaces + +toml++ uses conditional inline namespaces to prevent ODR violations when mixing translation units compiled with different configuration macros: + +```cpp +TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex); +// Creates either: +// namespace ex { ... } // when TOML_EXCEPTIONS == 1 +// namespace noex { ... } // when TOML_EXCEPTIONS == 0 + +TOML_ABI_NAMESPACE_BOOL(TOML_HAS_CUSTOM_OPTIONAL_TYPE, custopt, stdopt); +``` + +This means `toml::ex::parse_result` (exceptions enabled) and `toml::noex::parse_result` (exceptions disabled) are different types, preventing linker errors if you accidentally mix them. + +--- + +## Conditional Compilation + +Large portions of the library can be compiled out: + +| Macro | What it removes | +|-------|----------------| +| `TOML_ENABLE_PARSER=0` | All parsing functions, `parse_error`, `parse_result`, parser implementation | +| `TOML_ENABLE_FORMATTERS=0` | All formatter classes, `format_flags` | +| `TOML_HEADER_ONLY=0` | Switches to compiled mode (link against `toml.cpp`) | + +This enables minimal builds for projects that only need, say, programmatic TOML construction and serialization (no parsing). + +--- + +## Related Documentation + +- [node-system.md](node-system.md) — Detailed node API reference +- [tables.md](tables.md) — Table operations in depth +- [arrays.md](arrays.md) — Array operations in depth +- [values.md](values.md) — Value template details +- [parsing.md](parsing.md) — Parser behavior and error handling +- [formatting.md](formatting.md) — Formatter usage and customization diff --git a/docs/handbook/tomlplusplus/arrays.md b/docs/handbook/tomlplusplus/arrays.md new file mode 100644 index 0000000000..3e916392fc --- /dev/null +++ b/docs/handbook/tomlplusplus/arrays.md @@ -0,0 +1,625 @@ +# toml++ — Arrays + +## Overview + +`toml::array` extends `toml::node` and models a heterogeneous, ordered sequence of TOML nodes. Unlike C++ standard containers that store elements of a single type, a TOML array can contain any mix of value types, sub-tables, and nested arrays. + +Declared in `include/toml++/impl/array.hpp` with implementation in `array.inl`. + +--- + +## Internal Storage + +```cpp +class array : public node +{ + private: + std::vector<impl::node_ptr> elems_; + // impl::node_ptr = std::unique_ptr<node> +}; +``` + +- Each element is owned via `std::unique_ptr<node>` +- The array owns all child nodes — destruction cascades +- Elements can be any `node` subclass: `value<T>`, `table`, or nested `array` + +--- + +## Construction + +### Default Construction + +```cpp +toml::array arr; // empty array +``` + +### Initializer List Construction + +```cpp +auto arr = toml::array{ 1, 2, 3 }; // array of integers +auto mixed = toml::array{ 1, "hello", 3.14, true }; // mixed types +auto nested = toml::array{ + toml::array{ 1, 2 }, + toml::array{ 3, 4 } +}; + +// Array of tables (array-of-tables syntax in TOML) +auto aot = toml::array{ + toml::table{ { "name", "Alice" }, { "age", 30 } }, + toml::table{ { "name", "Bob" }, { "age", 25 } } +}; +``` + +Values are converted to nodes via `impl::make_node()`: +- `int`, `int64_t` → `value<int64_t>` +- `double`, `float` → `value<double>` +- `const char*`, `std::string` → `value<std::string>` +- `bool` → `value<bool>` +- `toml::date` → `value<date>` +- `toml::time` → `value<time>` +- `toml::date_time` → `value<date_time>` +- `toml::table` → `table` (moved) +- `toml::array` → `array` (moved) + +### Copy and Move + +```cpp +toml::array copy(original); // deep copy — all elements cloned +toml::array moved(std::move(arr)); // move — no allocation +``` + +Copy is recursive: nested tables and arrays are deep-copied. + +--- + +## Iterators + +### Types + +```cpp +using array_iterator = impl::array_iterator<false>; +using const_array_iterator = impl::array_iterator<true>; +``` + +`array_iterator` is a **RandomAccessIterator** (unlike `table_iterator` which is Bidirectional). This means it supports arithmetic, comparison, and random access: + +```cpp +auto it = arr.begin(); +it += 3; // jump forward 3 +ptrdiff_t diff = arr.end() - it; // distance +bool less = it < arr.end(); // comparison +``` + +Dereferencing yields `node&` (the `unique_ptr` is hidden): + +```cpp +for (auto it = arr.begin(); it != arr.end(); ++it) +{ + toml::node& elem = *it; + std::cout << elem << "\n"; +} +``` + +### Iterator Methods + +```cpp +iterator begin() noexcept; +iterator end() noexcept; +const_iterator begin() const noexcept; +const_iterator end() const noexcept; +const_iterator cbegin() const noexcept; +const_iterator cend() const noexcept; +``` + +### Range-Based For + +```cpp +for (auto& elem : arr) +{ + std::cout << elem.type() << ": " << elem << "\n"; +} +``` + +--- + +## Capacity + +```cpp +size_t size() const noexcept; // number of elements +bool empty() const noexcept; // true if size() == 0 +size_t capacity() const noexcept; // reserved capacity +size_t max_size() const noexcept; // maximum possible size + +void reserve(size_t new_cap); // reserve capacity +void shrink_to_fit(); // release excess capacity +``` + +--- + +## Element Access + +### `operator[]` — Unchecked Index Access + +```cpp +node& operator[](size_t index) noexcept; +const node& operator[](size_t index) const noexcept; +``` + +No bounds checking. UB if `index >= size()`. + +```cpp +auto arr = toml::array{ 10, 20, 30 }; +std::cout << arr[1].value_or(0) << "\n"; // 20 +``` + +### `at()` — Bounds-Checked Access + +```cpp +node& at(size_t index); +const node& at(size_t index) const; +``` + +Throws `std::out_of_range` if `index >= size()`. + +### `front()` / `back()` + +```cpp +node& front() noexcept; +node& back() noexcept; +const node& front() const noexcept; +const node& back() const noexcept; +``` + +### `get()` — Pointer Access + +```cpp +node* get(size_t index) noexcept; +const node* get(size_t index) const noexcept; +``` + +Returns `nullptr` if out of bounds (safe alternative to `operator[]`). + +### `get_as<T>()` — Typed Pointer Access + +```cpp +template <typename T> +impl::wrap_node<T>* get_as(size_t index) noexcept; +``` + +Returns a typed pointer if the element at `index` exists and matches type `T`: + +```cpp +auto arr = toml::array{ "hello", 42, true }; + +if (auto* s = arr.get_as<std::string>(0)) + std::cout << "String: " << s->get() << "\n"; + +if (auto* i = arr.get_as<int64_t>(1)) + std::cout << "Integer: " << i->get() << "\n"; +``` + +--- + +## Insertion + +### `push_back()` — Append + +```cpp +template <typename T> +decltype(auto) push_back(T&& val, value_flags flags = preserve_source_value_flags); +``` + +Appends a new element to the end: + +```cpp +arr.push_back(42); +arr.push_back("hello"); +arr.push_back(toml::table{ { "key", "value" } }); +arr.push_back(toml::array{ 1, 2, 3 }); +``` + +Returns a reference to the inserted node. + +### `emplace_back<T>()` — Construct at End + +```cpp +template <typename T, typename... Args> +decltype(auto) emplace_back(Args&&... args); +``` + +```cpp +arr.emplace_back<std::string>("constructed in place"); +arr.emplace_back<int64_t>(42); +arr.emplace_back<toml::table>(); // empty table +``` + +### `insert()` — Insert at Position + +```cpp +template <typename T> +iterator insert(const_iterator pos, T&& val, value_flags flags = preserve_source_value_flags); +``` + +```cpp +arr.insert(arr.begin(), "first"); // insert at front +arr.insert(arr.begin() + 2, 42); // insert at index 2 +arr.insert(arr.end(), toml::array{1,2}); // same as push_back +``` + +### `emplace()` — Construct at Position + +```cpp +template <typename T, typename... Args> +iterator emplace(const_iterator pos, Args&&... args); +``` + +```cpp +arr.emplace<std::string>(arr.begin(), "inserted string"); +``` + +--- + +## Removal + +### `pop_back()` + +```cpp +void pop_back() noexcept; +``` + +Removes the last element. + +### `erase()` — By Iterator + +```cpp +iterator erase(const_iterator pos) noexcept; +iterator erase(const_iterator first, const_iterator last) noexcept; +``` + +```cpp +arr.erase(arr.begin()); // remove first +arr.erase(arr.begin(), arr.begin() + 3); // remove first 3 +``` + +### `clear()` + +```cpp +void clear() noexcept; +``` + +Removes all elements. + +### `resize()` + +```cpp +void resize(size_t new_size, T&& default_value, value_flags flags = preserve_source_value_flags); +``` + +If `new_size > size()`, appends copies of `default_value`. If `new_size < size()`, truncates. + +### `truncate()` + +```cpp +void truncate(size_t new_size); +``` + +Removes elements beyond `new_size`. If `new_size >= size()`, does nothing. + +--- + +## Array Transformation + +### `flatten()` — Flatten Nested Arrays + +```cpp +array& flatten() &; +array&& flatten() &&; +``` + +Recursively flattens nested arrays into a single-level array: + +```cpp +auto arr = toml::array{ + 1, + toml::array{ 2, 3 }, + toml::array{ toml::array{ 4, 5 }, 6 } +}; + +arr.flatten(); +// arr is now: [1, 2, 3, 4, 5, 6] +``` + +Tables within nested arrays are **not** flattened — only arrays are unwrapped. + +### `prune()` — Remove Empty Containers + +```cpp +array& prune(bool recursive = true) &; +array&& prune(bool recursive = true) &&; +``` + +Removes empty tables and empty arrays. If `recursive` is true, prunes nested containers first, then removes them if they became empty: + +```cpp +auto arr = toml::array{ + 1, + toml::table{}, // empty table + toml::array{}, // empty array + toml::array{ toml::table{} } // array containing empty table +}; + +arr.prune(); +// arr is now: [1] +``` + +--- + +## Homogeneity + +### Checking Type Uniformity + +```cpp +bool is_homogeneous(node_type ntype) const noexcept; +bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept; +bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept; + +template <typename ElemType = void> +bool is_homogeneous() const noexcept; +``` + +```cpp +auto ints = toml::array{ 1, 2, 3 }; +auto mixed = toml::array{ 1, "two", 3.0 }; + +ints.is_homogeneous<int64_t>(); // true +ints.is_homogeneous<double>(); // false +ints.is_homogeneous(); // true (all same type) + +mixed.is_homogeneous(); // false +mixed.is_homogeneous(toml::node_type::none); // false + +// Find first mismatch: +toml::node* bad = nullptr; +mixed.is_homogeneous(toml::node_type::integer, bad); +// bad points to the "two" string value +``` + +**Important:** Empty arrays return `false` for all homogeneity checks — they don't contain "any" type. + +### `is_array_of_tables()` + +```cpp +bool is_array_of_tables() const noexcept; +``` + +Returns `true` only if the array is non-empty and every element is a `table`: + +```cpp +auto aot = toml::array{ + toml::table{ { "name", "Alice" } }, + toml::table{ { "name", "Bob" } } +}; +std::cout << aot.is_array_of_tables() << "\n"; // true + +auto mixed = toml::array{ toml::table{}, 42 }; +std::cout << mixed.is_array_of_tables() << "\n"; // false +``` + +--- + +## `for_each()` — Type-Safe Iteration + +```cpp +template <typename Func> +array& for_each(Func&& visitor) &; +template <typename Func> +array&& for_each(Func&& visitor) &&; +template <typename Func> +const array& for_each(Func&& visitor) const&; +``` + +Iterates elements, calling the visitor with each element in its concrete type. The visitor can accept: +- `(auto& element)` — element only +- `(size_t index, auto& element)` — index + element + +```cpp +auto arr = toml::array{ 1, "two", 3.0, true }; + +arr.for_each([](size_t idx, auto& elem) +{ + using T = std::remove_cvref_t<decltype(elem)>; + + std::cout << "[" << idx << "] "; + + if constexpr (toml::is_integer<T>) + std::cout << "int: " << elem.get() << "\n"; + else if constexpr (toml::is_string<T>) + std::cout << "string: " << elem.get() << "\n"; + else if constexpr (toml::is_floating_point<T>) + std::cout << "float: " << elem.get() << "\n"; + else if constexpr (toml::is_boolean<T>) + std::cout << "bool: " << elem.get() << "\n"; +}); +``` + +Output: +``` +[0] int: 1 +[1] string: two +[2] float: 3 +[3] bool: true +``` + +### Early Exit (Non-GCC-7) + +On supported compilers, returning `bool` from the visitor enables early termination: + +```cpp +arr.for_each([](auto& elem) -> bool +{ + if constexpr (toml::is_string<decltype(elem)>) + { + std::cout << "Found string: " << elem.get() << "\n"; + return false; // stop + } + return true; // continue +}); +``` + +--- + +## Array of Tables (TOML `[[syntax]]`) + +In TOML, `[[array_name]]` defines an array of tables: + +```toml +[[servers]] +name = "alpha" +ip = "10.0.0.1" + +[[servers]] +name = "beta" +ip = "10.0.0.2" +``` + +This parses to a `table` containing key `"servers"` → `array` → `[table, table]`. + +Accessing: +```cpp +auto tbl = toml::parse(/* above TOML */); + +if (auto* servers = tbl["servers"].as_array()) +{ + for (auto& server_node : *servers) + { + auto* server = server_node.as_table(); + if (server) + { + auto name = (*server)["name"].value_or(""sv); + auto ip = (*server)["ip"].value_or(""sv); + std::cout << name << " @ " << ip << "\n"; + } + } +} +``` + +Creating programmatically: +```cpp +auto tbl = toml::table{ + { "servers", toml::array{ + toml::table{ { "name", "alpha" }, { "ip", "10.0.0.1" } }, + toml::table{ { "name", "beta" }, { "ip", "10.0.0.2" } } + }} +}; +``` + +--- + +## Comparison + +### Equality + +```cpp +friend bool operator==(const array& lhs, const array& rhs) noexcept; +friend bool operator!=(const array& lhs, const array& rhs) noexcept; +``` + +Deep structural equality: same size, same element types, same values in order. + +--- + +## Printing + +Arrays are streamable: + +```cpp +auto arr = toml::array{ 1, 2, 3, "four" }; +std::cout << arr << "\n"; +// Output: [1, 2, 3, "four"] +``` + +The output format depends on the formatter. The default `toml_formatter` uses inline array syntax for simple arrays and multiline for arrays of tables. + +--- + +## Type Identity + +```cpp +node_type type() const noexcept final; // returns node_type::array +bool is_table() const noexcept final; // returns false +bool is_array() const noexcept final; // returns true +bool is_value() const noexcept final; // returns false +// ... all other is_*() return false + +array* as_array() noexcept final; // returns this +const array* as_array() const noexcept final; // returns this +// ... all other as_*() return nullptr +``` + +--- + +## Complete Example + +```cpp +#include <toml++/toml.hpp> +#include <iostream> + +int main() +{ + // Build an array + toml::array fruits; + fruits.push_back("apple"); + fruits.push_back("banana"); + fruits.push_back("cherry"); + + // Insert at position + fruits.insert(fruits.begin() + 1, "blueberry"); + + // Iterate + for (size_t i = 0; i < fruits.size(); i++) + { + std::cout << i << ": " << fruits[i].value_or(""sv) << "\n"; + } + + // Check homogeneity + std::cout << "All strings? " << fruits.is_homogeneous<std::string>() << "\n"; + + // Flatten nested arrays + auto nested = toml::array{ + toml::array{ 1, 2 }, + toml::array{ 3, toml::array{ 4, 5 } } + }; + nested.flatten(); + std::cout << "Flattened: " << nested << "\n"; + + // Array of tables + auto servers = toml::array{ + toml::table{ { "host", "alpha" }, { "port", 8080 } }, + toml::table{ { "host", "beta" }, { "port", 8081 } } + }; + std::cout << "Is array of tables? " << servers.is_array_of_tables() << "\n"; + + // for_each with type dispatch + auto mixed = toml::array{ 42, "hello", 3.14 }; + mixed.for_each([](auto& elem) + { + if constexpr (toml::is_integer<decltype(elem)>) + std::cout << "int: " << elem.get() << "\n"; + else if constexpr (toml::is_string<decltype(elem)>) + std::cout << "str: " << elem.get() << "\n"; + else if constexpr (toml::is_floating_point<decltype(elem)>) + std::cout << "flt: " << elem.get() << "\n"; + }); + + return 0; +} +``` + +--- + +## Related Documentation + +- [node-system.md](node-system.md) — Base node interface +- [tables.md](tables.md) — Table container details +- [values.md](values.md) — Leaf value details +- [formatting.md](formatting.md) — Array formatting options diff --git a/docs/handbook/tomlplusplus/basic-usage.md b/docs/handbook/tomlplusplus/basic-usage.md new file mode 100644 index 0000000000..e11d47c42c --- /dev/null +++ b/docs/handbook/tomlplusplus/basic-usage.md @@ -0,0 +1,705 @@ +# toml++ — Basic Usage + +## Including the Library + +The simplest way to start using toml++ is with the default header-only mode: + +```cpp +#include <toml++/toml.hpp> +``` + +Or with the single-header drop-in: + +```cpp +#include "toml.hpp" +``` + +The library places everything in the `toml` namespace. Most examples use the string literal namespace: + +```cpp +using namespace std::string_view_literals; // for "..."sv +``` + +--- + +## Parsing TOML + +### Parsing a String + +```cpp +#include <toml++/toml.hpp> +#include <iostream> + +int main() +{ + auto tbl = toml::parse(R"( + title = "My Config" + + [database] + server = "192.168.1.1" + ports = [ 8001, 8001, 8002 ] + enabled = true + )"); + + std::cout << tbl << "\n"; + return 0; +} +``` + +`toml::parse()` accepts a `std::string_view` and returns a `toml::table` (when exceptions are enabled) or a `toml::parse_result` (when exceptions are disabled). + +### Parsing a File + +```cpp +auto tbl = toml::parse_file("config.toml"); +``` + +`toml::parse_file()` takes a file path as `std::string_view`, opens the file, and parses its contents. + +### Parsing from a Stream + +```cpp +#include <fstream> + +std::ifstream file("config.toml"); +auto tbl = toml::parse(file, "config.toml"); +``` + +The second argument is the source path used for error messages and `source()` metadata. + +### Parsing with Source Path + +You can provide a source path for diagnostic purposes: + +```cpp +auto tbl = toml::parse(toml_string, "my_config.toml"); +``` + +This path is stored in each node's `source().path` and appears in error messages. + +--- + +## Error Handling + +### With Exceptions (Default) + +When `TOML_EXCEPTIONS` is enabled (the default if you don't disable them), `toml::parse()` and `toml::parse_file()` throw `toml::parse_error` on failure: + +```cpp +try +{ + auto tbl = toml::parse_file("config.toml"); +} +catch (const toml::parse_error& err) +{ + std::cerr << "Parse error: " << err.description() << "\n"; + std::cerr << " at " << err.source() << "\n"; + // err.source().begin.line, err.source().begin.column +} +``` + +`toml::parse_error` inherits from `std::runtime_error` in this mode. + +### Without Exceptions + +When compiled with `TOML_EXCEPTIONS=0` (or with `-fno-exceptions`), `parse()` returns a `toml::parse_result`: + +```cpp +toml::parse_result result = toml::parse_file("config.toml"); + +if (result) +{ + // Success — result implicitly converts to table& + toml::table& tbl = result; + std::cout << tbl << "\n"; +} +else +{ + // Failure + std::cerr << "Parse error: " << result.error().description() << "\n"; + std::cerr << " at " << result.error().source() << "\n"; +} +``` + +`parse_result` is a discriminated union that holds either a `toml::table` or a `toml::parse_error`. It converts to `bool` for success checking. + +--- + +## Accessing Values + +### Using `operator[]` — The Easy Way + +`operator[]` on a `toml::table` returns a `toml::node_view`, which is a safe optional-like wrapper: + +```cpp +auto tbl = toml::parse(R"( + [server] + host = "localhost" + port = 8080 + debug = false + tags = ["web", "api"] +)"); + +// Chained access — never throws, returns empty view if path doesn't exist +auto host_view = tbl["server"]["host"]; +auto port_view = tbl["server"]["port"]; + +// Check if the view refers to a node +if (host_view) + std::cout << "Host exists\n"; +``` + +### Getting Values with `value<T>()` + +```cpp +// Returns std::optional<T> +std::optional<std::string_view> host = tbl["server"]["host"].value<std::string_view>(); +std::optional<int64_t> port = tbl["server"]["port"].value<int64_t>(); +std::optional<bool> debug = tbl["server"]["debug"].value<bool>(); + +if (host) + std::cout << "Host: " << *host << "\n"; +``` + +`value<T>()` is permissive — it allows some type conversions (e.g., reading an integer as a double). + +### Getting Values with `value_exact<T>()` + +```cpp +// Strict — only returns a value if the types match exactly +std::optional<int64_t> port = tbl["server"]["port"].value_exact<int64_t>(); +``` + +`value_exact<T>()` only succeeds if the underlying node is exactly that type. No conversions. + +### Getting Values with `value_or()` + +The most convenient accessor — returns the value or a default: + +```cpp +std::string_view host = tbl["server"]["host"].value_or("0.0.0.0"sv); +int64_t port = tbl["server"]["port"].value_or(80); +bool debug = tbl["server"]["debug"].value_or(true); + +// Safe even if the key doesn't exist: +std::string_view missing = tbl["nonexistent"]["key"].value_or("default"sv); +``` + +### Using `as<T>()` for Pointer Access + +`as<T>()` returns a pointer to the node if it matches the type, or `nullptr`: + +```cpp +if (auto* str_val = tbl["server"]["host"].as_string()) + std::cout << "Host: " << str_val->get() << "\n"; + +if (auto* port_val = tbl["server"]["port"].as_integer()) + std::cout << "Port: " << port_val->get() << "\n"; + +// Generic template version: +if (auto* arr = tbl["server"]["tags"].as<toml::array>()) + std::cout << "Tags count: " << arr->size() << "\n"; +``` + +Specific convenience methods exist: +- `as_table()` → `toml::table*` +- `as_array()` → `toml::array*` +- `as_string()` → `toml::value<std::string>*` +- `as_integer()` → `toml::value<int64_t>*` +- `as_floating_point()` → `toml::value<double>*` +- `as_boolean()` → `toml::value<bool>*` +- `as_date()` → `toml::value<toml::date>*` +- `as_time()` → `toml::value<toml::time>*` +- `as_date_time()` → `toml::value<toml::date_time>*` + +### Direct Node Access with `get()` + +On `toml::table`: +```cpp +toml::node* node = tbl.get("server"); +if (node && node->is_table()) +{ + toml::table& server = *node->as_table(); + // ... +} +``` + +On `toml::array`: +```cpp +auto* arr = tbl["server"]["tags"].as_array(); +if (arr && arr->size() > 0) +{ + toml::node& first = (*arr)[0]; + std::cout << first.value_or(""sv) << "\n"; +} +``` + +### Typed get with `get_as<T>()` + +```cpp +// On table — returns pointer if key exists AND matches type +if (auto* val = tbl.get_as<std::string>("title")) + std::cout << "Title: " << val->get() << "\n"; + +// On array — returns pointer if index is valid AND matches type +auto* arr = tbl["tags"].as_array(); +if (arr) +{ + if (auto* s = arr->get_as<std::string>(0)) + std::cout << "First tag: " << s->get() << "\n"; +} +``` + +--- + +## Iterating Tables + +### Range-based For Loop + +```cpp +auto tbl = toml::parse(R"( + a = 1 + b = "hello" + c = true +)"); + +for (auto&& [key, value] : tbl) +{ + std::cout << key << " = " << value << " (type: " << value.type() << ")\n"; +} +``` + +Output: +``` +a = 1 (type: integer) +b = "hello" (type: string) +c = true (type: boolean) +``` + +### Using `for_each()` + +`for_each()` calls a visitor with each key-value pair. The value is passed as its concrete type: + +```cpp +tbl.for_each([](auto& key, auto& value) +{ + std::cout << key << ": "; + if constexpr (toml::is_string<decltype(value)>) + std::cout << "string = " << value.get() << "\n"; + else if constexpr (toml::is_integer<decltype(value)>) + std::cout << "integer = " << value.get() << "\n"; + else if constexpr (toml::is_boolean<decltype(value)>) + std::cout << "boolean = " << value.get() << "\n"; + else + std::cout << "(other)\n"; +}); +``` + +--- + +## Iterating Arrays + +### Range-based For Loop + +```cpp +auto tbl = toml::parse(R"( + numbers = [1, 2, 3, 4, 5] +)"); + +auto& arr = *tbl["numbers"].as_array(); +for (auto& elem : arr) +{ + std::cout << elem.value_or(0) << " "; +} +// Output: 1 2 3 4 5 +``` + +### Index-based Access + +```cpp +for (size_t i = 0; i < arr.size(); i++) +{ + std::cout << arr[i].value_or(0) << " "; +} +``` + +### Using `for_each()` + +```cpp +arr.for_each([](size_t index, auto& elem) +{ + if constexpr (toml::is_integer<decltype(elem)>) + std::cout << "[" << index << "] = " << elem.get() << "\n"; +}); +``` + +--- + +## Creating TOML Programmatically + +### Constructing a Table + +```cpp +auto tbl = toml::table{ + { "title", "My Application" }, + { "version", 2 }, + { "debug", false }, + { "database", toml::table{ + { "host", "localhost" }, + { "port", 5432 } + }}, + { "tags", toml::array{ "web", "api", "rest" } } +}; + +std::cout << tbl << "\n"; +``` + +Output: +```toml +title = "My Application" +version = 2 +debug = false +tags = ["web", "api", "rest"] + +[database] +host = "localhost" +port = 5432 +``` + +### Inserting Values + +```cpp +toml::table config; + +// insert() — only inserts if key doesn't exist +config.insert("name", "MyApp"); +config.insert("name", "Overwritten"); // no-op, key already exists + +// insert_or_assign() — inserts or replaces +config.insert_or_assign("name", "ReplacedApp"); + +// emplace() — construct in place if key doesn't exist +config.emplace<std::string>("greeting", "Hello, World!"); +``` + +### Building Arrays + +```cpp +toml::array arr; +arr.push_back(1); +arr.push_back(2); +arr.push_back(3); +arr.push_back("mixed types are fine"); + +// Or construct directly: +auto arr2 = toml::array{ 10, 20, 30 }; + +// Emplace: +arr2.emplace_back<std::string>("hello"); +``` + +### Creating Date/Time Values + +```cpp +auto tbl = toml::table{ + { "birthday", toml::date{ 1990, 6, 15 } }, + { "alarm", toml::time{ 7, 30 } }, + { "event", toml::date_time{ + toml::date{ 2024, 12, 25 }, + toml::time{ 9, 0 }, + toml::time_offset{ -5, 0 } // EST + }} +}; + +std::cout << tbl << "\n"; +``` + +Output: +```toml +birthday = 1990-06-15 +alarm = 07:30:00 +event = 2024-12-25T09:00:00-05:00 +``` + +--- + +## Modifying Parsed Data + +```cpp +auto tbl = toml::parse(R"( + [server] + host = "localhost" + port = 8080 +)"); + +// Change a value +tbl.insert_or_assign("server", toml::table{ + { "host", "0.0.0.0" }, + { "port", 443 }, + { "ssl", true } +}); + +// Add a new section +tbl.insert("logging", toml::table{ + { "level", "info" }, + { "file", "/var/log/app.log" } +}); + +// Remove a key +if (auto* server = tbl["server"].as_table()) + server->erase("ssl"); + +// Modify array +tbl.insert("features", toml::array{ "auth", "cache" }); +if (auto* features = tbl["features"].as_array()) +{ + features->push_back("logging"); + features->insert(features->begin(), "core"); +} + +std::cout << tbl << "\n"; +``` + +--- + +## Serialization + +### To TOML (Default) + +Simply stream a table or use `toml_formatter`: + +```cpp +// These are equivalent: +std::cout << tbl << "\n"; +std::cout << toml::toml_formatter{ tbl } << "\n"; +``` + +### To JSON + +```cpp +std::cout << toml::json_formatter{ tbl } << "\n"; +``` + +### To YAML + +```cpp +std::cout << toml::yaml_formatter{ tbl } << "\n"; +``` + +### To a String + +```cpp +#include <sstream> + +std::ostringstream ss; +ss << tbl; +std::string toml_string = ss.str(); + +// Or as JSON: +ss.str(""); +ss << toml::json_formatter{ tbl }; +std::string json_string = ss.str(); +``` + +### To a File + +```cpp +#include <fstream> + +std::ofstream file("output.toml"); +file << tbl; +``` + +--- + +## Path-Based Access + +### Using `at_path()` + +```cpp +auto tbl = toml::parse(R"( + [database] + servers = [ + { host = "alpha", port = 5432 }, + { host = "beta", port = 5433 } + ] +)"); + +// Dot-separated path with array indices +auto host = toml::at_path(tbl, "database.servers[0].host"); +std::cout << host.value_or("unknown"sv) << "\n"; // "alpha" + +auto port = toml::at_path(tbl, "database.servers[1].port"); +std::cout << port.value_or(0) << "\n"; // 5433 +``` + +### Using `toml::path` + +```cpp +toml::path p("database.servers[0].host"); +auto view = tbl[p]; +std::cout << view.value_or("unknown"sv) << "\n"; + +// Path manipulation +toml::path parent = p.parent_path(); // "database.servers[0]" +std::cout << tbl[parent] << "\n"; // { host = "alpha", port = 5432 } +``` + +--- + +## The Visitor Pattern + +### Using `visit()` + +```cpp +toml::node& some_node = *tbl.get("title"); + +some_node.visit([](auto& val) +{ + // val is the concrete type: table&, array&, or value<T>& + using T = std::remove_cvref_t<decltype(val)>; + + if constexpr (std::is_same_v<T, toml::table>) + std::cout << "It's a table\n"; + else if constexpr (std::is_same_v<T, toml::array>) + std::cout << "It's an array\n"; + else + std::cout << "It's a value: " << val.get() << "\n"; +}); +``` + +### Using `for_each()` on Tables and Arrays + +`for_each()` iterates and visits each element with its concrete type: + +```cpp +tbl.for_each([](const toml::key& key, auto& value) +{ + std::cout << key << " -> " << value << "\n"; +}); +``` + +--- + +## Source Information + +Every parsed node tracks where it was defined: + +```cpp +auto tbl = toml::parse_file("config.toml"); + +if (auto* name = tbl.get("name")) +{ + auto& src = name->source(); + std::cout << "Defined at line " << src.begin.line + << ", column " << src.begin.column << "\n"; + + if (src.path) + std::cout << "In file: " << *src.path << "\n"; +} +``` + +--- + +## Type Checking + +```cpp +toml::node& node = /* some node */; + +// Virtual method checks +if (node.is_string()) { /* ... */ } +if (node.is_integer()) { /* ... */ } +if (node.is_table()) { /* ... */ } + +// Template check +if (node.is<double>()) { /* ... */ } +if (node.is<toml::array>()) { /* ... */ } + +// Get the type enum +switch (node.type()) +{ + case toml::node_type::string: break; + case toml::node_type::integer: break; + case toml::node_type::floating_point: break; + case toml::node_type::boolean: break; + case toml::node_type::date: break; + case toml::node_type::time: break; + case toml::node_type::date_time: break; + case toml::node_type::table: break; + case toml::node_type::array: break; + default: break; +} +``` + +--- + +## Complete Example: Config File Reader + +```cpp +#include <toml++/toml.hpp> +#include <iostream> +#include <string_view> + +using namespace std::string_view_literals; + +int main() +{ + toml::table config; + try + { + config = toml::parse_file("app.toml"); + } + catch (const toml::parse_error& err) + { + std::cerr << "Failed to parse config:\n" << err << "\n"; + return 1; + } + + // Read application settings + auto app_name = config["app"]["name"].value_or("Unknown"sv); + auto app_version = config["app"]["version"].value_or(1); + auto log_level = config["logging"]["level"].value_or("info"sv); + auto log_file = config["logging"]["file"].value_or("/tmp/app.log"sv); + + std::cout << "Application: " << app_name << " v" << app_version << "\n"; + std::cout << "Log level: " << log_level << "\n"; + std::cout << "Log file: " << log_file << "\n"; + + // Read database connections + if (auto* dbs = config["databases"].as_array()) + { + for (auto& db_node : *dbs) + { + if (auto* db = db_node.as_table()) + { + auto host = (*db)["host"].value_or("localhost"sv); + auto port = (*db)["port"].value_or(5432); + auto name = (*db)["name"].value_or("mydb"sv); + std::cout << "DB: " << name << " @ " << host << ":" << port << "\n"; + } + } + } + + // Modify and write back + config.insert_or_assign("last_run", toml::date_time{ + toml::date{ 2024, 1, 15 }, + toml::time{ 14, 30, 0 } + }); + + std::ofstream out("app.toml"); + out << config; + + return 0; +} +``` + +--- + +## Related Documentation + +- [node-system.md](node-system.md) — Deep dive into node types and value retrieval +- [tables.md](tables.md) — Table manipulation details +- [arrays.md](arrays.md) — Array manipulation details +- [parsing.md](parsing.md) — Parser internals and error handling +- [formatting.md](formatting.md) — Serialization customization +- [path-system.md](path-system.md) — Path-based navigation diff --git a/docs/handbook/tomlplusplus/building.md b/docs/handbook/tomlplusplus/building.md new file mode 100644 index 0000000000..c70c76c297 --- /dev/null +++ b/docs/handbook/tomlplusplus/building.md @@ -0,0 +1,474 @@ +# toml++ — Building + +## Overview + +toml++ supports multiple build modes and build systems. It can be consumed as a header-only library, a single-header drop-in, or a compiled static/shared library. The primary build system is Meson, with CMake as a first-class alternative and Visual Studio project files also provided. + +--- + +## Build Modes + +### 1. Header-Only Mode (Default) + +The simplest way to use toml++. No compilation of the library itself is needed. + +**Setup:** +1. Add `tomlplusplus/include` to your include paths +2. `#include <toml++/toml.hpp>` in your source files +3. Compile your project with C++17 or later + +This is the default mode. The macro `TOML_HEADER_ONLY` defaults to `1`. + +**Advantages:** +- Zero build configuration +- No separate library to link +- Works with any build system + +**Disadvantages:** +- Every translation unit that includes toml++ compiles the full implementation +- Can increase compile times in large projects + +**Example CMake integration:** +```cmake +# Add tomlplusplus as a subdirectory or fetch it +add_subdirectory(external/tomlplusplus) +target_link_libraries(my_target PRIVATE tomlplusplus::tomlplusplus) +``` + +### 2. Single-Header Mode + +toml++ ships with a pre-amalgamated single-header file at the repository root: `toml.hpp`. + +**Setup:** +1. Copy `toml.hpp` into your project +2. `#include "toml.hpp"` in your source files +3. Done + +This file contains the entire library — all headers, all `.inl` implementation files — concatenated into one file. The API is identical to the multi-header version. + +### 3. Compiled Library Mode + +For projects where compile time matters, toml++ can be built as a compiled library. + +**Setup:** + +In exactly **one** translation unit, compile `src/toml.cpp`: + +```cpp +// src/toml.cpp — this is the entire compiled-library source +#ifndef TOML_IMPLEMENTATION +#define TOML_IMPLEMENTATION +#endif +#ifndef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 0 +#endif + +#include <toml++/toml.hpp> +``` + +In all other translation units, define `TOML_HEADER_ONLY=0` before including the header: + +```cpp +#define TOML_HEADER_ONLY 0 +#include <toml++/toml.hpp> +``` + +Or set it project-wide via compiler flags: +```bash +g++ -DTOML_HEADER_ONLY=0 -I/path/to/tomlplusplus/include ... +``` + +**Advantages:** +- The parser and formatter implementations are compiled once +- Faster incremental builds +- Smaller binary size (fewer inlined copies) + +**Disadvantages:** +- Requires linking the compiled translation unit +- Need to ensure `TOML_HEADER_ONLY=0` is consistent across all TUs + +### 4. C++20 Modules Mode + +When using CMake 3.28+ and a C++20 compiler: + +```cmake +cmake -DTOMLPLUSPLUS_BUILD_MODULES=ON .. +``` + +Then in your source: +```cpp +import tomlplusplus; +``` + +Module support is experimental and requires: +- CMake ≥ 3.28 +- A compiler with C++20 module support (recent GCC, Clang, or MSVC) + +The module source files are in `src/modules/`. + +--- + +## Meson Build System + +Meson is the primary build system for toml++. The project file is `meson.build` at the repository root. + +### Project Definition + +```meson +project( + 'tomlplusplus', + 'cpp', + license: 'MIT', + version: '3.4.0', + meson_version: '>=0.61.0', + default_options: [ + 'buildtype=release', + 'default_library=shared', + 'b_lto=false', + 'b_ndebug=if-release', + 'cpp_std=c++17' + ] +) +``` + +### Meson Options + +Options are defined in `meson_options.txt`: + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `devel` | bool | `false` | Development build (implies `build_tests`, `build_examples`, `pedantic`) | +| `build_lib` | bool | `false` | Compile as a library (implied by `devel`) | +| `build_examples` | bool | `false` | Build example programs (implied by `devel`) | +| `build_tests` | bool | `false` | Build test suite (implied by `devel`) | +| `build_tt` | bool | `false` | Build toml-test encoder/decoder (implied by `devel`, disabled by `unreleased_features`) | +| `pedantic` | bool | `false` | Enable maximum compiler warnings (implied by `devel`) | +| `permissive` | bool | `false` | MSVC `/permissive` mode (default is `/permissive-`) | +| `time_trace` | bool | `false` | Enable `-ftime-trace` (Clang only) | +| `unreleased_features` | bool | `false` | Enable `TOML_UNRELEASED_FEATURES=1` | +| `generate_cmake_config` | bool | `true` | Generate a CMake package config file | +| `use_vendored_libs` | bool | `true` | Use vendored Catch2 for tests | + +### Building with Meson + +```bash +# Configure +meson setup build +# Or with options: +meson setup build -Dbuild_tests=true -Dbuild_examples=true + +# Compile +meson compile -C build + +# Run tests +meson test -C build + +# Development build (builds everything, enables warnings) +meson setup build -Ddevel=true +``` + +### Using as a Meson Subproject + +Create `subprojects/tomlplusplus.wrap`: +```ini +[wrap-git] +url = https://github.com/marzer/tomlplusplus.git +revision = v3.4.0 + +[provide] +tomlplusplus = tomlplusplus_dep +``` + +Then in your `meson.build`: +```meson +tomlplusplus_dep = dependency('tomlplusplus', version: '>=3.4.0') +executable('my_app', 'main.cpp', dependencies: [tomlplusplus_dep]) +``` + +### Meson Library Target + +When `build_lib` is true (or implied), the library is compiled: + +```meson +# In the meson.build, the library creates a tomlplusplus_dep dependency +# that other targets consume +``` + +The compiled library defines: +- `TOML_HEADER_ONLY=0` +- `TOML_IMPLEMENTATION` + +### Compiler Flag Management + +The Meson build applies comprehensive compiler flags based on the detected compiler: + +**Common flags:** +``` +-ferror-limit=5 # Clang: max errors +-fmax-errors=5 # GCC: max errors +-fchar8_t # Enable char8_t +``` + +**MSVC-specific:** +``` +/bigobj # Large object file support +/utf-8 # UTF-8 source encoding +/Zc:__cplusplus # Correct __cplusplus value +/Zc:inline # Remove unreferenced COMDAT +/Zc:externConstexpr # External constexpr linkage +/Zc:preprocessor # Standards-conforming preprocessor +``` + +**Pedantic mode** enables extensive warning flags for both GCC and Clang (`-Weverything`, `-Wcast-align`, `-Wshadow`, etc.) with targeted suppressions for unavoidable warnings (`-Wno-c++98-compat`, `-Wno-padded`, etc.). + +--- + +## CMake Build System + +### CMake Project + +The `CMakeLists.txt` defines an interface (header-only) library: + +```cmake +cmake_minimum_required(VERSION 3.14) + +project( + tomlplusplus + VERSION 3.4.0 + DESCRIPTION "Header-only TOML config file parser and serializer for C++17" + HOMEPAGE_URL "https://marzer.github.io/tomlplusplus/" + LANGUAGES CXX +) + +add_library(tomlplusplus_tomlplusplus INTERFACE) +add_library(tomlplusplus::tomlplusplus ALIAS tomlplusplus_tomlplusplus) + +target_include_directories( + tomlplusplus_tomlplusplus + INTERFACE + "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" +) + +target_compile_features(tomlplusplus_tomlplusplus INTERFACE cxx_std_17) +``` + +### Using with CMake — Subdirectory + +```cmake +add_subdirectory(path/to/tomlplusplus) +target_link_libraries(my_target PRIVATE tomlplusplus::tomlplusplus) +``` + +### Using with CMake — FetchContent + +```cmake +include(FetchContent) +FetchContent_Declare( + tomlplusplus + GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git + GIT_TAG v3.4.0 +) +FetchContent_MakeAvailable(tomlplusplus) + +target_link_libraries(my_target PRIVATE tomlplusplus::tomlplusplus) +``` + +### Using with CMake — find_package + +If toml++ is installed system-wide or the CMake config was generated: + +```cmake +find_package(tomlplusplus REQUIRED) +target_link_libraries(my_target PRIVATE tomlplusplus::tomlplusplus) +``` + +### CMake Options + +```cmake +option(BUILD_EXAMPLES "Build examples tree." OFF) +option(BUILD_FUZZER "Build fuzzer." OFF) +option(TOMLPLUSPLUS_BUILD_MODULES "Build C++ modules support" OFF) +``` + +### CMake Install + +When `tomlplusplus_INSTALL` is true, install rules are included from `cmake/install-rules.cmake`. + +--- + +## Visual Studio + +The repository includes Visual Studio project files: +- `toml++.sln` — Solution file +- `toml++.vcxproj` — Main project file +- `toml++.vcxproj.filters` — Filter definition +- `toml++.props` — Property sheet +- `toml++.natvis` — Natvis debugger visualizer for TOML node types + +Individual examples also have `.vcxproj` files: +- `examples/simple_parser.vcxproj` +- `examples/toml_to_json_transcoder.vcxproj` +- `examples/toml_generator.vcxproj` +- `examples/error_printer.vcxproj` +- `examples/parse_benchmark.vcxproj` + +--- + +## Package Managers + +### Vcpkg + +```bash +vcpkg install tomlplusplus +``` + +### Conan + +In your `conanfile.txt`: +``` +[requires] +tomlplusplus/3.4.0 +``` + +### DDS + +In your `package.json5`: +```json5 +depends: [ + 'tomlpp^3.4.0', +] +``` + +### tipi.build + +In `.tipi/deps`: +```json +{ + "marzer/tomlplusplus": {} +} +``` + +--- + +## Compiler Requirements + +### Minimum Versions + +| Compiler | Minimum Version | +|----------|----------------| +| GCC | 8+ | +| Clang | 8+ | +| Apple Clang | Xcode 10+ | +| MSVC | VS2019 (19.20+) | +| Intel C++ | ICC 19+, ICL 19+ | + +### Required Standard + +C++17 is required. The preprocessor enforces this: + +```cpp +#if TOML_CPP < 17 +#error toml++ requires C++17 or higher. +#endif +``` + +### Compiler Feature Detection + +The library detects compiler capabilities: + +```cpp +// TOML_CPP — detected C++ standard version (11, 14, 17, 20, 23, 26, 29) +// TOML_GCC — GCC major version (0 if not GCC) +// TOML_CLANG — Clang major version (0 if not Clang) +// TOML_MSVC — MSVC version (0 if not MSVC) +// TOML_ICC — Intel compiler detection +// TOML_NVCC — NVIDIA CUDA compiler detection +// TOML_HAS_CHAR8 — char8_t available +// TOML_HAS_EXCEPTIONS — exceptions enabled +``` + +--- + +## Configuration Macros Reference + +Define these **before** including `<toml++/toml.hpp>`: + +### Core Configuration + +```cpp +// Library mode +#define TOML_HEADER_ONLY 1 // 1 = header-only (default), 0 = compiled + +// Feature toggles +#define TOML_ENABLE_PARSER 1 // 1 = include parser (default), 0 = no parser +#define TOML_ENABLE_FORMATTERS 1 // 1 = include formatters (default), 0 = no formatters + +// Exception handling +// Auto-detected from compiler settings. Override with: +#define TOML_EXCEPTIONS 1 // or 0 + +// Unreleased TOML features +#define TOML_UNRELEASED_FEATURES 0 // 1 = enable upcoming TOML spec features +``` + +### Platform Configuration + +```cpp +// Windows wide-string support (auto-detected on Windows) +#define TOML_ENABLE_WINDOWS_COMPAT 1 + +// Custom optional type +#define TOML_OPTIONAL_TYPE std::optional // or your custom type + +// Disable environment checks +#define TOML_DISABLE_ENVIRONMENT_CHECKS +``` + +### Example: Minimal Parse-Only Build + +```cpp +#define TOML_ENABLE_FORMATTERS 0 // Don't need serialization +#include <toml++/toml.hpp> +``` + +### Example: Serialize-Only Build + +```cpp +#define TOML_ENABLE_PARSER 0 // Don't need parsing +#include <toml++/toml.hpp> +``` + +--- + +## Build Troubleshooting + +### Common Issues + +**"toml++ requires C++17 or higher"** +Ensure your compiler is invoked with `-std=c++17` (or later) or the equivalent flag. + +**Large object files on MSVC** +Use `/bigobj` flag (the Meson build adds this automatically). + +**Long compile times** +Switch to compiled library mode (`TOML_HEADER_ONLY=0` + compile `src/toml.cpp`). + +**ODR violations when mixing settings** +Ensure all translation units use the same values for `TOML_EXCEPTIONS`, `TOML_ENABLE_PARSER`, etc. The ABI namespace system catches some mismatches at link time, but not all. + +**`char8_t` errors on older compilers** +Add `-fchar8_t` flag if your compiler supports it, or compile with C++20 mode. + +**RTTI disabled** +toml++ does not require RTTI. It uses virtual dispatch, not `dynamic_cast` or `typeid`. + +**Exceptions disabled** +Set `TOML_EXCEPTIONS=0` or use `-fno-exceptions`. The API adapts: `parse()` returns `parse_result` instead of throwing. + +--- + +## Related Documentation + +- [overview.md](overview.md) — Library feature list +- [basic-usage.md](basic-usage.md) — Getting started with parsing and serialization +- [testing.md](testing.md) — Running the test suite diff --git a/docs/handbook/tomlplusplus/code-style.md b/docs/handbook/tomlplusplus/code-style.md new file mode 100644 index 0000000000..43c16d3ce4 --- /dev/null +++ b/docs/handbook/tomlplusplus/code-style.md @@ -0,0 +1,277 @@ +# toml++ — Code Style + +## Overview + +This document describes the code conventions and formatting rules used in the toml++ project, derived from the `.clang-format` configuration and source code patterns. + +--- + +## Formatting Rules (`.clang-format`) + +The project uses clang-format with these key settings: + +### Indentation + +- **IndentWidth**: 4 (tabs are used, tab width 4) +- **UseTab**: `ForContinuationAndIndentation` +- **TabWidth**: 4 +- **ContinuationIndentWidth**: 4 +- **ConstructorInitializerIndentWidth**: 4 +- **AccessModifierOffset**: -4 (access specifiers at class indent level) +- **IndentCaseLabels**: true +- **NamespaceIndentation**: All + +### Braces + +- **BreakBeforeBraces**: Allman style + - Functions, classes, structs, enums, namespaces, control statements — all open brace on new line: + +```cpp +namespace toml +{ + class node + { + public: + void method() + { + if (condition) + { + // ... + } + } + }; +} +``` + +### Alignment + +- **AlignConsecutiveAssignments**: true +- **AlignConsecutiveDeclarations**: true +- **AlignTrailingComments**: true +- **AlignOperands**: true +- **AlignAfterOpenBracket**: Align + +### Line Length + +- **ColumnLimit**: 120 + +### Other Settings + +- **AllowShortFunctionsOnASingleLine**: Empty (empty functions on one line) +- **AllowShortIfStatementsOnASingleLine**: Never +- **AllowShortLoopsOnASingleLine**: false +- **AlwaysBreakTemplateDeclarations**: Yes +- **BinPackArguments**: false +- **BinPackParameters**: false +- **PointerAlignment**: Left (`int* ptr`, not `int *ptr`) +- **SpaceAfterTemplateKeyword**: true +- **SortIncludes**: false (manual include ordering) + +--- + +## Naming Conventions + +### Macros + +All macros use the `TOML_` prefix with `UPPER_SNAKE_CASE`: + +```cpp +TOML_HEADER_ONLY +TOML_EXCEPTIONS +TOML_ENABLE_PARSER +TOML_ENABLE_FORMATTERS +TOML_ENABLE_WINDOWS_COMPAT +TOML_UNRELEASED_FEATURES +TOML_LIB_MAJOR +TOML_NAMESPACE_START +TOML_NAMESPACE_END +TOML_EXPORTED_CLASS +TOML_EXPORTED_MEMBER_FUNCTION +TOML_EXPORTED_STATIC_FUNCTION +TOML_EXPORTED_FREE_FUNCTION +``` + +### Namespaces + +- Public API: `toml` namespace (aliased from a versioned namespace `toml::vN`) +- Internal implementation: `toml::impl` (aka `toml::vN::impl`) +- Macro-managed namespace boundaries: + +```cpp +TOML_NAMESPACE_START // opens toml::v3 +{ + // public API +} +TOML_NAMESPACE_END // closes + +TOML_IMPL_NAMESPACE_START // opens toml::v3::impl +{ + // internal details +} +TOML_IMPL_NAMESPACE_END +``` + +### Types and Classes + +- `snake_case` for all types: `node`, `table`, `array`, `value`, `path`, `path_component`, `parse_result`, `parse_error`, `source_region`, `source_position`, `date_time`, `time_offset`, `node_view`, `key` +- Template parameters: `PascalCase` (`ValueType`, `IsConst`, `ViewedType`, `ElemType`) + +### Member Variables + +- Private members use trailing underscore: `val_`, `flags_`, `elems_`, `map_`, `inline_`, `source_`, `components_` +- No prefix for public struct fields: `year`, `month`, `day`, `line`, `column`, `begin`, `end`, `path` + +### Methods + +- `snake_case`: `is_table()`, `as_array()`, `value_or()`, `push_back()`, `emplace_back()`, `is_homogeneous()`, `for_each()`, `parse_file()`, `at_path()` + +### Enums + +- `snake_case` enum type names: `node_type`, `value_flags`, `format_flags`, `path_component_type` +- `snake_case` enum values: `node_type::string`, `value_flags::format_as_hexadecimal`, `format_flags::indent_sub_tables` + +--- + +## Header Organization + +### File Pairs + +Most features have a `.hpp` declaration header and a `.inl` implementation file: + +``` +node.hpp / node.inl +table.hpp / table.inl +array.hpp / array.inl +parser.hpp / parser.inl +formatter.hpp / formatter.inl +``` + +### Include Guards + +Headers use `#pragma once` (no traditional include guards). + +### Header Structure + +Typical header layout: + +```cpp +// license header comment +#pragma once + +#include "preprocessor.hpp" // macros and config +#include "forward_declarations.hpp" // forward declarations +// ... other includes + +// Header-only mode guard +#if defined(TOML_IMPLEMENTATION) || !TOML_HEADER_ONLY + +TOML_NAMESPACE_START +{ + // declarations / implementations +} +TOML_NAMESPACE_END + +#endif // TOML_IMPLEMENTATION +``` + +### Export Annotations + +Exported symbols use macros for DLL visibility: + +```cpp +TOML_EXPORTED_CLASS table : public node +{ + TOML_EXPORTED_MEMBER_FUNCTION void clear() noexcept; + TOML_EXPORTED_STATIC_FUNCTION static table parse(...); +}; + +TOML_EXPORTED_FREE_FUNCTION parse_result parse(std::string_view); +``` + +--- + +## Preprocessor Conventions + +### Compiler Detection + +```cpp +TOML_GCC // GCC +TOML_CLANG // Clang +TOML_MSVC // MSVC +TOML_ICC // Intel C++ +TOML_ICC_CL // Intel C++ (MSVC frontend) +``` + +### Feature Detection + +```cpp +TOML_HAS_CHAR8 // char8_t available +TOML_HAS_CUSTOM_OPTIONAL_TYPE // user-provided optional +TOML_INT_CHARCONV // charconv for integers +TOML_FLOAT_CHARCONV // charconv for floats +``` + +### Warning Management + +Extensive `#pragma` blocks suppress known-benign warnings per compiler: + +```cpp +TOML_PUSH_WARNINGS +TOML_DISABLE_WARNINGS +// ... code ... +TOML_POP_WARNINGS + +TOML_DISABLE_ARITHMETIC_WARNINGS +TOML_DISABLE_SPAM_WARNINGS +``` + +--- + +## Conditional Compilation Patterns + +Major features are conditionally compiled: + +```cpp +#if TOML_ENABLE_PARSER + // parser code +#endif + +#if TOML_ENABLE_FORMATTERS + // formatter code +#endif + +#if TOML_ENABLE_WINDOWS_COMPAT + // wchar_t / wstring overloads +#endif + +#if TOML_EXCEPTIONS + // exception-based error handling +#else + // return-code error handling +#endif +``` + +--- + +## Documentation Conventions + +- Source comments use `//` style (not `/* */`) +- Doxygen is used for API documentation (the public `toml.hpp` single-header has `///` comments) +- Internal implementation headers have minimal comments — the code is expected to be self-documenting + +--- + +## Build System Conventions + +- Primary build: **Meson** (`meson.build`, `meson_options.txt`) +- Secondary: **CMake** (`CMakeLists.txt`) +- All configuration macros can be set via build system options or via `#define` before including the header +- Meson option names mirror the macro names: `is_header_only` → `TOML_HEADER_ONLY` + +--- + +## Related Documentation + +- [architecture.md](architecture.md) — Project structure and design +- [building.md](building.md) — Build system details +- [testing.md](testing.md) — Testing conventions diff --git a/docs/handbook/tomlplusplus/formatting.md b/docs/handbook/tomlplusplus/formatting.md new file mode 100644 index 0000000000..d46a433a86 --- /dev/null +++ b/docs/handbook/tomlplusplus/formatting.md @@ -0,0 +1,546 @@ +# toml++ — Formatting + +## Overview + +toml++ includes three formatters for serializing a TOML node tree to text: + +| Formatter | Output Format | Header | +|-----------|--------------|--------| +| `toml::toml_formatter` | Standard TOML | `toml_formatter.hpp` | +| `toml::json_formatter` | JSON | `json_formatter.hpp` | +| `toml::yaml_formatter` | YAML | `yaml_formatter.hpp` | + +All three inherit from the internal `impl::formatter` base class and share common indentation and streaming infrastructure. + +Formatters can be disabled entirely via `TOML_ENABLE_FORMATTERS=0`. + +--- + +## Base Formatter (`impl::formatter`) + +Declared in `include/toml++/impl/formatter.hpp`. Not directly instantiable — used through the concrete subclasses. + +### `formatter_constants` + +Each formatter defines a set of string constants: + +```cpp +struct formatter_constants +{ + format_flags mandatory_flags; // flags always applied + format_flags ignored_flags; // flags explicitly not applied + + std::string_view float_pos_inf; // "+inf", "Infinity", ".inf" + std::string_view float_neg_inf; // "-inf", "-Infinity", "-.inf" + std::string_view float_nan; // "nan", "NaN", ".nan" + + std::string_view bool_true; // "true" + std::string_view bool_false; // "false" +}; +``` + +### `formatter_config` + +```cpp +struct formatter_config +{ + format_flags flags; // active formatting flags +}; +``` + +### Internal State + +The base class manages: +- `const node* source_` — the node being formatted +- `formatter_constants constants_` — string representations +- `formatter_config config_` — user-supplied configuration +- `int indent_` — current indentation level +- `bool naked_newline_` — tracks newline state + +Helper methods: +- `increase_indent()` / `decrease_indent()` — adjust indentation +- `print_indent()` — emit current indentation +- `print_newline()` — emit newline with proper tracking +- `print_string()` — format a TOML string with proper escaping +- `print_value()` — format a leaf value (delegates to constants for inf/nan/bool) + +--- + +## `format_flags` + +Bitmask enum controlling formatting behavior: + +```cpp +enum class format_flags : uint64_t +{ + none = 0, + quote_dates_and_times = (1ull << 0), + quote_infinities_and_nans = (1ull << 1), + allow_literal_strings = (1ull << 2), + allow_multi_line_strings = (1ull << 3), + allow_real_tabs_in_values = (1ull << 4), + allow_unicode_strings = (1ull << 5), + allow_binary_integers = (1ull << 6), + allow_octal_integers = (1ull << 7), + allow_hexadecimal_integers = (1ull << 8), + indent_sub_tables = (1ull << 9), + indent_array_elements = (1ull << 10), + indentation = indent_sub_tables | indent_array_elements, + relaxed_float_precision = (1ull << 11), + terse_key_value_pairs = (1ull << 12), +}; +``` + +### Flag Details + +| Flag | Effect | +|------|--------| +| `quote_dates_and_times` | Emit dates/times as `"2024-01-15"` instead of `2024-01-15` | +| `quote_infinities_and_nans` | Emit `"inf"` / `"nan"` as quoted strings | +| `allow_literal_strings` | Use `'single quotes'` where possible | +| `allow_multi_line_strings` | Use `"""multi-line"""` where appropriate | +| `allow_real_tabs_in_values` | Emit `\t` as literal tab instead of escape | +| `allow_unicode_strings` | Keep Unicode characters instead of escaping to `\uXXXX` | +| `allow_binary_integers` | Emit `0b1010` for binary-flagged integers | +| `allow_octal_integers` | Emit `0o755` for octal-flagged integers | +| `allow_hexadecimal_integers` | Emit `0xFF` for hex-flagged integers | +| `indent_sub_tables` | Indent sub-table content | +| `indent_array_elements` | Indent array elements on separate lines | +| `relaxed_float_precision` | Use less precision for floats | +| `terse_key_value_pairs` | Emit `key=value` instead of `key = value` | + +--- + +## TOML Formatter + +### Constants + +```cpp +static constexpr formatter_constants toml_formatter_constants = { + // mandatory_flags: + format_flags::allow_literal_strings + | format_flags::allow_multi_line_strings + | format_flags::allow_unicode_strings + | format_flags::allow_binary_integers + | format_flags::allow_octal_integers + | format_flags::allow_hexadecimal_integers, + + // ignored_flags: + format_flags::quote_dates_and_times + | format_flags::quote_infinities_and_nans, + + // float_pos_inf, float_neg_inf, float_nan: + "inf"sv, "-inf"sv, "nan"sv, + + // bool_true, bool_false: + "true"sv, "false"sv +}; +``` + +### Default Flags + +```cpp +static constexpr format_flags default_flags = + format_flags::allow_literal_strings + | format_flags::allow_multi_line_strings + | format_flags::allow_unicode_strings + | format_flags::allow_binary_integers + | format_flags::allow_octal_integers + | format_flags::allow_hexadecimal_integers + | format_flags::indentation; +``` + +### Construction + +```cpp +// Format a table (most common) +toml::toml_formatter fmt{ my_table }; +std::cout << fmt; + +// With custom flags +toml::toml_formatter fmt2{ my_table, format_flags::indent_sub_tables }; +std::cout << fmt2; + +// Format any node (array, value, etc.) +toml::toml_formatter fmt3{ my_array }; +std::cout << fmt3; +``` + +### Key Path Tracking + +The TOML formatter maintains a `key_path_` to correctly generate fully-qualified section headers: + +```cpp +auto tbl = toml::parse(R"( + [server.database] + host = "localhost" + port = 5432 +)"); + +std::cout << toml::toml_formatter{ tbl }; +``` + +Output: +```toml +[server.database] +host = "localhost" +port = 5432 +``` + +### Inline Tables + +Tables marked as inline are output as `{ key = val, ... }`: + +```cpp +auto tbl = toml::table{ + { "point", toml::table{ { "x", 1 }, { "y", 2 } } } +}; +tbl["point"].as_table()->is_inline(true); + +std::cout << toml::toml_formatter{ tbl }; +// Output: point = { x = 1, y = 2 } +``` + +### Streaming + +The default `operator<<` for nodes uses `toml_formatter`: + +```cpp +auto tbl = toml::parse("key = 42"); +std::cout << tbl << "\n"; +// Equivalent to: std::cout << toml::toml_formatter{ tbl } << "\n"; +``` + +--- + +## JSON Formatter + +### Constants + +```cpp +static constexpr formatter_constants json_formatter_constants = { + // mandatory_flags: + format_flags::quote_dates_and_times + | format_flags::quote_infinities_and_nans, + + // ignored_flags: + format_flags::allow_literal_strings + | format_flags::allow_multi_line_strings + | format_flags::allow_binary_integers + | format_flags::allow_octal_integers + | format_flags::allow_hexadecimal_integers, + + // float_pos_inf, float_neg_inf, float_nan: + "Infinity"sv, "-Infinity"sv, "NaN"sv, + + // bool_true, bool_false: + "true"sv, "false"sv +}; +``` + +### Key Differences from TOML Formatter + +- **Dates and times** are always quoted: `"2024-01-15"` instead of `2024-01-15` +- **Infinity and NaN** are quoted: `"Infinity"`, `"-Infinity"`, `"NaN"` +- **Integers** always in decimal (no `0xFF`, `0o777`, `0b1010`) +- **Object keys** always quoted +- **No section headers** — uses nested `{ }` structure +- **Commas** separate elements + +### Default Flags + +```cpp +static constexpr format_flags default_flags = + format_flags::quote_dates_and_times + | format_flags::quote_infinities_and_nans + | format_flags::indentation; +``` + +### Usage + +```cpp +auto tbl = toml::parse(R"( + [server] + host = "localhost" + port = 8080 + tags = ["web", "api"] +)"); + +std::cout << toml::json_formatter{ tbl } << "\n"; +``` + +Output: +```json +{ + "server": { + "host": "localhost", + "port": 8080, + "tags": [ + "web", + "api" + ] + } +} +``` + +### Transcoding Example + +From the `examples/toml_to_json_transcoder.cpp`: + +```cpp +#include <toml++/toml.hpp> +#include <iostream> + +int main(int argc, char** argv) +{ + toml::table tbl; + try + { + tbl = toml::parse(std::cin, "stdin"sv); + } + catch (const toml::parse_error& err) + { + std::cerr << err << "\n"; + return 1; + } + + std::cout << toml::json_formatter{ tbl } << "\n"; + return 0; +} +``` + +--- + +## YAML Formatter + +### Constants + +```cpp +static constexpr formatter_constants yaml_formatter_constants = { + // mandatory_flags: + format_flags::quote_dates_and_times, + + // ignored_flags: + format_flags::allow_literal_strings + | format_flags::allow_multi_line_strings + | format_flags::allow_binary_integers + | format_flags::allow_octal_integers + | format_flags::allow_hexadecimal_integers, + + // float_pos_inf, float_neg_inf, float_nan: + ".inf"sv, "-.inf"sv, ".nan"sv, + + // bool_true, bool_false: + "true"sv, "false"sv +}; +``` + +### Key Differences + +- **Indentation** uses 2 spaces (indent level 1 = 2 spaces) +- **No braces or brackets** — uses YAML's indentation-based structure +- **Dates quoted**: `"2024-01-15"` +- **Inf/NaN**: `.inf`, `-.inf`, `.nan` (YAML style) +- **Array elements** prefixed with `- ` +- **No commas** + +### Default Flags + +```cpp +static constexpr format_flags default_flags = + format_flags::quote_dates_and_times + | format_flags::allow_unicode_strings + | format_flags::indentation; +``` + +### Usage + +```cpp +auto tbl = toml::parse(R"( + [server] + host = "localhost" + port = 8080 + tags = ["web", "api"] +)"); + +std::cout << toml::yaml_formatter{ tbl } << "\n"; +``` + +Output: +```yaml +server: + host: "localhost" + port: 8080 + tags: + - "web" + - "api" +``` + +--- + +## Printing Individual Nodes + +Any node can be formatted, not just tables: + +```cpp +auto arr = toml::array{ 1, 2, 3, "four" }; +std::cout << toml::toml_formatter{ arr } << "\n"; +// [1, 2, 3, "four"] + +std::cout << toml::json_formatter{ arr } << "\n"; +// [1, 2, 3, "four"] + +auto val = toml::value<std::string>{ "hello" }; +std::cout << toml::toml_formatter{ val } << "\n"; +// "hello" +``` + +--- + +## Writing to Files + +```cpp +#include <fstream> + +auto tbl = toml::parse_file("input.toml"); + +// Write as TOML +{ + std::ofstream out("output.toml"); + out << toml::toml_formatter{ tbl }; +} + +// Write as JSON +{ + std::ofstream out("output.json"); + out << toml::json_formatter{ tbl }; +} + +// Write as YAML +{ + std::ofstream out("output.yaml"); + out << toml::yaml_formatter{ tbl }; +} +``` + +--- + +## Customizing Output + +### Disabling Indentation + +```cpp +auto fmt = toml::toml_formatter{ tbl, format_flags::none }; +std::cout << fmt; +``` + +### Terse Key-Value Pairs + +```cpp +auto fmt = toml::toml_formatter{ + tbl, + format_flags::terse_key_value_pairs | format_flags::indentation +}; +// Output: key=value instead of key = value +``` + +### Preserving Source Format + +By default, integer format flags from parsing are preserved. A value parsed from `0xFF` will serialize back as `0xFF`: + +```cpp +auto tbl = toml::parse("mask = 0xFF"); +std::cout << tbl << "\n"; +// Output: mask = 0xFF +``` + +This works because the parser sets `value_flags::format_as_hexadecimal` on the value, and the TOML formatter has `allow_hexadecimal_integers` in its mandatory flags. + +--- + +## Formatter Comparison + +| Feature | `toml_formatter` | `json_formatter` | `yaml_formatter` | +|---------|-----------------|-----------------|-----------------| +| Format | TOML v1.0 | JSON | YAML | +| Indentation | Tab (default) | 4 spaces | 2 spaces | +| Infinity | `inf` | `"Infinity"` | `.inf` | +| NaN | `nan` | `"NaN"` | `.nan` | +| Dates | Unquoted | Quoted | Quoted | +| Integer formats | Hex/Oct/Bin | Decimal only | Decimal only | +| Literal strings | Yes | No | No | +| Multi-line strings | Yes | No | No | +| Section headers | `[table]` | N/A | N/A | +| Inline tables | `{ k = v }` | N/A | N/A | + +--- + +## Complete Example + +```cpp +#include <toml++/toml.hpp> +#include <iostream> +#include <sstream> + +int main() +{ + auto config = toml::parse(R"( + title = "My Config" + debug = true + max_connections = 0xFF + + [server] + host = "localhost" + port = 8080 + started = 2024-01-15T10:30:00Z + + [server.ssl] + enabled = true + cert = "/etc/ssl/cert.pem" + + [[server.routes]] + path = "/" + handler = "index" + + [[server.routes]] + path = "/api" + handler = "api" + )"); + + // TOML output (the default) + std::cout << "=== TOML ===\n"; + std::cout << config << "\n\n"; + + // JSON output + std::cout << "=== JSON ===\n"; + std::cout << toml::json_formatter{ config } << "\n\n"; + + // YAML output + std::cout << "=== YAML ===\n"; + std::cout << toml::yaml_formatter{ config } << "\n\n"; + + // Terse TOML + std::cout << "=== Terse TOML ===\n"; + std::cout << toml::toml_formatter{ + config, + toml::format_flags::terse_key_value_pairs + | toml::format_flags::indentation + } << "\n"; + + // Format to string + std::ostringstream ss; + ss << toml::json_formatter{ config }; + std::string json_string = ss.str(); + + return 0; +} +``` + +--- + +## Related Documentation + +- [parsing.md](parsing.md) — Parsing TOML into node trees +- [values.md](values.md) — Value flags affecting output format +- [tables.md](tables.md) — Inline table formatting +- [basic-usage.md](basic-usage.md) — Quick formatting examples diff --git a/docs/handbook/tomlplusplus/node-system.md b/docs/handbook/tomlplusplus/node-system.md new file mode 100644 index 0000000000..c34b531385 --- /dev/null +++ b/docs/handbook/tomlplusplus/node-system.md @@ -0,0 +1,625 @@ +# toml++ — Node System + +## Overview + +The node system is the core of toml++'s data model. Every element in a TOML document — tables, arrays, and leaf values — is represented as a `toml::node`. This document covers the base class interface, `node_view` for safe access, type checking mechanisms, value retrieval strategies, and visitation patterns. + +--- + +## `toml::node` — The Base Class + +`toml::node` is an abstract base class (`TOML_ABSTRACT_INTERFACE`) declared in `include/toml++/impl/node.hpp`. It cannot be instantiated directly; only its derived classes (`table`, `array`, `value<T>`) can. + +### Source Tracking + +Every node stores a `source_region` tracking its origin in the parsed document: + +```cpp +class node +{ + private: + source_region source_{}; + + public: + const source_region& source() const noexcept; +}; +``` + +For programmatically-constructed nodes, `source()` returns a default-constructed region (all zeros). For parsed nodes, it contains the file path and begin/end line/column. + +### Lifetime + +```cpp + protected: + node() noexcept; + node(const node&) noexcept; // copies source_region + node(node&&) noexcept; // moves source_region + node& operator=(const node&) noexcept; + node& operator=(node&&) noexcept; + + public: + virtual ~node() noexcept; +``` + +Constructors are protected — you create nodes by constructing `table`, `array`, or `value<T>` objects. + +--- + +## Type Checking + +### Virtual Type Checks + +Every `node` provides a full set of virtual type-checking methods: + +```cpp +virtual node_type type() const noexcept = 0; + +virtual bool is_table() const noexcept = 0; +virtual bool is_array() const noexcept = 0; +virtual bool is_array_of_tables() const noexcept; +virtual bool is_value() const noexcept = 0; +virtual bool is_string() const noexcept = 0; +virtual bool is_integer() const noexcept = 0; +virtual bool is_floating_point() const noexcept = 0; +virtual bool is_number() const noexcept = 0; +virtual bool is_boolean() const noexcept = 0; +virtual bool is_date() const noexcept = 0; +virtual bool is_time() const noexcept = 0; +virtual bool is_date_time() const noexcept = 0; +``` + +`is_number()` returns `true` for both integers and floating-point values. + +`is_array_of_tables()` returns `true` only for arrays where every element is a table. + +### Template Type Check: `is<T>()` + +```cpp +template <typename T> +bool is() const noexcept; +``` + +Accepts any TOML node or value type. Uses `if constexpr` internally to dispatch: + +```cpp +node.is<toml::table>() // equivalent to node.is_table() +node.is<toml::array>() // equivalent to node.is_array() +node.is<std::string>() // equivalent to node.is_string() +node.is<int64_t>() // equivalent to node.is_integer() +node.is<double>() // equivalent to node.is_floating_point() +node.is<bool>() // equivalent to node.is_boolean() +node.is<toml::date>() // equivalent to node.is_date() +node.is<toml::time>() // equivalent to node.is_time() +node.is<toml::date_time>() // equivalent to node.is_date_time() +``` + +You can also use the wrapped `value<T>` type: +```cpp +node.is<toml::value<int64_t>>() // same as node.is<int64_t>() +``` + +The `impl::unwrap_node<T>` trait unwraps `value<T>` → `T` and `node_view<T>` → `T`. + +### Compile-Time Type Traits + +The `toml` namespace provides type traits usable with `if constexpr`: + +```cpp +// Type traits for use in generic/template code +toml::is_table<decltype(val)> // true if val is table or node_view of table +toml::is_array<decltype(val)> // true if val is array or node_view of array +toml::is_string<decltype(val)> // true if val is value<std::string> +toml::is_integer<decltype(val)> // true if val is value<int64_t> +toml::is_floating_point<decltype(val)> // true if val is value<double> +toml::is_number<decltype(val)> // integer or floating-point +toml::is_boolean<decltype(val)> // true if val is value<bool> +toml::is_date<decltype(val)> // true if val is value<date> +toml::is_time<decltype(val)> // true if val is value<time> +toml::is_date_time<decltype(val)> // true if val is value<date_time> +toml::is_value<T> // true for any native value type +toml::is_container<T> // true for table or array +``` + +These are critical for `for_each()` visitors: + +```cpp +tbl.for_each([](auto& key, auto& value) +{ + if constexpr (toml::is_string<decltype(value)>) + std::cout << key << " is a string: " << value.get() << "\n"; + else if constexpr (toml::is_integer<decltype(value)>) + std::cout << key << " is an integer: " << value.get() << "\n"; + else if constexpr (toml::is_table<decltype(value)>) + std::cout << key << " is a table with " << value.size() << " entries\n"; +}); +``` + +### `node_type` Enum + +Runtime type identification uses the `node_type` enum: + +```cpp +enum class node_type : uint8_t +{ + none, // sentinel / empty + table, + array, + string, + integer, + floating_point, + boolean, + date, + time, + date_time +}; +``` + +Usage: +```cpp +switch (node.type()) +{ + case toml::node_type::string: /* ... */ break; + case toml::node_type::table: /* ... */ break; + // ... +} +``` + +`node_type` is streamable: +```cpp +std::cout << node.type() << "\n"; // prints "string", "integer", etc. +``` + +--- + +## Type Casts: `as<T>()` and Friends + +### Virtual Cast Methods + +```cpp +// Return pointer if this node IS that type, nullptr otherwise +virtual table* as_table() noexcept = 0; +virtual array* as_array() noexcept = 0; +virtual toml::value<std::string>* as_string() noexcept = 0; +virtual toml::value<int64_t>* as_integer() noexcept = 0; +virtual toml::value<double>* as_floating_point() noexcept = 0; +virtual toml::value<bool>* as_boolean() noexcept = 0; +virtual toml::value<date>* as_date() noexcept = 0; +virtual toml::value<time>* as_time() noexcept = 0; +virtual toml::value<date_time>* as_date_time() noexcept = 0; +// + const overloads +``` + +Each derived class implements these: `table::as_table()` returns `this`, all others return `nullptr`; `value<int64_t>::as_integer()` returns `this`, all others return `nullptr`. + +### Template Cast: `as<T>()` + +```cpp +template <typename T> +impl::wrap_node<T>* as() noexcept; + +template <typename T> +const impl::wrap_node<T>* as() const noexcept; +``` + +Dispatches to the appropriate `as_*()` method. `impl::wrap_node<T>` wraps native types in `value<T>`: +- `as<int64_t>()` → `value<int64_t>*` (via `as_integer()`) +- `as<toml::table>()` → `table*` (via `as_table()`) +- `as<toml::value<int64_t>>()` → `value<int64_t>*` (same as above) + +Usage: +```cpp +if (auto* tbl = node.as<toml::table>()) + std::cout << "Table with " << tbl->size() << " entries\n"; + +if (auto* val = node.as<int64_t>()) + std::cout << "Integer: " << val->get() << "\n"; +``` + +### Reference Access: `ref<T>()` + +```cpp +template <typename T> +decltype(auto) ref() & noexcept; +template <typename T> +decltype(auto) ref() && noexcept; +template <typename T> +decltype(auto) ref() const& noexcept; +template <typename T> +decltype(auto) ref() const&& noexcept; +``` + +Returns a **direct reference** to the underlying value. Unlike `as<T>()`, this does not return a pointer and does not check the type at runtime — it is **undefined behavior** to call `ref<T>()` with the wrong type. It asserts in debug builds. + +```cpp +// Only safe if you KNOW the type +int64_t& val = node.ref<int64_t>(); +``` + +--- + +## Value Retrieval + +### `value<T>()` — Permissive Retrieval + +```cpp +template <typename T> +optional<T> value() const noexcept(...); +``` + +Returns the node's value, allowing type conversions: + +| Source Type | Target Type | Behavior | +|-------------|-------------|----------| +| `int64_t` | `int64_t` | Exact | +| `int64_t` | `double` | Converts | +| `int64_t` | `int32_t` | Converts if lossless (range check) | +| `int64_t` | `uint32_t` | Converts if lossless (range check) | +| `double` | `double` | Exact | +| `double` | `int64_t` | No (returns empty) | +| `bool` | `bool` | Exact | +| `bool` | `int64_t` | Converts (0 or 1) | +| `std::string` | `std::string_view` | Returns view | +| `std::string` | `std::string` | Returns copy | +| `date` | `date` | Exact | +| `time` | `time` | Exact | +| `date_time` | `date_time` | Exact | + +```cpp +auto tbl = toml::parse("val = 42"); + +// These all work: +auto as_i64 = tbl["val"].value<int64_t>(); // 42 +auto as_dbl = tbl["val"].value<double>(); // 42.0 +auto as_i32 = tbl["val"].value<int32_t>(); // 42 +auto as_u16 = tbl["val"].value<uint16_t>(); // 42 + +// Returns empty: +auto as_str = tbl["val"].value<std::string>(); // nullopt (int != string) +``` + +### `value_exact<T>()` — Strict Retrieval + +```cpp +template <typename T> +optional<T> value_exact() const noexcept(...); +``` + +Only returns a value if the node's native type matches exactly: + +```cpp +auto tbl = toml::parse("val = 42"); + +auto exact = tbl["val"].value_exact<int64_t>(); // 42 +auto wrong = tbl["val"].value_exact<double>(); // nullopt (42 is integer, not float) +auto wrong2 = tbl["val"].value_exact<int32_t>(); // nullopt (native type is int64_t) +``` + +Allowed target types for `value_exact<T>()`: +- Native TOML types: `std::string`, `int64_t`, `double`, `bool`, `date`, `time`, `date_time` +- Lossless representations: `std::string_view`, `const char*` (for strings), `std::wstring` (Windows) + +### `value_or()` — Retrieval with Default + +```cpp +template <typename T> +auto value_or(T&& default_value) const noexcept(...); +``` + +Returns the value if the node contains a compatible type, otherwise returns the default. The return type matches the default value's type. + +```cpp +int64_t port = tbl["port"].value_or(8080); // 8080 if missing +std::string_view host = tbl["host"].value_or("localhost"sv); + +// Works safely on missing paths: +bool flag = tbl["section"]["missing"]["deep"].value_or(false); +``` + +--- + +## `toml::node_view<T>` — Safe Optional Node Access + +### Purpose + +`node_view` wraps a `node*` (possibly null) and provides the full node interface with null safety. It's what `table::operator[]` returns. + +### Template Parameter + +```cpp +template <typename ViewedType> +class node_view +{ + static_assert(impl::is_one_of<ViewedType, toml::node, const toml::node>); + // ... + mutable viewed_type* node_ = nullptr; +}; +``` + +- `node_view<node>` — mutable view +- `node_view<const node>` — const view + +### Construction + +```cpp +node_view() noexcept = default; // empty view +explicit node_view(viewed_type* node) noexcept; // from pointer +explicit node_view(viewed_type& node) noexcept; // from reference +``` + +### Boolean Conversion + +```cpp +explicit operator bool() const noexcept; // true if node_ != nullptr +``` + +### Chained Subscript + +The key design feature — subscript returns another `node_view`, enabling safe deep access: + +```cpp +// operator[] on node_view +node_view operator[](std::string_view key) const noexcept; +node_view operator[](size_t index) const noexcept; +node_view operator[](const toml::path& p) const noexcept; +``` + +If `node_` is null or isn't the right container type, returns an empty `node_view`. This makes arbitrarily deep access safe: + +```cpp +// All of these are safe even if intermediate keys don't exist: +auto v1 = tbl["a"]["b"]["c"].value_or(0); +auto v2 = tbl["missing"]["doesn't"]["exist"].value_or("default"sv); +``` + +### Full Interface Mirror + +`node_view` provides all the same methods as `node`: +- Type checks: `is_table()`, `is_string()`, `is<T>()`, etc. +- Type casts: `as_table()`, `as_string()`, `as<T>()`, etc. +- Value retrieval: `value<T>()`, `value_exact<T>()`, `value_or()` +- Source access: `source()` +- Visitation: `visit()` + +All safely return defaults/nullptr/empty-optionals when the view is empty. + +### `node()` Accessor + +```cpp +viewed_type* node() const noexcept; +``` + +Returns the raw pointer, which may be `nullptr`. + +### Printing + +```cpp +friend std::ostream& operator<<(std::ostream& os, const node_view& nv); +``` + +Prints the referenced node's value, or nothing if empty. + +--- + +## Homogeneity Checking + +### `is_homogeneous()` — Check Element Type Uniformity + +Available on `node`, `table`, and `array`: + +```cpp +// With node_type parameter: +virtual bool is_homogeneous(node_type ntype) const noexcept = 0; + +// With out-parameter for first mismatch: +virtual bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept = 0; + +// Template version: +template <typename ElemType = void> +bool is_homogeneous() const noexcept; +``` + +**Behavior:** +- `node_type::none` → "are all elements the same type?" (any type, as long as consistent) +- Any specific type → "are all elements this type?" +- Returns `false` for empty containers + +```cpp +auto arr = toml::array{ 1, 2, 3 }; + +arr.is_homogeneous(toml::node_type::integer); // true +arr.is_homogeneous(toml::node_type::string); // false +arr.is_homogeneous(toml::node_type::none); // true (all same type) +arr.is_homogeneous<int64_t>(); // true +arr.is_homogeneous<double>(); // false +arr.is_homogeneous(); // true (void = any consistent type) + +// Find the first mismatch: +auto mixed = toml::array{ 1, 2, "oops" }; +toml::node* mismatch = nullptr; +if (!mixed.is_homogeneous(toml::node_type::integer, mismatch)) +{ + std::cout << "Mismatch at " << mismatch->source() << "\n"; + std::cout << "Type: " << mismatch->type() << "\n"; // "string" +} +``` + +For `value<T>` nodes, `is_homogeneous()` trivially returns `true` (a single value is always homogeneous with itself). + +--- + +## Visitation with `visit()` + +```cpp +template <typename Func> +decltype(auto) visit(Func&& visitor) & noexcept(...); +template <typename Func> +decltype(auto) visit(Func&& visitor) && noexcept(...); +template <typename Func> +decltype(auto) visit(Func&& visitor) const& noexcept(...); +template <typename Func> +decltype(auto) visit(Func&& visitor) const&& noexcept(...); +``` + +Calls the visitor with the concrete derived type. The visitor must accept all possible types (use a generic lambda or overload set): + +```cpp +node.visit([](auto& concrete) +{ + using T = std::remove_cvref_t<decltype(concrete)>; + + if constexpr (std::is_same_v<T, toml::table>) + std::cout << "table with " << concrete.size() << " keys\n"; + else if constexpr (std::is_same_v<T, toml::array>) + std::cout << "array with " << concrete.size() << " elements\n"; + else + std::cout << "value: " << concrete.get() << "\n"; +}); +``` + +The visitor receives one of: +- `table&` / `const table&` +- `array&` / `const array&` +- `value<std::string>&` / `const value<std::string>&` +- `value<int64_t>&` / `const value<int64_t>&` +- `value<double>&` / `const value<double>&` +- `value<bool>&` / `const value<bool>&` +- `value<date>&` / `const value<date>&` +- `value<time>&` / `const value<time>&` +- `value<date_time>&` / `const value<date_time>&` + +### Return Values + +If your visitor returns a value, `visit()` returns it. All branches must return the same type: + +```cpp +std::string desc = node.visit([](auto& val) -> std::string +{ + using T = std::remove_cvref_t<decltype(val)>; + if constexpr (std::is_same_v<T, toml::table>) + return "table"; + else if constexpr (std::is_same_v<T, toml::array>) + return "array"; + else + return "value"; +}); +``` + +--- + +## `for_each()` Iteration + +Available on `table` and `array`: + +### On Tables + +```cpp +template <typename Func> +table& for_each(Func&& visitor) &; +``` + +The visitor receives `(const toml::key& key, auto& value)` where `value` is the concrete type: + +```cpp +tbl.for_each([](const toml::key& key, auto& value) +{ + std::cout << key.str() << " (" << key.source().begin.line << "): "; + + if constexpr (toml::is_string<decltype(value)>) + std::cout << '"' << value.get() << "\"\n"; + else if constexpr (toml::is_integer<decltype(value)>) + std::cout << value.get() << "\n"; + else if constexpr (toml::is_table<decltype(value)>) + std::cout << "{...}\n"; + else + std::cout << value << "\n"; +}); +``` + +### On Arrays + +```cpp +template <typename Func> +array& for_each(Func&& visitor) &; +``` + +The visitor receives `(size_t index, auto& element)` or just `(auto& element)`: + +```cpp +arr.for_each([](size_t idx, auto& elem) +{ + std::cout << "[" << idx << "] " << elem << "\n"; +}); +``` + +### Early Exit + +On compilers without the GCC 7 bug (`TOML_RETURN_BOOL_FROM_FOR_EACH_BROKEN == 0`), your visitor can return `bool` to stop iteration early: + +```cpp +tbl.for_each([](const toml::key& key, auto& value) -> bool +{ + if (key.str() == "stop_here") + return false; // stop iteration + std::cout << key << "\n"; + return true; // continue +}); +``` + +--- + +## `impl::unwrap_node<T>` and `impl::wrap_node<T>` + +These internal type traits handle the mapping between user-facing types and internal node types: + +- `unwrap_node<value<int64_t>>` → `int64_t` +- `unwrap_node<int64_t>` → `int64_t` (no change) +- `unwrap_node<node_view<node>>` → `node` (extracted viewed type) +- `wrap_node<int64_t>` → `value<int64_t>` +- `wrap_node<table>` → `table` (no change) +- `wrap_node<array>` → `array` (no change) + +These ensure that `as<int64_t>()` returns `value<int64_t>*` and `as<table>()` returns `table*`. + +--- + +## Node Comparison + +Nodes support equality comparison: + +```cpp +// Same type and value → equal +toml::value<int64_t> a(42); +toml::value<int64_t> b(42); +bool eq = (a == b); // true + +// Cross-type comparison is always false +toml::value<int64_t> i(42); +toml::value<double> d(42.0); +bool eq2 = (i == d); // false (different node types) + +// Tables and arrays compare structurally (deep equality) +``` + +--- + +## Summary: Choosing the Right Accessor + +| Need | Method | Returns | Null Safe | +|------|--------|---------|-----------| +| Check if key exists | `tbl["key"]` then `operator bool()` | `node_view` | Yes | +| Get value or default | `value_or(default)` | Value type | Yes | +| Get value, might be absent | `value<T>()` | `optional<T>` | Yes | +| Get exact-type value | `value_exact<T>()` | `optional<T>` | Yes | +| Get typed pointer | `as<T>()` | `T*` or `nullptr` | Yes | +| Direct reference (unsafe) | `ref<T>()` | `T&` | No (UB if wrong type) | +| Raw node pointer | `get(key)` | `node*` | Yes (returns null) | +| Typed node pointer | `get_as<T>(key)` | `wrap_node<T>*` | Yes (returns null) | + +--- + +## Related Documentation + +- [architecture.md](architecture.md) — Overall class hierarchy +- [tables.md](tables.md) — Table-specific operations +- [arrays.md](arrays.md) — Array-specific operations +- [values.md](values.md) — Value template details diff --git a/docs/handbook/tomlplusplus/overview.md b/docs/handbook/tomlplusplus/overview.md new file mode 100644 index 0000000000..d8ba7df5aa --- /dev/null +++ b/docs/handbook/tomlplusplus/overview.md @@ -0,0 +1,474 @@ +# toml++ (tomlplusplus) — Overview + +## What Is toml++? + +toml++ is a header-only TOML v1.0 parser, serializer, and data model library for C++17 and later. It is authored by Mark Gillard and published under the MIT license. The library version as of this documentation is **3.4.0**, implementing TOML language specification version **1.0.0**. + +The library lives in the `toml` namespace and provides a complete object model for TOML documents: tables, arrays, and typed values. It can parse TOML from strings, streams, and files; manipulate the resulting tree programmatically; and serialize back to TOML, JSON, or YAML. + +Repository: [https://github.com/marzer/tomlplusplus](https://github.com/marzer/tomlplusplus) + +--- + +## What Is TOML? + +TOML stands for **Tom's Obvious Minimal Language**. It is a configuration file format designed to be easy to read, unambiguous, and map cleanly to a hash table (dictionary). A TOML document is fundamentally a collection of key-value pairs organized into tables. + +### TOML Data Types + +TOML defines the following native data types: + +| TOML Type | C++ Representation in toml++ | `node_type` Enum | +|-----------------|------------------------------|-------------------------------| +| String | `std::string` | `node_type::string` | +| Integer | `int64_t` | `node_type::integer` | +| Float | `double` | `node_type::floating_point` | +| Boolean | `bool` | `node_type::boolean` | +| Local Date | `toml::date` | `node_type::date` | +| Local Time | `toml::time` | `node_type::time` | +| Offset Date-Time| `toml::date_time` | `node_type::date_time` | +| Array | `toml::array` | `node_type::array` | +| Table | `toml::table` | `node_type::table` | + +### Example TOML Document + +```toml +# This is a TOML document + +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +dob = 1979-05-27T07:32:00-08:00 + +[database] +enabled = true +ports = [ 8000, 8001, 8002 ] +data = [ ["delta", "phi"], [3.14] ] +temp_targets = { cpu = 79.5, case = 72.0 } + +[servers] + +[servers.alpha] +ip = "10.0.0.1" +role = "frontend" + +[servers.beta] +ip = "10.0.0.2" +role = "backend" +``` + +--- + +## C++17 Features Used + +toml++ requires C++17 as its minimum standard. The version detection logic in `preprocessor.hpp` checks `__cplusplus` and `_MSVC_LANG`, rejecting anything below C++17: + +```cpp +#if TOML_CPP < 17 +#error toml++ requires C++17 or higher. +#endif +``` + +Key C++17 features utilized throughout the library: + +### `std::string_view` +Used pervasively for zero-copy string references in parsing, key lookups, path parsing, and formatting. The parser accepts `std::string_view` for document content and source paths. + +### `std::optional<T>` +Returned by value retrieval functions like `node::value<T>()` and `node::value_exact<T>()`. The library also supports a custom optional type via `TOML_HAS_CUSTOM_OPTIONAL_TYPE`. + +### `if constexpr` +Used extensively in template code for compile-time type dispatch. For example, `node::is<T>()` and `node::as<T>()` use `if constexpr` chains to dispatch to the correct type check or cast: + +```cpp +template <typename T> +bool is() const noexcept +{ + using type = impl::remove_cvref<impl::unwrap_node<T>>; + if constexpr (std::is_same_v<type, table>) + return is_table(); + else if constexpr (std::is_same_v<type, array>) + return is_array(); + else if constexpr (std::is_same_v<type, std::string>) + return is_string(); + // ... +} +``` + +### Structured Bindings +Table iteration supports structured bindings: + +```cpp +for (auto&& [key, value] : my_table) +{ + std::cout << key << " = " << value << "\n"; +} +``` + +### Fold Expressions +Used in template parameter packs throughout the implementation, such as in `impl::all_integral<>` constraints for date/time constructors. + +### `std::variant` / `std::any` Awareness +While not directly depending on `std::variant`, the library includes `std_variant.hpp` for platforms that need it. The node hierarchy itself is polymorphic (virtual dispatch), not variant-based. + +### Inline Variables +Used for constants like `impl::node_type_friendly_names[]` and `impl::control_char_escapes[]`, which are declared `inline constexpr` in header files. + +### Class Template Argument Deduction (CTAD) +`toml::value` supports CTAD for constructing values from native types without explicitly specifying the template parameter. + +--- + +## C++20 Feature Support + +When compiled under C++20 or later, toml++ optionally supports: + +- **`char8_t` strings**: When `TOML_HAS_CHAR8` is true, the parser accepts `std::u8string_view` and `std::u8string` inputs. File paths can also be `char8_t`-based. +- **C++20 Modules**: The library ships with experimental module support via `import tomlplusplus;`. Enabled by setting `TOMLPLUSPLUS_BUILD_MODULES=ON` in CMake (requires CMake 3.28+). + +--- + +## Complete Feature List + +### Parsing +- Parse TOML from `std::string_view`, `std::istream`, or files (`toml::parse()`, `toml::parse_file()`) +- Full TOML v1.0.0 conformance — passes all tests in the [toml-test](https://github.com/toml-lang/toml-test) suite +- Optional support for unreleased TOML features (e.g., unicode bare keys from toml/pull/891) +- Proper UTF-8 handling including BOM detection and skipping +- Detailed error reporting with source positions (`toml::parse_error`, `toml::source_region`) +- Works with or without exceptions (`TOML_EXCEPTIONS` macro) +- Support for `char8_t` strings (C++20) +- Windows wide string compatibility (`TOML_ENABLE_WINDOWS_COMPAT`) + +### Data Model +- Complete node type hierarchy: `toml::node` (abstract base) → `toml::table`, `toml::array`, `toml::value<T>` +- `toml::node_view<T>` for safe, optional-like node access with chained subscript operators +- `toml::key` class preserving source location information for parsed keys +- Type-safe value access: `node::value<T>()`, `node::value_exact<T>()`, `node::value_or(default)` +- Template `as<T>()` for casting nodes to concrete types +- `is<T>()` family for type checking +- Visitor pattern via `node::visit()` and `for_each()` +- Homogeneity checking with `is_homogeneous()` + +### Manipulation +- Construct tables and arrays programmatically using initializer lists +- `table::insert()`, `table::insert_or_assign()`, `table::emplace()` +- `array::push_back()`, `array::emplace_back()`, `array::insert()` +- `table::erase()`, `array::erase()` +- Deep copy via copy constructors +- All containers are movable + +### Navigation +- `operator[]` subscript chaining: `tbl["section"]["key"]` +- `toml::at_path()` free function for dot-separated path lookup: `at_path(tbl, "section.key[0]")` +- `toml::path` class for programmatic path construction and manipulation +- `path::parent_path()`, `path::leaf()`, `path::subpath()` +- Path components are either keys (`std::string`) or array indices (`size_t`) + +### Serialization +- `toml::toml_formatter` — serialize as valid TOML (default when streaming nodes) +- `toml::json_formatter` — serialize as JSON +- `toml::yaml_formatter` — serialize as YAML +- All formatters support `format_flags` for fine-grained output control +- Format flags include: `indent_array_elements`, `indent_sub_tables`, `allow_literal_strings`, `allow_multi_line_strings`, `allow_unicode_strings`, `allow_real_tabs_in_strings`, `allow_binary_integers`, `allow_octal_integers`, `allow_hexadecimal_integers`, `quote_dates_and_times`, `quote_infinities_and_nans`, `terse_key_value_pairs`, `force_multiline_arrays` +- Inline table detection (`table::is_inline()`) +- Value flags for controlling integer format representation (`value_flags::format_as_binary`, `format_as_octal`, `format_as_hexadecimal`) + +### Build Modes +- **Header-only** (default): just `#include <toml++/toml.hpp>` +- **Single-header**: drop `toml.hpp` (root-level amalgamated file) into your project +- **Compiled library**: define `TOML_HEADER_ONLY=0` and compile `src/toml.cpp` +- **C++20 Modules**: `import tomlplusplus;` + +### Build Systems +- Meson (primary, with full option support) +- CMake (interface library target `tomlplusplus::tomlplusplus`) +- Visual Studio solution files (`.sln`, `.vcxproj`) +- Package managers: Conan, Vcpkg, DDS, tipi.build + +### Compiler Support +- GCC 8+ +- Clang 8+ (including Apple Clang) +- MSVC (VS2019+) +- Intel C++ Compiler (ICC/ICL) +- NVIDIA CUDA Compiler (NVCC) with workarounds + +### Platform Support +- x86, x64, ARM architectures +- Windows, Linux, macOS +- Does not require RTTI +- Works with or without exceptions + +--- + +## Version Information + +Version constants are defined in `include/toml++/impl/version.hpp`: + +```cpp +#define TOML_LIB_MAJOR 3 +#define TOML_LIB_MINOR 4 +#define TOML_LIB_PATCH 0 + +#define TOML_LANG_MAJOR 1 +#define TOML_LANG_MINOR 0 +#define TOML_LANG_PATCH 0 +``` + +- `TOML_LIB_MAJOR/MINOR/PATCH` — the library version (3.4.0) +- `TOML_LANG_MAJOR/MINOR/PATCH` — the TOML specification version implemented (1.0.0) + +--- + +## Configuration Macros + +toml++ is heavily configurable via preprocessor macros. Key ones include: + +| Macro | Default | Description | +|-------|---------|-------------| +| `TOML_HEADER_ONLY` | `1` | When `1`, the library is header-only. Set to `0` for compiled mode. | +| `TOML_EXCEPTIONS` | auto-detected | Whether to use exceptions. Auto-detected from compiler settings. | +| `TOML_ENABLE_PARSER` | `1` | Set to `0` to disable the parser entirely (serialization only). | +| `TOML_ENABLE_FORMATTERS` | `1` | Set to `0` to disable all formatters. | +| `TOML_ENABLE_WINDOWS_COMPAT` | `1` on Windows | Enables `std::wstring` overloads for Windows. | +| `TOML_UNRELEASED_FEATURES` | `0` | Enable support for unreleased TOML spec features. | +| `TOML_HAS_CUSTOM_OPTIONAL_TYPE` | `0` | Define with a custom optional type to use instead of `std::optional`. | +| `TOML_DISABLE_ENVIRONMENT_CHECKS` | undefined | Define to skip compile-time environment validation. | + +### Environment Ground-Truths + +The library validates its environment at compile time (unless `TOML_DISABLE_ENVIRONMENT_CHECKS` is defined): + +```cpp +static_assert(CHAR_BIT == 8, TOML_ENV_MESSAGE); +static_assert('A' == 65, TOML_ENV_MESSAGE); // ASCII +static_assert(sizeof(double) == 8, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits<double>::is_iec559, TOML_ENV_MESSAGE); // IEEE 754 +``` + +These ensure the library operates on platforms with 8-bit bytes, ASCII character encoding, and IEEE 754 double-precision floats. + +--- + +## Namespace Organization + +The library uses a layered namespace structure: + +- **`toml`** — The root namespace containing all public API types: `table`, `array`, `value<T>`, `node`, `node_view`, `key`, `path`, `date`, `time`, `date_time`, `source_position`, `source_region`, `parse_error`, `parse_result`, etc. +- **`toml::impl`** (internal) — Implementation details not part of the public API. Contains the parser, formatter base class, iterator implementations, and type trait utilities. +- **ABI namespaces** — Conditional inline namespaces (e.g., `ex`/`noex` for exception mode, `custopt`/`stdopt` for optional type) ensure ABI compatibility when linking translation units compiled with different settings. + +--- + +## Type Traits and Concepts + +The `toml` namespace provides several type trait utilities: + +```cpp +toml::is_value<T> // true for native value types (std::string, int64_t, double, bool, date, time, date_time) +toml::is_container<T> // true for table and array +toml::is_string<T> // true if T is a toml::value<std::string> or node_view thereof +toml::is_integer<T> // true if T is a toml::value<int64_t> or node_view thereof +toml::is_floating_point<T> +toml::is_number<T> +toml::is_boolean<T> +toml::is_date<T> +toml::is_time<T> +toml::is_date_time<T> +toml::is_table<T> +toml::is_array<T> +``` + +These are usable in `if constexpr` and `static_assert` contexts, making generic TOML-processing code straightforward. + +--- + +## The `node_type` Enumeration + +Defined in the forward declarations, `node_type` identifies the kind of a TOML node: + +```cpp +enum class node_type : uint8_t +{ + none, // Not a valid node type (used as nil sentinel) + table, // toml::table + array, // toml::array + string, // toml::value<std::string> + integer, // toml::value<int64_t> + floating_point, // toml::value<double> + boolean, // toml::value<bool> + date, // toml::value<toml::date> + time, // toml::value<toml::time> + date_time // toml::value<toml::date_time> +}; +``` + +Friendly display names are available via `impl::node_type_friendly_names[]`: +`"none"`, `"table"`, `"array"`, `"string"`, `"integer"`, `"floating-point"`, `"boolean"`, `"date"`, `"time"`, `"date-time"`. + +--- + +## The `value_flags` Enumeration + +Controls how integer values are formatted during serialization: + +```cpp +enum class value_flags : uint16_t +{ + none = 0, + format_as_binary = 1, // 0b... + format_as_octal = 2, // 0o... + format_as_hexadecimal = 3 // 0x... +}; +``` + +The sentinel value `preserve_source_value_flags` tells the library to keep whatever format the parser originally detected. + +--- + +## The `format_flags` Enumeration + +Controls formatter output behavior. It is a bitmask enum: + +```cpp +enum class format_flags : uint64_t +{ + none = 0, + quote_dates_and_times = 1, + quote_infinities_and_nans = 2, + allow_literal_strings = 4, + allow_multi_line_strings = 8, + allow_real_tabs_in_strings = 16, + allow_unicode_strings = 32, + allow_binary_integers = 64, + allow_octal_integers = 128, + allow_hexadecimal_integers = 256, + indent_sub_tables = 512, + indent_array_elements = 1024, + indentation = indent_sub_tables | indent_array_elements, + terse_key_value_pairs = 2048, + force_multiline_arrays = 4096 +}; +``` + +Each formatter has its own `default_flags` static constant and a set of mandatory/ignored flags defined in `formatter_constants`. + +--- + +## Minimal Usage Example + +```cpp +#include <toml++/toml.hpp> +#include <iostream> + +int main() +{ + // Parse a TOML string + auto config = toml::parse(R"( + [server] + host = "localhost" + port = 8080 + debug = false + )"); + + // Read values + std::string_view host = config["server"]["host"].value_or("0.0.0.0"sv); + int64_t port = config["server"]["port"].value_or(80); + bool debug = config["server"]["debug"].value_or(true); + + std::cout << "Host: " << host << "\n"; + std::cout << "Port: " << port << "\n"; + std::cout << "Debug: " << std::boolalpha << debug << "\n"; + + // Serialize back to TOML + std::cout << "\n" << config << "\n"; + + // Serialize as JSON + std::cout << toml::json_formatter{ config } << "\n"; + + return 0; +} +``` + +--- + +## File Organization + +``` +tomlplusplus/ +├── toml.hpp # Single-header amalgamation (drop-in) +├── include/ +│ └── toml++/ +│ ├── toml.hpp # Main include (includes all impl headers) +│ ├── toml.h # C-compatible header alias +│ └── impl/ +│ ├── forward_declarations.hpp # All forward decls, type aliases +│ ├── preprocessor.hpp # Compiler/platform detection, macros +│ ├── version.hpp # Version constants +│ ├── node.hpp # toml::node base class +│ ├── node.inl # node method implementations +│ ├── node_view.hpp # toml::node_view<T> +│ ├── table.hpp # toml::table +│ ├── table.inl # table method implementations +│ ├── array.hpp # toml::array +│ ├── array.inl # array method implementations +│ ├── value.hpp # toml::value<T> +│ ├── key.hpp # toml::key +│ ├── date_time.hpp # toml::date, toml::time, toml::date_time +│ ├── source_region.hpp # source_position, source_region +│ ├── parser.hpp # toml::parse(), toml::parse_file() +│ ├── parser.inl # Parser implementation +│ ├── parse_error.hpp # toml::parse_error +│ ├── parse_result.hpp # toml::parse_result (no-exceptions mode) +│ ├── path.hpp # toml::path, toml::path_component +│ ├── path.inl # Path implementation +│ ├── at_path.hpp # toml::at_path() free function +│ ├── at_path.inl # at_path implementation +│ ├── formatter.hpp # impl::formatter base class +│ ├── formatter.inl # formatter base implementation +│ ├── toml_formatter.hpp # toml::toml_formatter +│ ├── toml_formatter.inl # TOML formatter implementation +│ ├── json_formatter.hpp # toml::json_formatter +│ ├── json_formatter.inl # JSON formatter implementation +│ ├── yaml_formatter.hpp # toml::yaml_formatter +│ ├── yaml_formatter.inl # YAML formatter implementation +│ ├── make_node.hpp # impl::make_node() factory +│ ├── print_to_stream.hpp/.inl # Stream output helpers +│ ├── unicode.hpp # Unicode utilities +│ ├── unicode.inl # UTF-8 decoder +│ ├── unicode_autogenerated.hpp # Auto-generated Unicode tables +│ └── std_*.hpp # Standard library includes +├── src/ +│ └── toml.cpp # Compiled-library translation unit +├── tests/ # Catch2-based test suite +├── examples/ # Example programs +├── tools/ # Build/generation tools +├── meson.build # Primary build system +├── CMakeLists.txt # CMake build system +└── toml-test/ # toml-test conformance suite integration +``` + +--- + +## License + +toml++ is licensed under the **MIT License**. See `LICENSE` in the repository root for the full text. + +--- + +## Related Documentation + +- [architecture.md](architecture.md) — Class hierarchy and internal design +- [building.md](building.md) — Build instructions and integration +- [basic-usage.md](basic-usage.md) — Common usage patterns +- [node-system.md](node-system.md) — The node type system in depth +- [tables.md](tables.md) — Working with toml::table +- [arrays.md](arrays.md) — Working with toml::array +- [values.md](values.md) — Working with toml::value<T> +- [parsing.md](parsing.md) — Parser internals and error handling +- [formatting.md](formatting.md) — Output formatters +- [path-system.md](path-system.md) — Path-based navigation +- [unicode-handling.md](unicode-handling.md) — UTF-8 and Unicode support +- [code-style.md](code-style.md) — Code conventions +- [testing.md](testing.md) — Test framework and conformance diff --git a/docs/handbook/tomlplusplus/parsing.md b/docs/handbook/tomlplusplus/parsing.md new file mode 100644 index 0000000000..ac97ba89e8 --- /dev/null +++ b/docs/handbook/tomlplusplus/parsing.md @@ -0,0 +1,494 @@ +# toml++ — Parsing + +## Overview + +toml++ provides a recursive-descent parser that converts TOML text into a `toml::table` tree of nodes. The parser handles the full TOML v1.0.0 specification, including all string types, numeric formats, date/time values, inline tables, and arrays of tables. + +Key entry points are `toml::parse()` and `toml::parse_file()`, declared in `include/toml++/impl/parser.hpp`. + +The parser can be disabled entirely via `TOML_ENABLE_PARSER=0`. + +--- + +## Entry Points + +### `parse()` — Parse from String + +```cpp +// From std::string_view (most common) +parse_result parse(std::string_view doc, std::string_view source_path = {}); +parse_result parse(std::string_view doc, std::string&& source_path); + +// From std::istream +parse_result parse(std::istream& doc, std::string_view source_path = {}); +parse_result parse(std::istream& doc, std::string&& source_path); + +// From char8_t (C++20 u8 strings) +parse_result parse(std::u8string_view doc, std::string_view source_path = {}); +``` + +The `source_path` parameter is stored in `source_region` data and appears in error messages. It does not affect parsing behavior. + +```cpp +auto result = toml::parse(R"( + [server] + host = "localhost" + port = 8080 +)"); + +// With source path for error messages: +auto result2 = toml::parse(toml_string, "config.toml"); +``` + +### `parse_file()` — Parse from File Path + +```cpp +parse_result parse_file(std::string_view file_path); + +// Windows wstring overload (when TOML_ENABLE_WINDOWS_COMPAT=1) +parse_result parse_file(std::wstring_view file_path); +``` + +Reads the entire file and parses it: + +```cpp +auto config = toml::parse_file("settings.toml"); +auto config2 = toml::parse_file("/etc/myapp/config.toml"); +``` + +--- + +## parse_result + +`parse_result` is the return type from all parse functions. Its behavior depends on whether exceptions are enabled. + +Declared in `include/toml++/impl/parse_result.hpp`. + +### With Exceptions (`TOML_EXCEPTIONS=1`, the default) + +`parse_result` is simply a type alias for `toml::table`: + +```cpp +// Effectively: +using parse_result = table; +``` + +If parsing fails, `toml::parse_error` (derived from `std::runtime_error`) is thrown: + +```cpp +try +{ + toml::table config = toml::parse("invalid [[[toml"); +} +catch (const toml::parse_error& err) +{ + std::cerr << "Parse error: " << err.description() << "\n"; + std::cerr << " at: " << err.source() << "\n"; +} +``` + +### Without Exceptions (`TOML_EXCEPTIONS=0`) + +`parse_result` is a discriminated union — it holds either a `toml::table` (success) or a `toml::parse_error` (failure): + +```cpp +class parse_result +{ + public: + // Check success + bool succeeded() const noexcept; + bool failed() const noexcept; + explicit operator bool() const noexcept; // same as succeeded() + + // Access the table (success) + table& get() & noexcept; + const table& get() const& noexcept; + table&& get() && noexcept; + + table& operator*() & noexcept; + table* operator->() noexcept; + + // Access the error (failure) + const parse_error& error() const& noexcept; + + // Implicit conversion to table& (success only) + operator table&() noexcept; + operator const table&() const noexcept; +}; +``` + +Usage pattern: + +```cpp +auto result = toml::parse("..."); + +if (result) +{ + // Success — use the table + toml::table& config = result; + auto val = config["key"].value_or("default"sv); +} +else +{ + // Failure — inspect the error + std::cerr << "Error: " << result.error().description() << "\n"; + std::cerr << " at " << result.error().source() << "\n"; +} +``` + +### Internal Storage (No-Exceptions Mode) + +`parse_result` uses aligned storage to hold either type: + +```cpp +// Simplified internal layout: +alignas(table) mutable unsigned char storage_[sizeof(table)]; +bool err_; +parse_error err_val_; // only when err_ == true +``` + +The `table` is placement-new'd into `storage_` on success. On failure, `err_val_` is populated instead. + +--- + +## parse_error + +Declared in `include/toml++/impl/parse_error.hpp`. + +### With Exceptions + +```cpp +class parse_error : public std::runtime_error +{ + public: + std::string_view description() const noexcept; + const source_region& source() const noexcept; + + // what() returns the description +}; +``` + +### Without Exceptions + +```cpp +class parse_error +{ + public: + std::string_view description() const noexcept; + const source_region& source() const noexcept; + + // Streaming + friend std::ostream& operator<<(std::ostream&, const parse_error&); +}; +``` + +### Error Information + +```cpp +auto result = toml::parse("x = [1, 2,, 3]", "test.toml"); + +if (!result) +{ + const auto& err = result.error(); + + // Human-readable description + std::cout << err.description() << "\n"; + // e.g., "Unexpected character ',' (U+002C) in array" + + // Source location + const auto& src = err.source(); + std::cout << "File: " << *src.path << "\n"; // "test.toml" + std::cout << "Line: " << src.begin.line << "\n"; // 1 + std::cout << "Col: " << src.begin.column << "\n"; // 11 + + // Full error with location (via operator<<) + std::cout << err << "\n"; + // "Unexpected character ',' ... at line 1, column 11 of 'test.toml'" +} +``` + +--- + +## Source Tracking + +### `source_position` + +```cpp +struct source_position +{ + source_index line; // 1-based line number + source_index column; // 1-based column number (byte offset, not codepoint) + + explicit operator bool() const noexcept; // true if line > 0 + + friend bool operator==(const source_position&, const source_position&) noexcept; + friend bool operator!=(const source_position&, const source_position&) noexcept; + friend bool operator< (const source_position&, const source_position&) noexcept; + friend bool operator<=(const source_position&, const source_position&) noexcept; +}; +``` + +`source_index` is typically `uint32_t` (or `uint16_t` on small builds via `TOML_SMALL_INT_TYPE`). + +### `source_region` + +```cpp +struct source_region +{ + source_position begin; // start of the element + source_position end; // end of the element + source_path_ptr path; // shared_ptr<const std::string> +}; +``` + +Every `node` in the parsed tree carries a `source_region`: + +```cpp +auto tbl = toml::parse(R"( + name = "Alice" + age = 30 +)", "config.toml"); + +const auto& src = tbl["name"].node()->source(); +std::cout << "Defined at " + << *src.path << ":" + << src.begin.line << ":" + << src.begin.column << "\n"; +// "Defined at config.toml:2:5" +``` + +`source_path_ptr` is `std::shared_ptr<const std::string>`, shared among all nodes parsed from the same file. + +--- + +## Stream Parsing + +Parsing from `std::istream` allows reading from any stream source: + +```cpp +#include <fstream> + +std::ifstream file("config.toml"); +auto config = toml::parse(file, "config.toml"); + +// From stringstream +std::istringstream ss(R"(key = "value")"); +auto tbl = toml::parse(ss); +``` + +The stream is consumed entirely during parsing. + +--- + +## UTF-8 Handling During Parsing + +The parser expects UTF-8 encoded input. It handles: + +- **BOM stripping**: A leading UTF-8 BOM (`0xEF 0xBB 0xBF`) is silently consumed +- **Multi-byte sequences**: Bare keys, strings, and comments can contain Unicode +- **Escape sequences in strings**: `\uXXXX` and `\UXXXXXXXX` are decoded +- **Validation**: Invalid UTF-8 sequences are rejected with parse errors +- **Non-characters and surrogates**: Rejected as per the TOML specification + +### char8_t Support (C++20) + +```cpp +auto tbl = toml::parse(u8R"( + greeting = "Hello, 世界" +)"sv); +``` + +The `char8_t` overloads allow passing C++20 UTF-8 string literals directly. + +--- + +## Windows Compatibility + +When `TOML_ENABLE_WINDOWS_COMPAT=1` (default on Windows): + +```cpp +// Accept wstring file paths (converted to UTF-8 internally) +auto config = toml::parse_file(L"C:\\config\\settings.toml"); + +// Wide string values can be used with value() +auto path = config["path"].value<std::wstring>(); +``` + +--- + +## Parser Configuration Macros + +| Macro | Default | Effect | +|-------|---------|--------| +| `TOML_ENABLE_PARSER` | `1` | Set to `0` to remove the parser entirely (serialize-only builds) | +| `TOML_EXCEPTIONS` | Auto-detected | Controls exception vs. return-code error handling | +| `TOML_UNRELEASED_FEATURES` | `0` | Enable parsing of TOML features not yet in a released spec | +| `TOML_ENABLE_WINDOWS_COMPAT` | `1` on Windows | Enable wstring/wchar_t overloads | + +--- + +## Parsing Specific TOML Features + +### Strings + +```toml +basic = "hello\nworld" # basic (escape sequences) +literal = 'no \escapes' # literal (no escapes) +multi_basic = """ +multiline +string""" # multi-line basic +multi_literal = ''' +multiline +literal''' # multi-line literal +``` + +### Numbers + +```toml +int_dec = 42 +int_hex = 0xFF +int_oct = 0o755 +int_bin = 0b11010110 +float_std = 3.14 +float_exp = 1e10 +float_inf = inf +float_nan = nan +underscore = 1_000_000 +``` + +The parser records the integer format in `value_flags`: +- `0xFF` → `value_flags::format_as_hexadecimal` +- `0o755` → `value_flags::format_as_octal` +- `0b1010` → `value_flags::format_as_binary` + +### Inline Tables + +```toml +point = { x = 1, y = 2 } # single-line inline table +``` + +Parsed as a `toml::table` with `is_inline()` returning `true`. + +### Arrays of Tables + +```toml +[[products]] +name = "Hammer" +price = 9.99 + +[[products]] +name = "Nail" +price = 0.05 +``` + +Parsed as `table["products"]` → `array` containing two `table` nodes. + +--- + +## Error Recovery + +The parser does **not** attempt error recovery. Upon encountering the first error, parsing stops and the error is returned (or thrown). This design ensures: + +1. No partially-parsed trees with missing data +2. Clear, unambiguous error messages +3. The error source points to the exact location of the problem + +--- + +## Thread Safety + +- Parsing is **thread-safe**: multiple threads can call `parse()` concurrently with different inputs +- The parser uses no global state +- The returned `parse_result` / `table` is independent and owned by the caller + +--- + +## Complete Example + +```cpp +#include <toml++/toml.hpp> +#include <iostream> +#include <fstream> + +int main() +{ + // --- Parse from string --- + auto config = toml::parse(R"( + [database] + server = "192.168.1.1" + ports = [8001, 8001, 8002] + enabled = true + connection_max = 5000 + + [database.credentials] + username = "admin" + password_file = "/etc/db/pass" + )"); + +#if TOML_EXCEPTIONS + // With exceptions, config is a toml::table directly + auto server = config["database"]["server"].value_or(""sv); + std::cout << "Server: " << server << "\n"; + +#else + // Without exceptions, check for success first + if (!config) + { + std::cerr << "Parse failed:\n" << config.error() << "\n"; + return 1; + } + + auto& tbl = config.get(); + auto server = tbl["database"]["server"].value_or(""sv); + std::cout << "Server: " << server << "\n"; +#endif + + // --- Parse from file --- + try + { + auto file_config = toml::parse_file("app.toml"); + std::cout << file_config << "\n"; + } + catch (const toml::parse_error& err) + { + std::cerr << "Failed to parse app.toml:\n" + << err.description() << "\n" + << " at " << err.source() << "\n"; + return 1; + } + + // --- Source tracking --- + auto doc = toml::parse(R"( + title = "Example" + [owner] + name = "Tom" + )", "example.toml"); + + auto* name_node = doc.get("owner"); + if (name_node) + { + const auto& src = name_node->source(); + std::cout << "owner defined at: " + << *src.path << ":" + << src.begin.line << ":" + << src.begin.column << "\n"; + } + + // --- Stream parsing --- + std::istringstream ss(R"(key = "from stream")"); + auto stream_result = toml::parse(ss, "stream-input"); + std::cout << stream_result["key"].value_or(""sv) << "\n"; + + return 0; +} +``` + +--- + +## Related Documentation + +- [basic-usage.md](basic-usage.md) — Quick start guide with parsing examples +- [values.md](values.md) — Value types created by the parser +- [tables.md](tables.md) — Root table structure +- [formatting.md](formatting.md) — Serializing parsed data back to TOML +- [unicode-handling.md](unicode-handling.md) — UTF-8 handling details diff --git a/docs/handbook/tomlplusplus/path-system.md b/docs/handbook/tomlplusplus/path-system.md new file mode 100644 index 0000000000..d9ed1a6262 --- /dev/null +++ b/docs/handbook/tomlplusplus/path-system.md @@ -0,0 +1,412 @@ +# toml++ — Path System + +## Overview + +`toml::path` provides structured navigation into a TOML document tree using dot-separated key names and array indices. Rather than chaining `operator[]` calls, a path can be constructed from a string and applied to a node tree in a single operation. + +Declared in `include/toml++/impl/path.hpp` with `at_path()` free functions in `at_path.hpp`. + +--- + +## `path_component` + +Each segment of a path is a `path_component`, which is either a **key** (string) or an **array index** (integer): + +```cpp +class path_component +{ + public: + // Type query + path_component_type type() const noexcept; + // Returns path_component_type::key or path_component_type::array_index + + // Access (key) + const toml::key& key() const noexcept; + + // Access (array index) + size_t array_index() const noexcept; + + // Comparison + friend bool operator==(const path_component&, const path_component&) noexcept; + friend bool operator!=(const path_component&, const path_component&) noexcept; +}; +``` + +```cpp +enum class path_component_type : uint8_t +{ + key = 0x1, + array_index = 0x2 +}; +``` + +### Internal Storage + +`path_component` uses a union with type discrimination: + +```cpp +// Simplified internal layout: +union storage_t +{ + toml::key k; // for key components + size_t index; // for array_index components +}; + +path_component_type type_; +storage_t storage_; +``` + +--- + +## `toml::path` + +A path is a sequence of `path_component` values: + +```cpp +class path +{ + private: + std::vector<path_component> components_; +}; +``` + +### Construction + +#### From String + +```cpp +path(std::string_view str); +path(std::wstring_view str); // Windows compat +``` + +Path string syntax: +- Dot `.` separates keys: `"server.host"` → key("server"), key("host") +- Brackets `[N]` denote array indices: `"servers[0].host"` → key("servers"), index(0), key("host") +- Quoted keys for special chars: `"a.\"dotted.key\".b"` + +```cpp +toml::path p1("server.host"); // 2 components: key, key +toml::path p2("servers[0].name"); // 3 components: key, index, key +toml::path p3("[0][1]"); // 2 components: index, index +toml::path p4("database.\"dotted.key\""); // 2 components +``` + +#### From Components + +```cpp +path(); // empty path +path(const path& other); // copy +path(path&& other) noexcept; // move +``` + +### Size and Emptiness + +```cpp +size_t size() const noexcept; // number of components +bool empty() const noexcept; // true if no components + +explicit operator bool() const noexcept; // true if non-empty + +void clear() noexcept; // remove all components +``` + +### Element Access + +```cpp +path_component& operator[](size_t index) noexcept; +const path_component& operator[](size_t index) const noexcept; + +// Iterator support +auto begin() noexcept; +auto end() noexcept; +auto begin() const noexcept; +auto end() const noexcept; +auto cbegin() const noexcept; +auto cend() const noexcept; +``` + +```cpp +toml::path p("server.ports[0]"); + +for (const auto& component : p) +{ + if (component.type() == toml::path_component_type::key) + std::cout << "key: " << component.key() << "\n"; + else + std::cout << "index: " << component.array_index() << "\n"; +} +// key: server +// key: ports +// index: 0 +``` + +--- + +## Path Operations + +### Subpath Extraction + +```cpp +path parent_path() const; // all but last component +path leaf() const; // last component only + +path subpath(size_t start, size_t length) const; +path subpath(std::vector<path_component>::const_iterator start, + std::vector<path_component>::const_iterator end) const; + +path truncated(size_t n) const; // first n components +``` + +```cpp +toml::path p("a.b.c.d"); + +auto parent = p.parent_path(); // "a.b.c" +auto leaf = p.leaf(); // "d" +auto sub = p.subpath(1, 2); // "b.c" +auto trunc = p.truncated(2); // "a.b" +``` + +### Concatenation + +```cpp +path operator+(const path& rhs) const; +path operator+(const path_component& rhs) const; +path operator+(std::string_view rhs) const; + +path& operator+=(const path& rhs); +path& operator+=(const path_component& rhs); +path& operator+=(std::string_view rhs); + +// Prepend +path& prepend(const path& source); +path& prepend(path&& source); +``` + +```cpp +toml::path base("server"); +toml::path full = base + "host"; +// full == "server.host" + +toml::path p("a.b"); +p += "c.d"; +// p == "a.b.c.d" +``` + +### Assignment + +```cpp +path& assign(std::string_view str); +path& assign(const path& other); +path& assign(path&& other) noexcept; + +path& operator=(std::string_view str); +path& operator=(const path& other); +path& operator=(path&& other) noexcept; +``` + +--- + +## Comparison + +```cpp +friend bool operator==(const path& lhs, const path& rhs) noexcept; +friend bool operator!=(const path& lhs, const path& rhs) noexcept; + +friend bool operator==(const path& lhs, std::string_view rhs); +friend bool operator!=(const path& lhs, std::string_view rhs); +``` + +```cpp +toml::path a("server.host"); +toml::path b("server.host"); + +std::cout << (a == b) << "\n"; // true +std::cout << (a == "server.host") << "\n"; // true +``` + +--- + +## Hashing + +```cpp +size_t hash() const noexcept; + +// std::hash specialization +namespace std { + template<> struct hash<toml::path> { ... }; +} +``` + +Paths can be used as keys in `std::unordered_map` and `std::unordered_set`. + +--- + +## String Conversion + +```cpp +std::string str() const; +explicit operator std::string() const; + +friend std::ostream& operator<<(std::ostream&, const path&); +``` + +```cpp +toml::path p("servers[0].host"); +std::cout << p << "\n"; // servers[0].host +std::string s = p.str(); // "servers[0].host" +``` + +--- + +## `at_path()` — Path-Based Node Access + +Declared in `include/toml++/impl/at_path.hpp`. These free functions apply a path to a node tree: + +```cpp +node_view<node> at_path(node& root, const toml::path& path) noexcept; +node_view<const node> at_path(const node& root, const toml::path& path) noexcept; + +node_view<node> at_path(node& root, std::string_view path) noexcept; +node_view<const node> at_path(const node& root, std::string_view path) noexcept; + +// Windows compat +node_view<node> at_path(node& root, std::wstring_view path) noexcept; +``` + +Returns a `node_view` — null-safe wrapper that returns empty/default if the path doesn't resolve. + +```cpp +auto tbl = toml::parse(R"( + [server] + host = "localhost" + ports = [8080, 8081, 8082] + + [[servers]] + name = "alpha" + + [[servers]] + name = "beta" +)"); + +// Access nested value +auto host = toml::at_path(tbl, "server.host").value_or(""sv); +// "localhost" + +// Access array element +auto port = toml::at_path(tbl, "server.ports[1]").value_or(int64_t{0}); +// 8081 + +// Access array-of-tables element +auto name = toml::at_path(tbl, "servers[0].name").value_or(""sv); +// "alpha" + +// Non-existent path returns empty node_view +auto missing = toml::at_path(tbl, "nonexistent.path"); +std::cout << missing.value_or("default"sv) << "\n"; // "default" +``` + +### With `toml::path` Objects + +```cpp +toml::path p("server.ports[0]"); +auto port = toml::at_path(tbl, p).value_or(int64_t{0}); + +// Reuse path for multiple lookups +for (size_t i = 0; i < 3; i++) +{ + toml::path elem_path = toml::path("server.ports") + toml::path("[" + std::to_string(i) + "]"); + auto val = toml::at_path(tbl, elem_path).value_or(int64_t{0}); + std::cout << val << "\n"; +} +``` + +--- + +## `operator[]` with Path + +`table` and `node_view` also support path-like access via `operator[]`: + +```cpp +auto tbl = toml::parse(R"( + [server] + host = "localhost" +)"); + +// Chained subscript (each [] does a single lookup) +auto host = tbl["server"]["host"].value_or(""sv); + +// With toml::path (single lookup resolving the full path) +toml::path p("server.host"); +auto host2 = toml::at_path(tbl, p).value_or(""sv); +``` + +Note: `operator[]` on `table` does single-key lookups only. `at_path()` resolves multi-component paths. + +--- + +## Complete Example + +```cpp +#include <toml++/toml.hpp> +#include <iostream> + +int main() +{ + auto config = toml::parse(R"( + [database] + host = "db.example.com" + port = 5432 + + [database.pools] + read = 10 + write = 5 + + [[database.replicas]] + host = "replica1.example.com" + port = 5433 + + [[database.replicas]] + host = "replica2.example.com" + port = 5434 + )"); + + // Construct paths + toml::path db_host("database.host"); + toml::path db_port("database.port"); + toml::path pool_read("database.pools.read"); + + // Use at_path for access + std::cout << "Host: " << toml::at_path(config, db_host).value_or(""sv) << "\n"; + std::cout << "Port: " << toml::at_path(config, db_port).value_or(int64_t{0}) << "\n"; + std::cout << "Read pool: " << toml::at_path(config, pool_read).value_or(int64_t{0}) << "\n"; + + // Array-of-tables access + for (size_t i = 0; i < 2; i++) + { + auto host_path = toml::path("database.replicas[" + std::to_string(i) + "].host"); + auto port_path = toml::path("database.replicas[" + std::to_string(i) + "].port"); + + auto host = toml::at_path(config, host_path).value_or(""sv); + auto port = toml::at_path(config, port_path).value_or(int64_t{0}); + + std::cout << "Replica " << i << ": " << host << ":" << port << "\n"; + } + + // Path manipulation + toml::path base("database"); + auto full = base + "host"; + std::cout << "Full path: " << full << "\n"; // database.host + std::cout << "Parent: " << full.parent_path() << "\n"; // database + std::cout << "Leaf: " << full.leaf() << "\n"; // host + + return 0; +} +``` + +--- + +## Related Documentation + +- [basic-usage.md](basic-usage.md) — Simple access patterns +- [node-system.md](node-system.md) — node_view returned by at_path +- [tables.md](tables.md) — Table subscript access diff --git a/docs/handbook/tomlplusplus/tables.md b/docs/handbook/tomlplusplus/tables.md new file mode 100644 index 0000000000..d573ec0da2 --- /dev/null +++ b/docs/handbook/tomlplusplus/tables.md @@ -0,0 +1,551 @@ +# toml++ — Tables + +## Overview + +`toml::table` is the primary container in toml++. It extends `toml::node` and models an ordered map from `toml::key` objects to child `toml::node` pointers. Every parsed TOML document has a `table` as its root. + +Declared in `include/toml++/impl/table.hpp` with implementation in `table.inl`. + +--- + +## Internal Storage + +```cpp +class table : public node +{ + private: + using map_type = std::map<toml::key, impl::node_ptr, std::less<>>; + map_type map_; + bool inline_ = false; +}; +``` + +- **`map_type`** = `std::map<toml::key, std::unique_ptr<node>, std::less<>>` +- **`std::less<>`** enables heterogeneous lookup — you can search by `std::string_view` without constructing a `toml::key` +- **`inline_`** controls whether the table serializes as `{ a = 1, b = 2 }` (inline) or with `[section]` headers (non-inline) +- Ownership: the table owns all child nodes via `unique_ptr` + +--- + +## Construction + +### Default Construction + +```cpp +toml::table tbl; // empty table +``` + +### Initializer List Construction + +```cpp +auto tbl = toml::table{ + { "name", "toml++" }, + { "version", 3 }, + { "features", toml::array{ "parsing", "serialization" } }, + { "metadata", toml::table{ + { "author", "Mark Gillard" }, + { "license", "MIT" } + }} +}; +``` + +This uses `impl::table_init_pair`: +```cpp +struct table_init_pair +{ + mutable toml::key key; + mutable node_ptr value; + + template <typename K, typename V> + table_init_pair(K&& k, V&& v, value_flags flags = preserve_source_value_flags); +}; +``` + +Values are converted to nodes via `impl::make_node()`, which handles: +- Native types (`int`, `double`, `const char*`, etc.) → `value<T>` +- `toml::array` → `array` (moved) +- `toml::table` → `table` (moved) + +### Copy and Move + +```cpp +toml::table copy(original_table); // deep copy — all child nodes are cloned +toml::table moved(std::move(tbl)); // move — no allocation, transfers ownership +``` + +Copy is deep: every child node in the tree is recursively copied. + +--- + +## Iterators + +### Types + +```cpp +using table_iterator = impl::table_iterator<false>; +using const_table_iterator = impl::table_iterator<true>; +``` + +`table_iterator` is a **BidirectionalIterator**. Dereferencing yields `table_proxy_pair`: + +```cpp +template <bool IsConst> +struct table_proxy_pair +{ + using value_type = std::conditional_t<IsConst, const node, node>; + const toml::key& first; + value_type& second; +}; +``` + +The `unique_ptr` layer is hidden — you get `(const key&, node&)` pairs. + +### Iterator Methods + +```cpp +iterator begin() noexcept; +iterator end() noexcept; +const_iterator begin() const noexcept; +const_iterator end() const noexcept; +const_iterator cbegin() const noexcept; +const_iterator cend() const noexcept; +``` + +### Range-Based For + +```cpp +for (auto&& [key, value] : tbl) +{ + std::cout << key << " = " << value << "\n"; +} +``` + +Structured bindings work because `table_proxy_pair` has public `first` and `second` members. + +### Iterator to Key String + +```cpp +for (auto it = tbl.begin(); it != tbl.end(); ++it) +{ + const toml::key& k = it->first; + toml::node& v = it->second; + std::cout << k.str() << ": " << v.type() << "\n"; +} +``` + +--- + +## Capacity + +```cpp +size_t size() const noexcept; // number of key-value pairs +bool empty() const noexcept; // true if size() == 0 +``` + +--- + +## Element Access + +### `operator[]` — Returns `node_view` + +```cpp +node_view<node> operator[](std::string_view key) noexcept; +node_view<const node> operator[](std::string_view key) const noexcept; +``` + +Returns a `node_view` that wraps the node at that key, or an empty view if the key doesn't exist. This is the safe, chainable accessor: + +```cpp +auto val = tbl["section"]["subsection"]["key"].value_or(42); +``` + +### `at()` — Bounds-Checked Access + +```cpp +node& at(std::string_view key); +const node& at(std::string_view key) const; +``` + +Returns a reference to the node at the key. Throws `std::out_of_range` if the key doesn't exist. + +### `get()` — Raw Pointer Access + +```cpp +node* get(std::string_view key) noexcept; +const node* get(std::string_view key) const noexcept; +``` + +Returns a pointer to the node, or `nullptr` if not found: + +```cpp +if (auto* n = tbl.get("name")) +{ + std::cout << "Found: " << *n << "\n"; +} +``` + +### `get_as<T>()` — Typed Pointer Access + +```cpp +template <typename T> +impl::wrap_node<T>* get_as(std::string_view key) noexcept; + +template <typename T> +const impl::wrap_node<T>* get_as(std::string_view key) const noexcept; +``` + +Combines `get()` and `as<T>()`: + +```cpp +if (auto* val = tbl.get_as<std::string>("name")) + std::cout << "Name: " << val->get() << "\n"; + +if (auto* sub = tbl.get_as<toml::table>("database")) + std::cout << "Database has " << sub->size() << " keys\n"; +``` + +### `contains()` — Key Existence Check + +```cpp +bool contains(std::string_view key) const noexcept; +``` + +```cpp +if (tbl.contains("database")) + std::cout << "Has database config\n"; +``` + +--- + +## Insertion + +### `insert()` — Insert If Not Present + +```cpp +template <typename KeyType, typename ValueType> +std::pair<iterator, bool> insert(KeyType&& key, ValueType&& val, + value_flags flags = preserve_source_value_flags); +``` + +Inserts a new key-value pair only if the key doesn't already exist. Returns `(iterator, true)` on success, `(iterator_to_existing, false)` if the key was already present: + +```cpp +auto [it, inserted] = tbl.insert("name", "toml++"); +if (inserted) + std::cout << "Inserted: " << it->second << "\n"; +else + std::cout << "Key already exists\n"; +``` + +### `insert_or_assign()` — Insert or Replace + +```cpp +template <typename KeyType, typename ValueType> +std::pair<iterator, bool> insert_or_assign(KeyType&& key, ValueType&& val, + value_flags flags = preserve_source_value_flags); +``` + +Always succeeds — inserts if new, replaces if existing: + +```cpp +tbl.insert_or_assign("version", 4); // replaces any existing "version" +``` + +### `emplace<T>()` — Construct In Place + +```cpp +template <typename ValueType, typename KeyType, typename... Args> +std::pair<iterator, bool> emplace(KeyType&& key, Args&&... args); +``` + +Constructs a new node in place if the key doesn't exist: + +```cpp +tbl.emplace<std::string>("greeting", "Hello, World!"); +tbl.emplace<toml::array>("empty_list"); +tbl.emplace<toml::table>("empty_section"); +``` + +--- + +## Removal + +### `erase()` — By Key + +```cpp +size_t erase(std::string_view key) noexcept; +``` + +Returns 1 if the key was found and removed, 0 otherwise: + +```cpp +tbl.erase("deprecated_key"); +``` + +### `erase()` — By Iterator + +```cpp +iterator erase(iterator pos) noexcept; +iterator erase(const_iterator pos) noexcept; +iterator erase(const_iterator first, const_iterator last) noexcept; +``` + +```cpp +auto it = tbl.find("old_key"); +if (it != tbl.end()) + tbl.erase(it); +``` + +### `clear()` + +```cpp +void clear() noexcept; +``` + +Removes all key-value pairs. + +--- + +## Search + +### `find()` + +```cpp +iterator find(std::string_view key) noexcept; +const_iterator find(std::string_view key) const noexcept; +``` + +Returns an iterator to the key-value pair, or `end()` if not found. + +### `lower_bound()` / `upper_bound()` / `equal_range()` + +These operate on the underlying `std::map` with heterogeneous lookup: + +```cpp +iterator lower_bound(std::string_view key) noexcept; +iterator upper_bound(std::string_view key) noexcept; +std::pair<iterator, iterator> equal_range(std::string_view key) noexcept; +// + const overloads +``` + +--- + +## Metadata + +### `is_inline()` + +```cpp +bool is_inline() const noexcept; +void is_inline(bool val) noexcept; +``` + +Controls inline serialization. When `true`, the table formats as `{ a = 1, b = 2 }` instead of using `[section]` headers: + +```cpp +auto tbl = toml::table{ + { "a", 1 }, + { "b", 2 }, + { "nested", toml::table{ { "c", 3 } } } +}; + +std::cout << tbl << "\n"; +// Output: +// a = 1 +// b = 2 +// +// [nested] +// c = 3 + +tbl.is_inline(true); +std::cout << tbl << "\n"; +// Output: +// { a = 1, b = 2, nested = { c = 3 } } +``` + +Runtime-constructed tables default to non-inline. The parser sets `is_inline(true)` for tables parsed from inline syntax. + +--- + +## `for_each()` — Type-Safe Iteration + +```cpp +template <typename Func> +table& for_each(Func&& visitor) &; +``` + +Visits each key-value pair, passing the value as its concrete type: + +```cpp +tbl.for_each([](const toml::key& key, auto& value) +{ + std::cout << key << ": "; + + using value_type = std::remove_cvref_t<decltype(value)>; + if constexpr (std::is_same_v<value_type, toml::table>) + std::cout << "table (" << value.size() << " entries)\n"; + else if constexpr (std::is_same_v<value_type, toml::array>) + std::cout << "array (" << value.size() << " elements)\n"; + else + std::cout << value.get() << "\n"; +}); +``` + +The visitor is instantiated for all 9 possible value types (table, array, + 7 value types). + +--- + +## Path-Based Access + +### `at_path()` Member + +```cpp +node_view<node> at_path(std::string_view path) noexcept; +node_view<const node> at_path(std::string_view path) const noexcept; +node_view<node> at_path(const toml::path& path) noexcept; +``` + +Resolves dot-separated paths with array indices: + +```cpp +auto tbl = toml::parse(R"( + [database] + servers = [ + { host = "alpha", port = 5432 }, + { host = "beta", port = 5433 } + ] +)"); + +std::cout << tbl.at_path("database.servers[0].host") << "\n"; // "alpha" +std::cout << tbl.at_path("database.servers[1].port") << "\n"; // 5433 +``` + +### `operator[]` with `toml::path` + +```cpp +node_view<node> operator[](const toml::path& path) noexcept; +``` + +```cpp +toml::path p("database.servers[0].host"); +std::cout << tbl[p] << "\n"; // "alpha" +``` + +--- + +## Comparison + +### Equality + +```cpp +friend bool operator==(const table& lhs, const table& rhs) noexcept; +friend bool operator!=(const table& lhs, const table& rhs) noexcept; +``` + +Deep structural equality: two tables are equal if they have the same keys with equal values. Source regions and inline-ness are not compared. + +--- + +## Printing + +Tables are streamable via the default `toml_formatter`: + +```cpp +std::cout << tbl << "\n"; +``` + +Equivalent to: +```cpp +std::cout << toml::toml_formatter{ tbl } << "\n"; +``` + +--- + +## Type Identity + +`table` overrides all type-check virtuals from `node`: + +```cpp +node_type type() const noexcept final; // returns node_type::table +bool is_table() const noexcept final; // returns true +bool is_array() const noexcept final; // returns false +bool is_value() const noexcept final; // returns false +bool is_string() const noexcept final; // returns false +// ... all other is_*() return false + +table* as_table() noexcept final; // returns this +const table* as_table() const noexcept final; // returns this +// ... all other as_*() return nullptr +``` + +--- + +## Windows Compatibility + +When `TOML_ENABLE_WINDOWS_COMPAT` is enabled, additional overloads accept `std::wstring_view` for key parameters: + +```cpp +node* get(std::wstring_view key); +bool contains(std::wstring_view key) const; +node_view<node> operator[](std::wstring_view key) noexcept; +// etc. +``` + +Wide strings are internally narrowed via `impl::narrow()`. + +--- + +## Complete Example + +```cpp +#include <toml++/toml.hpp> +#include <iostream> + +int main() +{ + // Build a table programmatically + auto config = toml::table{ + { "app", toml::table{ + { "name", "MyApp" }, + { "version", 2 } + }}, + { "features", toml::array{ "auth", "logging" } } + }; + + // Navigate + std::cout << "App: " << config["app"]["name"].value_or("?"sv) << "\n"; + + // Insert + config["app"].as_table()->insert("debug", false); + + // Modify + config.insert_or_assign("features", + toml::array{ "auth", "logging", "metrics" }); + + // Check + if (config.contains("app")) + { + auto* app = config.get_as<toml::table>("app"); + std::cout << "App table has " << app->size() << " keys\n"; + } + + // Iterate + for (auto&& [key, value] : config) + { + std::cout << key << " (" << value.type() << ")\n"; + } + + // Serialize + std::cout << "\n" << config << "\n"; + + return 0; +} +``` + +--- + +## Related Documentation + +- [node-system.md](node-system.md) — Base node interface +- [arrays.md](arrays.md) — Array container details +- [values.md](values.md) — Value node details +- [path-system.md](path-system.md) — Path-based navigation diff --git a/docs/handbook/tomlplusplus/testing.md b/docs/handbook/tomlplusplus/testing.md new file mode 100644 index 0000000000..d8469413a2 --- /dev/null +++ b/docs/handbook/tomlplusplus/testing.md @@ -0,0 +1,226 @@ +# toml++ — Testing + +## Overview + +toml++ uses the **Catch2** testing framework. Tests are organized in `tests/` and built via Meson. The test suite includes unit tests for every major feature, TOML specification conformance tests, and third-party test suites. + +--- + +## Test Framework + +### Catch2 + +- Used as the test runner and assertion library +- Can be vendored (`extern/Catch2/`) or found as a system dependency +- Tests use `TEST_CASE`, `SECTION`, `REQUIRE`, `CHECK` macros + +### Build Configuration + +Tests are built with Meson. The test build options from `meson_options.txt`: + +``` +option('build_tests', type: 'boolean', value: false) +option('use_vendored_libs', type: 'boolean', value: true) +``` + +Build and run tests: + +```bash +meson setup build -Dbuild_tests=true +meson compile -C build +meson test -C build +``` + +--- + +## Test File Organization + +From `tests/meson.build`, the test suite consists of: + +### Conformance Tests + +Third-party test suites that validate the parser against the TOML specification: + +| Test Suite | Files | Description | +|-----------|-------|-------------| +| BurntSushi (valid) | `conformance_burntsushi_valid.cpp` | Validates that valid TOML parses correctly | +| BurntSushi (invalid) | `conformance_burntsushi_invalid.cpp` | Validates that invalid TOML is rejected | +| iarna (valid) | `conformance_iarna_valid.cpp` | Additional valid TOML test cases | +| iarna (invalid) | `conformance_iarna_invalid.cpp` | Additional invalid TOML test cases | + +Test data files are in `tests/` subdirectories corresponding to each third-party suite. + +### Parsing Tests + +Unit tests for the parser: + +| File | Content | +|------|---------| +| `parsing_arrays.cpp` | Array parsing edge cases | +| `parsing_booleans.cpp` | Boolean value parsing | +| `parsing_comments.cpp` | Comment handling | +| `parsing_dates_and_times.cpp` | Date/time value parsing | +| `parsing_floats.cpp` | Float parsing (inf, nan, precision) | +| `parsing_integers.cpp` | Integer parsing (hex, oct, bin, overflow) | +| `parsing_key_value_pairs.cpp` | Key-value pair syntax | +| `parsing_spec_example.cpp` | TOML spec example document | +| `parsing_strings.cpp` | All 4 string types, escape sequences | +| `parsing_tables.cpp` | Standard and inline tables | + +### Manipulation Tests + +Tests for programmatic construction and modification: + +| File | Content | +|------|---------| +| `manipulating_arrays.cpp` | Array push_back, erase, flatten, etc. | +| `manipulating_tables.cpp` | Table insert, emplace, merge, etc. | +| `manipulating_values.cpp` | Value construction, assignment, flags | +| `manipulating_parse_result.cpp` | parse_result access patterns | + +### Formatter Tests + +| File | Content | +|------|---------| +| `formatters.cpp` | TOML, JSON, and YAML formatter output | + +### Path Tests + +| File | Content | +|------|---------| +| `path.cpp` | Path parsing, navigation, at_path() | + +### Other Tests + +| File | Content | +|------|---------| +| `at_path.cpp` | at_path() function specifically | +| `for_each.cpp` | for_each() visitor pattern | +| `user_feedback.cpp` | Tests from user-reported issues | +| `windows_compat.cpp` | Windows wstring compatibility | +| `using_iterators.cpp` | Iterator usage patterns | +| `main.cpp` | Catch2 main entry point | +| `tests.hpp` | Shared test utilities and macros | + +--- + +## Running Tests + +### Full Test Suite + +```bash +cd tomlplusplus +meson setup build -Dbuild_tests=true +meson compile -C build +meson test -C build +``` + +### Verbose Output + +```bash +meson test -C build -v +``` + +### Running Specific Tests + +Catch2 allows filtering by test name: + +```bash +./build/tests/toml_test "parsing integers" +./build/tests/toml_test "[arrays]" +``` + +### Exception / No-Exception Modes + +Tests are compiled in both modes when possible: + +```bash +# With exceptions (default) +meson setup build_exc -Dbuild_tests=true + +# Without exceptions +meson setup build_noexc -Dbuild_tests=true -Dcpp_eh=none +``` + +--- + +## Test Patterns + +### Parsing Roundtrip + +A common pattern: parse TOML, verify values, re-serialize, verify output: + +```cpp +TEST_CASE("integers - hex") +{ + auto tbl = toml::parse("val = 0xFF"); + CHECK(tbl["val"].value<int64_t>() == 255); + CHECK(tbl["val"].as_integer()->flags() == toml::value_flags::format_as_hexadecimal); +} +``` + +### Invalid Input Rejection + +```cpp +TEST_CASE("invalid - unterminated string") +{ + CHECK_THROWS_AS(toml::parse("val = \"unterminated"), toml::parse_error); +} +``` + +Or without exceptions: + +```cpp +TEST_CASE("invalid - unterminated string") +{ + auto result = toml::parse("val = \"unterminated"); + CHECK(!result); + CHECK(result.error().description().find("unterminated") != std::string_view::npos); +} +``` + +### Manipulation Verification + +```cpp +TEST_CASE("array - push_back") +{ + toml::array arr; + arr.push_back(1); + arr.push_back("two"); + arr.push_back(3.0); + + REQUIRE(arr.size() == 3); + CHECK(arr[0].value<int64_t>() == 1); + CHECK(arr[1].value<std::string_view>() == "two"); + CHECK(arr[2].value<double>() == 3.0); +} +``` + +--- + +## Adding New Tests + +1. Create a `.cpp` file in `tests/` +2. Include `"tests.hpp"` for common utilities +3. Add the file to the test source list in `tests/meson.build` +4. Write `TEST_CASE` blocks using Catch2 macros +5. Rebuild and run + +```cpp +// tests/my_feature.cpp +#include "tests.hpp" + +TEST_CASE("my feature - basic behavior") +{ + auto tbl = toml::parse(R"(key = "value")"); + REQUIRE(tbl["key"].value<std::string_view>() == "value"); +} +``` + +--- + +## Related Documentation + +- [building.md](building.md) — Build system setup +- [code-style.md](code-style.md) — Code conventions +- [parsing.md](parsing.md) — Parser being tested diff --git a/docs/handbook/tomlplusplus/unicode-handling.md b/docs/handbook/tomlplusplus/unicode-handling.md new file mode 100644 index 0000000000..6cafb3deff --- /dev/null +++ b/docs/handbook/tomlplusplus/unicode-handling.md @@ -0,0 +1,335 @@ +# toml++ — Unicode Handling + +## Overview + +toml++ fully handles UTF-8 encoded input and output as required by the TOML specification. All TOML documents must be valid UTF-8, and the library validates, decodes, and encodes Unicode throughout parsing and formatting. + +Core Unicode utilities are in `include/toml++/impl/unicode.hpp` with auto-generated lookup tables in `unicode_autogenerated.hpp`. + +--- + +## UTF-8 Input Requirements + +The parser expects all input to be valid UTF-8: + +- **BOM handling**: A leading UTF-8 BOM (`0xEF 0xBB 0xBF`) is silently stripped before parsing begins +- **Validation**: Invalid byte sequences (overlong encodings, surrogate code points, truncated sequences) produce parse errors +- **Multi-byte characters**: Fully supported in string values, comments, and bare keys (where permitted by TOML) + +```cpp +// UTF-8 content works naturally +auto tbl = toml::parse(R"( + greeting = "Hello, 世界!" + emoji = "🎉" + name = "Ñoño" +)"); +``` + +--- + +## Character Classification + +The library classifies Unicode code points for parsing with functions in `unicode.hpp`: + +### `is_string_delimiter()` + +Identifies characters that can start/end strings: `"` (U+0022) and `'` (U+0027). + +### `is_ascii_letter()` + +`[A-Za-z]` — used in bare key validation and other ASCII-specific checks. + +### `is_ascii_whitespace()` + +Space (U+0020) and tab (U+0009). + +### `is_ascii_line_break()` + +LF (U+000A) and CR (U+000D). + +### `is_bare_key_character()` + +Characters permitted in TOML bare keys: `[A-Za-z0-9_-]` plus Unicode letters/digits when `TOML_LANG_UNRELEASED_FEATURES` is enabled. + +### `is_control_character()` + +Control characters (U+0000–U+001F, U+007F) excluding tab. These are forbidden in basic strings and must be escaped. + +### `is_non_ascii_letter()` + +Unicode letter code points outside ASCII — from auto-generated tables in `unicode_autogenerated.hpp`. Used for extended bare key support in unreleased TOML features. + +### `is_non_ascii_number()` + +Unicode digit code points outside ASCII (e.g., Arabic-Indic digits). + +### `is_non_ascii_whitespace()` + +Unicode whitespace beyond ASCII space/tab. + +--- + +## Escape Sequences in Strings + +TOML basic strings (`"..."` and `"""..."""`) support escape sequences. The parser decodes these into their UTF-8 representations: + +| Escape | Meaning | Code Point | +|--------|---------|------------| +| `\b` | Backspace | U+0008 | +| `\t` | Tab | U+0009 | +| `\n` | Line Feed | U+000A | +| `\f` | Form Feed | U+000C | +| `\r` | Carriage Return | U+000D | +| `\"` | Quote | U+0022 | +| `\\` | Backslash | U+005C | +| `\uXXXX` | Unicode (4 hex digits) | U+0000–U+FFFF | +| `\UXXXXXXXX` | Unicode (8 hex digits) | U+00000000–U+0010FFFF | + +### `control_char_escapes` Table + +The formatter uses a lookup table for serializing control characters back to escape sequences: + +```cpp +// In impl namespace: +inline constexpr const char* control_char_escapes[] = { + "\\u0000", "\\u0001", "\\u0002", "\\u0003", + "\\u0004", "\\u0005", "\\u0006", "\\u0007", + "\\b", "\\t", "\\n", "\\u000B", + "\\f", "\\r", "\\u000E", "\\u000F", + "\\u0010", "\\u0011", "\\u0012", "\\u0013", + "\\u0014", "\\u0015", "\\u0016", "\\u0017", + "\\u0018", "\\u0019", "\\u001A", "\\u001B", + "\\u001C", "\\u001D", "\\u001E", "\\u001F", +}; +``` + +--- + +## Unicode Escape Decoding + +The parser processes `\uXXXX` and `\UXXXXXXXX` escapes: + +1. Reads 4 or 8 hexadecimal digits +2. Validates the code point: + - Must not be a surrogate (U+D800–U+DFFF) + - Must not exceed U+10FFFF + - Must not be a non-character (U+FDD0–U+FDEF, U+xFFFE–U+xFFFF) +3. Encodes to UTF-8 bytes (1–4 bytes depending on code point range) + +```toml +# Valid Unicode escapes +escape_a = "\u0041" # "A" +escape_heart = "\u2764" # "❤" +escape_emoji = "\U0001F600" # "😀" +``` + +```cpp +auto tbl = toml::parse(R"( + a = "\u0041" + heart = "\u2764" +)"); + +std::cout << tbl["a"].value_or(""sv) << "\n"; // A +std::cout << tbl["heart"].value_or(""sv) << "\n"; // ❤ +``` + +--- + +## UTF-8 Encoding in Output + +When formatting, the behavior depends on `format_flags::allow_unicode_strings`: + +### With `allow_unicode_strings` (default for TOML and YAML formatters) + +Non-ASCII characters pass through unescaped: + +```cpp +auto tbl = toml::table{ { "name", "日本語" } }; +std::cout << tbl << "\n"; +// name = "日本語" +``` + +### Without `allow_unicode_strings` + +Non-ASCII characters are escaped to `\uXXXX` / `\UXXXXXXXX`: + +```cpp +auto tbl = toml::table{ { "name", "日本語" } }; +auto fmt = toml::toml_formatter{ + tbl, + toml::format_flags::indentation // no allow_unicode_strings +}; +std::cout << fmt << "\n"; +// name = "\u65E5\u672C\u8A9E" +``` + +--- + +## char8_t Support (C++20) + +When compiling with C++20, `char8_t` overloads are available for parsing: + +```cpp +auto tbl = toml::parse(u8R"( + greeting = "Hello, 世界" +)"sv); +``` + +The `char8_t` strings are internally treated as UTF-8 byte sequences. `std::u8string_view` is accepted by `parse()`. + +### `source_path` as u8string + +```cpp +auto tbl = toml::parse(doc, u8"config.toml"sv); +``` + +--- + +## Windows Compatibility (`TOML_ENABLE_WINDOWS_COMPAT`) + +When enabled (default on Windows), additional conversion overloads exist: + +- `parse_file(std::wstring_view)` — converts wide file path to UTF-8 +- `value<std::wstring>()` — converts stored UTF-8 string to wide string +- String comparison with `wchar_t*` / `std::wstring_view` + +The conversions use Windows API (`MultiByteToWideChar` / `WideCharToMultiByte`) internally. + +--- + +## Bare Key Unicode Rules + +Per TOML v1.0.0, bare keys are limited to ASCII letters, digits, hyphen, and underscore: + +```toml +valid-key = "value" +valid_key_2 = "value" +# 日本語 = "value" # NOT valid as bare key in TOML v1.0 +``` + +Non-ASCII keys must be quoted: + +```toml +"日本語" = "value" # valid as quoted key +``` + +### Unreleased Features + +With `TOML_UNRELEASED_FEATURES=1`, the parser accepts Unicode letters and digits in bare keys as proposed for future TOML versions: + +```toml +# Only with TOML_UNRELEASED_FEATURES=1: +日本語 = "value" # bare key with Unicode letters +``` + +The `is_non_ascii_letter()` and `is_non_ascii_number()` functions from `unicode_autogenerated.hpp` provide the code point tables for this classification. + +--- + +## Auto-Generated Unicode Tables + +`include/toml++/impl/unicode_autogenerated.hpp` contains machine-generated lookup tables derived from the Unicode Character Database. These tables classify code points by category: + +- **Letter** categories: Lu, Ll, Lt, Lm, Lo +- **Number** categories: Nd, Nl +- **Combining marks**: Mn, Mc +- **Connector punctuation**: Pc + +The tables use range-based compression for efficiency: + +```cpp +// Simplified representation: +struct code_point_range +{ + char32_t first; + char32_t last; +}; + +// Function uses binary search over sorted ranges +bool is_non_ascii_letter(char32_t cp) noexcept; +``` + +--- + +## String Handling in Formatters + +Each formatter handles strings slightly differently: + +### TOML Formatter + +- Defaults to basic strings with escaping: `"hello\nworld"` +- Uses literal strings when `allow_literal_strings` is set and string has no single quotes: `'no escapes needed'` +- Uses multi-line strings when `allow_multi_line_strings` is set and string contains newlines +- Preserves Unicode with `allow_unicode_strings` (default on) + +### JSON Formatter + +- Always uses double-quoted strings +- Escapes all required JSON characters +- Does not use literal or multi-line strings +- Unicode behavior follows `allow_unicode_strings` flag + +### YAML Formatter + +- Uses double-quoted strings +- `allow_unicode_strings` is on by default +- Escapes control characters + +--- + +## Complete Example + +```cpp +#include <toml++/toml.hpp> +#include <iostream> + +int main() +{ + // Parse document with Unicode content + auto config = toml::parse(R"( + title = "日本語テスト" + greeting = "Hello, 世界! 🌍" + escaped = "\u0048\u0065\u006C\u006C\u006F" + path = "C:\\Users\\名前\\config" + + [metadata] + "quoted.key" = "value" + author = "José García" + )"); + + // Read values — Unicode is preserved + auto title = config["title"].value_or(""sv); + std::cout << "Title: " << title << "\n"; + // Title: 日本語テスト + + auto greeting = config["greeting"].value_or(""sv); + std::cout << "Greeting: " << greeting << "\n"; + // Greeting: Hello, 世界! 🌍 + + // Escaped values are decoded + auto escaped = config["escaped"].value_or(""sv); + std::cout << "Escaped: " << escaped << "\n"; + // Escaped: Hello + + // Serialize back — Unicode preserved by default + std::cout << "\n=== TOML (Unicode) ===\n"; + std::cout << config << "\n"; + + // Serialize with Unicode escaping + std::cout << "\n=== TOML (Escaped) ===\n"; + std::cout << toml::toml_formatter{ + config, + toml::format_flags::indentation // no allow_unicode_strings + } << "\n"; + + return 0; +} +``` + +--- + +## Related Documentation + +- [parsing.md](parsing.md) — Parser UTF-8 input handling +- [formatting.md](formatting.md) — Unicode output control via format_flags +- [values.md](values.md) — String value type diff --git a/docs/handbook/tomlplusplus/values.md b/docs/handbook/tomlplusplus/values.md new file mode 100644 index 0000000000..453140df89 --- /dev/null +++ b/docs/handbook/tomlplusplus/values.md @@ -0,0 +1,547 @@ +# toml++ — Values + +## Overview + +`toml::value<T>` represents leaf TOML values — the concrete data in a TOML document. Each `value` wraps one of seven native C++ types corresponding to the TOML data types. + +Declared in `include/toml++/impl/value.hpp` with supporting types in `forward_declarations.hpp` and `date_time.hpp`. + +--- + +## Native Types + +The seven supported value types map to TOML types via `toml::value_type_of<T>`: + +| TOML Type | C++ Storage Type | `node_type` Enum | Alias | +|-------------------|---------------------|---------------------------|--------------------| +| String | `std::string` | `node_type::string` | `value<std::string>` | +| Integer | `int64_t` | `node_type::integer` | `value<int64_t>` | +| Float | `double` | `node_type::floating_point` | `value<double>` | +| Boolean | `bool` | `node_type::boolean` | `value<bool>` | +| Date | `toml::date` | `node_type::date` | `value<date>` | +| Time | `toml::time` | `node_type::time` | `value<time>` | +| Date-Time | `toml::date_time` | `node_type::date_time` | `value<date_time>` | + +Only these seven instantiations of `value<T>` exist. The template is constrained: + +```cpp +template <typename T> +class value : public node +{ + static_assert( + impl::is_native<T>, + "Template parameter must be one of the TOML native value types" + ); + + private: + value_type val_; + value_flags flags_ = value_flags::none; +}; +``` + +Where `value_type` is the `impl::native_type_of<T>` alias. + +--- + +## Type Traits + +```cpp +// Check at compile time +toml::is_string<value<std::string>> // true +toml::is_integer<value<int64_t>> // true +toml::is_floating_point<value<double>> // true +toml::is_boolean<value<bool>> // true +toml::is_date<value<date>> // true +toml::is_time<value<time>> // true +toml::is_date_time<value<date_time>> // true + +// Works on the raw types too +toml::is_integer<int64_t> // true +toml::is_number<int64_t> // true (integer or float) +toml::is_number<double> // true + +// Supertype checks +toml::is_value<value<int64_t>> // true (any value<T>) +toml::is_chronological<value<date>> // true (date, time, or date_time) +``` + +--- + +## Construction + +### From Compatible Types + +```cpp +toml::value<int64_t> i{ 42 }; +toml::value<double> f{ 3.14 }; +toml::value<std::string> s{ "hello" }; +toml::value<bool> b{ true }; + +// Implicit promotion from smaller integer types +toml::value<int64_t> from_int{ 42 }; // int → int64_t +toml::value<int64_t> from_short{ short(5) }; // short → int64_t + +// Implicit promotion from float → double +toml::value<double> from_float{ 1.5f }; // float → double +``` + +The `native_value_maker` mechanism handles promotions: + +```cpp +// In impl namespace: +template <typename T> +struct native_value_maker; + +// For integer types (int, unsigned, short, etc.): +// Promotes to int64_t + +// For floating-point (float): +// Promotes to double + +// For string types (const char*, string_view, etc.): +// Converts to std::string + +// For char8_t strings (u8"..."): +// Transcodes to std::string +``` + +### Copy and Move + +```cpp +toml::value<std::string> original{ "hello" }; +toml::value<std::string> copy{ original }; // deep copy +toml::value<std::string> moved{ std::move(original) }; // move +``` + +### Assignment + +```cpp +auto v = toml::value<int64_t>{ 10 }; +v = 42; // assign from raw value (operator=(ValueType)) +v = copy; // assign from another value (operator=(const value&)) +v = std::move(other); // move assign +``` + +--- + +## Retrieving Values + +### `get()` — Direct Access + +```cpp +ValueType& get() & noexcept; +ValueType&& get() && noexcept; +const ValueType& get() const& noexcept; +``` + +Returns a direct reference to the stored value: + +```cpp +auto v = toml::value<std::string>{ "hello" }; +std::string& s = v.get(); +s += " world"; +std::cout << v.get() << "\n"; // "hello world" +``` + +### `operator ValueType&()` — Implicit Conversion + +```cpp +explicit operator ValueType&() noexcept; +explicit operator const ValueType&() const noexcept; +``` + +```cpp +auto v = toml::value<int64_t>{ 42 }; +int64_t x = static_cast<int64_t>(v); +``` + +### `operator*()` / `operator->()` + +```cpp +ValueType& operator*() & noexcept; +const ValueType& operator*() const& noexcept; +ValueType* operator->() noexcept; +const ValueType* operator->() const noexcept; +``` + +Dereference-style access: + +```cpp +auto v = toml::value<std::string>{ "hello" }; +std::cout << v->length() << "\n"; // 5 +std::cout << (*v).size() << "\n"; // 5 +``` + +--- + +## Value Flags + +`value_flags` is a bitmask controlling how values are formatted when serialized: + +```cpp +enum class value_flags : uint16_t +{ + none = 0, + format_as_binary = 1, // 0b10101 + format_as_octal = 2, // 0o755 + format_as_hexadecimal = 4, // 0xFF + + // Special sentinel (default behavior): + // preserve_source_value_flags +}; +``` + +### Getting / Setting Flags + +```cpp +value_flags flags() const noexcept; +value<T>& flags(value_flags new_flags) noexcept; +``` + +```cpp +auto v = toml::value<int64_t>{ 255 }; +v.flags(toml::value_flags::format_as_hexadecimal); + +std::cout << toml::toml_formatter{ toml::table{ { "val", v } } }; +// Output: val = 0xFF +``` + +### Source Format Preservation + +When parsing, the library records the source format in the flags. When printing, if `preserve_source_value_flags` is used (the default), the original format is retained: + +```toml +port = 0xFF +mask = 0o777 +bits = 0b1010 +``` + +After parsing and re-serializing, these retain their hex/octal/binary format. + +--- + +## Date and Time Types + +Defined in `include/toml++/impl/date_time.hpp`. + +### `toml::date` + +```cpp +struct date +{ + uint16_t year; + uint8_t month; // 1-12 + uint8_t day; // 1-31 + + // Comparison operators + friend bool operator==(const date&, const date&) noexcept; + friend bool operator!=(const date&, const date&) noexcept; + friend bool operator< (const date&, const date&) noexcept; + friend bool operator<=(const date&, const date&) noexcept; + friend bool operator> (const date&, const date&) noexcept; + friend bool operator>=(const date&, const date&) noexcept; + + // Streaming + friend std::ostream& operator<<(std::ostream&, const date&); +}; +``` + +```cpp +auto d = toml::date{ 2024, 1, 15 }; +auto v = toml::value<toml::date>{ d }; +std::cout << v << "\n"; // 2024-01-15 +``` + +### `toml::time` + +```cpp +struct time +{ + uint8_t hour; // 0-23 + uint8_t minute; // 0-59 + uint8_t second; // 0-59 (0-60 for leap second) + uint32_t nanosecond; // 0-999999999 + + // Comparison and streaming operators +}; +``` + +```cpp +auto t = toml::time{ 14, 30, 0 }; +auto v = toml::value<toml::time>{ t }; +std::cout << v << "\n"; // 14:30:00 +``` + +### `toml::time_offset` + +```cpp +struct time_offset +{ + int16_t minutes; // UTC offset: -720 to +840 + + // Convenience for UTC: + static constexpr time_offset utc() noexcept { return { 0 }; } + + // Comparison operators +}; +``` + +### `toml::date_time` + +```cpp +struct date_time +{ + toml::date date; + toml::time time; + optional<time_offset> offset; // nullopt = local date-time + + // Constructor overloads: + constexpr date_time(const toml::date& d, const toml::time& t) noexcept; + constexpr date_time(const toml::date& d, const toml::time& t, + const toml::time_offset& off) noexcept; + + bool is_local() const noexcept; // true if no offset + + // Comparison and streaming operators +}; +``` + +#### TOML Date-Time Variants + +```toml +# Offset date-time (has time zone) +odt = 2024-01-15T14:30:00+05:30 +odt_utc = 2024-01-15T09:00:00Z + +# Local date-time (no time zone) +ldt = 2024-01-15T14:30:00 + +# Local date +ld = 2024-01-15 + +# Local time +lt = 14:30:00 +``` + +```cpp +auto tbl = toml::parse(R"( + odt = 2024-01-15T14:30:00+05:30 + ldt = 2024-01-15T14:30:00 + ld = 2024-01-15 + lt = 14:30:00 +)"); + +auto odt = tbl["odt"].value<toml::date_time>(); +// odt->offset has value, odt->is_local() == false + +auto ldt = tbl["ldt"].value<toml::date_time>(); +// ldt->offset is nullopt, ldt->is_local() == true + +auto ld = tbl["ld"].value<toml::date>(); +// ld->year == 2024, month == 1, day == 15 + +auto lt = tbl["lt"].value<toml::time>(); +// lt->hour == 14, minute == 30, second == 0 +``` + +--- + +## Type Identity + +```cpp +// For value<int64_t>: +node_type type() const noexcept final; // node_type::integer +bool is_integer() const noexcept final; // true +bool is_number() const noexcept final; // true +bool is_value() const noexcept final; // true +// All other is_*() return false + +value<int64_t>* as_integer() noexcept final; // returns this +// All other as_*() return nullptr +``` + +--- + +## Retrieving Values from Nodes + +From the base `node` or `node_view`, there are multiple retrieval patterns: + +### `value<T>()` — Get with Type Coercion + +```cpp +// As node method: +optional<T> value() const noexcept; +``` + +Attempts to return the value as `T`, with permitted coercions: +- `int64_t` → `int`, `unsigned`, `size_t`, etc. (bounds-checked) +- `double` → `float` (precision loss allowed) +- `double` ↔ `int64_t` (within representable range) + +```cpp +auto tbl = toml::parse("x = 42"); + +auto as_int = tbl["x"].value<int64_t>(); // optional(42) +auto as_double = tbl["x"].value<double>(); // optional(42.0) +auto as_int32 = tbl["x"].value<int>(); // optional(42) +auto as_string = tbl["x"].value<std::string>(); // nullopt (type mismatch) +``` + +### `value_exact<T>()` — No Coercion + +```cpp +optional<T> value_exact() const noexcept; +``` + +Only succeeds if the stored type exactly matches `T` (no int→double or similar coercions): + +```cpp +auto tbl = toml::parse("x = 42"); + +auto exact_int = tbl["x"].value_exact<int64_t>(); // optional(42) +auto exact_dbl = tbl["x"].value_exact<double>(); // nullopt (it's an int) +``` + +### `value_or()` — With Default + +```cpp +template <typename T> +auto value_or(T&& default_value) const noexcept; +``` + +Returns the value or a default: + +```cpp +auto tbl = toml::parse("name = \"Alice\""); + +auto name = tbl["name"].value_or("unknown"sv); // "Alice" +auto age = tbl["age"].value_or(int64_t{ 0 }); // 0 (key missing) +``` + +--- + +## Comparison + +```cpp +// Between values of same type +friend bool operator==(const value& lhs, const value& rhs) noexcept; + +// Between value and raw type +friend bool operator==(const value<T>& lhs, const T& rhs) noexcept; +``` + +```cpp +auto a = toml::value<int64_t>{ 42 }; +auto b = toml::value<int64_t>{ 42 }; + +std::cout << (a == b) << "\n"; // true +std::cout << (a == 42) << "\n"; // true +std::cout << (a != 99) << "\n"; // true +``` + +For `value<std::string>`, comparison also works with `std::string_view` and `const char*`: + +```cpp +auto s = toml::value<std::string>{ "hello" }; +std::cout << (s == "hello") << "\n"; // true +std::cout << (s == "world"sv) << "\n"; // false +``` + +--- + +## `make_value<T>` + +Utility function in `make_node.hpp` for constructing values: + +```cpp +template <typename T, typename... Args> +auto make_value(Args&&... args) + -> decltype(std::make_unique<impl::wrap_node<T>>(std::forward<Args>(args)...)); +``` + +Returns `std::unique_ptr<value<T>>`: + +```cpp +auto v = toml::make_value<int64_t>(42); +// v is std::unique_ptr<toml::value<int64_t>> +``` + +--- + +## Printing + +Values stream via the default formatter: + +```cpp +auto v = toml::value<std::string>{ "hello world" }; +std::cout << v << "\n"; // "hello world" + +auto d = toml::value<toml::date>{ toml::date{ 2024, 6, 15 } }; +std::cout << d << "\n"; // 2024-06-15 + +auto i = toml::value<int64_t>{ 255 }; +i.flags(toml::value_flags::format_as_hexadecimal); +std::cout << i << "\n"; // 0xFF +``` + +--- + +## Complete Example + +```cpp +#include <toml++/toml.hpp> +#include <iostream> + +int main() +{ + auto config = toml::parse(R"( + title = "My App" + version = 3 + debug = true + pi = 3.14159 + created = 2024-01-15T10:30:00Z + expires = 2025-12-31 + check_time = 08:00:00 + )"); + + // Type-safe retrieval with defaults + auto title = config["title"].value_or("Untitled"sv); + auto version = config["version"].value_or(int64_t{ 1 }); + auto debug = config["debug"].value_or(false); + auto pi = config["pi"].value_or(0.0); + + std::cout << "Title: " << title << "\n"; + std::cout << "Version: " << version << "\n"; + std::cout << "Debug: " << debug << "\n"; + std::cout << "Pi: " << pi << "\n"; + + // Date-time access + if (auto dt = config["created"].value<toml::date_time>()) + { + std::cout << "Created: " << dt->date.year + << "-" << (int)dt->date.month + << "-" << (int)dt->date.day << "\n"; + + if (!dt->is_local()) + std::cout << " Offset: " << dt->offset->minutes << " min\n"; + } + + // Modify and re-serialize + auto* v = config["version"].as_integer(); + if (v) *v = 4; + + std::cout << "\n" << config << "\n"; + + return 0; +} +``` + +--- + +## Related Documentation + +- [node-system.md](node-system.md) — Base node class and type dispatch +- [arrays.md](arrays.md) — Array container +- [tables.md](tables.md) — Table container +- [parsing.md](parsing.md) — Parsing values from TOML text +- [formatting.md](formatting.md) — Formatting values for output |
