diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..cb6403b9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+# Keep the third-party parser separate in reviews and preserve upstream bytes.
+src/bin/nlohmann/json.hpp linguist-vendored
+src/bin/nlohmann/LICENSE.MIT linguist-vendored -whitespace
diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml
index aadec7f3..8befa501 100644
--- a/.github/workflows/build-wasm.yml
+++ b/.github/workflows/build-wasm.yml
@@ -35,7 +35,7 @@ jobs:
python scripts/wasm/test-downloads.py
python scripts/wasm/test-vienna-release.py
- name: Test input, worker lifecycle and CSV handling
- run: npm test --prefix webinterface
+ run: npm test --prefix gui
- name: Resolve newest stable ViennaRNA 2.x release and checksum
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -68,7 +68,7 @@ jobs:
python scripts/wasm/test-browser.py http://127.0.0.1:8765/site/ --browser firefox
- uses: actions/upload-artifact@v4
with:
- name: intarna-webinterface
+ name: intarna-gui
path: build-wasm/site
if-no-files-found: error
@@ -82,14 +82,14 @@ jobs:
path: _site
- uses: actions/download-artifact@v4
with:
- name: intarna-webinterface
- path: _site/webinterface
+ name: intarna-gui
+ path: _site/gui
- name: Verify documentation and browser interface coexist
run: |
test -s _site/index.html
test -s _site/api/index.html
- test -s _site/webinterface/index.html
- test -s _site/webinterface/wasm/intarna.wasm
+ test -s _site/gui/index.html
+ test -s _site/gui/wasm/intarna.wasm
- uses: actions/upload-artifact@v4
with:
name: intarna-pages-preview
diff --git a/.gitignore b/.gitignore
index aecb0872..f09485a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -85,7 +85,7 @@ python/intarnapvalue.*
# Browser build outputs and local test dependencies
/build-wasm/
-/webinterface/wasm/
-/webinterface/node_modules/
+/gui/wasm/
+/gui/node_modules/
/scripts/wasm/__pycache__/
diff --git a/ChangeLog b/ChangeLog
index 02b0b5f7..c204105e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -14,6 +14,35 @@
## Interface and handling
+- reorganize GUI parameters, add conditional seed/accessibility controls and
+ grouped CSV defaults, expand FASTA limits and refine result headers (PR #299)
+
+- retain computed GUI runs as named, foldable sessions with JSON input restoration,
+ independent outputs, per-session ZIP export and deletion (PR #297 review)
+
+- permit clearing inherited CSV sorting when replaying JSON as text reports;
+ validate JSON/GUI behavior in native release/debug and real browsers (#287)
+
+- show prediction/evaluation tasks as always-visible choices with task-specific
+ descriptions and an expanded Seeds/Accessibility heading (PR #287 review)
+
+- upload or drop local FASTA files into each GUI strand with validation and
+ protection against stale asynchronous reads (PR #287 review)
+
+- clear all inputs on GUI reset and load named examples from maintainable JSON
+ files through an Example menu (PR #287 review)
+
+- load/store GUI settings as portable JSON; ZIPs contain parameter.json with
+ embedded inputs/provenance and all outputs (PR #287 review)
+
+- use one embedded-input JSON parameter document for each WASM invocation
+ (PR #287 review)
+
+- accept JSON parameter files with embedded text inputs and personality defaults
+ through --parameterFile while retaining legacy configuration (PR #287 review)
+
+- rename the browser interface directory and Pages URL to gui (PR #287 review)
+
- add selected-interaction vaRRI embeds and reproducible all-output ZIP downloads
to the browser interface; compact parameters into accessible tabs and update
the Freiburg group header and full-width result/footer notes (PR #287 review)
@@ -84,6 +113,9 @@
## Technical changes and Optimizations
+- record JSON/WASM/browser regression validation and preserve pinned JSON vendor
+ checksums while marking upstream files as vendored for review (PR #287)
+
- build WASM with ViennaRNA 2.7.2 locally and the newest stable 2.x in CI;
verify published archive checksums, isolate versioned caches and retain
selected dependency provenance and notices (PR #292 review)
@@ -253,6 +285,115 @@ energies, restricted partition sums, and trackers.
################################################################################
################################################################################
+261024 Alexander Mitrofanov
+ * gui/{app,options,columns}.js, gui/{index.html,styles.css}
+ * organize tabs/rows as Sequences, Seed, Output, Model & Energy, Query,
+ Target and CLI-only; align wrapped labels and place Compute first
+ + add a three-way seed selector, synchronized accessibility flags and
+ mode-dependent input availability; place SHAPE beside accessibility
+ * group compact CSV choices with descriptions and group selection actions;
+ default to 10 predictions and expanded task-specific energy columns,
+ explicitly preserving browser defaults in native-compatible JSON
+ * gui/{protocol,uploads,parameter-file}.js, gui/examples/
+ * accept 50 FASTA records per strand of 2-5,000 nt each, with longest
+ selected query x longest selected target <= 1,000,000; allow 1 MB FASTA
+ + warn about ignored FASTA header descriptions while using first-token IDs
+ * gui/{sessions,results}.js
+ * select results from the header/name, order Show/Hide before the name and
+ ZIP/delete actions, compact table headings and distinguish selected rows
+ * gui/tests/, scripts/wasm/test-browser.py, README.md, gui/docs/
+ + cover limits, native JSON replay/defaults, dependent controls, grouped CSV
+ choices, desktop/mobile alignment and result header selection
+ * address https://github.com/BackofenLab/IntaRNA/pull/299#issuecomment-6375752155
+
+261023 Alexander Mitrofanov
+ * gui/{app,sessions,results,visualization}.js, gui/{index.html,styles.css}
+ + retain successful runs in browser memory with unique IDs, editable names,
+ independent results/diagnostics and JSON snapshots of their computed inputs
+ * restore inputs and fold other output areas on selection; preserve sessions
+ across edits, reset, imports, failures and aborts; unload folded viewers
+ + export each session's original JSON/outputs from its header and delete it
+ without overwriting editable inputs; keep controls keyboard accessible
+ * scripts/wasm/{stage.sh,test-browser.py}, README.md, gui/docs/
+ * stage the sessions module, exercise session lifetime and archive isolation
+ in real browsers, and document temporary storage and updated reset behavior
+ * follow-up to https://github.com/BackofenLab/IntaRNA/pull/297#issuecomment-6367326708
+
+261023 Alexander Mitrofanov
+ * .gitattributes, gui/docs/json-workflow.md
+ + Preserve exact upstream license whitespace and mark the JSON header and
+ license as vendored. Record native release/debug, WASM replay, browser,
+ distribution and provenance validation for the PR #287 revisions.
+
+261023 Alexander Mitrofanov
+ * src/bin/CommandLineParsing.cpp, ParameterFile.h, tests/testParameterFile.py
+ * Accept an explicitly empty CSV sort value to clear a parameter-file
+ setting when reproducing non-CSV reports from the same JSON document.
+ * gui/app.js, gui/styles.css, scripts/wasm/test-browser.py
+ * Keep pending auxiliary-file reads from restoring reset inputs, return
+ Reset to Sequence input and make the entire task label clickable.
+ * doc/{Makefile.am,json-parameters.md}
+ + Include JSON format documentation in source distributions and record the
+ observed WASM footprint and reproducible dependency comparison (#287).
+
+261023 Alexander Mitrofanov
+ * gui/{index.html,app.js,styles.css}, scripts/wasm/test-browser.py
+ * Replace the task dropdown with visible, highlighted native radio choices
+ and task descriptions; update the heading to include Seeds/Accessibility.
+ * Keep Example, Reset, Load and Store adjacent and test keyboard selection
+ and responsive layout (PR #287 review).
+
+261023 Alexander Mitrofanov
+ * gui/{uploads.js,app.js,index.html,styles.css}, gui/docs/json-workflow.md
+ + Add Upload controls and file-drop hints to both FASTA headers; validate
+ file size/contents before replacing a strand and invalidate stale reads.
+ * gui/tests/uploads.test.mjs, scripts/wasm/{test-browser.py,stage.sh}
+ + Test local upload/drop, invalid files and reset during a pending read;
+ stage the file-input module (PR #287 review).
+
+261023 Alexander Mitrofanov
+ * gui/{app.js,index.html,styles.css,examples/}, gui/docs/json-workflow.md
+ * Reset clears inputs/results and restores defaults; example FASTA is shown
+ only as placeholders. Add a described Example dropdown beside Reset.
+ + Ship two editable JSON examples and a manifest, using the common importer.
+ * gui/tests/parameter-file.test.mjs, scripts/wasm/{stage.sh,test-browser.py}
+ + Validate/stage example files and check empty startup/reset behavior (#287).
+
+261023 Alexander Mitrofanov
+ * gui/parameter-file.js, archive.js, app.js, index.html, protocol.js
+ + Load and Store non-default settings and embedded inputs using native JSON;
+ validate imports before replacing the form and retain CSV selection order.
+ * ZIP exports contain only parameter.json and outputs. Record version and
+ native replay commands, including overrides for additional report formats.
+ * gui/tests/, scripts/wasm/{smoke-protocol.mjs,test-browser.py,stage.sh},
+ gui/docs/{json-workflow.md,step4-results-and-deployment.md}
+ + Test JSON round trips, invalid imports, real WASM replay and output equality;
+ stage the serializer and nlohmann/json license (PR #287 review).
+
+261023 Alexander Mitrofanov
+ * gui/protocol.js, gui/intarna-worker.js, gui/tests/protocol.test.mjs,
+ gui/docs/step2-worker-architecture.md
+ * Pass --parameterFile=/parameter.json to each WASM instance using the
+ native JSON format with embedded FASTA, SHAPE and accessibility text.
+ Preserve native output routing and independent report invocations (#287).
+
+261023 Alexander Mitrofanov
+ * src/bin/ParameterFile.{h,cpp}, CommandLineParsing.{h,cpp}, Makefile.am
+ + Detect JSON parameter files; reuse native CLI validation and precedence,
+ support embedded input text and retain files until computation finishes.
+ * Honor parameter-file personalities when selecting native defaults.
+ * src/bin/nlohmann/, doc/json-parameters.md, README.md, scripts/wasm/stage.sh
+ + Vendor nlohmann/json 3.12.0 with its license, document the schema,
+ dependency tradeoff and reproduction contract (PR #287 review).
+ * tests/runParameterFile.sh, tests/testParameterFile.py, tests/Makefile.am
+ + Check legacy/JSON equivalence, embedded inputs, precedence and errors.
+
+261023 Alexander Mitrofanov
+ * gui/, scripts/wasm/, .github/workflows/build-wasm.yml, README.md,
+ doc/api-documentation.md, .gitignore
+ * Rename webinterface to gui, including imports, staging, tests and the
+ combined Pages artifact at /IntaRNA/gui/ (PR #287 review).
+
261022 Alexander Mitrofanov
* scripts/wasm/{vienna-release.py,vienna-release.json}, build.sh
+ Resolve the newest stable ViennaRNA >=2.7.2 and <3 from all official GitHub
diff --git a/README.md b/README.md
index 3fd6a84d..ce94edbd 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,12 @@
**Efficient RNA-RNA interaction prediction incorporating accessibility and
seeding of interaction sites**
-Predict short RNA pairs or evaluate supplied structures locally with the [browser interface](https://backofenlab.github.io/IntaRNA/webinterface/).
-Select an interaction to view it in vaRRI, or download a ZIP with inputs, compiled versions, reproducible parameters and all outputs.
-See the [WebAssembly build instructions](webinterface/docs/step1-wasm-compilation.md) and
-[interface/deployment notes](webinterface/docs/step4-results-and-deployment.md).
+Predict short RNA pairs or evaluate supplied structures locally with the [browser interface](https://backofenlab.github.io/IntaRNA/gui/).
+The browser accepts up to 50 records per strand, 2–5,000 nucleotides each, with longest selected query × longest selected target at most 1,000,000. Prediction defaults to 10 interactions per pair and includes opening and seed energies; evaluation selects all energy columns.
+Computed runs stay in separate, foldable sessions under Results in the current tab. Name and select sessions to restore their inputs and compare results; editing or resetting parameters keeps earlier runs.
+Select an interaction to view it in vaRRI, or download a session’s ZIP with inputs, compiled versions, reproducible parameters and all outputs. Sessions are temporary and are cleared when the page is reloaded.
+See the [WebAssembly build instructions](gui/docs/step1-wasm-compilation.md) and
+[interface/deployment notes](gui/docs/step4-results-and-deployment.md).
During the last few years, several new small regulatory RNAs
@@ -30,7 +32,7 @@ as well as the existence of a user-definable seed interaction. We successfully a
IntaRNA to the prediction of bacterial sRNA targets and determined the exact
locations of the interactions with a higher accuracy than competing programs.
-For testing or ad hoc use of IntaRNA, you can use its webinterface at the
+For testing or ad hoc use of IntaRNA, you can use its gui at the
**==> [Freiburg RNA tools IntaRNA webserver](http://rna.informatik.uni-freiburg.de/IntaRNA/) <==**
@@ -504,6 +506,11 @@ using `configure --disable-multithreading`.
+JSON parameter files, including embedded FASTA, accessibility, SHAPE and energy
+parameter text, are also accepted by `--parameterFile` with automatic format
+detection. See [JSON format, examples and precedence](doc/json-parameters.md).
+
+
## Load arguments from file
If you are using IntaRNA with similar command line arguments (parameters), you
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 25910558..cf899e71 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -6,6 +6,7 @@
EXTRA_DIST = \
conda.txt \
api-documentation.md \
+ json-parameters.md \
build-api.sh \
check-api.py \
doxygen.cfg \
diff --git a/doc/api-documentation.md b/doc/api-documentation.md
index 3a9dc2a2..cd9ad808 100644
--- a/doc/api-documentation.md
+++ b/doc/api-documentation.md
@@ -45,7 +45,7 @@ manually to inspect documentation artifacts. The documentation build uploads
under `api/`.
`build-wasm.yml` combines that artifact with the tested browser interface under
-`webinterface/` and publishes **one** Pages artifact. This preserves the existing
+`gui/` and publishes **one** Pages artifact. This preserves the existing
guide and API; two independent publishers would overwrite each other's site.
Only a push or manual dispatch on `BackofenLab/IntaRNA`'s `master` can deploy.
Pull requests and forks cannot publish. The deployment job alone receives Pages
@@ -53,7 +53,7 @@ and OIDC write permissions, and deployments share one concurrency group.
- User guide:
- Development API:
-- Browser interface:
+- Browser interface:
### Pages configuration
@@ -67,5 +67,5 @@ change live publishing settings. See GitHub's
No generated HTML, access token, or separate documentation branch is committed.
The `intarna-pages-preview` artifact contains the combined site for PR review.
-See [the browser deployment notes](../webinterface/docs/step4-results-and-deployment.md)
+See [the browser deployment notes](../gui/docs/step4-results-and-deployment.md)
for the complete build and validation sequence.
diff --git a/doc/json-parameters.md b/doc/json-parameters.md
new file mode 100644
index 00000000..2a694d12
--- /dev/null
+++ b/doc/json-parameters.md
@@ -0,0 +1,79 @@
+# JSON parameter files
+
+`IntaRNA --parameterFile=parameter.json` accepts the same CLI option names and
+values as a legacy `key = value` file. Detection uses the first non-whitespace
+character, after an optional UTF-8 BOM, independent of the filename suffix.
+Legacy files remain supported. Explicit scalar CLI options override file values;
+composing options such as `out` accumulate as before. A file's `personality`
+selects defaults before parsing; an explicit CLI personality takes precedence.
+
+```json
+{
+ "schema": "IntaRNA-parameters-v1",
+ "_comment": ["Reproduce: IntaRNA --parameterFile=parameter.json"],
+ "parameters": {
+ "query": {"text": ">query\nGGGAGGAGGG\n"},
+ "target": {"text": ">target\nCCCUCCUCCC\n"},
+ "temperature": 25,
+ "outMode": "C",
+ "out": ["interactions.csv", "qPu:query-pu.txt"]
+ }
+}
+```
+
+`parameters` contains strings, JSON numbers and booleans. Repeated/composing
+options accept nonempty arrays. Flags without values accept `true` or `false`.
+Unknown options, duplicate keys, null values, unsupported nested values and
+recursive `parameterFile` are errors. Existing native type, range and cross-option
+validation still applies. JSON uses normal JSON escapes; `#`, whitespace and
+newlines in string values are not interpreted as legacy configuration syntax.
+
+The optional `_comment` field holds provenance and reproduction instructions as
+ordinary JSON data, so files work with standard JSON parsers. Optional `gui`
+metadata describes browser-only output selections and is ignored by the CLI.
+Other document fields and unsupported schema versions are rejected.
+
+## Embedded inputs
+
+For `query`, `target`, `qAccFile`, `tAccFile`, `qShape`, `tShape` and `energyVRNA`,
+use `{"text": "file contents"}` to embed UTF-8 text. FASTA may contain multiple
+records. Accessibility uses RNAplfold text; SHAPE and energy parameters retain
+their native formats. Ordinary strings retain their original meaning (sequence,
+preset or filesystem path). Relative paths remain relative to the working
+directory, as with legacy configuration files. Binary `.agz` caches can still be
+referenced by path; they are not a text encoding.
+
+For accessibility of multiple selected records, `{"texts": ["record 1 data",
+"record 2 data"]}` materializes files with the existing `-sN` suffixes, indexed by
+original FASTA record number. Use `text` when selecting a single record. No
+user-specified paths are used for embedded files. A private temporary directory
+keeps generated files alive for the entire prediction and removes them on normal
+exit or exception unwinding. WASM uses its private in-memory filesystem. Like
+other temporary resources, abrupt process termination can leave native files.
+
+## Dependency choice
+
+We evaluated [Boost.JSON](https://www.boost.org/doc/libs/1_87_0/libs/json/doc/html/json/overview.html)
+and [nlohmann/json](https://json.nlohmann.me/integration/). Both provide typed JSON
+and can avoid a separately linked JSON library. Boost.JSON would raise our
+current Boost 1.50 minimum to a release containing JSON, or require another
+vendored Boost component and its dependency integration. Boost.PropertyTree is
+already available but loses JSON scalar/array type distinctions needed here.
+
+We chose the unmodified, MIT-licensed nlohmann/json 3.12.0 single header (953,436
+bytes), compiled in one CLI translation unit. It adds no system package, linker
+flag, public library API or WASM build dependency. The tradeoff is a vendored
+header to maintain and extra compile/binary size; the parser is outside prediction
+loops. Checksums and update instructions live in `src/bin/nlohmann/README.md`.
+The source distribution and staged GUI include the license.
+
+### Observed WASM footprint
+
+With Emscripten 4.0.23, ViennaRNA 2.7.2 and Boost 1.87.0 using
+`scripts/wasm/build.sh`, the cached pre-JSON engine at `d44d7a6` is 3,217,224 bytes
+(781,737 bytes with Python `gzip.compress(..., mtime=0)`). The JSON-enabled engine
+is 3,362,458 bytes (823,241 gzip): an increase of 145,234 bytes raw and 41,504
+compressed. These are local artifact-size observations with matching dependency
+versions, not runtime benchmarks or guarantees for other toolchains. Reproduce
+by building both revisions, comparing `site/wasm/intarna.wasm`, and checking
+`site/build-info.json` for compiler/library provenance.
diff --git a/gui/app.js b/gui/app.js
new file mode 100644
index 00000000..446a7f60
--- /dev/null
+++ b/gui/app.js
@@ -0,0 +1,386 @@
+import { parseFasta, prepareRequest, CSV_COLUMNS } from './protocol.js';
+import { GROUPS, FIELDS, EXTRA_OUTPUTS, UI_DEFAULTS } from './options.js';
+import { CSV_CATALOG, CSV_GROUPS, defaultColumns } from './columns.js';
+import { PredictionRunner } from './runner.js';
+import { downloadText } from './results.js';
+import { SessionsView } from './sessions.js';
+import { importParameters, serializeParameters, MAX_PARAMETER_BYTES } from './parameter-file.js';
+import { readFastaFile } from './uploads.js';
+
+const byId = (id) => document.getElementById(id);
+const form = byId('prediction-form'), runner = new PredictionRunner();
+const getTask = () => form.querySelector('input[name=task]:checked').value;
+const setTask = (task) => { for (const input of form.querySelectorAll('input[name=task]')) input.checked = input.value === task; };
+const dialog = byId('loading'), status = byId('result-status'), errorBox = byId('input-error');
+const sessions = new SessionsView(byId('sessions'), byId('session-template'), (request, name) => {
+ applyParameters(request, `${name} selected. Its computed inputs have been restored.`);
+}, showInputError, (message) => { status.textContent = message; });
+const csvInputs = [], outputInputs = [];
+let inputRevision = 0;
+let csvOrder = CSV_COLUMNS.split(',');
+function element(tag, text, className) {
+ const node = document.createElement(tag);
+ if (text) node.textContent = text;
+ if (className) node.className = className;
+ return node;
+}
+function activateGroup(key, focus = false) {
+ for (const tab of byId('parameter-tabs').children) {
+ const active = tab.getAttribute('aria-controls') === key;
+ tab.setAttribute('aria-selected', String(active)); tab.tabIndex = active ? 0 : -1;
+ byId(tab.getAttribute('aria-controls')).hidden = !active;
+ if (active && focus) tab.focus();
+ }
+}
+function addTab(key, label) {
+ const tab = element('button', label); tab.type = 'button'; tab.id = `tab-${key}`;
+ tab.setAttribute('role', 'tab'); tab.setAttribute('aria-controls', key);
+ tab.addEventListener('click', () => activateGroup(key));
+ byId('parameter-tabs').append(tab);
+}
+function group(key, label) {
+ const panel = element('section', '', 'parameter-group'); panel.id = key;
+ panel.setAttribute('role', 'tabpanel'); panel.setAttribute('aria-labelledby', `tab-${key}`);
+ const body = element('div', '', 'group-body'); panel.append(body);
+ byId('parameter-groups').append(panel); addTab(key, label); return body;
+}
+addTab('sequence-input', 'Sequences');
+byId('parameter-tabs').addEventListener('keydown', (event) => {
+ if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
+ const tabs = [...byId('parameter-tabs').children].filter((tab) => !tab.hidden);
+ const current = tabs.indexOf(document.activeElement);
+ const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : (current + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length;
+ event.preventDefault(); activateGroup(tabs[next].getAttribute('aria-controls'), true);
+});
+// Render each requested row explicitly; labels share a grid row so wrapped
+// labels do not push neighboring controls out of alignment.
+function renderField(field, id = field.key) {
+ const wrapper = element('div', '', `field${field.type === 'checkbox' ? ' check' : ''}`);
+ const label = element('label', field.label); label.htmlFor = id; label.title = `--${field.key}`;
+ const input = element(field.type === 'select' ? 'select' : field.type === 'textarea' ? 'textarea' : 'input'); input.id = id;
+ if (input.tagName === 'INPUT') input.type = field.type;
+ if (field.type === 'select') for (const [value, name] of Object.entries(field.choices)) { const option = element('option', name); option.value = value; input.append(option); }
+ if (field.type === 'number') { input.min = field.min; input.max = field.max; input.step = field.step ?? 1; }
+ if (field.type === 'checkbox') { input.checked = field.value; wrapper.append(input, label); }
+ else { input.value = field.value; wrapper.append(label, input); }
+ if (field.hint) { const hint = element('p', field.hint, 'field-hint'); hint.id = `${id}-help`; input.setAttribute('aria-describedby', hint.id); wrapper.append(hint); }
+ if (field.type === 'textarea') {
+ input.spellcheck = false;
+ const upload = element('input'); upload.type = 'file'; upload.accept = '.txt,.csv,.shape,.dat'; upload.setAttribute('aria-label', `Load ${field.label} from a local text file`);
+ upload.dataset.for = field.key;
+ upload.addEventListener('change', async () => {
+ const file = upload.files[0]; upload.value = ''; if (!file) return;
+ const revision = ++inputRevision;
+ try {
+ if (file.size > 1000000) throw new Error('Choose a text file of at most 1 MB.');
+ const content = await file.text();
+ if (inputRevision !== revision || input.disabled) return;
+ input.value = content; input.dispatchEvent(new Event('input', { bubbles: true }));
+ } catch (error) { if (inputRevision === revision) showInputError(error); }
+ }); wrapper.append(upload);
+ }
+ if (field.key === 'noSeed') wrapper.hidden = true;
+ return wrapper;
+}
+function fieldRow(body, fields, suffix = '') {
+ const grid = element('div', '', `field-grid columns-${fields.length}`);
+ for (const field of fields) grid.append(renderField(field, field.key + suffix));
+ body.append(grid); return grid;
+}
+for (const definition of GROUPS) {
+ const body = group(definition.key, definition.label);
+ if (definition.key === 'seed-options') {
+ const wrapper = element('div', '', 'field seed-mode');
+ const label = element('label', 'Seed constraint'); label.htmlFor = 'seed-mode';
+ const select = element('select'); select.id = 'seed-mode';
+ for (const [value, name] of [['none', 'Disable seed constraint'], ['explicit', 'Explicit seed'], ['identify', 'Identify seeds']]) {
+ const option = element('option', name); option.value = value; select.append(option);
+ }
+ select.value = 'identify'; wrapper.append(label, select); body.append(wrapper);
+ }
+ if (definition.rows) {
+ for (const row of definition.rows) fieldRow(body, row);
+ } else fieldRow(body, definition.fields);
+ if (definition.strand) {
+ const p = definition.strand;
+ const shared = FIELDS.filter((field) => ['accNoLP', 'accNoGUend'].includes(field.key));
+ fieldRow(body, shared, p === 't' ? '-target' : '');
+ body.append(element('p', 'These two accessibility settings apply to both strands.', 'input-help'));
+ const columns = element('div', '', 'accessibility-columns'); body.append(columns);
+ const accessibility = element('div'), shape = element('div'); columns.append(accessibility, shape);
+ const field = (key) => FIELDS.find((entry) => entry.key === p + key);
+ fieldRow(accessibility, [field('AccConstr')]);
+ fieldRow(accessibility, [field('AccFile')]);
+ fieldRow(shape, [field('Shape')]);
+ fieldRow(shape, [field('ShapeMethod'), field('ShapeConversion')]);
+ }
+}
+function checkbox(container, key, labelText, description) {
+ const label = element('label'), input = element('input'); input.type = 'checkbox'; input.value = key;
+ if (description) label.title = description;
+ label.append(input, document.createTextNode(labelText)); container.append(label); return input;
+}
+const outputBody = byId('output-options').querySelector('.group-body');
+outputBody.append(element('h3', 'CSV output columns', 'subheading'));
+outputBody.append(element('p', 'seq1 is the target; seq2 is the query. Hover over a column name for details. Select columns for the table and CSV download; table visibility can be changed after computation.', 'input-help'));
+const selectionTools = element('div', '', 'selection-tools'); outputBody.append(selectionTools);
+const selectColumns = (selection) => {
+ csvOrder = [...selection]; for (const input of csvInputs) input.checked = selection.includes(input.value); inputsChanged();
+};
+for (const [name, selection] of [['Select all', () => CSV_CATALOG.map((c) => c.key)], ['Task defaults', () => defaultColumns(getTask())], ['Clear selection', () => []]]) {
+ const button = element('button', name); button.type = 'button';
+ button.addEventListener('click', () => selectColumns(selection())); selectionTools.append(button);
+}
+for (const group of CSV_GROUPS) {
+ const section = element('fieldset', '', 'csv-group'), legend = element('legend', group.label);
+ section.dataset.csvGroup = group.label;
+ for (const [name, checked] of [['Select all', true], ['Deselect all', false]]) {
+ const button = element('button', name, 'text-button'); button.type = 'button';
+ button.setAttribute('aria-label', `${name} ${group.label} columns`);
+ button.addEventListener('click', () => {
+ for (const input of csvInputs.filter((input) => group.keys.includes(input.value))) input.checked = checked;
+ inputsChanged();
+ }); legend.append(button);
+ }
+ const picker = element('div', '', 'checkbox-grid csv-columns'); section.append(legend, picker); outputBody.append(section);
+ for (const key of group.keys) {
+ const column = CSV_CATALOG.find((column) => column.key === key);
+ csvInputs.push(checkbox(picker, key, key, column.description));
+ }
+}
+outputBody.append(element('h3', 'Additional output files', 'subheading'));
+outputBody.append(element('p', 'Reports are computed with the same settings. ASCII, detailed and ensemble reports each run a fresh engine instance and can take extra time.', 'input-help'));
+const outputPicker = element('div', '', 'checkbox-grid'); outputBody.append(outputPicker);
+for (const output of EXTRA_OUTPUTS) outputInputs.push(checkbox(outputPicker, output.key, output.label));
+const generalBody = group('general-options', 'CLI-only');
+generalBody.append(element('p', 'Process-level settings (threads, log paths, parameter-file paths and terminal help) are available in the CLI.'));
+const docs = element('a', 'Full CLI parameter documentation'); docs.href = 'https://github.com/BackofenLab/IntaRNA#readme'; generalBody.append(docs);
+
+function inputsChanged(message = 'Inputs changed. Compute to update the interactions.') {
+ inputRevision++;
+ sessions.deselect();
+ byId('diagnostics').hidden = true; byId('diagnostic-text').textContent = ''; status.textContent = message;
+}
+function updateLength(name) {
+ const warning = byId(`${name}-warning`); warning.hidden = true; warning.textContent = '';
+ if (!byId(name).value.trim()) { byId(`${name}-length`).textContent = 'No sequence entered'; return; }
+ try {
+ const parsed = parseFasta(byId(name).value, name === 'query' ? 'Query' : 'Target', getTask() === 'prediction');
+ warning.textContent = parsed.warnings.join(' '); warning.hidden = !parsed.warnings.length;
+ byId(`${name}-length`).textContent = `${parsed.records.length} record${parsed.records.length === 1 ? '' : 's'}, ${parsed.records.reduce((sum, r) => sum + r.sequence.length, 0).toLocaleString()} nucleotides`;
+ } catch { byId(`${name}-length`).textContent = 'Check sequence format and length'; }
+}
+function updateAvailability() {
+ const evaluation = getTask() === 'evaluation';
+ byId('evaluation-input').hidden = !evaluation;
+ byId('task-help').textContent = evaluation ? 'Evaluate supplied structures for one query and one target. Prediction constraints are ignored.' : 'Predict interactions for all query–target sequence pairs.';
+ byId('compute').textContent = evaluation ? 'Evaluate interactions' : 'Compute interactions';
+ for (const definition of GROUPS) {
+ const tab = byId(`tab-${definition.key}`); tab.hidden = evaluation && !!definition.prediction;
+ if (tab.hidden) {
+ byId(definition.key).hidden = true;
+ if (tab.getAttribute('aria-selected') === 'true') activateGroup('sequence-input');
+ }
+ for (const field of definition.fields) {
+ const input = byId(field.key);
+ input.disabled = evaluation && !!field.prediction || field.key.startsWith('helix') && byId('model').value !== 'B' || field.key === 'kineticScore' && byId('mode').value !== 'K';
+ }
+ }
+ byId('noSeed').checked = byId('seed-mode').value === 'none';
+ for (const field of FIELDS.filter((field) => field.key.startsWith('seed'))) {
+ byId(field.key).disabled = evaluation || (field.key === 'seedTQ' ? byId('seed-mode').value !== 'explicit' : byId('seed-mode').value !== 'identify');
+ }
+ for (const p of ['q', 't']) {
+ const mode = byId(`${p}Acc`).value;
+ for (const key of ['AccW', 'AccL', 'PfScale', 'AccConstr', 'AccFile', 'Shape', 'ShapeMethod', 'ShapeConversion']) {
+ byId(p + key).disabled = mode === 'N' || ['AccW', 'AccL'].includes(key) && mode !== 'C' || key === 'AccFile' && !['P', 'E'].includes(mode);
+ }
+ for (const key of ['accNoLP', 'accNoGUend']) byId(key + (p === 't' ? '-target' : '')).disabled = mode === 'N';
+ }
+ // Upload controls follow the text area they populate, including async reads.
+ for (const upload of form.querySelectorAll('input[data-for]')) upload.disabled = byId(upload.dataset.for).disabled;
+ for (const input of outputInputs) { input.disabled = evaluation && EXTRA_OUTPUTS.find((entry) => entry.key === input.value).prediction; if (input.disabled) input.checked = false; }
+ updateLength('query'); updateLength('target');
+}
+function resetParameters({ clearInputs = false } = {}) {
+ if (clearInputs) {
+ setTask('prediction');
+ byId('query').value = ''; byId('target').value = '';
+ for (const input of form.querySelectorAll('input[type=file]')) input.value = '';
+ byId('examples-menu').open = false; activateGroup('sequence-input');
+ }
+ csvOrder = defaultColumns(getTask());
+ byId('seed-mode').value = 'identify';
+ for (const field of FIELDS) { const input = byId(field.key); if (field.type === 'checkbox') input.checked = UI_DEFAULTS[field.key]; else input.value = UI_DEFAULTS[field.key]; }
+ syncAccessibilityFlags();
+ for (const input of csvInputs) input.checked = csvOrder.includes(input.value);
+ for (const input of outputInputs) input.checked = false;
+ byId('qIdxPos0').value = 1; byId('tIdxPos0').value = 1; byId('rri').value = '';
+ updateAvailability(); errorBox.hidden = true; inputsChanged(clearInputs ? 'Inputs cleared. Enter sequences or choose an example.' : 'Parameters reset to task defaults. Compute to update the interactions.');
+}
+byId('task').addEventListener('change', resetParameters);
+byId('reset').addEventListener('click', () => resetParameters({ clearInputs: true }));
+function syncAccessibilityFlags(source) {
+ for (const key of ['accNoLP', 'accNoGUend']) {
+ if (source?.id === key + '-target') byId(key).checked = source.checked;
+ byId(key + '-target').checked = byId(key).checked;
+ }
+}
+form.addEventListener('input', (event) => {
+ if (event.target.type === 'file') return;
+ syncAccessibilityFlags(event.target);
+ updateAvailability(); errorBox.hidden = true; inputsChanged();
+});
+function showInputError(error) {
+ errorBox.textContent = error.message; errorBox.hidden = false;
+}
+function applyParameters(request, message = 'Parameters loaded. Compute to analyze the inputs.') {
+ // Import validates every setting before this atomic UI update.
+ setTask(request.task);
+ resetParameters();
+ for (const key of ['query', 'target', 'qIdxPos0', 'tIdxPos0', 'rri']) byId(key).value = request[key];
+ for (const field of FIELDS) {
+ const value = request.parameters[field.key] ?? field.value;
+ if (field.type === 'checkbox') byId(field.key).checked = value;
+ else byId(field.key).value = value;
+ }
+ byId('seed-mode').value = request.parameters.noSeed ? 'none' : request.parameters.seedTQ ? 'explicit' : 'identify';
+ syncAccessibilityFlags();
+ csvOrder = [...request.csvColumns];
+ for (const input of csvInputs) input.checked = request.csvColumns.includes(input.value);
+ for (const input of outputInputs) input.checked = request.outputs.includes(input.value);
+ updateAvailability(); activateGroup('sequence-input'); inputsChanged(message);
+}
+for (const strand of ['query', 'target']) {
+ const input = byId(strand), picker = byId(`${strand}-upload`);
+ const loadFasta = async (files) => {
+ if (byId('inputs').disabled) return;
+ const revision = ++inputRevision;
+ try {
+ if (files.length !== 1) throw new Error('Choose or drop one FASTA file per field.');
+ const content = await readFastaFile(files[0], strand === 'query' ? 'Query' : 'Target', getTask());
+ if (inputRevision !== revision) return;
+ input.value = content;
+ updateAvailability(); errorBox.hidden = true;
+ inputsChanged(`${strand === 'query' ? 'Query' : 'Target'} FASTA loaded. Compute to analyze the inputs.`);
+ } catch (error) { if (inputRevision === revision) showInputError(error); }
+ };
+ byId(`${strand}-upload-button`).addEventListener('click', () => picker.click());
+ picker.addEventListener('change', () => { const files = [...picker.files]; picker.value = ''; if (files.length) loadFasta(files); });
+ input.addEventListener('dragover', (event) => {
+ if (event.dataTransfer.types.includes('Files')) { event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; input.classList.add('drag-over'); }
+ });
+ input.addEventListener('dragleave', () => input.classList.remove('drag-over'));
+ input.addEventListener('drop', (event) => {
+ input.classList.remove('drag-over');
+ if (!event.dataTransfer.types.includes('Files')) return;
+ event.preventDefault(); loadFasta([...event.dataTransfer.files]);
+ });
+}
+byId('load-parameters').addEventListener('click', () => byId('parameter-upload').click());
+byId('parameter-upload').addEventListener('change', async (event) => {
+ const file = event.target.files[0]; event.target.value = ''; if (!file) return;
+ const revision = ++inputRevision;
+ try {
+ if (file.size > MAX_PARAMETER_BYTES) throw new Error('Choose a JSON file of at most 5 MB.');
+ const imported = importParameters(await file.text());
+ if (inputRevision !== revision) return;
+ applyParameters(imported);
+ } catch (error) { if (inputRevision === revision) showInputError(error); }
+});
+byId('store-parameters').addEventListener('click', async () => {
+ // Snapshot before waiting for build metadata so edits cannot change the download.
+ const request = currentRequest();
+ try { downloadText('parameter.json', serializeParameters(request, await buildInfo)); }
+ catch (error) { showInputError(error); }
+});
+async function loadExamples() {
+ const container = byId('example-options');
+ try {
+ const response = await fetch(new URL('./examples/index.json', import.meta.url));
+ if (!response.ok) throw new Error('Example list is unavailable.');
+ const files = await response.json();
+ if (!Array.isArray(files) || !files.length || files.some((name) => typeof name !== 'string' || !/^[a-z0-9-]+\.json$/.test(name))) throw new Error('Invalid example list.');
+ const examples = await Promise.all(files.map(async (filename) => {
+ const response = await fetch(new URL(`./examples/${filename}`, import.meta.url));
+ if (!response.ok) throw new Error('An example is unavailable.');
+ const example = await response.json();
+ if (typeof example.name !== 'string' || typeof example.description !== 'string') throw new Error('Invalid example description.');
+ const request = importParameters(example.configuration);
+ prepareRequest(request);
+ return { ...example, request };
+ }));
+ container.replaceChildren();
+ for (const example of examples) {
+ const button = element('button'); button.type = 'button';
+ button.append(element('strong', example.name), element('span', example.description));
+ button.addEventListener('click', () => {
+ if (byId('inputs').disabled) return;
+ applyParameters(example.request, `${example.name} loaded. Compute to analyze the example.`);
+ byId('examples-menu').open = false;
+ });
+ container.append(button);
+ }
+ } catch (error) { container.replaceChildren(element('p', `${error.message} Reload to try again.`, 'input-help')); }
+}
+loadExamples();
+byId('examples-menu').addEventListener('keydown', (event) => {
+ if (event.key === 'Escape') { byId('examples-menu').open = false; byId('examples-menu').querySelector('summary').focus(); }
+});
+byId('abort').addEventListener('click', () => runner.abort());
+dialog.addEventListener('cancel', (event) => { event.preventDefault(); runner.abort(); });
+window.addEventListener('pagehide', () => runner.abort());
+function currentParameters() {
+ // Keep inactive edits in the form for toggling back, but exclude them from
+ // validation, saved JSON and native computation. Shared flags stay active
+ // whenever either strand uses accessibility.
+ return Object.fromEntries(FIELDS.map((field) => {
+ const input = byId(field.key);
+ const inactive = ['accNoLP', 'accNoGUend'].includes(field.key)
+ ? input.disabled && byId(field.key + '-target').disabled : input.disabled;
+ return [field.key, inactive ? field.value : field.type === 'checkbox' ? input.checked : input.value];
+ }));
+}
+function currentRequest() {
+ const selected = csvInputs.filter((input) => input.checked).map((input) => input.value);
+ const columns = [...csvOrder.filter((key) => selected.includes(key)), ...selected.filter((key) => !csvOrder.includes(key))];
+ return {
+ task: getTask(), query: byId('query').value, target: byId('target').value,
+ qIdxPos0: byId('qIdxPos0').value, tIdxPos0: byId('tIdxPos0').value, rri: byId('rri').value,
+ parameters: currentParameters(),
+ csvColumns: columns, outputs: outputInputs.filter((input) => input.checked && !input.disabled).map((input) => input.value),
+ };
+}
+form.addEventListener('submit', async (event) => {
+ event.preventDefault();
+ const request = currentRequest();
+ errorBox.hidden = true;
+ let prepared;
+ try {
+ if (getTask() === 'prediction' && byId('seed-mode').value === 'explicit' && !request.parameters.seedTQ.trim()) throw new Error('Explicit seed: enter target&query dot-bar encodings.');
+ prepared = prepareRequest(request); } catch (error) { errorBox.textContent = error.message; errorBox.hidden = false; return; }
+ inputsChanged('Computing interactions…'); byId('loading-status').textContent = 'Loading the prediction engine…';
+ byId('inputs').disabled = true; byId('results').setAttribute('aria-busy', 'true'); dialog.showModal();
+ try {
+ const result = await runner.run(request, (message) => { byId('loading-status').textContent = message; });
+ // Keep the executed inputs and outputs together, independent of later edits.
+ const provenance = await buildInfo.catch(() => 'Compiled version information is unavailable.');
+ status.textContent = sessions.add(prepared, result, provenance);
+ } catch (error) {
+ if (error.name === 'AbortError') status.textContent = 'Computation aborted. You can edit the sequences and try again.';
+ else { status.textContent = 'Computation failed.'; errorBox.textContent = error.message; errorBox.hidden = false; byId('diagnostic-text').textContent = error.diagnostics || ''; byId('diagnostics').hidden = !error.diagnostics; }
+ } finally {
+ byId('inputs').disabled = false; updateAvailability(); byId('results').setAttribute('aria-busy', 'false'); dialog.close(); byId('compute').focus();
+ }
+});
+activateGroup('sequence-input');
+resetParameters({ clearInputs: true });
+const buildInfo = fetch(new URL('./build-info.txt', import.meta.url)).then((response) => {
+ if (!response.ok) throw new Error('Missing build provenance'); return response.text();
+});
+// Keep the rejection handled even if no archive is requested.
+buildInfo.catch(() => {});
+fetch(new URL('./build-info.json', import.meta.url)).then((response) => {
+ if (!response.ok) throw new Error('Missing build provenance'); return response.json();
+}).then((info) => { byId('version-info').textContent = `IntaRNA ${info.intarna} (${info.commit.slice(0, 12)}${info.dirty ? ', modified sources' : ''}) · ViennaRNA ${info.viennarna} · Emscripten ${info.emscripten}`; }).catch(() => { byId('version-info').textContent = 'Build versions unavailable. Serve the staged interface with its compiled engine and build-info files.'; });
+if (!globalThis.Worker || !globalThis.WebAssembly) { byId('compute').disabled = true; errorBox.textContent = 'This browser needs WebAssembly and Web Workers. Use a current browser or the command line version.'; errorBox.hidden = false; }
diff --git a/webinterface/archive.js b/gui/archive.js
similarity index 60%
rename from webinterface/archive.js
rename to gui/archive.js
index 63a510c9..8b1995ca 100644
--- a/webinterface/archive.js
+++ b/gui/archive.js
@@ -1,4 +1,4 @@
-import { makeArguments } from './protocol.js';
+import { serializeParameters } from './parameter-file.js';
import { projectCsv } from './results.js';
const encoder = new TextEncoder();
@@ -48,32 +48,11 @@ export function zipFiles(files) {
}
export function archiveFiles(request, result, provenance) {
- if (!provenance?.trim()) throw new Error('Compiled version information is unavailable');
- const versionComments = provenance.trimEnd().split(/\r?\n/).map((line) => `# ${line}`).join('\n');
- const reports = [['normal', 'N'], ['detailed', 'D'], ['ensemble', 'E']].filter(([key]) => request.outputs.includes(key));
- const config = (format, filename) => {
- // Reuse the worker's effective arguments, replacing only MEMFS paths and
- // internal visualization columns. Optional data are shipped as text files.
- const args = makeArguments(request, format).filter((arg) => !['--qIdxPos0=1', '--tIdxPos0=1'].includes(arg));
- const settings = args.map((arg) => {
- if (arg.startsWith('--outCsvCols=')) return `outCsvCols = ${request.csvColumns.join(',')}`;
- return arg.slice(2).replace('=', ' = ').replace(/\/(query\.fa|target\.fa|[qt](?:Shape|AccFile)\.txt)/g, '$1')
- .replace('/results.csv', 'intarna-interactions.csv').replace('/report.txt', `intarna-${filename}.txt`).replace('/output/', '');
- });
- return `${versionComments}\n# Run from the extracted archive directory with these compiled library versions.\n# Reproduce: IntaRNA --parameterFile=${filename}.cfg\n# Only non-default analysis settings are listed; input/output routing is explicit.\n${settings.join('\n')}\n`;
- };
- const parameters = config('C', 'parameters') + reports.map(([key]) => `# Additional report: IntaRNA --parameterFile=${key}.cfg\n`).join('');
- const files = [
- { name: 'query.fa', text: request.query.fasta }, { name: 'target.fa', text: request.target.fasta },
- { name: 'parameters.cfg', text: parameters }, { name: 'build-info.txt', text: provenance },
+ return [
+ { name: 'parameter.json', text: serializeParameters({ ...request, qIdxPos0: request.parameters.qIdxPos0, tIdxPos0: request.parameters.tIdxPos0 }, provenance) },
{ name: 'intarna-interactions.csv', text: projectCsv(result.csv, request.csvColumns) },
...result.files,
- ...reports.map(([key, format]) => ({ name: `${key}.cfg`, text: config(format, key) })),
];
- for (const [key, value] of Object.entries(request.parameters)) {
- if (/^(q|t)(Shape|AccFile)$/.test(key) && value) files.push({ name: `${key}.txt`, text: value + '\n' });
- }
- return files;
}
export function createArchive(request, result, provenance) {
diff --git a/webinterface/assets/README.md b/gui/assets/README.md
similarity index 100%
rename from webinterface/assets/README.md
rename to gui/assets/README.md
diff --git a/webinterface/assets/bioinf-fr-logo-blau.png b/gui/assets/bioinf-fr-logo-blau.png
similarity index 100%
rename from webinterface/assets/bioinf-fr-logo-blau.png
rename to gui/assets/bioinf-fr-logo-blau.png
diff --git a/webinterface/assets/logo-uni-freiburg.png b/gui/assets/logo-uni-freiburg.png
similarity index 100%
rename from webinterface/assets/logo-uni-freiburg.png
rename to gui/assets/logo-uni-freiburg.png
diff --git a/webinterface/columns.js b/gui/columns.js
similarity index 83%
rename from webinterface/columns.js
rename to gui/columns.js
index 6b42b93a..9c096021 100644
--- a/webinterface/columns.js
+++ b/gui/columns.js
@@ -313,3 +313,23 @@ export const CSV_CATALOG = [
"numeric": true
}
];
+
+// Every native column appears once. Ensemble energies and their components are
+// grouped with probabilities so users can see the cost/meaning of those outputs.
+export const CSV_GROUPS = [
+ { label: 'Energy', keys: ['E', 'ED1', 'ED2', 'E_init', 'E_loops', 'E_dangleL', 'E_dangleR', 'E_endL', 'E_endR', 'E_hybrid', 'E_norm', 'E_hybridNorm', 'E_add', 'seedE', 'seedED1', 'seedED2'] },
+ { label: 'Output', keys: ['subseqDP', 'subseqDB', 'hybridDP', 'hybridDB', 'hybridDPfull', 'hybridDBfull', 'bpList'] },
+ { label: 'Sequence', keys: ['id1', 'id2', 'seq1', 'seq2', 'subseq1', 'subseq2'] },
+ { label: 'Interaction', keys: ['start1', 'end1', 'start2', 'end2', 'seedStart1', 'seedEnd1', 'seedStart2', 'seedEnd2'] },
+ { label: 'Partition Function', keys: ['Pu1', 'Pu2', 'seedPu1', 'seedPu2', 'w', 'Etotal', 'Eall', 'Eall1', 'Eall2', 'Zall', 'Zall1', 'Zall2', 'EallTotal', 'P_E', 'RT'] },
+];
+
+export function defaultColumns(task) {
+ const context = ['id1', 'start1', 'end1', 'id2', 'start2', 'end2', 'subseqDP', 'hybridDP', 'E'];
+ // Seed energies remain available in evaluation, where native output is NAN.
+ // List them explicitly: seedEnd1/2 are coordinates, not energy columns.
+ const extra = task === 'evaluation'
+ ? [...CSV_CATALOG.filter(({ key }) => key.startsWith('E')).map(({ key }) => key), 'seedE', 'seedED1', 'seedED2']
+ : ['ED1', 'ED2', 'seedE', 'seedStart1', 'seedStart2'];
+ return [...new Set([...context, ...extra])];
+}
diff --git a/gui/docs/json-workflow.md b/gui/docs/json-workflow.md
new file mode 100644
index 00000000..df5f7dfb
--- /dev/null
+++ b/gui/docs/json-workflow.md
@@ -0,0 +1,149 @@
+# JSON settings and reproducible runs
+
+The action bar starts with Compute/Evaluate, followed by Input Parameters:
+Examples, Store, Load and Reset. Store downloads
+`parameter.json` containing non-default settings and embedded input text in the
+[native JSON format](../../doc/json-parameters.md). `_comment` records compiled
+tool/library versions and executable reproduction commands. It is JSON metadata,
+not a nonstandard comment syntax. Settings can be saved before sequences are
+complete; Compute still validates the complete job before loading WASM.
+
+Load validates the complete document before replacing the form, including the
+task, signed start indices, input text, CSV selection/order and additional
+outputs. Malformed documents and settings the browser cannot represent leave
+the form unchanged with an error. Native-only settings and external file paths
+must be used in the CLI; the browser requires embedded text. JSON uploads are
+limited to 5 MB. The application never uploads these files to a server.
+
+ZIP uses the same serializer on the completed run, including normalized FASTA.
+It contains **only** `parameter.json` and output files. Native replay uses:
+
+```sh
+IntaRNA --parameterFile=parameter.json --out=intarna-interactions.csv
+```
+
+Additional report commands are listed in `_comment`. They override `outMode`
+and CSV-only settings while retaining the same inputs and analysis settings.
+Reports run independently, matching the worker. The WASM integration test
+replays the JSON in fresh engines and compares every output byte.
+
+## Computed sessions
+
+Each successful computation, including a run with no interaction rows, creates a
+new session with a unique ID within the page and a default name such as
+`Prediction 1` or `Evaluation 2`. The session retains the computed inputs in JSON,
+CSV output, additional files and diagnostics. Editing parameters, Load, examples,
+Reset, invalid inputs, failed computations and cancellation preserve prior
+sessions. Failed or aborted computations do not create a session.
+
+The newest session opens automatically under **Results**. Use **Show**, select
+its header/name, or select its output area to restore that session’s
+computed inputs and fold the other sessions. The task, sequence text, signed
+indices, SHAPE/accessibility text, CSV selection/order and additional output
+selection are restored together. **Hide** folds the output. Each session
+keeps its own filter, sorting and visible columns; folded vaRRI frames are unloaded
+and restored when their session is reopened.
+
+Edit **Session name** directly in the header. Names are plain text; an empty name
+returns to the default. **Download ZIP** remains available while folded and always
+archives that session’s original JSON and complete outputs, regardless of the
+current form or table filters. The session name supplies a safe download filename;
+ZIP members remain `parameter.json` and output files. The garbage-bin button
+removes only its session and releases its output; it does not change form inputs.
+
+Sessions exist only in the current page’s browser memory. They are not written to
+browser storage or uploaded. Reloading or closing the page discards them; use ZIP
+exports to retain results. Memory use grows with the runs kept open, so delete
+sessions that are no longer needed. Store continues to save the editable form.
+If compiled metadata cannot be fetched, a completed run is still retained and its
+JSON explicitly records that version information is unavailable.
+
+## Reset and examples
+
+Reset clears the editable sequences, interactions and uploaded files and restores
+prediction defaults (10 interactions per pair, expanded energy/seed columns).
+Evaluation defaults select all energy columns. These GUI defaults are written
+explicitly in JSON when they differ from the CLI; importing existing JSON still
+uses native defaults for omitted parameters. Inactive controls keep their edits
+in the form for switching modes, but their values are omitted from executed and
+stored settings. Previously computed sessions remain available. Empty FASTA
+fields show the short demonstration sequences as placeholders only. The Examples
+disclosure menu follows the Input Parameters label.
+
+Maintainers edit `gui/examples/index.json` (an array of local JSON filenames).
+Each listed file contains `name`, `description`, and `configuration`, where the
+configuration is the same native JSON document used by Load. Two replaceable
+examples cover prediction and evaluation. Loading validates their settings and
+sequences through the same importer and computation validator. Missing or invalid
+examples show an error in the menu; they never overwrite the form.
+
+## Local FASTA files
+
+Each strand has an Upload button and accepts a text-file drop into its textarea.
+Files are read locally (at most 1 MB), validated with the same FASTA checks as
+manual entry, and replace only that strand after successful validation. Prediction
+accepts up to 50 FASTA records per strand, each 2–5,000 nt; evaluation requires
+one record. The longest selected query × longest selected target must be at most
+1,000,000. Names end at the first whitespace; ignored header descriptions produce
+a warning below the corresponding field. Both FASTA fields scroll horizontally
+and vertically. Invalid files leave the
+previous input intact. A later edit, reset, import or computation discards any
+pending file read, so delayed reads cannot replace newer input.
+
+## Validation of the PR #287 revision (2026-10-23)
+
+- GCC 14 release and debug: `make tests -j2` passed all nine API/CLI groups,
+ including `runParameterFile.sh`. The existing out-of-tree CLI fixture harness
+ required source data files linked into each build's `tests/data` directory.
+- `npm test --prefix gui`: all 18 tests passed.
+- Emscripten 4.0.23 / ViennaRNA 2.7.2 / Boost 1.87.0: actual WASM build,
+ `node scripts/wasm/smoke.mjs` and `node scripts/wasm/smoke-protocol.mjs` passed.
+ Exported JSON reproduced selected CSV, signed/multiple sequences, additional
+ outputs and reports byte for byte in fresh engines.
+- `scripts/wasm/test-browser.py` passed in Chromium and Firefox at a nested URL,
+ covering blank startup/reset, both JSON examples, Load/Store round trips,
+ FASTA file upload/drop, stale-read protection, task keyboard controls, JSON ZIP
+ contents, result exploration, cancellation/recovery and mobile overflow.
+ The desktop screenshot was inspected.
+- Embedded ViennaRNA energy-parameter text matched native file input. Existing
+ dependency-download and ViennaRNA-release tests, shell syntax, workflow YAML,
+ source-distribution contents, vendor checksums and `git diff --check` passed.
+
+## Session follow-up validation (2026-10-23)
+
+- `npm test --prefix gui`: all 18 Node tests passed.
+- `scripts/wasm/test-browser.py` passed in Chromium and Firefox with real WASM
+ at nested URLs. Session checks cover input/JSON restoration, task changes,
+ signed indices, SHAPE text, CSV order, reports, independent filters/columns,
+ edits/reset/imports, failed/aborted runs, keyboard selection, safe renaming,
+ folded-session archives, deletion, stale file reads and page reload.
+- Desktop screenshots were inspected; session controls remain accessible at a
+ 390 px viewport without document overflow.
+- `node scripts/wasm/smoke-protocol.mjs` passed native-reference checks and
+ byte-identical archive replay. Browser checks reused the parent branch’s
+ compiled Emscripten 4.0.23 / ViennaRNA 2.7.2 assets with their original metadata;
+ this follow-up changes no C++ code or worker protocol.
+- A fresh GCC 14 release build passed `make tests -j2`: all nine native API/CLI
+ groups passed. Tracked fixtures were linked into the out-of-tree build’s
+ `tests/data` directory for the existing CLI harness.
+- Static staging (including the new session module and engine digest checks),
+ shell syntax and `git diff --check` passed.
+
+## PR #299 review validation (2026-10-24)
+
+- `npm test --prefix gui`: all 20 tests passed, including FASTA limits, header
+ warnings, complete column grouping and explicit browser-default JSON replay.
+- `scripts/wasm/test-browser.py` passed with real WASM in Chromium and Firefox.
+ It covers the reordered panels, aligned controls, shared accessibility flags,
+ seed/accessibility/helix modes, inactive values, explicit-seed Load/Store,
+ group selection, FASTA scrolling/warnings and result header/name selection,
+ plus the existing prediction/evaluation, ZIP, abort and session checks.
+- Desktop and 390 px mobile screenshots were inspected. All seven panels have
+ no document overflow; table contents retain their own horizontal scrolling.
+- `node scripts/wasm/smoke-protocol.mjs ` passed native-reference
+ checks and byte-identical JSON archive replay. The unchanged engine uses the
+ parent branch's Emscripten 4.0.23 / ViennaRNA 2.7.2 assets and original metadata.
+- A fresh GCC 14 release build passed `make tests -j2`: all nine API/CLI groups.
+ Existing CLI fixtures were linked into the out-of-tree `tests/data` directory.
+- Static staging with engine digest validation, JavaScript/Python syntax checks,
+ shell syntax and `git diff --check` passed.
diff --git a/webinterface/docs/step1-wasm-compilation.md b/gui/docs/step1-wasm-compilation.md
similarity index 100%
rename from webinterface/docs/step1-wasm-compilation.md
rename to gui/docs/step1-wasm-compilation.md
diff --git a/webinterface/docs/step2-worker-architecture.md b/gui/docs/step2-worker-architecture.md
similarity index 85%
rename from webinterface/docs/step2-worker-architecture.md
rename to gui/docs/step2-worker-architecture.md
index 9617fbbd..018d3470 100644
--- a/webinterface/docs/step2-worker-architecture.md
+++ b/gui/docs/step2-worker-architecture.md
@@ -7,9 +7,10 @@ messages. `prepareRequest` validates both the UI and worker boundary; argument
construction only accepts schema keys, enumerated output formats and fixed paths.
The worker imports `wasm/intarna.js`, maps `locateFile` to the lowercase WASM
-asset and creates an isolated `createIntaRNA` instance. It writes normalized
-multi-FASTA to `/query.fa` and `/target.fa`; local SHAPE/accessibility text uses
-fixed `q/tShape.txt` or `q/tAccFile.txt` paths. CSV is written to `/results.csv`;
+asset and creates an isolated `createIntaRNA` instance. It writes one `/parameter.json` document using the native
+[JSON contract](../../doc/json-parameters.md), embedding normalized multi-FASTA
+and SHAPE/accessibility text. Each invocation passes only
+`--parameterFile=/parameter.json`. CSV is written to `/results.csv`;
additional native output prefixes write files under `/output/`. Multi-FASTA
filenames use the CLI's native record-number suffixes.
@@ -30,5 +31,5 @@ in the URL. The iframe is cleared on input edits; no third-party viewer is
loaded before selection. No backend, shared memory or cross-origin isolation
is needed for computation. A single WASM instance can grow up to 1 GiB.
-Run `npm test --prefix webinterface`, then the native-reference WASM smoke tests
+Run `npm test --prefix gui`, then the native-reference WASM smoke tests
and Chromium/Firefox suites described in step 4.
diff --git a/webinterface/docs/step3-frontend-ui.md b/gui/docs/step3-frontend-ui.md
similarity index 62%
rename from webinterface/docs/step3-frontend-ui.md
rename to gui/docs/step3-frontend-ui.md
index c16dcbde..e0436b75 100644
--- a/webinterface/docs/step3-frontend-ui.md
+++ b/gui/docs/step3-frontend-ui.md
@@ -7,7 +7,7 @@ header indigo `#344a9a`, action blue `#145a96`, dark blue `#0f4676`, pale blue
`#eaf2f8`, white `#ffffff` and focus gold `#f4bb44`. Segoe UI/Tahoma/Geneva serve
labels and body text; Consolas/Liberation Mono serve sequence and structure data.
The official group and university logos share the header with the IntaRNA mark.
-Their local copies and source attribution are in `webinterface/assets/`.
+Their local copies and source attribution are in `gui/assets/`.
The layout puts the scientific task first, then mutually exclusive parameter
groups, followed by the compute button, results, additional files and versions.
@@ -21,25 +21,26 @@ the group website. Footer and table notes use the available width, wrapping only
when the viewport requires it.
```text
-Group logo IntaRNA / RNA–RNA interaction analysis University logo
+Group logo IntaRNA package / RNA–RNA interaction analysis University logo
RNA–RNA interactions Computations stay on your device
-Task: Prediction / Evaluation Load example
-[ Sequence input | Query | Target | Seed | Helix | SHAPE | Energy | Output | General ]
+Task: Prediction / Evaluation
+[ Sequences | Seed | Output | Model & Energy | Query | Target | CLI-only ]
[ Active panel: e.g. query + target, start indices, evaluation structures ]
-Reset parameters Compute
-Interactions / search / show-hide columns / CSV + ZIP download
+Compute Input Parameters: Examples / Store / Load / Reset
+Results / Show-Hide / name / ZIP download / delete
+Interactions / search / show-hide columns / CSV download
Selected interaction / embedded vaRRI
Additional output files / diagnostics / compiled versions
```
-Every CLI group has a tab with one visible panel. Sequence input is selected
+Every CLI group has a tab with one visible panel. Sequences is selected
initially. Tabs expose `aria-selected`, `aria-controls` and roving focus; Left/Right
move between available tabs, and Home/End select the first/last. Tab names wrap
on narrow screens. Prediction-only tabs and controls are disabled/hidden during
-evaluation; if the selected tab becomes unavailable, Sequence input is selected.
-Changing task resets parameters to their CLI defaults while preserving sequence text; Load example also restores the
-complementary pair. The native default output count is one; browser users can
-request 0–100 interactions per pair. Zero enables accessibility-only output.
+evaluation; if the selected tab becomes unavailable, Sequences is selected.
+Changing task resets parameters to their task defaults while preserving sequence text; Load example also restores the
+complementary pair. The browser defaults to 10 interactions per pair (the native default remains
+one), and accepts 0–100. Zero enables accessibility-only output.
## Inputs, defaults and validation
@@ -53,15 +54,29 @@ CSV uses a fixed semicolon delimiter.
Process settings (threads, file/log paths, parameter files, help/version commands)
are replaced by the worker lifecycle, local data inputs, downloads and displayed
compiled versions. The base-pair maximization energy model is intentionally
-excluded. The helix-block interaction model B is supported.
+excluded. The helix-block interaction model B is supported, with helix controls at the end
+of Model & Energy, enabled only for model B. Seed constraint offers Disable,
+Explicit seed and Identify seeds, enabling only the relevant controls. Query and
+Target include their own accessibility/SHAPE columns and synchronized copies of
+the two shared accessibility flags. Ignore disables accessibility controls;
+import modes disable window/span and enable the data input. Disabled settings
+remain editable when re-enabled but do not enter the request. Explicit row grids
+align inputs even when their labels wrap.
-Prediction accepts plain sequence or up to 20 FASTA records per strand.
+CSV columns are grouped as Energy, Output, Sequence, Interaction and Partition
+Function, with per-group selection actions and name-only labels with description
+tooltips. Prediction adds ED1, ED2, seedE, seedStart1 and seedStart2 to the native
+CSV columns; evaluation selects all energy outputs. JSON records these browser
+defaults explicitly so native replay retains the same output.
+
+Prediction accepts plain sequence or up to 50 FASTA records per strand.
Evaluation requires one query and target and 1–100 predefined structures.
Wrapped/lowercase sequence text and DNA T normalize to uppercase RNA U. A/C/G/U/N
-are accepted. Each record is 2–2,000 nt; seed predictions additionally require
+are accepted. Each record is 2–5,000 nt; seed predictions additionally require
at least `seedBP` nucleotides. FASTA IDs end at the first whitespace, matching the CLI. Duplicate IDs (including collisions after
-native CSV semicolon replacement) are rejected. The total selected all-vs-all
-query length × target length, or sum of pairwise products, is capped at 1,000,000.
+native CSV semicolon replacement) are rejected. Ignored header descriptions are reported below the input. The longest selected
+query length × longest selected target length is capped at 1,000,000, including
+pairwise mode; the cap is not summed over records.
The per-strand start index applies to all records and defaults to one. Negative
indices skip zero, matching `RnaSequence`; output coordinates remain inclusive.
@@ -78,7 +93,7 @@ accessibility text can be pasted or loaded from a local file (up to 1 MB). Data
are written to fixed MEMFS paths, never used as user-provided CLI arguments.
Native diagnostics remain available for model-specific failures.
-Inputs/results clear on edits; the form disables during execution. Abort and
-Escape terminate the worker during startup or C++ execution. Status, errors,
+Edits update the form while completed sessions retain their inputs and results;
+the form disables during execution. Abort and Escape terminate the worker during startup or C++ execution. Status, errors,
empty output and diagnostic text have dedicated regions. Keyboard focus,
reduced motion and mobile overflow are covered by real browser tests.
diff --git a/webinterface/docs/step4-results-and-deployment.md b/gui/docs/step4-results-and-deployment.md
similarity index 85%
rename from webinterface/docs/step4-results-and-deployment.md
rename to gui/docs/step4-results-and-deployment.md
index 8d2e3aaf..0d665211 100644
--- a/webinterface/docs/step4-results-and-deployment.md
+++ b/gui/docs/step4-results-and-deployment.md
@@ -30,8 +30,10 @@ Empty output is successful and still permits CSV/accessibility downloads.
## Selected interaction in vaRRI
Each row has a keyboard-accessible View button; clicking a row also selects it.
-Selection survives table sorting, filtering and visibility changes. Input edits,
-new runs, errors and cancellation clear the viewer and archive snapshot.
+Selection survives table sorting, filtering and visibility changes within each
+session. Input edits, new runs, errors and cancellation preserve prior sessions
+and their archive snapshots. Folding a session unloads its viewer; reopening
+restores its selected interaction.
`visualization.js` uses native seq1 = target, seq2 = query and maps the native
CSV identifiers to the completed run's normalized FASTA records (including
semicolon-to-underscore ID conversion). It pads the interaction dot-bracket
@@ -54,31 +56,23 @@ the full-viewer link permits retry, and scientific outputs remain downloadable.
## Reproducible ZIP download
-Download ZIP sits beside Download CSV. `archive.js` creates a standard UTF-8,
-uncompressed ZIP with CRC-32 checksums locally, without a library or CDN. It uses
+Download ZIP sits in each session header, including while its output is folded.
+`archive.js` creates a standard UTF-8, uncompressed ZIP with CRC-32 checksums locally, without a library or CDN. It uses
the completed request and result, independent of table sorting/filtering and
-column visibility. Editing inputs discards the completed snapshot.
-
-The archive contains:
-
-- `query.fa` and `target.fa`: the exact normalized FASTA consumed by the worker.
-- `parameters.cfg`: native parameter-file syntax, non-default effective analysis
- settings, explicit input/output routing and the selected CSV columns. Comments
- include the exact compiled version/provenance text and the replay command
- `IntaRNA --parameterFile=parameters.cfg` from the extracted directory.
-- `build-info.txt`: original compiled engine/library versions and asset hashes.
-- `intarna-interactions.csv`: all results with the selected CSV columns, identical
- to Download CSV even if the table has been sorted, filtered or columns hidden.
-- Every generated additional output, with its original filename and contents.
-- Local SHAPE/accessibility input files when supplied, referenced by relative paths.
-- Separate `normal.cfg`, `detailed.cfg` or `ensemble.cfg` when those reports were
- requested, with their replay commands also listed in `parameters.cfg`. Each
- report requires a separate native invocation, matching worker behavior.
-
-Replay with the recorded IntaRNA and library versions. Export translates MEMFS
-paths to portable relative paths and omits defaults using the worker's own
-argument builder. ZIP download reports a clear error if compiled version text
-is unavailable, rather than claiming incomplete provenance.
+column visibility. Editing inputs preserves every completed snapshot.
+
+The archive contains only `parameter.json`, `intarna-interactions.csv`, and every
+generated additional output with its original filename and contents. JSON embeds
+normalized FASTA and any SHAPE/accessibility text, non-default settings, selected
+CSV columns, compiled provenance and native replay commands in `_comment`.
+Additional report commands override CSV-only settings and use another native
+invocation, matching the worker. All JSON is valid with standard parsers.
+
+Load/Store use this same JSON format. Store snapshots the current editable form;
+ZIP snapshots the completed run and ignores table filtering/sorting/visibility.
+Store requires the compiled version file. Completed sessions still retain their
+results if that file is unavailable, recording the missing version information
+explicitly in JSON. See [the JSON workflow](json-workflow.md).
## Packaging and deployment
@@ -87,7 +81,7 @@ bash scripts/wasm/build.sh
node scripts/wasm/smoke.mjs
node scripts/wasm/smoke-protocol.mjs
bash scripts/wasm/stage.sh
-npm test --prefix webinterface
+npm test --prefix gui
python3 -m pip install -r scripts/wasm/requirements-browser.txt
python3 -m playwright install chromium firefox
python3 -m http.server 8765 --directory build-wasm/site
@@ -115,7 +109,7 @@ the actual default branch, `master`. After JS tests, the real WASM smoke test an
Chromium/Firefox integration tests pass, the package job combines:
- Existing Jekyll guide at `/IntaRNA/` and API at `/IntaRNA/api/`.
-- Tested application at `/IntaRNA/webinterface/`.
+- Tested application at `/IntaRNA/gui/`.
The artifact `intarna-pages-preview` permits PR review of the complete site.
Pull requests and manual runs on feature branches do not publish. Only a push
@@ -146,7 +140,7 @@ and active C++, clean restart, empty predictions and asset-load recovery.
reference reports E = -28.04, ED1 = 4.60, ED2 = 4.86 kcal/mol; the short example
reports E = -21.40 with target/query coordinates 1–10 and ten suboptimals.
Invalid CLI input returns a nonzero status through C++ error handling.
-- `npm test --prefix webinterface`: all five input/lifecycle/CSV test groups pass.
+- `npm test --prefix gui`: all five input/lifecycle/CSV test groups pass.
- Playwright 1.64.0, Chromium and Firefox: full integration suite passes with the
official-dependency engine, including a nested `/site/` URL as used by Pages,
active-C++ cancellation, literal HTML-like identifiers and retry after failure.
@@ -177,7 +171,7 @@ claim a live Pages deployment.
Native imported accessibility reserves one length column for dangling-end
treatment; its energy is compared with the corresponding restricted native
interaction length rather than the unrestricted prediction.
-- `npm test --prefix webinterface`: ten protocol/results/lifecycle groups pass.
+- `npm test --prefix gui`: ten protocol/results/lifecycle groups pass.
- Real Chromium/Firefox integration covers all parameter groups, CLI defaults,
compiled versions/logos, task changes, multiple records, signed coordinates,
selected/visible CSV columns, additional previews/downloads and the original
diff --git a/gui/examples/evaluation.json b/gui/examples/evaluation.json
new file mode 100644
index 00000000..84cab246
--- /dev/null
+++ b/gui/examples/evaluation.json
@@ -0,0 +1,18 @@
+{
+ "name": "Evaluate a duplex",
+ "description": "Evaluate the energy of a supplied ten-base-pair RNA interaction.",
+ "configuration": {
+ "schema": "IntaRNA-parameters-v1",
+ "parameters": {
+ "personality": "IntaRNAeval",
+ "query": {
+ "text": ">query\nGGGAGGAGGG\n"
+ },
+ "target": {
+ "text": ">target\nCCCUCCUCCC\n"
+ },
+ "rri": "1||||||||||&1||||||||||",
+ "outCsvCols": "id1,start1,end1,id2,start2,end2,subseqDP,hybridDP,E,Etotal,ED1,ED2,E_init,E_loops,E_dangleL,E_dangleR,E_endL,E_endR,E_hybrid,E_norm,E_hybridNorm,E_add,Eall,Eall1,Eall2,EallTotal,seedE,seedED1,seedED2"
+ }
+ }
+}
diff --git a/gui/examples/index.json b/gui/examples/index.json
new file mode 100644
index 00000000..947fd53d
--- /dev/null
+++ b/gui/examples/index.json
@@ -0,0 +1 @@
+["prediction.json", "evaluation.json"]
diff --git a/gui/examples/prediction.json b/gui/examples/prediction.json
new file mode 100644
index 00000000..bd76029a
--- /dev/null
+++ b/gui/examples/prediction.json
@@ -0,0 +1,17 @@
+{
+ "name": "Short duplex",
+ "description": "Predict interactions between two complementary ten-nucleotide RNAs.",
+ "configuration": {
+ "schema": "IntaRNA-parameters-v1",
+ "parameters": {
+ "query": {
+ "text": ">query\nGGGAGGAGGG\n"
+ },
+ "target": {
+ "text": ">target\nCCCUCCUCCC\n"
+ },
+ "outNumber": 10,
+ "outCsvCols": "id1,start1,end1,id2,start2,end2,subseqDP,hybridDP,E,ED1,ED2,seedE,seedStart1,seedStart2"
+ }
+ }
+}
diff --git a/gui/index.html b/gui/index.html
new file mode 100644
index 00000000..a77b74e7
--- /dev/null
+++ b/gui/index.html
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+ IntaRNA · RNA–RNA interactions
+
+
+
+
+
+ Skip to sequence inputs
+
+
+
+
RNA–RNA interactions considering Seeds and Accessibility
+ Computations stay on your device
+
+ Enable JavaScript to run IntaRNA in your browser.
+
+
+ Results Enter your sequences or choose an example, then compute.
+ Completed runs stay in this tab until deleted or the page is reloaded. Select a session to restore its inputs; download its ZIP to keep a copy.
+
+ Computation diagnostics
+
+
+
+
+
+
+
+
Show or hide table columns
+
+
No interactions match this filter. Clear the search to see all results.
+
mfe Δ is the energy difference from the best reported interaction for the same query–target pair; in evaluation, the reference is the best supplied structure. It is not a free-energy change. Coordinates are inclusive and use your start indices. Table visibility does not change the selected CSV download columns.
+
+
+ Select View on an interaction to visualize it.
+ Selecting View loads vaRRI with that sequence pair and structure in its URL.
+
+
+
+
+
Computation diagnostics
+
+
+
+
+
+
+
Computing interactions Loading the prediction engine…
The first run downloads the engine. You can stop at any time.
Abort computation
+
+
diff --git a/webinterface/intarna-worker.js b/gui/intarna-worker.js
similarity index 81%
rename from webinterface/intarna-worker.js
rename to gui/intarna-worker.js
index d54cb06c..09568516 100644
--- a/webinterface/intarna-worker.js
+++ b/gui/intarna-worker.js
@@ -1,4 +1,4 @@
-import { prepareRequest, makeArguments } from './protocol.js';
+import { prepareRequest, makeParameterDocument } from './protocol.js';
// Each task owns a worker; each CLI invocation owns a fresh WASM instance.
let started = false;
@@ -20,14 +20,10 @@ self.onmessage = async ({ data }) => {
locateFile: () => new URL('./wasm/intarna.wasm', import.meta.url).href,
print: log, printErr: log,
});
- module.FS.writeFile('/query.fa', request.query.fasta);
- module.FS.writeFile('/target.fa', request.target.fasta);
+ module.FS.writeFile('/parameter.json', JSON.stringify(makeParameterDocument(request, format)));
module.FS.mkdir('/output');
- for (const [key, value] of Object.entries(request.parameters)) {
- if (/^(q|t)(Shape|AccFile)$/.test(key) && value) module.FS.writeFile(`/${key}.txt`, value + '\n');
- }
self.postMessage({ type: 'status', message: request.task === 'evaluation' ? 'Evaluating supplied RNA interactions…' : 'Predicting RNA interactions…' });
- const code = module.callMain(makeArguments(request, format));
+ const code = module.callMain(['--parameterFile=/parameter.json']);
if (code !== 0) throw new Error(`IntaRNA exited with status ${code}. Check the diagnostics and input settings.`);
return module.FS;
};
diff --git a/webinterface/mark.svg b/gui/mark.svg
similarity index 100%
rename from webinterface/mark.svg
rename to gui/mark.svg
diff --git a/gui/options.js b/gui/options.js
new file mode 100644
index 00000000..4e95bccf
--- /dev/null
+++ b/gui/options.js
@@ -0,0 +1,109 @@
+import { CSV_CATALOG } from './columns.js';
+
+// Browser controls mirror CommandLineParsing.cpp; process/file-management options
+// are replaced by local text inputs, downloads and a single cancellable worker.
+const number = (key, label, value, min, max, extra = {}) => ({ key, label, value, min, max, type: 'number', ...extra });
+const select = (key, label, value, choices, extra = {}) => ({ key, label, value, choices, type: 'select', ...extra });
+const flag = (key, label, extra = {}) => ({ key, label, value: false, type: 'checkbox', ...extra });
+const text = (key, label, extra = {}) => ({ key, label, value: '', type: 'text', ...extra });
+const prediction = { prediction: true };
+const strand = (prefix, label) => [
+ text(`${prefix}Set`, 'FASTA record subset', { ...prediction, hint: 'Record numbers, e.g. 1-3,5-5. Empty selects all.' }),
+ select(`${prefix}Acc`, 'Accessibility', 'C', { C: 'Compute', N: 'Ignore', P: 'Import unpaired probabilities', E: 'Import opening energies' }),
+ number(`${prefix}AccW`, 'Accessibility window', 150, 0, 99999, { exclude: [1, 2], hint: '0 uses the full sequence; otherwise at least 3 nt.' }),
+ number(`${prefix}AccL`, 'Maximum base-pair span', 100, 0, 99999, { exclude: [1, 2], hint: '0 uses the accessibility window.' }),
+ text(`${prefix}AccConstr`, 'Accessibility constraints', { hint: 'One sequence only: ./x/p/b per base, or ranges such as b:3-4,p:6-7.' }),
+ text(`${prefix}AccFile`, 'Accessibility data', { type: 'textarea', hint: 'Paste RNAplfold text for one sequence when importing P or E.' }),
+ number(`${prefix}IntLenMax`, 'Maximum interaction length', 0, 0, 99999, { ...prediction, hint: '0 uses the full sequence, bounded by the accessibility window.' }),
+ number(`${prefix}IntLoopMax`, 'Maximum unpaired bases per interior loop', prefix === 'q' ? 16 : 10, 0, 30, prediction),
+ text(`${prefix}Region`, 'Interaction regions', { ...prediction, hint: 'Inclusive sequence coordinates, e.g. 1-20,30-50.' }),
+ number(`${prefix}RegionLenMax`, 'Automatic region length limit', 0, 0, 99999, { ...prediction, hint: '0 disables automatic region detection.' }),
+ number(`${prefix}PfScale`, `${label} partition-function scale`, 1.07, 1, 99999, { step: 'any' }),
+];
+const fields = new Map([
+ ...strand('q', 'Query'), ...strand('t', 'Target'),
+ flag('accNoLP', 'Exclude lonely pairs in accessibility'),
+ flag('accNoGUend', 'Exclude GU helix ends in accessibility'),
+ flag('noSeed', 'Do not require a seed', prediction),
+ text('seedTQ', 'Explicit seeds', { ...prediction, hint: 'Target&query dot-bar encodings, e.g. 1|||&1|||; separate seeds with commas.' }),
+ number('seedBP', 'Seed base pairs', 7, 2, 20, prediction),
+ number('seedMaxUP', 'Maximum unpaired seed bases (both strands)', 0, 0, 20, prediction),
+ number('seedQMaxUP', 'Maximum unpaired query seed bases', -1, -1, 20, { ...prediction, hint: '-1 uses the combined limit.' }),
+ number('seedTMaxUP', 'Maximum unpaired target seed bases', -1, -1, 20, prediction),
+ number('seedMaxE', 'Maximum seed energy (kcal/mol)', 0, -999, 999, { ...prediction, step: 'any' }),
+ number('seedMaxEhybrid', 'Maximum seed hybridization energy (kcal/mol)', 999, -999, 999, { ...prediction, step: 'any' }),
+ number('seedMinPu', 'Minimum seed unpaired probability', 0, 0, 1, { ...prediction, step: 'any' }),
+ text('seedQRange', 'Query seed regions', prediction), text('seedTRange', 'Target seed regions', prediction),
+ flag('seedNoGU', 'Exclude GU pairs in seeds', prediction), flag('seedNoGUend', 'Exclude GU pairs at seed ends', prediction),
+ number('helixMinBP', 'Minimum helix base pairs', 2, 2, 4, prediction),
+ number('helixMaxBP', 'Maximum helix base pairs', 10, 2, 20, prediction),
+ number('helixMaxIL', 'Maximum helix interior-loop size', 0, 0, 2, prediction),
+ number('helixMinPu', 'Minimum helix unpaired probability', 0, 0, 1, { ...prediction, step: 'any' }),
+ number('helixMaxE', 'Maximum helix energy (kcal/mol)', 0, -999, 999, { ...prediction, step: 'any' }),
+ flag('helixFullE', 'Use full helix energy for the energy limit', prediction),
+ select('model', 'Interaction model', 'X', { X: 'Seed extension (MFE)', S: 'Single site (MFE)', P: 'Ensemble energy', B: 'Helix blocks (MFE)' }, prediction),
+ select('mode', 'Prediction algorithm', 'H', { H: 'Heuristic', M: 'Exact', S: 'Seed only', K: 'Greedy seed extension' }, prediction),
+ select('kineticScore', 'Greedy extension score', 'A', { A: 'Energy change', B: 'Change / (1 + s1 + s2)', C: 'Change / (1 + 2 max(s1,s2))' }, prediction),
+ select('energyVRNA', 'ViennaRNA parameter set', 'Turner04', { Turner04: 'Turner 2004', Turner99: 'Turner 1999', Andronescu07: 'Andronescu 2007' }),
+ number('temperature', 'Temperature (°C)', 37, 0, 100, { step: 'any' }),
+ number('energyAdd', 'Additional energy (kcal/mol)', 0, -999, 999, { step: 'any' }),
+ flag('energyNoDangles', 'Exclude dangling-end energy'),
+ number('windowWidth', 'Prediction window width', 0, 0, 99999, { ...prediction, hint: '0 disables prediction windows; otherwise at least 10 nt.' }),
+ number('windowOverlap', 'Prediction window overlap', 150, 0, 99999, prediction),
+ number('outNumber', 'Maximum interactions per sequence pair', 1, 0, 100, { ...prediction, hint: 'Browser limit: 100; 0 computes accessibility only.' }),
+ select('outOverlap', 'Allow overlapping interactions', 'B', { B: 'Both strands', N: 'Neither strand', T: 'Target only', Q: 'Query only' }, prediction),
+ number('outMaxE', 'Maximum interaction energy (exclusive, kcal/mol)', 0, -999, 999, { ...prediction, step: 'any' }),
+ number('outDeltaE', 'Maximum difference from MFE (kcal/mol)', 100, 0, 100, { ...prediction, step: 'any' }),
+ number('outMinPu', 'Minimum unpaired probability', 0, 0, 1, { ...prediction, step: 'any' }),
+ flag('outBestSeedOnly', 'Report only the best seed', prediction),
+ flag('outNoLP', 'Exclude lonely interaction pairs', prediction), flag('outNoGUend', 'Exclude GU interaction helix ends', prediction),
+ flag('outPerRegion', 'Report each region combination separately', prediction), flag('outPairwise', 'Match FASTA records pairwise', prediction),
+ select('outCsvSort', 'CSV download sort column', '', { '': 'Native computation order', ...Object.fromEntries(CSV_CATALOG.map((column) => [column.key, column.label])) }),
+ text('outSpotProbSpots', 'Interaction probability spots', { ...prediction, hint: 'Optional target&query coordinate pairs, e.g. 3&7,5&9. Empty selects all spots.' }),
+ ...['q', 't'].flatMap((p) => [
+ text(`${p}Shape`, `${p === 'q' ? 'Query' : 'Target'} SHAPE data`, { type: 'textarea', hint: 'One sequence only. Paste position and reactivity, one pair per line; positions start at 1.' }),
+ { ...text(`${p}ShapeMethod`, 'SHAPE method'), value: 'D', hint: 'D, Dm1.8b-0.6, Z, Zb0.89 or W.' },
+ { ...text(`${p}ShapeConversion`, 'SHAPE conversion'), value: 'Os1.6i-2.29', hint: 'M, C0.25, S, Ls0.68i0.2 or Os1.6i-2.29.' },
+ ]),
+].map((field) => [field.key, field]));
+
+// Explicit rows separate visual order from native parameter defaults.
+const rows = (keys) => keys.map((row) => row.map((key) => fields.get(key)));
+const strandRows = (p) => rows([
+ [`${p}Set`, `${p}Region`, `${p}IntLenMax`, `${p}IntLoopMax`, `${p}RegionLenMax`],
+ [`${p}Acc`, `${p}AccW`, `${p}AccL`, `${p}PfScale`],
+]);
+const strandGroup = (p, label) => ({
+ key: `${label.toLowerCase()}-options`, label, strand: p,
+ rows: strandRows(p),
+ fields: [...strandRows(p).flat(), ...rows([
+ ...(p === 'q' ? [['accNoLP', 'accNoGUend']] : []),
+ [`${p}AccConstr`, `${p}AccFile`, `${p}Shape`, `${p}ShapeMethod`, `${p}ShapeConversion`],
+ ]).flat()],
+});
+export const GROUPS = [
+ { key: 'seed-options', label: 'Seed', prediction: true, rows: rows([
+ ['noSeed'], ['seedTQ'], ['seedBP', 'seedMaxUP', 'seedQMaxUP', 'seedTMaxUP'],
+ ['seedMaxE', 'seedMinPu', 'seedMaxEhybrid'], ['seedQRange', 'seedTRange'], ['seedNoGU', 'seedNoGUend'],
+ ]) },
+ { key: 'output-options', label: 'Output', fields: [...fields.values()].filter((field) => field.key.startsWith('out')) },
+ { key: 'interaction-options', label: 'Model & Energy', rows: rows([
+ ['model', 'mode'], ['energyVRNA', 'temperature', 'energyAdd'], ['energyNoDangles'],
+ ['windowWidth', 'windowOverlap'], ['kineticScore'],
+ ['helixMinBP', 'helixMaxBP', 'helixMaxIL', 'helixMinPu', 'helixMaxE'], ['helixFullE'],
+ ]) },
+ strandGroup('q', 'Query'), strandGroup('t', 'Target'),
+].map((group) => ({ ...group, fields: group.fields ?? group.rows.flat() }));
+export const FIELDS = GROUPS.flatMap((group) => group.fields);
+export const PARAMETER_DEFAULTS = Object.freeze(Object.fromEntries(FIELDS.map((field) => [field.key, field.value])));
+export const UI_DEFAULTS = Object.freeze({ ...PARAMETER_DEFAULTS, outNumber: 10 });
+export const EXTRA_OUTPUTS = [
+ ['qMinE', 'Query minimum energies', true], ['tMinE', 'Target minimum energies', true],
+ ['qAcc', 'Query opening energies'], ['tAcc', 'Target opening energies'],
+ ['qPu', 'Query unpaired probabilities'], ['tPu', 'Target unpaired probabilities'],
+ ['qSpotProb', 'Query interaction probabilities', true], ['tSpotProb', 'Target interaction probabilities', true],
+ ['pMinE', 'Pair minimum energies', true], ['spotProb', 'Interaction spot probabilities', true],
+ ['bpProb', 'Actual base-pair probabilities (model P)', true], ['bpsvg', 'Base-pair probability SVG (model P)', true],
+ ['normal', 'ASCII interaction structures'], ['detailed', 'Detailed interaction report'], ['ensemble', 'Ensemble report'],
+].map(([key, label, predictionOnly = false]) => ({ key, label, prediction: predictionOnly }));
+export const ENSEMBLE_COLUMNS = ['Eall', 'Zall', 'EallTotal', 'P_E'];
diff --git a/webinterface/package.json b/gui/package.json
similarity index 78%
rename from webinterface/package.json
rename to gui/package.json
index 81e5b5f8..307fd5df 100644
--- a/webinterface/package.json
+++ b/gui/package.json
@@ -1,5 +1,5 @@
{
- "name": "intarna-webinterface",
+ "name": "intarna-gui",
"private": true,
"type": "module",
"scripts": { "test": "node --test --test-isolation=none tests/*.test.mjs" }
diff --git a/gui/parameter-file.js b/gui/parameter-file.js
new file mode 100644
index 00000000..7e52f341
--- /dev/null
+++ b/gui/parameter-file.js
@@ -0,0 +1,116 @@
+import { JSON_SCHEMA, CSV_COLUMNS, LIMITS, normalizeParameters } from './protocol.js';
+import { FIELDS, PARAMETER_DEFAULTS, EXTRA_OUTPUTS } from './options.js';
+import { CSV_CATALOG } from './columns.js';
+
+export const MAX_PARAMETER_BYTES = 5000000;
+const reports = { normal: 'N', detailed: 'D', ensemble: 'E' };
+const embeddedKeys = ['qShape', 'tShape', 'qAccFile', 'tAccFile'];
+const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
+function text(value, label, limit = 1000000) {
+ if (typeof value !== 'string' || value.length > limit || value.includes('\0')) throw new Error(`${label}: invalid or oversized text.`);
+ return value;
+}
+function selections(request) {
+ const columns = request.csvColumns ?? CSV_COLUMNS.split(','), outputs = request.outputs ?? [];
+ for (const [values, catalog, label] of [[columns, CSV_CATALOG, 'CSV columns'], [outputs, EXTRA_OUTPUTS.filter((entry) => request.task !== 'evaluation' || !entry.prediction), 'output files']]) {
+ if (!Array.isArray(values) || new Set(values).size !== values.length || values.some((key) => !catalog.some((entry) => entry.key === key))) throw new Error(`Choose available ${label}, without duplicates.`);
+ }
+ return { columns, outputs };
+}
+
+// Save editable settings, even before sequences have been entered. Computation
+// performs full sequence-dependent validation later through prepareRequest.
+export function exportParameters(request, provenance) {
+ if (!provenance?.trim()) throw new Error('Compiled version information is unavailable');
+ const normalized = normalizeParameters(request);
+ const { columns, outputs } = selections(request);
+ const parameters = {
+ query: { text: text(typeof request.query === 'string' ? request.query : request.query.fasta, 'Query', LIMITS.maxFastaBytes) },
+ target: { text: text(typeof request.target === 'string' ? request.target : request.target.fasta, 'Target', LIMITS.maxFastaBytes) },
+ };
+ if (request.task === 'evaluation') parameters.personality = 'IntaRNAeval';
+ for (const [key, value] of Object.entries(normalized)) {
+ if (key === 'outSpotProbSpots' || value === '' || value === (PARAMETER_DEFAULTS[key] ?? 1)) continue;
+ parameters[key] = embeddedKeys.includes(key) ? { text: value } : value;
+ }
+ if (request.task === 'evaluation' && request.rri) parameters.rri = text(request.rri, 'Interactions', 10000);
+ parameters.outMode = 'C';
+ if (columns.join(',') !== CSV_COLUMNS) parameters.outCsvCols = columns.join(',');
+ const nativeOutputs = outputs.filter((key) => !Object.hasOwn(reports, key)).map((key) => {
+ const spots = key === 'spotProb' && normalized.outSpotProbSpots ? `${normalized.outSpotProbSpots}:` : '';
+ return `${key}:${spots}${key}${key === 'bpsvg' ? '.svg' : '.csv'}`;
+ });
+ if (nativeOutputs.length) parameters.out = nativeOutputs;
+ const selectedReports = outputs.filter((key) => Object.hasOwn(reports, key));
+ const comments = [
+ ...provenance.trimEnd().split(/\r?\n/),
+ 'Reproduce: IntaRNA --parameterFile=parameter.json --out=intarna-interactions.csv',
+ ...selectedReports.map((key) => `Additional report: IntaRNA --parameterFile=parameter.json --outMode=${reports[key]} --outCsvCols=${CSV_COLUMNS} --outCsvSort '' --out=intarna-${key}.txt`),
+ ];
+ const gui = {};
+ if (selectedReports.length) gui.reports = selectedReports;
+ if (normalized.outSpotProbSpots) gui.outSpotProbSpots = normalized.outSpotProbSpots;
+ return { schema: JSON_SCHEMA, _comment: comments, parameters, ...(Object.keys(gui).length ? { gui } : {}) };
+}
+
+export function serializeParameters(request, provenance) {
+ return JSON.stringify(exportParameters(request, provenance), null, 2) + '\n';
+}
+
+// Import only controls the browser can represent. Native-only settings produce
+// an actionable error instead of being silently discarded or reaching WASM.
+export function importParameters(source) {
+ if (typeof source === 'string' && source.length > MAX_PARAMETER_BYTES) throw new Error('Choose a JSON file of at most 5 MB.');
+ const document = typeof source === 'string' ? JSON.parse(source.replace(/^\uFEFF/, '')) : source;
+ if (!object(document) || document.schema !== JSON_SCHEMA || !object(document.parameters)) throw new Error(`Expected a ${JSON_SCHEMA} parameter document.`);
+ for (const key of Object.keys(document)) if (!['schema', '_comment', 'parameters', 'gui'].includes(key)) throw new Error(`Unknown JSON document field: ${key}`);
+ const request = { task: 'prediction', query: '', target: '', qIdxPos0: 1, tIdxPos0: 1, rri: '', parameters: {}, outputs: [], csvColumns: CSV_COLUMNS.split(',') };
+ const known = new Set(FIELDS.map((field) => field.key));
+ for (const [key, value] of Object.entries(document.parameters)) {
+ if (value === null) throw new Error(`${key}: null is not a parameter value.`);
+ if (['query', 'target', ...embeddedKeys].includes(key)) {
+ const content = object(value) && Object.keys(value).length === 1 && typeof value.text === 'string' ? value.text : value;
+ // Plain nucleotide strings are native inline sequences; file paths need
+ // explicit embedding because the browser cannot open local paths.
+ if (typeof content !== 'string' || (content === value && (embeddedKeys.includes(key) || !/^[ACGUTNacgutn]*$/.test(value)))) throw new Error(`${key}: embed the input using {"text":"file contents"}.`);
+ if (key === 'query' || key === 'target') request[key] = text(content, key, LIMITS.maxFastaBytes);
+ else request.parameters[key] = text(content, key);
+ } else if (key === 'personality') {
+ if (!['IntaRNA', 'IntaRNAeval'].includes(value)) throw new Error('The browser supports IntaRNA prediction and IntaRNAeval evaluation.');
+ request.task = value === 'IntaRNAeval' ? 'evaluation' : 'prediction';
+ } else if (key === 'qIdxPos0' || key === 'tIdxPos0') request[key] = value;
+ else if (key === 'rri') request.rri = text(value, key, 10000);
+ else if (key === 'outMode') { if (value !== 'C') throw new Error('Use CSV outMode C and gui.reports for additional browser reports.'); }
+ else if (key === 'outCsvCols') request.csvColumns = text(value, key).split(',').filter(Boolean);
+ else if (key === 'out') {
+ const entries = Array.isArray(value) ? value : [value];
+ for (const entry of entries) {
+ const match = /^([A-Za-z]+):(?:(-?[\d&,\-]+):)?([^:]+)$/.exec(text(entry, 'out'));
+ if (!match || !EXTRA_OUTPUTS.some((output) => output.key === match[1] && !Object.hasOwn(reports, output.key))) throw new Error('The browser supports named additional outputs; omit main output paths from the parameter file.');
+ request.outputs.push(match[1]);
+ if (match[2]) {
+ if (match[1] !== 'spotProb') throw new Error('Only spotProb accepts explicit probability spots.');
+ request.parameters.outSpotProbSpots = match[2];
+ }
+ }
+ } else if (known.has(key)) request.parameters[key] = value;
+ else throw new Error(`The browser cannot represent parameter ${key}; use the native CLI.`);
+ }
+ if (document.gui !== undefined) {
+ if (!object(document.gui)) throw new Error('gui must be an object.');
+ for (const [key, value] of Object.entries(document.gui)) {
+ if (key === 'reports' && Array.isArray(value) && value.every((entry) => Object.hasOwn(reports, entry))) request.outputs.push(...value);
+ else if (key === 'outSpotProbSpots') {
+ if (request.parameters.outSpotProbSpots && request.parameters.outSpotProbSpots !== value) throw new Error('Conflicting spotProb selections.');
+ request.parameters.outSpotProbSpots = text(value, key, 10000);
+ } else throw new Error(`Unsupported GUI setting: ${key}`);
+ }
+ }
+ selections(request);
+ // Keep prediction-only controls from being silently ignored on import.
+ if (request.task === 'evaluation' && FIELDS.some((field) => field.prediction && Object.hasOwn(request.parameters, field.key))) throw new Error('Evaluation documents must omit prediction-only parameters.');
+ const normalized = normalizeParameters(request);
+ request.qIdxPos0 = normalized.qIdxPos0; request.tIdxPos0 = normalized.tIdxPos0;
+ request.parameters = normalized;
+ return request;
+}
diff --git a/webinterface/protocol.js b/gui/protocol.js
similarity index 87%
rename from webinterface/protocol.js
rename to gui/protocol.js
index d70c0a40..d452de28 100644
--- a/webinterface/protocol.js
+++ b/gui/protocol.js
@@ -1,41 +1,41 @@
import { FIELDS, PARAMETER_DEFAULTS, EXTRA_OUTPUTS, ENSEMBLE_COLUMNS } from './options.js';
import { CSV_CATALOG } from './columns.js';
-// Bound total work as well as per-strand length, including all-vs-all FASTA jobs.
-export const LIMITS = Object.freeze({ minLength: 2, maxLength: 2000, maxRecords: 20, maxPairs: 1000000 });
+// Bound the largest pair's matrix while allowing batches of shorter sequences.
+export const LIMITS = Object.freeze({ minLength: 2, maxLength: 5000, maxRecords: 50, maxPairs: 1000000, maxFastaBytes: 1000000 });
export const CSV_COLUMNS = 'id1,start1,end1,id2,start2,end2,subseqDP,hybridDP,E';
export const CONTEXT_COLUMNS = ['id1', 'start1', 'end1', 'id2', 'start2', 'end2', 'E'];
export const DEFAULTS = Object.freeze({ query: '>query\nGGGAGGAGGG', target: '>target\nCCCUCCUCCC', task: 'prediction' });
export const EVALUATION_EXAMPLE = '1||||||||||&1||||||||||';
export function parseFasta(text, label, multiple = true) {
- if (typeof text !== 'string' || text.length > 60000 || !text.trim()) throw new Error(`${label}: enter a sequence or FASTA records.`);
+ if (typeof text !== 'string' || text.length > LIMITS.maxFastaBytes || !text.trim()) throw new Error(`${label}: enter a sequence or FASTA records (at most 1 MB).`);
const lines = text.trim().split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
- const records = [];
+ const records = [], warnings = [];
let current;
for (const line of lines) {
if (line.startsWith('>')) {
- current = { id: line.slice(1).trim(), sequence: '' };
+ const header = line.slice(1).trim();
+ current = { id: header.split(/\s+/)[0], sequence: '' };
if (!current.id || current.id.length > 200 || /[\x00-\x1f\x7f]/.test(current.id)) throw new Error(`${label}: the FASTA header needs a name (at most 200 characters).`);
- // The native FASTA parser takes the identifier before the first space.
- current.id = current.id.split(/\s+/)[0];
+ if (/\s+\S/.test(header)) warnings.push(`${label}: ignoring FASTA description after the name “${current.id}”.`);
records.push(current);
} else {
if (!current) { current = { id: label.toLowerCase(), sequence: '' }; records.push(current); }
current.sequence += line.replace(/\s/g, '').toUpperCase().replaceAll('T', 'U');
}
}
- if ((!multiple && records.length !== 1) || records.length > LIMITS.maxRecords) throw new Error(`${label}: ${multiple ? 'use at most 20 FASTA records' : 'evaluation requires exactly one FASTA record'}.`);
+ if ((!multiple && records.length !== 1) || records.length > LIMITS.maxRecords) throw new Error(`${label}: ${multiple ? 'use at most 50 FASTA records' : 'evaluation requires exactly one FASTA record'}.`);
const ids = new Set();
for (const record of records) {
if (!record.sequence || /[^ACGUN]/.test(record.sequence)) throw new Error(`${label}: use only A, C, G, U, T or N in every sequence.`);
- if (record.sequence.length < LIMITS.minLength || record.sequence.length > LIMITS.maxLength) throw new Error(`${label}: use 2–2,000 nucleotides per record.`);
+ if (record.sequence.length < LIMITS.minLength || record.sequence.length > LIMITS.maxLength) throw new Error(`${label}: use 2–5,000 nucleotides per record.`);
// Native CSV replaces semicolons with underscores. Keep pair grouping unique.
const id = record.id.replaceAll(';', '_');
if (ids.has(id)) throw new Error(`${label}: FASTA identifiers must be unique (also after replacing semicolons with underscores).`);
ids.add(id);
}
- return { records, fasta: records.map(({ id, sequence }) => `>${id}\n${sequence}\n`).join('') };
+ return { records, warnings, fasta: records.map(({ id, sequence }) => `>${id}\n${sequence}\n`).join('') };
}
export function parseSequence(text, label) {
const parsed = parseFasta(text, label, false);
@@ -109,11 +109,10 @@ export function validateInteractions(encoding, query, target, qStart, tStart, se
}
}
-export function prepareRequest(request) {
- const task = request?.task ?? 'prediction';
- if (!['prediction', 'evaluation'].includes(task)) throw new Error('Choose prediction or evaluation.');
- const evaluating = task === 'evaluation';
- const query = parseFasta(request.query, 'Query', !evaluating), target = parseFasta(request.target, 'Target', !evaluating);
+// Validate scalar controls independently so unfinished forms can be saved/loaded.
+export function normalizeParameters(request) {
+ if (!['prediction', 'evaluation'].includes(request.task ?? 'prediction')) throw new Error('Choose prediction or evaluation.');
+ const evaluating = request.task === 'evaluation';
const parameters = {};
const supplied = request.parameters ?? {};
for (const field of FIELDS) {
@@ -138,10 +137,19 @@ export function prepareRequest(request) {
if (!Number.isInteger(n) || n < -2000000000 || n > 2000000000) throw new Error(`${key}: use an integer between -2,000,000,000 and 2,000,000,000.`);
parameters[key] = n;
}
+ return parameters;
+}
+
+export function prepareRequest(request) {
+ const task = request?.task ?? 'prediction';
+ if (!['prediction', 'evaluation'].includes(task)) throw new Error('Choose prediction or evaluation.');
+ const evaluating = task === 'evaluation';
+ const query = parseFasta(request.query, 'Query', !evaluating), target = parseFasta(request.target, 'Target', !evaluating);
+ const parameters = normalizeParameters(request);
const qRecords = selectedRecords(query, parameters.qSet, 'qSet'), tRecords = selectedRecords(target, parameters.tSet, 'tSet');
if (parameters.outPairwise && qRecords.length !== tRecords.length) throw new Error('Pairwise output requires the same number of selected query and target records.');
- const work = parameters.outPairwise ? qRecords.reduce((sum, q, i) => sum + q.sequence.length * tRecords[i].sequence.length, 0) : qRecords.reduce((sum, q) => sum + q.sequence.length, 0) * tRecords.reduce((sum, t) => sum + t.sequence.length, 0);
- if (work > LIMITS.maxPairs) throw new Error('The total query × target length across selected sequence pairs must be at most 1,000,000.');
+ const work = Math.max(...qRecords.map((q) => q.sequence.length)) * Math.max(...tRecords.map((t) => t.sequence.length));
+ if (work > LIMITS.maxPairs) throw new Error('The longest selected query × longest selected target must be at most 1,000,000.');
// Validate sequence-dependent settings before loading the engine.
for (const [p, records] of [['q', qRecords], ['t', tRecords]]) {
@@ -213,7 +221,7 @@ export function makeArguments(request, format = 'C') {
const args = ['--query=/query.fa', '--target=/target.fa', `--outMode=${format}`, `--out=${format === 'C' ? '/results.csv' : '/report.txt'}`];
if (request.task === 'evaluation') args.push('--personality=IntaRNAeval');
for (const [key, value] of Object.entries(request.parameters)) {
- if (key === 'outSpotProbSpots' || key === 'kineticScore' && request.parameters.mode !== 'K') continue;
+ if (key === 'outCsvSort' && format !== 'C' || key === 'outSpotProbSpots' || key === 'kineticScore' && request.parameters.mode !== 'K') continue;
// Omit defaults: the native parser distinguishes explicit values in several
// cross-option checks, especially accessibility/region constraints.
if (value === '' || value === PARAMETER_DEFAULTS[key]) continue;
@@ -230,3 +238,21 @@ export function makeArguments(request, format = 'C') {
}
return args;
}
+
+export const JSON_SCHEMA = 'IntaRNA-parameters-v1';
+
+// One parameter document crosses the JS/native boundary. File contents use the
+// native JSON embedding contract, so no browser-specific input paths are needed.
+export function makeParameterDocument(request, format = 'C') {
+ const parameters = {};
+ for (const argument of makeArguments(request, format)) {
+ const separator = argument.indexOf('='), key = argument.slice(2, separator);
+ let value = argument.slice(separator + 1);
+ if (key === 'query' || key === 'target') value = { text: request[key].fasta };
+ else if (/^[qt](Shape|AccFile)$/.test(key)) value = { text: request.parameters[key] + '\n' };
+ else if (Object.hasOwn(request.parameters, key)) value = request.parameters[key];
+ if (key === 'out') (parameters.out ??= []).push(value);
+ else if (!(['qIdxPos0', 'tIdxPos0'].includes(key) && value === 1)) parameters[key] = value;
+ }
+ return { schema: JSON_SCHEMA, parameters };
+}
diff --git a/webinterface/results.js b/gui/results.js
similarity index 89%
rename from webinterface/results.js
rename to gui/results.js
index aeceb38a..92a06f70 100644
--- a/webinterface/results.js
+++ b/gui/results.js
@@ -66,15 +66,15 @@ export class ResultsView {
constructor(root) {
this.root = root; this.rows = []; this.key = 'E'; this.direction = 1;
this.visualization = new InteractionVisualization(root);
- this.search = root.querySelector('#search');
+ this.search = root.querySelector('[data-role=search]');
this.table = root.querySelector('table');
this.body = this.table.querySelector('tbody');
this.header = this.table.querySelector('thead tr');
- this.count = root.querySelector('#visible-count');
- this.empty = root.querySelector('#no-matches');
- this.picker = root.querySelector('#visible-columns');
+ this.count = root.querySelector('[data-role=visible-count]');
+ this.empty = root.querySelector('[data-role=no-matches]');
+ this.picker = root.querySelector('[data-role=visible-columns]');
this.search.addEventListener('input', () => this.render());
- root.querySelector('#download').addEventListener('click', () => downloadText('intarna-interactions.csv', this.csv));
+ root.querySelector('[data-role=download]').addEventListener('click', () => downloadText('intarna-interactions.csv', this.csv));
}
clear() {
this.rows = []; this.csv = ''; this.body.replaceChildren(); this.root.hidden = true;
@@ -103,11 +103,15 @@ export class ResultsView {
const rows = selectRows(this.rows, this.search.value, this.key, this.direction);
const columns = this.columns.filter((column) => this.visible.has(column.key));
this.header.replaceChildren();
- const viewHeading = document.createElement('th'); viewHeading.scope = 'col'; viewHeading.textContent = 'vaRRI';
+ const viewHeading = document.createElement('th'); viewHeading.scope = 'col'; viewHeading.setAttribute('aria-label', 'Interaction visualization');
this.header.append(viewHeading);
for (const column of columns) {
const cell = document.createElement('th'), button = document.createElement('button');
- cell.scope = 'col'; button.type = 'button'; button.textContent = column.label;
+ cell.scope = 'col'; button.type = 'button';
+ const split = column.numeric ? column.label.indexOf(' ') : -1;
+ if (split > 0) button.append(document.createTextNode(column.label.slice(0, split)), document.createElement('br'), document.createTextNode(column.label.slice(split + 1)));
+ else button.textContent = column.label;
+ button.title = column.description ?? column.label;
const active = column.key === this.key, direction = this.direction === 1 ? 'ascending' : 'descending';
cell.setAttribute('aria-sort', active ? direction : 'none');
button.setAttribute('aria-label', `Sort by ${column.label}${active ? `, currently ${direction}` : ''}`);
diff --git a/webinterface/runner.js b/gui/runner.js
similarity index 100%
rename from webinterface/runner.js
rename to gui/runner.js
diff --git a/gui/sessions.js b/gui/sessions.js
new file mode 100644
index 00000000..71b712d0
--- /dev/null
+++ b/gui/sessions.js
@@ -0,0 +1,124 @@
+import { ResultsView, downloadBlob } from './results.js';
+import { serializeParameters, importParameters } from './parameter-file.js';
+import { zipFiles } from './archive.js';
+
+// Sessions live only in this page. Each owns a JSON snapshot and its own view;
+// editing the form or exploring another session cannot change an earlier run.
+export class SessionsView {
+ constructor(root, template, restore, reportError, announce) {
+ this.root = root; this.template = template; this.restore = restore; this.reportError = reportError; this.announce = announce;
+ this.entries = new Map(); this.nextNumber = 1; this.selected = null;
+ }
+
+ deselect() {
+ this.selected = null;
+ for (const session of this.entries.values()) {
+ session.root.dataset.selected = 'false';
+ session.toggle.textContent = 'Show';
+ }
+ }
+
+ setExpanded(session, expanded) {
+ session.panel.hidden = !expanded;
+ session.toggle.setAttribute('aria-expanded', String(expanded));
+ session.toggle.textContent = expanded ? 'Hide' : 'Show';
+ // Unload third-party viewers while folded. The table selection is retained
+ // and can be visualized again without recomputing the prediction.
+ if (!expanded) session.view.visualization.clear();
+ else if (session.view.selected) session.view.visualization.show(session.view.selected, session.view.request);
+ }
+
+ select(session, restore = true) {
+ if (restore) this.restore(importParameters(session.parameterJson), session.name);
+ this.selected = session.id;
+ for (const entry of this.entries.values()) {
+ entry.root.dataset.selected = String(entry === session);
+ this.setExpanded(entry, entry === session);
+ }
+ }
+
+ add(request, result, provenance) {
+ // Prepared indices live in parameters, whereas the JSON serializer accepts
+ // editable requests with top-level indices. Preserve signed coordinates.
+ const parameterJson = serializeParameters({ ...request, qIdxPos0: request.parameters.qIdxPos0, tIdxPos0: request.parameters.tIdxPos0 }, provenance);
+ const root = this.template.content.firstElementChild.cloneNode(true);
+ const find = (role) => root.querySelector(`[data-role=${role}]`);
+ const id = `session-${this.nextNumber}`, task = request.task === 'evaluation' ? 'Evaluation' : 'Prediction';
+ const session = {
+ id, name: `${task} ${this.nextNumber}`, parameterJson, root,
+ panel: find('panel'), toggle: find('toggle'), view: new ResultsView(find('results-view')),
+ };
+ const count = session.view.show(result.csv, result.csvColumns, request);
+ const summary = count ? `${task} complete: ${count} interaction${count === 1 ? '' : 's'}.` : request.task === 'prediction' && request.parameters.outNumber === 0 ? 'Accessibility computation complete. No interaction rows requested.' : 'No favorable interactions found with these sequences and settings.';
+ find('summary').textContent = summary;
+ root.dataset.sessionId = id;
+ session.panel.id = `${id}-panel`; session.toggle.setAttribute('aria-controls', session.panel.id);
+ const name = find('name'); name.value = session.name;
+ const rename = () => {
+ session.name = name.value.trim() || `${task} ${id.slice('session-'.length)}`;
+ root.setAttribute('aria-label', session.name);
+ session.toggle.setAttribute('aria-label', `Show or hide results and restore inputs for ${session.name}`);
+ find('delete').setAttribute('aria-label', `Delete session ${session.name}`);
+ find('download-zip').setAttribute('aria-label', `Download ZIP for ${session.name}`);
+ };
+ name.addEventListener('input', rename);
+ name.addEventListener('change', () => { rename(); name.value = session.name; });
+ name.addEventListener('keydown', (event) => { if (event.key === 'Enter') name.blur(); });
+ rename();
+ // The name and header background also select a run. Action buttons retain
+ // their own behavior, so exporting/deleting cannot overwrite edited inputs.
+ const header = root.querySelector('.session-header');
+ header.tabIndex = 0; header.setAttribute('role', 'group');
+ header.setAttribute('aria-label', 'Select result and restore its inputs');
+ for (const event of ['click', 'focusin']) header.addEventListener(event, (event) => {
+ if (!event.target.closest('button') && (this.selected !== id || session.panel.hidden)) this.select(session);
+ });
+ header.addEventListener('keydown', (event) => {
+ if (event.target === header && ['Enter', ' '].includes(event.key)) { event.preventDefault(); this.select(session); }
+ });
+ // Clicking or keyboard-focusing an output area selects its computed inputs.
+ for (const event of ['click', 'focusin']) session.panel.addEventListener(event, () => {
+ if (this.selected !== id) this.select(session);
+ });
+ session.toggle.addEventListener('click', () => {
+ if (this.selected === id && !session.panel.hidden) this.setExpanded(session, false);
+ else this.select(session);
+ });
+
+ // Each archive uses the saved JSON and full outputs, never the current form,
+ // filtered table, or the session selected at download time.
+ find('download-zip').addEventListener('click', () => {
+ try {
+ const files = [{ name: 'parameter.json', text: parameterJson }, { name: 'intarna-interactions.csv', text: session.view.csv }, ...result.files];
+ const filename = session.name.replace(/[^\p{L}\p{N}._-]+/gu, '-').replace(/^[.-]+|[.-]+$/g, '').slice(0, 80) || id;
+ downloadBlob(`${filename}.zip`, new Blob([zipFiles(files)], { type: 'application/zip' }));
+ } catch (error) { this.reportError(new Error(`ZIP download failed: ${error.message}`)); }
+ });
+ for (const file of result.files) {
+ const details = document.createElement('details'), label = document.createElement('summary'); label.textContent = file.name;
+ const preview = document.createElement('pre'); preview.textContent = file.text.slice(0, 100000);
+ const download = document.createElement('button'); download.type = 'button'; download.className = 'secondary'; download.textContent = 'Download file';
+ download.addEventListener('click', () => downloadBlob(file.name, new Blob([file.text], { type: 'text/plain;charset=utf-8' })));
+ details.append(label, download, preview);
+ if (file.text.length > 100000) {
+ const note = document.createElement('p'); note.textContent = 'Preview limited to 100,000 characters. Download contains the complete file.'; details.append(note);
+ }
+ find('file-list').append(details);
+ }
+ find('output-files').hidden = !result.files.length;
+ find('diagnostic-text').textContent = result.diagnostics; find('diagnostics').hidden = !result.diagnostics;
+
+ find('delete').addEventListener('click', () => {
+ const entries = [...this.entries.values()], index = entries.indexOf(session);
+ const next = entries[index + 1] ?? entries[index - 1];
+ session.view.clear(); root.remove(); this.entries.delete(id);
+ // Deleting output does not silently overwrite an edited form.
+ if (this.selected === id) this.selected = null;
+ this.announce(`${session.name} deleted. ${this.entries.size} session${this.entries.size === 1 ? '' : 's'} remaining.`);
+ (next?.toggle ?? document.getElementById('compute')).focus();
+ });
+ this.entries.set(id, session); this.nextNumber++;
+ this.root.prepend(root); this.select(session, false);
+ return summary;
+ }
+}
diff --git a/webinterface/styles.css b/gui/styles.css
similarity index 64%
rename from webinterface/styles.css
rename to gui/styles.css
index b776078c..360ba4c4 100644
--- a/webinterface/styles.css
+++ b/gui/styles.css
@@ -4,6 +4,8 @@
--brand-blue:#145a96;
--brand-blue-dark:#0f4676;
--brand-blue-soft:#eaf2f8;
+ --table-header:#d9e8f6;
+ --table-selection:#c6e5f4;
--brand-accent:#f4bb44;
--surface:#fff;
--surface-muted:#f3f7fb;
@@ -221,7 +223,7 @@ fieldset {
min-width:0;
}
-.task-bar,.compute-bar,.table-tools {
+.compute-bar,.table-tools {
display:flex;
align-items:center;
flex-wrap:wrap;
@@ -229,16 +231,6 @@ fieldset {
gap:12px;
}
-.task-bar {
- margin-bottom:8px;
-}
-
-.task-bar label {
- display:flex;
- align-items:center;
- gap:12px;
-}
-
#task-help {
font-size:.9rem;
color:var(--muted);
@@ -292,8 +284,26 @@ fieldset {
grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
gap:20px 24px;
align-items:start;
+ margin-bottom:20px;
}
+/* Explicit rows preserve the requested grouping; subgrid aligns inputs below
+ labels of different heights without fixing a font-dependent label height. */
+.field-grid.columns-1 { grid-template-columns:minmax(0,1fr); }
+.field-grid.columns-2 { grid-template-columns:repeat(2,minmax(0,1fr)); }
+.field-grid.columns-3 { grid-template-columns:repeat(3,minmax(0,1fr)); }
+.field-grid.columns-4 { grid-template-columns:repeat(4,minmax(0,1fr)); }
+.field-grid.columns-5 { grid-template-columns:repeat(5,minmax(0,1fr)); }
+.field-grid > .field:not(.check) { display:grid; grid-row:span 4; grid-template-rows:subgrid; row-gap:5px; }
+.field-grid > .field:not(.check) > :nth-child(2) { align-self:start; }
+.accessibility-columns { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:24px; margin-top:20px; }
+.accessibility-columns > div { min-width:0; }
+.seed-mode { max-width:24rem; margin-bottom:20px; }
+.field-grid:has(#noSeed) { display:none; }
+#query,#target { white-space:pre; overflow:auto; }
+#sequence-help,#general-options p { max-width:none; }
+.input-warning { font-size:.85rem; color:#79510b; max-width:none; overflow-wrap:anywhere; }
+
.strand-heading,.sequence-meta {
display:flex;
align-items:center;
@@ -364,10 +374,6 @@ fieldset {
background:transparent;
}
-.field:has(textarea) {
- grid-column:1/-1;
-}
-
.field:has(:disabled) {
opacity:.65;
}
@@ -406,6 +412,11 @@ fieldset {
padding:.3rem .6rem;
}
+.csv-group { margin-top:16px; }
+.csv-group legend { font-weight:600; }
+.text-button { background:none; border:0; padding:.15rem .5rem; color:var(--brand-blue); font-size:.8rem; text-decoration:underline; font-weight:400; }
+.csv-columns { grid-template-columns:repeat(auto-fill,minmax(125px,1fr)); gap:6px 16px; padding:6px 0; }
+
.results {
background:var(--surface);
border:1px solid var(--border);
@@ -436,7 +447,7 @@ fieldset {
font-size:.9rem;
}
-#visible-count {
+[data-role=visible-count] {
font-size:.85rem;
color:var(--muted);
}
@@ -460,14 +471,15 @@ table {
}
th {
- background:var(--brand-blue-soft);
+ background:var(--table-header);
text-align:left;
+ padding:0 5px;
}
th button {
background:transparent;
border:0;
- padding:12px;
+ padding:10px 0;
color:var(--brand-blue-dark);
width:100%;
text-align:left;
@@ -483,7 +495,7 @@ th[aria-sort=descending] button::after {
td {
border-top:1px solid #e0e8f0;
- padding:10px 12px;
+ padding:10px 5px;
}
tr:nth-child(even) {
@@ -501,7 +513,7 @@ td.number {
}
.view-interaction {
- padding:.25rem .6rem;
+ padding:.25rem .35rem;
color:var(--brand-blue-dark);
background:var(--surface);
}
@@ -510,16 +522,17 @@ tbody tr {
cursor:pointer;
}
-tbody tr:hover,tbody tr.selected {
+tbody tr:hover {
background:var(--brand-blue-soft);
}
+tbody tr.selected { background:var(--table-selection); }
.view-interaction[aria-pressed=true] {
background:var(--brand-blue);
color:white;
}
-#visualization {
+[data-role=visualization] {
margin-top:20px;
}
@@ -530,7 +543,7 @@ tbody tr:hover,tbody tr.selected {
gap:16px;
}
-#varri-frame {
+[data-role=varri-frame] {
display:block;
width:100%;
height:600px;
@@ -540,17 +553,17 @@ tbody tr:hover,tbody tr.selected {
background:white;
}
-#visualization-status {
+[data-role=visualization-status] {
max-width:none;
overflow-wrap:anywhere;
font-size:.9rem;
}
-#output-files {
+[data-role=output-files] {
margin-top:24px;
}
-#file-list details,#diagnostics {
+[data-role=file-list] details,:is(#diagnostics, [data-role=diagnostics]) {
border-top:1px solid var(--border);
margin-top:12px;
padding-top:12px;
@@ -565,7 +578,7 @@ pre {
padding:12px;
}
-#diagnostics pre {
+:is(#diagnostics, [data-role=diagnostics]) pre {
white-space:pre-wrap;
overflow-wrap:anywhere;
}
@@ -684,6 +697,7 @@ dialog::backdrop {
.field-grid {
grid-template-columns:repeat(auto-fill,minmax(min(100%,220px),1fr));
}
+ .field-grid[class*=columns-],.accessibility-columns { grid-template-columns:1fr; }
.results {
padding:14px;
}
@@ -693,9 +707,6 @@ dialog::backdrop {
.table-tools input {
width:100%;
}
- .task-bar label {
- flex-wrap:wrap;
- }
h1 {
font-size:1.55rem;
}
@@ -709,3 +720,45 @@ dialog::backdrop {
animation:none;
}
}
+
+/* Examples are a compact disclosure list with descriptions, not another form. */
+.examples-menu { position:relative; }
+.examples-menu > summary { list-style:none; padding:.6rem 1rem; border:1px solid var(--border); border-radius:5px; background:var(--surface); color:var(--brand-blue-dark); font-weight:600; }
+.examples-menu > summary::after { content:' ▾'; }
+.examples-menu > div { position:absolute; bottom:calc(100% + .5rem); left:0; width:min(26rem,80vw); padding:.4rem; background:var(--surface); border:1px solid var(--border); border-radius:5px; box-shadow:0 6px 20px #1c2b3920; z-index:2; }
+.examples-menu button { width:100%; text-align:left; background:var(--surface); border:0; }
+.examples-menu button:hover { background:var(--brand-blue-soft); }
+.examples-menu button strong,.examples-menu button span { display:block; }
+.examples-menu button span { font-size:.85rem; font-weight:400; color:var(--muted); }
+textarea::placeholder { color:#687d8e; opacity:1; }
+
+.strand-heading { flex-wrap:wrap; gap:.4rem .7rem; }
+.upload-controls { display:flex; align-items:center; flex-wrap:wrap; gap:.4rem; margin-left:auto; }
+button.small-button { padding:.25rem .65rem; font-size:.85rem; }
+textarea.drag-over { outline:3px solid var(--brand-accent); background:var(--brand-blue-soft); }
+
+/* Native radio semantics keep the task overview visible and keyboard operable. */
+.task-selector { display:flex; flex-wrap:wrap; gap:.5rem; margin-bottom:.6rem; }
+.task-choice { position:relative; display:flex; align-items:baseline; gap:.6rem; padding:.55rem 1rem; border:1px solid var(--border); border-radius:5px; background:var(--surface); cursor:pointer; }
+.task-choice input { position:absolute; inset:0; opacity:0; width:100%; height:100%; margin:0; cursor:pointer; z-index:1; }
+.task-choice:has(:checked) { background:var(--brand-blue); color:white; border-color:var(--brand-blue); }
+.task-choice:has(:checked) small { color:white; }
+.task-choice:has(:focus-visible) { outline:3px solid var(--brand-accent); outline-offset:3px; }
+.compute-bar { justify-content:flex-start; gap:8px; }
+#compute { margin-right:24px; }
+.parameter-actions-label { font-size:.9rem; }
+@media(max-width:480px) { #compute { flex-basis:100%; margin-right:0; } .parameter-actions-label { flex-basis:100%; } }
+
+/* Every completed run owns its controls and output, even while folded. */
+.session { margin-top:18px; border:1px solid var(--border); border-radius:6px; min-width:0; }
+.session[data-selected=true] { border-color:var(--brand-blue); }
+.session-header { display:flex; align-items:center; flex-wrap:wrap; gap:10px; padding:14px; background:var(--surface-muted); border-radius:6px; }
+.session-name { display:flex; align-items:center; flex:1 1 20rem; gap:10px; font-size:.85rem; }
+.session-name input { flex:1; width:12rem; font-weight:600; }
+.session-header button { font-size:.85rem; }
+.session-header [data-role=toggle]::before { content:'▸ '; }
+.session-header [aria-expanded=true]::before { content:'▾ '; }
+.delete-session { display:inline-flex; align-items:center; }
+.session-panel { padding:16px; }
+.session-summary { color:var(--muted); }
+@media(max-width:480px) { .session-header,.session-panel { padding:10px; } }
diff --git a/webinterface/tests/export.test.mjs b/gui/tests/export.test.mjs
similarity index 74%
rename from webinterface/tests/export.test.mjs
rename to gui/tests/export.test.mjs
index 60d83edd..6884cf2c 100644
--- a/webinterface/tests/export.test.mjs
+++ b/gui/tests/export.test.mjs
@@ -24,15 +24,17 @@ test('archive preserves effective non-default settings, selected CSV, auxiliary
request.outputs = ['detailed'];
const files = archiveFiles(request, { csv: 'id1;start1;end1;id2;start2;end2;E;hybridDP\ntarget;1;10;query;3;12;-21.4;((((((((((&))))))))))\n', files: [{ name: 'intarna-detailed.txt', text: 'report' }] }, 'IntaRNA 3.4.1\nViennaRNA 2.7.0\n');
const entries = Object.fromEntries(files.map((file) => [file.name, file.text]));
- assert.equal(entries['query.fa'], '>query\nGGGAGGAGGG\n');
+ assert.deepEqual(Object.keys(entries), ['parameter.json', 'intarna-interactions.csv', 'intarna-detailed.txt']);
+ const document = JSON.parse(entries['parameter.json']);
+ assert.equal(document.parameters.query.text, '>query\nGGGAGGAGGG\n');
assert.equal(entries['intarna-interactions.csv'], 'E\n-21.4\n');
- assert.equal(entries['qShape.txt'], '1 0.2\n2 0.3\n');
- assert.match(entries['parameters.cfg'], /# ViennaRNA 2.7.0/);
- for (const line of ['personality = IntaRNAeval', 'temperature = 21', 'qIdxPos0 = 3', 'qShape = qShape.txt', 'outCsvCols = E']) assert.ok(entries['parameters.cfg'].includes(line), line);
- assert.ok(!entries['parameters.cfg'].includes('tIdxPos0 ='));
- assert.ok(!entries['parameters.cfg'].includes('seedBP ='));
- assert.match(entries['detailed.cfg'], /out = intarna-detailed.txt/);
- assert.match(entries['parameters.cfg'], /IntaRNA --parameterFile=parameters.cfg/);
+ assert.equal(document.parameters.qShape.text, '1 0.2\n2 0.3');
+ assert.ok(document._comment.includes('ViennaRNA 2.7.0'));
+ for (const [key, value] of Object.entries({ personality: 'IntaRNAeval', temperature: 21, qIdxPos0: 3, outCsvCols: 'E' })) assert.equal(document.parameters[key], value);
+ assert.equal(document.parameters.tIdxPos0, undefined);
+ assert.equal(document.parameters.seedBP, undefined);
+ assert.deepEqual(document.gui.reports, ['detailed']);
+ assert.ok(document._comment.some((line) => line.includes('IntaRNA --parameterFile=parameter.json')));
assert.equal(entries['intarna-detailed.txt'], 'report');
assert.throws(() => archiveFiles(request, { csv: '', files: [] }, ''), /version/);
});
diff --git a/gui/tests/parameter-file.test.mjs b/gui/tests/parameter-file.test.mjs
new file mode 100644
index 00000000..25f9ba6d
--- /dev/null
+++ b/gui/tests/parameter-file.test.mjs
@@ -0,0 +1,58 @@
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import { DEFAULTS, EVALUATION_EXAMPLE, prepareRequest, CSV_COLUMNS } from '../protocol.js';
+import { FIELDS } from '../options.js';
+import { exportParameters, serializeParameters, importParameters } from '../parameter-file.js';
+const provenance = 'IntaRNA test\nViennaRNA test\n';
+
+test('JSON round trip retains embedded inputs, typed options, selected columns and reports', () => {
+ for (const request of [
+ { ...DEFAULTS, query: '>q1\nGGGAGGAGGG\n>q2\nGGGAGGAGGG', qIdxPos0: -3, tIdxPos0: -5, parameters: { temperature: 22, noSeed: true, mode: 'M', qRegion: '-3-7', outSpotProbSpots: '-5&-3' }, csvColumns: ['E', 'id2'], outputs: ['normal', 'qPu', 'spotProb'] },
+ { ...DEFAULTS, task: 'evaluation', rri: EVALUATION_EXAMPLE, parameters: { qShape: '1 0.2\n2 0.3' }, outputs: ['detailed', 'ensemble'] },
+ ]) {
+ const document = exportParameters(request, provenance);
+ const imported = importParameters(JSON.stringify(document));
+ const actual = prepareRequest(imported), expected = prepareRequest(request);
+ actual.outputs.sort(); expected.outputs.sort();
+ assert.deepEqual(actual, expected);
+ assert.deepEqual(exportParameters(imported, provenance), document);
+ assert.ok(!JSON.stringify(document).includes('/query.fa'));
+ }
+});
+
+test('empty inputs and unfinished forms remain editable after Load/Store', () => {
+ const request = { query: '', target: '', parameters: { seedBP: 12 }, csvColumns: [], outputs: [] };
+ const document = serializeParameters(request, provenance);
+ const loaded = importParameters(document);
+ assert.equal(loaded.query, ''); assert.equal(loaded.target, '');
+ assert.equal(loaded.parameters.seedBP, 12); assert.deepEqual(loaded.csvColumns, []);
+ assert.throws(() => prepareRequest(loaded), /enter a sequence/);
+ const defaults = exportParameters(DEFAULTS, provenance).parameters;
+ for (const field of FIELDS) assert.equal(defaults[field.key], undefined, field.key);
+ assert.equal(defaults.qIdxPos0, undefined);
+ assert.equal(defaults.outCsvCols, undefined);
+});
+
+test('invalid/native-only settings are rejected before replacing the form', () => {
+ const base = exportParameters(DEFAULTS, provenance);
+ for (const values of [{ threads: 8 }, { parameterFile: 'other.json' }, { query: '/input.fa' }, { qShape: '/data.txt' }, { noSeed: 'false' }, { temperature: null }, { out: ['../file'] }, { out: ['unknown:data'] }, { personality: 'IntaRNAens' }, { energyVRNA: { text: 'x' } }, { outMode: 'D' }, { query: { text: 'a', path: 'x' } }]) {
+ assert.throws(() => importParameters({ ...base, parameters: { ...base.parameters, ...values } }), JSON.stringify(values));
+ }
+ for (const document of [[], null, { ...base, schema: 'new' }, { ...base, other: true }, { ...base, gui: [] }, { ...base, gui: { reports: ['unknown'] } }]) assert.throws(() => importParameters(document));
+ assert.throws(() => importParameters('{invalid'));
+ assert.throws(() => importParameters(' '.repeat(5000001)), /5 MB/);
+ assert.deepEqual(importParameters('\ufeff'+JSON.stringify(base)).csvColumns, CSV_COLUMNS.split(','));
+});
+
+test('maintainer examples use the same JSON import and prediction validation', async () => {
+ const { readFile } = await import('node:fs/promises');
+ const examples = JSON.parse(await readFile(new URL('../examples/index.json', import.meta.url)));
+ assert.equal(examples.length, 2);
+ const tasks = new Set();
+ for (const file of examples) {
+ const example = JSON.parse(await readFile(new URL(`../examples/${file}`, import.meta.url)));
+ assert.ok(example.name && example.description);
+ tasks.add(prepareRequest(importParameters(example.configuration)).task);
+ }
+ assert.deepEqual([...tasks].sort(), ['evaluation', 'prediction']);
+});
diff --git a/webinterface/tests/protocol.test.mjs b/gui/tests/protocol.test.mjs
similarity index 61%
rename from webinterface/tests/protocol.test.mjs
rename to gui/tests/protocol.test.mjs
index a6608d7d..07a60071 100644
--- a/webinterface/tests/protocol.test.mjs
+++ b/gui/tests/protocol.test.mjs
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { DEFAULTS, EVALUATION_EXAMPLE, parseSequence, parseFasta, prepareRequest, makeArguments, CSV_COLUMNS, sequenceOffset } from '../protocol.js';
import { FIELDS } from '../options.js';
-import { CSV_CATALOG } from '../columns.js';
+import { CSV_CATALOG, CSV_GROUPS, defaultColumns } from '../columns.js';
const request = (parameters = {}, extra = {}) => ({ ...DEFAULTS, parameters, ...extra });
test('FASTA normalization, identifiers, bounded multi-record work and subsets', () => {
@@ -12,11 +12,11 @@ test('FASTA normalization, identifiers, bounded multi-record work and subsets',
const fasta = '>a\nGGGAGGAGGG\n>b\nGGGAGGAGGG';
assert.equal(prepareRequest(request({}, { query: fasta })).query.records.length, 2);
assert.throws(() => prepareRequest(request({}, { task: 'evaluation', query: fasta, rri: EVALUATION_EXAMPLE })), /exactly one/);
- assert.throws(() => prepareRequest(request({}, { query: '>a\n'+'A'.repeat(2000)+'\n>b\n'+'A'.repeat(2000), target: 'C'.repeat(300) })), /1,000,000/);
- assert.doesNotThrow(() => prepareRequest(request({ qSet: '2-2' }, { query: '>a\n'+'A'.repeat(2000)+'\n>b\n'+'A'.repeat(2000), target: 'C'.repeat(300) })));
+ assert.throws(() => prepareRequest(request({}, { query: '>a\n'+'A'.repeat(5000)+'\n>b\n'+'A'.repeat(2000), target: 'C'.repeat(300) })), /1,000,000/);
+ assert.doesNotThrow(() => prepareRequest(request({ qSet: '2-2' }, { query: '>a\n'+'A'.repeat(5000)+'\n>b\n'+'A'.repeat(2000), target: 'C'.repeat(300) })));
assert.throws(() => prepareRequest(request({ outPairwise: true }, { query: fasta })), /same number/);
assert.throws(() => prepareRequest(request({ qSet: '3-3' })), /within/);
- assert.throws(() => prepareRequest(request({}, { query: 'A'.repeat(2001) })));
+ assert.throws(() => prepareRequest(request({}, { query: 'A'.repeat(5001) })));
});
test('CLI defaults, ranges and strict typed controls reject invalid values', () => {
@@ -69,3 +69,51 @@ test('browser catalog covers every native CSV column and every control names a C
const source = readFileSync(new URL('../../src/bin/CommandLineParsing.cpp', import.meta.url), 'utf8');
for (const field of FIELDS.filter((field) => field.key !== 'outSpotProbSpots')) assert.ok(source.includes(`"${field.key}"`), field.key);
});
+
+test('JSON worker boundary embeds all inputs with typed non-default settings', async () => {
+ const { makeParameterDocument, JSON_SCHEMA } = await import('../protocol.js');
+ const prepared = prepareRequest(request({ temperature: 20, noSeed: true, qShape: '1 0.2' }, { outputs: ['qPu'] }));
+ const document = JSON.parse(JSON.stringify(makeParameterDocument(prepared)));
+ assert.equal(document.schema, JSON_SCHEMA);
+ assert.equal(document.parameters.query.text, prepared.query.fasta);
+ assert.equal(document.parameters.target.text, prepared.target.fasta);
+ assert.equal(document.parameters.qShape.text, '1 0.2\n');
+ assert.equal(document.parameters.temperature, 20);
+ assert.equal(document.parameters.noSeed, true);
+ assert.equal(document.parameters.qIdxPos0, undefined);
+ assert.deepEqual(document.parameters.out, ['/results.csv', 'qPu:/output/qPu.csv']);
+ const worker = readFileSync(new URL('../intarna-worker.js', import.meta.url), 'utf8');
+ assert.ok(worker.includes("callMain(['--parameterFile=/parameter.json'])"));
+});
+
+
+test('browser batches use longest selected strands and round-trip full FASTA inputs', async () => {
+ const batch = Array.from({ length: 50 }, (_, i) => `>q${i}\n${'A'.repeat(5000)}\n`).join('');
+ assert.equal(parseFasta(batch, 'Query').records.length, 50);
+ assert.throws(() => parseFasta(batch + '>extra\nAA', 'Query'), /50 FASTA/);
+ assert.doesNotThrow(() => prepareRequest(request({}, { query: batch, target: 'C'.repeat(200) })));
+ assert.throws(() => prepareRequest(request({}, { query: batch, target: 'C'.repeat(201) })), /1,000,000/);
+ const crossed = { query: '>q1\n' + 'A'.repeat(5000) + '\n>q2\nAA', target: '>t1\nCC\n>t2\n' + 'C'.repeat(5000) };
+ assert.throws(() => prepareRequest(request({ outPairwise: true, noSeed: true }, crossed)), /1,000,000/);
+ const { serializeParameters, importParameters } = await import('../parameter-file.js');
+ assert.equal(importParameters(serializeParameters(request({}, { query: batch }), 'test')).query, batch);
+ const parsed = parseFasta('>one\tdescription here\nACGU\n>two \nACGU', 'Query');
+ assert.deepEqual(parsed.records.map((record) => record.id), ['one', 'two']);
+ assert.equal(parsed.warnings.length, 1);
+ assert.match(parsed.warnings[0], /one/);
+ assert.equal(parsed.fasta, '>one\nACGU\n>two\nACGU\n');
+});
+
+test('CSV groups cover native columns once and task defaults serialize explicitly', async () => {
+ assert.deepEqual(CSV_GROUPS.flatMap((group) => group.keys).toSorted(), CSV_CATALOG.map((column) => column.key).toSorted());
+ const energy = [...CSV_GROUPS.find((group) => group.label === 'Energy').keys, 'Etotal', 'Eall', 'Eall1', 'Eall2', 'EallTotal'];
+ for (const key of energy) assert.ok(defaultColumns('evaluation').includes(key), key);
+ for (const key of ['seedStart1', 'seedStart2', 'seedEnd1', 'seedEnd2']) assert.ok(!defaultColumns('evaluation').includes(key), key);
+ for (const key of ['ED1', 'ED2', 'seedE', 'seedStart1', 'seedStart2']) assert.ok(defaultColumns('prediction').includes(key), key);
+ const { exportParameters, importParameters } = await import('../parameter-file.js');
+ const input = request({ outNumber: 10 }, { csvColumns: defaultColumns('prediction') });
+ const saved = exportParameters(input, 'test');
+ assert.equal(saved.parameters.outNumber, 10);
+ assert.equal(saved.parameters.outCsvCols, input.csvColumns.join(','));
+ assert.deepEqual(prepareRequest(importParameters(saved)), prepareRequest(input));
+});
diff --git a/webinterface/tests/results.test.mjs b/gui/tests/results.test.mjs
similarity index 100%
rename from webinterface/tests/results.test.mjs
rename to gui/tests/results.test.mjs
diff --git a/gui/tests/uploads.test.mjs b/gui/tests/uploads.test.mjs
new file mode 100644
index 00000000..76a13200
--- /dev/null
+++ b/gui/tests/uploads.test.mjs
@@ -0,0 +1,15 @@
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import { readFastaFile } from '../uploads.js';
+const file = (text) => ({ size: new TextEncoder().encode(text).length, text: async () => text });
+test('FASTA uploads accept text/CRLF and validate size, format and task before replacement', async () => {
+ const input = '>q\r\ngggaggaggg\r\n';
+ assert.equal(await readFastaFile(file(input), 'Query', 'prediction'), input);
+ const multi = '>q1\nGGGAGGAGGG\n>q2\nGGGAGGAGGG';
+ assert.equal(await readFastaFile(file(multi), 'Query', 'prediction'), multi);
+ await assert.rejects(readFastaFile(file(multi), 'Query', 'evaluation'), /exactly one/);
+ for (const invalid of ['', '\u0000bin', '>q\n--!', '>q\n']) await assert.rejects(readFastaFile(file(invalid), 'Query', 'prediction'));
+ const batch = Array.from({ length: 50 }, (_, i) => `>q${i}\n${'A'.repeat(5000)}\n`).join('');
+ assert.equal(await readFastaFile(file(batch), 'Query', 'prediction'), batch);
+ await assert.rejects(readFastaFile({ size: 1000001, text: () => { throw Error('must not read'); } }, 'Query', 'prediction'), /1 MB/);
+});
diff --git a/webinterface/tests/worker.test.mjs b/gui/tests/worker.test.mjs
similarity index 100%
rename from webinterface/tests/worker.test.mjs
rename to gui/tests/worker.test.mjs
diff --git a/gui/uploads.js b/gui/uploads.js
new file mode 100644
index 00000000..81748047
--- /dev/null
+++ b/gui/uploads.js
@@ -0,0 +1,9 @@
+import { parseFasta, LIMITS } from './protocol.js';
+
+/** Read and validate a local text file before replacing a FASTA input. */
+export async function readFastaFile(file, label, task) {
+ if (!file || file.size > LIMITS.maxFastaBytes) throw new Error(`${label}: choose a FASTA text file of at most 1 MB.`);
+ const text = await file.text();
+ parseFasta(text, label, task !== 'evaluation');
+ return text;
+}
diff --git a/webinterface/visualization.js b/gui/visualization.js
similarity index 93%
rename from webinterface/visualization.js
rename to gui/visualization.js
index 46837c57..0bc7b397 100644
--- a/webinterface/visualization.js
+++ b/gui/visualization.js
@@ -27,8 +27,8 @@ export function visualizationUrl(row, request) {
export class InteractionVisualization {
constructor(root) {
- this.frame = root.querySelector('#varri-frame'); this.link = root.querySelector('#varri-link');
- this.status = root.querySelector('#visualization-status');
+ this.frame = root.querySelector('[data-role=varri-frame]'); this.link = root.querySelector('[data-role=varri-link]');
+ this.status = root.querySelector('[data-role=visualization-status]');
}
clear() {
this.frame.removeAttribute('src'); this.frame.hidden = true;
diff --git a/scripts/wasm/smoke-protocol.mjs b/scripts/wasm/smoke-protocol.mjs
index ee27ad8b..9dd90369 100644
--- a/scripts/wasm/smoke-protocol.mjs
+++ b/scripts/wasm/smoke-protocol.mjs
@@ -3,10 +3,10 @@ import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
-import { DEFAULTS, EVALUATION_EXAMPLE, prepareRequest, makeArguments } from '../../webinterface/protocol.js';
-import { CSV_CATALOG } from '../../webinterface/columns.js';
-import { parseResults, projectCsv } from '../../webinterface/results.js';
-import { archiveFiles } from '../../webinterface/archive.js';
+import { DEFAULTS, CSV_COLUMNS, EVALUATION_EXAMPLE, prepareRequest, makeParameterDocument } from '../../gui/protocol.js';
+import { CSV_CATALOG } from '../../gui/columns.js';
+import { parseResults, projectCsv } from '../../gui/results.js';
+import { archiveFiles } from '../../gui/archive.js';
const asset = resolve(process.argv[2] || 'build-wasm/site/wasm/intarna.js');
const { default: createIntaRNA } = await import(pathToFileURL(asset));
const compiledVersion = readFileSync(new URL('../build-info.txt', pathToFileURL(asset)), 'utf8');
@@ -14,11 +14,9 @@ async function run(input, format = 'C') {
const request = prepareRequest({ ...DEFAULTS, ...input });
const diagnostics = [];
const module = await createIntaRNA({ noInitialRun: true, locateFile: () => new URL('./intarna.wasm', pathToFileURL(asset)).href, print: (line) => diagnostics.push(line), printErr: (line) => diagnostics.push(line) });
- module.FS.writeFile('/query.fa', request.query.fasta);
- module.FS.writeFile('/target.fa', request.target.fasta);
+ module.FS.writeFile('/parameter.json', JSON.stringify(makeParameterDocument(request, format)));
module.FS.mkdir('/output');
- for (const [key, value] of Object.entries(request.parameters)) if (/^(q|t)(Shape|AccFile)$/.test(key) && value) module.FS.writeFile(`/${key}.txt`, value + '\n');
- const args = makeArguments(request, format);
+ const args = ['--parameterFile=/parameter.json'];
assert.equal(module.callMain(args), 0, `${args.join(' ')}\n${diagnostics.join('\n')}`);
const text = module.FS.readFile(format === 'C' ? '/results.csv' : '/report.txt', { encoding: 'utf8' });
return { rows: format === 'C' ? parseResults(text) : [], text, fs: module.FS, request };
@@ -60,8 +58,10 @@ async function replayArchive(input) {
for (const config of ['parameters', ...reports.map(([key]) => key)]) {
const diagnostics = [];
const module = await createIntaRNA({ noInitialRun: true, locateFile: () => new URL('./intarna.wasm', pathToFileURL(asset)).href, print: (line) => diagnostics.push(line), printErr: (line) => diagnostics.push(line) });
- for (const file of files.filter((file) => /\.cfg$|^(query|target)\.fa$|^[qt](Shape|AccFile)\.txt$/.test(file.name))) module.FS.writeFile(`/${file.name}`, file.text);
- assert.equal(module.callMain([`--parameterFile=${config}.cfg`]), 0, diagnostics.join('\n'));
+ module.FS.writeFile('/parameter.json', files.find((file) => file.name === 'parameter.json').text);
+ const arguments_ = ['--parameterFile=/parameter.json', `--out=${config === 'parameters' ? 'intarna-interactions.csv' : `intarna-${config}.txt`}`];
+ if (config !== 'parameters') arguments_.push(`--outMode=${reports.find(([key]) => key === config)[1]}`, `--outCsvCols=${CSV_COLUMNS}`, "--outCsvSort", "");
+ assert.equal(module.callMain(arguments_), 0, diagnostics.join('\n'));
const expected = config === 'parameters' ? [{ name: 'intarna-interactions.csv', text: projectCsv(original.text, request.csvColumns) }, ...outputs.filter((file) => !file.name.startsWith('intarna-'))] : outputs.filter((file) => file.name === `intarna-${config}.txt`);
for (const file of expected) assert.equal(module.FS.readFile(`/${file.name}`, { encoding: 'utf8' }), file.text, file.name);
}
diff --git a/scripts/wasm/stage.sh b/scripts/wasm/stage.sh
index 20367c2e..040744ba 100644
--- a/scripts/wasm/stage.sh
+++ b/scripts/wasm/stage.sh
@@ -25,12 +25,13 @@ PYSOURCE
)
vienna_source="$build/sources/vienna-$vienna_version-$vienna_sha256/ViennaRNA-$vienna_version"
mkdir -p "$site/licenses"
-cp -R "$repo/webinterface/assets" "$site/"
-for file in index.html styles.css app.js mark.svg protocol.js runner.js results.js options.js columns.js archive.js visualization.js; do
- cp "$repo/webinterface/$file" "$site/$file"
+cp -R "$repo/gui/assets" "$repo/gui/examples" "$site/"
+for file in index.html styles.css app.js mark.svg protocol.js runner.js results.js options.js columns.js archive.js sessions.js visualization.js parameter-file.js uploads.js; do
+ cp "$repo/gui/$file" "$site/$file"
done
-cp "$repo/webinterface/intarna-worker.js" "$site/intarna-worker.js"
+cp "$repo/gui/intarna-worker.js" "$site/intarna-worker.js"
touch "$site/.nojekyll"
+cp "$repo/src/bin/nlohmann/LICENSE.MIT" "$site/licenses/nlohmann-json.txt"
cp "$repo/LICENSE" "$site/licenses/IntaRNA.txt"
cp "$repo/src/easylogging++.LICENCE.txt" "$site/licenses/easylogging.txt"
cp "$repo/src/mdspan/LICENSE" "$site/licenses/mdspan.txt"
@@ -52,6 +53,7 @@ cat > "$site/licenses/index.html" <This distribution uses ViennaRNA $vienna_version, Boost 1.87.0, zlib 1.3.1, bundled dlib ${dlib_directory#dlib-}, Kokkos mdspan, Easylogging++ and the Emscripten 4.0.23 runtime.
IntaRNA ViennaRNA
+nlohmann/json
Boost zlib
dlib Naview
Kokkos mdspan Easylogging++
diff --git a/scripts/wasm/test-browser.py b/scripts/wasm/test-browser.py
index 079fbdf5..990e5e58 100644
--- a/scripts/wasm/test-browser.py
+++ b/scripts/wasm/test-browser.py
@@ -3,6 +3,7 @@
from pathlib import Path
from io import BytesIO
import re
+import json
from urllib.parse import parse_qs, urljoin, urlparse
from zipfile import ZipFile
from playwright.sync_api import sync_playwright, expect
@@ -28,7 +29,9 @@ def viewer(route):
page.wait_for_load_state('networkidle')
compute = page.locator('#compute')
status = page.locator('#result-status')
- rows = page.locator('tbody tr')
+ sessions = page.locator('.session')
+ active = page.locator('.session:has([data-role=panel]:not([hidden]))')
+ rows = active.locator('tbody tr')
def open_group(key):
page.locator('#tab-' + key).click()
@@ -36,9 +39,14 @@ def open_group(key):
expect(page.locator('#tab-' + key)).to_have_attribute('aria-selected', 'true')
assert page.locator('.parameter-group:visible').count() == 1
- def download_archive():
+ def load_example(task='prediction'):
+ page.locator('#examples-menu summary').click()
+ page.get_by_role('button', name='Evaluate a duplex' if task == 'evaluation' else 'Short duplex', exact=False).click()
+ expect(page.locator('#query')).to_have_value('>query\nGGGAGGAGGG\n')
+
+ def download_archive(session=None):
with page.expect_download() as downloaded:
- page.locator('#download-zip').click()
+ (active if session is None else session).locator('[data-role=download-zip]').click()
with ZipFile(BytesIO(Path(downloaded.value.path()).read_bytes())) as archive:
assert archive.testzip() is None
return {name: archive.read(name).decode() for name in archive.namelist()}
@@ -50,10 +58,17 @@ def fill(key, value):
page.locator('#' + key).fill(str(value))
def predict():
+ count = sessions.count()
compute.click()
expect(status).to_contain_text('complete:', timeout=120000)
expect(page.locator('#input-error')).not_to_be_visible()
+ expect(sessions).to_have_count(count + 1)
+ expect(page.locator('#query')).to_have_value('')
+ expect(page.locator('#target')).to_have_value('')
+ expect(page.locator('#query')).to_have_attribute('placeholder', '>query\nGGGAGGAGGG')
+ compute.click()
+ expect(page.locator('#input-error')).to_contain_text('enter a sequence')
metadata = page.request.get(urljoin(args.url, 'build-info.json')).json()
vienna_version = metadata['viennarna']
assert re.fullmatch(r'2\.\d+\.\d+', vienna_version)
@@ -61,30 +76,177 @@ def predict():
expect(page.locator('#version-info')).to_contain_text(f'ViennaRNA {vienna_version}')
for logo in page.locator('.site-header img').all():
assert logo.evaluate('node => node.complete && node.naturalWidth > 0')
- for key in ['query-options', 'target-options', 'seed-options', 'helix-options', 'shape-options', 'interaction-options', 'output-options', 'general-options', 'sequence-input']:
+ for key in ['seed-options', 'output-options', 'interaction-options', 'query-options', 'target-options', 'general-options', 'sequence-input']:
open_group(key)
+ expect(page.get_by_role('radio', name='Prediction')).to_be_checked()
+ expect(page.get_by_role('radio', name='Evaluation')).to_be_visible()
+ page.get_by_role('radio', name='Prediction').focus()
+ page.keyboard.press('ArrowRight')
+ expect(page.get_by_role('radio', name='Evaluation')).to_be_checked()
+ expect(page.locator('#task-help')).to_contain_text('Evaluate supplied structures')
+ page.keyboard.press('ArrowLeft')
+ expect(page.get_by_role('radio', name='Prediction')).to_be_checked()
+
# Tabs support keyboard navigation and wrap without page overflow.
page.locator('#tab-sequence-input').focus()
page.keyboard.press('ArrowRight')
- expect(page.locator('#tab-query-options')).to_be_focused()
- expect(page.locator('#query-options')).to_be_visible()
+ expect(page.locator('#tab-seed-options')).to_be_focused()
+ expect(page.locator('#seed-options')).to_be_visible()
page.keyboard.press('End')
expect(page.locator('#tab-general-options')).to_be_focused()
page.keyboard.press('Home')
expect(page.locator('#tab-sequence-input')).to_be_focused()
+ # Review layout, task defaults and modes work with native parameters.
+ assert page.locator('[role=tab]').all_text_contents() == ['Sequences', 'Seed', 'Output', 'Model & Energy', 'Query', 'Target', 'CLI-only']
+ expect(page.locator('.brand')).to_contain_text('IntaRNA package')
+ expect(page.locator('#results-title')).to_have_text('Results')
+ expect(page.locator('#outNumber')).to_have_value('10')
+ assert page.locator('.compute-bar').evaluate('node => node.firstElementChild.id') == 'compute'
+ assert page.locator('.sequence-meta').first.evaluate('node => node.firstElementChild.tagName') == 'LABEL'
+ fill('query', '>named\tignored description\n' + 'G' * 1000 + '\n' * 20 + 'GG')
+ expect(page.locator('#query-warning')).to_contain_text('ignoring FASTA description')
+ expect(page.locator('#query-warning')).to_contain_text('named')
+ assert page.locator('#query').evaluate('node => node.scrollWidth > node.clientWidth && node.scrollHeight > node.clientHeight')
+ fill('query', '>named \nGGGAGGAGGG')
+ expect(page.locator('#query-warning')).not_to_be_visible()
+
+ for prefix, group in [('q', 'query-options'), ('t', 'target-options')]:
+ open_group(group)
+ # Each explicit row shares label height, including wrapped labels.
+ for keys in [[prefix + suffix for suffix in ['Set', 'Region', 'IntLenMax', 'IntLoopMax', 'RegionLenMax']], [prefix + suffix for suffix in ['Acc', 'AccW', 'AccL', 'PfScale']]]:
+ tops = [page.locator('#' + key).bounding_box()['y'] for key in keys]
+ assert max(tops) - min(tops) < 1, tops
+ expect(page.locator('#' + prefix + 'AccFile')).to_be_disabled()
+ page.locator('#' + prefix + 'Acc').select_option('P')
+ expect(page.locator('#' + prefix + 'AccFile')).to_be_enabled()
+ for suffix in ['AccW', 'AccL']:
+ expect(page.locator('#' + prefix + suffix)).to_be_disabled()
+ page.locator('#' + prefix + 'Acc').select_option('N')
+ for suffix in ['AccW', 'AccL', 'AccConstr', 'AccFile', 'PfScale', 'Shape', 'ShapeMethod', 'ShapeConversion']:
+ expect(page.locator('#' + prefix + suffix)).to_be_disabled()
+ expect(page.locator('input[data-for="' + prefix + 'Shape"]')).to_be_disabled()
+ page.locator('#' + prefix + 'Acc').select_option('C')
+ expect(page.locator('#' + prefix + 'Shape')).to_be_enabled()
+ page.locator('#accNoLP-target').check()
+ expect(page.locator('#accNoLP')).to_be_checked()
+ open_group('query-options')
+ page.locator('#accNoLP').uncheck()
+ expect(page.locator('#accNoLP-target')).not_to_be_checked()
+ open_group('interaction-options')
+ expect(page.locator('#helixMinBP')).to_be_disabled()
+ page.locator('#model').select_option('B')
+ expect(page.locator('#helixMinBP')).to_be_enabled()
+ page.locator('#model').select_option('X')
+ expect(page.locator('#helixMinBP')).to_be_disabled()
+ open_group('seed-options')
+ expect(page.locator('#seedTQ')).to_be_disabled()
+ page.locator('#seed-mode').select_option('none')
+ for key in ['seedBP', 'seedTQ', 'seedQRange', 'seedNoGU']:
+ expect(page.locator('#' + key)).to_be_disabled()
+ page.locator('#seed-mode').select_option('explicit')
+ expect(page.locator('#seedTQ')).to_be_enabled()
+ expect(page.locator('#seedBP')).to_be_disabled()
+ page.locator('#seed-mode').select_option('identify')
+ expect(page.locator('#seedBP')).to_be_enabled()
+ expect(page.locator('#seedTQ')).to_be_disabled()
+ open_group('output-options')
+ groups = page.locator('.csv-group')
+ assert groups.count() == 5
+ for group in groups.all():
+ group.get_by_role('button', name=re.compile('^Select all')).click()
+ assert group.locator('input:not(:checked)').count() == 0
+ group.get_by_role('button', name='Deselect all').click()
+ assert group.locator('input:checked').count() == 0
+ page.get_by_role('button', name='Task defaults', exact=True).click()
+ for key in ['ED1', 'ED2', 'seedE', 'seedStart1', 'seedStart2']:
+ expect(page.locator('.csv-group input[value="' + key + '"]')).to_be_checked()
+ assert all(label.get_attribute('title') and ' — ' not in label.inner_text() for label in groups.locator('label').all())
+ # Inspect all changed panels at desktop and mobile widths.
+ for width in [1440, 390]:
+ page.set_viewport_size({'width': width, 'height': 1100 if width == 1440 else 844})
+ for key in ['sequence-input', 'seed-options', 'output-options', 'interaction-options', 'query-options', 'target-options', 'general-options']:
+ open_group(key)
+ assert page.evaluate('document.documentElement.scrollWidth <= innerWidth'), (width, key)
+ if args.screenshot:
+ path = Path(args.screenshot)
+ page.screenshot(path=str(path.with_name(f'{path.stem}-{key}-{width}.png')), full_page=True)
+ page.set_viewport_size({'width': 1440, 'height': 1100})
+ page.locator('#reset').click()
+
+ # Upload and file drop use local text, validate before replacing, and reset.
+ with page.expect_file_chooser() as chooser:
+ page.locator('#query-upload-button').click()
+ chooser.value.set_files({'name': 'query.fa', 'mimeType': 'text/plain', 'buffer': b'>uploaded\nGGGAGGAGGG\n'})
+ expect(page.locator('#query')).to_have_value('>uploaded\nGGGAGGAGGG\n')
+ page.locator('#target').evaluate(r"""node => {
+ const transfer = new DataTransfer();
+ transfer.items.add(new File(['>dropped\nCCCUCCUCCC\n'], 'target.fa', {type:'text/plain'}));
+ node.dispatchEvent(new DragEvent('drop', {dataTransfer:transfer, bubbles:true, cancelable:true}));
+ }""")
+ expect(page.locator('#target')).to_have_value('>dropped\nCCCUCCUCCC\n')
+ page.locator('#query-upload').set_input_files({'name': 'bad.fa', 'mimeType': 'text/plain', 'buffer': b'not a sequence!'})
+ expect(page.locator('#input-error')).to_be_visible()
+ expect(page.locator('#query')).to_have_value('>uploaded\nGGGAGGAGGG\n')
+ # A file read completing after Reset cannot restore the previous input.
+ page.evaluate(r"""() => { window.originalFileText = File.prototype.text; File.prototype.text = function() { return new Promise(resolve => { window.finishRead = () => resolve('>late\nGGGAGGAGGG\n'); }); }; }""")
+ page.locator('#query-upload').set_input_files({'name': 'late.fa', 'mimeType': 'text/plain', 'buffer': b'>late\nGGGAGGAGGG\n'})
+ page.locator('#reset').click()
+ page.evaluate('() => { File.prototype.text = window.originalFileText; window.finishRead(); }')
+ expect(page.locator('#query')).to_have_value('')
+
# JavaScript validation works even when the offending control is in another tab.
fill('query', '>bad\nACGU-ACGU')
compute.click()
expect(page.locator('#input-error')).to_contain_text('use only')
expect(page.locator('#loading')).not_to_be_visible()
- page.locator('#example').click()
+ load_example()
fill('temperature', '101')
open_group('sequence-input')
compute.click()
expect(page.locator('#input-error')).to_contain_text('Temperature')
page.locator('#reset').click()
+ expect(page.locator('#query')).to_have_value('')
+ expect(page.locator('#target')).to_have_value('')
+ expect(page.locator('#rri')).to_have_value('')
+ load_example()
+
+ # Disabled stale edits neither block a run nor leak into JSON. Re-enabling
+ # restores local edits; loading/storing uses effective native settings.
+ fill('seedBP', '')
+ page.locator('#seed-mode').select_option('explicit')
+ compute.click()
+ expect(page.locator('#input-error')).to_contain_text('Explicit seed: enter')
+ fill('seedTQ', '1|||&1|||')
+ predict()
+ explicit_settings = json.loads(download_archive()['parameter.json'])
+ assert explicit_settings['parameters']['seedTQ'] == '1|||&1|||'
+ assert 'seedBP' not in explicit_settings['parameters']
+ with page.expect_download() as stored:
+ page.locator('#store-parameters').click()
+ explicit_file = Path(stored.value.path()).read_bytes()
+ page.locator('#reset').click()
+ page.locator('#parameter-upload').set_input_files({'name': 'explicit.json', 'mimeType': 'application/json', 'buffer': explicit_file})
+ expect(page.locator('#seed-mode')).to_have_value('explicit')
+ open_group('seed-options')
+ page.locator('#seedTQ').fill('invalid inactive seed')
+ page.locator('#seed-mode').select_option('none')
+ fill('qShape', 'invalid inactive shape')
+ page.locator('#qAcc').select_option('N')
+ open_group('target-options')
+ page.locator('#accNoGUend-target').check()
+ predict()
+ inactive_settings = json.loads(download_archive()['parameter.json'])['parameters']
+ assert inactive_settings['noSeed'] is True
+ assert inactive_settings['qAcc'] == 'N'
+ assert inactive_settings['accNoGUend'] is True
+ assert 'qShape' not in inactive_settings and 'seedTQ' not in inactive_settings
+ open_group('query-options')
+ page.locator('#qAcc').select_option('C')
+ expect(page.locator('#qShape')).to_have_value('invalid inactive shape')
+ page.locator('#reset').click()
+ load_example()
# Reference prediction, selectable CSV output and independent table visibility.
fill('outNumber', 10)
@@ -92,45 +254,70 @@ def predict():
predict()
expect(rows).to_have_count(10)
assert rows.first.locator('td').all_text_contents()[1:7] == ['target', '1', '10', 'query', '1', '10']
- assert rows.first.locator('td').all_text_contents()[-2:] == ['-21.40', '0.00']
+ assert rows.first.locator('td').nth(9).inner_text() == '-21.40'
+ assert rows.first.locator('td').last.inner_text() == '0.00'
page.get_by_role('button', name='Sort by Energy (kcal/mol)', exact=False).click()
assert float(rows.first.locator('td').nth(9).inner_text()) == -14.3
page.get_by_role('button', name='Sort by Energy (kcal/mol)', exact=False).click()
- page.locator('#search').fill('-21.4')
+ active.locator('[data-role=search]').fill('-21.4')
expect(rows).to_have_count(1)
- page.locator('#search').fill('no match')
- expect(page.locator('#no-matches')).to_be_visible()
- page.locator('#search').fill('')
- page.locator('.column-picker summary').click()
- page.locator('#visible-columns input[value="start1"]').uncheck()
- assert 'Target start' not in page.locator('thead').inner_text()
- page.locator('#visible-columns input[value="start1"]').check()
+ active.locator('[data-role=search]').fill('no match')
+ expect(active.locator('[data-role=no-matches]')).to_be_visible()
+ active.locator('[data-role=search]').fill('')
+ active.locator('.column-picker summary').click()
+ active.locator('[data-role=visible-columns] input[value="start1"]').uncheck()
+ assert 'Target start' not in active.locator('thead').inner_text()
+ active.locator('[data-role=visible-columns] input[value="start1"]').check()
with page.expect_download() as downloaded:
- page.locator('#download').click()
+ active.locator('[data-role=download]').click()
csv = Path(downloaded.value.path()).read_text()
- assert csv.splitlines()[0] == 'id1;start1;end1;id2;start2;end2;subseqDP;hybridDP;E'
+ assert csv.splitlines()[0] == 'id1;start1;end1;id2;start2;end2;subseqDP;hybridDP;E;ED1;ED2;seedE;seedStart1;seedStart2'
assert 'target;1;10;query;1;10;' in csv
assert 'deltaE' not in csv
+ assert active.locator('thead th').first.inner_text() == ''
+ assert active.locator('thead th button br').count() >= 4
+ assert rows.first.locator('td').first.evaluate('node => getComputedStyle(node).paddingLeft') == '5px'
assert not visualization_urls # Third-party content loads only on selection.
rows.first.get_by_role('button', name='Visualize').click()
- expect(page.locator('#varri-frame')).to_be_visible()
- expect(page.frame_locator('#varri-frame').get_by_role('heading', name='vaRRI viewer')).to_be_visible()
+ expect(active.locator('[data-role=varri-frame]')).to_be_visible()
+ expect(active.frame_locator('[data-role=varri-frame]').get_by_role('heading', name='vaRRI viewer')).to_be_visible()
expect(rows.first.locator('button')).to_have_attribute('aria-pressed', 'true')
- query = parse_qs(urlparse(page.locator('#varri-frame').get_attribute('src')).query)
+ query = parse_qs(urlparse(active.locator('[data-role=varri-frame]').get_attribute('src')).query)
assert query['sequence'] == ['CCCUCCUCCC&GGGAGGAGGG']
assert query['structure'] == ['((((((((((&))))))))))']
assert query['seqName1'] == ['target'] and query['seqName2'] == ['query']
assert query['startIndex1'] == ['1']
assert query['showRenderingOnly'] == ['true']
- assert 'showRenderingOnly' not in page.locator('#varri-link').get_attribute('href')
+ assert 'showRenderingOnly' not in active.locator('[data-role=varri-link]').get_attribute('href')
archive = download_archive()
assert archive['intarna-interactions.csv'] == csv
- assert archive['query.fa'] == '>query\nGGGAGGAGGG\n'
- assert '# IntaRNA 3.4.1' in archive['parameters.cfg']
- assert f'Vienna RNA package {vienna_version}' in archive['parameters.cfg']
- assert 'outNumber = 10' in archive['parameters.cfg']
- assert 'IntaRNA --parameterFile=parameters.cfg' in archive['parameters.cfg']
- assert 'temperature =' not in archive['parameters.cfg']
+ saved = json.loads(archive['parameter.json'])
+ assert set(archive) == {'parameter.json', 'intarna-interactions.csv'}
+ assert saved['parameters']['query']['text'] == '>query\nGGGAGGAGGG\n'
+ assert 'IntaRNA 3.4.1' in archive['parameter.json']
+ assert f'Vienna RNA package {vienna_version}' in archive['parameter.json']
+ assert saved['parameters']['outNumber'] == 10
+ assert 'IntaRNA --parameterFile=parameter.json' in archive['parameter.json']
+ assert 'temperature' not in saved['parameters']
+ # Store snapshots the editable form; Load is atomic and preserves CSV order.
+ with page.expect_download() as stored:
+ page.locator('#store-parameters').click()
+ settings = Path(stored.value.path()).read_bytes()
+ imported = json.loads(settings)
+ imported['parameters']['outCsvCols'] = 'E,id2'
+ imported['parameters']['temperature'] = 24
+ page.locator('#parameter-upload').set_input_files({'name': 'settings.json', 'mimeType': 'application/json', 'buffer': json.dumps(imported).encode()})
+ expect(page.locator('#temperature')).to_have_value('24')
+ expect(active.locator('[data-role=results-view]')).to_be_visible()
+ with page.expect_download() as stored_again:
+ page.locator('#store-parameters').click()
+ assert json.loads(Path(stored_again.value.path()).read_text())['parameters']['outCsvCols'] == 'E,id2'
+ page.locator('#parameter-upload').set_input_files({'name': 'bad.json', 'mimeType': 'application/json', 'buffer': b'{invalid'})
+ expect(page.locator('#input-error')).to_be_visible()
+ expect(page.locator('#temperature')).to_have_value('24')
+ page.locator('#parameter-upload').set_input_files({'name': 'settings.json', 'mimeType': 'application/json', 'buffer': settings})
+ expect(page.locator('#temperature')).to_have_value('37')
+ predict()
if args.screenshot:
open_group('sequence-input')
@@ -140,7 +327,8 @@ def predict():
page.set_viewport_size({'width': 1440, 'height': 1100})
# Multi-FASTA prediction, signed inclusive output and additional files.
- page.locator('#example').click()
+ load_example()
+ fill('outNumber', 1)
fill('query', '>q1\nGGGAGGAGGG\n>q2\nGGGAGGAGGG')
fill('qIdxPos0', -3)
fill('tIdxPos0', -5)
@@ -149,15 +337,16 @@ def predict():
predict()
expect(rows).to_have_count(2)
assert rows.first.locator('td').all_text_contents()[1:7] == ['target', '-5', '5', 'q1', '-3', '7']
- expect(page.locator('#file-list details')).to_have_count(2)
- page.locator('#file-list summary').first.click()
- expect(page.locator('#file-list pre').first).to_contain_text('minE')
+ expect(active.locator('[data-role=file-list] details')).to_have_count(2)
+ active.locator('[data-role=file-list] summary').first.click()
+ expect(active.locator('[data-role=file-list] pre').first).to_contain_text('minE')
archive = download_archive()
assert len([name for name in archive if name.startswith('qMinE')]) == 2
- assert archive['query.fa'].count('>') == 2
- assert 'qIdxPos0 = -3' in archive['parameters.cfg']
+ saved = json.loads(archive['parameter.json'])
+ assert saved['parameters']['query']['text'].count('>') == 2
+ assert saved['parameters']['qIdxPos0'] == -3
rows.nth(1).get_by_role('button', name='Visualize').click()
- query = parse_qs(urlparse(page.locator('#varri-frame').get_attribute('src')).query)
+ query = parse_qs(urlparse(active.locator('[data-role=varri-frame]').get_attribute('src')).query)
assert query['seqName2'] == ['q2']
assert query['startIndex1'] == ['-5'] and query['startIndex2'] == ['-3']
@@ -165,19 +354,19 @@ def predict():
fill('query', '>q first\nGGGAGGAGGG\n>q second\nGGGAGGAGGG')
compute.click()
expect(page.locator('#input-error')).to_contain_text('unique')
- expect(page.locator('#varri-frame')).not_to_be_visible()
- assert page.locator('#varri-frame').get_attribute('src') is None
- expect(page.locator('#download-zip')).not_to_be_visible()
+ expect(active.locator('[data-role=varri-frame]')).to_be_visible()
+ expect(active.locator('[data-role=download-zip]')).to_be_visible()
+ assert download_archive() == archive
# Evaluation resets native defaults and removes prediction-only controls.
fill('query', '>q1\nGGGAGGAGGG\n>q2\nGGGAGGAGGG')
- page.locator('#task').select_option('evaluation')
+ page.get_by_role('radio', name='Evaluation').check()
expect(page.locator('#seed-options')).not_to_be_visible()
expect(page.locator('#outNumber')).to_be_disabled()
fill('rri', '1||||||||||&1||||||||||')
compute.click()
expect(page.locator('#input-error')).to_contain_text('exactly one')
- page.locator('#example').click()
+ load_example('evaluation')
fill('rri', '1||||||||||&1||||||||||:1|||||||||&2|||||||||')
open_group('output-options')
page.locator('#output-options input[value="detailed"]').check()
@@ -185,19 +374,20 @@ def predict():
predict()
expect(status).to_contain_text('Evaluation complete')
expect(rows).to_have_count(2)
- expect(page.locator('thead')).to_contain_text('ED1')
- expect(page.locator('#file-list summary')).to_contain_text('intarna-detailed.txt')
- page.locator('#file-list summary').click()
- expect(page.locator('#file-list pre')).to_contain_text('-21.4')
+ expect(active.locator('thead')).to_contain_text('ED1')
+ expect(active.locator('[data-role=file-list] summary')).to_contain_text('intarna-detailed.txt')
+ active.locator('[data-role=file-list] summary').click()
+ expect(active.locator('[data-role=file-list] pre')).to_contain_text('-21.4')
with page.expect_download() as report:
- page.locator('#file-list button').click()
+ active.locator('[data-role=file-list] button').click()
assert '-21.4' in Path(report.value.path()).read_text()
archive = download_archive()
assert '-21.4' in archive['intarna-detailed.txt']
- assert 'personality = IntaRNAeval' in archive['parameters.cfg']
- assert 'rri = 1||||||||||&1||||||||||:1|||||||||&2|||||||||' in archive['parameters.cfg']
- assert 'seedBP =' not in archive['parameters.cfg']
- assert 'out = intarna-detailed.txt' in archive['detailed.cfg']
+ saved = json.loads(archive['parameter.json'])
+ assert saved['parameters']['personality'] == 'IntaRNAeval'
+ assert saved['parameters']['rri'] == '1||||||||||&1||||||||||:1|||||||||&2|||||||||'
+ assert 'seedBP' not in saved['parameters']
+ assert 'detailed' in saved['gui']['reports']
# Visualization stays available when structure/identity columns are deselected.
open_group('output-options')
@@ -205,23 +395,25 @@ def predict():
page.locator('#output-options input[value="E"]').check()
predict()
rows.first.get_by_role('button', name='Visualize').click()
- expect(page.locator('#varri-frame')).to_be_visible()
+ expect(active.locator('[data-role=varri-frame]')).to_be_visible()
archive = download_archive()
assert archive['intarna-interactions.csv'].splitlines()[0] == 'E'
- assert 'hybridDP' not in archive['parameters.cfg']
+ assert json.loads(archive['parameter.json'])['parameters']['outCsvCols'] == 'E'
fill('rri', '1|||&1||')
compute.click()
expect(page.locator('#input-error')).to_contain_text('same nonzero')
# Startup and synchronous C++ cancellation both allow a fresh prediction.
- page.locator('#task').select_option('prediction')
- page.locator('#example').click()
+ page.get_by_role('radio', name='Prediction').check()
+ load_example()
held_routes = []
+ session_count = sessions.count()
page.route('**/wasm/intarna.wasm', lambda route: held_routes.append(route))
compute.click()
expect(page.locator('#loading')).to_be_visible()
page.locator('#abort').click()
expect(status).to_contain_text('aborted')
+ expect(sessions).to_have_count(session_count)
for route in held_routes:
route.abort()
page.unroute('**/wasm/intarna.wasm')
@@ -231,28 +423,185 @@ def predict():
expect(page.locator('#loading-status')).to_contain_text('Predicting RNA', timeout=120000)
page.keyboard.press('Escape')
expect(status).to_contain_text('aborted')
- page.locator('#example').click()
+ expect(sessions).to_have_count(session_count)
+ load_example()
predict()
fill('query', '>no_pairs\nNNNNNNNNNN')
compute.click()
expect(status).to_contain_text('No favorable interactions', timeout=120000)
expect(rows).to_have_count(0)
- expect(page.locator('#varri-frame')).not_to_be_visible()
+ expect(active.locator('[data-role=varri-frame]')).not_to_be_visible()
assert download_archive()['intarna-interactions.csv'].count('\n') == 1
fill('query', '> \nGGGAGGAGGG')
predict()
- assert page.locator('tbody img').count() == 0
+ assert active.locator('tbody img').count() == 0
assert ' 1
+ page.reload()
+ page.wait_for_load_state('networkidle')
+ expect(sessions).to_have_count(0)
+ load_example()
+ fill('temperature', 24)
+ fill('qIdxPos0', -3)
+ fill('tIdxPos0', -5)
+ fill('qShape', '1 0.2\n2 0.3')
+ fill('outNumber', 3)
+ open_group('output-options')
+ page.locator('#output-options input[value="qMinE"]').check()
+ predict()
+ first_id = active.get_attribute('data-session-id')
+ first = page.locator(f'[data-session-id="{first_id}"]')
+ first.get_by_label('Session name', exact=True).fill('Cool duplex')
+ first.get_by_label('Session name', exact=True).press('Enter')
+ first_archive = download_archive(first)
+ first_csv = first_archive['intarna-interactions.csv']
+ first.locator('[data-role=search]').fill('no match')
+ expect(first.locator('[data-role=no-matches]')).to_be_visible()
+ first.locator('.column-picker summary').click()
+ first.locator('[data-role=visible-columns] input[value="start1"]').uncheck()
+ first_diagnostics = first.locator('[data-role=diagnostic-text]').text_content()
+
+ # Edits and Reset preserve outputs. Restore also works for an unfolded run.
+ fill('temperature', 31)
+ expect(first.locator('[data-role=panel]')).to_be_visible()
+ assert download_archive(first) == first_archive
+ first.locator('[data-role=summary]').click()
+ expect(page.locator('#temperature')).to_have_value('24')
+ fill('temperature', 31)
+ first.locator('[data-role=toggle]').click()
+ expect(page.locator('#temperature')).to_have_value('24')
+ expect(first.locator('[data-role=panel]')).to_be_visible()
+ first.locator('[data-role=toggle]').click()
+ expect(first.locator('[data-role=panel]')).not_to_be_visible()
+ first.locator('[data-role=toggle]').press('Enter')
+ expect(first.locator('[data-role=panel]')).to_be_visible()
+ page.locator('#reset').click()
+ expect(sessions).to_have_count(1)
+ expect(page.locator('#query')).to_have_value('')
+ assert download_archive(first) == first_archive
+
+ # A different task, CSV order and output report own a second result area.
+ load_example('evaluation')
+ with page.expect_download() as stored:
+ page.locator('#store-parameters').click()
+ second_settings = json.loads(Path(stored.value.path()).read_text())
+ second_settings['parameters']['temperature'] = 42
+ second_settings['parameters']['outCsvCols'] = 'E,id2'
+ second_settings['gui'] = {'reports': ['detailed']}
+ page.locator('#parameter-upload').set_input_files({'name': 'evaluation.json', 'mimeType': 'application/json', 'buffer': json.dumps(second_settings).encode()})
+ predict()
+ expect(sessions).to_have_count(2)
+ second_id = active.get_attribute('data-session-id')
+ assert second_id != first_id
+ second = page.locator(f'[data-session-id="{second_id}"]')
+ expect(first.locator('[data-role=panel]')).not_to_be_visible()
+ expect(first.locator('[data-role=download-zip]')).to_be_visible()
+ assert download_archive(first) == first_archive
+ expect(page.get_by_role('radio', name='Evaluation')).to_be_checked()
+ expect(page.locator('#temperature')).to_have_value('42')
+ second_archive = download_archive(second)
+ assert second_archive['intarna-interactions.csv'].splitlines()[0] == 'E;id2'
+ assert 'intarna-detailed.txt' in second_archive
+
+ # Selecting reloads all computed inputs while retaining each table's filter.
+ first.locator('.session-header').focus()
+ first.locator('.session-header').press('Space')
+ expect(second.locator('[data-role=panel]')).not_to_be_visible()
+ expect(first.locator('[data-role=panel]')).to_be_visible()
+ assert first.locator('.session-header').evaluate('node => node.firstElementChild.dataset.role') == 'toggle'
+ expect(first.locator('[data-role=toggle]')).to_have_text('Hide')
+ expect(page.get_by_role('radio', name='Prediction')).to_be_checked()
+ expect(page.locator('#qIdxPos0')).to_have_value('-3')
+ expect(page.locator('#tIdxPos0')).to_have_value('-5')
+ expect(page.locator('#qShape')).to_have_value('1 0.2\n2 0.3')
+ expect(page.locator('#temperature')).to_have_value('24')
+ expect(page.locator('#output-options input[value="qMinE"]')).to_be_checked()
+ expect(page.locator('#output-options input[value="detailed"]')).not_to_be_checked()
+ expect(first.locator('[data-role=search]')).to_have_value('no match')
+ expect(first.locator('[data-role=visible-columns] input[value="start1"]')).not_to_be_checked()
+ assert first.locator('[data-role=diagnostic-text]').text_content() == first_diagnostics
+ with page.expect_download() as restored:
+ page.locator('#store-parameters').click()
+ assert Path(restored.value.path()).read_text() == first_archive['parameter.json']
+ first.locator('[data-role=search]').fill('')
+ expect(first.locator('tbody tr')).to_have_count(3)
+ first.locator('tbody tr').first.get_by_role('button', name='Visualize').click()
+ first_url = first.locator('[data-role=varri-frame]').get_attribute('src')
+ assert parse_qs(urlparse(first_url).query)['startIndex1'] == ['-5']
+
+ # A pending file read must not overwrite a selected session's snapshot.
+ page.evaluate(r"""() => { window.originalFileText = File.prototype.text; File.prototype.text = function() { return new Promise(resolve => { window.finishRead = () => resolve('>late\nGGGAGGAGGG\n'); }); }; }""")
+ page.locator('#query-upload').set_input_files({'name': 'late.fa', 'mimeType': 'text/plain', 'buffer': b'>late\nGGGAGGAGGG\n'})
+ second.locator('[data-role=toggle]').click()
+ page.evaluate('() => { File.prototype.text = window.originalFileText; window.finishRead(); }')
+ expect(page.locator('#query')).to_have_value('>query\nGGGAGGAGGG\n')
+ expect(page.locator('#qShape')).to_have_value('')
+ expect(page.locator('#temperature')).to_have_value('42')
+ expect(page.get_by_role('radio', name='Evaluation')).to_be_checked()
+ expect(page.locator('#rri')).to_have_value(second_settings['parameters']['rri'])
+ expect(page.locator('#output-options input[value="detailed"]')).to_be_checked()
+ assert first.locator('[data-role=varri-frame]').get_attribute('src') is None
+ with page.expect_download() as restored:
+ page.locator('#store-parameters').click()
+ assert Path(restored.value.path()).read_text() == second_archive['parameter.json']
+ assert download_archive(first)['intarna-interactions.csv'] == first_csv
+
+ # Names are literal text and cannot introduce markup, duplicate IDs or paths.
+ first.get_by_label('Session name', exact=True).fill(' / β')
+ first.get_by_label('Session name', exact=True).press('Enter')
+ expect(page.locator('#temperature')).to_have_value('24')
+ expect(first.locator('[data-role=panel]')).to_be_visible()
+ assert first.locator('img').count() == 0
+ with page.expect_download() as renamed:
+ first.locator('[data-role=download-zip]').click()
+ assert '/' not in renamed.value.suggested_filename
+ assert '\\' not in renamed.value.suggested_filename
+ ids = page.locator('[id]').evaluate_all('nodes => nodes.map(node => node.id)')
+ assert len(ids) == len(set(ids))
+ first.get_by_label('Session name', exact=True).fill('')
+ first.get_by_label('Session name', exact=True).press('Enter')
+ expect(first.get_by_label('Session name', exact=True)).to_have_value('Prediction 1')
+ first.get_by_label('Session name', exact=True).fill('Cool duplex — 24 °C')
+ first.get_by_label('Session name', exact=True).press('Enter')
+ if args.screenshot:
+ page.screenshot(path=args.screenshot, full_page=True)
+ page.set_viewport_size({'width': 390, 'height': 844})
+ assert page.evaluate('document.documentElement.scrollWidth <= innerWidth')
+ for session in [first, second]:
+ expect(session.locator('[data-role=download-zip]')).to_be_visible()
+ expect(session.locator('[data-role=delete]')).to_be_visible()
+ page.set_viewport_size({'width': 1440, 'height': 1100})
+
+ # Delete releases only that output; current inputs and other sessions survive.
+ second.locator('[data-role=toggle]').click()
+ first.locator('[data-role=delete]').click()
+ expect(sessions).to_have_count(1)
+ expect(page.locator('#temperature')).to_have_value('42')
+ assert download_archive(second) == second_archive
+ second.locator('[data-role=delete]').click()
+ expect(sessions).to_have_count(0)
+ expect(compute).to_be_focused()
+ expect(page.locator('#temperature')).to_have_value('42')
+ predict()
+ assert active.get_attribute('data-session-id') not in [first_id, second_id]
+ assert download_archive() == second_archive
+ page.reload()
+ page.wait_for_load_state('networkidle')
+ expect(sessions).to_have_count(0)
assert errors == [], errors
browser.close()
-print(f'{args.browser}: real WASM, groups/defaults/versions, prediction/evaluation, FASTA/coordinates, tabs/keyboard, visualization URLs/selection, ZIP replay contents, columns/reports, mobile and abort/recovery passed')
+print(f'{args.browser}: real WASM, groups/defaults/versions, prediction/evaluation, FASTA/coordinates, tabs/keyboard, visualization URLs/selection, ZIP replay contents, columns/reports, mobile, abort/recovery and retained session lifecycle passed')
diff --git a/src/bin/CommandLineParsing.cpp b/src/bin/CommandLineParsing.cpp
index 6650cde4..8b13c23e 100644
--- a/src/bin/CommandLineParsing.cpp
+++ b/src/bin/CommandLineParsing.cpp
@@ -1079,7 +1079,7 @@ CommandLineParsing::CommandLineParsing( const Personality personality )
("personality", value(&(personalityParamValue))
, "IntaRNA personality to be used, which defines default values, available program arguments and tool behavior")
("parameterFile", value(&(configFileName))
- , "file from where to read additional command line arguments")
+ , "legacy key=value or JSON parameter file (auto-detected); JSON can embed text input files")
("help,h", "show the help page for basic parameters")
("fullhelp", "show the extended help page for all available parameters")
;
@@ -1142,7 +1142,7 @@ parse(int argc, char** argv)
LOG(ERROR) <<"Parsing of 'parameterFile' : could not open file '"<(paramfile, opts_cmdline_all), vm);
+ store( parameterFile.parse(paramfile, opts_cmdline_all), vm);
}
} catch (std::exception & ex) {
LOG(ERROR) <<"error while parsing of 'parameterFile="<
#include
+#include "ParameterFile.h"
+
#include
#include
#include
@@ -814,6 +816,9 @@ class CommandLineParsing {
//! (optional) file name for input parameter configuration file
std::string configFileName;
+ //! owns embedded parameter-file inputs until all predictions finish
+ ParameterFile parameterFile;
+
//! the vienna energy parameter handler initialized by #parse()
mutable VrnaHandler vrnaHandler;
diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am
index db72af6f..1edf62a0 100644
--- a/src/bin/Makefile.am
+++ b/src/bin/Makefile.am
@@ -29,7 +29,12 @@ nodist_IntaRNA_SOURCES = \
# intaRNA sources
IntaRNA_SOURCES = IntaRNA.cpp \
CommandLineParsing.h \
- CommandLineParsing.cpp
+ CommandLineParsing.cpp \
+ ParameterFile.h \
+ ParameterFile.cpp \
+ nlohmann/json.hpp
+
+EXTRA_DIST = nlohmann/LICENSE.MIT nlohmann/README.md
IntaRNA_LDADD = $(top_builddir)/src/IntaRNA/libIntaRNA.a \
$(top_builddir)/src/libeasylogging.a
diff --git a/src/bin/ParameterFile.cpp b/src/bin/ParameterFile.cpp
new file mode 100644
index 00000000..f7f8443c
--- /dev/null
+++ b/src/bin/ParameterFile.cpp
@@ -0,0 +1,162 @@
+#include "ParameterFile.h"
+#include "nlohmann/json.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+using Json = nlohmann::json;
+using namespace boost::program_options;
+
+// Read once, remove a UTF-8 BOM, then detect JSON independently of the suffix.
+std::string readText(std::istream& input)
+{
+ std::string text{std::istreambuf_iterator(input), {}};
+ if (input.bad()) throw std::runtime_error("Could not read parameter file");
+ if (text.starts_with("\xef\xbb\xbf")) text.erase(0, 3);
+ return text;
+}
+
+bool isJson(const std::string& text)
+{
+ const auto first = text.find_first_not_of(" \t\r\n");
+ return first != std::string::npos && (text[first] == '{' || text[first] == '[');
+}
+
+Json readJson(const std::string& text)
+{
+ // Reject duplicate keys and excessive nesting rather than silently replacing
+ // settings or consuming an unbounded call stack on malformed input.
+ std::vector> keys;
+ auto callback = [&keys](int depth, Json::parse_event_t event, Json& value) {
+ if (depth > 32) throw std::runtime_error("JSON parameter nesting exceeds 32 levels");
+ if (event == Json::parse_event_t::object_start) keys.emplace_back();
+ if (event == Json::parse_event_t::key && !keys.back().insert(value.get()).second)
+ throw std::runtime_error("Duplicate JSON key: " + value.get());
+ if (event == Json::parse_event_t::object_end) keys.pop_back();
+ return true;
+ };
+ Json document = Json::parse(text, callback);
+ if (!document.is_object()) throw std::runtime_error("JSON parameter file must be an object");
+ for (const auto& [key, value] : document.items()) {
+ if (key != "parameters" && key != "schema" && key != "_comment" && key != "gui")
+ throw std::runtime_error("Unknown JSON document field: " + key);
+ }
+ if (document.value("schema", std::string()) != "IntaRNA-parameters-v1")
+ throw std::runtime_error("Expected schema IntaRNA-parameters-v1");
+ if (!document.contains("parameters") || !document["parameters"].is_object())
+ throw std::runtime_error("JSON parameters must be an object");
+ return document["parameters"];
+}
+
+std::string scalar(const Json& value)
+{
+ if (value.is_string()) {
+ const auto text = value.get();
+ if (text.find('\0') != std::string::npos) throw std::runtime_error("NUL in JSON parameter text");
+ return text;
+ }
+ if (value.is_boolean() || value.is_number()) return value.dump();
+ throw std::runtime_error("JSON parameter values must be strings, numbers, booleans or embedded text");
+}
+}
+
+ParameterFile::~ParameterFile()
+{
+ // Cleanup must also succeed during exception unwinding and after failed input.
+ if (!directory.empty()) {
+ boost::system::error_code ignored;
+ boost::filesystem::remove_all(directory, ignored);
+ }
+}
+
+std::string ParameterFile::writeText(const std::string& name, const std::string& text)
+{
+ // Never interpret embedded data or JSON keys as filesystem paths.
+ if (directory.empty()) {
+ const auto path = boost::filesystem::temp_directory_path()
+ / boost::filesystem::unique_path("intarna-json-%%%%-%%%%-%%%%-%%%%");
+ if (!boost::filesystem::create_directory(path)) throw std::runtime_error("Could not create embedded-input directory");
+ directory = path.string();
+ boost::filesystem::permissions(path, boost::filesystem::owner_all);
+ }
+ const auto path = (boost::filesystem::path(directory) / name).string();
+ std::ofstream file(path, std::ios::binary);
+ file.exceptions(std::ios::badbit | std::ios::failbit);
+ file.write(text.data(), text.size());
+ file.close();
+ return path;
+}
+
+boost::program_options::parsed_options
+ParameterFile::parse(std::istream& input, const boost::program_options::options_description& options)
+{
+ const auto text = readText(input);
+ if (!isJson(text)) {
+ std::istringstream legacy(text);
+ return boost::program_options::parse_config_file(legacy, options);
+ }
+ const auto parameters = readJson(text);
+ boost::program_options::parsed_options parsed(&options);
+ const std::set fileInputs = {"query", "target", "qAccFile", "tAccFile", "qShape", "tShape", "energyVRNA"};
+ for (const auto& [key, value] : parameters.items()) {
+ // Reuse Program_options type conversion and validation without converting
+ // through config-file syntax (which would reinterpret # or newlines).
+ const auto* definition = options.find_nothrow(key, false);
+ if (!definition || definition->long_name() != key || key == "parameterFile")
+ throw std::runtime_error("Unknown or recursive JSON parameter: " + key);
+ auto append = [&](const Json& item) {
+ boost::program_options::option option;
+ option.string_key = key;
+ if (definition->semantic()->max_tokens() == 0) {
+ if (!item.is_boolean()) throw std::runtime_error(key + " requires a JSON boolean");
+ if (!item.get()) return;
+ } else if (item.is_object()) {
+ if (!fileInputs.contains(key)) throw std::runtime_error(key + " does not accept embedded files");
+ if (item.size() == 1 && item.contains("text") && item["text"].is_string()) {
+ option.value.push_back(writeText(key + ".txt", scalar(item["text"])));
+ } else if ((key == "qAccFile" || key == "tAccFile") && item.size() == 1 && item.contains("texts") && item["texts"].is_array() && !item["texts"].empty()) {
+ // Multi-sequence accessibility follows the existing -sN naming
+ // convention; indices refer to original FASTA record numbers.
+ const auto& texts = item["texts"];
+ for (size_t i = 0; i < texts.size(); ++i) {
+ if (!texts[i].is_string()) throw std::runtime_error(key + ": texts must contain strings");
+ writeText(key + "-s" + std::to_string(i + 1) + ".txt", scalar(texts[i]));
+ }
+ option.value.push_back((boost::filesystem::path(directory) / (key + ".txt")).string());
+ } else throw std::runtime_error(key + ": expected {\"text\":\"...\"} (or accessibility {\"texts\":[...]})");
+ } else option.value.push_back(scalar(item));
+ parsed.options.push_back(std::move(option));
+ };
+ if (value.is_array()) {
+ if (!definition->semantic()->is_composing() || value.empty()) throw std::runtime_error(key + " does not accept an empty or repeated-value array");
+ for (const auto& item : value) append(item);
+ } else append(value);
+ }
+ return parsed;
+}
+
+std::string ParameterFile::personality(const std::string& filename)
+{
+ std::ifstream input(filename, std::ios::binary);
+ if (!input) throw std::runtime_error("Could not open parameter file: " + filename);
+ const auto text = readText(input);
+ if (isJson(text)) {
+ const auto parameters = readJson(text);
+ if (!parameters.contains("personality")) return {};
+ if (!parameters["personality"].is_string()) throw std::runtime_error("personality must be a string");
+ return scalar(parameters["personality"]);
+ }
+ // Legacy parameter files receive the same personality/default precedence.
+ boost::program_options::options_description options;
+ options.add_options()("personality", boost::program_options::value());
+ std::istringstream legacy(text);
+ boost::program_options::variables_map values;
+ store(boost::program_options::parse_config_file(legacy, options, true), values);
+ return values.count("personality") ? values["personality"].as() : "";
+}
diff --git a/src/bin/ParameterFile.h b/src/bin/ParameterFile.h
new file mode 100644
index 00000000..97bf13f5
--- /dev/null
+++ b/src/bin/ParameterFile.h
@@ -0,0 +1,45 @@
+#ifndef INTARNA_PARAMETERFILE_H_
+#define INTARNA_PARAMETERFILE_H_
+
+#include
+#include
+#include
+
+/** Reads legacy or JSON CLI parameters and owns any embedded input files. */
+class ParameterFile {
+public:
+ ParameterFile();
+ ~ParameterFile();
+ ParameterFile(const ParameterFile&) = delete;
+ ParameterFile& operator=(const ParameterFile&) = delete;
+
+ /**
+ * Parse parameters through the existing option definitions. Embedded text
+ * lives in private temporary files until this object is destroyed.
+ * @param input parameter file stream (format detected from its contents)
+ * @param options supported CLI options, including their validators
+ * @return parsed options for boost::program_options::store
+ * @throws std::exception on malformed parameters or failed file operations
+ */
+ boost::program_options::parsed_options
+ parse(std::istream& input, const boost::program_options::options_description& options);
+
+ /**
+ * Read a file's personality before constructing personality-specific defaults.
+ * @param filename legacy or JSON parameter file
+ * @return personality name, or empty if not specified
+ */
+ static std::string personality(const std::string& filename);
+
+private:
+ //! private directory containing generated filenames only; never user paths
+ std::string directory;
+
+ /** Materialize text with a generated basename; return its absolute path. */
+ std::string writeText(const std::string& name, const std::string& text);
+};
+
+inline
+ParameterFile::ParameterFile() = default;
+
+#endif
diff --git a/src/bin/nlohmann/LICENSE.MIT b/src/bin/nlohmann/LICENSE.MIT
new file mode 100644
index 00000000..a1dacc8d
--- /dev/null
+++ b/src/bin/nlohmann/LICENSE.MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2013-2025 Niels Lohmann
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/bin/nlohmann/README.md b/src/bin/nlohmann/README.md
new file mode 100644
index 00000000..b4d4fac5
--- /dev/null
+++ b/src/bin/nlohmann/README.md
@@ -0,0 +1,12 @@
+# nlohmann/json 3.12.0
+
+Unmodified single header and MIT license from
+https://github.com/nlohmann/json/tree/v3.12.0.
+
+- `single_include/nlohmann/json.hpp`: SHA-256 `aaf127c04cb31c406e5b04a63f1ae89369fccde6d8fa7cdda1ed4f32dfc5de63`
+- `LICENSE.MIT`: SHA-256 `46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603`
+
+Only `ParameterFile.cpp` includes the header. No JSON types enter public library
+headers. See [the format and dependency decision](../../../doc/json-parameters.md).
+Update the header and license together from a tagged upstream release, record
+checksums here, and run the native JSON, WASM and GUI round-trip tests.
diff --git a/src/bin/nlohmann/json.hpp b/src/bin/nlohmann/json.hpp
new file mode 100644
index 00000000..82d69f7c
--- /dev/null
+++ b/src/bin/nlohmann/json.hpp
@@ -0,0 +1,25526 @@
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+/****************************************************************************\
+ * Note on documentation: The source files contain links to the online *
+ * documentation of the public API at https://json.nlohmann.me. This URL *
+ * contains the most recent documentation and should also be applicable to *
+ * previous versions; documentation for deprecated functions is not *
+ * removed, but marked deprecated. See "Generate documentation" section in *
+ * file docs/README.md. *
+\****************************************************************************/
+
+#ifndef INCLUDE_NLOHMANN_JSON_HPP_
+#define INCLUDE_NLOHMANN_JSON_HPP_
+
+#include // all_of, find, for_each
+#include // nullptr_t, ptrdiff_t, size_t
+#include // hash, less
+#include // initializer_list
+#ifndef JSON_NO_IO
+ #include // istream, ostream
+#endif // JSON_NO_IO
+#include // random_access_iterator_tag
+#include // unique_ptr
+#include // string, stoi, to_string
+#include // declval, forward, move, pair, swap
+#include // vector
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+// This file contains all macro definitions affecting or depending on the ABI
+
+#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
+ #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
+ #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
+ #warning "Already included a different version of the library!"
+ #endif
+ #endif
+#endif
+
+#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
+#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
+#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
+
+#ifndef JSON_DIAGNOSTICS
+ #define JSON_DIAGNOSTICS 0
+#endif
+
+#ifndef JSON_DIAGNOSTIC_POSITIONS
+ #define JSON_DIAGNOSTIC_POSITIONS 0
+#endif
+
+#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+ #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
+#endif
+
+#if JSON_DIAGNOSTICS
+ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
+#else
+ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
+#endif
+
+#if JSON_DIAGNOSTIC_POSITIONS
+ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
+#else
+ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
+#endif
+
+#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+ #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
+#else
+ #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
+ #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
+#endif
+
+// Construct the namespace ABI tags component
+#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
+#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
+ NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
+
+#define NLOHMANN_JSON_ABI_TAGS \
+ NLOHMANN_JSON_ABI_TAGS_CONCAT( \
+ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
+ NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
+ NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
+
+// Construct the namespace version component
+#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+ _v ## major ## _ ## minor ## _ ## patch
+#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
+ NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
+
+#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
+#define NLOHMANN_JSON_NAMESPACE_VERSION
+#else
+#define NLOHMANN_JSON_NAMESPACE_VERSION \
+ NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
+ NLOHMANN_JSON_VERSION_MINOR, \
+ NLOHMANN_JSON_VERSION_PATCH)
+#endif
+
+// Combine namespace components
+#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
+#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
+ NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
+
+#ifndef NLOHMANN_JSON_NAMESPACE
+#define NLOHMANN_JSON_NAMESPACE \
+ nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
+ NLOHMANN_JSON_ABI_TAGS, \
+ NLOHMANN_JSON_NAMESPACE_VERSION)
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
+#define NLOHMANN_JSON_NAMESPACE_BEGIN \
+ namespace nlohmann \
+ { \
+ inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
+ NLOHMANN_JSON_ABI_TAGS, \
+ NLOHMANN_JSON_NAMESPACE_VERSION) \
+ {
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_END
+#define NLOHMANN_JSON_NAMESPACE_END \
+ } /* namespace (inline namespace) NOLINT(readability/namespace) */ \
+ } // namespace nlohmann
+#endif
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // transform
+#include // array
+#include // forward_list
+#include // inserter, front_inserter, end
+#include // map
+#ifdef JSON_HAS_CPP_17
+ #include // optional
+#endif
+#include // string
+#include // tuple, make_tuple
+#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
+#include // unordered_map
+#include // pair, declval
+#include // valarray
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // nullptr_t
+#include // exception
+#if JSON_DIAGNOSTICS
+ #include // accumulate
+#endif
+#include // runtime_error
+#include // to_string
+#include // vector
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // array
+#include // size_t
+#include // uint8_t
+#include // string
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // declval, pair
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+// #include
+
+
+NLOHMANN_JSON_NAMESPACE_BEGIN
+namespace detail
+{
+
+template struct make_void
+{
+ using type = void;
+};
+template using void_t = typename make_void::type;
+
+} // namespace detail
+NLOHMANN_JSON_NAMESPACE_END
+
+
+NLOHMANN_JSON_NAMESPACE_BEGIN
+namespace detail
+{
+
+// https://en.cppreference.com/w/cpp/experimental/is_detected
+struct nonesuch
+{
+ nonesuch() = delete;
+ ~nonesuch() = delete;
+ nonesuch(nonesuch const&) = delete;
+ nonesuch(nonesuch const&&) = delete;
+ void operator=(nonesuch const&) = delete;
+ void operator=(nonesuch&&) = delete;
+};
+
+template class Op,
+ class... Args>
+struct detector
+{
+ using value_t = std::false_type;
+ using type = Default;
+};
+
+template class Op, class... Args>
+struct detector>, Op, Args...>
+{
+ using value_t = std::true_type;
+ using type = Op;
+};
+
+template class Op, class... Args>
+using is_detected = typename detector::value_t;
+
+template class Op, class... Args>
+struct is_detected_lazy : is_detected { };
+
+template class Op, class... Args>
+using detected_t = typename detector::type;
+
+template class Op, class... Args>
+using detected_or = detector;
+
+template class Op, class... Args>
+using detected_or_t = typename detected_or::type;
+
+template class Op, class... Args>
+using is_detected_exact = std::is_same>;
+
+template class Op, class... Args>
+using is_detected_convertible =
+ std::is_convertible, To>;
+
+} // namespace detail
+NLOHMANN_JSON_NAMESPACE_END
+
+// #include
+
+
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-FileCopyrightText: 2016 - 2021 Evan Nemerson
+// SPDX-License-Identifier: MIT
+
+/* Hedley - https://nemequ.github.io/hedley
+ * Created by Evan Nemerson
+ */
+
+#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15)
+#if defined(JSON_HEDLEY_VERSION)
+ #undef JSON_HEDLEY_VERSION
+#endif
+#define JSON_HEDLEY_VERSION 15
+
+#if defined(JSON_HEDLEY_STRINGIFY_EX)
+ #undef JSON_HEDLEY_STRINGIFY_EX
+#endif
+#define JSON_HEDLEY_STRINGIFY_EX(x) #x
+
+#if defined(JSON_HEDLEY_STRINGIFY)
+ #undef JSON_HEDLEY_STRINGIFY
+#endif
+#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x)
+
+#if defined(JSON_HEDLEY_CONCAT_EX)
+ #undef JSON_HEDLEY_CONCAT_EX
+#endif
+#define JSON_HEDLEY_CONCAT_EX(a,b) a##b
+
+#if defined(JSON_HEDLEY_CONCAT)
+ #undef JSON_HEDLEY_CONCAT
+#endif
+#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b)
+
+#if defined(JSON_HEDLEY_CONCAT3_EX)
+ #undef JSON_HEDLEY_CONCAT3_EX
+#endif
+#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c
+
+#if defined(JSON_HEDLEY_CONCAT3)
+ #undef JSON_HEDLEY_CONCAT3
+#endif
+#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c)
+
+#if defined(JSON_HEDLEY_VERSION_ENCODE)
+ #undef JSON_HEDLEY_VERSION_ENCODE
+#endif
+#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision))
+
+#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR)
+ #undef JSON_HEDLEY_VERSION_DECODE_MAJOR
+#endif
+#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000)
+
+#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR)
+ #undef JSON_HEDLEY_VERSION_DECODE_MINOR
+#endif
+#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000)
+
+#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION)
+ #undef JSON_HEDLEY_VERSION_DECODE_REVISION
+#endif
+#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000)
+
+#if defined(JSON_HEDLEY_GNUC_VERSION)
+ #undef JSON_HEDLEY_GNUC_VERSION
+#endif
+#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__)
+ #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#elif defined(__GNUC__)
+ #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0)
+#endif
+
+#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK)
+ #undef JSON_HEDLEY_GNUC_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_GNUC_VERSION)
+ #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_MSVC_VERSION)
+ #undef JSON_HEDLEY_MSVC_VERSION
+#endif
+#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL)
+ #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100)
+#elif defined(_MSC_FULL_VER) && !defined(__ICL)
+ #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10)
+#elif defined(_MSC_VER) && !defined(__ICL)
+ #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0)
+#endif
+
+#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK)
+ #undef JSON_HEDLEY_MSVC_VERSION_CHECK
+#endif
+#if !defined(JSON_HEDLEY_MSVC_VERSION)
+ #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0)
+#elif defined(_MSC_VER) && (_MSC_VER >= 1400)
+ #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch)))
+#elif defined(_MSC_VER) && (_MSC_VER >= 1200)
+ #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch)))
+#else
+ #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor)))
+#endif
+
+#if defined(JSON_HEDLEY_INTEL_VERSION)
+ #undef JSON_HEDLEY_INTEL_VERSION
+#endif
+#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL)
+ #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE)
+#elif defined(__INTEL_COMPILER) && !defined(__ICL)
+ #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0)
+#endif
+
+#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK)
+ #undef JSON_HEDLEY_INTEL_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_INTEL_VERSION)
+ #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_INTEL_CL_VERSION)
+ #undef JSON_HEDLEY_INTEL_CL_VERSION
+#endif
+#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL)
+ #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0)
+#endif
+
+#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK)
+ #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_INTEL_CL_VERSION)
+ #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_PGI_VERSION)
+ #undef JSON_HEDLEY_PGI_VERSION
+#endif
+#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__)
+ #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__)
+#endif
+
+#if defined(JSON_HEDLEY_PGI_VERSION_CHECK)
+ #undef JSON_HEDLEY_PGI_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_PGI_VERSION)
+ #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_SUNPRO_VERSION)
+ #undef JSON_HEDLEY_SUNPRO_VERSION
+#endif
+#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000)
+ #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10)
+#elif defined(__SUNPRO_C)
+ #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf)
+#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000)
+ #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10)
+#elif defined(__SUNPRO_CC)
+ #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf)
+#endif
+
+#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK)
+ #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_SUNPRO_VERSION)
+ #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION)
+ #undef JSON_HEDLEY_EMSCRIPTEN_VERSION
+#endif
+#if defined(__EMSCRIPTEN__)
+ #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__)
+#endif
+
+#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK)
+ #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION)
+ #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_ARM_VERSION)
+ #undef JSON_HEDLEY_ARM_VERSION
+#endif
+#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION)
+ #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100)
+#elif defined(__CC_ARM) && defined(__ARMCC_VERSION)
+ #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100)
+#endif
+
+#if defined(JSON_HEDLEY_ARM_VERSION_CHECK)
+ #undef JSON_HEDLEY_ARM_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_ARM_VERSION)
+ #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_IBM_VERSION)
+ #undef JSON_HEDLEY_IBM_VERSION
+#endif
+#if defined(__ibmxl__)
+ #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__)
+#elif defined(__xlC__) && defined(__xlC_ver__)
+ #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff)
+#elif defined(__xlC__)
+ #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0)
+#endif
+
+#if defined(JSON_HEDLEY_IBM_VERSION_CHECK)
+ #undef JSON_HEDLEY_IBM_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_IBM_VERSION)
+ #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_TI_VERSION)
+ #undef JSON_HEDLEY_TI_VERSION
+#endif
+#if \
+ defined(__TI_COMPILER_VERSION__) && \
+ ( \
+ defined(__TMS470__) || defined(__TI_ARM__) || \
+ defined(__MSP430__) || \
+ defined(__TMS320C2000__) \
+ )
+#if (__TI_COMPILER_VERSION__ >= 16000000)
+ #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
+#endif
+#endif
+
+#if defined(JSON_HEDLEY_TI_VERSION_CHECK)
+ #undef JSON_HEDLEY_TI_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_TI_VERSION)
+ #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_TI_CL2000_VERSION)
+ #undef JSON_HEDLEY_TI_CL2000_VERSION
+#endif
+#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__)
+ #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
+#endif
+
+#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK)
+ #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_TI_CL2000_VERSION)
+ #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_TI_CL430_VERSION)
+ #undef JSON_HEDLEY_TI_CL430_VERSION
+#endif
+#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__)
+ #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
+#endif
+
+#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK)
+ #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_TI_CL430_VERSION)
+ #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_TI_ARMCL_VERSION)
+ #undef JSON_HEDLEY_TI_ARMCL_VERSION
+#endif
+#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__))
+ #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
+#endif
+
+#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK)
+ #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_TI_ARMCL_VERSION)
+ #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_TI_CL6X_VERSION)
+ #undef JSON_HEDLEY_TI_CL6X_VERSION
+#endif
+#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__)
+ #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
+#endif
+
+#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK)
+ #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_TI_CL6X_VERSION)
+ #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_TI_CL7X_VERSION)
+ #undef JSON_HEDLEY_TI_CL7X_VERSION
+#endif
+#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__)
+ #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
+#endif
+
+#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK)
+ #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_TI_CL7X_VERSION)
+ #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_TI_CLPRU_VERSION)
+ #undef JSON_HEDLEY_TI_CLPRU_VERSION
+#endif
+#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__)
+ #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
+#endif
+
+#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK)
+ #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_TI_CLPRU_VERSION)
+ #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_CRAY_VERSION)
+ #undef JSON_HEDLEY_CRAY_VERSION
+#endif
+#if defined(_CRAYC)
+ #if defined(_RELEASE_PATCHLEVEL)
+ #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL)
+ #else
+ #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0)
+ #endif
+#endif
+
+#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK)
+ #undef JSON_HEDLEY_CRAY_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_CRAY_VERSION)
+ #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_IAR_VERSION)
+ #undef JSON_HEDLEY_IAR_VERSION
+#endif
+#if defined(__IAR_SYSTEMS_ICC__)
+ #if __VER__ > 1000
+ #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000))
+ #else
+ #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0)
+ #endif
+#endif
+
+#if defined(JSON_HEDLEY_IAR_VERSION_CHECK)
+ #undef JSON_HEDLEY_IAR_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_IAR_VERSION)
+ #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_TINYC_VERSION)
+ #undef JSON_HEDLEY_TINYC_VERSION
+#endif
+#if defined(__TINYC__)
+ #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100)
+#endif
+
+#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK)
+ #undef JSON_HEDLEY_TINYC_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_TINYC_VERSION)
+ #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_DMC_VERSION)
+ #undef JSON_HEDLEY_DMC_VERSION
+#endif
+#if defined(__DMC__)
+ #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf)
+#endif
+
+#if defined(JSON_HEDLEY_DMC_VERSION_CHECK)
+ #undef JSON_HEDLEY_DMC_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_DMC_VERSION)
+ #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_COMPCERT_VERSION)
+ #undef JSON_HEDLEY_COMPCERT_VERSION
+#endif
+#if defined(__COMPCERT_VERSION__)
+ #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100)
+#endif
+
+#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK)
+ #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_COMPCERT_VERSION)
+ #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_PELLES_VERSION)
+ #undef JSON_HEDLEY_PELLES_VERSION
+#endif
+#if defined(__POCC__)
+ #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0)
+#endif
+
+#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK)
+ #undef JSON_HEDLEY_PELLES_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_PELLES_VERSION)
+ #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_MCST_LCC_VERSION)
+ #undef JSON_HEDLEY_MCST_LCC_VERSION
+#endif
+#if defined(__LCC__) && defined(__LCC_MINOR__)
+ #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__)
+#endif
+
+#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK)
+ #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_MCST_LCC_VERSION)
+ #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_GCC_VERSION)
+ #undef JSON_HEDLEY_GCC_VERSION
+#endif
+#if \
+ defined(JSON_HEDLEY_GNUC_VERSION) && \
+ !defined(__clang__) && \
+ !defined(JSON_HEDLEY_INTEL_VERSION) && \
+ !defined(JSON_HEDLEY_PGI_VERSION) && \
+ !defined(JSON_HEDLEY_ARM_VERSION) && \
+ !defined(JSON_HEDLEY_CRAY_VERSION) && \
+ !defined(JSON_HEDLEY_TI_VERSION) && \
+ !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \
+ !defined(JSON_HEDLEY_TI_CL430_VERSION) && \
+ !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \
+ !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \
+ !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \
+ !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \
+ !defined(__COMPCERT__) && \
+ !defined(JSON_HEDLEY_MCST_LCC_VERSION)
+ #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION
+#endif
+
+#if defined(JSON_HEDLEY_GCC_VERSION_CHECK)
+ #undef JSON_HEDLEY_GCC_VERSION_CHECK
+#endif
+#if defined(JSON_HEDLEY_GCC_VERSION)
+ #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+ #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(JSON_HEDLEY_HAS_ATTRIBUTE)
+ #undef JSON_HEDLEY_HAS_ATTRIBUTE
+#endif
+#if \
+ defined(__has_attribute) && \
+ ( \
+ (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \
+ )
+# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute)
+#else
+# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0)
+#endif
+
+#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE)
+ #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE
+#endif
+#if defined(__has_attribute)
+ #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)
+#else
+ #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE)
+ #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE
+#endif
+#if defined(__has_attribute)
+ #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)
+#else
+ #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE)
+ #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE
+#endif
+#if \
+ defined(__has_cpp_attribute) && \
+ defined(__cplusplus) && \
+ (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0))
+ #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute)
+#else
+ #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0)
+#endif
+
+#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS)
+ #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS
+#endif
+#if !defined(__cplusplus) || !defined(__has_cpp_attribute)
+ #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)
+#elif \
+ !defined(JSON_HEDLEY_PGI_VERSION) && \
+ !defined(JSON_HEDLEY_IAR_VERSION) && \
+ (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \
+ (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0))
+ #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute)
+#else
+ #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)
+#endif
+
+#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE)
+ #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE
+#endif
+#if defined(__has_cpp_attribute) && defined(__cplusplus)
+ #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)
+#else
+ #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE)
+ #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE
+#endif
+#if defined(__has_cpp_attribute) && defined(__cplusplus)
+ #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)
+#else
+ #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_HAS_BUILTIN)
+ #undef JSON_HEDLEY_HAS_BUILTIN
+#endif
+#if defined(__has_builtin)
+ #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin)
+#else
+ #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0)
+#endif
+
+#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN)
+ #undef JSON_HEDLEY_GNUC_HAS_BUILTIN
+#endif
+#if defined(__has_builtin)
+ #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)
+#else
+ #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN)
+ #undef JSON_HEDLEY_GCC_HAS_BUILTIN
+#endif
+#if defined(__has_builtin)
+ #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)
+#else
+ #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_HAS_FEATURE)
+ #undef JSON_HEDLEY_HAS_FEATURE
+#endif
+#if defined(__has_feature)
+ #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature)
+#else
+ #define JSON_HEDLEY_HAS_FEATURE(feature) (0)
+#endif
+
+#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE)
+ #undef JSON_HEDLEY_GNUC_HAS_FEATURE
+#endif
+#if defined(__has_feature)
+ #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)
+#else
+ #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_GCC_HAS_FEATURE)
+ #undef JSON_HEDLEY_GCC_HAS_FEATURE
+#endif
+#if defined(__has_feature)
+ #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)
+#else
+ #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_HAS_EXTENSION)
+ #undef JSON_HEDLEY_HAS_EXTENSION
+#endif
+#if defined(__has_extension)
+ #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension)
+#else
+ #define JSON_HEDLEY_HAS_EXTENSION(extension) (0)
+#endif
+
+#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION)
+ #undef JSON_HEDLEY_GNUC_HAS_EXTENSION
+#endif
+#if defined(__has_extension)
+ #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)
+#else
+ #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION)
+ #undef JSON_HEDLEY_GCC_HAS_EXTENSION
+#endif
+#if defined(__has_extension)
+ #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)
+#else
+ #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE)
+ #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE
+#endif
+#if defined(__has_declspec_attribute)
+ #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute)
+#else
+ #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0)
+#endif
+
+#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE)
+ #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE
+#endif
+#if defined(__has_declspec_attribute)
+ #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)
+#else
+ #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE)
+ #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE
+#endif
+#if defined(__has_declspec_attribute)
+ #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)
+#else
+ #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_HAS_WARNING)
+ #undef JSON_HEDLEY_HAS_WARNING
+#endif
+#if defined(__has_warning)
+ #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning)
+#else
+ #define JSON_HEDLEY_HAS_WARNING(warning) (0)
+#endif
+
+#if defined(JSON_HEDLEY_GNUC_HAS_WARNING)
+ #undef JSON_HEDLEY_GNUC_HAS_WARNING
+#endif
+#if defined(__has_warning)
+ #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)
+#else
+ #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_GCC_HAS_WARNING)
+ #undef JSON_HEDLEY_GCC_HAS_WARNING
+#endif
+#if defined(__has_warning)
+ #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)
+#else
+ #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if \
+ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \
+ defined(__clang__) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \
+ JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \
+ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \
+ (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR))
+ #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value)
+#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)
+ #define JSON_HEDLEY_PRAGMA(value) __pragma(value)
+#else
+ #define JSON_HEDLEY_PRAGMA(value)
+#endif
+
+#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH)
+ #undef JSON_HEDLEY_DIAGNOSTIC_PUSH
+#endif
+#if defined(JSON_HEDLEY_DIAGNOSTIC_POP)
+ #undef JSON_HEDLEY_DIAGNOSTIC_POP
+#endif
+#if defined(__clang__)
+ #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push")
+ #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop")
+#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)")
+ #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)")
+#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
+ #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop")
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push))
+ #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop))
+#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push")
+ #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop")
+#elif \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push")
+ #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop")
+#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)")
+ #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)")
+#else
+ #define JSON_HEDLEY_DIAGNOSTIC_PUSH
+ #define JSON_HEDLEY_DIAGNOSTIC_POP
+#endif
+
+/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for
+ HEDLEY INTERNAL USE ONLY. API subject to change without notice. */
+#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_)
+ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_
+#endif
+#if defined(__cplusplus)
+# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat")
+# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions")
+# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions")
+# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \
+ _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \
+ _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \
+ xpr \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+# else
+# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \
+ _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \
+ xpr \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+# endif
+# else
+# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \
+ xpr \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+# endif
+# endif
+#endif
+#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x
+#endif
+
+#if defined(JSON_HEDLEY_CONST_CAST)
+ #undef JSON_HEDLEY_CONST_CAST
+#endif
+#if defined(__cplusplus)
+# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr))
+#elif \
+ JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)
+# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \
+ ((T) (expr)); \
+ JSON_HEDLEY_DIAGNOSTIC_POP \
+ }))
+#else
+# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr))
+#endif
+
+#if defined(JSON_HEDLEY_REINTERPRET_CAST)
+ #undef JSON_HEDLEY_REINTERPRET_CAST
+#endif
+#if defined(__cplusplus)
+ #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr))
+#else
+ #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr))
+#endif
+
+#if defined(JSON_HEDLEY_STATIC_CAST)
+ #undef JSON_HEDLEY_STATIC_CAST
+#endif
+#if defined(__cplusplus)
+ #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr))
+#else
+ #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr))
+#endif
+
+#if defined(JSON_HEDLEY_CPP_CAST)
+ #undef JSON_HEDLEY_CPP_CAST
+#endif
+#if defined(__cplusplus)
+# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast")
+# define JSON_HEDLEY_CPP_CAST(T, expr) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \
+ ((T) (expr)) \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0)
+# define JSON_HEDLEY_CPP_CAST(T, expr) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ _Pragma("diag_suppress=Pe137") \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+# else
+# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr))
+# endif
+#else
+# define JSON_HEDLEY_CPP_CAST(T, expr) (expr)
+#endif
+
+#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED)
+ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
+#endif
+#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations")
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
+#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)")
+#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786))
+#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445")
+#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444")
+#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
+#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996))
+#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444")
+#elif \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718")
+#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)")
+#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)")
+#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215")
+#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)")
+#else
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
+#endif
+
+#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS)
+ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS
+#endif
+#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas")
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"")
+#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)")
+#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161))
+#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675")
+#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"")
+#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068))
+#elif \
+ JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163")
+#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163")
+#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161")
+#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161")
+#else
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS
+#endif
+
+#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES)
+ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES
+#endif
+#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes")
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"")
+#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
+#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)")
+#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292))
+#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030))
+#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098")
+#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097")
+#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)")
+#elif \
+ JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173")
+#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097")
+#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097")
+#else
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES
+#endif
+
+#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL)
+ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL
+#endif
+#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual")
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"")
+#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)")
+#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"")
+#else
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL
+#endif
+
+#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION)
+ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION
+#endif
+#if JSON_HEDLEY_HAS_WARNING("-Wunused-function")
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"")
+#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"")
+#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505))
+#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142")
+#else
+ #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION
+#endif
+
+#if defined(JSON_HEDLEY_DEPRECATED)
+ #undef JSON_HEDLEY_DEPRECATED
+#endif
+#if defined(JSON_HEDLEY_DEPRECATED_FOR)
+ #undef JSON_HEDLEY_DEPRECATED_FOR
+#endif
+#if \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since))
+ #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement))
+#elif \
+ (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \
+ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since)))
+ #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement)))
+#elif defined(__cplusplus) && (__cplusplus >= 201402L)
+ #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]])
+ #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]])
+#elif \
+ JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \
+ JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)
+ #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__))
+ #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__))
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
+ JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated)
+ #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated)
+#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)
+ #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated")
+ #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated")
+#else
+ #define JSON_HEDLEY_DEPRECATED(since)
+ #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement)
+#endif
+
+#if defined(JSON_HEDLEY_UNAVAILABLE)
+ #undef JSON_HEDLEY_UNAVAILABLE
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since)))
+#else
+ #define JSON_HEDLEY_UNAVAILABLE(available_since)
+#endif
+
+#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT)
+ #undef JSON_HEDLEY_WARN_UNUSED_RESULT
+#endif
+#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG)
+ #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \
+ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__))
+#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L)
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]])
+#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard)
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])
+#elif defined(_Check_return_) /* SAL */
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_
+#else
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT
+ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg)
+#endif
+
+#if defined(JSON_HEDLEY_SENTINEL)
+ #undef JSON_HEDLEY_SENTINEL
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position)))
+#else
+ #define JSON_HEDLEY_SENTINEL(position)
+#endif
+
+#if defined(JSON_HEDLEY_NO_RETURN)
+ #undef JSON_HEDLEY_NO_RETURN
+#endif
+#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)
+ #define JSON_HEDLEY_NO_RETURN __noreturn
+#elif \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__))
+#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+ #define JSON_HEDLEY_NO_RETURN _Noreturn
+#elif defined(__cplusplus) && (__cplusplus >= 201103L)
+ #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]])
+#elif \
+ JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)
+ #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__))
+#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)
+ #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return")
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_NO_RETURN __declspec(noreturn)
+#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)
+ #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;")
+#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)
+ #define JSON_HEDLEY_NO_RETURN __attribute((noreturn))
+#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0)
+ #define JSON_HEDLEY_NO_RETURN __declspec(noreturn)
+#else
+ #define JSON_HEDLEY_NO_RETURN
+#endif
+
+#if defined(JSON_HEDLEY_NO_ESCAPE)
+ #undef JSON_HEDLEY_NO_ESCAPE
+#endif
+#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape)
+ #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__))
+#else
+ #define JSON_HEDLEY_NO_ESCAPE
+#endif
+
+#if defined(JSON_HEDLEY_UNREACHABLE)
+ #undef JSON_HEDLEY_UNREACHABLE
+#endif
+#if defined(JSON_HEDLEY_UNREACHABLE_RETURN)
+ #undef JSON_HEDLEY_UNREACHABLE_RETURN
+#endif
+#if defined(JSON_HEDLEY_ASSUME)
+ #undef JSON_HEDLEY_ASSUME
+#endif
+#if \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_ASSUME(expr) __assume(expr)
+#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume)
+ #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr)
+#elif \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)
+ #if defined(__cplusplus)
+ #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr)
+ #else
+ #define JSON_HEDLEY_ASSUME(expr) _nassert(expr)
+ #endif
+#endif
+#if \
+ (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \
+ JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \
+ JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable()
+#elif defined(JSON_HEDLEY_ASSUME)
+ #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0)
+#endif
+#if !defined(JSON_HEDLEY_ASSUME)
+ #if defined(JSON_HEDLEY_UNREACHABLE)
+ #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1)))
+ #else
+ #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr)
+ #endif
+#endif
+#if defined(JSON_HEDLEY_UNREACHABLE)
+ #if \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)
+ #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value))
+ #else
+ #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE()
+ #endif
+#else
+ #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value)
+#endif
+#if !defined(JSON_HEDLEY_UNREACHABLE)
+ #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0)
+#endif
+
+JSON_HEDLEY_DIAGNOSTIC_PUSH
+#if JSON_HEDLEY_HAS_WARNING("-Wpedantic")
+ #pragma clang diagnostic ignored "-Wpedantic"
+#endif
+#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus)
+ #pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#endif
+#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0)
+ #if defined(__clang__)
+ #pragma clang diagnostic ignored "-Wvariadic-macros"
+ #elif defined(JSON_HEDLEY_GCC_VERSION)
+ #pragma GCC diagnostic ignored "-Wvariadic-macros"
+ #endif
+#endif
+#if defined(JSON_HEDLEY_NON_NULL)
+ #undef JSON_HEDLEY_NON_NULL
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0)
+ #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__)))
+#else
+ #define JSON_HEDLEY_NON_NULL(...)
+#endif
+JSON_HEDLEY_DIAGNOSTIC_POP
+
+#if defined(JSON_HEDLEY_PRINTF_FORMAT)
+ #undef JSON_HEDLEY_PRINTF_FORMAT
+#endif
+#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO)
+ #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check)))
+#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO)
+ #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check)))
+#elif \
+ JSON_HEDLEY_HAS_ATTRIBUTE(format) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check)))
+#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0)
+ #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check))
+#else
+ #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check)
+#endif
+
+#if defined(JSON_HEDLEY_CONSTEXPR)
+ #undef JSON_HEDLEY_CONSTEXPR
+#endif
+#if defined(__cplusplus)
+ #if __cplusplus >= 201103L
+ #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr)
+ #endif
+#endif
+#if !defined(JSON_HEDLEY_CONSTEXPR)
+ #define JSON_HEDLEY_CONSTEXPR
+#endif
+
+#if defined(JSON_HEDLEY_PREDICT)
+ #undef JSON_HEDLEY_PREDICT
+#endif
+#if defined(JSON_HEDLEY_LIKELY)
+ #undef JSON_HEDLEY_LIKELY
+#endif
+#if defined(JSON_HEDLEY_UNLIKELY)
+ #undef JSON_HEDLEY_UNLIKELY
+#endif
+#if defined(JSON_HEDLEY_UNPREDICTABLE)
+ #undef JSON_HEDLEY_UNPREDICTABLE
+#endif
+#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable)
+ #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr))
+#endif
+#if \
+ (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability))
+# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability))
+# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability))
+# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 )
+# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 )
+#elif \
+ (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \
+ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+# define JSON_HEDLEY_PREDICT(expr, expected, probability) \
+ (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)))
+# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \
+ (__extension__ ({ \
+ double hedley_probability_ = (probability); \
+ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \
+ }))
+# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \
+ (__extension__ ({ \
+ double hedley_probability_ = (probability); \
+ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \
+ }))
+# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1)
+# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0)
+#else
+# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))
+# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr))
+# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr))
+# define JSON_HEDLEY_LIKELY(expr) (!!(expr))
+# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr))
+#endif
+#if !defined(JSON_HEDLEY_UNPREDICTABLE)
+ #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5)
+#endif
+
+#if defined(JSON_HEDLEY_MALLOC)
+ #undef JSON_HEDLEY_MALLOC
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_MALLOC __attribute__((__malloc__))
+#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)
+ #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory")
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_MALLOC __declspec(restrict)
+#else
+ #define JSON_HEDLEY_MALLOC
+#endif
+
+#if defined(JSON_HEDLEY_PURE)
+ #undef JSON_HEDLEY_PURE
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+# define JSON_HEDLEY_PURE __attribute__((__pure__))
+#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)
+# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data")
+#elif defined(__cplusplus) && \
+ ( \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \
+ )
+# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;")
+#else
+# define JSON_HEDLEY_PURE
+#endif
+
+#if defined(JSON_HEDLEY_CONST)
+ #undef JSON_HEDLEY_CONST
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(const) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_CONST __attribute__((__const__))
+#elif \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)
+ #define JSON_HEDLEY_CONST _Pragma("no_side_effect")
+#else
+ #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE
+#endif
+
+#if defined(JSON_HEDLEY_RESTRICT)
+ #undef JSON_HEDLEY_RESTRICT
+#endif
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus)
+ #define JSON_HEDLEY_RESTRICT restrict
+#elif \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \
+ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \
+ defined(__clang__) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_RESTRICT __restrict
+#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus)
+ #define JSON_HEDLEY_RESTRICT _Restrict
+#else
+ #define JSON_HEDLEY_RESTRICT
+#endif
+
+#if defined(JSON_HEDLEY_INLINE)
+ #undef JSON_HEDLEY_INLINE
+#endif
+#if \
+ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \
+ (defined(__cplusplus) && (__cplusplus >= 199711L))
+ #define JSON_HEDLEY_INLINE inline
+#elif \
+ defined(JSON_HEDLEY_GCC_VERSION) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0)
+ #define JSON_HEDLEY_INLINE __inline__
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_INLINE __inline
+#else
+ #define JSON_HEDLEY_INLINE
+#endif
+
+#if defined(JSON_HEDLEY_ALWAYS_INLINE)
+ #undef JSON_HEDLEY_ALWAYS_INLINE
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \
+ JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)
+# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+# define JSON_HEDLEY_ALWAYS_INLINE __forceinline
+#elif defined(__cplusplus) && \
+ ( \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \
+ )
+# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;")
+#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)
+# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced")
+#else
+# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE
+#endif
+
+#if defined(JSON_HEDLEY_NEVER_INLINE)
+ #undef JSON_HEDLEY_NEVER_INLINE
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \
+ JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)
+ #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__))
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline)
+#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0)
+ #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline")
+#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)
+ #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;")
+#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)
+ #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never")
+#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)
+ #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline))
+#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0)
+ #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline)
+#else
+ #define JSON_HEDLEY_NEVER_INLINE
+#endif
+
+#if defined(JSON_HEDLEY_PRIVATE)
+ #undef JSON_HEDLEY_PRIVATE
+#endif
+#if defined(JSON_HEDLEY_PUBLIC)
+ #undef JSON_HEDLEY_PUBLIC
+#endif
+#if defined(JSON_HEDLEY_IMPORT)
+ #undef JSON_HEDLEY_IMPORT
+#endif
+#if defined(_WIN32) || defined(__CYGWIN__)
+# define JSON_HEDLEY_PRIVATE
+# define JSON_HEDLEY_PUBLIC __declspec(dllexport)
+# define JSON_HEDLEY_IMPORT __declspec(dllimport)
+#else
+# if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
+ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
+ ( \
+ defined(__TI_EABI__) && \
+ ( \
+ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \
+ ) \
+ ) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden")))
+# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default")))
+# else
+# define JSON_HEDLEY_PRIVATE
+# define JSON_HEDLEY_PUBLIC
+# endif
+# define JSON_HEDLEY_IMPORT extern
+#endif
+
+#if defined(JSON_HEDLEY_NO_THROW)
+ #undef JSON_HEDLEY_NO_THROW
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__))
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0)
+ #define JSON_HEDLEY_NO_THROW __declspec(nothrow)
+#else
+ #define JSON_HEDLEY_NO_THROW
+#endif
+
+#if defined(JSON_HEDLEY_FALL_THROUGH)
+ #undef JSON_HEDLEY_FALL_THROUGH
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__))
+#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough)
+ #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]])
+#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough)
+ #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]])
+#elif defined(__fallthrough) /* SAL */
+ #define JSON_HEDLEY_FALL_THROUGH __fallthrough
+#else
+ #define JSON_HEDLEY_FALL_THROUGH
+#endif
+
+#if defined(JSON_HEDLEY_RETURNS_NON_NULL)
+ #undef JSON_HEDLEY_RETURNS_NON_NULL
+#endif
+#if \
+ JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__))
+#elif defined(_Ret_notnull_) /* SAL */
+ #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_
+#else
+ #define JSON_HEDLEY_RETURNS_NON_NULL
+#endif
+
+#if defined(JSON_HEDLEY_ARRAY_PARAM)
+ #undef JSON_HEDLEY_ARRAY_PARAM
+#endif
+#if \
+ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
+ !defined(__STDC_NO_VLA__) && \
+ !defined(__cplusplus) && \
+ !defined(JSON_HEDLEY_PGI_VERSION) && \
+ !defined(JSON_HEDLEY_TINYC_VERSION)
+ #define JSON_HEDLEY_ARRAY_PARAM(name) (name)
+#else
+ #define JSON_HEDLEY_ARRAY_PARAM(name)
+#endif
+
+#if defined(JSON_HEDLEY_IS_CONSTANT)
+ #undef JSON_HEDLEY_IS_CONSTANT
+#endif
+#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR)
+ #undef JSON_HEDLEY_REQUIRE_CONSTEXPR
+#endif
+/* JSON_HEDLEY_IS_CONSTEXPR_ is for
+ HEDLEY INTERNAL USE ONLY. API subject to change without notice. */
+#if defined(JSON_HEDLEY_IS_CONSTEXPR_)
+ #undef JSON_HEDLEY_IS_CONSTEXPR_
+#endif
+#if \
+ JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
+ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \
+ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \
+ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \
+ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+ #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr)
+#endif
+#if !defined(__cplusplus)
+# if \
+ JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
+ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \
+ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24)
+#if defined(__INTPTR_TYPE__)
+ #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)
+#else
+ #include
+ #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*)
+#endif
+# elif \
+ ( \
+ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \
+ !defined(JSON_HEDLEY_SUNPRO_VERSION) && \
+ !defined(JSON_HEDLEY_PGI_VERSION) && \
+ !defined(JSON_HEDLEY_IAR_VERSION)) || \
+ (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \
+ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \
+ JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0)
+#if defined(__INTPTR_TYPE__)
+ #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)
+#else
+ #include
+ #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0)
+#endif
+# elif \
+ defined(JSON_HEDLEY_GCC_VERSION) || \
+ defined(JSON_HEDLEY_INTEL_VERSION) || \
+ defined(JSON_HEDLEY_TINYC_VERSION) || \
+ defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \
+ JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \
+ defined(JSON_HEDLEY_TI_CL2000_VERSION) || \
+ defined(JSON_HEDLEY_TI_CL6X_VERSION) || \
+ defined(JSON_HEDLEY_TI_CL7X_VERSION) || \
+ defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \
+ defined(__clang__)
+# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \
+ sizeof(void) != \
+ sizeof(*( \
+ 1 ? \
+ ((void*) ((expr) * 0L) ) : \
+((struct { char v[sizeof(void) * 2]; } *) 1) \
+ ) \
+ ) \
+ )
+# endif
+#endif
+#if defined(JSON_HEDLEY_IS_CONSTEXPR_)
+ #if !defined(JSON_HEDLEY_IS_CONSTANT)
+ #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr)
+ #endif
+ #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1))
+#else
+ #if !defined(JSON_HEDLEY_IS_CONSTANT)
+ #define JSON_HEDLEY_IS_CONSTANT(expr) (0)
+ #endif
+ #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr)
+#endif
+
+#if defined(JSON_HEDLEY_BEGIN_C_DECLS)
+ #undef JSON_HEDLEY_BEGIN_C_DECLS
+#endif
+#if defined(JSON_HEDLEY_END_C_DECLS)
+ #undef JSON_HEDLEY_END_C_DECLS
+#endif
+#if defined(JSON_HEDLEY_C_DECL)
+ #undef JSON_HEDLEY_C_DECL
+#endif
+#if defined(__cplusplus)
+ #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" {
+ #define JSON_HEDLEY_END_C_DECLS }
+ #define JSON_HEDLEY_C_DECL extern "C"
+#else
+ #define JSON_HEDLEY_BEGIN_C_DECLS
+ #define JSON_HEDLEY_END_C_DECLS
+ #define JSON_HEDLEY_C_DECL
+#endif
+
+#if defined(JSON_HEDLEY_STATIC_ASSERT)
+ #undef JSON_HEDLEY_STATIC_ASSERT
+#endif
+#if \
+ !defined(__cplusplus) && ( \
+ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \
+ (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \
+ JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+ defined(_Static_assert) \
+ )
+# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message)
+#elif \
+ (defined(__cplusplus) && (__cplusplus >= 201103L)) || \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message))
+#else
+# define JSON_HEDLEY_STATIC_ASSERT(expr, message)
+#endif
+
+#if defined(JSON_HEDLEY_NULL)
+ #undef JSON_HEDLEY_NULL
+#endif
+#if defined(__cplusplus)
+ #if __cplusplus >= 201103L
+ #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr)
+ #elif defined(NULL)
+ #define JSON_HEDLEY_NULL NULL
+ #else
+ #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0)
+ #endif
+#elif defined(NULL)
+ #define JSON_HEDLEY_NULL NULL
+#else
+ #define JSON_HEDLEY_NULL ((void*) 0)
+#endif
+
+#if defined(JSON_HEDLEY_MESSAGE)
+ #undef JSON_HEDLEY_MESSAGE
+#endif
+#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas")
+# define JSON_HEDLEY_MESSAGE(msg) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \
+ JSON_HEDLEY_PRAGMA(message msg) \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+#elif \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)
+# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg)
+#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0)
+# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg)
+#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)
+# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg))
+#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0)
+# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg))
+#else
+# define JSON_HEDLEY_MESSAGE(msg)
+#endif
+
+#if defined(JSON_HEDLEY_WARNING)
+ #undef JSON_HEDLEY_WARNING
+#endif
+#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas")
+# define JSON_HEDLEY_WARNING(msg) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \
+ JSON_HEDLEY_PRAGMA(clang warning msg) \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+#elif \
+ JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \
+ JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \
+ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)
+# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg)
+#elif \
+ JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg))
+#else
+# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg)
+#endif
+
+#if defined(JSON_HEDLEY_REQUIRE)
+ #undef JSON_HEDLEY_REQUIRE
+#endif
+#if defined(JSON_HEDLEY_REQUIRE_MSG)
+ #undef JSON_HEDLEY_REQUIRE_MSG
+#endif
+#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if)
+# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat")
+# define JSON_HEDLEY_REQUIRE(expr) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \
+ __attribute__((diagnose_if(!(expr), #expr, "error"))) \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \
+ __attribute__((diagnose_if(!(expr), msg, "error"))) \
+ JSON_HEDLEY_DIAGNOSTIC_POP
+# else
+# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error")))
+# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error")))
+# endif
+#else
+# define JSON_HEDLEY_REQUIRE(expr)
+# define JSON_HEDLEY_REQUIRE_MSG(expr,msg)
+#endif
+
+#if defined(JSON_HEDLEY_FLAGS)
+ #undef JSON_HEDLEY_FLAGS
+#endif
+#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion"))
+ #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__))
+#else
+ #define JSON_HEDLEY_FLAGS
+#endif
+
+#if defined(JSON_HEDLEY_FLAGS_CAST)
+ #undef JSON_HEDLEY_FLAGS_CAST
+#endif
+#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0)
+# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \
+ JSON_HEDLEY_DIAGNOSTIC_PUSH \
+ _Pragma("warning(disable:188)") \
+ ((T) (expr)); \
+ JSON_HEDLEY_DIAGNOSTIC_POP \
+ }))
+#else
+# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr)
+#endif
+
+#if defined(JSON_HEDLEY_EMPTY_BASES)
+ #undef JSON_HEDLEY_EMPTY_BASES
+#endif
+#if \
+ (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \
+ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+ #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases)
+#else
+ #define JSON_HEDLEY_EMPTY_BASES
+#endif
+
+/* Remaining macros are deprecated. */
+
+#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK)
+ #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK
+#endif
+#if defined(__clang__)
+ #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0)
+#else
+ #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE)
+ #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE
+#endif
+#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)
+
+#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE)
+ #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE
+#endif
+#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute)
+
+#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN)
+ #undef JSON_HEDLEY_CLANG_HAS_BUILTIN
+#endif
+#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin)
+
+#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE)
+ #undef JSON_HEDLEY_CLANG_HAS_FEATURE
+#endif
+#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature)
+
+#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION)
+ #undef JSON_HEDLEY_CLANG_HAS_EXTENSION
+#endif
+#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension)
+
+#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE)
+ #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE
+#endif
+#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute)
+
+#if defined(JSON_HEDLEY_CLANG_HAS_WARNING)
+ #undef JSON_HEDLEY_CLANG_HAS_WARNING
+#endif
+#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning)
+
+#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */
+
+
+// This file contains all internal macro definitions (except those affecting ABI)
+// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them
+
+// #include
+
+
+// exclude unsupported compilers
+#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK)
+ #if defined(__clang__)
+ #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400
+ #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers"
+ #endif
+ #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER))
+ #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800
+ #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers"
+ #endif
+ #endif
+#endif
+
+// C++ language standard detection
+// if the user manually specified the used c++ version this is skipped
+#if !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11)
+ #if (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L)
+ #define JSON_HAS_CPP_23
+ #define JSON_HAS_CPP_20
+ #define JSON_HAS_CPP_17
+ #define JSON_HAS_CPP_14
+ #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L)
+ #define JSON_HAS_CPP_20
+ #define JSON_HAS_CPP_17
+ #define JSON_HAS_CPP_14
+ #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
+ #define JSON_HAS_CPP_17
+ #define JSON_HAS_CPP_14
+ #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
+ #define JSON_HAS_CPP_14
+ #endif
+ // the cpp 11 flag is always specified because it is the minimal required version
+ #define JSON_HAS_CPP_11
+#endif
+
+#ifdef __has_include
+ #if __has_include()
+ #include
+ #endif
+#endif
+
+#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM)
+ #ifdef JSON_HAS_CPP_17
+ #if defined(__cpp_lib_filesystem)
+ #define JSON_HAS_FILESYSTEM 1
+ #elif defined(__cpp_lib_experimental_filesystem)
+ #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1
+ #elif !defined(__has_include)
+ #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1
+ #elif __has_include()
+ #define JSON_HAS_FILESYSTEM 1
+ #elif __has_include()
+ #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1
+ #endif
+
+ // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/
+ #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8
+ #undef JSON_HAS_FILESYSTEM
+ #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM
+ #endif
+
+ // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support
+ #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8
+ #undef JSON_HAS_FILESYSTEM
+ #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM
+ #endif
+
+ // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support
+ #if defined(__clang_major__) && __clang_major__ < 7
+ #undef JSON_HAS_FILESYSTEM
+ #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM
+ #endif
+
+ // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support
+ #if defined(_MSC_VER) && _MSC_VER < 1914
+ #undef JSON_HAS_FILESYSTEM
+ #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM
+ #endif
+
+ // no filesystem support before iOS 13
+ #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000
+ #undef JSON_HAS_FILESYSTEM
+ #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM
+ #endif
+
+ // no filesystem support before macOS Catalina
+ #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
+ #undef JSON_HAS_FILESYSTEM
+ #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM
+ #endif
+ #endif
+#endif
+
+#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM
+ #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0
+#endif
+
+#ifndef JSON_HAS_FILESYSTEM
+ #define JSON_HAS_FILESYSTEM 0
+#endif
+
+#ifndef JSON_HAS_THREE_WAY_COMPARISON
+ #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \
+ && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L
+ #define JSON_HAS_THREE_WAY_COMPARISON 1
+ #else
+ #define JSON_HAS_THREE_WAY_COMPARISON 0
+ #endif
+#endif
+
+#ifndef JSON_HAS_RANGES
+ // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error
+ #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427
+ #define JSON_HAS_RANGES 0
+ #elif defined(__cpp_lib_ranges)
+ #define JSON_HAS_RANGES 1
+ #else
+ #define JSON_HAS_RANGES 0
+ #endif
+#endif
+
+#ifndef JSON_HAS_STATIC_RTTI
+ #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0
+ #define JSON_HAS_STATIC_RTTI 1
+ #else
+ #define JSON_HAS_STATIC_RTTI 0
+ #endif
+#endif
+
+#ifdef JSON_HAS_CPP_17
+ #define JSON_INLINE_VARIABLE inline
+#else
+ #define JSON_INLINE_VARIABLE
+#endif
+
+#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address)
+ #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]]
+#else
+ #define JSON_NO_UNIQUE_ADDRESS
+#endif
+
+// disable documentation warnings on clang
+#if defined(__clang__)
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdocumentation"
+ #pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
+#endif
+
+// allow disabling exceptions
+#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
+ #define JSON_THROW(exception) throw exception
+ #define JSON_TRY try
+ #define JSON_CATCH(exception) catch(exception)
+ #define JSON_INTERNAL_CATCH(exception) catch(exception)
+#else
+ #include
+ #define JSON_THROW(exception) std::abort()
+ #define JSON_TRY if(true)
+ #define JSON_CATCH(exception) if(false)
+ #define JSON_INTERNAL_CATCH(exception) if(false)
+#endif
+
+// override exception macros
+#if defined(JSON_THROW_USER)
+ #undef JSON_THROW
+ #define JSON_THROW JSON_THROW_USER
+#endif
+#if defined(JSON_TRY_USER)
+ #undef JSON_TRY
+ #define JSON_TRY JSON_TRY_USER
+#endif
+#if defined(JSON_CATCH_USER)
+ #undef JSON_CATCH
+ #define JSON_CATCH JSON_CATCH_USER
+ #undef JSON_INTERNAL_CATCH
+ #define JSON_INTERNAL_CATCH JSON_CATCH_USER
+#endif
+#if defined(JSON_INTERNAL_CATCH_USER)
+ #undef JSON_INTERNAL_CATCH
+ #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER
+#endif
+
+// allow overriding assert
+#if !defined(JSON_ASSERT)
+ #include // assert
+ #define JSON_ASSERT(x) assert(x)
+#endif
+
+// allow to access some private functions (needed by the test suite)
+#if defined(JSON_TESTS_PRIVATE)
+ #define JSON_PRIVATE_UNLESS_TESTED public
+#else
+ #define JSON_PRIVATE_UNLESS_TESTED private
+#endif
+
+/*!
+@brief macro to briefly define a mapping between an enum and JSON
+@def NLOHMANN_JSON_SERIALIZE_ENUM
+@since version 3.4.0
+*/
+#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \
+ template \
+ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \
+ { \
+ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
+ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \
+ /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \
+ static const std::pair m[] = __VA_ARGS__; \
+ auto it = std::find_if(std::begin(m), std::end(m), \
+ [e](const std::pair& ej_pair) -> bool \
+ { \
+ return ej_pair.first == e; \
+ }); \
+ j = ((it != std::end(m)) ? it : std::begin(m))->second; \
+ } \
+ template \
+ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
+ { \
+ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
+ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \
+ /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \
+ static const std::pair m[] = __VA_ARGS__; \
+ auto it = std::find_if(std::begin(m), std::end(m), \
+ [&j](const std::pair& ej_pair) -> bool \
+ { \
+ return ej_pair.second == j; \
+ }); \
+ e = ((it != std::end(m)) ? it : std::begin(m))->first; \
+ }
+
+// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
+// may be removed in the future once the class is split.
+
+#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \
+ template class ObjectType, \
+ template class ArrayType, \
+ class StringType, class BooleanType, class NumberIntegerType, \
+ class NumberUnsignedType, class NumberFloatType, \
+ template class AllocatorType, \
+ template class JSONSerializer, \
+ class BinaryType, \
+ class CustomBaseClass>
+
+#define NLOHMANN_BASIC_JSON_TPL \
+ basic_json
+
+// Macros to simplify conversion from/to types
+
+#define NLOHMANN_JSON_EXPAND( x ) x
+#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME
+#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
+ NLOHMANN_JSON_PASTE64, \
+ NLOHMANN_JSON_PASTE63, \
+ NLOHMANN_JSON_PASTE62, \
+ NLOHMANN_JSON_PASTE61, \
+ NLOHMANN_JSON_PASTE60, \
+ NLOHMANN_JSON_PASTE59, \
+ NLOHMANN_JSON_PASTE58, \
+ NLOHMANN_JSON_PASTE57, \
+ NLOHMANN_JSON_PASTE56, \
+ NLOHMANN_JSON_PASTE55, \
+ NLOHMANN_JSON_PASTE54, \
+ NLOHMANN_JSON_PASTE53, \
+ NLOHMANN_JSON_PASTE52, \
+ NLOHMANN_JSON_PASTE51, \
+ NLOHMANN_JSON_PASTE50, \
+ NLOHMANN_JSON_PASTE49, \
+ NLOHMANN_JSON_PASTE48, \
+ NLOHMANN_JSON_PASTE47, \
+ NLOHMANN_JSON_PASTE46, \
+ NLOHMANN_JSON_PASTE45, \
+ NLOHMANN_JSON_PASTE44, \
+ NLOHMANN_JSON_PASTE43, \
+ NLOHMANN_JSON_PASTE42, \
+ NLOHMANN_JSON_PASTE41, \
+ NLOHMANN_JSON_PASTE40, \
+ NLOHMANN_JSON_PASTE39, \
+ NLOHMANN_JSON_PASTE38, \
+ NLOHMANN_JSON_PASTE37, \
+ NLOHMANN_JSON_PASTE36, \
+ NLOHMANN_JSON_PASTE35, \
+ NLOHMANN_JSON_PASTE34, \
+ NLOHMANN_JSON_PASTE33, \
+ NLOHMANN_JSON_PASTE32, \
+ NLOHMANN_JSON_PASTE31, \
+ NLOHMANN_JSON_PASTE30, \
+ NLOHMANN_JSON_PASTE29, \
+ NLOHMANN_JSON_PASTE28, \
+ NLOHMANN_JSON_PASTE27, \
+ NLOHMANN_JSON_PASTE26, \
+ NLOHMANN_JSON_PASTE25, \
+ NLOHMANN_JSON_PASTE24, \
+ NLOHMANN_JSON_PASTE23, \
+ NLOHMANN_JSON_PASTE22, \
+ NLOHMANN_JSON_PASTE21, \
+ NLOHMANN_JSON_PASTE20, \
+ NLOHMANN_JSON_PASTE19, \
+ NLOHMANN_JSON_PASTE18, \
+ NLOHMANN_JSON_PASTE17, \
+ NLOHMANN_JSON_PASTE16, \
+ NLOHMANN_JSON_PASTE15, \
+ NLOHMANN_JSON_PASTE14, \
+ NLOHMANN_JSON_PASTE13, \
+ NLOHMANN_JSON_PASTE12, \
+ NLOHMANN_JSON_PASTE11, \
+ NLOHMANN_JSON_PASTE10, \
+ NLOHMANN_JSON_PASTE9, \
+ NLOHMANN_JSON_PASTE8, \
+ NLOHMANN_JSON_PASTE7, \
+ NLOHMANN_JSON_PASTE6, \
+ NLOHMANN_JSON_PASTE5, \
+ NLOHMANN_JSON_PASTE4, \
+ NLOHMANN_JSON_PASTE3, \
+ NLOHMANN_JSON_PASTE2, \
+ NLOHMANN_JSON_PASTE1)(__VA_ARGS__))
+#define NLOHMANN_JSON_PASTE2(func, v1) func(v1)
+#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2)
+#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3)
+#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4)
+#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5)
+#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6)
+#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7)
+#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8)
+#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9)
+#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10)
+#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11)
+#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12)
+#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13)
+#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14)
+#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15)
+#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16)
+#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17)
+#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18)
+#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19)
+#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20)
+#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21)
+#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22)
+#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23)
+#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24)
+#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
+#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26)
+#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27)
+#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28)
+#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29)
+#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30)
+#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31)
+#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32)
+#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33)
+#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34)
+#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35)
+#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36)
+#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37)
+#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38)
+#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39)
+#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40)
+#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41)
+#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42)
+#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43)
+#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44)
+#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45)
+#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46)
+#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47)
+#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48)
+#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49)
+#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50)
+#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51)
+#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52)
+#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53)
+#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54)
+#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55)
+#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56)
+#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57)
+#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58)
+#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59)
+#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60)
+#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61)
+#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62)
+#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63)
+
+#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1;
+#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1);
+#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1;
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_TYPE_INTRUSIVE
+@since version 3.9.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
+*/
+#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \
+ template::value, int> = 0> \
+ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
+ template::value, int> = 0> \
+ friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT
+@since version 3.11.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
+*/
+#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \
+ template::value, int> = 0> \
+ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
+ template::value, int> = 0> \
+ friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE
+@since version 3.11.3
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
+*/
+#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \
+ template::value, int> = 0> \
+ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE
+@since version 3.9.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
+*/
+#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \
+ template::value, int> = 0> \
+ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
+ template::value, int> = 0> \
+ void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT
+@since version 3.11.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
+*/
+#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \
+ template::value, int> = 0> \
+ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
+ template::value, int> = 0> \
+ void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE
+@since version 3.11.3
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
+*/
+#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \
+ template::value, int> = 0> \
+ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE
+@since version 3.12.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
+*/
+#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \
+ template::value, int> = 0> \
+ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
+ template::value, int> = 0> \
+ friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT
+@since version 3.12.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
+*/
+#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \
+ template::value, int> = 0> \
+ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
+ template::value, int> = 0> \
+ friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE
+@since version 3.12.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
+*/
+#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \
+ template::value, int> = 0> \
+ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE
+@since version 3.12.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
+*/
+#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \
+ template::value, int> = 0> \
+ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
+ template::value, int> = 0> \
+ void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT
+@since version 3.12.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
+*/
+#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \
+ template::value, int> = 0> \
+ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
+ template::value, int> = 0> \
+ void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
+
+/*!
+@brief macro
+@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE
+@since version 3.12.0
+@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
+*/
+#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \
+ template::value, int> = 0> \
+ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
+
+// inspired from https://stackoverflow.com/a/26745591
+// allows calling any std function as if (e.g., with begin):
+// using std::begin; begin(x);
+//
+// it allows using the detected idiom to retrieve the return type
+// of such an expression
+#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \
+ namespace detail { \
+ using std::std_name; \
+ \
+ template \
+ using result_of_##std_name = decltype(std_name(std::declval()...)); \
+ } \
+ \
+ namespace detail2 { \
+ struct std_name##_tag \
+ { \
+ }; \
+ \
+ template \
+ std_name##_tag std_name(T&&...); \
+ \
+ template \
+ using result_of_##std_name = decltype(std_name(std::declval()...)); \
+ \
+ template \
+ struct would_call_std_##std_name \
+ { \
+ static constexpr auto const value = ::nlohmann::detail:: \
+ is_detected_exact::value; \
+ }; \
+ } /* namespace detail2 */ \
+ \
+ template \
+ struct would_call_std_##std_name : detail2::would_call_std_##std_name \
+ { \
+ }
+
+#ifndef JSON_USE_IMPLICIT_CONVERSIONS
+ #define JSON_USE_IMPLICIT_CONVERSIONS 1
+#endif
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ #define JSON_EXPLICIT
+#else
+ #define JSON_EXPLICIT explicit
+#endif
+
+#ifndef JSON_DISABLE_ENUM_SERIALIZATION
+ #define JSON_DISABLE_ENUM_SERIALIZATION 0
+#endif
+
+#ifndef JSON_USE_GLOBAL_UDLS
+ #define JSON_USE_GLOBAL_UDLS 1
+#endif
+
+#if JSON_HAS_THREE_WAY_COMPARISON
+ #include // partial_ordering
+#endif
+
+NLOHMANN_JSON_NAMESPACE_BEGIN
+namespace detail
+{
+
+///////////////////////////
+// JSON type enumeration //
+///////////////////////////
+
+/*!
+@brief the JSON type enumeration
+
+This enumeration collects the different JSON types. It is internally used to
+distinguish the stored values, and the functions @ref basic_json::is_null(),
+@ref basic_json::is_object(), @ref basic_json::is_array(),
+@ref basic_json::is_string(), @ref basic_json::is_boolean(),
+@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),
+@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),
+@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and
+@ref basic_json::is_structured() rely on it.
+
+@note There are three enumeration entries (number_integer, number_unsigned, and
+number_float), because the library distinguishes these three types for numbers:
+@ref basic_json::number_unsigned_t is used for unsigned integers,
+@ref basic_json::number_integer_t is used for signed integers, and
+@ref basic_json::number_float_t is used for floating-point numbers or to
+approximate integers which do not fit in the limits of their respective type.
+
+@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON
+value with the default value for a given type
+
+@since version 1.0.0
+*/
+enum class value_t : std::uint8_t
+{
+ null, ///< null value
+ object, ///< object (unordered set of name/value pairs)
+ array, ///< array (ordered collection of values)
+ string, ///< string value
+ boolean, ///< boolean value
+ number_integer, ///< number value (signed integer)
+ number_unsigned, ///< number value (unsigned integer)
+ number_float, ///< number value (floating-point)
+ binary, ///< binary array (ordered collection of bytes)
+ discarded ///< discarded by the parser callback function
+};
+
+/*!
+@brief comparison operator for JSON types
+
+Returns an ordering that is similar to Python:
+- order: null < boolean < number < object < array < string < binary
+- furthermore, each type is not smaller than itself
+- discarded values are not comparable
+- binary is represented as a b"" string in python and directly comparable to a
+ string; however, making a binary array directly comparable with a string would
+ be surprising behavior in a JSON file.
+
+@since version 1.0.0
+*/
+#if JSON_HAS_THREE_WAY_COMPARISON
+ inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD*
+#else
+ inline bool operator<(const value_t lhs, const value_t rhs) noexcept
+#endif
+{
+ static constexpr std::array order = {{
+ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,
+ 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */,
+ 6 /* binary */
+ }
+ };
+
+ const auto l_index = static_cast(lhs);
+ const auto r_index = static_cast(rhs);
+#if JSON_HAS_THREE_WAY_COMPARISON
+ if (l_index < order.size() && r_index < order.size())
+ {
+ return order[l_index] <=> order[r_index]; // *NOPAD*
+ }
+ return std::partial_ordering::unordered;
+#else
+ return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index];
+#endif
+}
+
+// GCC selects the built-in operator< over an operator rewritten from
+// a user-defined spaceship operator
+// Clang, MSVC, and ICC select the rewritten candidate
+// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200)
+#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__)
+inline bool operator<(const value_t lhs, const value_t rhs) noexcept
+{
+ return std::is_lt(lhs <=> rhs); // *NOPAD*
+}
+#endif
+
+} // namespace detail
+NLOHMANN_JSON_NAMESPACE_END
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+// #include
+
+
+NLOHMANN_JSON_NAMESPACE_BEGIN
+namespace detail
+{
+
+/*!
+@brief replace all occurrences of a substring by another string
+
+@param[in,out] s the string to manipulate; changed so that all
+ occurrences of @a f are replaced with @a t
+@param[in] f the substring to replace with @a t
+@param[in] t the string to replace @a f
+
+@pre The search string @a f must not be empty. **This precondition is
+enforced with an assertion.**
+
+@since version 2.0.0
+*/
+template
+inline void replace_substring(StringType& s, const StringType& f,
+ const StringType& t)
+{
+ JSON_ASSERT(!f.empty());
+ for (auto pos = s.find(f); // find first occurrence of f
+ pos != StringType::npos; // make sure f was found
+ s.replace(pos, f.size(), t), // replace with t, and
+ pos = s.find(f, pos + t.size())) // find next occurrence of f
+ {}
+}
+
+/*!
+ * @brief string escaping as described in RFC 6901 (Sect. 4)
+ * @param[in] s string to escape
+ * @return escaped string
+ *
+ * Note the order of escaping "~" to "~0" and "/" to "~1" is important.
+ */
+template
+inline StringType escape(StringType s)
+{
+ replace_substring(s, StringType{"~"}, StringType{"~0"});
+ replace_substring(s, StringType{"/"}, StringType{"~1"});
+ return s;
+}
+
+/*!
+ * @brief string unescaping as described in RFC 6901 (Sect. 4)
+ * @param[in] s string to unescape
+ * @return unescaped string
+ *
+ * Note the order of escaping "~1" to "/" and "~0" to "~" is important.
+ */
+template
+static void unescape(StringType& s)
+{
+ replace_substring(s, StringType{"~1"}, StringType{"/"});
+ replace_substring(s, StringType{"~0"}, StringType{"~"});
+}
+
+} // namespace detail
+NLOHMANN_JSON_NAMESPACE_END
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // size_t
+
+// #include
+
+
+NLOHMANN_JSON_NAMESPACE_BEGIN
+namespace detail
+{
+
+/// struct to capture the start position of the current token
+struct position_t
+{
+ /// the total number of characters read
+ std::size_t chars_read_total = 0;
+ /// the number of characters read in the current line
+ std::size_t chars_read_current_line = 0;
+ /// the number of lines read
+ std::size_t lines_read = 0;
+
+ /// conversion to size_t to preserve SAX interface
+ constexpr operator size_t() const
+ {
+ return chars_read_total;
+ }
+};
+
+} // namespace detail
+NLOHMANN_JSON_NAMESPACE_END
+
+// #include
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-FileCopyrightText: 2018 The Abseil Authors
+// SPDX-License-Identifier: MIT
+
+
+
+#include // array
+#include // size_t
+#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
+#include // index_sequence, make_index_sequence, index_sequence_for
+
+// #include
+
+
+NLOHMANN_JSON_NAMESPACE_BEGIN
+namespace detail
+{
+
+template
+using uncvref_t = typename std::remove_cv::type>::type;
+
+#ifdef JSON_HAS_CPP_14
+
+// the following utilities are natively available in C++14
+using std::enable_if_t;
+using std::index_sequence;
+using std::make_index_sequence;
+using std::index_sequence_for;
+
+#else
+
+// alias templates to reduce boilerplate
+template
+using enable_if_t = typename std::enable_if::type;
+
+// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h
+// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0.
+
+//// START OF CODE FROM GOOGLE ABSEIL
+
+// integer_sequence
+//
+// Class template representing a compile-time integer sequence. An instantiation
+// of `integer_sequence` has a sequence of integers encoded in its
+// type through its template arguments (which is a common need when
+// working with C++11 variadic templates). `absl::integer_sequence` is designed
+// to be a drop-in replacement for C++14's `std::integer_sequence`.
+//
+// Example:
+//
+// template< class T, T... Ints >
+// void user_function(integer_sequence);
+//
+// int main()
+// {
+// // user_function's `T` will be deduced to `int` and `Ints...`
+// // will be deduced to `0, 1, 2, 3, 4`.
+// user_function(make_integer_sequence());
+// }
+template
+struct integer_sequence
+{
+ using value_type = T;
+ static constexpr std::size_t size() noexcept
+ {
+ return sizeof...(Ints);
+ }
+};
+
+// index_sequence
+//
+// A helper template for an `integer_sequence` of `size_t`,
+// `absl::index_sequence` is designed to be a drop-in replacement for C++14's
+// `std::index_sequence`.
+template
+using index_sequence = integer_sequence;
+
+namespace utility_internal
+{
+
+template
+struct Extend;
+
+// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency.
+template
+struct Extend, SeqSize, 0>
+{
+ using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >;
+};
+
+template
+struct Extend, SeqSize, 1>
+{
+ using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >;
+};
+
+// Recursion helper for 'make_integer_sequence'.
+// 'Gen::type' is an alias for 'integer_sequence'.
+template
+struct Gen
+{
+ using type =
+ typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type;
+};
+
+template
+struct Gen
+{
+ using type = integer_sequence;
+};
+
+} // namespace utility_internal
+
+// Compile-time sequences of integers
+
+// make_integer_sequence
+//
+// This template alias is equivalent to
+// `integer_sequence`, and is designed to be a drop-in
+// replacement for C++14's `std::make_integer_sequence`.
+template
+using make_integer_sequence = typename utility_internal::Gen::type;
+
+// make_index_sequence
+//
+// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`,
+// and is designed to be a drop-in replacement for C++14's
+// `std::make_index_sequence`.
+template
+using make_index_sequence = make_integer_sequence;
+
+// index_sequence_for
+//
+// Converts a typename pack into an index sequence of the same length, and
+// is designed to be a drop-in replacement for C++14's
+// `std::index_sequence_for()`
+template
+using index_sequence_for = make_index_sequence;
+
+//// END OF CODE FROM GOOGLE ABSEIL
+
+#endif
+
+// dispatch utility (taken from ranges-v3)
+template struct priority_tag : priority_tag < N - 1 > {};
+template<> struct priority_tag<0> {};
+
+// taken from ranges-v3
+template
+struct static_const
+{
+ static JSON_INLINE_VARIABLE constexpr T value{};
+};
+
+#ifndef JSON_HAS_CPP_17
+ template
+ constexpr T static_const::value;
+#endif
+
+template
+constexpr std::array make_array(Args&& ... args)
+{
+ return std::array {{static_cast(std::forward(args))...}};
+}
+
+} // namespace detail
+NLOHMANN_JSON_NAMESPACE_END
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // numeric_limits
+#include // char_traits
+#include // tuple
+#include // false_type, is_constructible, is_integral, is_same, true_type
+#include // declval
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // random_access_iterator_tag
+
+// #include
+
+// #include
+
+// #include
+
+
+NLOHMANN_JSON_NAMESPACE_BEGIN
+namespace detail
+{
+
+template
+struct iterator_types {};
+
+template
+struct iterator_types <
+ It,
+ void_t>
+{
+ using difference_type = typename It::difference_type;
+ using value_type = typename It::value_type;
+ using pointer = typename It::pointer;
+ using reference = typename It::reference;
+ using iterator_category = typename It::iterator_category;
+};
+
+// This is required as some compilers implement std::iterator_traits in a way that
+// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341.
+template
+struct iterator_traits
+{
+};
+
+template
+struct iterator_traits < T, enable_if_t < !std::is_pointer::value >>
+ : iterator_types
+{
+};
+
+template
+struct iterator_traits