12. The CEL environment
Contents of DISL 0.4
DISL uses CEL as specified at https://github.com/google/cel-spec, with these standard extensions REQUIRED: strings (charAt, indexOf, lastIndexOf, lowerAscii, replace, split, substring, trim, upperAscii, format, join, reverse, quote), math (math.greatest, math.least, math.ceil, math.floor, math.round, math.abs, math.sqrt, …), lists (distinct, flatten, range, slice, sort, sortBy), sets (sets.contains, sets.intersects, sets.equivalent), optional types (?., .?, optional.of, orValue), and the cel.bind macro. Comprehension macros all, exists, exists_one, map, filter and has are available as in core CEL.
String order. <, sort() and sortBy() MUST order strings by Unicode code point, comparing code point by code point, which is CEL's own string order; a runtime on a platform whose strings are UTF-16 MUST NOT compare UTF-16 code units directly, which orders characters beyond U+FFFF differently. sort() and sortBy() MUST be stable. DISL has no locale-dependent sort.
| CEL type | Fields and methods |
|---|---|
Element (nodes, relations, ports, diagram) |
id (string), type (string), kind ("node", "relation", "port", "diagram"), every attribute as a field (typed from the metamodel; references resolve to Element or null), isA(typeName) → bool, isTagged(tag) → bool, view → ViewData (placement of the element in the current view, null in headless contexts), label() → string (value of labelAttribute), location() → optional(map) (the SourceLocation (8.6) the reader recorded for the start of the element's declaration; optional.none() when it recorded none, as for every element of a DID definition), location(attr) → optional(map) (the same for one attribute's value), derived → bool (whether the element is derived, 4.11), sources → list(Element) (a derived element's sources, 4.11.4; [] for a stored element), findingSeverity() → string (the severity of the worst open, unsuppressed finding on the element: "error", "warning", "info", "hint" or ""), findings() → list(map) (the open findings on the element, each with constraint, code, severity and message), drawn() → bool (whether the current view draws the element, 6.1). findingSeverity(), findings() and drawn() are not readable in the deterministic contexts of 12.5. An attribute named derived or sources, which a 0.1 specification may declare, shadows the member for its type. |
| Node-specific | parent → Element?, owner → Element (parent or diagram), slot → string, children → list(Element), childrenOfType(t), descendants(), ancestors() (the parent first and the top-level ancestor last), ancestorsOfType(t) (in the same order), depth() → int, ports → list(Element), portsOfType(name), incoming → list(Element), outgoing → list(Element), incomingOf(relType), outgoingOf(relType), neighbors(), successors(relType), predecessors(relType), reachable(relType) → list(Element) (transitive successors, excluding self unless on a cycle), inCycle(relType) → bool. |
| Relation-specific | source → Element, target → Element (null for a relation without a target, 4.9), sourcePort, targetPort (Element?), other(e) → Element (the opposite end), owner → Element (the element the relation belongs to: its derived owner, else the nearest common ancestor of its ends, else the diagram, 4.11.3). |
| Port-specific | owner → Element, connections → list(Element), direction → string. |
| Diagram | nodes → list(Element) (model nodes, not view-only), relations, elements (both), nodesOfType(t), relationsOfType(t), elementById(id) → Element?, hasCycle(relType) → bool, viewOnly → list(Element) (in the current view), file → string (the model's file relative to the diagram's subject, 8.6), views → list(View) (the views of the model: those a DID definition stores, or those the model's readings define), drawn → list(Element) (the elements the current view draws, after the five steps of 6.1, in model order; [] in headless contexts; not readable in the deterministic contexts of 12.5), diagram attributes as fields. |
View |
id (string), viewpoint (string), name (string), members → list(Element) (the elements of the model the view shows). Bound as view in constraints with over: "view" (8.2). |
ViewData |
x, y, x2, y2, width, height (domain values, dyn: double, timestamp, duration, month index or band id), bounds → Bounds (canvas units), collapsed (the viewer's value when collapsed is viewer state, 11.6), rotation, z, pinned, params (map), selected → bool (whether the element is selected; viewer state). |
Bounds |
x, y, width, height, x2, y2 (canvas or domain units depending on the context, as documented there), center() → list(double), intersects(Bounds) → bool, contains(Bounds) → bool. |
Env |
now → timestamp, locale → string, mode → string (theme mode), zoom → double, user → map (id, name; empty unless the runtime is configured to expose it), viewpoint → string, readOnly → bool. |
Attribute types map to CEL types per 4.2; enum values are strings (their keys, never their stored forms, 4.5); yearMonth values are int month indices; structs are map(string, dyn) with statically known fields; many attributes are lists.
Type names include subtypes. Wherever a function or member takes a type name — nodesOfType, relationsOfType, childrenOfType, ancestorsOfType, incomingOf, outgoingOf, successors, predecessors, reachable, inCycle, hasCycle, cycles, cyclesTruncated, knots and isA — the name MUST include every subtype of the type (2.7), so a query over an abstract relation type covers any mix of its subtypes.
The context of an expression determines its variables. Validators type-check each expression in exactly one context.
| Context | Used by | Variables |
|---|---|---|
element |
derived attributes, labels, styles conditions, variants, visibility, tooltips, markers, notation Bindables, form.visible of embedded forms |
self, diagram, env; p (the shape's parameter values) in a part's tooltip (6.8) |
compartmentItem |
itemText, itemIcon, itemStyle |
self, item, index, diagram, env |
shape |
GeomExpr in shapes, markers, handles, form regions | w, h, p, self, env; in markers additionally sw (stroke width) |
handle |
Handle.value, yValue, label |
as shape, plus px, py |
placement |
computed placement sources | self, diagram, env, parent, axis ({kind, min, max}) |
placementWrite |
placement write actions |
as placement, plus value (snapped domain value) |
snap |
CEL snap rules | value, axis, zoom, self, parent, diagram |
categories |
ordinal categories expressions |
diagram, env |
constraint |
invariant when, rule, message, target, severity, forEach, location, subject, fix when and label, built-in message and computed code (DISL 0.3) |
self, diagram, env (env.now is fixed at the start of a validation run); item, index with forEach; view with over: "view"; detail in built-in messages and codes (8.1, 8.7) |
identity |
id expression and ephemeral of the derived strategy (11.5.2) |
self, diagram; no env, so an id never depends on locale, user or time. self.id is not readable; the ids readable are those of self's ancestors and, for a relation, of its ends and their ancestors |
gesture:<kind> |
gesture constraints; a built-in's refusal |
per 8.4; violation in a built-in's refusal; count in deletion confirmations (9.5) |
create |
attribute CEL defaults, tool initial, CreateEnd initial (7.2), a drop's refusal, id expression of the cel strategy |
diagram, env, parent, elementType, position; dropTarget and tool for a drop (7.2); other and relationType in a CreateEnd |
parentPlacement |
a parent placement's candidates, rank and index (9.1, DISL 0.3) |
as create, with position the drop point or the point the menu was opened at; the as name (default candidate) in rank; parent in index |
form |
form visible, enabled, validate, options, label, placeholder, initial, readOnlyReasons, absentText, emptyText, button confirm; display, parse, optionLabel and refusals (DISL 0.3) |
self, value (current field value; the entered text in parse), diagram, env; position in initial (7.5); count in confirmations (9.5); item in optionLabel and newValue in refusals of a type item (7.5) |
hook |
hook when and actions |
self, old, event, diagram, env, plus let/as bindings |
operation |
operation enabled, label, unavailable, confirm and actions |
self or selection, p (not in label, unavailable and enabled), diagram, env, position (the point it was invoked at, 7.3, or null), plus bindings; count in confirmations (9.5); operationId in behavior.messages.std.notApplicable |
derive |
a derived type's from (4.11) |
diagram, env |
deriveItem |
every other expression of a derived type (4.11) | item (the group's first item), group (its items in from order), index (the group's position), diagram, env |
filter |
a canvas filter's keep and options (6.13.1) |
self (not in options), value, match, diagram, env |
legend |
legend.computed (6.13) |
self, diagram, env |
chrome |
notice, empty-canvas and notice-button texts and conditions, a budget notice's text (6.13, 3.2.1) | diagram, env; diagram.drawn, budget() and filterValue() are readable |
layoutExtent |
a rows layout's extent (10.2, DISL 0.3) |
self, diagram, env, and the element's bounds in canvas units as x, y, x2, y2, width, height |
budget |
a budget's measure, order and unit (3.2.1) |
self (the candidate; absent in measure), index (its position in persistence order), diagram, env |
connection |
a connection context-menu set's when and entries (7.3) |
source, target, sourceAnchor, position, diagram, env |
handleWrite |
a handle's write actions (6.8) |
self, diagram, env, p, w, h, value, yValue (the new, snapped parameter values) |
simulation |
a simulation's initial, next and until (9.6) |
self, state, step, p, diagram, env |
template |
template attribute and position expressions | p, diagram, env, refs (map of created elements by ref) |
migration |
migration steps | element (raw record map), value, document (raw map), from, to (version strings) |
function |
user functions | parameters, plus diagram/env if declared in uses |
Entries generated by a context tool's forEach (7.3) see item (or the as name) and index in addition to the variables of their context. The gesture:reorder context of 8.4 binds self, parent, oldIndex, newIndex and siblings.
In addition to the members listed in 12.2, these global functions are available in all contexts unless noted:
| Function | Description |
|---|---|
l.isUnique() → bool |
All elements of the list differ. |
l.sum(), l.min(), l.max(), l.avg() |
Numeric aggregates (int/double/duration). |
l.count(x, pred) |
Macro: number of elements satisfying pred. |
l.indexOf(v) → int |
First index or −1. |
l.first(), l.last() |
optional element. |
snap(v, step) → double |
Round to the nearest multiple, halves away from zero. snap(v, step, ties) rounds halves as the snap rule ties of 5.10 says. snapFloor, snapCeil. |
snapTo(v, list) → double |
Nearest value from a list. |
clamp(v, lo, hi) |
Clamp a number. |
min(a, b, …), max(a, b, …) |
Smallest or largest of two to four numbers (aliases of math.least / math.greatest, convenient in geometry). |
m.with(m2) → map |
A copy of map m with the entries of m2 added or replaced (used in migrations and actions to build attribute maps). |
ordinal(value, enumName) → int |
Position of an enum value. |
enumLabel(enumName, value) → string, enumColor(...), enumIcon(...) |
Enum metadata. |
token(name) → string |
Resolved theme token (element, shape contexts). |
color(c).lighten(f), .darken(f), .alpha(f), .mix(c2, f), .contrastText() → string |
Color manipulation; contrastText returns a readable text color for a background. mix and alpha are pinned below. |
formatNumber(n, pattern) → string |
ICU number formatting in env.locale. |
formatDate(t, pattern) → string, formatDate(t, pattern, tz) |
LDML date formatting. |
formatDuration(d, style) → string |
"short" (3d 4h), "long", "iso". |
date(y, m, d) → timestamp, timestamp(string) (core) |
Construction. |
t.startOf(unit[, tz]), t.endOf(unit[, tz]), t.addUnits(n, unit[, tz]) |
Calendar arithmetic (units as in 5.5). |
workingDays(a, b, calendarId) → int, addWorkingDays(t, n, calendarId) → timestamp, isWorkingTime(t, calendarId) → bool |
Calendar functions using axis calendars. |
daysBetween(a, b) → int |
Calendar days. |
distance(p1, p2) → double |
Euclidean distance of points. |
lower(s), upper(s) |
Aliases of lowerAscii/upperAscii with Unicode case mapping. |
matchesGlob(s, glob) → bool |
Glob matching (safer than regex for users): * matches any run of characters, including none, ? exactly one character, and every other character itself; the whole of s must match, and case matters. |
diagram.nodesOfType(t, includeViewOnly) |
Include view-only elements. |
e.positionIn(l) → int |
The zero-based position of element e in list l, comparing elements by identity, never by id, or −1. Lists of elements are in reading order (8.6), so positionIn works while ids are being computed and when ids are duplicated: self.positionIn(group) == 0 holds for the first of a group only. |
diagram.cycles(relType, max) → list(list(Element)) |
The elementary cycles over relations of relType (including its subtypes), at most max of them. Each cycle is in loop order and starts at its member that comes first in model order; the cycles are ordered by their first member, then lexicographically by member order. A self-loop is a cycle of one. |
diagram.cyclesTruncated(relType, max) → bool |
Whether max cut the list diagram.cycles(relType, max) returns. |
diagram.knots(relType) → list(list(Element)) |
The strongly connected components over relations of relType that have more than one member or a self-loop; members in model order, components ordered by their first member. |
fs.exists(path) → optional(bool), fs.isDirectory(path) → optional(bool) |
File-system facts, constraint context only (below). |
budget(id) → map, budget() → map |
The state of a budget (3.2.1): shown (int), total (int), truncated (bool) and withheld (bool, whether edits are withheld now). budget() aggregates every budget: truncated and withheld are true when they are for any. Not in the deterministic contexts of 12.5. |
filterValue(id) → dyn |
The viewer's value of a canvas filter (6.13.1); element and chrome contexts only. |
axisRange(axis, value) → string |
The id of the range of axis axis (5.3) that holds value, or '' when none does. |
textWidth(text, fontSize) → double, textHeight(lines, fontSize) → double |
The width of text and the height of lines lines at fontSize, under the declared text metric (6.5). With the host metric, not in the deterministic contexts of 12.5. |
yearMonth(y, m) → int, ym.year() → int, ym.month() → int |
Build a month index from an astronomical year and a month 1–12, and take it apart (4.2). |
formatYearMonth(i, pattern) → string, parseYearMonth(s) → optional(int) |
Format a month index with an LDML pattern, where u is the signed astronomical year, and parse the "±YYYY-MM" form (4.2); parseYearMonth returns optional.none() for a string not in that form or with a month outside 1–12, so a validation can test hasValue(). |
precisionOf(self, attr) → string |
The precision a datetime value was written with (4.2): "date", "minute", "second", "millisecond", or "" when unset; element, snap and placementWrite contexts. |
Implementations MUST provide cost estimates for all library functions; graph traversals (reachable, hasCycle, inCycle, knots) have cost proportional to the number of relations of the given type and SHOULD be memoised per validation run. diagram.cycles and diagram.cyclesTruncated MUST NOT return or consider more than max cycles, and their cost MUST be estimated as proportional to (nodes + relations) × (max + 1), which keeps CEL's termination guarantee. All three graph functions MUST be deterministic given the model and the reading order.
Colour mixing. color(c).mix(c2, f), with f from 0 to 1, MUST return CSS Color 5 color-mix(in srgb, c (1 − f) × 100%, c2 f × 100%): interpolated on gamma-encoded sRGB with premultiplied alpha. color(c).alpha(f) MUST multiply the colour's alpha by f. Both results MUST be written as #RRGGBBAA. A Paint string MAY also be a CSS color-mix() literal whose arguments are colour literals, not tokens:
"fill": { "cel": "color(token('ansible.play')).mix(token('color.surface.raised'), 0.82)" }
An Ansible play's fill is its hue mixed 18 % into the raised surface colour, in every theme mode.
File-system facts. fs.exists(path) and fs.isDirectory(path) are the only file-system facts DISL defines, and they are available in the constraint context only.
pathMUST be resolved relative to the folder of the diagram's subject (8.6), with/separators. The result MUST beoptional.none()when the path is absolute, carries a URI scheme, resolves outside the root the runtime was given (the host's project, or the headless validator's root), or when the runtime has no file-system access; a rule then stays silent throughorValue.- The functions MUST reveal only whether a path exists and whether it is a folder, never a file's content, size or time, and MUST NOT follow network paths (section 16).
- The facts MUST be taken once per validation run, as
env.nowis (12.5). A runtime MUST re-evaluate the constraints that use them on explicit validation and on save, and SHOULD re-evaluate them when the file system changes.
{ "id": "submapMissing", "code": "wardley.submap-missing", "scope": "Submap", "severity": "warning",
"when": "self.url != '' && !self.url.contains(':')",
"rule": "fs.exists(self.url).orValue(true)",
"message": { "cel": "\"The submap '\" + self.name + \"' points at '\" + self.url + \"', which is not in this project.\"" } }
Expressions in the contexts constraint, identity, migration, create (except env.now), placement, snap, derive, deriveItem and budget MUST be deterministic given the DID definition. env.now is the only source of time and is fixed per evaluation run; the file-system facts of 12.4 are likewise taken once per run. Members that depend on the viewer, on findings or on budgets MUST NOT be used in these contexts, and a validator MUST reject an expression there that uses one: diagram.drawn, e.drawn(), filterValue(), self.view for a kind of viewer state (11.6), a transient: "viewer" attribute (4.3), findingSeverity(), findings() and budget(), and textWidth() and textHeight() under the host text metric (6.5). The budget context MAY read what viewer filters leave, since budgets apply after them (3.2.1), but nothing else a viewer sets. A plugin function declared deterministic: false (13.1) MUST NOT be called in these contexts. Random functions do not exist. Iteration order of diagram.nodes and similar lists is the persistence order (11.7), with derived elements after the stored ones in their computation order (4.11.5), so results are reproducible across tools.
Sorting is deterministic too: strings order by code point and sorts are stable (12.1).
Referenced from
- 1. Introduction
- 2. Foundations
- 3. Specification structure, layers and viewpoints
- 4. Layer 1 — Metamodel
- 5. Layer 2 — Coordinate systems, placement and snapping
- 6. Layer 3 — Notation (visual definition)
- 7. Layer 4 — Toolbox, context tools and forms
- 8. Layer 5 — Constraints
- 9. Layer 6 — Behavior
- 10. Layer 7 — Layout
- 11. Layer 8 — Persistence
- 14. Processing model
- 16. Security, privacy and robustness
- 17. Complete examples
- Appendix D — Design rationale and open questions