Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdcfc178af | ||
|
|
30e1adef93 | ||
|
|
afea570755 | ||
|
|
e7d245db7e | ||
|
|
d38a04bd3e |
No files matched your search
@@ -113,15 +113,15 @@ basic_json(basic_json&& other) noexcept;
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A(["initializer_list init"]) --> B{{"empty, or every element is a 2-element<br/>array whose first element is a string?"}}
|
A(["initializer_list init"]) --> B{"empty, or every element is a 2-element<br/>array whose first element is a string?"}
|
||||||
B -->|"yes"| C{{"type_deduction"}}
|
B -->|"yes"| C{"type_deduction"}
|
||||||
B -->|"no"| D{{"type_deduction"}}
|
B -->|"no"| D{"type_deduction"}
|
||||||
C -->|"true"| OBJ["create object"]
|
C -->|"true"| OBJ["create object"]
|
||||||
C -->|"false"| E{{"manual_type"}}
|
C -->|"false"| E{"manual_type"}
|
||||||
E -->|"object"| OBJ
|
E -->|"object"| OBJ
|
||||||
E -->|"array"| ARR["create array"]
|
E -->|"array"| ARR["create array"]
|
||||||
D -->|"true"| ARR
|
D -->|"true"| ARR
|
||||||
D -->|"false"| F{{"manual_type"}}
|
D -->|"false"| F{"manual_type"}
|
||||||
F -->|"array"| ARR
|
F -->|"array"| ARR
|
||||||
F -->|"object"| ERR["throw type_error.301"]
|
F -->|"object"| ERR["throw type_error.301"]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ const_iterator begin() const noexcept;
|
|||||||
|
|
||||||
Returns an iterator to the first element.
|
Returns an iterator to the first element.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-begin-end.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ const_iterator cbegin() const noexcept;
|
|||||||
|
|
||||||
Returns an iterator to the first element.
|
Returns an iterator to the first element.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-begin-end.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ const_iterator cend() const noexcept;
|
|||||||
|
|
||||||
Returns an iterator to one past the last element.
|
Returns an iterator to one past the last element.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-begin-end.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ const_reverse_iterator crbegin() const noexcept;
|
|||||||
|
|
||||||
Returns an iterator to the reverse-beginning; that is, the last element.
|
Returns an iterator to the reverse-beginning; that is, the last element.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-rbegin-rend.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ const_reverse_iterator crend() const noexcept;
|
|||||||
Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder,
|
Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder,
|
||||||
attempting to access it results in undefined behavior.
|
attempting to access it results in undefined behavior.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-rbegin-rend.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ const_iterator end() const noexcept;
|
|||||||
|
|
||||||
Returns an iterator to one past the last element.
|
Returns an iterator to one past the last element.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-begin-end.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ The parser callback distinguishes the following events:
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/callback_events.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ table describes the values of the parameters `depth`, `event`, and `parsed`.
|
|||||||
| `parse_event_t::array_end` | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array |
|
| `parse_event_t::array_end` | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array |
|
||||||
| `parse_event_t::value` | the parser finished reading a JSON value | depth of the value | the parsed JSON value |
|
| `parse_event_t::value` | the parser finished reading a JSON value | depth of the value | the parsed JSON value |
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/callback_events.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
Discarding a value (i.e., returning `#!cpp false`) has different effects depending on the context in which function was
|
Discarding a value (i.e., returning `#!cpp false`) has different effects depending on the context in which function was
|
||||||
called:
|
called:
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ const_reverse_iterator rbegin() const noexcept;
|
|||||||
|
|
||||||
Returns an iterator to the reverse-beginning; that is, the last element.
|
Returns an iterator to the reverse-beginning; that is, the last element.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-rbegin-rend.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ const_reverse_iterator rend() const noexcept;
|
|||||||
Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder,
|
Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder,
|
||||||
attempting to access it results in undefined behavior.
|
attempting to access it results in undefined behavior.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-rbegin-rend.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
|
|||||||
@@ -1,739 +0,0 @@
|
|||||||
/* nlohmann/json brand for Material for MkDocs (values from brand/kit/tokens.css) */
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Inter";
|
|
||||||
src: url("fonts/Inter.woff2") format("woff2");
|
|
||||||
font-weight: 100 900;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Inter";
|
|
||||||
src: url("fonts/Inter-Italic.woff2") format("woff2");
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 100 900;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "JetBrains Mono";
|
|
||||||
src: url("fonts/JetBrainsMono.woff2") format("woff2");
|
|
||||||
font-weight: 100 800;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "JetBrains Mono";
|
|
||||||
src: url("fonts/JetBrainsMono-Italic.woff2") format("woff2");
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--md-text-font: "Inter";
|
|
||||||
--md-code-font: "JetBrains Mono";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* light */
|
|
||||||
[data-md-color-scheme="default"] {
|
|
||||||
--nj-blue: #1747a6;
|
|
||||||
--nj-blue-deep: #10357d;
|
|
||||||
--nj-blue-soft: #eaf1fd;
|
|
||||||
--nj-band: #1747a6;
|
|
||||||
--nj-red: #d93a3f;
|
|
||||||
--nj-red-soft: #fdeced;
|
|
||||||
--nj-string: #b4322a;
|
|
||||||
--nj-comment: #646d7a;
|
|
||||||
|
|
||||||
--md-primary-fg-color: var(--nj-band);
|
|
||||||
--md-primary-fg-color--light: #1d55c4;
|
|
||||||
--md-primary-fg-color--dark: #10357d;
|
|
||||||
--md-accent-fg-color: var(--nj-blue-deep);
|
|
||||||
--md-accent-fg-color--transparent: rgba(23, 71, 166, .1);
|
|
||||||
--md-typeset-a-color: var(--nj-blue);
|
|
||||||
--md-default-fg-color: #0f1419;
|
|
||||||
--md-default-fg-color--light: #4a515c;
|
|
||||||
--md-default-fg-color--lighter: #8a919c;
|
|
||||||
--md-default-fg-color--lightest: #e3e6eb;
|
|
||||||
--md-typeset-color: #0f1419;
|
|
||||||
--md-code-bg-color: #f5f7fa;
|
|
||||||
--md-code-fg-color: #0f1419;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* dark */
|
|
||||||
[data-md-color-scheme="slate"] {
|
|
||||||
--nj-blue: #7aa7f5;
|
|
||||||
--nj-blue-deep: #a9c6f8;
|
|
||||||
--nj-blue-soft: #16233b;
|
|
||||||
--nj-band: #173e8c;
|
|
||||||
--nj-red: #f06a6e;
|
|
||||||
--nj-red-soft: #3a1c1e;
|
|
||||||
--nj-string: #f08a7e;
|
|
||||||
--nj-comment: #8b929e;
|
|
||||||
|
|
||||||
--md-hue: 220;
|
|
||||||
--md-primary-fg-color: var(--nj-band);
|
|
||||||
--md-primary-fg-color--light: #163f93;
|
|
||||||
--md-primary-fg-color--dark: #0c2556;
|
|
||||||
--md-accent-fg-color: var(--nj-blue-deep);
|
|
||||||
--md-accent-fg-color--transparent: rgba(122, 167, 245, .12);
|
|
||||||
--md-typeset-a-color: var(--nj-blue);
|
|
||||||
--md-default-bg-color: #0f1218;
|
|
||||||
--md-default-fg-color: #f2f4f7;
|
|
||||||
--md-default-fg-color--light: #b3b9c4;
|
|
||||||
--md-default-fg-color--lighter: #7b828e;
|
|
||||||
--md-default-fg-color--lightest: #272c36;
|
|
||||||
--md-typeset-color: #f2f4f7;
|
|
||||||
--md-code-bg-color: #171b23;
|
|
||||||
--md-code-fg-color: #f2f4f7;
|
|
||||||
--md-footer-bg-color: #0b0e13;
|
|
||||||
--md-footer-bg-color--dark: #08090d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* syntax colours (all at least 4.5:1 on the code panel in both schemes) */
|
|
||||||
[data-md-color-scheme="default"] {
|
|
||||||
--nj-hl-keyword: #1747a6;
|
|
||||||
--nj-hl-type: #0d6e72;
|
|
||||||
--nj-hl-function: #6b3fb8;
|
|
||||||
--nj-hl-string: #23793a;
|
|
||||||
--nj-hl-escape: #0f5d2a;
|
|
||||||
--nj-hl-key: #10357d;
|
|
||||||
--nj-hl-number: #b04a07;
|
|
||||||
--nj-hl-preproc: #a3247a;
|
|
||||||
}
|
|
||||||
[data-md-color-scheme="slate"] {
|
|
||||||
--nj-hl-keyword: #7aa7f5;
|
|
||||||
--nj-hl-type: #4fd1c5;
|
|
||||||
--nj-hl-function: #c4a3ff;
|
|
||||||
--nj-hl-string: #7ed491;
|
|
||||||
--nj-hl-escape: #b2ecc0;
|
|
||||||
--nj-hl-key: #a9c6f8;
|
|
||||||
--nj-hl-number: #f5a65b;
|
|
||||||
--nj-hl-preproc: #f48fc8;
|
|
||||||
}
|
|
||||||
.md-typeset .highlight :is(.k, .kd, .kn, .kr, .kp, .kv, .ow) { color: var(--nj-hl-keyword); font-weight: 600; }
|
|
||||||
.md-typeset .highlight :is(.kt, .nc, .nn, .na) { color: var(--nj-hl-type); }
|
|
||||||
.md-typeset .highlight :is(.nf, .fm, .nb, .bp) { color: var(--nj-hl-function); }
|
|
||||||
.md-typeset .highlight :is(.s, .s1, .s2, .sb, .sc, .sd, .sh, .sx, .sr, .ss, .sa, .dl, .cpf) { color: var(--nj-hl-string); }
|
|
||||||
.md-typeset .highlight :is(.se, .si) { color: var(--nj-hl-escape); font-weight: 600; }
|
|
||||||
.md-typeset .highlight .nt { color: var(--nj-hl-key); }
|
|
||||||
.md-typeset .highlight :is(.m, .mi, .mf, .mh, .mo, .mb, .il, .kc) { color: var(--nj-hl-number); }
|
|
||||||
.md-typeset .highlight .kc { font-weight: 500; }
|
|
||||||
.md-typeset .highlight .cp { color: var(--nj-hl-preproc); }
|
|
||||||
.md-typeset .highlight :is(.c, .c1, .cm, .ch, .cs) { color: var(--nj-comment); font-style: italic; }
|
|
||||||
.md-typeset .highlight :is(.o, .p) { color: var(--md-default-fg-color--light); }
|
|
||||||
.md-typeset .highlight :is(.n, .nx, .nv, .no, .ni, .nl, .py) { color: var(--md-code-fg-color); }
|
|
||||||
.md-typeset .highlight .err { color: inherit; background: none; }
|
|
||||||
|
|
||||||
/* typography: Inter with cv11/ss03, bold headlines with negative tracking */
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv11", "ss03";
|
|
||||||
}
|
|
||||||
.md-typeset {
|
|
||||||
font-size: .85rem; /* 17 px body */
|
|
||||||
line-height: 1.65;
|
|
||||||
}
|
|
||||||
.md-typeset h1 {
|
|
||||||
color: var(--md-typeset-color);
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: -.035em;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
.md-typeset h2 {
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: -.025em;
|
|
||||||
line-height: 1.15;
|
|
||||||
}
|
|
||||||
.md-typeset h3 {
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -.015em;
|
|
||||||
}
|
|
||||||
.md-typeset table:not([class]) {
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* header and tabs: one blue band with the faint syntax pattern; wordmark instead of the site name.
|
|
||||||
A veil over the tile keeps the glyphs from running through the navigation text; the hero shows the pattern at full strength. */
|
|
||||||
.md-header, .md-tabs {
|
|
||||||
--nj-veil: linear-gradient(color-mix(in srgb, var(--md-primary-fg-color) 62%, transparent), color-mix(in srgb, var(--md-primary-fg-color) 62%, transparent));
|
|
||||||
background-color: var(--md-primary-fg-color);
|
|
||||||
background-image: var(--nj-veil), url("pattern-on-blue.svg");
|
|
||||||
background-size: auto, 160px 160px;
|
|
||||||
background-position: 0 0, 0 0;
|
|
||||||
}
|
|
||||||
.md-tabs {
|
|
||||||
background-position: 0 0, 0 -48px; /* continue the header's tile */
|
|
||||||
}
|
|
||||||
.md-header__button.md-logo img {
|
|
||||||
width: auto;
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
.md-header__topic:first-child .md-ellipsis { /* the wordmark replaces the site name at every width */
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 76.234375em) { /* Material hides the header logo here: show the wordmark on phones, too */
|
|
||||||
.md-header__button.md-logo {
|
|
||||||
display: block;
|
|
||||||
margin: 0 0 0 .2rem;
|
|
||||||
padding: .4rem .2rem;
|
|
||||||
}
|
|
||||||
.md-header__button[for="__drawer"] {
|
|
||||||
order: -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.md-nav__button.md-logo img {
|
|
||||||
width: auto;
|
|
||||||
height: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* shapes: 6 px corners on blocks, 4 px on inline code */
|
|
||||||
.md-typeset pre > code, .md-typeset .highlight, .md-typeset .highlighttable,
|
|
||||||
.md-typeset .admonition, .md-typeset details, .md-typeset .tabbed-set,
|
|
||||||
.md-typeset .grid.cards > ul > li, .md-typeset .md-button {
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.md-typeset code {
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* inline code: the font alone marks it, no grey box; linked code keeps the link colour */
|
|
||||||
.md-typeset :not(pre) > code {
|
|
||||||
padding: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
color: inherit;
|
|
||||||
font-size: .88em;
|
|
||||||
}
|
|
||||||
.md-typeset .highlight pre > code, .md-typeset .highlighttable {
|
|
||||||
border: 1px solid var(--md-default-fg-color--lightest);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* callouts: 3 px left border; note/info/tip in blue on blue-soft, danger/bug in red on red-soft */
|
|
||||||
.md-typeset .admonition, .md-typeset details {
|
|
||||||
border-width: 0 0 0 3px;
|
|
||||||
box-shadow: none;
|
|
||||||
font-size: .8rem;
|
|
||||||
}
|
|
||||||
.md-typeset :is(.admonition, details):is(.note, .info, .tip, .abstract, .example, .question) {
|
|
||||||
border-color: var(--nj-blue);
|
|
||||||
background-color: var(--nj-blue-soft);
|
|
||||||
}
|
|
||||||
.md-typeset :is(.note, .info, .tip, .abstract, .example, .question) > :is(.admonition-title, summary) {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.md-typeset :is(.note, .info, .tip, .abstract, .example, .question) > :is(.admonition-title, summary)::before {
|
|
||||||
background-color: var(--nj-blue);
|
|
||||||
}
|
|
||||||
.md-typeset :is(.admonition, details):is(.danger, .bug, .failure) {
|
|
||||||
border-color: var(--nj-red);
|
|
||||||
background-color: var(--nj-red-soft);
|
|
||||||
}
|
|
||||||
.md-typeset :is(.danger, .bug, .failure) > :is(.admonition-title, summary) {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.md-typeset :is(.danger, .bug, .failure) > :is(.admonition-title, summary)::before {
|
|
||||||
background-color: var(--nj-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* buttons */
|
|
||||||
.md-typeset .md-button--primary {
|
|
||||||
background-color: var(--nj-blue);
|
|
||||||
border-color: var(--nj-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* home page hero (overrides/home.html): blue band with the syntax pattern, headline, lead, buttons, code panel */
|
|
||||||
.nj-hero {
|
|
||||||
--nj-hero-to: #123a8a;
|
|
||||||
color: #fff;
|
|
||||||
background-color: var(--md-primary-fg-color);
|
|
||||||
background-image: url("pattern-on-blue.svg"), linear-gradient(160deg, var(--md-primary-fg-color), var(--nj-hero-to));
|
|
||||||
background-size: 160px 160px, auto;
|
|
||||||
background-position: 0 -96px, 0 0; /* continue the tile of header and tabs */
|
|
||||||
}
|
|
||||||
[data-md-color-scheme="slate"] .nj-hero {
|
|
||||||
--nj-hero-to: #0c2556;
|
|
||||||
}
|
|
||||||
.nj-hero__inner {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
|
|
||||||
gap: 2.4rem;
|
|
||||||
align-items: center;
|
|
||||||
padding: 3rem .8rem 3.6rem;
|
|
||||||
}
|
|
||||||
.nj-hero__kicker {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
padding: .15rem .55rem;
|
|
||||||
border: 1px solid rgba(255, 255, 255, .35);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #bcd3fb;
|
|
||||||
font-family: var(--md-code-font-family);
|
|
||||||
font-size: .65rem; /* 13 px */
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: .02em;
|
|
||||||
}
|
|
||||||
.nj-hero__title {
|
|
||||||
margin: 0 0 .8rem;
|
|
||||||
color: #fff;
|
|
||||||
font-size: clamp(1.7rem, 4.2vw, 3.2rem); /* 34-64 px */
|
|
||||||
font-weight: 800;
|
|
||||||
line-height: 1.02;
|
|
||||||
letter-spacing: -.035em;
|
|
||||||
}
|
|
||||||
.nj-hero__title span {
|
|
||||||
color: #bcd3fb;
|
|
||||||
}
|
|
||||||
.nj-hero__lead {
|
|
||||||
max-width: 22em;
|
|
||||||
margin: 0 0 2rem;
|
|
||||||
color: rgba(255, 255, 255, .9);
|
|
||||||
font-size: clamp(1.1rem, 2.2vw, 1.6rem); /* 22-32 px */
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.35;
|
|
||||||
letter-spacing: -.015em;
|
|
||||||
}
|
|
||||||
.nj-hero__actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: .6rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.nj-hero .md-button.nj-hero__button {
|
|
||||||
margin: 0;
|
|
||||||
padding: .55em 1.3em;
|
|
||||||
border: 1px solid rgba(255, 255, 255, .6);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: #fff;
|
|
||||||
font-size: .75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.nj-hero .md-button--primary.nj-hero__button {
|
|
||||||
border-color: #fff;
|
|
||||||
background-color: #fff;
|
|
||||||
color: var(--md-primary-fg-color);
|
|
||||||
}
|
|
||||||
.nj-hero .md-button.nj-hero__button:is(:hover, :focus) {
|
|
||||||
border-color: #fff;
|
|
||||||
background-color: rgba(255, 255, 255, .12);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.nj-hero .md-button--primary.nj-hero__button:is(:hover, :focus) {
|
|
||||||
background-color: #eaf1fd;
|
|
||||||
color: var(--md-primary-fg-color);
|
|
||||||
}
|
|
||||||
.nj-hero__code {
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: var(--md-code-bg-color);
|
|
||||||
box-shadow: 0 12px 32px rgba(5, 18, 48, .35);
|
|
||||||
}
|
|
||||||
.nj-hero__code pre {
|
|
||||||
margin: 0;
|
|
||||||
padding: 1.1rem 1.3rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
color: var(--md-code-fg-color);
|
|
||||||
font-family: var(--md-code-font-family);
|
|
||||||
font-size: .7rem; /* 14 px */
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
.nj-hero__code .nj-k { color: var(--nj-hl-type); }
|
|
||||||
.nj-hero__code .nj-s { color: var(--nj-hl-string); }
|
|
||||||
.nj-hero__code .nj-n { color: var(--nj-hl-number); }
|
|
||||||
.nj-hero__code .nj-c { color: var(--nj-comment); font-style: italic; }
|
|
||||||
@media screen and (max-width: 59.984375em) {
|
|
||||||
.nj-hero__inner {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
gap: 1.8rem;
|
|
||||||
padding: 2rem .8rem 2.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 29.984375em) {
|
|
||||||
.nj-hero__code pre {
|
|
||||||
font-size: .6rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the hero carries the headline: keep the page's h1 for the title and screen readers only */
|
|
||||||
body:has(.nj-hero) .md-content__inner > h1 {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0 0 0 0);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* cards: 1 px line border, 6 px corners, blue icon on soft blue, blue border on hover */
|
|
||||||
.md-typeset .grid.cards > ul > li {
|
|
||||||
padding: 1rem 1.1rem;
|
|
||||||
border: 1px solid var(--md-default-fg-color--lightest);
|
|
||||||
background-color: var(--md-default-bg-color);
|
|
||||||
transition: border-color 150ms, box-shadow 150ms, transform 150ms;
|
|
||||||
}
|
|
||||||
.md-typeset .grid.cards > ul > li:is(:hover, :focus-within) {
|
|
||||||
border-color: var(--nj-blue);
|
|
||||||
box-shadow: 0 6px 18px rgba(23, 71, 166, .12);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
.md-typeset .grid.cards > ul > li > hr {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.md-typeset .grid.cards .twemoji.lg {
|
|
||||||
box-sizing: content-box;
|
|
||||||
margin-right: .5rem;
|
|
||||||
padding: .3rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: var(--nj-blue-soft);
|
|
||||||
color: var(--nj-blue);
|
|
||||||
}
|
|
||||||
.md-typeset .grid.cards > ul > li > p:first-child strong {
|
|
||||||
font-size: .9rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -.01em;
|
|
||||||
}
|
|
||||||
.md-typeset .grid.cards > ul > li > p:last-child a {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.md-typeset .grid.cards > ul > li { transition: none; transform: none !important; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* home page: no table of contents, so keep the text at reading width and the cards in three columns */
|
|
||||||
body:has(.nj-hero) .md-content {
|
|
||||||
max-width: 58rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 60em) {
|
|
||||||
body:has(.nj-hero) .md-typeset .grid.cards {
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* lists: Material's indent, bullets as small blue squares, numbers in the secondary text colour */
|
|
||||||
.md-typeset ul:not(.task-list, .md-annotation-list) > li {
|
|
||||||
position: relative;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
.md-typeset ul:not(.task-list, .md-annotation-list) > li::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: .65em; /* middle of the first line at line-height 1.65 */
|
|
||||||
left: -.95em;
|
|
||||||
width: .36em;
|
|
||||||
height: .36em;
|
|
||||||
background-color: var(--nj-blue);
|
|
||||||
}
|
|
||||||
.md-typeset ol > li::marker {
|
|
||||||
color: var(--md-default-fg-color--light);
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
.md-typeset .grid.cards > ul > li::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* wider page: Material's grid stops at 61rem (1220 px) */
|
|
||||||
.md-grid {
|
|
||||||
max-width: 72rem; /* 1440 px */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* tables: full width, 1 px line border with 6 px corners, header row on the panel colour, 15 px text */
|
|
||||||
.md-typeset__table {
|
|
||||||
display: block; /* Material: inline-block, so tables only grow to their content */
|
|
||||||
}
|
|
||||||
.md-typeset table:not([class]) {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid var(--md-default-fg-color--lightest);
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: .75rem;
|
|
||||||
line-height: 1.55;
|
|
||||||
}
|
|
||||||
.md-typeset table:not([class]) th {
|
|
||||||
padding: .65em 1em;
|
|
||||||
border-bottom: 1px solid var(--md-default-fg-color--lightest);
|
|
||||||
background-color: var(--md-code-bg-color);
|
|
||||||
color: var(--md-typeset-color);
|
|
||||||
font-weight: 600;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
.md-typeset table:not([class]) th:first-child { border-top-left-radius: 5px; }
|
|
||||||
.md-typeset table:not([class]) th:last-child { border-top-right-radius: 5px; }
|
|
||||||
.md-typeset table:not([class]) td {
|
|
||||||
padding: .65em 1em;
|
|
||||||
border-top: 1px solid var(--md-default-fg-color--lightest);
|
|
||||||
}
|
|
||||||
.md-typeset table:not([class]) tbody tr:first-child td {
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
.md-typeset table:not([class]) tbody tr:hover {
|
|
||||||
background-color: var(--nj-blue-soft);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.md-typeset table:not([class]) td code {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
/* numbers and header code stay on one line; a table wider than the screen scrolls sideways instead */
|
|
||||||
.md-typeset table:not([class]) :is(td, th):is([align="right"], [style*="right"]),
|
|
||||||
.md-typeset table:not([class]) th code {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 44.984375em) {
|
|
||||||
.md-typeset table:not([class]) {
|
|
||||||
font-size: .7rem;
|
|
||||||
}
|
|
||||||
.md-typeset table:not([class]) :is(th, td) {
|
|
||||||
padding: .55em .75em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* more room before headings (Material: h2 1.6em, h3 1.6em, h4-h6 1-1.25em) and at the end of each page (1.2rem) */
|
|
||||||
.md-typeset h2 { margin-top: 2.4em; }
|
|
||||||
.md-typeset h3 { margin-top: 2.2em; }
|
|
||||||
.md-typeset h2 + h3 { margin-top: .8em; }
|
|
||||||
.md-typeset :is(h4, h5, h6) { margin-top: 1.8em; }
|
|
||||||
.md-content__inner {
|
|
||||||
margin-bottom: 4.8rem; /* 96 px */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mermaid diagrams: white boxes with a blue outline, grey lines, Inter; shapes and spacing in js/mermaid-brand.js */
|
|
||||||
:root > * {
|
|
||||||
--md-mermaid-font-family: var(--md-text-font-family), sans-serif;
|
|
||||||
--md-mermaid-edge-color: var(--md-default-fg-color--lighter);
|
|
||||||
--md-mermaid-node-bg-color: var(--md-default-bg-color);
|
|
||||||
--md-mermaid-node-fg-color: var(--nj-blue);
|
|
||||||
--md-mermaid-label-bg-color: var(--md-default-bg-color);
|
|
||||||
--md-mermaid-label-fg-color: var(--md-default-fg-color);
|
|
||||||
--md-mermaid-sequence-actor-bg-color: var(--nj-blue-soft);
|
|
||||||
--md-mermaid-sequence-actor-line-color: var(--md-default-fg-color--lighter);
|
|
||||||
--md-mermaid-sequence-note-bg-color: var(--md-code-bg-color);
|
|
||||||
--md-mermaid-sequence-note-border-color: var(--md-default-fg-color--lightest);
|
|
||||||
--md-mermaid-sequence-number-fg-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* a diagram wider than the column scrolls sideways (js/mermaid-brand.js stops shrinking it at 80 %) */
|
|
||||||
.md-typeset .mermaid {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ---- polish pass ---- */
|
|
||||||
|
|
||||||
/* header: a search field that reads as a field on the band, tabs with an active marker, readable GitHub facts */
|
|
||||||
.md-header .md-search__form {
|
|
||||||
border: 1px solid rgba(255, 255, 255, .22);
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: rgba(255, 255, 255, .12);
|
|
||||||
transition: background-color 150ms, border-color 150ms;
|
|
||||||
}
|
|
||||||
.md-header .md-search__form:hover {
|
|
||||||
border-color: rgba(255, 255, 255, .4);
|
|
||||||
background-color: rgba(255, 255, 255, .2);
|
|
||||||
}
|
|
||||||
.md-search__output {
|
|
||||||
border-radius: 0 0 6px 6px;
|
|
||||||
box-shadow: 0 16px 40px rgba(5, 18, 48, .28);
|
|
||||||
}
|
|
||||||
.md-search-result__link:focus-visible, .md-search-result__more summary:focus-visible {
|
|
||||||
outline: 2px solid var(--nj-blue);
|
|
||||||
outline-offset: -2px;
|
|
||||||
}
|
|
||||||
.md-tabs__link {
|
|
||||||
font-size: .72rem;
|
|
||||||
font-weight: 500;
|
|
||||||
opacity: .85;
|
|
||||||
}
|
|
||||||
.md-tabs__item--active .md-tabs__link, .md-tabs__link:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.md-tabs__item--active {
|
|
||||||
box-shadow: inset 0 -2px 0 #fff;
|
|
||||||
}
|
|
||||||
.md-source__facts {
|
|
||||||
font-size: .62rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* footer: deep brand blue instead of neutral black (the dark scheme keeps its own near-black) */
|
|
||||||
[data-md-color-scheme="default"] {
|
|
||||||
--md-footer-bg-color: #0d2a66;
|
|
||||||
--md-footer-bg-color--dark: #0a2152;
|
|
||||||
}
|
|
||||||
.md-footer-meta .md-copyright, .md-footer-meta .md-copyright a {
|
|
||||||
color: rgba(255, 255, 255, .72);
|
|
||||||
}
|
|
||||||
.md-footer__link {
|
|
||||||
border-radius: 6px;
|
|
||||||
transition: background-color 150ms;
|
|
||||||
}
|
|
||||||
.md-footer__link:is(:hover, :focus-visible) {
|
|
||||||
background-color: rgba(255, 255, 255, .08);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* page title: tighter to the first paragraph; page actions (view/edit source) stay in the background */
|
|
||||||
.md-typeset h1 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.md-content__button {
|
|
||||||
opacity: .55;
|
|
||||||
transition: opacity 150ms;
|
|
||||||
}
|
|
||||||
.md-content__button:is(:hover, :focus-visible) {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
body:has(.nj-hero) .md-content__button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* sidebars: bold marker for the current page, rail and marker for the table of contents */
|
|
||||||
.md-nav__link--active {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.md-nav--secondary > .md-nav__list {
|
|
||||||
margin-left: -.1rem;
|
|
||||||
border-left: 1px solid color-mix(in srgb, var(--md-default-fg-color) 16%, transparent);
|
|
||||||
}
|
|
||||||
.md-nav--secondary .md-nav__link {
|
|
||||||
margin-left: -1px;
|
|
||||||
padding-left: .6rem;
|
|
||||||
border-left: 2px solid transparent;
|
|
||||||
}
|
|
||||||
.md-nav--secondary .md-nav__link--active {
|
|
||||||
border-left-color: var(--nj-blue);
|
|
||||||
color: var(--nj-blue);
|
|
||||||
}
|
|
||||||
.md-nav--secondary .md-nav__list .md-nav__list {
|
|
||||||
padding-left: .6rem;
|
|
||||||
}
|
|
||||||
.md-nav--secondary .md-nav__title {
|
|
||||||
font-size: .64rem;
|
|
||||||
letter-spacing: .04em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* keyboard focus: one visible ring in the brand blue */
|
|
||||||
.md-typeset a:focus-visible, .md-typeset .md-button:focus-visible, .md-typeset summary:focus-visible,
|
|
||||||
.md-nav__link:focus-visible, .md-tabs__link:focus-visible, .md-top:focus-visible {
|
|
||||||
outline: 2px solid var(--nj-blue);
|
|
||||||
outline-offset: 2px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.md-header :is(a, button, label):focus-visible, .nj-hero a:focus-visible {
|
|
||||||
outline: 2px solid #fff;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* inline code must not stretch the line box (JetBrains Mono has tall metrics), abbreviations stay quiet */
|
|
||||||
.md-typeset :not(pre) > code {
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.md-typeset abbr {
|
|
||||||
border-bottom-color: var(--md-default-fg-color--lighter);
|
|
||||||
}
|
|
||||||
.md-typeset :is(h1, h2, h3, h4, a, .grid.cards > ul > li > p:first-child) abbr {
|
|
||||||
border-bottom-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* number railroad diagram: inline SVG (docs/images/json_syntax_number.svg), follows the colour scheme */
|
|
||||||
.md-typeset .railroad-wrap, .md-typeset .nj-fig-wrap {
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
}
|
|
||||||
.md-typeset svg.railroad {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 36rem;
|
|
||||||
max-width: 52rem;
|
|
||||||
margin-inline: auto;
|
|
||||||
color: var(--md-default-fg-color);
|
|
||||||
font-family: var(--md-text-font-family), sans-serif;
|
|
||||||
}
|
|
||||||
.railroad .rr-t { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, transparent); stroke: var(--md-typeset-a-color); stroke-width: 2; }
|
|
||||||
.railroad .rr-track, .railroad .rr-n { stroke: color-mix(in srgb, currentColor 60%, var(--md-default-bg-color)); stroke-opacity: 1; } /* opaque, so overlapping segments do not darken */
|
|
||||||
.railroad .rr-n { fill: color-mix(in srgb, currentColor 6%, transparent); stroke-width: 2; }
|
|
||||||
.railroad .rr-bg { fill: var(--md-default-bg-color); }
|
|
||||||
.railroad .rr-arrow { fill: currentColor; fill-opacity: .6; }
|
|
||||||
.railroad text { fill: currentColor; text-anchor: middle; }
|
|
||||||
.railroad .rr-c { font-family: var(--md-code-font-family), monospace; font-size: 16px; font-weight: 500; }
|
|
||||||
.railroad .rr-b { font-size: 14.5px; font-weight: 500; }
|
|
||||||
.railroad .rr-l { font-size: 14px; text-anchor: start; fill-opacity: .6; }
|
|
||||||
.railroad .rr-name { font-size: 17px; font-weight: 700; fill-opacity: 1; }
|
|
||||||
/* figures drawn as inline SVG (docs/images/callback_events.svg, range-*.svg): they follow the colour scheme */
|
|
||||||
.md-typeset svg.nj-fig {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
margin-inline: auto;
|
|
||||||
color: var(--md-default-fg-color);
|
|
||||||
font-family: var(--md-text-font-family), sans-serif;
|
|
||||||
}
|
|
||||||
.md-typeset svg.nj-fig--range { min-width: 30rem; max-width: 40rem; }
|
|
||||||
.md-typeset svg.nj-fig--cb { max-width: 30rem; }
|
|
||||||
.nj-fig { --fg-line: color-mix(in srgb, currentColor 55%, var(--md-default-bg-color)); }
|
|
||||||
.nj-fig .fg-cell { fill: color-mix(in srgb, var(--md-typeset-a-color) 14%, var(--md-default-bg-color)); stroke: var(--fg-line); stroke-width: 1.5; }
|
|
||||||
.nj-fig .fg-cell--end { fill: none; stroke-dasharray: 4 3; }
|
|
||||||
.nj-fig .fg-pill { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, var(--md-default-bg-color)); stroke: var(--md-typeset-a-color); stroke-width: 1.5; }
|
|
||||||
.nj-fig .fg-pilltext { fill: var(--md-typeset-a-color); font-family: var(--md-code-font-family), monospace; font-size: 14px; font-weight: 500; text-anchor: middle; }
|
|
||||||
.nj-fig .fg-arrow { stroke: var(--fg-line); stroke-width: 1.5; fill: none; }
|
|
||||||
.nj-fig .fg-arrow--dash { stroke-dasharray: 3 3; }
|
|
||||||
.nj-fig .fg-head { fill: var(--fg-line); }
|
|
||||||
.nj-fig .fg-note { fill: currentColor; fill-opacity: .7; font-size: 13.5px; text-anchor: middle; }
|
|
||||||
.nj-fig .fg-note--r { text-anchor: end; }
|
|
||||||
.nj-fig .fg-note--l { text-anchor: start; }
|
|
||||||
.nj-fig .fg-h { fill: currentColor; fill-opacity: .7; font-size: 15px; font-weight: 600; }
|
|
||||||
.nj-fig .fg-code { fill: currentColor; font-family: var(--md-code-font-family), monospace; font-size: 16px; white-space: pre; }
|
|
||||||
.nj-fig .fg-leg { font-size: 15px; }
|
|
||||||
.nj-fig { --cb-os: #4eb05a; --cb-oe: #1a6b34; --cb-k: #e5534b; --cb-as: #f0c000; --cb-ae: #b07d00; --cb-v: #1f6fd1; }
|
|
||||||
[data-md-color-scheme="slate"] .nj-fig { --cb-os: #7ee787; --cb-oe: #2ea043; --cb-k: #ff7b72; --cb-as: #f2cc60; --cb-ae: #c69026; --cb-v: #58a6ff; }
|
|
||||||
.nj-fig .cb-os { fill: var(--cb-os); }
|
|
||||||
.nj-fig .cb-oe { fill: var(--cb-oe); }
|
|
||||||
.nj-fig .cb-k { fill: var(--cb-k); }
|
|
||||||
.nj-fig .cb-as { fill: var(--cb-as); }
|
|
||||||
.nj-fig .cb-ae { fill: var(--cb-ae); }
|
|
||||||
.nj-fig .cb-v { fill: var(--cb-v); }
|
|
||||||
.md-typeset img[src*="json-light"], .md-typeset img[src*="json-dark"] {
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.md-typeset img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* home page: content uses the same column as every other page */
|
|
||||||
body:has(.nj-hero) .md-content {
|
|
||||||
max-width: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.md-typeset .grid.cards {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 404 page (overrides/404.html) */
|
|
||||||
.nj-404 {
|
|
||||||
max-width: 30rem;
|
|
||||||
padding: 2rem 0 3rem;
|
|
||||||
}
|
|
||||||
.nj-404__code {
|
|
||||||
margin: 0 0 .4rem;
|
|
||||||
color: var(--nj-blue);
|
|
||||||
font-family: var(--md-code-font-family);
|
|
||||||
font-size: .8rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.md-typeset .nj-404 h1 {
|
|
||||||
margin-bottom: .8rem;
|
|
||||||
}
|
|
||||||
.nj-404 .md-button {
|
|
||||||
margin: .4rem .4rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* touch targets and phones */
|
|
||||||
@media screen and (max-width: 59.984375em) {
|
|
||||||
.md-nav__link {
|
|
||||||
padding-block: .2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 29.984375em) {
|
|
||||||
.md-typeset h1 {
|
|
||||||
font-size: 1.7em;
|
|
||||||
}
|
|
||||||
.md-typeset .highlight pre > code {
|
|
||||||
font-size: .68rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><g font-family='Menlo,monospace' font-size='26' font-weight='600' fill='#ffffff' fill-opacity='0.10'><text x='14' y='34'>{</text><text x='92' y='22'>]</text><text x='136' y='60'>:</text><text x='52' y='82'>[</text><text x='118' y='112'>,</text><text x='20' y='140'>}</text><text x='78' y='150'>:</text><text x='140' y='158'>{</text></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 408 B |
@@ -124,20 +124,20 @@ For all the macros, the first parameter is the name of the class/struct. The `DE
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A["choosing a NLOHMANN_DEFINE_* macro"] --> B{{"adding fields to a base class?"}}
|
A["choosing a NLOHMANN_DEFINE_* macro"] --> B{"adding fields to a base class?"}
|
||||||
B -->|"yes"| C["...DERIVED_TYPE..."]
|
B -->|"yes"| C["...DERIVED_TYPE..."]
|
||||||
B -->|"no"| D["...TYPE..."]
|
B -->|"no"| D["...TYPE..."]
|
||||||
C --> E{{"need access to private members?"}}
|
C --> E{"need access to private members?"}
|
||||||
D --> E
|
D --> E
|
||||||
E -->|"yes"| F["...INTRUSIVE... (used inside the class)"]
|
E -->|"yes"| F["...INTRUSIVE... (used inside the class)"]
|
||||||
E -->|"no"| G["...NON_INTRUSIVE... (used in the namespace)"]
|
E -->|"no"| G["...NON_INTRUSIVE... (used in the namespace)"]
|
||||||
F --> H{{"only serializing, never parsing back?"}}
|
F --> H{"only serializing, never parsing back?"}
|
||||||
G --> H
|
G --> H
|
||||||
H -->|"yes"| I["...ONLY_SERIALIZE"]
|
H -->|"yes"| I["...ONLY_SERIALIZE"]
|
||||||
H -->|"no"| J{{"allow missing keys when parsing?"}}
|
H -->|"no"| J{"allow missing keys when parsing?"}
|
||||||
J -->|"yes"| K["...WITH_DEFAULT"]
|
J -->|"yes"| K["...WITH_DEFAULT"]
|
||||||
J -->|"no"| L["plain (missing keys throw)"]
|
J -->|"no"| L["plain (missing keys throw)"]
|
||||||
I --> M{{"need custom JSON key names?"}}
|
I --> M{"need custom JSON key names?"}
|
||||||
K --> M
|
K --> M
|
||||||
L --> M
|
L --> M
|
||||||
M -->|"yes"| N["...WITH_NAMES"]
|
M -->|"yes"| N["...WITH_NAMES"]
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ the value, or `end()` if it is not found).
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A["accessing a value"] --> B{{"must it exist?"}}
|
A["accessing a value"] --> B{"must it exist?"}
|
||||||
B -->|"yes, missing is an error"| C["at() -- throws"]
|
B -->|"yes, missing is an error"| C["at() -- throws"]
|
||||||
B -->|"yes, but checking is my job"| D["operator[] -- unchecked"]
|
B -->|"yes, but checking is my job"| D["operator[] -- unchecked"]
|
||||||
B -->|"no, a fallback is fine"| E["value() -- default value"]
|
B -->|"no, a fallback is fine"| E["value() -- default value"]
|
||||||
A --> F{{"just testing first?"}}
|
A --> F{"just testing first?"}
|
||||||
F -->|"yes"| G["contains() / find()"]
|
F -->|"yes"| G["contains() / find()"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ As for other containers, [`begin()`](../api/basic_json/begin.md) returns an iter
|
|||||||
placeholder and cannot be dereferenced. In case of null values, empty arrays, or empty objects, `begin()` will return
|
placeholder and cannot be dereferenced. In case of null values, empty arrays, or empty objects, `begin()` will return
|
||||||
`end()`.
|
`end()`.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-begin-end.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Iteration order for objects
|
### Iteration order for objects
|
||||||
|
|
||||||
@@ -114,9 +112,7 @@ for (auto& [key, val] : j_object.items())
|
|||||||
[`rbegin()`](../api/basic_json/rbegin.md) and [`rend()`](../api/basic_json/rend.md) return iterators in the reverse
|
[`rbegin()`](../api/basic_json/rbegin.md) and [`rend()`](../api/basic_json/rend.md) return iterators in the reverse
|
||||||
sequence.
|
sequence.
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|

|
||||||
--8<-- "images/range-rbegin-rend.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
??? example "Example: reverse iteration with `rbegin()` and `rend()`"
|
??? example "Example: reverse iteration with `rbegin()` and `rend()`"
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ JSON defines the syntax of numbers as follows:
|
|||||||
|
|
||||||
The following railroad diagram from [json.org](https://json.org) visualizes the number syntax:
|
The following railroad diagram from [json.org](https://json.org) visualizes the number syntax:
|
||||||
|
|
||||||
<div class="railroad-wrap" markdown>
|

|
||||||
--8<-- "images/json_syntax_number.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Number interoperability
|
### Number interoperability
|
||||||
|
|
||||||
@@ -71,9 +69,9 @@ otherwise, it uses unsigned integer storage.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A["number literal"] --> B{{"has a fraction (.) or exponent (e/E)?"}}
|
A["number literal"] --> B{"has a fraction (.) or exponent (e/E)?"}
|
||||||
B -->|"yes"| F["number_float_t"]
|
B -->|"yes"| F["number_float_t"]
|
||||||
B -->|"no"| C{{"has a leading minus sign?"}}
|
B -->|"no"| C{"has a leading minus sign?"}
|
||||||
C -->|"yes"| D["try number_integer_t"]
|
C -->|"yes"| D["try number_integer_t"]
|
||||||
C -->|"no"| E["try number_unsigned_t"]
|
C -->|"no"| E["try number_unsigned_t"]
|
||||||
D -->|"overflow"| F
|
D -->|"overflow"| F
|
||||||
|
|||||||
|
After Width: | Height: | Size: 45 KiB |
@@ -1,39 +0,0 @@
|
|||||||
<svg class="nj-fig nj-fig--cb" viewBox="0 0 480 262" role="img" aria-labelledby="fig-cb-title" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<title id="fig-cb-title">Example of the parse events a callback sees for the J​SON text {"name": "Berlin", "location": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete.</title>
|
|
||||||
<text class="fg-h" x="16" y="28">J​SON to parse</text>
|
|
||||||
<text class="fg-h" x="320" y="28">callback events</text>
|
|
||||||
<text class="fg-code" x="16" y="74" xml:space="preserve">{</text>
|
|
||||||
<circle class="cb-os" cx="35.6" cy="69" r="5.5"/>
|
|
||||||
<text class="fg-code" x="35.2" y="102" xml:space="preserve">"name"</text>
|
|
||||||
<circle class="cb-k" cx="102.8" cy="97" r="5.5"/>
|
|
||||||
<text class="fg-code" x="112.8" y="102" xml:space="preserve">: "Berlin"</text>
|
|
||||||
<circle class="cb-v" cx="218.8" cy="97" r="5.5"/>
|
|
||||||
<text class="fg-code" x="228.8" y="102" xml:space="preserve">,</text>
|
|
||||||
<text class="fg-code" x="35.2" y="130" xml:space="preserve">"location"</text>
|
|
||||||
<circle class="cb-k" cx="141.2" cy="125" r="5.5"/>
|
|
||||||
<text class="fg-code" x="151.2" y="130" xml:space="preserve">: [</text>
|
|
||||||
<circle class="cb-as" cx="190" cy="125" r="5.5"/>
|
|
||||||
<text class="fg-code" x="54.4" y="158" xml:space="preserve">52.519444</text>
|
|
||||||
<circle class="cb-v" cx="150.8" cy="153" r="5.5"/>
|
|
||||||
<text class="fg-code" x="160.8" y="158" xml:space="preserve">,</text>
|
|
||||||
<text class="fg-code" x="54.4" y="186" xml:space="preserve">13.406667</text>
|
|
||||||
<circle class="cb-v" cx="150.8" cy="181" r="5.5"/>
|
|
||||||
<text class="fg-code" x="35.2" y="214" xml:space="preserve">]</text>
|
|
||||||
<circle class="cb-ae" cx="54.8" cy="209" r="5.5"/>
|
|
||||||
<circle class="cb-v" cx="74.8" cy="209" r="5.5"/>
|
|
||||||
<text class="fg-code" x="16" y="242" xml:space="preserve">}</text>
|
|
||||||
<circle class="cb-oe" cx="35.6" cy="237" r="5.5"/>
|
|
||||||
<circle class="cb-v" cx="55.6" cy="237" r="5.5"/>
|
|
||||||
<circle class="cb-os" cx="327" cy="69" r="6"/>
|
|
||||||
<text class="fg-code fg-leg" x="344" y="74">object_start</text>
|
|
||||||
<circle class="cb-k" cx="327" cy="99" r="6"/>
|
|
||||||
<text class="fg-code fg-leg" x="344" y="104">key</text>
|
|
||||||
<circle class="cb-oe" cx="327" cy="129" r="6"/>
|
|
||||||
<text class="fg-code fg-leg" x="344" y="134">object_end</text>
|
|
||||||
<circle class="cb-as" cx="327" cy="159" r="6"/>
|
|
||||||
<text class="fg-code fg-leg" x="344" y="164">array_start</text>
|
|
||||||
<circle class="cb-ae" cx="327" cy="189" r="6"/>
|
|
||||||
<text class="fg-code fg-leg" x="344" y="194">array_end</text>
|
|
||||||
<circle class="cb-v" cx="327" cy="219" r="6"/>
|
|
||||||
<text class="fg-code fg-leg" x="344" y="224">value</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1200 675" width="1200" height="675" role="img"><title>JSON for Modern C++: feature slideshow (animated, 20 slides, 15 s each)</title><style>.bg{fill:#fff} .tx{fill:#0f1419} .tx2{fill:#3d444f} .tx3{fill:#8a919c} .com{fill:#646d7a}.bl{fill:#1747a6} .blk{fill:#1747a6} .kw{fill:#1747a6} .ty{fill:#0d6e72} .fn{fill:#6b3fb8} .str{fill:#23793a} .num{fill:#b04a07} .pp{fill:#a3247a}.panel{fill:#f5f7fa;stroke:#e3e6eb;stroke-width:1} .rule{stroke:#e3e6eb;stroke-width:1}.wm-a{fill:#0f1419} .wm-s{fill:#1747a6}.on-a,.on-tx{fill:#fff} .red{fill:#d93a3f} .on-panel{fill:#fff;fill-opacity:.08;stroke:#fff;stroke-opacity:.16} .on-s{fill:#bcd3fb} .w70{fill:#fff;fill-opacity:.7} .on-tx2{fill:#dbe6fb} .chip{fill:#fff;fill-opacity:.14}.pat{fill:#fff;fill-opacity:.09}.h1{stop-color:#1d55c4} .h2{stop-color:#123a8a}.seg{fill:#e3e6eb} .seg-on{fill:#1747a6}.seg.on-band{fill:#fff;fill-opacity:.25} .seg-on.on-band{fill:#fff;fill-opacity:1}.bg{fill:#0f1218} .tx,.wm-a{fill:#f2f4f7} .tx2{fill:#c3c8d1} .tx3{fill:#7b828e} .com{fill:#8b929e}.bl,.blk,.wm-s,.seg-on,.kw{fill:#7aa7f5} .ty{fill:#4fd1c5} .fn{fill:#c4a3ff} .str{fill:#7ed491} .num{fill:#f5a65b} .pp{fill:#f48fc8}.panel{fill:#171b23;stroke:#272c36} .rule{stroke:#272c36} .seg{fill:#272c36}.red{fill:#c8383d} .h1{stop-color:#163f93} .h2{stop-color:#0c2556}.s{opacity:0;animation:show 300s linear infinite backwards}.fill{transform-box:fill-box;transform-origin:left;animation:fill 300s linear infinite backwards;animation-delay:inherit}.s0{animation-name:show0} .sl{animation-name:showl}@keyframes show{0%{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)} 0.2000%{opacity:1} 5.2000%{opacity:1} 5.2100%{opacity:0} 100%{opacity:0}}@keyframes show0{0%{opacity:1} 5.2000%{opacity:1} 5.2100%{opacity:0} 100%{opacity:0}}@keyframes showl{0%{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)} 0.2000%{opacity:1} 5.0000%{opacity:1} 5.2000%{opacity:0} 100%{opacity:0}}@keyframes fill{0%{transform:scaleX(0)} 5.0000%{transform:scaleX(1)} 100%{transform:scaleX(1)}}@media (prefers-reduced-motion:reduce){.s,.fill{animation:none} .s:first-of-type{opacity:1}}</style><defs><path id="g0" d="M233 0 46 730H148L269 238Q281 191 289 148Q298 105 302 81Q306 105 315 148Q324 191 335 239L452 730H554L366 0H233Z"/><path id="g1" d="M97 0V730H522V641H195V424H487V337H195V89H522V0H97Z"/><path id="g2" d="M89 0V730H315Q382 730 432 703Q483 677 510 629Q538 582 538 518Q538 445 501 391Q463 338 401 316L548 0H439L300 305H187V0H89ZM187 393H315Q370 393 403 427Q436 461 436 517Q436 574 403 608Q370 642 315 642H187V393Z"/><path id="g3" d="M304 -10Q233 -10 179 15Q126 41 97 88Q68 136 68 202H167Q167 144 204 111Q242 78 304 78Q364 78 399 112Q435 146 435 201Q435 243 412 274Q390 306 347 317L239 346Q168 366 126 419Q85 471 85 543Q85 602 112 646Q138 691 186 715Q234 740 297 740Q361 740 409 715Q457 691 485 647Q512 603 512 545H414Q414 593 381 623Q348 653 297 653Q245 653 214 623Q182 593 182 545Q182 507 203 480Q224 453 262 442L373 412Q448 392 490 336Q532 279 532 201Q532 137 504 89Q476 42 424 16Q373 -10 304 -10Z"/><path id="g4" d="M101 0V90H250V640H101V730H499V640H350V90H499V0H101Z"/><path id="g5" d="M300 -10Q200 -10 142 48Q84 106 84 212V518Q84 625 142 682Q200 740 300 740Q400 740 458 682Q516 625 516 519V212Q516 106 458 48Q400 -10 300 -10ZM300 78Q357 78 387 110Q417 142 417 202V528Q417 588 387 620Q357 652 300 652Q244 652 213 620Q183 588 183 528V202Q183 142 213 110Q244 78 300 78Z"/><path id="g6" d="M85 0V730H214L432 114Q429 141 427 179Q424 217 422 259Q421 301 421 337V730H515V0H386L170 616Q172 591 174 553Q176 516 178 474Q179 433 179 396V0H85Z"/><path id="g8" d="M290 -10Q187 -10 127 46Q67 103 67 200H166Q166 143 200 110Q233 78 291 78Q348 78 382 110Q415 143 415 200V251Q415 308 382 341Q349 375 291 375H209V458L385 641H89V730H496V640L291 428L293 462Q395 462 455 405Q515 349 515 251V200Q515 103 454 46Q394 -10 290 -10Z"/><path id="g9" d="M300 -10Q262 -10 238 13Q215 36 215 72Q215 111 238 134Q262 158 300 158Q338 158 362 134Q385 111 385 72Q385 36 362 13Q338 -10 300 -10Z"/><path id="ga" d="M88 0V90H284V641L88 495V607L254 730H383V90H543V0H88Z"/><path id="gb" d="M300 -10Q232 -10 182 17Q132 43 104 92Q77 141 77 206V524Q77 590 104 638Q132 687 182 713Q232 740 300 740Q369 740 419 713Q468 687 496 638Q523 590 523 524V206Q523 141 496 92Q468 43 418 17Q368 -10 300 -10ZM300 75Q358 75 393 111Q428 148 428 206V524Q428 582 393 619Q358 655 300 655Q242 655 207 619Q172 582 172 524V206Q172 148 207 111Q242 75 300 75ZM300 309Q272 309 255 324Q238 339 238 368Q238 396 255 411Q272 425 300 425Q328 425 345 411Q362 396 362 368Q362 339 345 324Q328 309 300 309Z"/><path id="gc" d="M180 688V0H134V1056H180V816H168Q204 944 310 1008Q417 1072 544 1072Q659 1072 746 1024Q834 975 884 889Q934 803 934 688V0H888V688Q888 840 792 933Q696 1026 544 1026Q440 1026 358 982Q275 939 228 862Q180 786 180 688Z"/><path id="gd" d="M180 1490V0H134V1490H180Z"/><path id="ge" d="M530 -16Q394 -16 289 54Q184 125 125 248Q66 371 6Line truncated
|
|
||||||
|
Before Width: | Height: | Size: 395 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1200 675" width="1200" height="675" role="img"><title>JSON for Modern C++: feature slideshow (animated, 20 slides, 15 s each)</title><style>.bg{fill:#fff} .tx{fill:#0f1419} .tx2{fill:#3d444f} .tx3{fill:#8a919c} .com{fill:#646d7a}.bl{fill:#1747a6} .blk{fill:#1747a6} .kw{fill:#1747a6} .ty{fill:#0d6e72} .fn{fill:#6b3fb8} .str{fill:#23793a} .num{fill:#b04a07} .pp{fill:#a3247a}.panel{fill:#f5f7fa;stroke:#e3e6eb;stroke-width:1} .rule{stroke:#e3e6eb;stroke-width:1}.wm-a{fill:#0f1419} .wm-s{fill:#1747a6}.on-a,.on-tx{fill:#fff} .red{fill:#d93a3f} .on-panel{fill:#fff;fill-opacity:.08;stroke:#fff;stroke-opacity:.16} .on-s{fill:#bcd3fb} .w70{fill:#fff;fill-opacity:.7} .on-tx2{fill:#dbe6fb} .chip{fill:#fff;fill-opacity:.14}.pat{fill:#fff;fill-opacity:.09}.h1{stop-color:#1d55c4} .h2{stop-color:#123a8a}.seg{fill:#e3e6eb} .seg-on{fill:#1747a6}.seg.on-band{fill:#fff;fill-opacity:.25} .seg-on.on-band{fill:#fff;fill-opacity:1}.s{opacity:0;animation:show 300s linear infinite backwards}.fill{transform-box:fill-box;transform-origin:left;animation:fill 300s linear infinite backwards;animation-delay:inherit}.s0{animation-name:show0} .sl{animation-name:showl}@keyframes show{0%{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)} 0.2000%{opacity:1} 5.2000%{opacity:1} 5.2100%{opacity:0} 100%{opacity:0}}@keyframes show0{0%{opacity:1} 5.2000%{opacity:1} 5.2100%{opacity:0} 100%{opacity:0}}@keyframes showl{0%{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)} 0.2000%{opacity:1} 5.0000%{opacity:1} 5.2000%{opacity:0} 100%{opacity:0}}@keyframes fill{0%{transform:scaleX(0)} 5.0000%{transform:scaleX(1)} 100%{transform:scaleX(1)}}@media (prefers-reduced-motion:reduce){.s,.fill{animation:none} .s:first-of-type{opacity:1}}</style><defs><path id="g0" d="M233 0 46 730H148L269 238Q281 191 289 148Q298 105 302 81Q306 105 315 148Q324 191 335 239L452 730H554L366 0H233Z"/><path id="g1" d="M97 0V730H522V641H195V424H487V337H195V89H522V0H97Z"/><path id="g2" d="M89 0V730H315Q382 730 432 703Q483 677 510 629Q538 582 538 518Q538 445 501 391Q463 338 401 316L548 0H439L300 305H187V0H89ZM187 393H315Q370 393 403 427Q436 461 436 517Q436 574 403 608Q370 642 315 642H187V393Z"/><path id="g3" d="M304 -10Q233 -10 179 15Q126 41 97 88Q68 136 68 202H167Q167 144 204 111Q242 78 304 78Q364 78 399 112Q435 146 435 201Q435 243 412 274Q390 306 347 317L239 346Q168 366 126 419Q85 471 85 543Q85 602 112 646Q138 691 186 715Q234 740 297 740Q361 740 409 715Q457 691 485 647Q512 603 512 545H414Q414 593 381 623Q348 653 297 653Q245 653 214 623Q182 593 182 545Q182 507 203 480Q224 453 262 442L373 412Q448 392 490 336Q532 279 532 201Q532 137 504 89Q476 42 424 16Q373 -10 304 -10Z"/><path id="g4" d="M101 0V90H250V640H101V730H499V640H350V90H499V0H101Z"/><path id="g5" d="M300 -10Q200 -10 142 48Q84 106 84 212V518Q84 625 142 682Q200 740 300 740Q400 740 458 682Q516 625 516 519V212Q516 106 458 48Q400 -10 300 -10ZM300 78Q357 78 387 110Q417 142 417 202V528Q417 588 387 620Q357 652 300 652Q244 652 213 620Q183 588 183 528V202Q183 142 213 110Q244 78 300 78Z"/><path id="g6" d="M85 0V730H214L432 114Q429 141 427 179Q424 217 422 259Q421 301 421 337V730H515V0H386L170 616Q172 591 174 553Q176 516 178 474Q179 433 179 396V0H85Z"/><path id="g8" d="M290 -10Q187 -10 127 46Q67 103 67 200H166Q166 143 200 110Q233 78 291 78Q348 78 382 110Q415 143 415 200V251Q415 308 382 341Q349 375 291 375H209V458L385 641H89V730H496V640L291 428L293 462Q395 462 455 405Q515 349 515 251V200Q515 103 454 46Q394 -10 290 -10Z"/><path id="g9" d="M300 -10Q262 -10 238 13Q215 36 215 72Q215 111 238 134Q262 158 300 158Q338 158 362 134Q385 111 385 72Q385 36 362 13Q338 -10 300 -10Z"/><path id="ga" d="M88 0V90H284V641L88 495V607L254 730H383V90H543V0H88Z"/><path id="gb" d="M300 -10Q232 -10 182 17Q132 43 104 92Q77 141 77 206V524Q77 590 104 638Q132 687 182 713Q232 740 300 740Q369 740 419 713Q468 687 496 638Q523 590 523 524V206Q523 141 496 92Q468 43 418 17Q368 -10 300 -10ZM300 75Q358 75 393 111Q428 148 428 206V524Q428 582 393 619Q358 655 300 655Q242 655 207 619Q172 582 172 524V206Q172 148 207 111Q242 75 300 75ZM300 309Q272 309 255 324Q238 339 238 368Q238 396 255 411Q272 425 300 425Q328 425 345 411Q362 396 362 368Q362 339 345 324Q328 309 300 309Z"/><path id="gc" d="M180 688V0H134V1056H180V816H168Q204 944 310 1008Q417 1072 544 1072Q659 1072 746 1024Q834 975 884 889Q934 803 934 688V0H888V688Q888 840 792 933Q696 1026 544 1026Q440 1026 358 982Q275 939 228 862Q180 786 180 688Z"/><path id="gd" d="M180 1490V0H134V1490H180Z"/><path id="ge" d="M530 -16Q394 -16 289 54Q184 125 125 248Q66 371 66 528Q66 686 125 808Q184 931 289 1002Q394 1072 530 1072Q666 1072 770 1002Q874 931 933 808Q992 686 992 528Q992 371 933 248Q874 125 770 54Q666 -16 530 -16ZM530 30Q652 30 746 94Q840 157 893 270Q946 382 946 528Q946 674 893 786Q840 899 746 962Q652 1026 530 1026Q408 1026 314 962Q219 899 166 786Q112 674 112 528Q112 382 166 270Q219 157 314 94Q408 30 530 30Z"/><path id="gf" d="MLine truncated
|
|
||||||
|
Before Width: | Height: | Size: 394 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -1,66 +0,0 @@
|
|||||||
<svg class="railroad" viewBox="0 0 860 214" role="img" aria-labelledby="rr-title" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<title id="rr-title">Railroad diagram of the J​SON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits).</title>
|
|
||||||
<g class="rr-track" fill="none" stroke="currentColor" stroke-opacity=".6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M8 44 L850 44"/>
|
|
||||||
<path d="M24 44 L31 44 Q38 44 38 51 L38 82 Q38 92 48 92 L59 92"/>
|
|
||||||
<path d="M85 92 L96 92 Q106 92 106 82 L106 51 Q106 44 113 44 L120 44"/>
|
|
||||||
<path d="M120 44 L127 44 Q134 44 134 51 L134 82 Q134 92 144 92 L160 92"/>
|
|
||||||
<path d="M242 92 L370 92 Q380 92 380 82 L380 52 Q380 44 388 44 L396 44"/>
|
|
||||||
<path d="M242 92 L250 92 Q258 92 258 100 L258 126 Q258 136 268 136 L278 136"/>
|
|
||||||
<path d="M332 136 L340 136 Q348 136 348 128 L348 92"/>
|
|
||||||
<path d="M340 136 L340 160 Q340 170 330 170 L280 170 Q270 170 270 160 L270 136"/>
|
|
||||||
<path d="M396 44 L403 44 Q410 44 410 51 L410 84.5 Q410 92 417.5 92 L425 92"/>
|
|
||||||
<path d="M451 92 L462 92"/>
|
|
||||||
<path d="M524 92 L524 116 Q524 126 514 126 L466 126 Q456 126 456 116 L456 92"/>
|
|
||||||
<path d="M516 92 L523 92 Q530 92 530 85 L530 52 Q530 44 538 44 L546 44"/>
|
|
||||||
<path d="M546 44 L553 44 Q560 44 560 51 L560 83.5 Q560 92 568.5 92 L577 92"/>
|
|
||||||
<path d="M546 44 L553 44 Q560 44 560 51 L560 127.5 Q560 136 568.5 136 L577 136"/>
|
|
||||||
<path d="M603 92 L728 92"/>
|
|
||||||
<path d="M603 136 L611.5 136 Q620 136 620 127.5 L620 92"/>
|
|
||||||
<path d="M640 92 L647 92 Q654 92 654 99 L654 128 Q654 136 662 136 L670 136"/>
|
|
||||||
<path d="M640 92 L647 92 Q654 92 654 99 L654 172 Q654 180 662 180 L670 180"/>
|
|
||||||
<path d="M696 136 L704 136 Q712 136 712 128 L712 92"/>
|
|
||||||
<path d="M696 180 L704 180 Q712 180 712 172 L712 92"/>
|
|
||||||
<path d="M728 92 L746 92"/>
|
|
||||||
<path d="M808 92 L808 116 Q808 126 798 126 L748 126 Q738 126 738 116 L738 92"/>
|
|
||||||
<path d="M800 92 L808 92 Q816 92 816 84 L816 52 Q816 44 824 44 L832 44"/>
|
|
||||||
</g>
|
|
||||||
<polygon class="rr-arrow" points="2,38 12,44 2,50"/>
|
|
||||||
<polygon class="rr-arrow" points="846,38 858,44 846,50"/>
|
|
||||||
<circle class="rr-bg" cx="72" cy="92" r="13"/>
|
|
||||||
<circle class="rr-t" cx="72" cy="92" r="13"/>
|
|
||||||
<circle class="rr-bg" cx="257" cy="44" r="13"/>
|
|
||||||
<circle class="rr-t" cx="257" cy="44" r="13"/>
|
|
||||||
<rect class="rr-bg" x="160" y="78" width="82" height="28" rx="6"/>
|
|
||||||
<rect class="rr-n" x="160" y="78" width="82" height="28" rx="6"/>
|
|
||||||
<rect class="rr-bg" x="278" y="122" width="54" height="28" rx="6"/>
|
|
||||||
<rect class="rr-n" x="278" y="122" width="54" height="28" rx="6"/>
|
|
||||||
<circle class="rr-bg" cx="438" cy="92" r="13"/>
|
|
||||||
<circle class="rr-t" cx="438" cy="92" r="13"/>
|
|
||||||
<rect class="rr-bg" x="462" y="78" width="54" height="28" rx="6"/>
|
|
||||||
<rect class="rr-n" x="462" y="78" width="54" height="28" rx="6"/>
|
|
||||||
<circle class="rr-bg" cx="590" cy="92" r="13"/>
|
|
||||||
<circle class="rr-t" cx="590" cy="92" r="13"/>
|
|
||||||
<circle class="rr-bg" cx="590" cy="136" r="13"/>
|
|
||||||
<circle class="rr-t" cx="590" cy="136" r="13"/>
|
|
||||||
<circle class="rr-bg" cx="683" cy="136" r="13"/>
|
|
||||||
<circle class="rr-t" cx="683" cy="136" r="13"/>
|
|
||||||
<circle class="rr-bg" cx="683" cy="180" r="13"/>
|
|
||||||
<circle class="rr-t" cx="683" cy="180" r="13"/>
|
|
||||||
<rect class="rr-bg" x="746" y="78" width="54" height="28" rx="6"/>
|
|
||||||
<rect class="rr-n" x="746" y="78" width="54" height="28" rx="6"/>
|
|
||||||
<text class="rr-c" x="72" y="97">−</text>
|
|
||||||
<text class="rr-c" x="257" y="49">0</text>
|
|
||||||
<text class="rr-b" x="201" y="97">digit 1–9</text>
|
|
||||||
<text class="rr-b" x="305" y="141">digit</text>
|
|
||||||
<text class="rr-c" x="438" y="97">.</text>
|
|
||||||
<text class="rr-b" x="489" y="97">digit</text>
|
|
||||||
<text class="rr-c" x="590" y="97">e</text>
|
|
||||||
<text class="rr-c" x="590" y="141">E</text>
|
|
||||||
<text class="rr-c" x="683" y="141">−</text>
|
|
||||||
<text class="rr-c" x="683" y="185">+</text>
|
|
||||||
<text class="rr-b" x="773" y="97">digit</text>
|
|
||||||
<text class="rr-l rr-name" x="24" y="32">number</text>
|
|
||||||
<text class="rr-l" x="420" y="32">fraction</text>
|
|
||||||
<text class="rr-l" x="580" y="32">exponent</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,28 +1,447 @@
|
|||||||
<svg class="nj-fig nj-fig--range" viewBox="0 0 600 150" role="img" aria-labelledby="fig-be-title" xmlns="http://www.w3.org/2000/svg">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<title id="fig-be-title">A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position.</title>
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
<defs><marker id="fig-be-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>
|
|
||||||
<rect class="fg-cell" x="30" y="62" width="30" height="30" rx="5"/>
|
<svg
|
||||||
<rect class="fg-cell" x="64" y="62" width="30" height="30" rx="5"/>
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
<rect class="fg-cell" x="98" y="62" width="30" height="30" rx="5"/>
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
<rect class="fg-cell" x="132" y="62" width="30" height="30" rx="5"/>
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
<rect class="fg-cell" x="166" y="62" width="30" height="30" rx="5"/>
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
<rect class="fg-cell" x="200" y="62" width="30" height="30" rx="5"/>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<rect class="fg-cell" x="234" y="62" width="30" height="30" rx="5"/>
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
<rect class="fg-cell" x="268" y="62" width="30" height="30" rx="5"/>
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
<rect class="fg-cell" x="302" y="62" width="30" height="30" rx="5"/>
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
<rect class="fg-cell" x="336" y="62" width="30" height="30" rx="5"/>
|
width="545.89282"
|
||||||
<rect class="fg-cell" x="370" y="62" width="30" height="30" rx="5"/>
|
height="156.9409"
|
||||||
<rect class="fg-cell" x="404" y="62" width="30" height="30" rx="5"/>
|
id="svg2"
|
||||||
<rect class="fg-cell" x="438" y="62" width="30" height="30" rx="5"/>
|
version="1.1"
|
||||||
<rect class="fg-cell" x="472" y="62" width="30" height="30" rx="5"/>
|
inkscape:version="0.48.4 r9939"
|
||||||
<rect class="fg-cell" x="506" y="62" width="30" height="30" rx="5"/>
|
sodipodi:docname="range-begin-end.svg">
|
||||||
<rect class="fg-cell fg-cell--end" x="540" y="62" width="30" height="30" rx="5"/>
|
<style type="text/css">
|
||||||
<rect class="fg-pill" x="15" y="8" width="60" height="24" rx="6"/>
|
@media (prefers-color-scheme: dark) {
|
||||||
<text class="fg-pilltext" x="45" y="24">begin</text>
|
[style*="stroke:#000000"] { stroke: #e6e6e6 !important; }
|
||||||
<line class="fg-arrow" x1="45" y1="32" x2="45" y2="60" marker-end="url(#fig-be-arrow)"/>
|
[style*="fill:#000000"] { fill: #e6e6e6 !important; }
|
||||||
<rect class="fg-pill" x="531" y="8" width="48" height="24" rx="6"/>
|
[style*="fill:#ffffff"] { fill: #1e1e1e !important; }
|
||||||
<text class="fg-pilltext" x="555" y="24">end</text>
|
[style*="fill:#0000ff"] { fill: #7aa2ff !important; }
|
||||||
<line class="fg-arrow" x1="555" y1="32" x2="555" y2="60" marker-end="url(#fig-be-arrow)"/>
|
marker path { fill: #e6e6e6 !important; stroke: #e6e6e6 !important; }
|
||||||
<text class="fg-note fg-note--r" x="537" y="132">Past-the-last element</text>
|
stop[style*="#c1b1b1"] { stop-color: #5a5a5a !important; }
|
||||||
<line class="fg-arrow" x1="555" y1="124" x2="555" y2="96" marker-end="url(#fig-be-arrow)"/>
|
stop[style*="#b6b6b6"] { stop-color: #4a4a4a !important; }
|
||||||
|
stop[style*="#e3e3e3"] { stop-color: #2a2a2a !important; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<marker
|
||||||
|
inkscape:stockid="Arrow1Mend"
|
||||||
|
orient="auto"
|
||||||
|
refY="0"
|
||||||
|
refX="0"
|
||||||
|
id="Arrow1Mend"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
id="path6525"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
|
||||||
|
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
|
||||||
|
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
inkscape:stockid="Arrow1Lend"
|
||||||
|
orient="auto"
|
||||||
|
refY="0"
|
||||||
|
refX="0"
|
||||||
|
id="Arrow1Lend"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
id="path6519"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
|
||||||
|
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
|
||||||
|
transform="matrix(-0.8,0,0,-0.8,-10,0)"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</marker>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5488">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#c1b1b1;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5490" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#e3e3e3;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop5492" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient4040">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#b6b6b6;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop4042" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#e3e3e3;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop4044" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient5488"
|
||||||
|
id="linearGradient6284"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="81"
|
||||||
|
y1="1001.3622"
|
||||||
|
x2="81"
|
||||||
|
y2="971.36218" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient5488-2"
|
||||||
|
id="linearGradient6284-3"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="81"
|
||||||
|
y1="1001.3622"
|
||||||
|
x2="81"
|
||||||
|
y2="971.36218" />
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5488-2">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#c1b1b1;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5490-5" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#e3e3e3;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop5492-8" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient5488-0"
|
||||||
|
id="linearGradient6284-5"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="81"
|
||||||
|
y1="1001.3622"
|
||||||
|
x2="81"
|
||||||
|
y2="971.36218" />
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5488-0">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#c1b1b1;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5490-1" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#e3e3e3;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop5492-3" />
|
||||||
|
</linearGradient>
|
||||||
|
<marker
|
||||||
|
inkscape:stockid="Arrow1Mend"
|
||||||
|
orient="auto"
|
||||||
|
refY="0"
|
||||||
|
refX="0"
|
||||||
|
id="Arrow1Mend-8"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path6525-3"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
|
||||||
|
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
|
||||||
|
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
|
||||||
|
</marker>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient5488"
|
||||||
|
id="linearGradient9026"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="81"
|
||||||
|
y1="1001.3622"
|
||||||
|
x2="81"
|
||||||
|
y2="971.36218" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1.4"
|
||||||
|
inkscape:cx="332.80082"
|
||||||
|
inkscape:cy="165.04542"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1680"
|
||||||
|
inkscape:window-height="993"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="34"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:snap-bbox="true"
|
||||||
|
inkscape:object-nodes="true"
|
||||||
|
inkscape:snap-smooth-nodes="false"
|
||||||
|
inkscape:bbox-nodes="false"
|
||||||
|
inkscape:snap-bbox-edge-midpoints="false"
|
||||||
|
inkscape:object-paths="true"
|
||||||
|
fit-margin-top="10"
|
||||||
|
fit-margin-right="10"
|
||||||
|
fit-margin-bottom="10"
|
||||||
|
fit-margin-left="10" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-26.107143,-893.4807)">
|
||||||
|
<g
|
||||||
|
id="g7184">
|
||||||
|
<path
|
||||||
|
inkscape:tile-y0="219"
|
||||||
|
inkscape:tile-x0="51"
|
||||||
|
inkscape:tile-h="30.00002"
|
||||||
|
inkscape:tile-w="29.999997"
|
||||||
|
inkscape:tile-cy="234.00001"
|
||||||
|
inkscape:tile-cx="65.999998"
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3006"
|
||||||
|
d="m 80.999997,1001.3622 -29.999997,0 0,-30.00002 29.999997,0 z"
|
||||||
|
style="fill:url(#linearGradient9026);fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||||
|
<use
|
||||||
|
height="300"
|
||||||
|
width="600"
|
||||||
|
style="fill:none"
|
||||||
|
id="use4010"
|
||||||
|
transform="translate(29.999997,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0" />
|
||||||
|
<use
|
||||||
|
id="use4012"
|
||||||
|
transform="translate(59.999994,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4014"
|
||||||
|
transform="translate(89.999991,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4016"
|
||||||
|
transform="translate(119.99999,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4018"
|
||||||
|
transform="translate(149.99998,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4020"
|
||||||
|
transform="translate(179.99998,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4022"
|
||||||
|
transform="translate(209.99998,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4024"
|
||||||
|
transform="translate(239.99998,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4026"
|
||||||
|
transform="translate(269.99997,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4028"
|
||||||
|
transform="translate(299.99997,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4030"
|
||||||
|
transform="translate(329.99997,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4032"
|
||||||
|
transform="translate(359.99996,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4034"
|
||||||
|
transform="translate(389.99996,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4036"
|
||||||
|
transform="translate(419.99996,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<use
|
||||||
|
id="use4038"
|
||||||
|
transform="translate(449.99995,0)"
|
||||||
|
xlink:href="#path3006"
|
||||||
|
inkscape:tiled-clone-of="#path3006"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
width="600"
|
||||||
|
height="300" />
|
||||||
|
<path
|
||||||
|
inkscape:tile-y0="219"
|
||||||
|
inkscape:tile-x0="51"
|
||||||
|
inkscape:tile-h="30.00002"
|
||||||
|
inkscape:tile-w="29.999997"
|
||||||
|
inkscape:tile-cy="234.00001"
|
||||||
|
inkscape:tile-cx="65.999998"
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3006-3"
|
||||||
|
d="m 560.99995,1001.3622 -30,0 0,-30.00002 30,0 z"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:2, 2;stroke-dashoffset:0;marker-start:none" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:55.00000119%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||||
|
x="392.94931"
|
||||||
|
y="1040.2404"
|
||||||
|
id="text6504"
|
||||||
|
sodipodi:linespacing="55.000001%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan6506"
|
||||||
|
x="392.94931"
|
||||||
|
y="1040.2404"
|
||||||
|
rotate="0 0 0 0 0 0 0 0 0.50000191 0 0 0 0 0 0 0 0 0 0 0 0 0"
|
||||||
|
dy="0"
|
||||||
|
dx="0"
|
||||||
|
style="font-size:12.80000019px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">Past-the-last element</tspan></text>
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)"
|
||||||
|
d="m 531.84531,279.29187 c 14.75083,-11.1311 12.86656,-14.42379 15.15229,-29.29442"
|
||||||
|
id="path6510"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
transform="translate(0,752.36218)"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<g
|
||||||
|
id="g7203"
|
||||||
|
transform="translate(-40,-18.482143)">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="m 128.39286,947.36218 -51.785717,0 0,-21.51786 51.785717,0 z"
|
||||||
|
id="use6288-9"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
inkscape:tile-x0="51"
|
||||||
|
inkscape:tile-y0="219" />
|
||||||
|
<text
|
||||||
|
transform="translate(0,752.36218)"
|
||||||
|
sodipodi:linespacing="55.000001%"
|
||||||
|
id="text7176"
|
||||||
|
y="189.10715"
|
||||||
|
x="80.535713"
|
||||||
|
style="font-size:15px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:55.00000119%;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
y="189.10715"
|
||||||
|
x="80.535713"
|
||||||
|
id="tspan7178"
|
||||||
|
sodipodi:role="line">begin</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g7238"
|
||||||
|
transform="translate(20.035895,11.306019)">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="m 538.39286,914.19254 -37.50001,0 0,-21.51786 37.50001,0 z"
|
||||||
|
id="use6288-9-7"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
inkscape:tile-x0="51"
|
||||||
|
inkscape:tile-y0="219" />
|
||||||
|
<text
|
||||||
|
sodipodi:linespacing="55.000001%"
|
||||||
|
id="text7176-3"
|
||||||
|
y="908.29968"
|
||||||
|
x="504.82141"
|
||||||
|
style="font-size:15px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:55.00000119%;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
y="908.29968"
|
||||||
|
x="504.82141"
|
||||||
|
id="tspan7178-7"
|
||||||
|
sodipodi:role="line">end</tspan></text>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-mid:none;marker-end:url(#Arrow1Mend)"
|
||||||
|
d="M 60.16588,176.51786 C 64.809921,191.17027 67.41717,201.02245 68.196339,219"
|
||||||
|
id="path7243"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
transform="translate(0,752.36218)"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-mid:none;marker-end:url(#Arrow1Mend)"
|
||||||
|
d="m 540.40821,925.49856 c 2.63474,15.08647 5.72454,31.23577 6.06617,45.86362"
|
||||||
|
id="path7243-8"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 41 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-78 -1741 14091 2499" width="495" height="88" role="img" aria-label="nlohmann/json"><title>nlohmann/json</title><g transform="scale(1 -1)"><g fill="#ffffff"><path transform="translate(0.0 0)" d="M279.2 697.3V0H167.3V1104.2H276.1L276.4 850.4H255.4Q299 990.6 399.3 1055.2Q499.6 1119.9 626.3 1119.9Q739.7 1119.9 826.2 1072.4Q912.8 1024.9 962.1 935.2Q1011.4 845.5 1011.4 717.1V0H899.6V709.9Q899.6 851.3 818 933.9Q736.5 1016.6 601.8 1016.6Q510.1 1016.6 437 977.7Q363.8 938.9 321.5 867.2Q279.2 795.6 279.2 697.3Z"/><path transform="translate(1138.0 0)" d="M279.2 1490V0H167.3V1490H279.2Z"/><path transform="translate(1544.1 0)" d="M590.7 -23.1Q448.9 -23.1 339.6 50.3Q230.3 123.7 168.3 252.7Q106.3 381.6 106.3 548Q106.3 715.8 168.3 844.8Q230.3 973.8 339.6 1047.1Q448.9 1120.3 590.7 1120.3Q732.6 1120.3 841.7 1047.1Q950.9 973.8 1012.9 844.6Q1074.9 715.4 1074.9 548Q1074.9 381.6 1013.1 252.7Q951.3 123.7 842.1 50.3Q733 -23.1 590.7 -23.1ZM590.7 79.6Q705.6 79.6 788.9 141.9Q872.2 204.2 917.1 310.4Q962 416.7 962 548Q962 679.6 916.9 786Q871.8 892.4 788.5 955Q705.2 1017.6 590.7 1017.6Q476.7 1017.6 393.5 954.9Q310.3 892.2 265 786.1Q219.6 679.9 219.6 548Q219.6 416.6 264.8 310.4Q309.9 204.2 393.1 141.9Q476.2 79.6 590.7 79.6Z"/><path transform="translate(2684.1 0)" d="M279.2 697.3V0H167.3V1490H279.2V850.4H255.4Q299 990.6 399.3 1055.2Q499.6 1119.9 626.3 1119.9Q739.7 1119.9 826.4 1072.4Q913.2 1024.9 962.5 935.2Q1011.8 845.5 1011.8 717.1V0H899.6V709.9Q899.6 851.3 818 933.9Q736.5 1016.6 601.8 1016.6Q510.1 1016.6 437 977.7Q363.8 938.9 321.5 867.2Q279.2 795.6 279.2 697.3Z"/><path transform="translate(3822.2 0)" d="M167.3 0V1104.2H276.8L277.2 853.7H261.4Q285.1 945.7 337 1004.8Q388.9 1064 457.8 1093.1Q526.7 1122.2 599.7 1122.2Q722.9 1122.2 805.7 1046.7Q888.4 971.2 910.3 844.9H888.1Q907.9 930.7 959.4 992.8Q1011 1054.9 1085.3 1088.6Q1159.6 1122.2 1248.1 1122.2Q1349.1 1122.2 1427 1078Q1505 1033.7 1549.5 948.2Q1594 862.7 1594 738.2V0H1482.1V736.3Q1482.1 878 1408.3 948Q1334.4 1018.1 1221.8 1018.1Q1139.1 1018.1 1074.7 982.6Q1010.3 947 973.4 881.6Q936.6 816.3 936.6 726.7V0H824.7V745.3Q824.7 873 754.3 945.6Q683.9 1018.1 570.7 1018.1Q491.1 1018.1 424.8 981.1Q358.4 944 318.8 875.8Q279.2 807.7 279.2 713.4V0H167.3Z"/><path transform="translate(5542.2 0)" d="M560.3 -22.7Q425.4 -22.7 323.1 49.7Q220.8 122 163.6 251Q106.3 380.1 106.3 549.8Q106.3 718.7 164 847.4Q221.7 976 324.4 1047.9Q427.1 1119.9 561.9 1119.9Q657.4 1119.9 727.7 1085.2Q798.1 1050.6 845.2 994.3Q892.3 938.1 916.4 874.3H924.9V1104.2H1036.2V0H925.3V225.1H916.4Q891.1 161.2 843.7 104.4Q796.3 47.5 725.9 12.4Q655.4 -22.7 560.3 -22.7ZM574.9 79.9Q685.3 79.9 764.2 140.4Q843.1 200.8 885.1 307.1Q927.2 413.4 927.2 550.6Q927.2 687.8 885.1 793Q843.1 898.2 764.2 957.8Q685.3 1017.4 574.9 1017.4Q464.6 1017.4 384.9 957.2Q305.3 897.1 262.4 791.7Q219.6 686.2 219.6 550.6Q219.6 415 262.4 308.6Q305.3 202.3 384.9 141.1Q464.6 79.9 574.9 79.9Z"/><path transform="translate(6705.2 0)" d="M279.2 697.3V0H167.3V1104.2H276.1L276.4 850.4H255.4Q299 990.6 399.3 1055.2Q499.6 1119.9 626.3 1119.9Q739.7 1119.9 826.2 1072.4Q912.8 1024.9 962.1 935.2Q1011.4 845.5 1011.4 717.1V0H899.6V709.9Q899.6 851.3 818 933.9Q736.5 1016.6 601.8 1016.6Q510.1 1016.6 437 977.7Q363.8 938.9 321.5 867.2Q279.2 795.6 279.2 697.3Z"/><path transform="translate(7843.3 0)" d="M279.2 697.3V0H167.3V1104.2H276.1L276.4 850.4H255.4Q299 990.6 399.3 1055.2Q499.6 1119.9 626.3 1119.9Q739.7 1119.9 826.2 1072.4Q912.8 1024.9 962.1 935.2Q1011.4 845.5 1011.4 717.1V0H899.6V709.9Q899.6 851.3 818 933.9Q736.5 1016.6 601.8 1016.6Q510.1 1016.6 437 977.7Q363.8 938.9 321.5 867.2Q279.2 795.6 279.2 697.3Z"/><path transform="translate(9729.7 0)" d="M111.2 1104.2H459.2V-51.7Q459.4 -183.9 406.6 -264.4Q353.7 -344.9 255.5 -381.4Q157.2 -418 21.4 -418H-63.7V-152.7H-14.8Q55.3 -152.7 83.3 -125Q111.2 -97.4 111.2 -39.2V1104.2ZM285.1 1228.6Q208.7 1228.6 155.6 1278Q102.6 1327.5 102.6 1398.2Q102.6 1469.9 155.6 1518.8Q208.7 1567.8 285 1567.8Q361.5 1567.8 414.7 1518.9Q467.8 1470.1 467.8 1398.1Q467.8 1327.4 414.7 1278Q361.5 1228.6 285.1 1228.6Z"/><path transform="translate(10258.7 0)" d="M580.7 -22.1Q436.8 -22.1 326.1 19.4Q215.5 60.9 146.9 138.4Q78.2 215.9 61 323.7L388.3 363.3Q406.3 294.2 455.2 257.5Q504.1 220.8 589.7 220.8Q662.2 220.8 703.6 246.4Q745 271.9 745 313.9Q745 351.2 711.8 374.4Q678.7 397.6 610.3 410.8L416 447.9Q252.5 479.2 170.3 559.5Q88.1 639.9 88.1 767.7Q88.1 878 147.7 956.7Q207.4 1035.5 316.7 1077.7Q426.1 1119.9 575.6 1119.9Q717.4 1119.9 822.1 1080.8Q926.7 1041.7 989.4 969.8Q1052.1 897.9 1067.5 797.8L757.5 759.8Q745.3 815.8 699.4 848.2Q653.5 880.6 583.2 880.6Q518.8 880.6 476.8 855.3Q434.8 829.9 434.8 787.7Q434.8 753.9 462.5 729Q490.3 704.1 557.8 691L771.4 651.3Q937.2 620.2 1017.9 546.8Q1098.7 473.4 1098.7 352.5Q1098.7 239.1 1032.3 154.9Q965.9 70.8 849.2 24.3Q732.5 -22.1 580.7 -22.1Z"/><path transform="translate(11376.8 0)" d="M628.4 -21.6Q455.7 -21.6 329.7 49.8Q203.6 121.3 135.6 249.6Q67.7 378 67.7 548.9Q67.7 720.4 135.6 848.9Q203.6 977.3 329.7Line truncated
|
|
||||||
|
Before Width: | Height: | Size: 5.8 KiB |
@@ -1,11 +1,63 @@
|
|||||||
---
|
|
||||||
template: home.html
|
|
||||||
hide:
|
|
||||||
- toc
|
|
||||||
---
|
|
||||||
|
|
||||||
# JSON for Modern C++
|
# JSON for Modern C++
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-class C++ data type, using the operator
|
||||||
|
magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like
|
||||||
|
Python. The whole library is available as a single header, `json.hpp`, with no dependencies, no subproject, and no
|
||||||
|
complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times
|
||||||
|
down, `<nlohmann/json_literals.hpp>` holds the user-defined string literals on their own, and
|
||||||
|
`<nlohmann/json_view.hpp>` adds zero-copy views and editable documents. See
|
||||||
|
[header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
|
||||||
|
coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
|
||||||
|
OSS-Fuzz.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
Add the single header to your project and use the library like this:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// parse a JSON string
|
||||||
|
json j = json::parse(R"({"happy": true, "pi": 3.141})");
|
||||||
|
|
||||||
|
// access and modify values
|
||||||
|
j["name"] = "Niels";
|
||||||
|
j["list"] = {1, 0, 2};
|
||||||
|
|
||||||
|
// read a value into a C++ variable and use it
|
||||||
|
double pi = j["pi"].get<double>();
|
||||||
|
std::cout << "2 * pi = " << 2 * pi << '\n';
|
||||||
|
|
||||||
|
// serialize with an indent of 4 spaces
|
||||||
|
std::cout << j.dump(4) << '\n';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Get the library by copying the single header [`json.hpp`](https://github.com/nlohmann/json/releases) from the
|
||||||
|
releases page into a directory `nlohmann` on your include path, or by installing it with a package manager:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew install nlohmann-json # Homebrew
|
||||||
|
vcpkg install nlohmann-json # vcpkg
|
||||||
|
```
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
find_package(nlohmann_json 3.12.0 REQUIRED)
|
||||||
|
target_link_libraries(myproject PRIVATE nlohmann_json::nlohmann_json)
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Integration](integration/index.md) for CMake in detail, all supported package managers (Conan, Meson, Bazel,
|
||||||
|
Conda, and more), and pkg-config.
|
||||||
|
|
||||||
|
## Explore the documentation
|
||||||
|
|
||||||
<div class="grid cards" markdown>
|
<div class="grid cards" markdown>
|
||||||
|
|
||||||
- :octicons-rocket-24:{ .lg .middle } __Features__
|
- :octicons-rocket-24:{ .lg .middle } __Features__
|
||||||
@@ -60,5 +112,11 @@ hide:
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|
!!! info "Unreleased changes"
|
||||||

|
|
||||||
|
This documentation is built from the `develop` branch and may describe changes that are not part of a release
|
||||||
|
yet. Their version numbers are followed by an <span class="unreleased-version">unreleased</span> badge; see
|
||||||
|
[Releases](home/releases/index.md) for what shipped in each version.
|
||||||
|
|
||||||
|
The library is licensed under the [MIT License](home/license.md). The source code, issue tracker, and discussions
|
||||||
|
are on [GitHub](https://github.com/nlohmann/json).
|
||||||
@@ -5,13 +5,13 @@ pick one:
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A[Add the library to a C++ project] --> B{{Already using CMake?}}
|
A[Add the library to a C++ project] --> B{Already using CMake?}
|
||||||
B -- no --> C{{Using pkg-config or plain Makefiles?}}
|
B -- no --> C{Using pkg-config or plain Makefiles?}
|
||||||
C -- yes --> D[pkg-config]
|
C -- yes --> D[pkg-config]
|
||||||
C -- no --> E[Copy the single header]
|
C -- no --> E[Copy the single header]
|
||||||
B -- yes --> F{{Library installed system-wide?}}
|
B -- yes --> F{Library installed system-wide?}
|
||||||
F -- yes --> G["find_package()"]
|
F -- yes --> G["find_package()"]
|
||||||
F -- no --> H{{Use a package manager?}}
|
F -- no --> H{Use a package manager?}
|
||||||
H -- yes --> I[Package manager]
|
H -- yes --> I[Package manager]
|
||||||
H -- no --> J["add_subdirectory() or FetchContent"]
|
H -- no --> J["add_subdirectory() or FetchContent"]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* Brand look for Mermaid diagrams.
|
|
||||||
*
|
|
||||||
* Material for MkDocs loads Mermaid on demand and calls mermaid.initialize() with its own theme CSS. This script
|
|
||||||
* catches Mermaid when it arrives on window.mermaid and wraps two calls:
|
|
||||||
* - initialize(): adds the brand's font, spacing and shape rules below to Material's options;
|
|
||||||
* - render(): marks hexagons ({{...}} in a flowchart) with the class "nj-decision", since in the SVG a hexagon and
|
|
||||||
* a parallelogram are both a <polygon> that CSS cannot tell apart; and keeps a wide diagram at 80 % of its size
|
|
||||||
* or more, so its text stays readable and the diagram scrolls sideways instead (see .mermaid in brand.css).
|
|
||||||
* The colours come from the --md-mermaid-* variables in brand.css, so the diagrams follow the light/dark toggle.
|
|
||||||
*/
|
|
||||||
(function () {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var css = [
|
|
||||||
// text: Inter everywhere, labels on boxes semibold
|
|
||||||
".label, .nodeLabel, .nodeLabel p, .edgeLabel, .edgeLabel p, .cluster span, text { font-family: var(--md-mermaid-font-family) !important; }",
|
|
||||||
".nodeLabel, .nodeLabel p { font-weight: 600; }",
|
|
||||||
".edgeLabel, .edgeLabel p { color: var(--md-default-fg-color--light) !important; font-size: 13px; font-weight: 500; }",
|
|
||||||
|
|
||||||
// boxes: 1.5 px outline, 6 px corners; "(...)" nodes stay rounder than "[...]" nodes
|
|
||||||
".node rect, .node path, .node polygon, .node circle, .node ellipse { stroke-width: 1.5px !important; }",
|
|
||||||
".node rect:not([rx]) { rx: 6px; ry: 6px; }",
|
|
||||||
".node rect[rx] { rx: 14px; ry: 14px; }",
|
|
||||||
|
|
||||||
// decisions: solid blue hexagons with white text
|
|
||||||
".node.nj-decision polygon { fill: var(--nj-band) !important; stroke: var(--nj-band) !important; stroke-linejoin: round; }",
|
|
||||||
".node.nj-decision .nodeLabel, .node.nj-decision .nodeLabel p { color: #fff !important; }",
|
|
||||||
|
|
||||||
// lines: 1.5 px, solid unless written as -.-
|
|
||||||
".edgePath .path, .flowchart-link { stroke-width: 1.5px !important; }",
|
|
||||||
"marker path { stroke: none !important; }",
|
|
||||||
|
|
||||||
// class diagrams; a node with its own fill ("style X fill:#CCCCFF" on the exception pages) is the highlighted
|
|
||||||
// one and gets soft blue, which also works in dark mode
|
|
||||||
"g.classGroup rect, .classGroup line, g.node.default > g > path { stroke-width: 1.5px !important; }",
|
|
||||||
".node path[style*='fill'], .node rect[style*='fill'] { fill: var(--nj-blue-soft) !important; }",
|
|
||||||
".relation { stroke-width: 1.5px !important; }",
|
|
||||||
|
|
||||||
// sequence diagrams
|
|
||||||
".actor { stroke-width: 1.5px !important; rx: 6px; ry: 6px; }",
|
|
||||||
".actor-line { stroke-width: 1px; }",
|
|
||||||
".messageLine0, .messageLine1 { stroke-width: 1.5px !important; }",
|
|
||||||
".note { rx: 6px; ry: 6px; }"
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
theme: "base",
|
|
||||||
fontFamily: "Inter, sans-serif",
|
|
||||||
themeVariables: { fontFamily: "Inter, sans-serif", fontSize: "15px" },
|
|
||||||
flowchart: { curve: "basis", padding: 12, nodeSpacing: 36, rankSpacing: 48, wrappingWidth: 240 }
|
|
||||||
};
|
|
||||||
|
|
||||||
var minScale = 0.8;
|
|
||||||
|
|
||||||
function postProcess(svg) {
|
|
||||||
// shrink to fit the column, but not below minScale
|
|
||||||
svg = svg.replace(/^(<svg[^>]*style="[^"]*)max-width:\s*([\d.]+)px;/, function (all, head, width) {
|
|
||||||
return head + "max-width: " + width + "px; min-width: " + Math.round(width * minScale) + "px;";
|
|
||||||
});
|
|
||||||
// add class "nj-decision" to every flowchart node drawn as a hexagon (a polygon with six corners)
|
|
||||||
return svg.replace(/<g class="node ([^"]*)"([^>]*)><polygon points="([^"]*)"/g, function (all, cls, rest, points) {
|
|
||||||
var corners = points.trim().split(/\s+/).length;
|
|
||||||
return corners === 6 ? '<g class="node nj-decision ' + cls + '"' + rest + '><polygon points="' + points + '"' : all;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function brand(mermaid) {
|
|
||||||
if (!mermaid || typeof mermaid.initialize !== "function" || mermaid.njBrand) {
|
|
||||||
return mermaid;
|
|
||||||
}
|
|
||||||
var initialize = mermaid.initialize.bind(mermaid);
|
|
||||||
var render = mermaid.render.bind(mermaid);
|
|
||||||
mermaid.initialize = function (config) {
|
|
||||||
config = Object.assign({}, config || {});
|
|
||||||
config.theme = options.theme;
|
|
||||||
config.fontFamily = options.fontFamily;
|
|
||||||
config.themeVariables = Object.assign({}, config.themeVariables, options.themeVariables);
|
|
||||||
config.flowchart = Object.assign({}, config.flowchart, options.flowchart);
|
|
||||||
config.themeCSS = (config.themeCSS || "") + "\n" + css;
|
|
||||||
return initialize(config);
|
|
||||||
};
|
|
||||||
mermaid.render = function () {
|
|
||||||
return render.apply(null, arguments).then(function (result) {
|
|
||||||
result.svg = postProcess(result.svg);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
mermaid.njBrand = true;
|
|
||||||
return mermaid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mermaid ends with globalThis["mermaid"] = ..., which runs the setter below
|
|
||||||
var current = brand(window.mermaid);
|
|
||||||
Object.defineProperty(window, "mermaid", {
|
|
||||||
configurable: true,
|
|
||||||
get: function () { return current; },
|
|
||||||
set: function (value) { current = brand(value); }
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
# Project information
|
# Project information
|
||||||
site_name: JSON for Modern C++
|
site_name: JSON for Modern C++
|
||||||
site_description: JSON for Modern C++ is a header-only C++11 library that makes JSON a first-class data type.
|
|
||||||
site_author: Niels Lohmann
|
site_author: Niels Lohmann
|
||||||
site_url: https://json.nlohmann.me/
|
site_url: https://json.nlohmann.me/
|
||||||
|
|
||||||
@@ -15,38 +14,33 @@ copyright: Copyright © 2013-2026 Niels Lohmann
|
|||||||
# Configuration
|
# Configuration
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
custom_dir: overrides
|
|
||||||
language: en
|
language: en
|
||||||
palette:
|
palette:
|
||||||
- media: '(prefers-color-scheme: light)'
|
- media: '(prefers-color-scheme: light)'
|
||||||
scheme: default
|
scheme: default
|
||||||
primary: custom
|
primary: indigo
|
||||||
accent: custom
|
accent: indigo
|
||||||
toggle:
|
toggle:
|
||||||
icon: material/brightness-7
|
icon: material/brightness-7
|
||||||
name: Switch to dark mode
|
name: Switch to dark mode
|
||||||
- media: '(prefers-color-scheme: dark)'
|
- media: '(prefers-color-scheme: dark)'
|
||||||
scheme: slate
|
scheme: slate
|
||||||
primary: custom
|
primary: indigo
|
||||||
accent: custom
|
accent: indigo
|
||||||
toggle:
|
toggle:
|
||||||
icon: material/brightness-4
|
icon: material/brightness-4
|
||||||
name: Switch to light mode
|
name: Switch to light mode
|
||||||
icon:
|
icon:
|
||||||
repo: fontawesome/brands/github
|
repo: fontawesome/brands/github
|
||||||
font: false
|
font:
|
||||||
logo: images/wordmark-header.svg
|
text: Roboto
|
||||||
|
code: JetBrains Mono
|
||||||
features:
|
features:
|
||||||
- navigation.instant
|
- navigation.instant
|
||||||
- navigation.tracking
|
- navigation.tracking
|
||||||
- navigation.tabs
|
- navigation.tabs
|
||||||
- navigation.indexes
|
- navigation.indexes
|
||||||
- navigation.top
|
- navigation.top
|
||||||
- navigation.footer
|
|
||||||
- toc.follow
|
|
||||||
- search.suggest
|
|
||||||
- search.highlight
|
|
||||||
- content.code.copy
|
|
||||||
- content.tabs.link
|
- content.tabs.link
|
||||||
- content.action.edit
|
- content.action.edit
|
||||||
- content.action.view
|
- content.action.view
|
||||||
@@ -625,7 +619,3 @@ plugins:
|
|||||||
|
|
||||||
extra_css:
|
extra_css:
|
||||||
- css/custom.css
|
- css/custom.css
|
||||||
- css/brand.css
|
|
||||||
|
|
||||||
extra_javascript:
|
|
||||||
- js/mermaid-brand.js
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{#- 404 page: say what happened and offer the three useful ways out -#}
|
|
||||||
{% extends "main.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="nj-404">
|
|
||||||
<p class="nj-404__code">// error 404</p>
|
|
||||||
<h1>This page does not exist</h1>
|
|
||||||
<p>The page may have moved or been renamed. Try the search in the header, or start from one of these pages.</p>
|
|
||||||
<p>
|
|
||||||
<a href="{{ '' | url }}" class="md-button md-button--primary">Home</a>
|
|
||||||
<a href="{{ 'features/' | url }}" class="md-button">Features</a>
|
|
||||||
<a href="{{ 'api/basic_json/' | url }}" class="md-button">API documentation</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{#- Home page: the brand's blue hero band below the tabs (selected with "template: home.html" in index.md) -#}
|
|
||||||
{% extends "main.html" %}
|
|
||||||
|
|
||||||
{% block tabs %}
|
|
||||||
{{ super() }}
|
|
||||||
<section class="nj-hero">
|
|
||||||
<div class="nj-hero__inner md-grid">
|
|
||||||
<div class="nj-hero__text">
|
|
||||||
<h1 class="nj-hero__title">JSON for <span>Modern C++</span></h1>
|
|
||||||
<p class="nj-hero__lead">What if JSON was part of modern C++?</p>
|
|
||||||
<p class="nj-hero__actions">
|
|
||||||
<a href="{{ 'integration/' | url }}" class="md-button md-button--primary nj-hero__button">Get started</a>
|
|
||||||
<a href="{{ 'api/basic_json/' | url }}" class="md-button nj-hero__button">API documentation</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="nj-hero__code" aria-hidden="true">
|
|
||||||
<pre><code><span class="nj-k">#include</span> <span class="nj-s"><iostream></span>
|
|
||||||
<span class="nj-k">#include</span> <span class="nj-s"><nlohmann/json.hpp></span>
|
|
||||||
|
|
||||||
<span class="nj-k">using</span> json = nlohmann::json;
|
|
||||||
|
|
||||||
<span class="nj-k">int</span> main()
|
|
||||||
{
|
|
||||||
json j = json::parse(<span class="nj-s">R"({"happy": true})"</span>);
|
|
||||||
j[<span class="nj-s">"pi"</span>] = <span class="nj-n">3.141</span>;
|
|
||||||
j[<span class="nj-s">"list"</span>] = {<span class="nj-n">1</span>, <span class="nj-n">0</span>, <span class="nj-n">2</span>};
|
|
||||||
|
|
||||||
std::cout << j.dump(<span class="nj-n">4</span>) << <span class="nj-s">"\n"</span>;
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"""Draws the parser-callback and iterator illustrations as an inline SVG into docs/images/.
|
|
||||||
|
|
||||||
The SVG is not an image: the pages include its text with `--8<-- "images/callback_events.svg, range-begin-end.svg and range-rbegin-rend.svg"`, so it picks up the colours and fonts of the
|
|
||||||
page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/iterator_callback_figures.py` from docs/mkdocs after
|
|
||||||
changing the drawing, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the
|
|
||||||
abbreviation extension wraps it in an <abbr> and the text disappears.
|
|
||||||
"""
|
|
||||||
import html
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
OUT = Path(__file__).resolve().parents[2] / "docs" / "images"
|
|
||||||
CW = 9.6 # JetBrains Mono advance at 16px
|
|
||||||
|
|
||||||
def write(name, W, H, cls, tid, title, body, defs=""):
|
|
||||||
s = ['<svg class="nj-fig %s" viewBox="0 0 %d %d" role="img" aria-labelledby="%s" xmlns="http://www.w3.org/2000/svg">' % (cls, W, H, tid),
|
|
||||||
'<title id="%s">%s</title>' % (tid, html.escape(title).replace('J&#8203;SON', 'J​SON'))]
|
|
||||||
if defs: s.append(defs)
|
|
||||||
s += body
|
|
||||||
s.append('</svg>')
|
|
||||||
open(OUT / name, "w").write("\n".join(s) + "\n")
|
|
||||||
|
|
||||||
# ---------- callback events ----------
|
|
||||||
def callback():
|
|
||||||
body, y0, lh = [], 74, 28
|
|
||||||
body.append('<text class="fg-h" x="16" y="28">J​SON to parse</text>')
|
|
||||||
body.append('<text class="fg-h" x="320" y="28">callback events</text>')
|
|
||||||
lines = [
|
|
||||||
(0, [("{", None), (None, "os")]),
|
|
||||||
(2, [('"name"', None), (None, "k"), (': "Berlin"', None), (None, "v"), (",", None)]),
|
|
||||||
(2, [('"location"', None), (None, "k"), (": [", None), (None, "as")]),
|
|
||||||
(4, [("52.519444", None), (None, "v"), (",", None)]),
|
|
||||||
(4, [("13.406667", None), (None, "v")]),
|
|
||||||
(2, [("]", None), (None, "ae"), (None, "v")]),
|
|
||||||
(0, [("}", None), (None, "oe"), (None, "v")]),
|
|
||||||
]
|
|
||||||
for i, (indent, parts) in enumerate(lines):
|
|
||||||
y = y0 + i * lh
|
|
||||||
x = 16 + indent * CW
|
|
||||||
for text, ev in parts:
|
|
||||||
if text is not None:
|
|
||||||
body.append('<text class="fg-code" x="%g" y="%d" xml:space="preserve">%s</text>' % (x, y, html.escape(text)))
|
|
||||||
x += CW * len(text)
|
|
||||||
else:
|
|
||||||
body.append('<circle class="cb-%s" cx="%g" cy="%d" r="5.5"/>' % (ev, x + 10, y - 5))
|
|
||||||
x += 20 if text is None else 0
|
|
||||||
legend = [("os", "object_start"), ("k", "key"), ("oe", "object_end"), ("as", "array_start"), ("ae", "array_end"), ("v", "value")]
|
|
||||||
for i, (ev, label) in enumerate(legend):
|
|
||||||
y = 74 + i * 30
|
|
||||||
body.append('<circle class="cb-%s" cx="327" cy="%d" r="6"/>' % (ev, y - 5))
|
|
||||||
body.append('<text class="fg-code fg-leg" x="344" y="%d">%s</text>' % (y, label))
|
|
||||||
write("callback_events.svg", 480, 262, "nj-fig--cb", "fig-cb-title",
|
|
||||||
"Example of the parse events a callback sees for the J​SON text {\"name\": \"Berlin\", \"location\": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete.", body)
|
|
||||||
|
|
||||||
# ---------- ranges ----------
|
|
||||||
BW, GAP, X0 = 30, 4, 30
|
|
||||||
def cell_x(i): return X0 + i * (BW + GAP)
|
|
||||||
def cell(i, y, kind):
|
|
||||||
c = "fg-cell" + (" fg-cell--end" if kind == "end" else "")
|
|
||||||
return '<rect class="%s" x="%d" y="%d" width="%d" height="30" rx="5"/>' % (c, cell_x(i), y, BW)
|
|
||||||
def pill(cx, y, label, W, label_w):
|
|
||||||
x = max(4, min(W - 4 - label_w, cx - label_w / 2))
|
|
||||||
return ['<rect class="fg-pill" x="%g" y="%d" width="%d" height="24" rx="6"/>' % (x, y, label_w),
|
|
||||||
'<text class="fg-pilltext" x="%g" y="%d">%s</text>' % (x + label_w / 2, y + 16.5, label)]
|
|
||||||
def arrow(x, ya, yb, mid, dashed=False):
|
|
||||||
return '<line class="fg-arrow%s" x1="%g" y1="%g" x2="%g" y2="%g" marker-end="url(#%s)"/>' % (" fg-arrow--dash" if dashed else "", x, ya, x, yb, mid)
|
|
||||||
|
|
||||||
def ranges1():
|
|
||||||
W, H, mid = 600, 150, "fig-be-arrow"
|
|
||||||
defs = '<defs><marker id="%s" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>' % mid
|
|
||||||
body = []
|
|
||||||
for i in range(15): body.append(cell(i, 62, "elem"))
|
|
||||||
body.append(cell(15, 62, "end"))
|
|
||||||
body += pill(cell_x(0) + BW / 2, 8, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid))
|
|
||||||
body += pill(cell_x(15) + BW / 2, 8, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid))
|
|
||||||
body.append('<text class="fg-note fg-note--r" x="%d" y="132">Past-the-last element</text>' % (cell_x(15) + BW / 2 - 18))
|
|
||||||
body.append(arrow(cell_x(15) + BW / 2, 124, 96, mid))
|
|
||||||
write("range-begin-end.svg", W, H, "nj-fig--range", "fig-be-title",
|
|
||||||
"A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position.", body, defs)
|
|
||||||
|
|
||||||
def ranges2():
|
|
||||||
W, H, mid = 600, 236, "fig-rbe-arrow"
|
|
||||||
defs = '<defs><marker id="%s" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>' % mid
|
|
||||||
body = []
|
|
||||||
ytop, ybot = 62, 144
|
|
||||||
body.append(cell(0, ytop, "end"))
|
|
||||||
for i in range(1, 16): body.append(cell(i, ytop, "elem"))
|
|
||||||
for i in range(15): body.append(cell(i, ybot, "elem"))
|
|
||||||
body.append(cell(15, ybot, "end"))
|
|
||||||
body += pill(cell_x(0) + BW / 2, 8, "rend", W, 52); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid))
|
|
||||||
body += pill(cell_x(15) + BW / 2, 8, "rbegin", W, 68); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid))
|
|
||||||
body.append('<text class="fg-note fg-note--l" x="%d" y="24">Reverse past-the-last element</text>' % (cell_x(0) + BW / 2 + 36))
|
|
||||||
for col in (0, 15):
|
|
||||||
x = cell_x(col) + BW / 2
|
|
||||||
body.append('<line class="fg-arrow fg-arrow--dash" x1="%g" y1="94" x2="%g" y2="%d"/>' % (x, x, ybot - 2))
|
|
||||||
body.append('<text class="fg-note" x="300" y="123">A reverse iterator stores an iterator to the next element</text>')
|
|
||||||
body.append('<text class="fg-note" x="300" y="141" dy="0" style="display:none"></text>')
|
|
||||||
body += pill(cell_x(0) + BW / 2, 204, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 202, 178, mid))
|
|
||||||
body += pill(cell_x(15) + BW / 2, 204, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 202, 178, mid))
|
|
||||||
body.append('<text class="fg-note fg-note--r" x="%d" y="221">Past-the-last element</text>' % (cell_x(15) + BW / 2 - 40))
|
|
||||||
write("range-rbegin-rend.svg", W, H, "nj-fig--range", "fig-rbe-title",
|
|
||||||
"Two rows of sixteen cells. The upper row shows the reversed sequence: rend() points to a dashed position before the first element and rbegin() to the last element. The lower row shows the forward sequence: begin() points to the first element and end() to a dashed position after the last element. A reverse iterator stores an iterator to the next element, so rend() corresponds to begin() and rbegin() to end().", body, defs)
|
|
||||||
|
|
||||||
callback(); ranges1(); ranges2()
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
"""Draws the JSON number railroad diagram as an inline SVG into docs/images/.
|
|
||||||
|
|
||||||
The SVG is not an image: the pages include its text with `--8<-- "images/json_syntax_number.svg"`, so it picks up the colours and fonts of the
|
|
||||||
page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/railroad_number.py` from docs/mkdocs after
|
|
||||||
changing the drawing, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the
|
|
||||||
abbreviation extension wraps it in an <abbr> and the text disappears.
|
|
||||||
"""
|
|
||||||
import math
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
OUT = Path(__file__).resolve().parents[2] / "docs" / "images"
|
|
||||||
Y0, Y1, Y2, Y3 = 44, 92, 136, 180
|
|
||||||
R = 10
|
|
||||||
paths, shapes, texts = [], [], []
|
|
||||||
|
|
||||||
def rounded(pts, r=R):
|
|
||||||
d = "M%g %g" % pts[0]
|
|
||||||
for i in range(1, len(pts) - 1):
|
|
||||||
(x0, y0), (x1, y1), (x2, y2) = pts[i-1], pts[i], pts[i+1]
|
|
||||||
l1 = math.hypot(x1-x0, y1-y0); l2 = math.hypot(x2-x1, y2-y1)
|
|
||||||
rr = min(r, l1/2, l2/2)
|
|
||||||
ax, ay = x1 - (x1-x0)/l1*rr, y1 - (y1-y0)/l1*rr
|
|
||||||
bx, by = x1 + (x2-x1)/l2*rr, y1 + (y2-y1)/l2*rr
|
|
||||||
d += " L%g %g Q%g %g %g %g" % (ax, ay, x1, y1, bx, by)
|
|
||||||
d += " L%g %g" % pts[-1]
|
|
||||||
return d
|
|
||||||
|
|
||||||
def path(*pts): paths.append(rounded(list(pts)))
|
|
||||||
def circle(cx, cy, ch):
|
|
||||||
shapes.append('<circle class="rr-bg" cx="%g" cy="%g" r="13"/>' % (cx, cy))
|
|
||||||
shapes.append('<circle class="rr-t" cx="%g" cy="%g" r="13"/>' % (cx, cy))
|
|
||||||
texts.append('<text class="rr-c" x="%g" y="%g">%s</text>' % (cx, cy+5, ch))
|
|
||||||
def box(x0, x1, cy, label):
|
|
||||||
shapes.append('<rect class="rr-bg" x="%g" y="%g" width="%g" height="28" rx="6"/>' % (x0, cy-14, x1-x0))
|
|
||||||
shapes.append('<rect class="rr-n" x="%g" y="%g" width="%g" height="28" rx="6"/>' % (x0, cy-14, x1-x0))
|
|
||||||
texts.append('<text class="rr-b" x="%g" y="%g">%s</text>' % ((x0+x1)/2, cy+5, label))
|
|
||||||
def loop(xr, xl, cy): # return path below a box row
|
|
||||||
path((xr, cy), (xr, cy+34), (xl, cy+34), (xl, cy))
|
|
||||||
|
|
||||||
# start / main
|
|
||||||
path((8, Y0), (850, Y0))
|
|
||||||
# A: optional minus
|
|
||||||
path((24, Y0), (38, Y0), (38, Y1), (59, Y1)); circle(72, Y1, "−"); path((85, Y1), (106, Y1), (106, Y0), (120, Y0))
|
|
||||||
# B: 0 | 1-9 digit*
|
|
||||||
circle(257, Y0, "0")
|
|
||||||
path((120, Y0), (134, Y0), (134, Y1), (160, Y1)); box(160, 242, Y1, "digit 1–9")
|
|
||||||
path((242, Y1), (380, Y1), (380, Y0), (396, Y0))
|
|
||||||
path((242, Y1), (258, Y1), (258, Y2), (278, Y2)); box(278, 332, Y2, "digit")
|
|
||||||
path((332, Y2), (348, Y2), (348, Y1)); loop(340, 270, Y2)
|
|
||||||
# C: fraction
|
|
||||||
path((396, Y0), (410, Y0), (410, Y1), (425, Y1)); circle(438, Y1, ".")
|
|
||||||
path((451, Y1), (462, Y1)); box(462, 516, Y1, "digit"); loop(524, 456, Y1)
|
|
||||||
path((516, Y1), (530, Y1), (530, Y0), (546, Y0))
|
|
||||||
# D: exponent
|
|
||||||
path((546, Y0), (560, Y0), (560, Y1), (577, Y1)); circle(590, Y1, "e")
|
|
||||||
path((546, Y0), (560, Y0), (560, Y2), (577, Y2)); circle(590, Y2, "E")
|
|
||||||
path((603, Y1), (728, Y1)); path((603, Y2), (620, Y2), (620, Y1))
|
|
||||||
path((640, Y1), (654, Y1), (654, Y2), (670, Y2)); circle(683, Y2, "−")
|
|
||||||
path((640, Y1), (654, Y1), (654, Y3), (670, Y3)); circle(683, Y3, "+")
|
|
||||||
path((696, Y2), (712, Y2), (712, Y1)); path((696, Y3), (712, Y3), (712, Y1))
|
|
||||||
path((728, Y1), (746, Y1)); box(746, 800, Y1, "digit"); loop(808, 738, Y1)
|
|
||||||
path((800, Y1), (816, Y1), (816, Y0), (832, Y0))
|
|
||||||
|
|
||||||
svg = ['<svg class="railroad" viewBox="0 0 860 214" role="img" aria-labelledby="rr-title" xmlns="http://www.w3.org/2000/svg">',
|
|
||||||
'<title id="rr-title">Railroad diagram of the J​SON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits).</title>',
|
|
||||||
'<g class="rr-track" fill="none" stroke="currentColor" stroke-opacity=".6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">']
|
|
||||||
for d in paths: svg.append('<path d="%s"/>' % d)
|
|
||||||
svg.append('</g>')
|
|
||||||
svg.append('<polygon class="rr-arrow" points="2,%d 12,%d 2,%d"/>' % (Y0-6, Y0, Y0+6))
|
|
||||||
svg.append('<polygon class="rr-arrow" points="846,%d 858,%d 846,%d"/>' % (Y0-6, Y0, Y0+6))
|
|
||||||
svg += shapes + texts
|
|
||||||
svg.append('<text class="rr-l rr-name" x="24" y="%d">number</text>' % (Y0-12))
|
|
||||||
svg.append('<text class="rr-l" x="420" y="%d">fraction</text>' % (Y0-12))
|
|
||||||
svg.append('<text class="rr-l" x="580" y="%d">exponent</text>' % (Y0-12))
|
|
||||||
svg.append('</svg>')
|
|
||||||
open(OUT / "json_syntax_number.svg", "w").write("\n".join(svg) + "\n")
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
fonts/
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# Rebuild the animated feature slideshow of the home page (docs/images/json-light.svg and json-dark.svg) when
|
|
||||||
# slides.md, build.py or the fonts change. Needs uv (build.py declares its dependencies inline, PEP 723), curl,
|
|
||||||
# unzip and shasum. Run it from docs/mkdocs/scripts/slideshow.
|
|
||||||
#
|
|
||||||
# make download missing fonts and build both SVGs
|
|
||||||
# make fonts download only the fonts
|
|
||||||
# make SECONDS=12 change the time per slide
|
|
||||||
# make clean remove the generated SVGs
|
|
||||||
# make distclean also remove the downloaded fonts
|
|
||||||
|
|
||||||
SECONDS ?= 15
|
|
||||||
|
|
||||||
IMAGES := ../../docs/images
|
|
||||||
LIGHT := $(IMAGES)/json-light.svg
|
|
||||||
DARK := $(IMAGES)/json-dark.svg
|
|
||||||
|
|
||||||
RUN := uv run --script build.py
|
|
||||||
FONTS := fonts/Inter.ttf fonts/JetBrainsMono.ttf
|
|
||||||
DEPS := build.py slides.md $(FONTS)
|
|
||||||
|
|
||||||
# Pinned sources; the checksums make a changed upstream file fail instead of silently changing the output.
|
|
||||||
# Inter: Google Fonts repository at a fixed commit (variable font with opsz and wght axes).
|
|
||||||
INTER_URL := https://raw.githubusercontent.com/google/fonts/0b58fb370093f9a9f4ff785d94405710b79de67c/ofl/inter/Inter%5Bopsz%2Cwght%5D.ttf
|
|
||||||
# JetBrains Mono: release 2.304 (the variable font in fonts/variable/ of the zip).
|
|
||||||
JBM_URL := https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip
|
|
||||||
JBM_ZIP := fonts/JetBrainsMono-2.304.zip
|
|
||||||
|
|
||||||
SHA256_Inter.ttf := 29160a80ff49ddcab2c97711247e08b1fab27a484a329ce8b813d820dc559031
|
|
||||||
SHA256_JetBrainsMono.ttf := 662a196d58f1183bf2d77428b6d5283fe3f45161ab021bea4036bc98e5cac016
|
|
||||||
SHA256_JetBrainsMono-2.304.zip := 6f6376c6ed2960ea8a963cd7387ec9d76e3f629125bc33d1fdcd7eb7012f7bbf
|
|
||||||
|
|
||||||
# Check $@.tmp against its pinned checksum, then move it into place.
|
|
||||||
define verify
|
|
||||||
echo '$(SHA256_$(notdir $@)) $@.tmp' | shasum -a 256 -c --quiet - || { rm -f $@.tmp; exit 1; }
|
|
||||||
mv $@.tmp $@
|
|
||||||
endef
|
|
||||||
|
|
||||||
.PHONY: all fonts clean distclean
|
|
||||||
.INTERMEDIATE: $(JBM_ZIP)
|
|
||||||
|
|
||||||
all: $(LIGHT) $(DARK)
|
|
||||||
|
|
||||||
fonts: $(FONTS)
|
|
||||||
|
|
||||||
# fixed colours for the docs: an <img> cannot see Material's theme toggle, so the home page includes both
|
|
||||||
# and shows the one that matches
|
|
||||||
$(LIGHT): $(DEPS)
|
|
||||||
$(RUN) $@.tmp $(SECONDS) light
|
|
||||||
mv $@.tmp $@
|
|
||||||
|
|
||||||
$(DARK): $(DEPS)
|
|
||||||
$(RUN) $@.tmp $(SECONDS) dark
|
|
||||||
mv $@.tmp $@
|
|
||||||
|
|
||||||
fonts/Inter.ttf:
|
|
||||||
mkdir -p fonts
|
|
||||||
curl -fsSL -m 300 -o $@.tmp '$(INTER_URL)'
|
|
||||||
$(verify)
|
|
||||||
|
|
||||||
$(JBM_ZIP):
|
|
||||||
mkdir -p fonts
|
|
||||||
curl -fsSL -m 300 -o $@.tmp '$(JBM_URL)'
|
|
||||||
$(verify)
|
|
||||||
|
|
||||||
fonts/JetBrainsMono.ttf: | $(JBM_ZIP)
|
|
||||||
unzip -p $(JBM_ZIP) 'fonts/variable/JetBrainsMono[[]wght].ttf' > $@.tmp
|
|
||||||
$(verify)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(LIGHT) $(DARK) $(IMAGES)/*.svg.tmp
|
|
||||||
|
|
||||||
distclean: clean
|
|
||||||
rm -f $(FONTS) fonts/*.tmp $(JBM_ZIP)
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Feature slideshow of the home page
|
|
||||||
|
|
||||||
`make` builds `docs/images/json-light.svg` and `json-dark.svg`, the animated feature tour on the home page. They replace
|
|
||||||
`json.gif` there. All text is outlined, so the SVGs need no fonts. Material's theme toggle cannot be seen from an
|
|
||||||
`<img>`, so the home page includes both files (`#only-light`, `#only-dark`) and Material shows the one that matches.
|
|
||||||
|
|
||||||
The slides are in [`slides.md`](slides.md): one `# Title` per slide (`{badge: TEXT}` at the end adds a red chip), `- `
|
|
||||||
bullets, and one fenced code block. `build.py` adds the title and closing slides around them, so the slide count follows
|
|
||||||
the file. Every snippet should compile against the current `develop`. Use a non-breaking space to keep words together
|
|
||||||
("16 bytes").
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make # download missing fonts, build both SVGs
|
|
||||||
make SECONDS=12
|
|
||||||
make clean # remove the SVGs; make distclean also removes the fonts
|
|
||||||
```
|
|
||||||
|
|
||||||
Run it from this directory. It needs [uv](https://docs.astral.sh/uv/), plus curl, unzip and shasum for the fonts:
|
|
||||||
`build.py` declares its dependencies inline (PEP 723, pinned, so the output stays byte-identical) and `uv run --script`
|
|
||||||
provides them. `fonts/` is git-ignored: Inter (variable, Google Fonts repository) and JetBrains Mono 2.304 (variable)
|
|
||||||
are downloaded from pinned URLs and checked against their SHA-256; a changed upstream file stops the build.
|
|
||||||
`./build.py OUT.svg [seconds] [auto|light|dark]` works directly too. A mistake in `slides.md` stops the build with
|
|
||||||
`slides.md:LINE` and leaves the old SVGs in place.
|
|
||||||
|
|
||||||
Layout: title 48 px, bullets 24 px, code 19 px; the build warns when a slide's code would drop below 18 px. Syntax colours
|
|
||||||
are the `code-*` tokens of the brand (also used by `css/brand.css`). A CSS animation dissolves each slide over the
|
|
||||||
previous one and fills the progress bar; with reduced motion only the first slide shows.
|
|
||||||
@@ -1,461 +0,0 @@
|
|||||||
#!/usr/bin/env -S uv run --script
|
|
||||||
# /// script
|
|
||||||
# requires-python = ">=3.9"
|
|
||||||
# dependencies = ["fonttools==4.67.0", "uharfbuzz==0.57.0"]
|
|
||||||
# ///
|
|
||||||
"""Animated SVG slideshow prototype for nlohmann/json, all text outlined (harfbuzz shaping, glyphs as <path> + <use>).
|
|
||||||
|
|
||||||
Usage: uv run --script build.py OUT.svg [seconds-per-slide] [auto|light|dark]
|
|
||||||
|
|
||||||
auto (the default) follows prefers-color-scheme. light and dark fix the colours, for pages with their own theme
|
|
||||||
toggle (MkDocs Material), which an <img> cannot see.
|
|
||||||
|
|
||||||
The pinned versions keep the output byte-identical; a different harfbuzz may shape slightly differently.
|
|
||||||
"""
|
|
||||||
import re, sys, os
|
|
||||||
import uharfbuzz as hb
|
|
||||||
from fontTools.pens.svgPathPen import SVGPathPen
|
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
FONTS = os.path.join(HERE, "fonts")
|
|
||||||
W, H = 1200, 675
|
|
||||||
TOTAL = 0 # number of slides, set once slides.md is read (for the "02 / 20" labels and the progress segments)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- glyphs
|
|
||||||
|
|
||||||
class Face:
|
|
||||||
def __init__(self, file, key, feats=None):
|
|
||||||
blob = hb.Blob.from_file_path(os.path.join(FONTS, file))
|
|
||||||
self.face = hb.Face(blob)
|
|
||||||
self.upem = self.face.upem
|
|
||||||
self.key = key
|
|
||||||
self.feats = feats or {}
|
|
||||||
|
|
||||||
def font(self, var):
|
|
||||||
f = hb.Font(self.face)
|
|
||||||
f.set_variations(var)
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
INTER = Face("Inter.ttf", "i", {"cv11": True, "ss03": True, "kern": True})
|
|
||||||
MONO = Face("JetBrainsMono.ttf", "m", {"calt": False, "liga": False})
|
|
||||||
|
|
||||||
GLYPHS = {} # (face, var, gid) -> id
|
|
||||||
DEFS = []
|
|
||||||
|
|
||||||
|
|
||||||
def gid_ref(face, font, var, gid):
|
|
||||||
k = (face.key, tuple(sorted(var.items())), gid)
|
|
||||||
if k not in GLYPHS:
|
|
||||||
pen = SVGPathPen(None, ntos=lambda v: str(round(v)))
|
|
||||||
font.draw_glyph_with_pen(gid, pen)
|
|
||||||
d = pen.getCommands()
|
|
||||||
gidname = f"g{len(GLYPHS):x}"
|
|
||||||
GLYPHS[k] = gidname if d else None
|
|
||||||
if d:
|
|
||||||
DEFS.append(f'<path id="{gidname}" d="{d}"/>')
|
|
||||||
return GLYPHS[k]
|
|
||||||
|
|
||||||
|
|
||||||
def shape(face, text, wght, size, track=0.0):
|
|
||||||
if not text:
|
|
||||||
return [], 0, face.upem
|
|
||||||
var = {"wght": wght}
|
|
||||||
if face is INTER:
|
|
||||||
var["opsz"] = max(14, min(32, size))
|
|
||||||
font = face.font(var)
|
|
||||||
buf = hb.Buffer()
|
|
||||||
buf.add_str(text)
|
|
||||||
buf.guess_segment_properties()
|
|
||||||
hb.shape(font, buf, face.feats)
|
|
||||||
out, x = [], 0
|
|
||||||
tr = track * face.upem
|
|
||||||
for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
|
|
||||||
out.append((gid_ref(face, font, var, info.codepoint), x + pos.x_offset, pos.y_offset))
|
|
||||||
x += pos.x_advance + tr
|
|
||||||
return out, x - tr, face.upem
|
|
||||||
|
|
||||||
|
|
||||||
def width(text, face=INTER, wght=400, size=16, track=0.0):
|
|
||||||
_, w, upem = shape(face, text, wght, size, track)
|
|
||||||
return w * size / upem
|
|
||||||
|
|
||||||
|
|
||||||
def run(text, x, y, size, wght=400, cls="tx", face=INTER, track=0.0):
|
|
||||||
"""One line of text, baseline at y. Returns (svg, width_px)."""
|
|
||||||
glyphs, w, upem = shape(face, text, wght, size, track)
|
|
||||||
s = size / upem
|
|
||||||
uses = "".join(f'<use href="#{g}" x="{gx}"' + (f' y="{-gy}"' if gy else "") + "/>"
|
|
||||||
for g, gx, gy in glyphs if g)
|
|
||||||
return (f'<g class="{cls}" transform="translate({x:g} {y:g}) scale({s:.6g} {-s:.6g})">{uses}</g>', w * s)
|
|
||||||
|
|
||||||
|
|
||||||
def runs(parts, x, y, size, face=INTER, track=0.0):
|
|
||||||
"""Several (text, wght, cls) pieces on one line."""
|
|
||||||
out = []
|
|
||||||
for text, wght, cls in parts:
|
|
||||||
svg, w = run(text, x, y, size, wght, cls, face, track)
|
|
||||||
out.append(svg)
|
|
||||||
x += w + track * size
|
|
||||||
return "".join(out), x
|
|
||||||
|
|
||||||
|
|
||||||
def wrap(text, maxw, size, wght=400):
|
|
||||||
lines = _wrap(text, maxw, size, wght)
|
|
||||||
# no one-word widow: pull the last word of the previous line down if it still fits
|
|
||||||
if len(lines) > 1 and " " not in lines[-1] and " " in lines[-2]:
|
|
||||||
head, last = lines[-2].rsplit(" ", 1)
|
|
||||||
if width(last + " " + lines[-1], size=size, wght=wght) <= maxw:
|
|
||||||
lines[-2:] = [head, last + " " + lines[-1]]
|
|
||||||
return lines
|
|
||||||
|
|
||||||
|
|
||||||
def _wrap(text, maxw, size, wght=400):
|
|
||||||
lines, cur = [], ""
|
|
||||||
for word in text.split(" "):
|
|
||||||
t = (cur + " " + word).strip()
|
|
||||||
if cur and width(t, size=size, wght=wght) > maxw:
|
|
||||||
lines.append(cur)
|
|
||||||
cur = word
|
|
||||||
else:
|
|
||||||
cur = t
|
|
||||||
return lines + [cur]
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- pieces
|
|
||||||
|
|
||||||
|
|
||||||
def wordmark(x, y, size, on="page"):
|
|
||||||
"""The wordmark with hand kerning around the slash (see the logo in the brand kit). Below 32 px it switches to the
|
|
||||||
small optical master (nlohmann 250 / slash 450), because Thin falls apart at footer size."""
|
|
||||||
a, sl = ("wm-a", "wm-s") if on == "page" else ("on-a", "on-s")
|
|
||||||
small = size < 32
|
|
||||||
w = (250, 450, 800) if small else (100, 400, 800)
|
|
||||||
k = (0.004, 0.024) if small else (-0.006, 0.012)
|
|
||||||
out = []
|
|
||||||
for i, (t, wg, c) in enumerate([("nlohmann", w[0], a), ("/", w[1], sl), ("json", w[2], a)]):
|
|
||||||
svg, wd = run(t, x, y, size, wg, c, track=-0.02)
|
|
||||||
out.append(svg)
|
|
||||||
x += wd - 0.02 * size + (k[i] * size if i < 2 else 0)
|
|
||||||
return "".join(out), x
|
|
||||||
|
|
||||||
|
|
||||||
KW = {"using", "auto", "return", "const", "struct", "for", "override"}
|
|
||||||
TYPES = {"int", "void", "bool", "double", "char", "float"}
|
|
||||||
CONSTANTS = {"true", "false", "nullptr"}
|
|
||||||
TOKEN = re.compile(r'(//.*$)|(R"\(.*?\)"|R"\(.*$|"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)\')|(<[\w/.]+>(?=\s*$))|(#\w+)'
|
|
||||||
r'|(\b\d+(?:\.\d+)?\b)|(\b[A-Za-z_]\w*\b)|(\s+)|(.)')
|
|
||||||
|
|
||||||
|
|
||||||
def code_block(lines, x, y, size, lh):
|
|
||||||
"""Lines of C++; a raw string literal may span lines."""
|
|
||||||
out, in_raw = [], False
|
|
||||||
for line in lines:
|
|
||||||
if line:
|
|
||||||
svg, in_raw = code_line(line, x, y, size, in_raw)
|
|
||||||
out.append(svg)
|
|
||||||
y += lh
|
|
||||||
else:
|
|
||||||
y += lh * BLANK
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
BLANK = 0.55 # an empty code line is a paragraph break, not a full line
|
|
||||||
|
|
||||||
|
|
||||||
def code_height(lines, size, lh):
|
|
||||||
n_full = sum(1 for l in lines if l)
|
|
||||||
n_blank = len(lines) - n_full
|
|
||||||
return lh * (n_full - 1) + lh * BLANK * n_blank + size
|
|
||||||
|
|
||||||
|
|
||||||
def code_line(line, x, y, size, in_raw=False):
|
|
||||||
if line.startswith("$ "):
|
|
||||||
return runs([("$ ", 400, "tx3"), (line[2:], 400, "tx")], x, y, size, MONO)[0], False
|
|
||||||
out, cx = [], x
|
|
||||||
if in_raw:
|
|
||||||
end = line.find(')"')
|
|
||||||
head = line if end < 0 else line[:end + 2]
|
|
||||||
svg, w = run(head, cx, y, size, 400, "str", MONO)
|
|
||||||
out.append(svg)
|
|
||||||
if end < 0:
|
|
||||||
return "".join(out), True
|
|
||||||
cx += w
|
|
||||||
line = line[end + 2:]
|
|
||||||
in_raw = False
|
|
||||||
for m in TOKEN.finditer(line):
|
|
||||||
com, st, inc, pp, num, ident, ws, other = m.groups()
|
|
||||||
t = m.group(0)
|
|
||||||
if st and st.startswith('R"(') and not st.endswith(')"'):
|
|
||||||
in_raw = True
|
|
||||||
face, wght, cls = MONO, 400, "tx"
|
|
||||||
if com:
|
|
||||||
cls = "com" # upright: JetBrains Mono Italic's cursive f reads as a florin at README size
|
|
||||||
elif st or inc:
|
|
||||||
cls = "str"
|
|
||||||
elif pp:
|
|
||||||
cls = "pp"
|
|
||||||
elif ident in KW:
|
|
||||||
wght, cls = 600, "kw"
|
|
||||||
elif ident in TYPES:
|
|
||||||
cls = "ty"
|
|
||||||
elif num or ident in CONSTANTS:
|
|
||||||
cls = "num"
|
|
||||||
elif ident and line[m.end():m.end() + 1] == "(":
|
|
||||||
cls = "fn" # a call or definition: name directly followed by "("
|
|
||||||
svg, w = run(t, cx, y, size, wght, cls, face)
|
|
||||||
if not ws:
|
|
||||||
out.append(svg)
|
|
||||||
cx += w
|
|
||||||
return "".join(out), in_raw
|
|
||||||
|
|
||||||
|
|
||||||
SEG, SEG_GAP = 20, 5
|
|
||||||
|
|
||||||
|
|
||||||
def progress(idx, n_proto, y):
|
|
||||||
"""TOTAL segments at the bottom right; earlier slides full, current one fills over the slide's time."""
|
|
||||||
seg, gap = SEG, SEG_GAP
|
|
||||||
x0 = W - 64 - TOTAL * seg - (TOTAL - 1) * gap
|
|
||||||
out = []
|
|
||||||
for k in range(TOTAL):
|
|
||||||
x = x0 + k * (seg + gap)
|
|
||||||
out.append(f'<rect class="seg" x="{x}" y="{y}" width="{seg}" height="3" rx="1.5"/>')
|
|
||||||
if k < idx:
|
|
||||||
out.append(f'<rect class="seg-on" x="{x}" y="{y}" width="{seg}" height="3" rx="1.5"/>')
|
|
||||||
elif k == idx:
|
|
||||||
out.append(f'<rect class="seg-on fill" x="{x}" y="{y}" width="{seg}" height="3" rx="1.5"/>')
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def pattern_def():
|
|
||||||
tile = [("{", 14, 34), ("]", 92, 22), (":", 136, 60), ("[", 52, 82), (",", 118, 112),
|
|
||||||
("}", 20, 140), (":", 78, 150), ("{", 140, 158)]
|
|
||||||
glyphs = "".join(run(c, x, y, 26, 600, "pat", MONO)[0] for c, x, y in tile)
|
|
||||||
return f'<pattern id="pat" width="160" height="160" patternUnits="userSpaceOnUse">{glyphs}</pattern>'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- slides
|
|
||||||
|
|
||||||
|
|
||||||
CODE_SIZES = []
|
|
||||||
SLOGAN = "What if JSON was part of modern C++?"
|
|
||||||
|
|
||||||
|
|
||||||
def band_bg():
|
|
||||||
"""Hero gradient plus the syntax pattern, faded out towards the text side (left)."""
|
|
||||||
return ('<rect width="1200" height="675" fill="url(#hero)"/>'
|
|
||||||
'<rect width="1200" height="675" fill="url(#pat)" mask="url(#patfade)"/>')
|
|
||||||
|
|
||||||
|
|
||||||
def footer_band(idx, n, left=None):
|
|
||||||
out = []
|
|
||||||
if left:
|
|
||||||
out.append(run(left, 64, FOOT_Y, 20, 600, "on-tx")[0])
|
|
||||||
out.append(progress(idx, n, FOOT_Y - 7.5).replace('class="seg"', 'class="seg on-band"').replace('class="seg-on', 'class="seg-on on-band'))
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
FOOT_Y = 640 # one footer baseline on every slide: wordmark or URL left, progress right
|
|
||||||
|
|
||||||
|
|
||||||
def slide_title(idx, n):
|
|
||||||
out = [band_bg()]
|
|
||||||
x = 96
|
|
||||||
label = "VERSION 3.13.0"
|
|
||||||
lw = width(label, MONO, 500, 15, 0.04)
|
|
||||||
out.append(f'<rect class="chip" x="{x}" y="186" width="{lw + 28:.1f}" height="34" rx="4"/>')
|
|
||||||
out.append(run(label, x + 14, 208.5, 15, 500, "on-tx", MONO, 0.04)[0])
|
|
||||||
out.append(wordmark(x - 6, 336, 112, on="band")[0])
|
|
||||||
out.append(runs([("// ", 400, "w70"), (SLOGAN, 400, "on-s")], x, 404, 26, MONO)[0])
|
|
||||||
out.append(footer_band(idx, n, "github.com/nlohmann/json"))
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def slide_feature(idx, n, title, bullets, code, badge=None):
|
|
||||||
out = ['<rect class="bg" width="1200" height="675"/>']
|
|
||||||
x, top, bottom = 64, 136, 596
|
|
||||||
# title row: 48/800, -3 %
|
|
||||||
svg, tw = run(title, x - 2, 98, 48, 800, "tx", track=-0.03)
|
|
||||||
out.append(svg)
|
|
||||||
if badge:
|
|
||||||
cx = x + tw + 16
|
|
||||||
cw = width(badge, MONO, 600, 13, 0.06) + 18
|
|
||||||
out.append(f'<rect class="red" x="{cx:.1f}" y="68.5" width="{cw:.1f}" height="26" rx="4"/>')
|
|
||||||
out.append(run(badge, cx + 9, 86.2, 13, 600, "on-tx", MONO, 0.06)[0])
|
|
||||||
# code panel: fixed left edge, fixed width, fixed top on every slide; only its height follows the code
|
|
||||||
pad, lf = 28, 1.45
|
|
||||||
px, pw = 488, 1136 - 488
|
|
||||||
em = max(width(l, MONO, 400, 1) for l in code)
|
|
||||||
cs = 19.0
|
|
||||||
while cs > 14 and (em * cs > pw - 2 * pad or code_height(code, cs, lf * cs) > bottom - top - 2 * pad):
|
|
||||||
cs -= 0.5
|
|
||||||
CODE_SIZES.append((title, cs))
|
|
||||||
if cs < 18:
|
|
||||||
print(f" warning: {title!r} code at {cs} px ({em * 19:.0f} px wide at 19 px)", file=sys.stderr)
|
|
||||||
lh = lf * cs
|
|
||||||
ph = max(2 * pad + code_height(code, cs, lh), 240)
|
|
||||||
out.append(f'<rect class="panel" x="{px}" y="{top}" width="{pw}" height="{ph:.0f}" rx="6"/>')
|
|
||||||
first = top + pad + cs * 0.78
|
|
||||||
out.append(code_block(code, px + pad, first, cs, lh))
|
|
||||||
# bullets: 24/400 on 34, first baseline level with the first code baseline
|
|
||||||
colw = px - x - 40
|
|
||||||
by = first
|
|
||||||
for b in bullets:
|
|
||||||
out.append(f'<rect class="blk" x="{x}" y="{by - 10.6:.1f}" width="8" height="8" rx="1.5"/>')
|
|
||||||
for line in wrap(b, colw - 24, 24):
|
|
||||||
out.append(run(line, x + 24, by, 24, 400, "tx2")[0])
|
|
||||||
by += 34
|
|
||||||
by += 16
|
|
||||||
# footer: small-master wordmark left, progress right, no rule, no numeral
|
|
||||||
out.append(wordmark(64, FOOT_Y, 22)[0])
|
|
||||||
out.append(progress(idx, n, FOOT_Y - 7.5))
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def slide_closing(idx, n):
|
|
||||||
out = [band_bg()]
|
|
||||||
x = 96
|
|
||||||
out.append(wordmark(x - 4, 146, 80, on="band")[0])
|
|
||||||
out.append(runs([("// ", 400, "w70"), (SLOGAN, 400, "on-s")], x, 200, 22, MONO)[0])
|
|
||||||
info = [
|
|
||||||
("{", None),
|
|
||||||
(' "copyright": ', "(c) 2013-2026 Niels Lohmann"),
|
|
||||||
(' "license": ', "MIT"),
|
|
||||||
(' "docs": ', "json.nlohmann.me"),
|
|
||||||
(' "github": ', "github.com/nlohmann/json"),
|
|
||||||
(' "sponsor": ', "github.com/sponsors/nlohmann"),
|
|
||||||
(' "mail": ', "mail@nlohmann.me"),
|
|
||||||
("}", None),
|
|
||||||
]
|
|
||||||
cs, lh, pad = 21, 33, 28
|
|
||||||
py = 244
|
|
||||||
pw = max(width(k + '"' + (v or "") + '",', MONO, 400, cs) for k, v in info) + 2 * pad
|
|
||||||
out.append(f'<rect class="on-panel" x="{x}" y="{py}" width="{pw:.0f}" height="{2 * pad + lh * (len(info) - 1) + cs:.0f}" rx="6"/>')
|
|
||||||
y = py + pad + cs * 0.8
|
|
||||||
for k, (key, val) in enumerate(info):
|
|
||||||
if val is None:
|
|
||||||
out.append(run(key, x + pad, y, cs, 400, "on-tx", MONO)[0])
|
|
||||||
else:
|
|
||||||
comma = "," if k < len(info) - 2 else ""
|
|
||||||
out.append(runs([(key, 400, "on-tx"), (f'"{val}"', 400, "on-s"), (comma, 400, "on-tx")], x + pad, y, cs, MONO)[0])
|
|
||||||
y += lh
|
|
||||||
out.append(footer_band(idx, n))
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def load_slides(path):
|
|
||||||
"""Feature slides from Markdown: '# Title {badge: TEXT}', '- bullet' lines, one fenced code block."""
|
|
||||||
text = re.sub(r"<!--.*?-->", "", open(path).read(), flags=re.S)
|
|
||||||
slides, cur, in_code = [], None, False
|
|
||||||
for no, line in enumerate(text.split("\n"), 1):
|
|
||||||
if in_code:
|
|
||||||
if line.startswith("```"):
|
|
||||||
in_code = False
|
|
||||||
else:
|
|
||||||
cur["code"].append(line)
|
|
||||||
elif line.startswith("```"):
|
|
||||||
in_code = True
|
|
||||||
elif line.startswith("# "):
|
|
||||||
title, badge = line[2:].strip(), None
|
|
||||||
m = re.search(r"\s*\{badge:\s*(.*?)\}$", title)
|
|
||||||
if m:
|
|
||||||
title, badge = title[:m.start()], m.group(1)
|
|
||||||
cur = {"title": title, "bullets": [], "code": [], "badge": badge}
|
|
||||||
slides.append(cur)
|
|
||||||
elif line.startswith("- ") and cur:
|
|
||||||
cur["bullets"].append(line[2:].strip())
|
|
||||||
elif line[:1] in " \t" and line.strip() and cur and cur["bullets"]:
|
|
||||||
cur["bullets"][-1] += " " + line.strip()
|
|
||||||
elif line.strip():
|
|
||||||
raise SystemExit(f"{path}:{no}: unexpected line: {line!r}")
|
|
||||||
for sl in slides:
|
|
||||||
while sl["code"] and not sl["code"][-1].strip():
|
|
||||||
sl["code"].pop()
|
|
||||||
if not sl["code"]:
|
|
||||||
raise SystemExit(f"{path}: slide {sl['title']!r} has no code block")
|
|
||||||
return slides
|
|
||||||
|
|
||||||
|
|
||||||
FEATURES = load_slides(os.path.join(HERE, "slides.md"))
|
|
||||||
|
|
||||||
|
|
||||||
def _feature(f):
|
|
||||||
return lambda i, n: slide_feature(i, n, f["title"], f["bullets"], f["code"], f["badge"])
|
|
||||||
|
|
||||||
|
|
||||||
SLIDES = [slide_title] + [_feature(f) for f in FEATURES] + [slide_closing]
|
|
||||||
TOTAL = len(SLIDES)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- assemble
|
|
||||||
|
|
||||||
|
|
||||||
def build(sec):
|
|
||||||
n = len(SLIDES)
|
|
||||||
T = n * sec
|
|
||||||
fade = 0.6
|
|
||||||
a, b, c = fade / T * 100, sec / T * 100, (sec + fade) / T * 100
|
|
||||||
def cls(i):
|
|
||||||
return "s s0" if i == 0 else "s sl" if i == n - 1 else "s"
|
|
||||||
bodies = [f'<g class="{cls(i)}" style="animation-delay:{i * sec:g}s">{fn(i, n)}</g>' for i, fn in enumerate(SLIDES)]
|
|
||||||
css = f"""
|
|
||||||
.bg{{fill:#fff}} .tx{{fill:#0f1419}} .tx2{{fill:#3d444f}} .tx3{{fill:#8a919c}} .com{{fill:#646d7a}}
|
|
||||||
.bl{{fill:#1747a6}} .blk{{fill:#1747a6}} .kw{{fill:#1747a6}} .ty{{fill:#0d6e72}} .fn{{fill:#6b3fb8}} .str{{fill:#23793a}} .num{{fill:#b04a07}} .pp{{fill:#a3247a}}
|
|
||||||
.panel{{fill:#f5f7fa;stroke:#e3e6eb;stroke-width:1}} .rule{{stroke:#e3e6eb;stroke-width:1}}
|
|
||||||
.wm-a{{fill:#0f1419}} .wm-s{{fill:#1747a6}}
|
|
||||||
.on-a,.on-tx{{fill:#fff}} .red{{fill:#d93a3f}} .on-panel{{fill:#fff;fill-opacity:.08;stroke:#fff;stroke-opacity:.16}} .on-s{{fill:#bcd3fb}} .w70{{fill:#fff;fill-opacity:.7}} .on-tx2{{fill:#dbe6fb}} .chip{{fill:#fff;fill-opacity:.14}}
|
|
||||||
.pat{{fill:#fff;fill-opacity:.09}}
|
|
||||||
.h1{{stop-color:#1d55c4}} .h2{{stop-color:#123a8a}}
|
|
||||||
.seg{{fill:#e3e6eb}} .seg-on{{fill:#1747a6}}
|
|
||||||
.seg.on-band{{fill:#fff;fill-opacity:.25}} .seg-on.on-band{{fill:#fff;fill-opacity:1}}
|
|
||||||
@media (prefers-color-scheme:dark){{
|
|
||||||
.bg{{fill:#0f1218}} .tx,.wm-a{{fill:#f2f4f7}} .tx2{{fill:#c3c8d1}} .tx3{{fill:#7b828e}} .com{{fill:#8b929e}}
|
|
||||||
.bl,.blk,.wm-s,.seg-on,.kw{{fill:#7aa7f5}} .ty{{fill:#4fd1c5}} .fn{{fill:#c4a3ff}} .str{{fill:#7ed491}} .num{{fill:#f5a65b}} .pp{{fill:#f48fc8}}
|
|
||||||
.panel{{fill:#171b23;stroke:#272c36}} .rule{{stroke:#272c36}} .seg{{fill:#272c36}}
|
|
||||||
.red{{fill:#c8383d}} .h1{{stop-color:#163f93}} .h2{{stop-color:#0c2556}}
|
|
||||||
}}
|
|
||||||
.s{{opacity:0;animation:show {T:g}s linear infinite backwards}}
|
|
||||||
.fill{{transform-box:fill-box;transform-origin:left;animation:fill {T:g}s linear infinite backwards;animation-delay:inherit}}
|
|
||||||
.s0{{animation-name:show0}} .sl{{animation-name:showl}}
|
|
||||||
@keyframes show{{0%{{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)}} {a:.4f}%{{opacity:1}} {c:.4f}%{{opacity:1}} {c + .01:.4f}%{{opacity:0}} 100%{{opacity:0}}}}
|
|
||||||
@keyframes show0{{0%{{opacity:1}} {c:.4f}%{{opacity:1}} {c + .01:.4f}%{{opacity:0}} 100%{{opacity:0}}}}
|
|
||||||
@keyframes showl{{0%{{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)}} {a:.4f}%{{opacity:1}} {b:.4f}%{{opacity:1}} {c:.4f}%{{opacity:0}} 100%{{opacity:0}}}}
|
|
||||||
@keyframes fill{{0%{{transform:scaleX(0)}} {b:.4f}%{{transform:scaleX(1)}} 100%{{transform:scaleX(1)}}}}
|
|
||||||
@media (prefers-reduced-motion:reduce){{.s,.fill{{animation:none}} .s:first-of-type{{opacity:1}}}}
|
|
||||||
"""
|
|
||||||
css = re.sub(r"\n\s*", "", css)
|
|
||||||
pat = pattern_def()
|
|
||||||
return (f'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 {W} {H}" width="{W}" height="{H}" role="img">'
|
|
||||||
f'<title>JSON for Modern C++: feature slideshow (animated, {n} slides, {sec:g} s each)</title>'
|
|
||||||
f'<style>{css}</style>'
|
|
||||||
f'<defs>{"".join(DEFS)}{pat}'
|
|
||||||
f'<linearGradient id="hero" gradientTransform="rotate(70 .5 .5)"><stop class="h1" offset="0"/><stop class="h2" offset="1"/></linearGradient>'
|
|
||||||
f'<linearGradient id="fadeg" x1="0" x2="1" y1="0" y2="0"><stop offset=".25" stop-color="#fff" stop-opacity="0"/><stop offset=".85" stop-color="#fff" stop-opacity="1"/></linearGradient>'
|
|
||||||
f'<mask id="patfade" maskUnits="userSpaceOnUse" x="0" y="0" width="{W}" height="{H}"><rect width="{W}" height="{H}" fill="url(#fadeg)"/></mask></defs>'
|
|
||||||
f'<rect class="bg" width="{W}" height="{H}"/>'
|
|
||||||
f'{"".join(bodies)}</svg>')
|
|
||||||
|
|
||||||
|
|
||||||
def fix_scheme(svg, scheme):
|
|
||||||
"""Drop the dark-mode block (light) or apply it unconditionally (dark)."""
|
|
||||||
if scheme == "auto":
|
|
||||||
return svg
|
|
||||||
start = svg.index("@media (prefers-color-scheme:dark){")
|
|
||||||
i, depth = svg.index("{", start), 0
|
|
||||||
for j in range(i, len(svg)):
|
|
||||||
depth += {"{": 1, "}": -1}.get(svg[j], 0)
|
|
||||||
if depth == 0:
|
|
||||||
break
|
|
||||||
inner = svg[i + 1:j] if scheme == "dark" else ""
|
|
||||||
return svg[:start] + inner + svg[j + 1:]
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
out = sys.argv[1]
|
|
||||||
sec = float(sys.argv[2]) if len(sys.argv) > 2 else 15
|
|
||||||
scheme = sys.argv[3] if len(sys.argv) > 3 else "auto"
|
|
||||||
if scheme not in ("auto", "light", "dark"):
|
|
||||||
sys.exit(f"unknown colour scheme {scheme!r}: use auto, light or dark")
|
|
||||||
svg = fix_scheme(build(sec), scheme)
|
|
||||||
# defs are appended while slides render, so build the slides first (done inside build via f-string order)
|
|
||||||
open(out, "w").write(svg)
|
|
||||||
print(out, len(svg.encode()) // 1024, "KB,", len(GLYPHS), "glyphs; code px:", " ".join(f"{c:g}" for _, c in CODE_SIZES))
|
|
||||||
@@ -1,412 +0,0 @@
|
|||||||
<!--
|
|
||||||
Feature slides for the animated slideshow. build.py adds the title slide before them and the closing slide after them.
|
|
||||||
|
|
||||||
# Title one slide per heading; `{badge: TEXT}` at the end adds a red chip
|
|
||||||
- bullet bullets; a long bullet may continue on indented lines
|
|
||||||
```cpp ... ``` the code shown on the right (a line starting with `$ ` is a shell command)
|
|
||||||
-->
|
|
||||||
|
|
||||||
# Trivial integration
|
|
||||||
|
|
||||||
- single header file
|
|
||||||
- no build system needed
|
|
||||||
- vanilla C++11
|
|
||||||
- works with GCC 4.8+, Clang 3.4+ and MSVC 2015+
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
// for convenience
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
// a JSON pretty-printer in 3 lines
|
|
||||||
json j;
|
|
||||||
std::cin >> j;
|
|
||||||
std::cout << std::setw(4) << j << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ g++ pretty.cpp -std=c++11 -o pretty
|
|
||||||
```
|
|
||||||
|
|
||||||
# First-class datatype
|
|
||||||
|
|
||||||
- write JSON like any other C++ literal
|
|
||||||
- initializer lists become arrays and objects
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
json j = {
|
|
||||||
{"pi", 3.141},
|
|
||||||
{"happy", true},
|
|
||||||
{"name", "Niels"},
|
|
||||||
{"nothing", nullptr},
|
|
||||||
{"answer", {
|
|
||||||
{"everything", 42}
|
|
||||||
}},
|
|
||||||
{"list", {1, 0, 2}},
|
|
||||||
{"object", {
|
|
||||||
{"currency", "USD"},
|
|
||||||
{"value", 42.99}
|
|
||||||
}}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
# Array construction
|
|
||||||
|
|
||||||
- arrays work the way you expect
|
|
||||||
- adding to a null value makes it an array
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// create null value
|
|
||||||
json j;
|
|
||||||
|
|
||||||
// add via member function
|
|
||||||
j.push_back("Hello");
|
|
||||||
j.emplace_back("World");
|
|
||||||
|
|
||||||
// add via += operator
|
|
||||||
j += false;
|
|
||||||
|
|
||||||
// add via index
|
|
||||||
j[2] = 42.3;
|
|
||||||
|
|
||||||
// gaps are filled with null
|
|
||||||
j[4] = "World";
|
|
||||||
```
|
|
||||||
|
|
||||||
# Object construction
|
|
||||||
|
|
||||||
- objects work the way you expect
|
|
||||||
- adding to a null value makes it an object
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// create null value
|
|
||||||
json j;
|
|
||||||
|
|
||||||
// add via member function
|
|
||||||
j.emplace("key2", "value");
|
|
||||||
j.push_back({"key", "value"});
|
|
||||||
|
|
||||||
// add via += operator
|
|
||||||
j += {"another key", false};
|
|
||||||
|
|
||||||
// add via key
|
|
||||||
j["pi"] = 3.1415926;
|
|
||||||
j["answer"]["everything"] = 42;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Container operations
|
|
||||||
|
|
||||||
- use JSON like an STL container
|
|
||||||
- satisfies the ReversibleContainer requirements
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// container empty?
|
|
||||||
j.empty();
|
|
||||||
|
|
||||||
// current number of elements
|
|
||||||
j.size();
|
|
||||||
|
|
||||||
// maximal number of elements
|
|
||||||
j.max_size();
|
|
||||||
|
|
||||||
// swap elements
|
|
||||||
j.swap(j2);
|
|
||||||
std::swap(j, j2);
|
|
||||||
|
|
||||||
// clear a container
|
|
||||||
j.clear();
|
|
||||||
```
|
|
||||||
|
|
||||||
# STL containers
|
|
||||||
|
|
||||||
- switch between STL and JSON containers
|
|
||||||
- conversion works in both directions
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// some STL containers
|
|
||||||
std::vector<int> v;
|
|
||||||
std::unordered_map<std::string, bool> m;
|
|
||||||
|
|
||||||
// automatic transformation
|
|
||||||
json j_array(v);
|
|
||||||
json j_object(m);
|
|
||||||
|
|
||||||
// works both ways
|
|
||||||
auto l = j_array.get<std::list<int>>();
|
|
||||||
auto u = j_object
|
|
||||||
.get<std::map<std::string, bool>>();
|
|
||||||
```
|
|
||||||
|
|
||||||
# Type conversion
|
|
||||||
|
|
||||||
- convert your own types to and from JSON
|
|
||||||
- one macro writes to_json and from_json
|
|
||||||
- or write both functions for full control
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// some user-defined type
|
|
||||||
struct person {
|
|
||||||
std::string name;
|
|
||||||
int age;
|
|
||||||
};
|
|
||||||
|
|
||||||
// defines to_json and from_json
|
|
||||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person,
|
|
||||||
name, age)
|
|
||||||
|
|
||||||
// conversion in action
|
|
||||||
person p {"Ned Flanders", 60};
|
|
||||||
json j = p;
|
|
||||||
// {"age": 60, "name": "Ned Flanders"}
|
|
||||||
|
|
||||||
auto p2 = j.get<person>();
|
|
||||||
```
|
|
||||||
|
|
||||||
# Type deduction
|
|
||||||
|
|
||||||
- types are deduced automatically
|
|
||||||
- member functions tell you the type
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// type deduction
|
|
||||||
json j1 = nullptr;
|
|
||||||
json j2 = false;
|
|
||||||
json j3 = 12.23;
|
|
||||||
json j4 = {1, 2, 3};
|
|
||||||
json j5 = {{"key", "value"}};
|
|
||||||
json j6 = "Hello";
|
|
||||||
|
|
||||||
// quick checks
|
|
||||||
j1.is_null();
|
|
||||||
j2.is_boolean();
|
|
||||||
j3.is_number();
|
|
||||||
j4.is_array();
|
|
||||||
j5.is_object();
|
|
||||||
j6.is_string();
|
|
||||||
```
|
|
||||||
|
|
||||||
# Serialization
|
|
||||||
|
|
||||||
- to a string or a stream
|
|
||||||
- pretty-printing included
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// default serialization
|
|
||||||
auto s1 = j.dump();
|
|
||||||
|
|
||||||
// pretty-printed
|
|
||||||
int indent = 4;
|
|
||||||
std::string s2 = j.dump(indent);
|
|
||||||
|
|
||||||
// to stream
|
|
||||||
std::cout << j << std::endl;
|
|
||||||
|
|
||||||
// pretty-printed
|
|
||||||
std::cout << std::setw(indent) << j << std::endl;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Deserialization
|
|
||||||
|
|
||||||
- parse strings, streams and files
|
|
||||||
- the _json literal puts plain JSON in your code
|
|
||||||
- comments and trailing commas on request
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// from string
|
|
||||||
json j1 = json::parse("[1,null,false]");
|
|
||||||
|
|
||||||
// from file
|
|
||||||
std::ifstream f("example.json");
|
|
||||||
json j2 = json::parse(f);
|
|
||||||
|
|
||||||
// from string literal
|
|
||||||
json j3 = "[3.14159]"_json;
|
|
||||||
|
|
||||||
// from raw string literal
|
|
||||||
json j4 = R"(
|
|
||||||
{"one": 1, "two": 2, "three": [1, 2, 3]}
|
|
||||||
)"_json;
|
|
||||||
```
|
|
||||||
|
|
||||||
# SAX parsing
|
|
||||||
|
|
||||||
- the parser can emit SAX events
|
|
||||||
- process input without building a JSON value
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// define a SAX event processor
|
|
||||||
struct sax_processor : json::json_sax_t
|
|
||||||
{
|
|
||||||
bool boolean(bool val) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// implement other functions...
|
|
||||||
};
|
|
||||||
|
|
||||||
// create object
|
|
||||||
sax_processor my_processor;
|
|
||||||
|
|
||||||
// use it to process input
|
|
||||||
json::sax_parse(input, &my_processor);
|
|
||||||
```
|
|
||||||
|
|
||||||
# Zero-copy views {badge: NEW IN 3.13}
|
|
||||||
|
|
||||||
- parse without building a tree: 16 bytes per value
|
|
||||||
- strings and numbers stay in the source text
|
|
||||||
- materialize a json value only where you need one
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <nlohmann/json_view.hpp>
|
|
||||||
|
|
||||||
// parse into a flat index, not a tree
|
|
||||||
std::ifstream f("big.json");
|
|
||||||
auto doc = nlohmann::json_document::parse(f);
|
|
||||||
|
|
||||||
// read-only views with familiar access
|
|
||||||
auto view = doc.root();
|
|
||||||
double pi = view["pi"].get<double>();
|
|
||||||
for (auto v : view["list"])
|
|
||||||
std::cout << v.get<int>() << '\n';
|
|
||||||
|
|
||||||
// build a json value only where needed
|
|
||||||
json part = view["list"].materialize();
|
|
||||||
```
|
|
||||||
|
|
||||||
# JSON Pointer
|
|
||||||
|
|
||||||
- address any element with a JSON Pointer
|
|
||||||
- flatten a value to pointer/value pairs, and back
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
json j = {"good", "bad", "ugly"};
|
|
||||||
|
|
||||||
// JSON pointer to the 3rd list element
|
|
||||||
auto ptr = json::json_pointer("/2");
|
|
||||||
|
|
||||||
// use JSON pointer to access element
|
|
||||||
j[ptr] = {{"it", "cattivo"}};
|
|
||||||
|
|
||||||
// user-defined string literal
|
|
||||||
j["/2/en"_json_pointer] = "ugly";
|
|
||||||
// ["good","bad",{"en":"ugly","it":"cattivo"}]
|
|
||||||
|
|
||||||
json flat = j.flatten();
|
|
||||||
// {"/0":"good","/1":"bad","/2/en":"ugly",
|
|
||||||
// "/2/it":"cattivo"}
|
|
||||||
|
|
||||||
json j2 = flat.unflatten(); // j == j2
|
|
||||||
```
|
|
||||||
|
|
||||||
# JSON Patches
|
|
||||||
|
|
||||||
- use JSON Patch to describe edit operations
|
|
||||||
- create diffs from two JSON values
|
|
||||||
- or use a JSON Merge Patch that looks like the document
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
json source = R"({"F": "Paris", "D": "Berlin"})"_json;
|
|
||||||
|
|
||||||
// a JSON Patch (RFC 6902)
|
|
||||||
json p1 = R"([
|
|
||||||
{"op": "add", "path": "/GB", "value": "London"}
|
|
||||||
])"_json;
|
|
||||||
json target = source.patch(p1);
|
|
||||||
|
|
||||||
// create a diff from two JSON values
|
|
||||||
json p2 = json::diff(target, source);
|
|
||||||
// [{"op": "remove", "path": "/GB"}]
|
|
||||||
|
|
||||||
// a JSON Merge Patch (RFC 7396)
|
|
||||||
target.merge_patch(R"({"F": null})"_json);
|
|
||||||
// {"D": "Berlin", "GB": "London"}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Binary formats
|
|
||||||
|
|
||||||
- compact binary representations
|
|
||||||
- BJData, BON8, BSON, CBOR, MessagePack and UBJSON
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// a JSON value
|
|
||||||
json j = R"({"compact": true, "schema": 0})"_json;
|
|
||||||
|
|
||||||
// serialize to CBOR
|
|
||||||
std::vector<std::uint8_t> v = json::to_cbor(j);
|
|
||||||
// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61,
|
|
||||||
// 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68,
|
|
||||||
// 0x65, 0x6D, 0x61, 0x00
|
|
||||||
|
|
||||||
// roundtrip
|
|
||||||
json j_from_cbor = json::from_cbor(v);
|
|
||||||
|
|
||||||
// the same for the other formats
|
|
||||||
auto b = json::to_bon8(j);
|
|
||||||
auto m = json::to_msgpack(j);
|
|
||||||
```
|
|
||||||
|
|
||||||
# Algorithms
|
|
||||||
|
|
||||||
- the standard algorithms just work
|
|
||||||
- sort, find, count, merge and more
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// sort JSON containers
|
|
||||||
std::sort(j.begin(), j.end());
|
|
||||||
|
|
||||||
// find element
|
|
||||||
auto pos = std::find(j.begin(), j.end(),
|
|
||||||
json("needle"));
|
|
||||||
|
|
||||||
// count all numbers
|
|
||||||
auto n = std::count_if(j.begin(), j.end(),
|
|
||||||
[](const json& v) { return v.is_number(); });
|
|
||||||
|
|
||||||
// merge two sorted containers
|
|
||||||
std::merge(j1.begin(), j1.end(),
|
|
||||||
j2.begin(), j2.end(),
|
|
||||||
std::back_inserter(j3));
|
|
||||||
```
|
|
||||||
|
|
||||||
# Iterators
|
|
||||||
|
|
||||||
- iterators, const iterators, and reverse iterators
|
|
||||||
- random access for arrays
|
|
||||||
- items() gives you keys and values
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
json j = {1, 2, 3, 4, 5};
|
|
||||||
|
|
||||||
// range for
|
|
||||||
for (auto& el : j)
|
|
||||||
{
|
|
||||||
// print all values
|
|
||||||
std::cerr << el << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
// classical iterators
|
|
||||||
for (auto it = j.begin(); it != j.end(); ++it)
|
|
||||||
{
|
|
||||||
// double all values
|
|
||||||
*it = it->get<int>() * 2;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Structured bindings
|
|
||||||
|
|
||||||
- since C++17, unpack keys and values with structured bindings
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// JSON object
|
|
||||||
auto j = R"({"foo": 23, "bar": 42})"_json;
|
|
||||||
|
|
||||||
// structured bindings
|
|
||||||
for (auto& [key, val] : j.items())
|
|
||||||
{
|
|
||||||
std::cerr << key << ':' << val << '\n';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
#include <nlohmann/detail/view/scan.hpp>
|
#include <nlohmann/detail/view/scan.hpp>
|
||||||
|
#include <nlohmann/detail/view/simd.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -103,12 +104,56 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
|||||||
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
/*!
|
||||||
|
@brief the value of the first 16 digits of a run of digits with a point after
|
||||||
|
int_digits of them (or none, if int_digits >= 16)
|
||||||
|
|
||||||
|
The 17 bytes at p are read at once: the bytes after the point move down by one,
|
||||||
|
then pairs, quadruples, and octets of digits are combined by multiplications
|
||||||
|
(after simdjson). The bytes [p, p + 32) must be readable. Bytes that are not
|
||||||
|
digits (only in a damaged image loaded with image_check::bounds) give a wrong
|
||||||
|
value, but no undefined behavior.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t sixteen_digits(const unsigned char* p, unsigned int_digits) noexcept
|
||||||
|
{
|
||||||
|
#if NLOHMANN_VIEW_NEON
|
||||||
|
const uint8x16_t low = vld1q_u8(p);
|
||||||
|
const uint8x16_t index = vcombine_u8(vcreate_u8(0x0706050403020100u), vcreate_u8(0x0F0E0D0C0B0A0908u));
|
||||||
|
const uint8x16_t before_point = vcltq_u8(index, vdupq_n_u8(static_cast<std::uint8_t>(int_digits)));
|
||||||
|
const uint8x16_t digits = vsubq_u8(vbslq_u8(before_point, low, vextq_u8(low, vld1q_u8(p + 16), 1)), vdupq_n_u8('0'));
|
||||||
|
const uint16x8_t twos = vpaddlq_u8(vmulq_u8(digits, vreinterpretq_u8_u16(vdupq_n_u16(0x010A))));
|
||||||
|
const uint32x4_t fours = vpaddlq_u16(vmulq_u16(twos, vreinterpretq_u16_u32(vdupq_n_u32(0x00010064))));
|
||||||
|
const uint64x2_t eights = vpaddlq_u32(vmulq_u32(fours, vreinterpretq_u32_u64(vdupq_n_u64(0x0000000100002710u))));
|
||||||
|
return (vgetq_lane_u64(eights, 0) * 100000000u) + vgetq_lane_u64(eights, 1);
|
||||||
|
#else
|
||||||
|
// NOLINTBEGIN(portability-simd-intrinsics)
|
||||||
|
const __m128i low = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i high = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + 16))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i before_point = _mm_cmpgt_epi8(_mm_set1_epi8(static_cast<char>(int_digits)), _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15));
|
||||||
|
const __m128i moved = _mm_or_si128(_mm_srli_si128(low, 1), _mm_slli_si128(high, 15));
|
||||||
|
const __m128i digits = _mm_sub_epi8(_mm_or_si128(_mm_and_si128(before_point, low), _mm_andnot_si128(before_point, moved)), _mm_set1_epi8('0'));
|
||||||
|
// (16-bit lanes: SSE2 has no multiply-add of bytes)
|
||||||
|
const __m128i tens = _mm_setr_epi16(10, 1, 10, 1, 10, 1, 10, 1);
|
||||||
|
const __m128i twos = _mm_packs_epi32(_mm_madd_epi16(_mm_unpacklo_epi8(digits, _mm_setzero_si128()), tens),
|
||||||
|
_mm_madd_epi16(_mm_unpackhi_epi8(digits, _mm_setzero_si128()), tens));
|
||||||
|
const __m128i fours = _mm_madd_epi16(twos, _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1));
|
||||||
|
const __m128i eights = _mm_madd_epi16(_mm_packs_epi32(fours, fours), _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1));
|
||||||
|
const auto upper = static_cast<std::uint32_t>(_mm_cvtsi128_si32(eights));
|
||||||
|
const auto lower = static_cast<std::uint32_t>(_mm_cvtsi128_si32(_mm_srli_si128(eights, 4)));
|
||||||
|
// NOLINTEND(portability-simd-intrinsics)
|
||||||
|
return (std::uint64_t{upper} * 100000000u) + lower;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the digits of a float token with at most 19 digits, from its layout
|
@brief the digits of a float token with at most 19 digits, from its layout
|
||||||
|
|
||||||
The digit layout recorded while parsing says where the integer digits, the
|
The digit layout recorded while parsing says where the integer digits, the
|
||||||
fraction digits, and the exponent are, so the digits are read eight at a
|
fraction digits, and the exponent are, so the digits are read eight at a
|
||||||
time without scanning.
|
time without scanning (with SIMD, a token of 16 to 19 digits sixteen at a
|
||||||
|
time).
|
||||||
|
|
||||||
@param[in] p first character of the token
|
@param[in] p first character of the token
|
||||||
@param[in] e end of the token
|
@param[in] e end of the token
|
||||||
@@ -118,15 +163,34 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
|||||||
{
|
{
|
||||||
const bool negative = *p == '-';
|
const bool negative = *p == '-';
|
||||||
p += negative ? 1 : 0;
|
p += negative ? 1 : 0;
|
||||||
std::uint64_t w = parse_upto19(p, int_digits, limit);
|
std::uint64_t w = 0;
|
||||||
p += int_digits;
|
|
||||||
std::int64_t q = 0;
|
std::int64_t q = 0;
|
||||||
if (frac_digits != 0)
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
const unsigned count = int_digits + frac_digits;
|
||||||
|
if (count >= 16 && limit - p >= 32)
|
||||||
{
|
{
|
||||||
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
// (the point, if any, follows the integer digits; the digits after
|
||||||
p += 1 + frac_digits;
|
// the first 16 are at most three)
|
||||||
|
w = sixteen_digits(p, int_digits);
|
||||||
|
for (unsigned i = 16; i < count; ++i)
|
||||||
|
{
|
||||||
|
w = (w * 10) + static_cast<std::uint64_t>(p[i + (i >= int_digits ? 1 : 0)] - '0');
|
||||||
|
}
|
||||||
|
p += count + (frac_digits != 0 ? 1 : 0);
|
||||||
q = -static_cast<std::int64_t>(frac_digits);
|
q = -static_cast<std::int64_t>(frac_digits);
|
||||||
}
|
}
|
||||||
|
else /* NOLINT(readability/braces) */
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
w = parse_upto19(p, int_digits, limit);
|
||||||
|
p += int_digits;
|
||||||
|
if (frac_digits != 0)
|
||||||
|
{
|
||||||
|
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
||||||
|
p += 1 + frac_digits;
|
||||||
|
q = -static_cast<std::int64_t>(frac_digits);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (p != e)
|
if (p != e)
|
||||||
{
|
{
|
||||||
// [eE][+-]digits; huge exponents saturate (the parser rejected
|
// [eE][+-]digits; huge exponents saturate (the parser rejected
|
||||||
@@ -161,12 +225,22 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
|||||||
The result is correctly rounded by the lexer's conversion
|
The result is correctly rounded by the lexer's conversion
|
||||||
(detail::decimal_to_float(): Clinger's fast path where both operands are
|
(detail::decimal_to_float(): Clinger's fast path where both operands are
|
||||||
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
|
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
|
||||||
digits), so it is the value parse() produces.
|
digits), so it is the value parse() produces. Tokens of 16 or more digits go
|
||||||
|
to Eisel-Lemire directly: the fast path takes few of them (none of 17 digits),
|
||||||
|
and its check is a branch that data like GeoJSON coordinates mispredicts.
|
||||||
*/
|
*/
|
||||||
template<typename FloatType>
|
template<typename FloatType>
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
|
||||||
{
|
{
|
||||||
return decimal_to_float<FloatType>(layout_decimal(p, e, int_digits, frac_digits, limit));
|
const float_significand d = layout_decimal(p, e, int_digits, frac_digits, limit);
|
||||||
|
if (int_digits + frac_digits >= 16)
|
||||||
|
{
|
||||||
|
using format = ieee_binary_format<std::numeric_limits<FloatType>::digits>;
|
||||||
|
native_float_t<FloatType> value{};
|
||||||
|
float_from_bits(eisel_lemire<format>(d.exponent, d.w) | (d.negative ? (std::uint64_t{1} << format::sign_bit()) : 0u), value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return decimal_to_float<FloatType>(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value of a float set by an edit: its token (the shortest round-trip
|
/// the value of a float set by an edit: its token (the shortest round-trip
|
||||||
@@ -185,6 +259,80 @@ NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n)
|
|||||||
return float_value<FloatType>(token, n);
|
return float_value<FloatType>(token, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
/// eisel_lemire() for the token w * 10^q of coordinate_float()
|
||||||
|
template<typename Format>
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, Format /*format*/) noexcept
|
||||||
|
{
|
||||||
|
return eisel_lemire<Format>(q, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (binary64) eisel_lemire() without the cases that tokens of 16 or 17
|
||||||
|
digits with 5 or more after the point cannot reach: a value of 0, a
|
||||||
|
subnormal, an infinity, or a tie (q < -4)
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, ieee_binary_format<53> format) noexcept
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(w == 0 || q > -5))
|
||||||
|
{
|
||||||
|
return eisel_lemire<ieee_binary_format<53>>(q, w);
|
||||||
|
}
|
||||||
|
static_cast<void>(format);
|
||||||
|
const int lz = count_leading_zeros(w);
|
||||||
|
const std::uint64_t normalized = w << static_cast<unsigned>(lz);
|
||||||
|
const auto index = static_cast<std::size_t>(2 * (q - pow5_128_smallest_power));
|
||||||
|
const uint128_parts product = full_multiplication(normalized, pow5_128()[index]);
|
||||||
|
std::uint64_t high = product.high;
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu))
|
||||||
|
{
|
||||||
|
// (the lower bits may carry into the result)
|
||||||
|
const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high;
|
||||||
|
high += product.low + second < product.low ? 1u : 0u;
|
||||||
|
}
|
||||||
|
const auto upperbit = static_cast<unsigned>(high >> 63u);
|
||||||
|
std::uint64_t mantissa = high >> (upperbit + 9);
|
||||||
|
auto power2 = static_cast<std::uint64_t>(((217706 * q) >> 16) + 63 + static_cast<std::int64_t>(upperbit) - lz + 1023);
|
||||||
|
mantissa = (mantissa + (mantissa & 1u)) >> 1u;
|
||||||
|
const bool carry = mantissa >= (std::uint64_t{2} << 52u);
|
||||||
|
mantissa = carry ? (std::uint64_t{1} << 52u) : mantissa;
|
||||||
|
power2 += carry ? 1u : 0u;
|
||||||
|
return (mantissa & ~(std::uint64_t{1} << 52u)) | (power2 << 52u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (binary32 and binary64) the value of a float token of the source with
|
||||||
|
16 or 17 digits, a point, and no exponent, the shape of most coordinates
|
||||||
|
written with full precision; whether the token of n has this shape
|
||||||
|
|
||||||
|
These tokens skip the general layout of layout_decimal(): no digits beyond
|
||||||
|
the 17th, no exponent (and Eisel-Lemire directly, as in layout_float()).
|
||||||
|
*/
|
||||||
|
template<typename FloatType>
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE bool coordinate_float(const document_data& d, const node& n, FloatType& value) noexcept
|
||||||
|
{
|
||||||
|
const unsigned int_digits = n.extra & 0xFFu;
|
||||||
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
|
const unsigned count = int_digits + frac_digits;
|
||||||
|
const auto* const first = reinterpret_cast<const unsigned char*>(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
const unsigned negative = first[0] == '-' ? 1u : 0u;
|
||||||
|
if (count - 16u > 1u || frac_digits == 0 || n.len != negative + count + 1 || d.size - n.off < 33)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const unsigned char* const digits = first + negative;
|
||||||
|
const std::uint64_t w = sixteen_digits(digits, int_digits);
|
||||||
|
// (the 17th digit follows the point)
|
||||||
|
const std::uint64_t w17 = (w * 10) + static_cast<std::uint64_t>(digits[17] - '0');
|
||||||
|
using format = ieee_binary_format<std::numeric_limits<FloatType>::digits>;
|
||||||
|
native_float_t<FloatType> result{};
|
||||||
|
float_from_bits(coordinate_bits(-static_cast<std::int64_t>(frac_digits), count == 17 ? w17 : w, format{}) | (static_cast<std::uint64_t>(negative) << format::sign_bit()), result);
|
||||||
|
value = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/// the value of the float token of a node, as parse() converts it; floats and
|
/// the value of the float token of a node, as parse() converts it; floats and
|
||||||
/// doubles with at most 19 digits are converted from the digit layout
|
/// doubles with at most 19 digits are converted from the digit layout
|
||||||
template<typename FloatType>
|
template<typename FloatType>
|
||||||
@@ -200,6 +348,13 @@ FloatType float_value(const document_data& d, const node& n)
|
|||||||
template<typename FloatType>
|
template<typename FloatType>
|
||||||
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
|
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
|
||||||
{
|
{
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
FloatType value {};
|
||||||
|
if (coordinate_float(d, n, value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
const unsigned int_digits = n.extra & 0xFFu;
|
const unsigned int_digits = n.extra & 0xFFu;
|
||||||
const unsigned frac_digits = n.extra >> 8u;
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
|
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <cstring> // memcpy, memset
|
#include <cstring> // memcpy, memset
|
||||||
|
#include <functional> // less
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
|
#include <memory> // unique_ptr
|
||||||
#include <type_traits> // integral_constant
|
#include <type_traits> // integral_constant
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
@@ -384,6 +386,127 @@ struct depth_counter<false>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w <
|
||||||
|
10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||||
|
|
||||||
|
dump() writes the shortest decimal in the rounding interval of x (the closest
|
||||||
|
one if there are several, see zmij::to_shortest()). Its digits are those of a
|
||||||
|
multiple of 10^g of the token's last digit, with g = dec_exp + 1 - q for x's
|
||||||
|
decimal exponent dec_exp: the multiple of 10^g nearest to x if it lies in the
|
||||||
|
interval (at most one does), else the multiple of 10^(g - 1) nearest to x
|
||||||
|
(see token_digits_1() and token_digits_2()). For g <= 0, the token itself is
|
||||||
|
in the interval.
|
||||||
|
|
||||||
|
The position is exact in a 64-bit fixed-point number with 56 bits after the
|
||||||
|
point (of the token's last digit): x / 10^q = 2c * 5^-q / 2^t for x = c * 2^e2
|
||||||
|
and t = q + 1 - e2, with at most 56 bits after the point here, and |x / 10^q -
|
||||||
|
w| is at most half the spacing of the doubles, below 12 units. So only the low
|
||||||
|
64 bits of 2c * 5^-q are needed. c and e2 come from Eisel-Lemire's product
|
||||||
|
directly (as eisel_lemire() rounds it: no tie is possible for q < -4, and its
|
||||||
|
second product is added where it can carry into the result).
|
||||||
|
|
||||||
|
@param[out] offset x / 10^q - w + 32, with 56 bits after the point
|
||||||
|
@param[out] radius half the spacing of the doubles at x, likewise (+ 1 if
|
||||||
|
the interval is closed)
|
||||||
|
@return g; 3 if the digits are not decided here (a power of two, whose
|
||||||
|
interval is not symmetric, or out of range)
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE int locate_token(std::uint64_t w, int q, std::uint64_t& offset, std::uint64_t& radius) noexcept
|
||||||
|
{
|
||||||
|
const int lz = count_leading_zeros(w);
|
||||||
|
const std::uint64_t normalized = w << static_cast<unsigned>(lz);
|
||||||
|
const auto index = static_cast<std::size_t>(2 * (q - pow5_128_smallest_power));
|
||||||
|
const uint128_parts product = full_multiplication(normalized, pow5_128()[index]);
|
||||||
|
std::uint64_t high = product.high;
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu))
|
||||||
|
{
|
||||||
|
// (as eisel_lemire(): the lower bits may carry into the result)
|
||||||
|
const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high;
|
||||||
|
high += product.low + second < product.low ? 1u : 0u;
|
||||||
|
}
|
||||||
|
const auto upperbit = static_cast<unsigned>(high >> 63u);
|
||||||
|
const std::uint64_t mantissa = high >> (upperbit + 9);
|
||||||
|
const std::uint64_t c = (mantissa + (mantissa & 1u)) >> 1u;
|
||||||
|
// e2 as eisel_lemire() computes the exponent (log2(10) ~ 217706 / 2^16)
|
||||||
|
const int e2 = static_cast<int>((217706 * static_cast<std::int64_t>(q)) >> 16) + 63 + static_cast<int>(upperbit) - lz - 52;
|
||||||
|
const int t = q + 1 - e2;
|
||||||
|
const int g = ((e2 * 315653) >> 20) + 1 - q; // (dec_exp as in zmij::to_shortest(), for a regular x)
|
||||||
|
static const std::array<std::uint64_t, 17> fives = {{1u, 5u, 25u, 125u, 625u, 3125u, 15625u, 78125u, 390625u, 1953125u, 9765625u, 48828125u, 244140625u, 1220703125u, 6103515625u, 30517578125u, 152587890625u}};
|
||||||
|
const std::uint64_t five = fives[static_cast<std::size_t>(-q)]; // 5^-q
|
||||||
|
const unsigned s = static_cast<unsigned>(56 - t) & 63u;
|
||||||
|
offset = ((2 * c * five) << s) - (w << 56u) + (std::uint64_t{32} << 56u);
|
||||||
|
radius = (five << s) + ((c & 1u) == 0 ? 1u : 0u);
|
||||||
|
// (c = 2^52: a power of two; c = 2^53: rounded up to one. With 8 to 16
|
||||||
|
// fraction digits, as the batch's tokens have, t is in [15, 40] and g at
|
||||||
|
// most 2. x lies halfway between two candidates only if its distance from
|
||||||
|
// the token is a whole unit (g == 2) or a half (g == 1): such tokens are
|
||||||
|
// left to the full conversion)
|
||||||
|
const bool decided = c - ((std::uint64_t{1} << 52u) + 1) < (std::uint64_t{1} << 52u) - 1
|
||||||
|
&& (offset & ((std::uint64_t{1} << 55u) - 1)) != 0;
|
||||||
|
return decided ? g : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the distance of a position (see locate_token()) from the biased point 32
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t biased_distance(std::uint64_t position) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t center = std::uint64_t{32} << 56u;
|
||||||
|
return position >= center ? position - center : center - position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (g == 1) the digits dump() writes for a token, from the position of its
|
||||||
|
double (see locate_token()): the multiple of 10 nearest to x if it is in the
|
||||||
|
interval, else w rounded to x (an exact tie between two candidates, like
|
||||||
|
754570942.33984373, whose double is 754570942.33984375, is excluded by
|
||||||
|
locate_token())
|
||||||
|
|
||||||
|
With |x - w| at most the radius, which is below half the spacing of the
|
||||||
|
candidates, the multiple of 10 nearest to x is w - r10 or the one above it.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_1(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||||
|
const std::uint64_t from = offset + (r10 * one); // x - (w - r10) (+ 32)
|
||||||
|
const bool up = from > 37 * one; // (nearer to the multiple above)
|
||||||
|
const bool shorter = biased_distance(from - (up ? 10 * one : 0)) < radius;
|
||||||
|
const std::uint64_t a = w - r10 + (up ? 10u : 0u);
|
||||||
|
const std::uint64_t b = w + ((offset + (one / 2)) >> 56u) - 32; // w rounded to x
|
||||||
|
return shorter ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (g == 2) the digits dump() writes for a token, as token_digits_1(): the
|
||||||
|
multiple of 100 nearest to x if it is in the interval, else the multiple of 10
|
||||||
|
nearest to x
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_2(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10, std::uint64_t r100) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||||
|
const std::uint64_t from = offset + (r100 * one);
|
||||||
|
const bool up = from > 82 * one;
|
||||||
|
const bool shorter = biased_distance(from - (up ? 100 * one : 0)) < radius;
|
||||||
|
const std::uint64_t a = w - r100 + (up ? 100u : 0u);
|
||||||
|
// x - (w - r10), in [-12, 21]: the multiple of 10 nearest to x is that one,
|
||||||
|
// or one or two above, or one below
|
||||||
|
const std::uint64_t y = offset + (r10 * one);
|
||||||
|
const std::uint64_t steps = (y > 37 * one ? 1u : 0u) + (y > 47 * one ? 1u : 0u);
|
||||||
|
const bool down = y < 27 * one;
|
||||||
|
const std::uint64_t b = w - r10 + (10 * steps) - (down ? 10u : 0u);
|
||||||
|
return shorter ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the four ASCII digits of x < 10^4, the first in the lowest byte (as
|
||||||
|
/// little-endian memory holds them)
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t four_digit_text(std::uint32_t x) noexcept
|
||||||
|
{
|
||||||
|
const std::uint32_t ab = (x * 5243u) >> 19u; // x / 100
|
||||||
|
std::uint32_t v = ab | ((x - (ab * 100u)) << 16u);
|
||||||
|
const std::uint32_t a = ((v * 103u) >> 10u) & 0x000F000Fu; // / 10 per 16-bit lane
|
||||||
|
v = a | ((v - (a * 10u)) << 8u);
|
||||||
|
return v + 0x30303030u;
|
||||||
|
}
|
||||||
|
|
||||||
/// how the view's dump() writes a value
|
/// how the view's dump() writes a value
|
||||||
struct dump_style
|
struct dump_style
|
||||||
{
|
{
|
||||||
@@ -711,6 +834,7 @@ class view_serializer
|
|||||||
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
std::vector<frame> heap;
|
std::vector<frame> heap;
|
||||||
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||||
|
float_batch batch;
|
||||||
const char* const src = m_doc.src;
|
const char* const src = m_doc.src;
|
||||||
const char* const src_end = src + m_doc.size;
|
const char* const src_end = src + m_doc.size;
|
||||||
char* w = m_out.cursor();
|
char* w = m_out.cursor();
|
||||||
@@ -850,7 +974,7 @@ class view_serializer
|
|||||||
else if (enabled(fast_double::value))
|
else if (enabled(fast_double::value))
|
||||||
{
|
{
|
||||||
room(64);
|
room(64);
|
||||||
w = write_double_at(w, *n);
|
w = write_double_ahead(w, src, n, batch);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -875,6 +999,20 @@ class view_serializer
|
|||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||||
|
// (read-only documents, compact output) a pair of floats,
|
||||||
|
// like the coordinates of GeoJSON, without a frame
|
||||||
|
if (enabled(!Editable && !Pretty && !SourceNumbers && fast_double::value) && n->next == 3 && n->len == 2
|
||||||
|
&& n[1].kind == static_cast<std::uint8_t>(value_t::number_float) && n[2].kind == static_cast<std::uint8_t>(value_t::number_float))
|
||||||
|
{
|
||||||
|
room(2 * 64);
|
||||||
|
*w++ = '[';
|
||||||
|
w = write_double_ahead(w, src, n + 1, batch);
|
||||||
|
*w++ = ',';
|
||||||
|
w = write_double_ahead(w, src, n + 2, batch);
|
||||||
|
*w++ = ']';
|
||||||
|
n += 2; // (the element before the next node)
|
||||||
|
break;
|
||||||
|
}
|
||||||
room(8);
|
room(8);
|
||||||
if (n->len == 0)
|
if (n->len == 0)
|
||||||
{
|
{
|
||||||
@@ -984,6 +1122,254 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (float_batch) the mark of digits whose rounding carried beyond the
|
||||||
|
/// token's last four (see mark())
|
||||||
|
static constexpr std::uint64_t carried() noexcept
|
||||||
|
{
|
||||||
|
return std::uint64_t{1} << 63u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// floats of 16 or 17 digits ahead of the walk, converted together (see
|
||||||
|
/// convert_ahead()), with their digits as mark() keeps them
|
||||||
|
struct float_batch
|
||||||
|
{
|
||||||
|
static constexpr std::size_t capacity() noexcept
|
||||||
|
{
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
/// (on the heap, allocated by the first conversion: a frame of more
|
||||||
|
/// than a page made every dump of a small document slower)
|
||||||
|
struct entries
|
||||||
|
{
|
||||||
|
std::array<const node*, 256> at; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint64_t, 256> digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
};
|
||||||
|
std::unique_ptr<entries> storage{};
|
||||||
|
const node** at = nullptr;
|
||||||
|
std::uint64_t* digits = nullptr;
|
||||||
|
std::size_t count = 0;
|
||||||
|
std::size_t next = 0;
|
||||||
|
const node* scanned = nullptr; ///< where the last scan of the node array ended
|
||||||
|
};
|
||||||
|
|
||||||
|
/// (doubles) the float at n as dump() writes it, at w (64 bytes of room):
|
||||||
|
/// from the batch if it holds n
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE char* write_double_ahead(char* w, const char* src, const node* n, float_batch& batch)
|
||||||
|
{
|
||||||
|
#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
|
// (digits 0: not written by the batch; carried(): rounding carried
|
||||||
|
// beyond the last four digits)
|
||||||
|
if (batch.next != batch.count && batch.at[batch.next] == n && batch.digits[batch.next] - 1 < carried() - 1)
|
||||||
|
{
|
||||||
|
return write_token_digits(w, src, *n, batch.digits[batch.next++]);
|
||||||
|
}
|
||||||
|
return write_double_beside(w, n, batch);
|
||||||
|
#else
|
||||||
|
static_cast<void>(src);
|
||||||
|
static_cast<void>(batch);
|
||||||
|
return write_double_at(w, *n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
|
/// write_double_ahead() for a float that is not the batch's next one (out
|
||||||
|
/// of line: the walk is faster with less code in its loop)
|
||||||
|
NLOHMANN_VIEW_NOINLINE char* write_double_beside(char* w, const node* n, float_batch& batch)
|
||||||
|
{
|
||||||
|
// (an editable document's walk can skip nodes of the array)
|
||||||
|
const std::less<const node*> before{}; // NOLINT(modernize-use-transparent-functors) C++11 has no std::less<>
|
||||||
|
while (batch.next != batch.count && before(batch.at[batch.next], n))
|
||||||
|
{
|
||||||
|
++batch.next;
|
||||||
|
}
|
||||||
|
if ((batch.next != batch.count && batch.at[batch.next] == n) || convert_ahead(n, batch))
|
||||||
|
{
|
||||||
|
const std::uint64_t digits = batch.digits[batch.next++];
|
||||||
|
if ((digits & carried()) != 0)
|
||||||
|
{
|
||||||
|
return write_carried_digits(w, *n, digits & ~carried());
|
||||||
|
}
|
||||||
|
if (digits != 0)
|
||||||
|
{
|
||||||
|
return write_token_digits(w, m_doc.src, *n, digits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return write_double_at(w, *n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert the floats of 16 or 17 digits among the next nodes of the
|
||||||
|
array at once; whether n is the first of them
|
||||||
|
|
||||||
|
Such tokens (like the coordinates of GeoJSON, written with 17 digits) are
|
||||||
|
written as the token with its last four digits replaced
|
||||||
|
(write_token_digits()). Each step (the digits, locate_token(),
|
||||||
|
token_digits_1/2()) is a long chain of dependent operations; one loop per
|
||||||
|
step over many floats lets the processor overlap the floats, which it does
|
||||||
|
not across the steps of one float (on Apple M1, one loop for the digits and
|
||||||
|
locate_token() made the dump of canada.json 12 % slower). Each node of the array
|
||||||
|
is scanned once; a node that the walk does not reach in order (in an
|
||||||
|
editable document) is written without the batch.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_NOINLINE bool convert_ahead(const node* n, float_batch& batch)
|
||||||
|
{
|
||||||
|
const std::less<const node*> before{}; // NOLINT(modernize-use-transparent-functors) C++11 has no std::less<>
|
||||||
|
const node* const last = m_doc.nodes + m_doc.node_count;
|
||||||
|
if (before(n, batch.scanned) || before(n, m_doc.nodes) || !before(n, last))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const char* const src = m_doc.src;
|
||||||
|
const char* const src_end = src + m_doc.size;
|
||||||
|
const node* const stop = last - n > 1024 ? n + 1024 : last;
|
||||||
|
batch.count = 0;
|
||||||
|
batch.next = 0;
|
||||||
|
// the digits of each token (and its last four from the text)
|
||||||
|
std::array<std::uint64_t, 256> digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<int, 256> exponent; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint32_t, 256> last_four; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
const node* p = n;
|
||||||
|
if (!batch.storage)
|
||||||
|
{
|
||||||
|
batch.storage.reset(new typename float_batch::entries); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique): C++11
|
||||||
|
batch.at = batch.storage->at.data();
|
||||||
|
batch.digits = batch.storage->digits.data();
|
||||||
|
}
|
||||||
|
for (; p != stop && batch.count != float_batch::capacity(); ++p)
|
||||||
|
{
|
||||||
|
// a token of the source: 16 or 17 digits, at least 8 after the
|
||||||
|
// point, no exponent, 32 bytes to read
|
||||||
|
const unsigned frac_digits = p->extra >> 8u;
|
||||||
|
const unsigned int_digits = p->extra & 0xFFu;
|
||||||
|
const unsigned count = int_digits + frac_digits;
|
||||||
|
if (p->kind == static_cast<std::uint8_t>(value_t::number_float) && (p->flags & node_flags::storage) == 0
|
||||||
|
&& count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33)
|
||||||
|
{
|
||||||
|
const auto* const token = reinterpret_cast<const unsigned char*>(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
const unsigned sign = token[0] == '-' ? 1 : 0;
|
||||||
|
const unsigned char* const first = token + sign;
|
||||||
|
const std::uint64_t w = sixteen_digits(first, int_digits);
|
||||||
|
// (the 17th digit follows the point; a token with an exponent
|
||||||
|
// gets digits outside the range of locate_token(), and is not
|
||||||
|
// written by the batch. A token 0.xxx with 16 significant
|
||||||
|
// digits is: its double is written as "0." and its digits too)
|
||||||
|
const std::uint64_t w17 = (w * 10) + static_cast<std::uint64_t>(first[17] - '0');
|
||||||
|
const std::size_t i = batch.count++;
|
||||||
|
batch.at[i] = p;
|
||||||
|
if (p->len != sign + count + 1)
|
||||||
|
{
|
||||||
|
digits[i] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
digits[i] = count == 17 ? w17 : w;
|
||||||
|
}
|
||||||
|
exponent[i] = -static_cast<int>(frac_digits);
|
||||||
|
// (byte 0: the last digit, byte 1: the last two, bits 16-29: the last four)
|
||||||
|
std::uint32_t four = 0;
|
||||||
|
std::memcpy(&four, token + p->len - 4, 4);
|
||||||
|
four -= 0x30303030u;
|
||||||
|
const std::uint32_t pairs = ((four & 0x0F0F0F0Fu) * 2561u) >> 8u; // (byte 0, 2: the pairs)
|
||||||
|
last_four[i] = (four >> 24u) | ((pairs >> 16u & 0xFFu) << 8u) | ((((pairs & 0x00FF00FFu) * 6553601u) >> 16u) << 16u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batch.scanned = p;
|
||||||
|
// the position of each double, and the floats of either kind
|
||||||
|
std::array<std::uint64_t, 256> offset; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint64_t, 256> radius; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint8_t, 256> kind_1; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint8_t, 256> kind_2; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::size_t count_1 = 0;
|
||||||
|
std::size_t count_2 = 0;
|
||||||
|
for (std::size_t i = 0; i != batch.count; ++i)
|
||||||
|
{
|
||||||
|
const std::uint64_t w = digits[i];
|
||||||
|
// (the range excludes the values of bytes that are not digits, only
|
||||||
|
// in a damaged image loaded with image_check::bounds)
|
||||||
|
const int g = w - 1000000000000000u < 99000000000000000u ? locate_token(w, exponent[i], offset[i], radius[i]) : 3;
|
||||||
|
batch.digits[i] = g <= 0 ? (last_four[i] >> 16u) + 1 : 0;
|
||||||
|
kind_1[count_1] = static_cast<std::uint8_t>(i);
|
||||||
|
count_1 += g == 1 ? 1 : 0;
|
||||||
|
kind_2[count_2] = static_cast<std::uint8_t>(i);
|
||||||
|
count_2 += g == 2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
// the digits to write, in one loop per kind (see mark())
|
||||||
|
for (std::size_t k = 0; k != count_1; ++k)
|
||||||
|
{
|
||||||
|
const std::size_t i = kind_1[k];
|
||||||
|
const std::uint64_t s = token_digits_1(digits[i], offset[i], radius[i], last_four[i] & 0xFFu);
|
||||||
|
batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u);
|
||||||
|
}
|
||||||
|
for (std::size_t k = 0; k != count_2; ++k)
|
||||||
|
{
|
||||||
|
const std::size_t i = kind_2[k];
|
||||||
|
const std::uint64_t s = token_digits_2(digits[i], offset[i], radius[i], last_four[i] & 0xFFu, (last_four[i] >> 8u) & 0xFFu);
|
||||||
|
batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u);
|
||||||
|
}
|
||||||
|
return batch.count != 0 && batch.at[0] == n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes
|
||||||
|
of room), from the digits of its double (see locate_token()) whose
|
||||||
|
rounding carried beyond the token's last four digits
|
||||||
|
|
||||||
|
(like 43.42027299999999 -> 43.420273): the digits go to the writer of
|
||||||
|
Zmij's results as its 15 or 16 leading digits and one more, which omits
|
||||||
|
the trailing zeros.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_NOINLINE char* write_carried_digits(char* w, const node& n, std::uint64_t digits) const noexcept
|
||||||
|
{
|
||||||
|
const int exponent = -(n.extra >> 8u);
|
||||||
|
const std::uint64_t leading = digits / 10;
|
||||||
|
const auto last = static_cast<unsigned char>(digits - (leading * 10));
|
||||||
|
const bool seventeen = digits >= 10000000000000000u;
|
||||||
|
*w = '-';
|
||||||
|
w += m_doc.src[n.off] == '-' ? 1 : 0;
|
||||||
|
return ::nlohmann::detail::dtoa_impl::write_shortest(w, seventeen ? ::nlohmann::detail::zmij::shortest_decimal{leading, exponent, last, last != 0}
|
||||||
|
: ::nlohmann::detail::zmij::shortest_decimal{digits, exponent - 1, 0, false});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the digits s of a token with the digits w and the last four digits
|
||||||
|
/// r4, as the batch keeps them: the new last four digits + 1 (only those
|
||||||
|
/// are written over the token's), or s marked if it differs from w beyond
|
||||||
|
/// them (0 stays for a float the batch does not write)
|
||||||
|
static std::uint64_t mark(std::uint64_t w, std::uint64_t s, std::uint64_t r4) noexcept
|
||||||
|
{
|
||||||
|
const std::uint64_t last = s - (w - r4);
|
||||||
|
return last < 10000 ? last + 1 : s | carried();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes
|
||||||
|
of room): the token with its last four digits replaced by digits - 1 (see
|
||||||
|
mark(); the others are the token's), and without trailing zeros after the
|
||||||
|
point, but one
|
||||||
|
*/
|
||||||
|
char* write_token_digits(char* w, const char* src, const node& n, std::uint64_t digits)
|
||||||
|
{
|
||||||
|
const char* const token = src + n.off;
|
||||||
|
std::memcpy(w, token, 32);
|
||||||
|
char* const end = w + n.len;
|
||||||
|
const std::uint32_t last = four_digit_text(static_cast<std::uint32_t>(digits - 1));
|
||||||
|
std::memcpy(end - 4, &last, 4);
|
||||||
|
// (the token's text before the new digits, from the source: reading
|
||||||
|
// the output back would wait for the stores)
|
||||||
|
std::uint64_t before = 0;
|
||||||
|
std::memcpy(&before, token + n.len - 12, 8);
|
||||||
|
const std::uint32_t last_zeros = last ^ 0x30303030u;
|
||||||
|
const std::uint64_t before_zeros = before ^ 0x3030303030303030u;
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(last_zeros == 0 && before_zeros == 0))
|
||||||
|
{
|
||||||
|
return write_double_at(w, n); // (twelve or more trailing zeros)
|
||||||
|
}
|
||||||
|
const unsigned zeros = last_zeros != 0 ? (static_cast<unsigned>(count_leading_zeros(last_zeros)) / 8) - 4
|
||||||
|
: 4 + (static_cast<unsigned>(count_leading_zeros(before_zeros)) / 8);
|
||||||
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
|
return end - (zeros < frac_digits ? zeros : frac_digits - 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// a float node as dump() writes it
|
/// a float node as dump() writes it
|
||||||
void write_float_node(const node& n)
|
void write_float_node(const node& n)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4605,6 +4605,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
// #include <nlohmann/detail/view/scan.hpp>
|
// #include <nlohmann/detail/view/scan.hpp>
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/view/simd.hpp>
|
||||||
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -4689,12 +4691,56 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
|||||||
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
/*!
|
||||||
|
@brief the value of the first 16 digits of a run of digits with a point after
|
||||||
|
int_digits of them (or none, if int_digits >= 16)
|
||||||
|
|
||||||
|
The 17 bytes at p are read at once: the bytes after the point move down by one,
|
||||||
|
then pairs, quadruples, and octets of digits are combined by multiplications
|
||||||
|
(after simdjson). The bytes [p, p + 32) must be readable. Bytes that are not
|
||||||
|
digits (only in a damaged image loaded with image_check::bounds) give a wrong
|
||||||
|
value, but no undefined behavior.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t sixteen_digits(const unsigned char* p, unsigned int_digits) noexcept
|
||||||
|
{
|
||||||
|
#if NLOHMANN_VIEW_NEON
|
||||||
|
const uint8x16_t low = vld1q_u8(p);
|
||||||
|
const uint8x16_t index = vcombine_u8(vcreate_u8(0x0706050403020100u), vcreate_u8(0x0F0E0D0C0B0A0908u));
|
||||||
|
const uint8x16_t before_point = vcltq_u8(index, vdupq_n_u8(static_cast<std::uint8_t>(int_digits)));
|
||||||
|
const uint8x16_t digits = vsubq_u8(vbslq_u8(before_point, low, vextq_u8(low, vld1q_u8(p + 16), 1)), vdupq_n_u8('0'));
|
||||||
|
const uint16x8_t twos = vpaddlq_u8(vmulq_u8(digits, vreinterpretq_u8_u16(vdupq_n_u16(0x010A))));
|
||||||
|
const uint32x4_t fours = vpaddlq_u16(vmulq_u16(twos, vreinterpretq_u16_u32(vdupq_n_u32(0x00010064))));
|
||||||
|
const uint64x2_t eights = vpaddlq_u32(vmulq_u32(fours, vreinterpretq_u32_u64(vdupq_n_u64(0x0000000100002710u))));
|
||||||
|
return (vgetq_lane_u64(eights, 0) * 100000000u) + vgetq_lane_u64(eights, 1);
|
||||||
|
#else
|
||||||
|
// NOLINTBEGIN(portability-simd-intrinsics)
|
||||||
|
const __m128i low = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i high = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + 16))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i before_point = _mm_cmpgt_epi8(_mm_set1_epi8(static_cast<char>(int_digits)), _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15));
|
||||||
|
const __m128i moved = _mm_or_si128(_mm_srli_si128(low, 1), _mm_slli_si128(high, 15));
|
||||||
|
const __m128i digits = _mm_sub_epi8(_mm_or_si128(_mm_and_si128(before_point, low), _mm_andnot_si128(before_point, moved)), _mm_set1_epi8('0'));
|
||||||
|
// (16-bit lanes: SSE2 has no multiply-add of bytes)
|
||||||
|
const __m128i tens = _mm_setr_epi16(10, 1, 10, 1, 10, 1, 10, 1);
|
||||||
|
const __m128i twos = _mm_packs_epi32(_mm_madd_epi16(_mm_unpacklo_epi8(digits, _mm_setzero_si128()), tens),
|
||||||
|
_mm_madd_epi16(_mm_unpackhi_epi8(digits, _mm_setzero_si128()), tens));
|
||||||
|
const __m128i fours = _mm_madd_epi16(twos, _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1));
|
||||||
|
const __m128i eights = _mm_madd_epi16(_mm_packs_epi32(fours, fours), _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1));
|
||||||
|
const auto upper = static_cast<std::uint32_t>(_mm_cvtsi128_si32(eights));
|
||||||
|
const auto lower = static_cast<std::uint32_t>(_mm_cvtsi128_si32(_mm_srli_si128(eights, 4)));
|
||||||
|
// NOLINTEND(portability-simd-intrinsics)
|
||||||
|
return (std::uint64_t{upper} * 100000000u) + lower;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the digits of a float token with at most 19 digits, from its layout
|
@brief the digits of a float token with at most 19 digits, from its layout
|
||||||
|
|
||||||
The digit layout recorded while parsing says where the integer digits, the
|
The digit layout recorded while parsing says where the integer digits, the
|
||||||
fraction digits, and the exponent are, so the digits are read eight at a
|
fraction digits, and the exponent are, so the digits are read eight at a
|
||||||
time without scanning.
|
time without scanning (with SIMD, a token of 16 to 19 digits sixteen at a
|
||||||
|
time).
|
||||||
|
|
||||||
@param[in] p first character of the token
|
@param[in] p first character of the token
|
||||||
@param[in] e end of the token
|
@param[in] e end of the token
|
||||||
@@ -4704,15 +4750,34 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
|||||||
{
|
{
|
||||||
const bool negative = *p == '-';
|
const bool negative = *p == '-';
|
||||||
p += negative ? 1 : 0;
|
p += negative ? 1 : 0;
|
||||||
std::uint64_t w = parse_upto19(p, int_digits, limit);
|
std::uint64_t w = 0;
|
||||||
p += int_digits;
|
|
||||||
std::int64_t q = 0;
|
std::int64_t q = 0;
|
||||||
if (frac_digits != 0)
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
const unsigned count = int_digits + frac_digits;
|
||||||
|
if (count >= 16 && limit - p >= 32)
|
||||||
{
|
{
|
||||||
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
// (the point, if any, follows the integer digits; the digits after
|
||||||
p += 1 + frac_digits;
|
// the first 16 are at most three)
|
||||||
|
w = sixteen_digits(p, int_digits);
|
||||||
|
for (unsigned i = 16; i < count; ++i)
|
||||||
|
{
|
||||||
|
w = (w * 10) + static_cast<std::uint64_t>(p[i + (i >= int_digits ? 1 : 0)] - '0');
|
||||||
|
}
|
||||||
|
p += count + (frac_digits != 0 ? 1 : 0);
|
||||||
q = -static_cast<std::int64_t>(frac_digits);
|
q = -static_cast<std::int64_t>(frac_digits);
|
||||||
}
|
}
|
||||||
|
else /* NOLINT(readability/braces) */
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
w = parse_upto19(p, int_digits, limit);
|
||||||
|
p += int_digits;
|
||||||
|
if (frac_digits != 0)
|
||||||
|
{
|
||||||
|
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
||||||
|
p += 1 + frac_digits;
|
||||||
|
q = -static_cast<std::int64_t>(frac_digits);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (p != e)
|
if (p != e)
|
||||||
{
|
{
|
||||||
// [eE][+-]digits; huge exponents saturate (the parser rejected
|
// [eE][+-]digits; huge exponents saturate (the parser rejected
|
||||||
@@ -4747,12 +4812,22 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
|||||||
The result is correctly rounded by the lexer's conversion
|
The result is correctly rounded by the lexer's conversion
|
||||||
(detail::decimal_to_float(): Clinger's fast path where both operands are
|
(detail::decimal_to_float(): Clinger's fast path where both operands are
|
||||||
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
|
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
|
||||||
digits), so it is the value parse() produces.
|
digits), so it is the value parse() produces. Tokens of 16 or more digits go
|
||||||
|
to Eisel-Lemire directly: the fast path takes few of them (none of 17 digits),
|
||||||
|
and its check is a branch that data like GeoJSON coordinates mispredicts.
|
||||||
*/
|
*/
|
||||||
template<typename FloatType>
|
template<typename FloatType>
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
|
||||||
{
|
{
|
||||||
return decimal_to_float<FloatType>(layout_decimal(p, e, int_digits, frac_digits, limit));
|
const float_significand d = layout_decimal(p, e, int_digits, frac_digits, limit);
|
||||||
|
if (int_digits + frac_digits >= 16)
|
||||||
|
{
|
||||||
|
using format = ieee_binary_format<std::numeric_limits<FloatType>::digits>;
|
||||||
|
native_float_t<FloatType> value{};
|
||||||
|
float_from_bits(eisel_lemire<format>(d.exponent, d.w) | (d.negative ? (std::uint64_t{1} << format::sign_bit()) : 0u), value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return decimal_to_float<FloatType>(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value of a float set by an edit: its token (the shortest round-trip
|
/// the value of a float set by an edit: its token (the shortest round-trip
|
||||||
@@ -4771,6 +4846,80 @@ NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n)
|
|||||||
return float_value<FloatType>(token, n);
|
return float_value<FloatType>(token, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
/// eisel_lemire() for the token w * 10^q of coordinate_float()
|
||||||
|
template<typename Format>
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, Format /*format*/) noexcept
|
||||||
|
{
|
||||||
|
return eisel_lemire<Format>(q, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (binary64) eisel_lemire() without the cases that tokens of 16 or 17
|
||||||
|
digits with 5 or more after the point cannot reach: a value of 0, a
|
||||||
|
subnormal, an infinity, or a tie (q < -4)
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, ieee_binary_format<53> format) noexcept
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(w == 0 || q > -5))
|
||||||
|
{
|
||||||
|
return eisel_lemire<ieee_binary_format<53>>(q, w);
|
||||||
|
}
|
||||||
|
static_cast<void>(format);
|
||||||
|
const int lz = count_leading_zeros(w);
|
||||||
|
const std::uint64_t normalized = w << static_cast<unsigned>(lz);
|
||||||
|
const auto index = static_cast<std::size_t>(2 * (q - pow5_128_smallest_power));
|
||||||
|
const uint128_parts product = full_multiplication(normalized, pow5_128()[index]);
|
||||||
|
std::uint64_t high = product.high;
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu))
|
||||||
|
{
|
||||||
|
// (the lower bits may carry into the result)
|
||||||
|
const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high;
|
||||||
|
high += product.low + second < product.low ? 1u : 0u;
|
||||||
|
}
|
||||||
|
const auto upperbit = static_cast<unsigned>(high >> 63u);
|
||||||
|
std::uint64_t mantissa = high >> (upperbit + 9);
|
||||||
|
auto power2 = static_cast<std::uint64_t>(((217706 * q) >> 16) + 63 + static_cast<std::int64_t>(upperbit) - lz + 1023);
|
||||||
|
mantissa = (mantissa + (mantissa & 1u)) >> 1u;
|
||||||
|
const bool carry = mantissa >= (std::uint64_t{2} << 52u);
|
||||||
|
mantissa = carry ? (std::uint64_t{1} << 52u) : mantissa;
|
||||||
|
power2 += carry ? 1u : 0u;
|
||||||
|
return (mantissa & ~(std::uint64_t{1} << 52u)) | (power2 << 52u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (binary32 and binary64) the value of a float token of the source with
|
||||||
|
16 or 17 digits, a point, and no exponent, the shape of most coordinates
|
||||||
|
written with full precision; whether the token of n has this shape
|
||||||
|
|
||||||
|
These tokens skip the general layout of layout_decimal(): no digits beyond
|
||||||
|
the 17th, no exponent (and Eisel-Lemire directly, as in layout_float()).
|
||||||
|
*/
|
||||||
|
template<typename FloatType>
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE bool coordinate_float(const document_data& d, const node& n, FloatType& value) noexcept
|
||||||
|
{
|
||||||
|
const unsigned int_digits = n.extra & 0xFFu;
|
||||||
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
|
const unsigned count = int_digits + frac_digits;
|
||||||
|
const auto* const first = reinterpret_cast<const unsigned char*>(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
const unsigned negative = first[0] == '-' ? 1u : 0u;
|
||||||
|
if (count - 16u > 1u || frac_digits == 0 || n.len != negative + count + 1 || d.size - n.off < 33)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const unsigned char* const digits = first + negative;
|
||||||
|
const std::uint64_t w = sixteen_digits(digits, int_digits);
|
||||||
|
// (the 17th digit follows the point)
|
||||||
|
const std::uint64_t w17 = (w * 10) + static_cast<std::uint64_t>(digits[17] - '0');
|
||||||
|
using format = ieee_binary_format<std::numeric_limits<FloatType>::digits>;
|
||||||
|
native_float_t<FloatType> result{};
|
||||||
|
float_from_bits(coordinate_bits(-static_cast<std::int64_t>(frac_digits), count == 17 ? w17 : w, format{}) | (static_cast<std::uint64_t>(negative) << format::sign_bit()), result);
|
||||||
|
value = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/// the value of the float token of a node, as parse() converts it; floats and
|
/// the value of the float token of a node, as parse() converts it; floats and
|
||||||
/// doubles with at most 19 digits are converted from the digit layout
|
/// doubles with at most 19 digits are converted from the digit layout
|
||||||
template<typename FloatType>
|
template<typename FloatType>
|
||||||
@@ -4786,6 +4935,13 @@ FloatType float_value(const document_data& d, const node& n)
|
|||||||
template<typename FloatType>
|
template<typename FloatType>
|
||||||
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
|
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
|
||||||
{
|
{
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
FloatType value {};
|
||||||
|
if (coordinate_float(d, n, value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
const unsigned int_digits = n.extra & 0xFFu;
|
const unsigned int_digits = n.extra & 0xFFu;
|
||||||
const unsigned frac_digits = n.extra >> 8u;
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
|
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
|
||||||
@@ -6357,7 +6513,9 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <cstring> // memcpy, memset
|
#include <cstring> // memcpy, memset
|
||||||
|
#include <functional> // less
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
|
#include <memory> // unique_ptr
|
||||||
#include <type_traits> // integral_constant
|
#include <type_traits> // integral_constant
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
@@ -6732,6 +6890,127 @@ struct depth_counter<false>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w <
|
||||||
|
10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||||
|
|
||||||
|
dump() writes the shortest decimal in the rounding interval of x (the closest
|
||||||
|
one if there are several, see zmij::to_shortest()). Its digits are those of a
|
||||||
|
multiple of 10^g of the token's last digit, with g = dec_exp + 1 - q for x's
|
||||||
|
decimal exponent dec_exp: the multiple of 10^g nearest to x if it lies in the
|
||||||
|
interval (at most one does), else the multiple of 10^(g - 1) nearest to x
|
||||||
|
(see token_digits_1() and token_digits_2()). For g <= 0, the token itself is
|
||||||
|
in the interval.
|
||||||
|
|
||||||
|
The position is exact in a 64-bit fixed-point number with 56 bits after the
|
||||||
|
point (of the token's last digit): x / 10^q = 2c * 5^-q / 2^t for x = c * 2^e2
|
||||||
|
and t = q + 1 - e2, with at most 56 bits after the point here, and |x / 10^q -
|
||||||
|
w| is at most half the spacing of the doubles, below 12 units. So only the low
|
||||||
|
64 bits of 2c * 5^-q are needed. c and e2 come from Eisel-Lemire's product
|
||||||
|
directly (as eisel_lemire() rounds it: no tie is possible for q < -4, and its
|
||||||
|
second product is added where it can carry into the result).
|
||||||
|
|
||||||
|
@param[out] offset x / 10^q - w + 32, with 56 bits after the point
|
||||||
|
@param[out] radius half the spacing of the doubles at x, likewise (+ 1 if
|
||||||
|
the interval is closed)
|
||||||
|
@return g; 3 if the digits are not decided here (a power of two, whose
|
||||||
|
interval is not symmetric, or out of range)
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE int locate_token(std::uint64_t w, int q, std::uint64_t& offset, std::uint64_t& radius) noexcept
|
||||||
|
{
|
||||||
|
const int lz = count_leading_zeros(w);
|
||||||
|
const std::uint64_t normalized = w << static_cast<unsigned>(lz);
|
||||||
|
const auto index = static_cast<std::size_t>(2 * (q - pow5_128_smallest_power));
|
||||||
|
const uint128_parts product = full_multiplication(normalized, pow5_128()[index]);
|
||||||
|
std::uint64_t high = product.high;
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu))
|
||||||
|
{
|
||||||
|
// (as eisel_lemire(): the lower bits may carry into the result)
|
||||||
|
const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high;
|
||||||
|
high += product.low + second < product.low ? 1u : 0u;
|
||||||
|
}
|
||||||
|
const auto upperbit = static_cast<unsigned>(high >> 63u);
|
||||||
|
const std::uint64_t mantissa = high >> (upperbit + 9);
|
||||||
|
const std::uint64_t c = (mantissa + (mantissa & 1u)) >> 1u;
|
||||||
|
// e2 as eisel_lemire() computes the exponent (log2(10) ~ 217706 / 2^16)
|
||||||
|
const int e2 = static_cast<int>((217706 * static_cast<std::int64_t>(q)) >> 16) + 63 + static_cast<int>(upperbit) - lz - 52;
|
||||||
|
const int t = q + 1 - e2;
|
||||||
|
const int g = ((e2 * 315653) >> 20) + 1 - q; // (dec_exp as in zmij::to_shortest(), for a regular x)
|
||||||
|
static const std::array<std::uint64_t, 17> fives = {{1u, 5u, 25u, 125u, 625u, 3125u, 15625u, 78125u, 390625u, 1953125u, 9765625u, 48828125u, 244140625u, 1220703125u, 6103515625u, 30517578125u, 152587890625u}};
|
||||||
|
const std::uint64_t five = fives[static_cast<std::size_t>(-q)]; // 5^-q
|
||||||
|
const unsigned s = static_cast<unsigned>(56 - t) & 63u;
|
||||||
|
offset = ((2 * c * five) << s) - (w << 56u) + (std::uint64_t{32} << 56u);
|
||||||
|
radius = (five << s) + ((c & 1u) == 0 ? 1u : 0u);
|
||||||
|
// (c = 2^52: a power of two; c = 2^53: rounded up to one. With 8 to 16
|
||||||
|
// fraction digits, as the batch's tokens have, t is in [15, 40] and g at
|
||||||
|
// most 2. x lies halfway between two candidates only if its distance from
|
||||||
|
// the token is a whole unit (g == 2) or a half (g == 1): such tokens are
|
||||||
|
// left to the full conversion)
|
||||||
|
const bool decided = c - ((std::uint64_t{1} << 52u) + 1) < (std::uint64_t{1} << 52u) - 1
|
||||||
|
&& (offset & ((std::uint64_t{1} << 55u) - 1)) != 0;
|
||||||
|
return decided ? g : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the distance of a position (see locate_token()) from the biased point 32
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t biased_distance(std::uint64_t position) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t center = std::uint64_t{32} << 56u;
|
||||||
|
return position >= center ? position - center : center - position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (g == 1) the digits dump() writes for a token, from the position of its
|
||||||
|
double (see locate_token()): the multiple of 10 nearest to x if it is in the
|
||||||
|
interval, else w rounded to x (an exact tie between two candidates, like
|
||||||
|
754570942.33984373, whose double is 754570942.33984375, is excluded by
|
||||||
|
locate_token())
|
||||||
|
|
||||||
|
With |x - w| at most the radius, which is below half the spacing of the
|
||||||
|
candidates, the multiple of 10 nearest to x is w - r10 or the one above it.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_1(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||||
|
const std::uint64_t from = offset + (r10 * one); // x - (w - r10) (+ 32)
|
||||||
|
const bool up = from > 37 * one; // (nearer to the multiple above)
|
||||||
|
const bool shorter = biased_distance(from - (up ? 10 * one : 0)) < radius;
|
||||||
|
const std::uint64_t a = w - r10 + (up ? 10u : 0u);
|
||||||
|
const std::uint64_t b = w + ((offset + (one / 2)) >> 56u) - 32; // w rounded to x
|
||||||
|
return shorter ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (g == 2) the digits dump() writes for a token, as token_digits_1(): the
|
||||||
|
multiple of 100 nearest to x if it is in the interval, else the multiple of 10
|
||||||
|
nearest to x
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_2(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10, std::uint64_t r100) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||||
|
const std::uint64_t from = offset + (r100 * one);
|
||||||
|
const bool up = from > 82 * one;
|
||||||
|
const bool shorter = biased_distance(from - (up ? 100 * one : 0)) < radius;
|
||||||
|
const std::uint64_t a = w - r100 + (up ? 100u : 0u);
|
||||||
|
// x - (w - r10), in [-12, 21]: the multiple of 10 nearest to x is that one,
|
||||||
|
// or one or two above, or one below
|
||||||
|
const std::uint64_t y = offset + (r10 * one);
|
||||||
|
const std::uint64_t steps = (y > 37 * one ? 1u : 0u) + (y > 47 * one ? 1u : 0u);
|
||||||
|
const bool down = y < 27 * one;
|
||||||
|
const std::uint64_t b = w - r10 + (10 * steps) - (down ? 10u : 0u);
|
||||||
|
return shorter ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the four ASCII digits of x < 10^4, the first in the lowest byte (as
|
||||||
|
/// little-endian memory holds them)
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t four_digit_text(std::uint32_t x) noexcept
|
||||||
|
{
|
||||||
|
const std::uint32_t ab = (x * 5243u) >> 19u; // x / 100
|
||||||
|
std::uint32_t v = ab | ((x - (ab * 100u)) << 16u);
|
||||||
|
const std::uint32_t a = ((v * 103u) >> 10u) & 0x000F000Fu; // / 10 per 16-bit lane
|
||||||
|
v = a | ((v - (a * 10u)) << 8u);
|
||||||
|
return v + 0x30303030u;
|
||||||
|
}
|
||||||
|
|
||||||
/// how the view's dump() writes a value
|
/// how the view's dump() writes a value
|
||||||
struct dump_style
|
struct dump_style
|
||||||
{
|
{
|
||||||
@@ -7059,6 +7338,7 @@ class view_serializer
|
|||||||
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
std::vector<frame> heap;
|
std::vector<frame> heap;
|
||||||
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||||
|
float_batch batch;
|
||||||
const char* const src = m_doc.src;
|
const char* const src = m_doc.src;
|
||||||
const char* const src_end = src + m_doc.size;
|
const char* const src_end = src + m_doc.size;
|
||||||
char* w = m_out.cursor();
|
char* w = m_out.cursor();
|
||||||
@@ -7198,7 +7478,7 @@ class view_serializer
|
|||||||
else if (enabled(fast_double::value))
|
else if (enabled(fast_double::value))
|
||||||
{
|
{
|
||||||
room(64);
|
room(64);
|
||||||
w = write_double_at(w, *n);
|
w = write_double_ahead(w, src, n, batch);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -7223,6 +7503,20 @@ class view_serializer
|
|||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||||
|
// (read-only documents, compact output) a pair of floats,
|
||||||
|
// like the coordinates of GeoJSON, without a frame
|
||||||
|
if (enabled(!Editable && !Pretty && !SourceNumbers && fast_double::value) && n->next == 3 && n->len == 2
|
||||||
|
&& n[1].kind == static_cast<std::uint8_t>(value_t::number_float) && n[2].kind == static_cast<std::uint8_t>(value_t::number_float))
|
||||||
|
{
|
||||||
|
room(2 * 64);
|
||||||
|
*w++ = '[';
|
||||||
|
w = write_double_ahead(w, src, n + 1, batch);
|
||||||
|
*w++ = ',';
|
||||||
|
w = write_double_ahead(w, src, n + 2, batch);
|
||||||
|
*w++ = ']';
|
||||||
|
n += 2; // (the element before the next node)
|
||||||
|
break;
|
||||||
|
}
|
||||||
room(8);
|
room(8);
|
||||||
if (n->len == 0)
|
if (n->len == 0)
|
||||||
{
|
{
|
||||||
@@ -7332,6 +7626,254 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (float_batch) the mark of digits whose rounding carried beyond the
|
||||||
|
/// token's last four (see mark())
|
||||||
|
static constexpr std::uint64_t carried() noexcept
|
||||||
|
{
|
||||||
|
return std::uint64_t{1} << 63u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// floats of 16 or 17 digits ahead of the walk, converted together (see
|
||||||
|
/// convert_ahead()), with their digits as mark() keeps them
|
||||||
|
struct float_batch
|
||||||
|
{
|
||||||
|
static constexpr std::size_t capacity() noexcept
|
||||||
|
{
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
/// (on the heap, allocated by the first conversion: a frame of more
|
||||||
|
/// than a page made every dump of a small document slower)
|
||||||
|
struct entries
|
||||||
|
{
|
||||||
|
std::array<const node*, 256> at; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint64_t, 256> digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
};
|
||||||
|
std::unique_ptr<entries> storage{};
|
||||||
|
const node** at = nullptr;
|
||||||
|
std::uint64_t* digits = nullptr;
|
||||||
|
std::size_t count = 0;
|
||||||
|
std::size_t next = 0;
|
||||||
|
const node* scanned = nullptr; ///< where the last scan of the node array ended
|
||||||
|
};
|
||||||
|
|
||||||
|
/// (doubles) the float at n as dump() writes it, at w (64 bytes of room):
|
||||||
|
/// from the batch if it holds n
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE char* write_double_ahead(char* w, const char* src, const node* n, float_batch& batch)
|
||||||
|
{
|
||||||
|
#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
|
// (digits 0: not written by the batch; carried(): rounding carried
|
||||||
|
// beyond the last four digits)
|
||||||
|
if (batch.next != batch.count && batch.at[batch.next] == n && batch.digits[batch.next] - 1 < carried() - 1)
|
||||||
|
{
|
||||||
|
return write_token_digits(w, src, *n, batch.digits[batch.next++]);
|
||||||
|
}
|
||||||
|
return write_double_beside(w, n, batch);
|
||||||
|
#else
|
||||||
|
static_cast<void>(src);
|
||||||
|
static_cast<void>(batch);
|
||||||
|
return write_double_at(w, *n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
|
/// write_double_ahead() for a float that is not the batch's next one (out
|
||||||
|
/// of line: the walk is faster with less code in its loop)
|
||||||
|
NLOHMANN_VIEW_NOINLINE char* write_double_beside(char* w, const node* n, float_batch& batch)
|
||||||
|
{
|
||||||
|
// (an editable document's walk can skip nodes of the array)
|
||||||
|
const std::less<const node*> before{}; // NOLINT(modernize-use-transparent-functors) C++11 has no std::less<>
|
||||||
|
while (batch.next != batch.count && before(batch.at[batch.next], n))
|
||||||
|
{
|
||||||
|
++batch.next;
|
||||||
|
}
|
||||||
|
if ((batch.next != batch.count && batch.at[batch.next] == n) || convert_ahead(n, batch))
|
||||||
|
{
|
||||||
|
const std::uint64_t digits = batch.digits[batch.next++];
|
||||||
|
if ((digits & carried()) != 0)
|
||||||
|
{
|
||||||
|
return write_carried_digits(w, *n, digits & ~carried());
|
||||||
|
}
|
||||||
|
if (digits != 0)
|
||||||
|
{
|
||||||
|
return write_token_digits(w, m_doc.src, *n, digits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return write_double_at(w, *n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert the floats of 16 or 17 digits among the next nodes of the
|
||||||
|
array at once; whether n is the first of them
|
||||||
|
|
||||||
|
Such tokens (like the coordinates of GeoJSON, written with 17 digits) are
|
||||||
|
written as the token with its last four digits replaced
|
||||||
|
(write_token_digits()). Each step (the digits, locate_token(),
|
||||||
|
token_digits_1/2()) is a long chain of dependent operations; one loop per
|
||||||
|
step over many floats lets the processor overlap the floats, which it does
|
||||||
|
not across the steps of one float (on Apple M1, one loop for the digits and
|
||||||
|
locate_token() made the dump of canada.json 12 % slower). Each node of the array
|
||||||
|
is scanned once; a node that the walk does not reach in order (in an
|
||||||
|
editable document) is written without the batch.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_NOINLINE bool convert_ahead(const node* n, float_batch& batch)
|
||||||
|
{
|
||||||
|
const std::less<const node*> before{}; // NOLINT(modernize-use-transparent-functors) C++11 has no std::less<>
|
||||||
|
const node* const last = m_doc.nodes + m_doc.node_count;
|
||||||
|
if (before(n, batch.scanned) || before(n, m_doc.nodes) || !before(n, last))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const char* const src = m_doc.src;
|
||||||
|
const char* const src_end = src + m_doc.size;
|
||||||
|
const node* const stop = last - n > 1024 ? n + 1024 : last;
|
||||||
|
batch.count = 0;
|
||||||
|
batch.next = 0;
|
||||||
|
// the digits of each token (and its last four from the text)
|
||||||
|
std::array<std::uint64_t, 256> digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<int, 256> exponent; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint32_t, 256> last_four; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
const node* p = n;
|
||||||
|
if (!batch.storage)
|
||||||
|
{
|
||||||
|
batch.storage.reset(new typename float_batch::entries); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique): C++11
|
||||||
|
batch.at = batch.storage->at.data();
|
||||||
|
batch.digits = batch.storage->digits.data();
|
||||||
|
}
|
||||||
|
for (; p != stop && batch.count != float_batch::capacity(); ++p)
|
||||||
|
{
|
||||||
|
// a token of the source: 16 or 17 digits, at least 8 after the
|
||||||
|
// point, no exponent, 32 bytes to read
|
||||||
|
const unsigned frac_digits = p->extra >> 8u;
|
||||||
|
const unsigned int_digits = p->extra & 0xFFu;
|
||||||
|
const unsigned count = int_digits + frac_digits;
|
||||||
|
if (p->kind == static_cast<std::uint8_t>(value_t::number_float) && (p->flags & node_flags::storage) == 0
|
||||||
|
&& count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33)
|
||||||
|
{
|
||||||
|
const auto* const token = reinterpret_cast<const unsigned char*>(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
const unsigned sign = token[0] == '-' ? 1 : 0;
|
||||||
|
const unsigned char* const first = token + sign;
|
||||||
|
const std::uint64_t w = sixteen_digits(first, int_digits);
|
||||||
|
// (the 17th digit follows the point; a token with an exponent
|
||||||
|
// gets digits outside the range of locate_token(), and is not
|
||||||
|
// written by the batch. A token 0.xxx with 16 significant
|
||||||
|
// digits is: its double is written as "0." and its digits too)
|
||||||
|
const std::uint64_t w17 = (w * 10) + static_cast<std::uint64_t>(first[17] - '0');
|
||||||
|
const std::size_t i = batch.count++;
|
||||||
|
batch.at[i] = p;
|
||||||
|
if (p->len != sign + count + 1)
|
||||||
|
{
|
||||||
|
digits[i] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
digits[i] = count == 17 ? w17 : w;
|
||||||
|
}
|
||||||
|
exponent[i] = -static_cast<int>(frac_digits);
|
||||||
|
// (byte 0: the last digit, byte 1: the last two, bits 16-29: the last four)
|
||||||
|
std::uint32_t four = 0;
|
||||||
|
std::memcpy(&four, token + p->len - 4, 4);
|
||||||
|
four -= 0x30303030u;
|
||||||
|
const std::uint32_t pairs = ((four & 0x0F0F0F0Fu) * 2561u) >> 8u; // (byte 0, 2: the pairs)
|
||||||
|
last_four[i] = (four >> 24u) | ((pairs >> 16u & 0xFFu) << 8u) | ((((pairs & 0x00FF00FFu) * 6553601u) >> 16u) << 16u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batch.scanned = p;
|
||||||
|
// the position of each double, and the floats of either kind
|
||||||
|
std::array<std::uint64_t, 256> offset; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint64_t, 256> radius; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint8_t, 256> kind_1; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<std::uint8_t, 256> kind_2; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::size_t count_1 = 0;
|
||||||
|
std::size_t count_2 = 0;
|
||||||
|
for (std::size_t i = 0; i != batch.count; ++i)
|
||||||
|
{
|
||||||
|
const std::uint64_t w = digits[i];
|
||||||
|
// (the range excludes the values of bytes that are not digits, only
|
||||||
|
// in a damaged image loaded with image_check::bounds)
|
||||||
|
const int g = w - 1000000000000000u < 99000000000000000u ? locate_token(w, exponent[i], offset[i], radius[i]) : 3;
|
||||||
|
batch.digits[i] = g <= 0 ? (last_four[i] >> 16u) + 1 : 0;
|
||||||
|
kind_1[count_1] = static_cast<std::uint8_t>(i);
|
||||||
|
count_1 += g == 1 ? 1 : 0;
|
||||||
|
kind_2[count_2] = static_cast<std::uint8_t>(i);
|
||||||
|
count_2 += g == 2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
// the digits to write, in one loop per kind (see mark())
|
||||||
|
for (std::size_t k = 0; k != count_1; ++k)
|
||||||
|
{
|
||||||
|
const std::size_t i = kind_1[k];
|
||||||
|
const std::uint64_t s = token_digits_1(digits[i], offset[i], radius[i], last_four[i] & 0xFFu);
|
||||||
|
batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u);
|
||||||
|
}
|
||||||
|
for (std::size_t k = 0; k != count_2; ++k)
|
||||||
|
{
|
||||||
|
const std::size_t i = kind_2[k];
|
||||||
|
const std::uint64_t s = token_digits_2(digits[i], offset[i], radius[i], last_four[i] & 0xFFu, (last_four[i] >> 8u) & 0xFFu);
|
||||||
|
batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u);
|
||||||
|
}
|
||||||
|
return batch.count != 0 && batch.at[0] == n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes
|
||||||
|
of room), from the digits of its double (see locate_token()) whose
|
||||||
|
rounding carried beyond the token's last four digits
|
||||||
|
|
||||||
|
(like 43.42027299999999 -> 43.420273): the digits go to the writer of
|
||||||
|
Zmij's results as its 15 or 16 leading digits and one more, which omits
|
||||||
|
the trailing zeros.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_NOINLINE char* write_carried_digits(char* w, const node& n, std::uint64_t digits) const noexcept
|
||||||
|
{
|
||||||
|
const int exponent = -(n.extra >> 8u);
|
||||||
|
const std::uint64_t leading = digits / 10;
|
||||||
|
const auto last = static_cast<unsigned char>(digits - (leading * 10));
|
||||||
|
const bool seventeen = digits >= 10000000000000000u;
|
||||||
|
*w = '-';
|
||||||
|
w += m_doc.src[n.off] == '-' ? 1 : 0;
|
||||||
|
return ::nlohmann::detail::dtoa_impl::write_shortest(w, seventeen ? ::nlohmann::detail::zmij::shortest_decimal{leading, exponent, last, last != 0}
|
||||||
|
: ::nlohmann::detail::zmij::shortest_decimal{digits, exponent - 1, 0, false});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the digits s of a token with the digits w and the last four digits
|
||||||
|
/// r4, as the batch keeps them: the new last four digits + 1 (only those
|
||||||
|
/// are written over the token's), or s marked if it differs from w beyond
|
||||||
|
/// them (0 stays for a float the batch does not write)
|
||||||
|
static std::uint64_t mark(std::uint64_t w, std::uint64_t s, std::uint64_t r4) noexcept
|
||||||
|
{
|
||||||
|
const std::uint64_t last = s - (w - r4);
|
||||||
|
return last < 10000 ? last + 1 : s | carried();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes
|
||||||
|
of room): the token with its last four digits replaced by digits - 1 (see
|
||||||
|
mark(); the others are the token's), and without trailing zeros after the
|
||||||
|
point, but one
|
||||||
|
*/
|
||||||
|
char* write_token_digits(char* w, const char* src, const node& n, std::uint64_t digits)
|
||||||
|
{
|
||||||
|
const char* const token = src + n.off;
|
||||||
|
std::memcpy(w, token, 32);
|
||||||
|
char* const end = w + n.len;
|
||||||
|
const std::uint32_t last = four_digit_text(static_cast<std::uint32_t>(digits - 1));
|
||||||
|
std::memcpy(end - 4, &last, 4);
|
||||||
|
// (the token's text before the new digits, from the source: reading
|
||||||
|
// the output back would wait for the stores)
|
||||||
|
std::uint64_t before = 0;
|
||||||
|
std::memcpy(&before, token + n.len - 12, 8);
|
||||||
|
const std::uint32_t last_zeros = last ^ 0x30303030u;
|
||||||
|
const std::uint64_t before_zeros = before ^ 0x3030303030303030u;
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(last_zeros == 0 && before_zeros == 0))
|
||||||
|
{
|
||||||
|
return write_double_at(w, n); // (twelve or more trailing zeros)
|
||||||
|
}
|
||||||
|
const unsigned zeros = last_zeros != 0 ? (static_cast<unsigned>(count_leading_zeros(last_zeros)) / 8) - 4
|
||||||
|
: 4 + (static_cast<unsigned>(count_leading_zeros(before_zeros)) / 8);
|
||||||
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
|
return end - (zeros < frac_digits ? zeros : frac_digits - 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// a float node as dump() writes it
|
/// a float node as dump() writes it
|
||||||
void write_float_node(const node& n)
|
void write_float_node(const node& n)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ using json_view_test::has_duplicate_keys;
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// These tests were split off unit-json_view.cpp, whose object file got too large
|
// These tests were split off unit-json_view.cpp, whose object file got too large for the MinGW
|
||||||
// for the MinGW linker (see json_view_test_helpers.hpp). Like that file, this one
|
// linker (see json_view_test_helpers.hpp). Like that file, this one is also built
|
||||||
// is also built with C++17, as it mentions JSON_HAS_CPP_17.
|
// with C++17, as it mentions JSON_HAS_CPP_17.
|
||||||
|
|
||||||
TEST_CASE("json_view dump")
|
TEST_CASE("json_view dump")
|
||||||
{
|
{
|
||||||
@@ -150,6 +150,88 @@ TEST_CASE("json_view dump")
|
|||||||
CHECK(many_doc.root().dump() == json::parse(many_tokens).dump());
|
CHECK(many_doc.root().dump() == json::parse(many_tokens).dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tokens of 16 and 17 digits without an exponent (like GeoJSON
|
||||||
|
// coordinates), which dump() converts in batches: doubles exactly
|
||||||
|
// halfway between two candidates for the shortest digits, random
|
||||||
|
// digits, and digits near a shorter decimal (...99999997,
|
||||||
|
// ...00000003), whose rounding carries; in pairs and among other
|
||||||
|
// values, so that the batches are interrupted; the last token near the
|
||||||
|
// end of the text
|
||||||
|
{
|
||||||
|
std::mt19937_64 tokens(5627); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
|
const auto draw = [&tokens](std::size_t n)
|
||||||
|
{
|
||||||
|
const std::uint64_t r = tokens() % n;
|
||||||
|
return static_cast<std::size_t>(r);
|
||||||
|
};
|
||||||
|
std::string coordinates = "[754570942.33984373,257769445.56835938,858106901.94921875,97944964.876953125,"
|
||||||
|
"[0.5,1e-7],[1.5,-0.0,2.5],[1E2,12.5,0.1,3.25,4.5,5.75,6.125,7.0,8.5],[],[1.5],[1,2.5],[2.5,\"x\"],[[1.5],2.5],";
|
||||||
|
for (int i = 0; i < 20000; ++i)
|
||||||
|
{
|
||||||
|
const std::size_t length = 16 + draw(2);
|
||||||
|
std::string digits(1, static_cast<char>('1' + draw(9)));
|
||||||
|
const std::size_t kept = draw(3) == 0 ? length : 2 + draw(length - 3);
|
||||||
|
for (std::size_t k = 1; k < kept; ++k)
|
||||||
|
{
|
||||||
|
digits += static_cast<char>('0' + draw(10));
|
||||||
|
}
|
||||||
|
digits += std::string(length - kept, draw(2) == 0 ? '9' : '0');
|
||||||
|
digits.back() = static_cast<char>('0' + draw(10));
|
||||||
|
const std::size_t point = 1 + draw(9);
|
||||||
|
const std::string token = (draw(2) == 0 ? "-" : "") + digits.substr(0, point) + "." + digits.substr(point);
|
||||||
|
if (i != 0)
|
||||||
|
{
|
||||||
|
coordinates += ',';
|
||||||
|
}
|
||||||
|
if (draw(8) == 0)
|
||||||
|
{
|
||||||
|
coordinates += R"({"x":)";
|
||||||
|
coordinates += token;
|
||||||
|
coordinates += R"(,"s":"a"})";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
coordinates += '[';
|
||||||
|
coordinates += token;
|
||||||
|
coordinates += ',';
|
||||||
|
coordinates += token;
|
||||||
|
coordinates += ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coordinates += ",-65.613616999999977]";
|
||||||
|
const json_document parsed = json_document::parse(coordinates);
|
||||||
|
const ordered_json j = ordered_json::parse(coordinates);
|
||||||
|
CHECK(parsed.root().dump() == j.dump());
|
||||||
|
CHECK(parsed.root().dump(1) == j.dump(1));
|
||||||
|
CHECK(parsed.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(parsed.root().back().get<double>() == j.back().get<double>());
|
||||||
|
CHECK(parsed.root().back().dump() == "-65.61361699999998");
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (const json_view v : parsed.root())
|
||||||
|
{
|
||||||
|
const ordered_json& e = j[i++];
|
||||||
|
CHECK(v.dump() == e.dump());
|
||||||
|
if (v.is_array() && !v.empty() && v[0].is_number())
|
||||||
|
{
|
||||||
|
CHECK(v[0].get<double>() == e[0].get<double>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// an editable document: edits move some arrays, so that the walk
|
||||||
|
// leaves the order of the parsed nodes
|
||||||
|
ordered_json_editable_document editable = ordered_json_editable_document::parse(coordinates);
|
||||||
|
ordered_json expected = j;
|
||||||
|
for (std::size_t k = 0; k < 20000; k += 97)
|
||||||
|
{
|
||||||
|
if (expected[k].is_array())
|
||||||
|
{
|
||||||
|
editable.push_back(editable.root()[k], ordered_json(1.5));
|
||||||
|
expected[k].push_back(1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CHECK(editable.root().dump() == expected.dump());
|
||||||
|
}
|
||||||
|
|
||||||
// random doubles, written as parse() and dump() would
|
// random doubles, written as parse() and dump() would
|
||||||
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
std::string many = "[";
|
std::string many = "[";
|
||||||
@@ -171,768 +253,4 @@ TEST_CASE("json_view dump")
|
|||||||
const nlohmann::basic_json_document<json_float> float_document = nlohmann::basic_json_document<json_float>::parse("[0.1, 1.5e10, 3.4028235e38]");
|
const nlohmann::basic_json_document<json_float> float_document = nlohmann::basic_json_document<json_float>::parse("[0.1, 1.5e10, 3.4028235e38]");
|
||||||
CHECK(float_document.root().dump() == json_float::parse("[0.1, 1.5e10, 3.4028235e38]").dump());
|
CHECK(float_document.root().dump() == json_float::parse("[0.1, 1.5e10, 3.4028235e38]").dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("members in document order, all of them")
|
|
||||||
{
|
|
||||||
const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})");
|
|
||||||
CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})");
|
|
||||||
CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}");
|
|
||||||
// dump() writes all members, though the lookup finds the first
|
|
||||||
CHECK(d.root()["b"].dump() == "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep nesting")
|
|
||||||
{
|
|
||||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
|
||||||
const json_document deep_document = json_document::parse(deep);
|
|
||||||
CHECK(deep_document.root().dump() == deep);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ensure_ascii: a character that needs an escape at each position of a string")
|
|
||||||
{
|
|
||||||
// string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text
|
|
||||||
for (const char* const special :
|
|
||||||
{
|
|
||||||
"\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
for (std::size_t length = 0; length <= 40; ++length)
|
|
||||||
{
|
|
||||||
for (std::size_t position = 0; position <= length; ++position)
|
|
||||||
{
|
|
||||||
std::string content(length, 'a');
|
|
||||||
content.insert(position, special);
|
|
||||||
const std::array<std::string, 3> texts =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
"\"" + content + "\"",
|
|
||||||
"[\"" + content + "\"]",
|
|
||||||
"{\"" + content + "\":1}",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (const std::string& text : texts)
|
|
||||||
{
|
|
||||||
CAPTURE(text)
|
|
||||||
const ordered_json_document d = ordered_json_document::parse(text);
|
|
||||||
const ordered_json j = ordered_json::parse(text);
|
|
||||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
|
||||||
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
|
||||||
CHECK(d.root().dump() == j.dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond")
|
|
||||||
{
|
|
||||||
// (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF)
|
|
||||||
std::string text = "[";
|
|
||||||
std::size_t in_chunk = 0;
|
|
||||||
const auto append = [&](std::uint32_t cp)
|
|
||||||
{
|
|
||||||
if (cp < 0x80)
|
|
||||||
{
|
|
||||||
if (cp < 0x20 || cp == '"' || cp == '\\')
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
text += static_cast<char>(cp);
|
|
||||||
}
|
|
||||||
else if (cp < 0x800)
|
|
||||||
{
|
|
||||||
text += static_cast<char>(0xC0 | (cp >> 6));
|
|
||||||
text += static_cast<char>(0x80 | (cp & 0x3F));
|
|
||||||
}
|
|
||||||
else if (cp < 0x10000)
|
|
||||||
{
|
|
||||||
text += static_cast<char>(0xE0 | (cp >> 12));
|
|
||||||
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
|
||||||
text += static_cast<char>(0x80 | (cp & 0x3F));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text += static_cast<char>(0xF0 | (cp >> 18));
|
|
||||||
text += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
|
|
||||||
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
|
||||||
text += static_cast<char>(0x80 | (cp & 0x3F));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp)
|
|
||||||
{
|
|
||||||
if (cp >= 0xD800 && cp < 0xE000)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (in_chunk == 0)
|
|
||||||
{
|
|
||||||
text += text.size() > 1 ? ",\"" : "\"";
|
|
||||||
}
|
|
||||||
append(cp);
|
|
||||||
if (++in_chunk == 37)
|
|
||||||
{
|
|
||||||
text += "\"";
|
|
||||||
in_chunk = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text += in_chunk != 0 ? "\"]" : "]";
|
|
||||||
const ordered_json_document d = ordered_json_document::parse(text);
|
|
||||||
const ordered_json j = ordered_json::parse(text);
|
|
||||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
|
||||||
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
|
||||||
CHECK(d.root().dump() == j.dump());
|
|
||||||
CHECK(d.root().dump(2) == j.dump(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep nesting, indented")
|
|
||||||
{
|
|
||||||
// indentations of up to and beyond the width of the writer's fixed-size copy
|
|
||||||
for (const int depth :
|
|
||||||
{
|
|
||||||
1, 5, 20, 70, 300
|
|
||||||
})
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
for (int i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
text += i % 2 == 0 ? "[1," : R"({"k":2,"\u00e9":)";
|
|
||||||
}
|
|
||||||
text += "[]";
|
|
||||||
for (int i = depth - 1; i >= 0; --i)
|
|
||||||
{
|
|
||||||
text += i % 2 == 0 ? "]" : "}";
|
|
||||||
}
|
|
||||||
const ordered_json_document d = ordered_json_document::parse(text);
|
|
||||||
const ordered_json j = ordered_json::parse(text);
|
|
||||||
for (const int indent :
|
|
||||||
{
|
|
||||||
0, 1, 2, 3, 8
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CHECK(d.root().dump(indent) == j.dump(indent));
|
|
||||||
CHECK(d.root().dump(indent, '\t') == j.dump(indent, '\t'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the output buffer of a small value is small")
|
|
||||||
{
|
|
||||||
// an escaped key after the value: its node lies in the arena, so the
|
|
||||||
// source extent of the value cannot be read from the next node
|
|
||||||
const std::string big(100000, 'a');
|
|
||||||
const std::string text = R"({"small":1,"list":[1,2,3],"k\n":")" + big + R"("})";
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const auto small = d.root()["small"].dump();
|
|
||||||
CHECK(small == "1");
|
|
||||||
CHECK(small.capacity() < 4096);
|
|
||||||
const auto list = d.root()["list"].dump();
|
|
||||||
CHECK(list == "[1,2,3]");
|
|
||||||
CHECK(list.capacity() < 4096);
|
|
||||||
CHECK(d.root()["list"].dump(2).capacity() < 4096);
|
|
||||||
|
|
||||||
// the whole document and the large value are unaffected
|
|
||||||
CHECK(d.root().dump() == ordered_json::parse(text).dump());
|
|
||||||
CHECK(d.root()["k\n"].dump() == "\"" + big + "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("output that outgrows the estimate")
|
|
||||||
{
|
|
||||||
// ensure_ascii writes six bytes for each two-byte character
|
|
||||||
std::string chars;
|
|
||||||
for (int i = 0; i < 5000; ++i)
|
|
||||||
{
|
|
||||||
chars += "\xC3\xA9";
|
|
||||||
}
|
|
||||||
const json_document d = json_document::parse(R"({"a":")" + chars + R"(","k\n":1})");
|
|
||||||
const json expected = json::parse("\"" + chars + "\"");
|
|
||||||
CHECK(d.root()["a"].dump(-1, ' ', true) == expected.dump(-1, ' ', true));
|
|
||||||
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ensure_ascii with strings of the source, decoded strings, and edited strings")
|
|
||||||
{
|
|
||||||
// plain ASCII, non-ASCII, escaped (decoded), and mixed strings, as values and as keys;
|
|
||||||
// the second text has a DEL, which ensure_ascii writes as an escape
|
|
||||||
for (const char* const del :
|
|
||||||
{"", "\x7F"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const std::string text = std::string("{\"plain\":\"abc\",\"caf\xC3\xA9\":\"\xC3\xA9\xE2\x82\xAC\xF0\x9F\x98\x80\",")
|
|
||||||
+ "\"esc\\n\\\"\":\"a\\tb\\u00e4\\ud83d\\ude00\",\"del\":\"a" + del + "b\",\"\":\"\"," // NOLINT(modernize-raw-string-literal)
|
|
||||||
+ "\"list\":[\"x\",\"\xC3\xBC\",\"\\\\\",\"long ASCII string with more than thirty-two bytes in it\","
|
|
||||||
+ "\"long string with more than thirty-two bytes and one non-ASCII byte \xC3\xB6\"],"
|
|
||||||
+ "\"n\":[1,2.5,-0,true,null]}";
|
|
||||||
const ordered_json j = ordered_json::parse(text);
|
|
||||||
const ordered_json_document d = ordered_json_document::parse(text);
|
|
||||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
|
||||||
CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true));
|
|
||||||
CHECK(d.root().dump() == j.dump());
|
|
||||||
CHECK(d.root()["list"].dump(-1, ' ', true) == j["list"].dump(-1, ' ', true));
|
|
||||||
CHECK(d.root()["del"].dump(-1, ' ', true) == j["del"].dump(-1, ' ', true));
|
|
||||||
|
|
||||||
// strings that an edit wrote (the source strings that they replace were plain)
|
|
||||||
ordered_json_editable_document e = ordered_json_editable_document::parse(text);
|
|
||||||
ordered_json k = j;
|
|
||||||
e.set(e.root()["plain"], std::string("\xC3\xA4gypten"));
|
|
||||||
k["plain"] = "\xC3\xA4gypten";
|
|
||||||
e.set(e.root()["list"][0], std::string("ascii after edit"));
|
|
||||||
k["list"][0] = "ascii after edit";
|
|
||||||
e.set(e.root()["list"][1], std::string("\xC3\xA4"));
|
|
||||||
k["list"][1] = "\xC3\xA4";
|
|
||||||
e.set(e.root()["del"], std::string("tab\there"));
|
|
||||||
k["del"] = "tab\there";
|
|
||||||
CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true));
|
|
||||||
CHECK(e.root().dump(2, ' ', true) == k.dump(2, ' ', true));
|
|
||||||
CHECK(e.root().dump() == k.dump());
|
|
||||||
|
|
||||||
// strings that were edited from or to ASCII text
|
|
||||||
e.set(e.root()["list"][2], std::string("\xC3\xA4"));
|
|
||||||
k["list"][2] = "\xC3\xA4";
|
|
||||||
e.set(e.root()["list"][4], std::string("plain again"));
|
|
||||||
k["list"][4] = "plain again";
|
|
||||||
CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ensure_ascii with DEL or a non-ASCII character at every position")
|
|
||||||
{
|
|
||||||
// the check whether a string of the source has no byte >= 0x7F reads
|
|
||||||
// eight bytes at a time and then the rest one by one
|
|
||||||
for (std::size_t length = 1; length <= 20; ++length)
|
|
||||||
{
|
|
||||||
for (std::size_t pos = 0; pos < length; ++pos)
|
|
||||||
{
|
|
||||||
for (const char* const c :
|
|
||||||
{
|
|
||||||
"\x7F", "\x7E", "\xC3\xA4", "\xC3\xBF" // NOLINT(modernize-raw-string-literal)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const std::string s = std::string(pos, 'a') + c + std::string(length - pos - 1, 'b');
|
|
||||||
std::string text = "[\"";
|
|
||||||
text.append(s).append("\",{\"").append(s).append("\":1}]");
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const json j = json::parse(text);
|
|
||||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
|
||||||
CHECK(d.root().dump(1, ' ', true) == j.dump(1, ' ', true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("streams and discarded views")
|
|
||||||
{
|
|
||||||
const json_document d = json_document::parse(R"({"a": [1, 2]})");
|
|
||||||
std::ostringstream compact;
|
|
||||||
compact << d.root();
|
|
||||||
CHECK(compact.str() == R"({"a":[1,2]})");
|
|
||||||
std::ostringstream pretty;
|
|
||||||
pretty << std::setw(2) << std::setfill('.') << d.root() << d.root()["a"];
|
|
||||||
CHECK(pretty.str() == "{\n..\"a\": [\n....1,\n....2\n..]\n}[1,2]");
|
|
||||||
CHECK(json_view().dump() == json(json::value_t::discarded).dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("json_view comparison")
|
|
||||||
{
|
|
||||||
SECTION("equality of the values parse() produces")
|
|
||||||
{
|
|
||||||
generator g;
|
|
||||||
std::vector<std::string> texts;
|
|
||||||
for (int i = 0; i < 600; ++i)
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
g.value(text, 0);
|
|
||||||
texts.push_back(text);
|
|
||||||
// the same value written differently: sorted keys, canonical numbers
|
|
||||||
texts.push_back(json::parse(text).dump(1));
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i + 2 < texts.size(); ++i)
|
|
||||||
{
|
|
||||||
for (std::size_t k = i; k < i + 3; ++k)
|
|
||||||
{
|
|
||||||
CAPTURE(texts[i])
|
|
||||||
CAPTURE(texts[k])
|
|
||||||
const json_document a = json_document::parse(texts[i]);
|
|
||||||
const json_document b = json_document::parse(texts[k]);
|
|
||||||
const json ja = json::parse(texts[i]);
|
|
||||||
const json jb = json::parse(texts[k]);
|
|
||||||
CHECK((a.root() == b.root()) == (ja == jb));
|
|
||||||
CHECK((a.root() != b.root()) == (ja != jb));
|
|
||||||
CHECK((a.root() == jb) == (ja == jb));
|
|
||||||
CHECK((jb == a.root()) == (ja == jb));
|
|
||||||
CHECK((a.root() != jb) == (ja != jb));
|
|
||||||
CHECK((jb != a.root()) == (ja != jb));
|
|
||||||
|
|
||||||
// ordered_json compares members in order
|
|
||||||
const ordered_json_document oa = ordered_json_document::parse(texts[i]);
|
|
||||||
const ordered_json_document ob = ordered_json_document::parse(texts[k]);
|
|
||||||
const ordered_json oja = ordered_json::parse(texts[i]);
|
|
||||||
const ordered_json ojb = ordered_json::parse(texts[k]);
|
|
||||||
CHECK((oa.root() == ob.root()) == (oja == ojb));
|
|
||||||
CHECK((oa.root() == ojb) == (oja == ojb));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("numbers, duplicate keys, member order")
|
|
||||||
{
|
|
||||||
const auto same = [](const char* x, const char* y)
|
|
||||||
{
|
|
||||||
const json_document dx = json_document::parse(x);
|
|
||||||
const json_document dy = json_document::parse(y);
|
|
||||||
return dx.root() == dy.root();
|
|
||||||
};
|
|
||||||
CHECK(same("1", "1.0"));
|
|
||||||
CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]"));
|
|
||||||
CHECK(!same("1", "1.5"));
|
|
||||||
CHECK(same("18446744073709551615", "18446744073709551615"));
|
|
||||||
CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})"));
|
|
||||||
CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})"));
|
|
||||||
CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})"));
|
|
||||||
CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})"));
|
|
||||||
CHECK(!same("[1, 2]", "[2, 1]"));
|
|
||||||
CHECK(!same("\"a\"", "\"b\""));
|
|
||||||
CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\""));
|
|
||||||
CHECK(!same("null", "false"));
|
|
||||||
CHECK(!same("[]", "{}"));
|
|
||||||
const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
|
||||||
const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
|
||||||
CHECK(dup.root() == last.root());
|
|
||||||
// (== compares as parse() resolves duplicates, a lookup finds the first)
|
|
||||||
CHECK(dup.root()["a"].materialize() == 1);
|
|
||||||
CHECK(dup.root()["a"] != last.root()["a"]);
|
|
||||||
const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
|
||||||
const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
|
||||||
CHECK(ab.root() != ba.root());
|
|
||||||
|
|
||||||
// discarded values compare as basic_json's do
|
|
||||||
const json discarded(json::value_t::discarded);
|
|
||||||
CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested
|
|
||||||
CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty)
|
|
||||||
const json_document null_document = json_document::parse("null");
|
|
||||||
CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty)
|
|
||||||
CHECK(!(null_document.root() == discarded));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep nesting")
|
|
||||||
{
|
|
||||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
|
||||||
const json_document a = json_document::parse(deep);
|
|
||||||
const json_document b = json_document::parse(deep);
|
|
||||||
CHECK(a.root() == b.root());
|
|
||||||
CHECK(a.root() == json::parse(deep));
|
|
||||||
const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']');
|
|
||||||
const json_document c = json_document::parse(other);
|
|
||||||
CHECK(a.root() != c.root());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("json_view large objects")
|
|
||||||
{
|
|
||||||
// objects with 128 members or more are looked up with a hash index
|
|
||||||
for (const std::size_t members :
|
|
||||||
{
|
|
||||||
127u, 128u, 129u, 10000u
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(members)
|
|
||||||
std::string text = "{";
|
|
||||||
for (std::size_t i = 0; i < members; ++i)
|
|
||||||
{
|
|
||||||
text += (i != 0 ? ",\"" : "\"") + std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\\n" : "") + "\":" + std::to_string(i);
|
|
||||||
}
|
|
||||||
text += R"(,"":"empty key","k1":"a duplicate of an earlier key"})";
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const json_view v = d.root();
|
|
||||||
const json j = json::parse(text);
|
|
||||||
for (std::size_t i = 0; i < members; ++i)
|
|
||||||
{
|
|
||||||
const std::string key = std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\n" : "");
|
|
||||||
CHECK(v[key].get<std::size_t>() == i);
|
|
||||||
CHECK(v.contains(key));
|
|
||||||
CHECK(v.find(key).key() == key);
|
|
||||||
CHECK(v.at(key).get<std::size_t>() == i);
|
|
||||||
CHECK(!v.contains(key + "x"));
|
|
||||||
}
|
|
||||||
CHECK(v[""].get_string() == "empty key");
|
|
||||||
CHECK(v["k1"].get<int>() == 1); // the first of duplicate keys, as for small objects
|
|
||||||
CHECK(!v.contains("missing"));
|
|
||||||
CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
|
|
||||||
CHECK(v == j);
|
|
||||||
CHECK(v.materialize() == j);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("duplicate keys: the first member wins, with and without a table")
|
|
||||||
{
|
|
||||||
// an object of `total` members: the keys "k0".."k<n-1>" in order, then
|
|
||||||
// three keys repeated twice more (one copy in the middle, one at the
|
|
||||||
// end), and two keys repeated once; the value of a member is its
|
|
||||||
// position, so that the first member of a key can be told apart
|
|
||||||
struct member
|
|
||||||
{
|
|
||||||
std::string key;
|
|
||||||
std::size_t position;
|
|
||||||
};
|
|
||||||
const auto make_members = [](std::size_t total)
|
|
||||||
{
|
|
||||||
std::vector<std::string> keys;
|
|
||||||
for (std::size_t i = 0; i + 8 < total; ++i)
|
|
||||||
{
|
|
||||||
keys.push_back("k" + std::to_string(i));
|
|
||||||
}
|
|
||||||
const std::size_t n = keys.size();
|
|
||||||
const std::array<std::size_t, 3> triple = {{3, 17, n - 1}};
|
|
||||||
const std::array<std::size_t, 2> twice = {{5, n / 2}};
|
|
||||||
std::vector<std::string> ordered = keys;
|
|
||||||
for (const std::size_t i : triple)
|
|
||||||
{
|
|
||||||
ordered.insert(ordered.begin() + static_cast<std::ptrdiff_t>(ordered.size() / 2), keys[i]);
|
|
||||||
}
|
|
||||||
for (const std::size_t i : twice)
|
|
||||||
{
|
|
||||||
ordered.insert(ordered.begin() + static_cast<std::ptrdiff_t>(ordered.size() / 3), keys[i]);
|
|
||||||
}
|
|
||||||
for (const std::size_t i : triple)
|
|
||||||
{
|
|
||||||
ordered.push_back(keys[i]);
|
|
||||||
}
|
|
||||||
std::vector<member> result;
|
|
||||||
result.reserve(ordered.size());
|
|
||||||
for (std::size_t i = 0; i < ordered.size(); ++i)
|
|
||||||
{
|
|
||||||
result.push_back({ordered[i], i});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 100 and 127 members: no table; 128 members and more: a table
|
|
||||||
for (const std::size_t total :
|
|
||||||
{
|
|
||||||
100u, 127u, 128u, 200u, 5000u
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(total)
|
|
||||||
const std::vector<member> members = make_members(total);
|
|
||||||
REQUIRE(members.size() >= total);
|
|
||||||
REQUIRE(members.size() >= 100);
|
|
||||||
std::string text = "{";
|
|
||||||
std::map<std::string, std::size_t> first;
|
|
||||||
std::map<std::string, std::size_t> last;
|
|
||||||
std::map<std::string, std::size_t> occurrences;
|
|
||||||
for (const member& m : members)
|
|
||||||
{
|
|
||||||
text += (text.size() > 1 ? ",\"" : "\"") + m.key + "\":" + std::to_string(m.position);
|
|
||||||
first.insert({m.key, m.position});
|
|
||||||
last[m.key] = m.position;
|
|
||||||
++occurrences[m.key];
|
|
||||||
}
|
|
||||||
text += '}';
|
|
||||||
REQUIRE(first.size() < members.size());
|
|
||||||
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const json_view v = d.root();
|
|
||||||
const json j = json::parse(text);
|
|
||||||
CHECK(v.size() == members.size()); // every occurrence is visited
|
|
||||||
const json m = v.materialize();
|
|
||||||
for (const auto& entry : first)
|
|
||||||
{
|
|
||||||
CAPTURE(entry.first)
|
|
||||||
const std::size_t expected = entry.second;
|
|
||||||
CHECK(v[entry.first].get<std::size_t>() == expected);
|
|
||||||
CHECK(v.at(entry.first).get<std::size_t>() == expected);
|
|
||||||
CHECK(v.find(entry.first).value().get<std::size_t>() == expected);
|
|
||||||
CHECK(v.value(entry.first, std::size_t{0}) == expected);
|
|
||||||
CHECK(v.contains(entry.first));
|
|
||||||
CHECK(v.count(entry.first) == occurrences[entry.first]);
|
|
||||||
const std::string pointer = "/" + entry.first;
|
|
||||||
CHECK(v[json::json_pointer(pointer)].get<std::size_t>() == expected);
|
|
||||||
CHECK(v.at(json::json_pointer(pointer)).get<std::size_t>() == expected);
|
|
||||||
CHECK(v.value(json::json_pointer(pointer), std::size_t{0}) == expected);
|
|
||||||
CHECK(v.contains(json::json_pointer(pointer)));
|
|
||||||
// materialize() and parse() keep the last value instead
|
|
||||||
CHECK(j[entry.first].get<std::size_t>() == last.at(entry.first));
|
|
||||||
CHECK(m[entry.first].get<std::size_t>() == last.at(entry.first));
|
|
||||||
}
|
|
||||||
CHECK(!v.contains("k"));
|
|
||||||
CHECK(v["missing"].is_discarded());
|
|
||||||
CHECK(v.materialize() == j);
|
|
||||||
CHECK(v == j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("colliding keys")
|
|
||||||
{
|
|
||||||
// the hash is not seeded, so keys that all land in one place must not
|
|
||||||
// make the table build quadratic: such an object gets no table and is
|
|
||||||
// searched linearly
|
|
||||||
constexpr std::size_t members = 300;
|
|
||||||
constexpr std::size_t table_size = 1024; // the table size for 300 members: the next power of two >= 600
|
|
||||||
std::vector<std::string> colliding;
|
|
||||||
std::vector<std::string> spread;
|
|
||||||
for (std::uint64_t counter = 0; colliding.size() < members || spread.size() < members; ++counter)
|
|
||||||
{
|
|
||||||
std::string key(8, 'a');
|
|
||||||
std::uint64_t x = counter;
|
|
||||||
for (std::size_t i = 0; i < 8; ++i, x /= 26)
|
|
||||||
{
|
|
||||||
key[i] = static_cast<char>('a' + (x % 26));
|
|
||||||
}
|
|
||||||
const bool lands_in_slot_zero = (nlohmann::detail::view::key_hash(key.data(), key.size()) & (table_size - 1)) == 0;
|
|
||||||
if (lands_in_slot_zero && colliding.size() < members)
|
|
||||||
{
|
|
||||||
colliding.push_back(key);
|
|
||||||
}
|
|
||||||
else if (!lands_in_slot_zero && spread.size() < members)
|
|
||||||
{
|
|
||||||
spread.push_back(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto make_text = [](const std::vector<std::string>& keys)
|
|
||||||
{
|
|
||||||
std::string text = "{";
|
|
||||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
|
||||||
{
|
|
||||||
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i % 10);
|
|
||||||
}
|
|
||||||
return text + "}";
|
|
||||||
};
|
|
||||||
const std::string colliding_text = make_text(colliding);
|
|
||||||
const std::string spread_text = make_text(spread);
|
|
||||||
|
|
||||||
json_document with_collisions = json_document::parse(colliding_text);
|
|
||||||
json_document without_collisions = json_document::parse(spread_text);
|
|
||||||
for (const auto* pair :
|
|
||||||
{
|
|
||||||
&colliding, &spread
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const json_view v = (pair == &colliding ? with_collisions : without_collisions).root();
|
|
||||||
for (std::size_t i = 0; i < members; ++i)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CHECK(v[(*pair)[i]].get<std::size_t>() == i % 10);
|
|
||||||
CHECK(v.at((*pair)[i]).get<std::size_t>() == i % 10);
|
|
||||||
CHECK(v.find((*pair)[i]).key() == (*pair)[i]);
|
|
||||||
CHECK(!v.contains((*pair)[i] + "x"));
|
|
||||||
}
|
|
||||||
CHECK(!v.contains("missing"));
|
|
||||||
}
|
|
||||||
CHECK(with_collisions.root() == json::parse(colliding_text));
|
|
||||||
|
|
||||||
// only the object with the spread keys got a table (both texts have the
|
|
||||||
// same length, so the tables are the only difference)
|
|
||||||
with_collisions.shrink_to_fit();
|
|
||||||
without_collisions.shrink_to_fit();
|
|
||||||
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("keys that differ in their last bytes are spread over the table")
|
|
||||||
{
|
|
||||||
// The hash must spread the usual families of keys (numbered names, hex
|
|
||||||
// strings, dates, short names): a key farther than index_max_displacement
|
|
||||||
// from its home slot would leave the object without a table. The keys
|
|
||||||
// are placed with linear probing, as the table does.
|
|
||||||
const auto max_displacement = [](const std::vector<std::string>& keys)
|
|
||||||
{
|
|
||||||
std::size_t slots = 16;
|
|
||||||
while (slots < 2 * keys.size())
|
|
||||||
{
|
|
||||||
slots *= 2;
|
|
||||||
}
|
|
||||||
std::vector<bool> used(slots, false);
|
|
||||||
std::size_t farthest = 0;
|
|
||||||
for (const std::string& key : keys)
|
|
||||||
{
|
|
||||||
std::size_t i = nlohmann::detail::conditional_static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1);
|
|
||||||
std::size_t distance = 0;
|
|
||||||
while (used[i])
|
|
||||||
{
|
|
||||||
i = (i + 1) & (slots - 1);
|
|
||||||
++distance;
|
|
||||||
}
|
|
||||||
used[i] = true;
|
|
||||||
farthest = (std::max)(farthest, distance);
|
|
||||||
}
|
|
||||||
return farthest;
|
|
||||||
};
|
|
||||||
// the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream
|
|
||||||
// (a format string that is not a literal is a Flawfinder error)
|
|
||||||
const auto numbered = [](const char* format, int count, int step)
|
|
||||||
{
|
|
||||||
const std::string pattern(format);
|
|
||||||
const std::size_t percent = pattern.find('%');
|
|
||||||
std::size_t conversion = percent + 1;
|
|
||||||
std::size_t width = 0;
|
|
||||||
while (pattern[conversion] >= '0' && pattern[conversion] <= '9')
|
|
||||||
{
|
|
||||||
width = (width * 10) + static_cast<std::size_t>(pattern[conversion] - '0');
|
|
||||||
++conversion;
|
|
||||||
}
|
|
||||||
const bool hex = pattern[conversion] == 'x';
|
|
||||||
std::vector<std::string> keys;
|
|
||||||
for (int i = 0; i < count; ++i)
|
|
||||||
{
|
|
||||||
std::ostringstream key;
|
|
||||||
key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast<int>(width))
|
|
||||||
<< (hex ? std::hex : std::dec) << (i * step) << pattern.substr(conversion + 1);
|
|
||||||
keys.push_back(key.str());
|
|
||||||
}
|
|
||||||
return keys;
|
|
||||||
};
|
|
||||||
for (const char* format :
|
|
||||||
{"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d",
|
|
||||||
"configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(format)
|
|
||||||
CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement); // NOLINT(abseil-string-find-str-contains)
|
|
||||||
CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement);
|
|
||||||
}
|
|
||||||
std::mt19937_64 rng(7); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
|
||||||
for (const std::size_t length :
|
|
||||||
{
|
|
||||||
std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(length)
|
|
||||||
std::set<std::string> distinct;
|
|
||||||
// (there are 26 and 676 keys of one and two letters)
|
|
||||||
std::size_t wanted = 700;
|
|
||||||
if (length == 1)
|
|
||||||
{
|
|
||||||
wanted = 26;
|
|
||||||
}
|
|
||||||
else if (length == 2)
|
|
||||||
{
|
|
||||||
wanted = 500;
|
|
||||||
}
|
|
||||||
while (distinct.size() < wanted)
|
|
||||||
{
|
|
||||||
std::string key(length, 'a');
|
|
||||||
for (char& c : key)
|
|
||||||
{
|
|
||||||
c = static_cast<char>('a' + (rng() % 26));
|
|
||||||
}
|
|
||||||
distinct.insert(key);
|
|
||||||
}
|
|
||||||
CHECK(max_displacement(std::vector<std::string>(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("keys of every length that differ in one byte")
|
|
||||||
{
|
|
||||||
// In a large object, a key is found by comparing its bytes with those of the member the
|
|
||||||
// table points to: up to 16 bytes as two overlapping words, longer ones with memcmp.
|
|
||||||
// Keys of every length from 0 to 40, and keys that differ from them in one byte.
|
|
||||||
std::mt19937_64 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
|
||||||
std::vector<std::string> keys;
|
|
||||||
std::set<std::string> distinct;
|
|
||||||
for (std::size_t length = 0; length <= 40; ++length)
|
|
||||||
{
|
|
||||||
for (int copy = 0; copy < 5; ++copy)
|
|
||||||
{
|
|
||||||
std::string key(length, 'a');
|
|
||||||
for (char& c : key)
|
|
||||||
{
|
|
||||||
c = static_cast<char>('a' + (rng() % 26));
|
|
||||||
}
|
|
||||||
if (distinct.insert(key).second)
|
|
||||||
{
|
|
||||||
keys.push_back(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::string text = "{";
|
|
||||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
|
||||||
{
|
|
||||||
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
|
|
||||||
}
|
|
||||||
text += "}";
|
|
||||||
REQUIRE(keys.size() >= 128); // (a table is built for 128 members)
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const json_view v = d.root();
|
|
||||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
|
||||||
{
|
|
||||||
CAPTURE(keys[i])
|
|
||||||
CHECK(v[keys[i]].get<std::size_t>() == i);
|
|
||||||
CHECK(v.at(keys[i]).get<std::size_t>() == i);
|
|
||||||
CHECK(v.contains(keys[i]));
|
|
||||||
for (std::size_t at = 0; at < keys[i].size(); ++at)
|
|
||||||
{
|
|
||||||
std::string other = keys[i];
|
|
||||||
other[at] = other[at] == 'z' ? 'y' : 'z';
|
|
||||||
CAPTURE(other)
|
|
||||||
CHECK(v.contains(other) == (distinct.count(other) != 0));
|
|
||||||
}
|
|
||||||
CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0));
|
|
||||||
if (!keys[i].empty())
|
|
||||||
{
|
|
||||||
CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("shrink_to_fit releases the tables' spare capacity")
|
|
||||||
{
|
|
||||||
const auto make_text = [](int objects, int members)
|
|
||||||
{
|
|
||||||
std::string text = "[";
|
|
||||||
for (int object = 0; object < objects; ++object)
|
|
||||||
{
|
|
||||||
text += object != 0 ? ",{" : "{";
|
|
||||||
for (int i = 0, n = members + object; i < n; ++i)
|
|
||||||
{
|
|
||||||
text += (i != 0 ? ",\"" : "\"") + std::to_string(i) + "\":" + std::to_string(i);
|
|
||||||
}
|
|
||||||
text += '}';
|
|
||||||
}
|
|
||||||
return text + "]";
|
|
||||||
};
|
|
||||||
const std::string small_text = make_text(5, 150);
|
|
||||||
const std::string big_text = make_text(40, 400);
|
|
||||||
|
|
||||||
// reading a big text, and then a small one, leaves the spare capacity
|
|
||||||
// of the big one: shrink_to_fit() brings the document to the size of
|
|
||||||
// one parsed from the small text alone
|
|
||||||
json_document d = json_document::parse(big_text);
|
|
||||||
const std::size_t big = d.memory_usage();
|
|
||||||
d.read(small_text);
|
|
||||||
CHECK(d.memory_usage() >= big);
|
|
||||||
d.shrink_to_fit();
|
|
||||||
json_document fresh = json_document::parse(small_text);
|
|
||||||
fresh.shrink_to_fit();
|
|
||||||
CHECK(d.memory_usage() == fresh.memory_usage());
|
|
||||||
CHECK(d.memory_usage() < big / 2);
|
|
||||||
CHECK(d.root() == json::parse(small_text));
|
|
||||||
for (int object = 0; object < 5; ++object)
|
|
||||||
{
|
|
||||||
const json_view v = d.root()[static_cast<std::size_t>(object)];
|
|
||||||
for (int i = 0, n = 150 + object; i < n; ++i)
|
|
||||||
{
|
|
||||||
CHECK(v[std::to_string(i)].get<int>() == i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("nested, reused, and in arrays")
|
|
||||||
{
|
|
||||||
std::string inner = "{";
|
|
||||||
for (int i = 0; i < 300; ++i)
|
|
||||||
{
|
|
||||||
inner += (i != 0 ? ",\"m" : "\"m") + std::to_string(i) + "\":" + std::to_string(i);
|
|
||||||
}
|
|
||||||
inner += '}';
|
|
||||||
const std::string text = "[" + inner + ",{\"x\":" + inner + "}," + inner + "]";
|
|
||||||
json_document d = json_document::parse(text);
|
|
||||||
CHECK(d.root()[0]["m299"].get<int>() == 299);
|
|
||||||
CHECK(d.root()[1]["x"]["m150"].get<int>() == 150);
|
|
||||||
CHECK(d.root()[2]["m0"].get<int>() == 0);
|
|
||||||
const std::size_t with_index = d.memory_usage();
|
|
||||||
d.read(std::string("{\"small\": 1}"));
|
|
||||||
CHECK(d.root()["small"].get<int>() == 1);
|
|
||||||
d.read(text);
|
|
||||||
CHECK(d.root()[2]["m7"].get<int>() == 7);
|
|
||||||
CHECK(d.memory_usage() >= with_index / 2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::ordered_json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::ordered_json_editable_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
using nlohmann::ordered_json_document;
|
||||||
|
using nlohmann::ordered_json_view;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <map>
|
||||||
|
#include <random>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// These tests were split off unit-json_view_dump.cpp, whose object file got too large for the MinGW
|
||||||
|
// linker (see json_view_test_helpers.hpp). Like that file, this one is also built
|
||||||
|
// with C++17, as it mentions JSON_HAS_CPP_17.
|
||||||
|
|
||||||
|
TEST_CASE("json_view dump: escaping, nesting, and output buffers")
|
||||||
|
{
|
||||||
|
SECTION("members in document order, all of them")
|
||||||
|
{
|
||||||
|
const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})");
|
||||||
|
CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})");
|
||||||
|
CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}");
|
||||||
|
// dump() writes all members, though the lookup finds the first
|
||||||
|
CHECK(d.root()["b"].dump() == "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting")
|
||||||
|
{
|
||||||
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
|
const json_document deep_document = json_document::parse(deep);
|
||||||
|
CHECK(deep_document.root().dump() == deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii: a character that needs an escape at each position of a string")
|
||||||
|
{
|
||||||
|
// string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text
|
||||||
|
for (const char* const special :
|
||||||
|
{
|
||||||
|
"\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (std::size_t length = 0; length <= 40; ++length)
|
||||||
|
{
|
||||||
|
for (std::size_t position = 0; position <= length; ++position)
|
||||||
|
{
|
||||||
|
std::string content(length, 'a');
|
||||||
|
content.insert(position, special);
|
||||||
|
const std::array<std::string, 3> texts =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"\"" + content + "\"",
|
||||||
|
"[\"" + content + "\"]",
|
||||||
|
"{\"" + content + "\":1}",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const std::string& text : texts)
|
||||||
|
{
|
||||||
|
CAPTURE(text)
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond")
|
||||||
|
{
|
||||||
|
// (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF)
|
||||||
|
std::string text = "[";
|
||||||
|
std::size_t in_chunk = 0;
|
||||||
|
const auto append = [&](std::uint32_t cp)
|
||||||
|
{
|
||||||
|
if (cp < 0x80)
|
||||||
|
{
|
||||||
|
if (cp < 0x20 || cp == '"' || cp == '\\')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
text += static_cast<char>(cp);
|
||||||
|
}
|
||||||
|
else if (cp < 0x800)
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xC0 | (cp >> 6));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
else if (cp < 0x10000)
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xE0 | (cp >> 12));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xF0 | (cp >> 18));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp)
|
||||||
|
{
|
||||||
|
if (cp >= 0xD800 && cp < 0xE000)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_chunk == 0)
|
||||||
|
{
|
||||||
|
text += text.size() > 1 ? ",\"" : "\"";
|
||||||
|
}
|
||||||
|
append(cp);
|
||||||
|
if (++in_chunk == 37)
|
||||||
|
{
|
||||||
|
text += "\"";
|
||||||
|
in_chunk = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += in_chunk != 0 ? "\"]" : "]";
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
CHECK(d.root().dump(2) == j.dump(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting, indented")
|
||||||
|
{
|
||||||
|
// indentations of up to and beyond the width of the writer's fixed-size copy
|
||||||
|
for (const int depth :
|
||||||
|
{
|
||||||
|
1, 5, 20, 70, 300
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
for (int i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += i % 2 == 0 ? "[1," : R"({"k":2,"\u00e9":)";
|
||||||
|
}
|
||||||
|
text += "[]";
|
||||||
|
for (int i = depth - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
text += i % 2 == 0 ? "]" : "}";
|
||||||
|
}
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
for (const int indent :
|
||||||
|
{
|
||||||
|
0, 1, 2, 3, 8
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(d.root().dump(indent) == j.dump(indent));
|
||||||
|
CHECK(d.root().dump(indent, '\t') == j.dump(indent, '\t'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the output buffer of a small value is small")
|
||||||
|
{
|
||||||
|
// an escaped key after the value: its node lies in the arena, so the
|
||||||
|
// source extent of the value cannot be read from the next node
|
||||||
|
const std::string big(100000, 'a');
|
||||||
|
const std::string text = R"({"small":1,"list":[1,2,3],"k\n":")" + big + R"("})";
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const auto small = d.root()["small"].dump();
|
||||||
|
CHECK(small == "1");
|
||||||
|
CHECK(small.capacity() < 4096);
|
||||||
|
const auto list = d.root()["list"].dump();
|
||||||
|
CHECK(list == "[1,2,3]");
|
||||||
|
CHECK(list.capacity() < 4096);
|
||||||
|
CHECK(d.root()["list"].dump(2).capacity() < 4096);
|
||||||
|
|
||||||
|
// the whole document and the large value are unaffected
|
||||||
|
CHECK(d.root().dump() == ordered_json::parse(text).dump());
|
||||||
|
CHECK(d.root()["k\n"].dump() == "\"" + big + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("output that outgrows the estimate")
|
||||||
|
{
|
||||||
|
// ensure_ascii writes six bytes for each two-byte character
|
||||||
|
std::string chars;
|
||||||
|
for (int i = 0; i < 5000; ++i)
|
||||||
|
{
|
||||||
|
chars += "\xC3\xA9";
|
||||||
|
}
|
||||||
|
const json_document d = json_document::parse(R"({"a":")" + chars + R"(","k\n":1})");
|
||||||
|
const json expected = json::parse("\"" + chars + "\"");
|
||||||
|
CHECK(d.root()["a"].dump(-1, ' ', true) == expected.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii with strings of the source, decoded strings, and edited strings")
|
||||||
|
{
|
||||||
|
// plain ASCII, non-ASCII, escaped (decoded), and mixed strings, as values and as keys;
|
||||||
|
// the second text has a DEL, which ensure_ascii writes as an escape
|
||||||
|
for (const char* const del :
|
||||||
|
{"", "\x7F"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string text = std::string("{\"plain\":\"abc\",\"caf\xC3\xA9\":\"\xC3\xA9\xE2\x82\xAC\xF0\x9F\x98\x80\",")
|
||||||
|
+ "\"esc\\n\\\"\":\"a\\tb\\u00e4\\ud83d\\ude00\",\"del\":\"a" + del + "b\",\"\":\"\"," // NOLINT(modernize-raw-string-literal)
|
||||||
|
+ "\"list\":[\"x\",\"\xC3\xBC\",\"\\\\\",\"long ASCII string with more than thirty-two bytes in it\","
|
||||||
|
+ "\"long string with more than thirty-two bytes and one non-ASCII byte \xC3\xB6\"],"
|
||||||
|
+ "\"n\":[1,2.5,-0,true,null]}";
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true));
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
CHECK(d.root()["list"].dump(-1, ' ', true) == j["list"].dump(-1, ' ', true));
|
||||||
|
CHECK(d.root()["del"].dump(-1, ' ', true) == j["del"].dump(-1, ' ', true));
|
||||||
|
|
||||||
|
// strings that an edit wrote (the source strings that they replace were plain)
|
||||||
|
ordered_json_editable_document e = ordered_json_editable_document::parse(text);
|
||||||
|
ordered_json k = j;
|
||||||
|
e.set(e.root()["plain"], std::string("\xC3\xA4gypten"));
|
||||||
|
k["plain"] = "\xC3\xA4gypten";
|
||||||
|
e.set(e.root()["list"][0], std::string("ascii after edit"));
|
||||||
|
k["list"][0] = "ascii after edit";
|
||||||
|
e.set(e.root()["list"][1], std::string("\xC3\xA4"));
|
||||||
|
k["list"][1] = "\xC3\xA4";
|
||||||
|
e.set(e.root()["del"], std::string("tab\there"));
|
||||||
|
k["del"] = "tab\there";
|
||||||
|
CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true));
|
||||||
|
CHECK(e.root().dump(2, ' ', true) == k.dump(2, ' ', true));
|
||||||
|
CHECK(e.root().dump() == k.dump());
|
||||||
|
|
||||||
|
// strings that were edited from or to ASCII text
|
||||||
|
e.set(e.root()["list"][2], std::string("\xC3\xA4"));
|
||||||
|
k["list"][2] = "\xC3\xA4";
|
||||||
|
e.set(e.root()["list"][4], std::string("plain again"));
|
||||||
|
k["list"][4] = "plain again";
|
||||||
|
CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii with DEL or a non-ASCII character at every position")
|
||||||
|
{
|
||||||
|
// the check whether a string of the source has no byte >= 0x7F reads
|
||||||
|
// eight bytes at a time and then the rest one by one
|
||||||
|
for (std::size_t length = 1; length <= 20; ++length)
|
||||||
|
{
|
||||||
|
for (std::size_t pos = 0; pos < length; ++pos)
|
||||||
|
{
|
||||||
|
for (const char* const c :
|
||||||
|
{
|
||||||
|
"\x7F", "\x7E", "\xC3\xA4", "\xC3\xBF" // NOLINT(modernize-raw-string-literal)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string s = std::string(pos, 'a') + c + std::string(length - pos - 1, 'b');
|
||||||
|
std::string text = "[\"";
|
||||||
|
text.append(s).append("\",{\"").append(s).append("\":1}]");
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const json j = json::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(1, ' ', true) == j.dump(1, ' ', true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("streams and discarded views")
|
||||||
|
{
|
||||||
|
const json_document d = json_document::parse(R"({"a": [1, 2]})");
|
||||||
|
std::ostringstream compact;
|
||||||
|
compact << d.root();
|
||||||
|
CHECK(compact.str() == R"({"a":[1,2]})");
|
||||||
|
std::ostringstream pretty;
|
||||||
|
pretty << std::setw(2) << std::setfill('.') << d.root() << d.root()["a"];
|
||||||
|
CHECK(pretty.str() == "{\n..\"a\": [\n....1,\n....2\n..]\n}[1,2]");
|
||||||
|
CHECK(json_view().dump() == json(json::value_t::discarded).dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,545 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::ordered_json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::ordered_json_editable_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
using nlohmann::ordered_json_document;
|
||||||
|
using nlohmann::ordered_json_view;
|
||||||
|
|
||||||
|
#include "json_view_test_helpers.hpp"
|
||||||
|
using json_view_test::generator;
|
||||||
|
using json_view_test::has_duplicate_keys;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <map>
|
||||||
|
#include <random>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// These tests were split off unit-json_view_dump.cpp, whose object file got too large for the MinGW
|
||||||
|
// linker (see json_view_test_helpers.hpp). Like that file, this one is also built
|
||||||
|
// with C++17, as it mentions JSON_HAS_CPP_17.
|
||||||
|
|
||||||
|
TEST_CASE("json_view comparison")
|
||||||
|
{
|
||||||
|
SECTION("equality of the values parse() produces")
|
||||||
|
{
|
||||||
|
generator g;
|
||||||
|
std::vector<std::string> texts;
|
||||||
|
for (int i = 0; i < 600; ++i)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
g.value(text, 0);
|
||||||
|
texts.push_back(text);
|
||||||
|
// the same value written differently: sorted keys, canonical numbers
|
||||||
|
texts.push_back(json::parse(text).dump(1));
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i + 2 < texts.size(); ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = i; k < i + 3; ++k)
|
||||||
|
{
|
||||||
|
CAPTURE(texts[i])
|
||||||
|
CAPTURE(texts[k])
|
||||||
|
const json_document a = json_document::parse(texts[i]);
|
||||||
|
const json_document b = json_document::parse(texts[k]);
|
||||||
|
const json ja = json::parse(texts[i]);
|
||||||
|
const json jb = json::parse(texts[k]);
|
||||||
|
CHECK((a.root() == b.root()) == (ja == jb));
|
||||||
|
CHECK((a.root() != b.root()) == (ja != jb));
|
||||||
|
CHECK((a.root() == jb) == (ja == jb));
|
||||||
|
CHECK((jb == a.root()) == (ja == jb));
|
||||||
|
CHECK((a.root() != jb) == (ja != jb));
|
||||||
|
CHECK((jb != a.root()) == (ja != jb));
|
||||||
|
|
||||||
|
// ordered_json compares members in order
|
||||||
|
const ordered_json_document oa = ordered_json_document::parse(texts[i]);
|
||||||
|
const ordered_json_document ob = ordered_json_document::parse(texts[k]);
|
||||||
|
const ordered_json oja = ordered_json::parse(texts[i]);
|
||||||
|
const ordered_json ojb = ordered_json::parse(texts[k]);
|
||||||
|
CHECK((oa.root() == ob.root()) == (oja == ojb));
|
||||||
|
CHECK((oa.root() == ojb) == (oja == ojb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("numbers, duplicate keys, member order")
|
||||||
|
{
|
||||||
|
const auto same = [](const char* x, const char* y)
|
||||||
|
{
|
||||||
|
const json_document dx = json_document::parse(x);
|
||||||
|
const json_document dy = json_document::parse(y);
|
||||||
|
return dx.root() == dy.root();
|
||||||
|
};
|
||||||
|
CHECK(same("1", "1.0"));
|
||||||
|
CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]"));
|
||||||
|
CHECK(!same("1", "1.5"));
|
||||||
|
CHECK(same("18446744073709551615", "18446744073709551615"));
|
||||||
|
CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})"));
|
||||||
|
CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})"));
|
||||||
|
CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})"));
|
||||||
|
CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})"));
|
||||||
|
CHECK(!same("[1, 2]", "[2, 1]"));
|
||||||
|
CHECK(!same("\"a\"", "\"b\""));
|
||||||
|
CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\""));
|
||||||
|
CHECK(!same("null", "false"));
|
||||||
|
CHECK(!same("[]", "{}"));
|
||||||
|
const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||||
|
const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
||||||
|
CHECK(dup.root() == last.root());
|
||||||
|
// (== compares as parse() resolves duplicates, a lookup finds the first)
|
||||||
|
CHECK(dup.root()["a"].materialize() == 1);
|
||||||
|
CHECK(dup.root()["a"] != last.root()["a"]);
|
||||||
|
const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
||||||
|
const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
||||||
|
CHECK(ab.root() != ba.root());
|
||||||
|
|
||||||
|
// discarded values compare as basic_json's do
|
||||||
|
const json discarded(json::value_t::discarded);
|
||||||
|
CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested
|
||||||
|
CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty)
|
||||||
|
const json_document null_document = json_document::parse("null");
|
||||||
|
CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty)
|
||||||
|
CHECK(!(null_document.root() == discarded));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting")
|
||||||
|
{
|
||||||
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
|
const json_document a = json_document::parse(deep);
|
||||||
|
const json_document b = json_document::parse(deep);
|
||||||
|
CHECK(a.root() == b.root());
|
||||||
|
CHECK(a.root() == json::parse(deep));
|
||||||
|
const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']');
|
||||||
|
const json_document c = json_document::parse(other);
|
||||||
|
CHECK(a.root() != c.root());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("json_view large objects")
|
||||||
|
{
|
||||||
|
// objects with 128 members or more are looked up with a hash index
|
||||||
|
for (const std::size_t members :
|
||||||
|
{
|
||||||
|
127u, 128u, 129u, 10000u
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(members)
|
||||||
|
std::string text = "{";
|
||||||
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
|
{
|
||||||
|
text += (i != 0 ? ",\"" : "\"") + std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\\n" : "") + "\":" + std::to_string(i);
|
||||||
|
}
|
||||||
|
text += R"(,"":"empty key","k1":"a duplicate of an earlier key"})";
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const json_view v = d.root();
|
||||||
|
const json j = json::parse(text);
|
||||||
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
|
{
|
||||||
|
const std::string key = std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\n" : "");
|
||||||
|
CHECK(v[key].get<std::size_t>() == i);
|
||||||
|
CHECK(v.contains(key));
|
||||||
|
CHECK(v.find(key).key() == key);
|
||||||
|
CHECK(v.at(key).get<std::size_t>() == i);
|
||||||
|
CHECK(!v.contains(key + "x"));
|
||||||
|
}
|
||||||
|
CHECK(v[""].get_string() == "empty key");
|
||||||
|
CHECK(v["k1"].get<int>() == 1); // the first of duplicate keys, as for small objects
|
||||||
|
CHECK(!v.contains("missing"));
|
||||||
|
CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
|
||||||
|
CHECK(v == j);
|
||||||
|
CHECK(v.materialize() == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("duplicate keys: the first member wins, with and without a table")
|
||||||
|
{
|
||||||
|
// an object of `total` members: the keys "k0".."k<n-1>" in order, then
|
||||||
|
// three keys repeated twice more (one copy in the middle, one at the
|
||||||
|
// end), and two keys repeated once; the value of a member is its
|
||||||
|
// position, so that the first member of a key can be told apart
|
||||||
|
struct member
|
||||||
|
{
|
||||||
|
std::string key;
|
||||||
|
std::size_t position;
|
||||||
|
};
|
||||||
|
const auto make_members = [](std::size_t total)
|
||||||
|
{
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
for (std::size_t i = 0; i + 8 < total; ++i)
|
||||||
|
{
|
||||||
|
keys.push_back("k" + std::to_string(i));
|
||||||
|
}
|
||||||
|
const std::size_t n = keys.size();
|
||||||
|
const std::array<std::size_t, 3> triple = {{3, 17, n - 1}};
|
||||||
|
const std::array<std::size_t, 2> twice = {{5, n / 2}};
|
||||||
|
std::vector<std::string> ordered = keys;
|
||||||
|
for (const std::size_t i : triple)
|
||||||
|
{
|
||||||
|
ordered.insert(ordered.begin() + static_cast<std::ptrdiff_t>(ordered.size() / 2), keys[i]);
|
||||||
|
}
|
||||||
|
for (const std::size_t i : twice)
|
||||||
|
{
|
||||||
|
ordered.insert(ordered.begin() + static_cast<std::ptrdiff_t>(ordered.size() / 3), keys[i]);
|
||||||
|
}
|
||||||
|
for (const std::size_t i : triple)
|
||||||
|
{
|
||||||
|
ordered.push_back(keys[i]);
|
||||||
|
}
|
||||||
|
std::vector<member> result;
|
||||||
|
result.reserve(ordered.size());
|
||||||
|
for (std::size_t i = 0; i < ordered.size(); ++i)
|
||||||
|
{
|
||||||
|
result.push_back({ordered[i], i});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 100 and 127 members: no table; 128 members and more: a table
|
||||||
|
for (const std::size_t total :
|
||||||
|
{
|
||||||
|
100u, 127u, 128u, 200u, 5000u
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(total)
|
||||||
|
const std::vector<member> members = make_members(total);
|
||||||
|
REQUIRE(members.size() >= total);
|
||||||
|
REQUIRE(members.size() >= 100);
|
||||||
|
std::string text = "{";
|
||||||
|
std::map<std::string, std::size_t> first;
|
||||||
|
std::map<std::string, std::size_t> last;
|
||||||
|
std::map<std::string, std::size_t> occurrences;
|
||||||
|
for (const member& m : members)
|
||||||
|
{
|
||||||
|
text += (text.size() > 1 ? ",\"" : "\"") + m.key + "\":" + std::to_string(m.position);
|
||||||
|
first.insert({m.key, m.position});
|
||||||
|
last[m.key] = m.position;
|
||||||
|
++occurrences[m.key];
|
||||||
|
}
|
||||||
|
text += '}';
|
||||||
|
REQUIRE(first.size() < members.size());
|
||||||
|
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const json_view v = d.root();
|
||||||
|
const json j = json::parse(text);
|
||||||
|
CHECK(v.size() == members.size()); // every occurrence is visited
|
||||||
|
const json m = v.materialize();
|
||||||
|
for (const auto& entry : first)
|
||||||
|
{
|
||||||
|
CAPTURE(entry.first)
|
||||||
|
const std::size_t expected = entry.second;
|
||||||
|
CHECK(v[entry.first].get<std::size_t>() == expected);
|
||||||
|
CHECK(v.at(entry.first).get<std::size_t>() == expected);
|
||||||
|
CHECK(v.find(entry.first).value().get<std::size_t>() == expected);
|
||||||
|
CHECK(v.value(entry.first, std::size_t{0}) == expected);
|
||||||
|
CHECK(v.contains(entry.first));
|
||||||
|
CHECK(v.count(entry.first) == occurrences[entry.first]);
|
||||||
|
const std::string pointer = "/" + entry.first;
|
||||||
|
CHECK(v[json::json_pointer(pointer)].get<std::size_t>() == expected);
|
||||||
|
CHECK(v.at(json::json_pointer(pointer)).get<std::size_t>() == expected);
|
||||||
|
CHECK(v.value(json::json_pointer(pointer), std::size_t{0}) == expected);
|
||||||
|
CHECK(v.contains(json::json_pointer(pointer)));
|
||||||
|
// materialize() and parse() keep the last value instead
|
||||||
|
CHECK(j[entry.first].get<std::size_t>() == last.at(entry.first));
|
||||||
|
CHECK(m[entry.first].get<std::size_t>() == last.at(entry.first));
|
||||||
|
}
|
||||||
|
CHECK(!v.contains("k"));
|
||||||
|
CHECK(v["missing"].is_discarded());
|
||||||
|
CHECK(v.materialize() == j);
|
||||||
|
CHECK(v == j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("colliding keys")
|
||||||
|
{
|
||||||
|
// the hash is not seeded, so keys that all land in one place must not
|
||||||
|
// make the table build quadratic: such an object gets no table and is
|
||||||
|
// searched linearly
|
||||||
|
constexpr std::size_t members = 300;
|
||||||
|
constexpr std::size_t table_size = 1024; // the table size for 300 members: the next power of two >= 600
|
||||||
|
std::vector<std::string> colliding;
|
||||||
|
std::vector<std::string> spread;
|
||||||
|
for (std::uint64_t counter = 0; colliding.size() < members || spread.size() < members; ++counter)
|
||||||
|
{
|
||||||
|
std::string key(8, 'a');
|
||||||
|
std::uint64_t x = counter;
|
||||||
|
for (std::size_t i = 0; i < 8; ++i, x /= 26)
|
||||||
|
{
|
||||||
|
key[i] = static_cast<char>('a' + (x % 26));
|
||||||
|
}
|
||||||
|
const bool lands_in_slot_zero = (nlohmann::detail::view::key_hash(key.data(), key.size()) & (table_size - 1)) == 0;
|
||||||
|
if (lands_in_slot_zero && colliding.size() < members)
|
||||||
|
{
|
||||||
|
colliding.push_back(key);
|
||||||
|
}
|
||||||
|
else if (!lands_in_slot_zero && spread.size() < members)
|
||||||
|
{
|
||||||
|
spread.push_back(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto make_text = [](const std::vector<std::string>& keys)
|
||||||
|
{
|
||||||
|
std::string text = "{";
|
||||||
|
for (std::size_t i = 0; i < keys.size(); ++i)
|
||||||
|
{
|
||||||
|
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i % 10);
|
||||||
|
}
|
||||||
|
return text + "}";
|
||||||
|
};
|
||||||
|
const std::string colliding_text = make_text(colliding);
|
||||||
|
const std::string spread_text = make_text(spread);
|
||||||
|
|
||||||
|
json_document with_collisions = json_document::parse(colliding_text);
|
||||||
|
json_document without_collisions = json_document::parse(spread_text);
|
||||||
|
for (const auto* pair :
|
||||||
|
{
|
||||||
|
&colliding, &spread
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const json_view v = (pair == &colliding ? with_collisions : without_collisions).root();
|
||||||
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CHECK(v[(*pair)[i]].get<std::size_t>() == i % 10);
|
||||||
|
CHECK(v.at((*pair)[i]).get<std::size_t>() == i % 10);
|
||||||
|
CHECK(v.find((*pair)[i]).key() == (*pair)[i]);
|
||||||
|
CHECK(!v.contains((*pair)[i] + "x"));
|
||||||
|
}
|
||||||
|
CHECK(!v.contains("missing"));
|
||||||
|
}
|
||||||
|
CHECK(with_collisions.root() == json::parse(colliding_text));
|
||||||
|
|
||||||
|
// only the object with the spread keys got a table (both texts have the
|
||||||
|
// same length, so the tables are the only difference)
|
||||||
|
with_collisions.shrink_to_fit();
|
||||||
|
without_collisions.shrink_to_fit();
|
||||||
|
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("keys that differ in their last bytes are spread over the table")
|
||||||
|
{
|
||||||
|
// The hash must spread the usual families of keys (numbered names, hex
|
||||||
|
// strings, dates, short names): a key farther than index_max_displacement
|
||||||
|
// from its home slot would leave the object without a table. The keys
|
||||||
|
// are placed with linear probing, as the table does.
|
||||||
|
const auto max_displacement = [](const std::vector<std::string>& keys)
|
||||||
|
{
|
||||||
|
std::size_t slots = 16;
|
||||||
|
while (slots < 2 * keys.size())
|
||||||
|
{
|
||||||
|
slots *= 2;
|
||||||
|
}
|
||||||
|
std::vector<bool> used(slots, false);
|
||||||
|
std::size_t farthest = 0;
|
||||||
|
for (const std::string& key : keys)
|
||||||
|
{
|
||||||
|
std::size_t i = nlohmann::detail::conditional_static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1);
|
||||||
|
std::size_t distance = 0;
|
||||||
|
while (used[i])
|
||||||
|
{
|
||||||
|
i = (i + 1) & (slots - 1);
|
||||||
|
++distance;
|
||||||
|
}
|
||||||
|
used[i] = true;
|
||||||
|
farthest = (std::max)(farthest, distance);
|
||||||
|
}
|
||||||
|
return farthest;
|
||||||
|
};
|
||||||
|
// the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream
|
||||||
|
// (a format string that is not a literal is a Flawfinder error)
|
||||||
|
const auto numbered = [](const char* format, int count, int step)
|
||||||
|
{
|
||||||
|
const std::string pattern(format);
|
||||||
|
const std::size_t percent = pattern.find('%');
|
||||||
|
std::size_t conversion = percent + 1;
|
||||||
|
std::size_t width = 0;
|
||||||
|
while (pattern[conversion] >= '0' && pattern[conversion] <= '9')
|
||||||
|
{
|
||||||
|
width = (width * 10) + static_cast<std::size_t>(pattern[conversion] - '0');
|
||||||
|
++conversion;
|
||||||
|
}
|
||||||
|
const bool hex = pattern[conversion] == 'x';
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
std::ostringstream key;
|
||||||
|
key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast<int>(width))
|
||||||
|
<< (hex ? std::hex : std::dec) << (i * step) << pattern.substr(conversion + 1);
|
||||||
|
keys.push_back(key.str());
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
for (const char* format :
|
||||||
|
{"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d",
|
||||||
|
"configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(format)
|
||||||
|
CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement); // NOLINT(abseil-string-find-str-contains)
|
||||||
|
CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement);
|
||||||
|
}
|
||||||
|
std::mt19937_64 rng(7); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
|
for (const std::size_t length :
|
||||||
|
{
|
||||||
|
std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(length)
|
||||||
|
std::set<std::string> distinct;
|
||||||
|
// (there are 26 and 676 keys of one and two letters)
|
||||||
|
std::size_t wanted = 700;
|
||||||
|
if (length == 1)
|
||||||
|
{
|
||||||
|
wanted = 26;
|
||||||
|
}
|
||||||
|
else if (length == 2)
|
||||||
|
{
|
||||||
|
wanted = 500;
|
||||||
|
}
|
||||||
|
while (distinct.size() < wanted)
|
||||||
|
{
|
||||||
|
std::string key(length, 'a');
|
||||||
|
for (char& c : key)
|
||||||
|
{
|
||||||
|
c = static_cast<char>('a' + (rng() % 26));
|
||||||
|
}
|
||||||
|
distinct.insert(key);
|
||||||
|
}
|
||||||
|
CHECK(max_displacement(std::vector<std::string>(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("keys of every length that differ in one byte")
|
||||||
|
{
|
||||||
|
// In a large object, a key is found by comparing its bytes with those of the member the
|
||||||
|
// table points to: up to 16 bytes as two overlapping words, longer ones with memcmp.
|
||||||
|
// Keys of every length from 0 to 40, and keys that differ from them in one byte.
|
||||||
|
std::mt19937_64 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
std::set<std::string> distinct;
|
||||||
|
for (std::size_t length = 0; length <= 40; ++length)
|
||||||
|
{
|
||||||
|
for (int copy = 0; copy < 5; ++copy)
|
||||||
|
{
|
||||||
|
std::string key(length, 'a');
|
||||||
|
for (char& c : key)
|
||||||
|
{
|
||||||
|
c = static_cast<char>('a' + (rng() % 26));
|
||||||
|
}
|
||||||
|
if (distinct.insert(key).second)
|
||||||
|
{
|
||||||
|
keys.push_back(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string text = "{";
|
||||||
|
for (std::size_t i = 0; i < keys.size(); ++i)
|
||||||
|
{
|
||||||
|
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
|
||||||
|
}
|
||||||
|
text += "}";
|
||||||
|
REQUIRE(keys.size() >= 128); // (a table is built for 128 members)
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const json_view v = d.root();
|
||||||
|
for (std::size_t i = 0; i < keys.size(); ++i)
|
||||||
|
{
|
||||||
|
CAPTURE(keys[i])
|
||||||
|
CHECK(v[keys[i]].get<std::size_t>() == i);
|
||||||
|
CHECK(v.at(keys[i]).get<std::size_t>() == i);
|
||||||
|
CHECK(v.contains(keys[i]));
|
||||||
|
for (std::size_t at = 0; at < keys[i].size(); ++at)
|
||||||
|
{
|
||||||
|
std::string other = keys[i];
|
||||||
|
other[at] = other[at] == 'z' ? 'y' : 'z';
|
||||||
|
CAPTURE(other)
|
||||||
|
CHECK(v.contains(other) == (distinct.count(other) != 0));
|
||||||
|
}
|
||||||
|
CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0));
|
||||||
|
if (!keys[i].empty())
|
||||||
|
{
|
||||||
|
CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("shrink_to_fit releases the tables' spare capacity")
|
||||||
|
{
|
||||||
|
const auto make_text = [](int objects, int members)
|
||||||
|
{
|
||||||
|
std::string text = "[";
|
||||||
|
for (int object = 0; object < objects; ++object)
|
||||||
|
{
|
||||||
|
text += object != 0 ? ",{" : "{";
|
||||||
|
for (int i = 0, n = members + object; i < n; ++i)
|
||||||
|
{
|
||||||
|
text += (i != 0 ? ",\"" : "\"") + std::to_string(i) + "\":" + std::to_string(i);
|
||||||
|
}
|
||||||
|
text += '}';
|
||||||
|
}
|
||||||
|
return text + "]";
|
||||||
|
};
|
||||||
|
const std::string small_text = make_text(5, 150);
|
||||||
|
const std::string big_text = make_text(40, 400);
|
||||||
|
|
||||||
|
// reading a big text, and then a small one, leaves the spare capacity
|
||||||
|
// of the big one: shrink_to_fit() brings the document to the size of
|
||||||
|
// one parsed from the small text alone
|
||||||
|
json_document d = json_document::parse(big_text);
|
||||||
|
const std::size_t big = d.memory_usage();
|
||||||
|
d.read(small_text);
|
||||||
|
CHECK(d.memory_usage() >= big);
|
||||||
|
d.shrink_to_fit();
|
||||||
|
json_document fresh = json_document::parse(small_text);
|
||||||
|
fresh.shrink_to_fit();
|
||||||
|
CHECK(d.memory_usage() == fresh.memory_usage());
|
||||||
|
CHECK(d.memory_usage() < big / 2);
|
||||||
|
CHECK(d.root() == json::parse(small_text));
|
||||||
|
for (int object = 0; object < 5; ++object)
|
||||||
|
{
|
||||||
|
const json_view v = d.root()[static_cast<std::size_t>(object)];
|
||||||
|
for (int i = 0, n = 150 + object; i < n; ++i)
|
||||||
|
{
|
||||||
|
CHECK(v[std::to_string(i)].get<int>() == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested, reused, and in arrays")
|
||||||
|
{
|
||||||
|
std::string inner = "{";
|
||||||
|
for (int i = 0; i < 300; ++i)
|
||||||
|
{
|
||||||
|
inner += (i != 0 ? ",\"m" : "\"m") + std::to_string(i) + "\":" + std::to_string(i);
|
||||||
|
}
|
||||||
|
inner += '}';
|
||||||
|
const std::string text = "[" + inner + ",{\"x\":" + inner + "}," + inner + "]";
|
||||||
|
json_document d = json_document::parse(text);
|
||||||
|
CHECK(d.root()[0]["m299"].get<int>() == 299);
|
||||||
|
CHECK(d.root()[1]["x"]["m150"].get<int>() == 150);
|
||||||
|
CHECK(d.root()[2]["m0"].get<int>() == 0);
|
||||||
|
const std::size_t with_index = d.memory_usage();
|
||||||
|
d.read(std::string("{\"small\": 1}"));
|
||||||
|
CHECK(d.root()["small"].get<int>() == 1);
|
||||||
|
d.read(text);
|
||||||
|
CHECK(d.root()[2]["m7"].get<int>() == 7);
|
||||||
|
CHECK(d.memory_usage() >= with_index / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||