5. Rules
Contents of FBL 0.7
An element rule turns entries into elements of one node type:
| Property | Type | Description |
|---|---|---|
name |
Name | Required. Referenced by parent, cascade, reference.to and within. |
type |
TypeRef | Required. The node type produced, as the tool type's specification declares it. |
at |
Selector | yaml, json, xml: where the entries are. |
line |
Regex | lines, blocks: the statement; bullets: the statement, or with within the item's text (section 4.8). Exactly one of at and line. |
within |
Name[] | blocks: the block rules or element rules whose block encloses the statement (section 4.7); (FBL 0.7) bullets: the sections whose items the rule matches (section 4.8). |
opens, caseInsensitive |
opens: blocks (section 4.7); caseInsensitive: lines, blocks and bullets. |
|
files |
Name[] | Folder subjects: the file rules whose files this rule reads (section 10). |
when |
Expression | The entry is matched only when this holds. |
id |
IdBinding | Where the element's id is stored (section 5.3). |
parent |
{rules, slot} |
Containment (section 5.5). |
attributes |
map attribute → AttributeBinding | Section 5.2. |
insert |
Insert | How a new element is written (section 6.2). Absent: adding is refused. |
remove |
Remove | How an element is removed (section 6.2). Absent: removing is refused. |
undo |
"inverse" (default), "snapshot" |
Section 7.1. |
readOnly |
bool or LocalizedText | Section 3.4. |
(FBL 0.2) A rule's type, and the attributes its bindings name, are the metamodel's, unless the tool type's specification maps the binding's names onto its metamodel with persistence.typeMap (DISL §11.2): then they are the binding's own names, and the map says what each type and attribute becomes, including a type read as the diagram's attributes, as a header, as an unreadable entry or as a relation whose ends are reference attributes. FBL reads and writes the body by the binding's names either way.
When several rules match the same entry, the first in the binding's order (elements before relations) whose when holds takes it; an entry becomes at most one element or relation. Rules of the same node type are how a type is written in several shapes (the timeline's Period and Moment are one kind of entry, told apart by has(entry.end)).
A slot is exactly one place a value is read from and written to:
| Slot | Families | The value |
|---|---|---|
{key} |
yaml, json | The value of that member of the entry's mapping; with child, of the mapping the relative selector child reaches. |
{attribute} |
xml | The value of that attribute of the element, or of the element child selects. |
{text: true} |
xml | The text of the element, or of the element child selects. |
{group} |
lines, blocks, bullets | The named group; with word (and flag), a word of it (section 4.6). |
{continuation: true} |
bullets | (FBL 0.7) The item's continuation, its lines with their indentation taken off (section 4.8). |
{parent: a} |
all | The value of attribute a's slot in the enclosing entry the rule's parent names, or the nearest enclosing matched entry. Read-only unless that entry's slot is writable. |
{capture: c} |
yaml, json, xml | The key a {c} selector segment matched; writing it renames the key. |
{value: cel} |
all | Computed; read-only. |
An attribute binding is a slot bound to one attribute of the rule's type, with these options:
| Property | Values | Meaning |
|---|---|---|
empty |
"remove", "keep", "refuse" |
Setting the attribute to its empty value (empty string, empty list, null) removes the slot (remove-key), writes the empty value (keep, the default for strings), or is refused. (FBL 0.6) The rule, with the same default, applies to an insert as well as to a set: it decides whether a new entry that an add gives this attribute's empty value has its key (section 6.2). |
absent |
map family → "insert", "refuse" |
Setting a value when the slot is absent from the entry: insert it (insert-key, the default) or refuse, per family. |
default |
value | The value read when the slot is absent. |
number |
"shortest", {decimals} |
How a number is written (section 6.3). |
time |
"keep-precision" |
A written date or date-time keeps the precision of the value it replaces (section 6.3). |
style |
plain, double, single, literal, flow |
yaml: the style of a newly written value, when the value it replaces gives none (section 6.3). |
reference |
{to, by} |
The value names an entry of the rules to by the value of their attribute by, or, with by: "id", by the id those rules store with id.from (section 5.7). DISL reserves id as an element's built-in field, so it is never an attribute's name. |
map |
map wire value → model value | Translates values both ways. (FBL 0.7) In a rule with caseInsensitive, a wire value matches a key ignoring the case of ASCII letters, and is kept as written when the model value does not change. Several wire values may map to one model value; writing uses the first key in document order of the map whose value matches, unless the value being replaced already maps to it, in which case it is kept. |
override |
Slot | A second slot that wins on read when present and is removed (remove-key) when the attribute is written. |
content |
"text", "html-paragraphs" |
xml text slots (section 4.5). |
create |
{emit, place}, (FBL 0.4) {at: "end"} |
xml: how a missing child element holding the value is written: emit with {value} as the placeholder, placed first, last or {before: name} among the element's children. Emptying the value removes the child with the line span it has (remove-key). (FBL 0.4) yaml, json, on a {key} slot with child: when a level of the mapping child reaches is missing, setting a value creates every missing level as an insert's at: "end" does (section 6.2), then inserts the key into the innermost (insert-key), all at one offset; into a mapping that exists, the key goes right after the last key of that mapping the rule binds before it, or as its first key when there is none. Emptying the value (empty: "remove") then also removes the levels the key leaves empty, as remove-empty-levels does. |
readOnly |
bool or LocalizedText | Section 3.4. |
read |
"text", "typed" |
(FBL 0.5) yaml, json: what the slot reads from a scalar. text: its written text (section 4.1.5). typed: its typed value (section 4.1.4), converted to the attribute's type. Absent: text for an attribute whose type is a string, typed for every other (below). |
quoted |
"text" (default), "convert" |
(FBL 0.5) yaml, with read: "typed": whether a quoted scalar converts to a number or a bool from its written text ("12" reads as 12) or stays a text that does not convert. |
fallback |
{value, report} |
(FBL 0.5) What the attribute is when its slot is present and its value does not convert to the attribute's type: value. The entry stays readable and the bytes of the slot are kept. report (default true): whether the finding fbl.unconverted-value is reported (section 7.4). |
Reading a scalar (FBL 0.5). What a slot of a yaml or json body reads is settled by read, and without it by the attribute's type in the tool type's specification:
| The scalar is written | A string attribute, an id, a reference; or read: "text" |
An int or number attribute | A bool attribute | An attribute of any other type, without read |
|---|---|---|---|---|
yes |
"yes" |
does not convert | true |
its typed value, true, converted |
1.0 |
"1.0" |
1.0 (an int attribute: 1) | does not convert | 1.0, converted |
007 |
"007" |
7 | does not convert | 7, converted |
"12" |
"12" |
does not convert; 12 with quoted: "convert" |
does not convert | "12", converted |
nothing (key:) |
"" |
null: the attribute is absent, and default applies |
the same | the same |
| the key is absent | the attribute is absent: default applies |
the same | the same | the same |
- A slot read as text MUST give the written text, whatever the scalar's typed value.
maptranslates the written text. An id (section 5.3) and a reference (section 5.7) are read and compared as written text, with or withoutread. - A slot read as typed converts the typed value to the attribute's type. A conversion that loses nothing succeeds (the int 1 to a number, the double 1.0 to an int); any other does not convert. With
quoted: "convert", a quoted scalar is first read by the core schema from its written text. - A value that does not convert makes its entry unreadable (section 7.4), unless the binding gives the attribute a
fallback. - A binding type the tool type's specification maps with
typeMap, and a host attribute, have the type of the attribute they map to; a host attribute, which has none, is read as text. read,quotedandfallbackMUST NOT be given on a slot of thexml,lines,blocksorbulletsfamily, nor on avalueorcaptureslot;fallbackapplies there in no case, since a text always reads.
These are the two places where 0.5 gives a meaning that 0.4 left open. 0.4 said what CEL sees of a scalar (section 4.1.4) and that a value which "does not convert to the attribute's type" makes an entry unreadable, and did not say what a string attribute reads from a scalar that the core schema types otherwise, nor how an id written 007 compares. Hosts read them differently; 0.5 settles both as the written text, which is what the tools that own such files do.
"attributes": {
"label": { "key": "label", "empty": "keep" },
"begin": { "key": "begin", "read": "text", "time": "keep-precision" },
"row": { "key": "row", "default": 0, "quoted": "convert", "fallback": { "value": 0, "report": false } }
}
label is a string attribute and reads the written text unasked. begin is a date-time attribute that this tool keeps as the text it was written with, so that it can report a time it cannot read in its own words. row: wide reads as row 0 and the element is still drawn; row: "3" reads as 3.
An attribute of the node type that no binding names is not stored in the body: it takes its DISL default, or is derived as DISL says. A DISL attribute marked fixed (DISL §4.3) MUST NOT be bound.
How ids are made, derived and whether they are ephemeral is DISL's (persistence.ids, DISL §11.5). FBL says only where an id is stored, with the rule's id:
{"from": Slot}: the id is the value of the slot. It is read from it, and written to it when the element is created (with a new id DISL's strategy generates, once, when the gesture happens, so a redo re-creates the element under the same id). A rename of the id is areplace-valueplus the reference rewrites of section 5.7. (FBL 0.5) The id is the slot's written text (section 4.1.5): an entry writtenid: 007has the id007, and a reference written7does not name it.{"sidecar": {"key": cel}}: the id is kept in the registration'sidentitiesblock (section 8.6), keyed by the value ofkey, for formats whose entries have no id of their own (a Wardley map's components are named, not identified).- No
id: the id is the one DISL derives (strategy: "derived"), from the element's attributes and, throughself.location(), its place in the file.
Ids that DISL marks ephemeral (an id that changes when the file is edited, such as one built from a line number) are never stored: not in a registration's layout (section 8.5), not in a sidecar. A missing or duplicate id is not a reason to refuse the body: DISL's ids.missing and its findings std.missingId and std.duplicateId decide, and an entry is always addressable by its place.
A relation rule is an element rule with two more slots, source and target, each REQUIRED. Their values reference the ends by id: an end whose value names no element is a dangling reference, reported as fbl.dangling-reference on the entry, and the relation is not created (DISL relations always have both ends). A relation whose ends are its enclosing entries takes them with {parent: …} (a Databricks dependency's target is the task whose depends_on it is in).
parent: {rules, slot} makes the element a child, in DISL containment slot slot, of the nearest enclosing entry matched by one of rules: the nearest ancestor node in the tree families and xml, the nearest enclosing block in blocks, (FBL 0.7) the nearest item the entry is nested in in bullets, which is the nearest open item above it with its marker in a smaller column (section 4.8). A rule without parent produces top-level elements. Moving an element to another parent is a remove-entry in the old parent and an insert-entry in the new one, in one edit, keeping the entry's own bytes. (FBL 0.7) In bullets they are the entry's own bytes re-indented: a move to another depth shifts the indentation of every line of the entry by the difference (section 6.3), and changes nothing else.
header names a version or identity mark: a root key with a value (yaml, json), or a line expression the first statement or the root start tag must match (lines, blocks, xml), or (FBL 0.7) that line 1 must match, after a byte-order mark (bullets). A body without the mark, or with another value, is read, and the difference is reported as fbl.header-mismatch (a warning). With required: true, a body without the mark is unreadable (section 7.5) instead. The header is never written except by the template.
A slot is a reference when its attribute binding has reference, or when it is a relation's source or target (which reference ids). A reference by id is written by: "id", and every rule it names MUST store its id with id.from. When the referenced value changes (the by attribute of an entry of the to rules, or an id stored with from), every reference to the old value is rewritten in the same edit, one rewrite-reference splice each. A reference inside a word or a list value is rewritten in place, word by word or item by item. A rename to a value another entry of the same rules already has is refused.
A group holding several references (a causal loop's members) is split into words, each a reference.