14. Processing model
Contents of DISL 0.2
- Parse the JSON; reject duplicate keys.
- Check versions:
dislmajor version supported (or its deprecated alias, section 18). - Resolve imports recursively, verify integrity hashes, detect cycles, apply
includefilters, prefix names with aliases. - Validate structure against the JSON Schema
$defs/Specification. - Resolve names: every type, style, shape, marker, icon, form, tool, snap profile, layout, operation, function and plugin reference must resolve.
- Flatten inheritance: compute C3 linearisations; merge attributes, ports, containment; check narrowing rules (4.7).
- Derive built-ins: generate built-in constraints (8.7), default notations, default forms and the default toolbox where absent.
- Compile CEL: parse and type-check every expression in its context (12.3); estimate costs against
language.limits.celCost; check user functions for recursion other than the bounded self-recursion of 3.4, and plugin function names for clashes (13.1.1). - Check semantic rules not expressible in JSON Schema: reserved attribute names, placement attribute types compatible with axes, snapping rules compatible with axis kinds,
x2vswidthexclusivity, handle parameters exist, labeleditablerequires attribute binding, exactly one ofpath/parts/svg/pluginin shapes, and all other MUST statements of this document. - Build the runtime model.
Validators report every error or warning in a specification with the JSON Pointer of its location, a severity (error makes the specification unusable; warning does not) and a message.
A runtime loads a stored diagram as DID specifies (DID-specification.md, section 8.1): it checks the language id and version against the specification's, runs this specification's migrations (11.9), validates, resolves view data, evaluates live constraints and renders.
Loading is tolerant of ids (11.5.4). A runtime MUST load a model in which ids are missing or duplicated rather than refuse it, and MUST in this order:
- read the elements in reading order (8.6), reporting
std.missingIdfor each element without a usable id and, followingpersistence.ids.compare,std.duplicateIdfor each second and later element with an id already used; - give an element without a usable id a new id under
missing: "assign"(held in memory and written with the next save, never on open) or treat it as ephemeral undermissing: "ephemeral", and treat every second and later holder of a duplicated id as ephemeral; - compute the
derivedids (11.5.2), nodes before relations, letting references follow a computed id that differs from the stored one; - compute the derived elements (4.11.5), derived node types then derived relation types, each in declaration order;
- resolve view data, ignoring and reporting by
std.ephemeralViewDataany view data, style override or suppression keyed by an ephemeral id (11.5.3); a view key that names no element once derived elements are computed is kept and ignored.
A model read through FBL is loaded in the same order, with FBL's reading order and FBL raising the reader findings.
For a diagram whose persistence format is "fbl", the body is read through its binding and the view data comes from the registration, as FBL specifies (FBL-specification.md, section 14.2).
Unknown types, attributes and view properties in a stored diagram are preserved as DID specifies (DID-specification.md, section 8.2), and unknown elements are reported by std.typeExists.
Every user gesture, form commit, operation or quick fix is one transaction:
Diagram as text (Mermaid)
---
config:
flowchart:
rankSpacing: 20
---
flowchart TB
accTitle: The editing transaction
accDescr: A gesture passes through gesture constraints, which may prevent it, then snapping, then the change is applied. Then run the before hooks, which may abort, the after hooks, and the recomputation of derived values and bindings. Invariants with enforcement prevent then either reject the transaction, which rolls it back, or let it commit as one undo step. After the commit, live constraints are evaluated, debounced, and the diagram is rendered.
gesture([gesture]) --> gestureConstraints["gesture constraints<br/>prevent?"]
gestureConstraints --> snapping[snapping] --> apply[apply change]
apply --> before["#quot;before#quot; hooks<br/>may abort"]
before --> after["#quot;after#quot; hooks"] --> recompute["recompute derived<br/>values & bindings"]
recompute --> invariants["invariants with<br/>enforcement #quot;prevent#quot;"]
invariants -- reject --> rollback([roll back])
invariants --> commit["commit<br/>(one undo step)"]
commit --> live["live constraints<br/>(debounced)"] --> render([render])Transactions are atomic: either all effects apply or none. Remote changes from collaborators apply as transactions without hooks but with constraint evaluation. Changes of viewer state (11.6) and the steps of a simulation (9.6) are not transactions: they change no model, run no hook or constraint, and create no undo step.
The "recompute derived values & bindings" step also recomputes derived ids (11.5.2) and rewrites references to any id that changed, and then recomputes the derived elements (4.11.5), all before invariants are checked. Expressions evaluated later in the transaction see derived elements as of the working state. A runtime MAY recompute incrementally, but the result MUST equal a full recomputation. Derived elements are never part of the undo step: undo and redo restore the stored elements and recompute the derived ones, and the selection and stored view data follow them by id. Ids the transaction generates for new elements are generated once, when it is first applied; redo re-creates the elements with the same ids and is refused when that would duplicate an id (11.5.1).
- Evaluate constraints with
savetiming. IfblockSaveOn: "error"and errors exist, ask the user (saving MUST remain possible under a different name or as a draft if the runtime supports drafts). - Write the DID definition canonically and atomically, as DID specifies (DID-specification.md, section 8.3). With
format: "fbl", write the body's edits as FBL specifies (FBL-specification.md, section 6.6) instead.