WebAssembly and JavaScript

The web integration runs the existing C++ DIPL parser and inspector, and the PUQ conversion command, in a browser through WebAssembly. Its JavaScript module provides string and bundled-project parsing, value descriptions, schema inspection, validation, override reevaluation, and unit conversion. It has no browser UI dependency. The JavaScript module is built from this repository and is not currently published as an npm package.

Build it with Emscripten’s emcmake and a C++17-capable toolchain:

export EM_CACHE="$PWD/build-web/cache"
emcmake cmake -S bindings/web -B build-web -G Ninja
cmake --build build-web -j4
node bindings/web/tests/smoke.mjs

Emscripten also requires Python 3.10 or newer. Put that python3 first on PATH if your operating system provides an older default. EM_CACHE keeps the compiler cache in a writable project build directory.

The build writes bindings/web/dist/dipl_web.mjs and bindings/web/dist/dipl_web.wasm. When serving these files to a browser, serve the JavaScript and WASM from the same origin and use a server that sends application/wasm for the .wasm file.

The module initializes asynchronously. Evaluated values are returned as DIPL text, including units separately, so callers do not lose integer precision or array type information through an implicit JavaScript conversion:

import { loadDIPL } from "./bindings/web/index.mjs";

const dipl = await loadDIPL();
const source = `distance float = 24 m
duration float = 8 s
speed float = ({?distance} / {?duration}) m/s`;
const model = dipl.parse(source);
console.log(model.paths());
console.log(model.describe("speed").valueText);
console.log(model.describeMany(["distance", "duration", "speed"]));

const candidate = model.withOverride("distance = 32 m");
console.log(candidate.describe("speed").valueText);
console.log(dipl.validate(source, "missing = 1"));
candidate.dispose();
model.dispose();

withOverride evaluates the original source with a replacement override body and returns a new model. It does not mutate the original. This uses the same parser and validation rules as native DIPL. schemas() exposes the schema definition and application hierarchy for building navigation or forms. describeMany(paths, maxValueElements) returns one description per requested path, in the same order, through a single WebAssembly call. It uses the model’s already evaluated environment; each result has the same fields and value limits as describe(). Pass the paths you need to avoid serializing the full model. See Inspecting environments for the related C++ inspection API.

For a model split across several sources, pass text-backed registrations to parseProject. This calls the core C++ project registration method, which processes units, named schema bodies, existing overrides, and ordered code in the same phases as a DIPfile. Code and override name fields are source identities used in diagnostics; schema and unit name fields are their DIPL names. Schema bodies and override bodies have no $schema or $override wrapper. A website can fetch or generate this object without providing a filesystem to the parser:

const project = {
  units: [{ name: "custom_length", text: "m" }],
  schemas: [{ name: "settings", text: "distance float = 24 custom_length\nduration float = 8 s" }],
  code: [
    { name: "model.dip", text: "simulation : settings" },
    { name: "derived.dip", text: "speed float = ({?simulation.distance} / {?simulation.duration}) m/s" },
  ],
  overrides: [],
};
const model = dipl.parseProject(project);
const next = model.withOverride("simulation.distance = 32 custom_length");
console.log(next.describe("speed").valueText);
console.log(dipl.validateProject(project, "unknown = 1"));
next.dispose();
model.dispose();

withOverride replaces the previous interactive override body and leaves project.overrides intact. DIPL rejects two overrides of the same path. If the project input already contains an override for the path being edited, replace that entry in project.overrides and call parseProject again; the web binding retains the core parser’s duplicate-override rule.

parseProject remains useful for projects assembled in memory. For a DIPfile with local files, package it with the SNT CLI instead. The package command uses the native C++ project loader to collect files opened during evaluation, including sources[] entries and raw .dipt tables. It rejects missing files and paths outside the DIPfile directory.

snt dip package --project path/to/DIPfile --output project.bundle.json

The result is a static JSON object containing format, entry and files. Its paths are relative to the DIPfile directory. A site may fetch this asset explicitly, then load it without further network access:

const bundle = await fetch("./project.bundle.json").then((response) => response.json());
const model = dipl.parseBundle(bundle);
console.log(model.describe("measurements.mass"));
const candidate = model.withOverride("scale = 4");
console.log(candidate.describe("result").valueText);
console.log(dipl.validateBundle(bundle, "scale = 4"));
candidate.dispose();
model.dispose();

The browser calls the same C++ DIPfile registration and table parsers as native SNT. A missing bundled file or a path escaping the bundle is an error; the runtime does not fetch it. $source paths derived from values are collected when packaging the original project. If a later override selects a different file, pass --include relative/path to snt dip package or the override will fail clearly. Run the package command from the project directory when its $source paths are relative to the working directory. DIPH5 persistence, reports, adapters, and DIPL source serialization are separate features.

PUQ conversion

loadPUQ exposes the existing C++ PUQConvert command to JavaScript. It accepts the same expression, target units, and optional unit-system and physical-quantity context as snt puq convert:

import { loadPUQ } from "./bindings/web/index.mjs";

const puq = await loadPUQ();
console.log(puq.convert("35*eV", "J"));
console.log(puq.convert("12*statA", "A", {
  inputSystem: "ESU", outputSystem: "SI", outputQuantity: "I",
}));
console.log(puq.systems());

The PUQ conversion page includes a live browser converter using this interface. The documentation build compiles the WebAssembly module and copies it into Sphinx’s static assets.