modl Manual Override Diagram Language Open the editor

Grammar reference

A compact, complete description of modl: the grammar, how a line is processed, the defaults, the error messages, and how attributes are drawn.

Grammar

In EBNF. Words are separated by runs of spaces or tabs. Reserved words are lowercase and case-sensitive.

source       = line , { newline , line } ;
newline      = "\n" | "\r\n" ;
line         = [ statement ] , [ comment ] ;
comment      = "#" , { any character } ;
               (* only at the start of the line (after optional whitespace)
                  or directly after whitespace, and never inside a string;
                  a "#" inside a word is text *)

statement    = background | edge-line | node-line ;
               (* tried in this order; see "Processing a line" *)

background   = color ;                     (* the only word on the line, unquoted *)

node-line    = id , node-attrs ;
node-attrs   = [ size ] , [ shape ] , [ color ] , [ url ] , [ text ] ;

edge-line    = segment , "->" , segment , { "->" , segment } ;
               (* empty segments are skipped; at least two must remain *)
segment      = id , { word } ;
               (* words after the id are attributes only in the last
                  non-empty segment, and ignored in every other one *)
edge-attrs   = [ size ] , [ line-style ] , [ color ] , [ url ] , [ text ] ;

id           = id-char , { id-char } | quoted-word ;
               (* a quoted id may hold any characters but can't be empty *)
id-char      = "A".."Z" | "a".."z" | "0".."9" | "_" | ":" | "-" ;

size         = "tiny" | "small" | "normal" | "big" | "bigger" | "huge" ;
shape        = "none" | "circle" | "square" | "diamond" | "triangle" | "hex" ;
line-style   = "solid" | "dashed" | "dotted" | "bold" ;
color        = "gray" | "black" | "white" | "red" | "orange" | "yellow"
             | "green" | "teal" | "blue" | "purple" | "pink" | "brown" ;
url          = ( "/" | "http://" | "https://" | "www." | "mailto:" ) , { non-space } ;
text         = word , { word } ;           (* rejoined with single spaces *)
word         = plain-word | quoted-word ;
plain-word   = non-space , { non-space } ;
               (* never contains "->", which always separates words *)
quoted-word  = string , { string | non-space } ;
               (* the value is the word with the outer quotes of each
                  string removed; a quoted word is never a keyword or url *)
string       = '"' , { any character but '"' } , '"'
             | "'" , { any character but "'" } , "'" ;
               (* opens only at the start of a word or right after another
                  string; a quote without a matching one later on the line
                  is a plain character *)

Attribute slots are matched greedily, left to right, each at most once. A word that doesn't fit the current or a later slot starts the text, and the text runs to the end of the line. Quoted words never fit a slot.

Processing a line

  1. Split into words. Read the line left to right, recognizing strings, -> and the comment. If the first non-blank character is #, the line is empty. Otherwise everything from the first whitespace-then-# outside a string onward is removed. Strings are found first, so ->, # and whitespace inside them are text.
  2. Skip blank lines.
  3. Background. If the line is exactly one unquoted color name, it becomes the background color, replacing any earlier background.
  4. Edge line. If the line contains -> outside strings:
    1. Split on -> and drop segments without words. Fewer than two remaining is a malformed edge line.
    2. From each segment except the last, take the first word as an id. An invalid id is reported and left out of the chain.
    3. The first word of the last segment is the final id. If it is invalid, the error is reported and the whole line is skipped. The remaining words are parsed as edge-attrs.
    4. For each consecutive pair of ids, create a node for each id that doesn't exist yet, and add an edge with the attributes, applying defaults for missing ones.
  5. Node line. Otherwise the first word is the id (an invalid id is reported and the line skipped). The remaining words are parsed as node-attrs. The node is created if needed, then each attribute that was given replaces the node's current value. Text is replaced only when new text is given, and an empty string ("" or '') as the text clears it.

Lines with errors are skipped as described, and processing continues. The diagram always shows everything that parsed.

Defaults

sizestylecolorurltext / label
Nodenormalcirclegraynonenone: the id is the label
Edgenormalsolidgraynonenone: no label
Backgroundnone: the editor's backdrop shows through

URL normalization

Token starts withResulting link
/https:// + the rest of the token
www.https:// + the token
http://, https://, mailto:the token unchanged

Node URLs using http(s) are also tried as images. See image nodes.

Error messages

Errors appear in the box under the source and the status reads Parse errors. Line numbers count every line, including blank and comment lines, starting at 1.

MessageCause
Line N: invalid node id "x" The first word of a node line, or an id in an edge line, is unquoted and contains a character other than letters, digits, _, : and -. try it
Line N: empty node id An id is an empty string, "" or ''. try it
Line N: malformed edge line The line contains -> but fewer than two non-empty segments, such as a -> or -> b. try it

Everything else is valid modl. Misplaced keywords, unknown words and extra URLs never cause errors; they become label text. See Gotchas.

How attributes are drawn

PropertyValue
Node box110 × 64 px times the size scale (tiny ¼, small ½, normal 1, big 2, bigger 4, huge 8)
Circleradius 0.42 × the smaller box side
Squarethe full box, corner radius 12 px × scale
Diamond, triangle, hexfill the box; the triangle points up, and the hexagon has flat top and bottom
Node fill / outlinethe node color at 18% / 85% opacity, outline 2.2 px
Node labelbold, max(10, 13 × scale) px, at most 22 characters; dark text on white, yellow and green nodes, light text on all others
Edge line2.2 px (bold 3.5 px); dashes 10/7 px, dots 2/7 px; arrowhead at the target in the edge color
Edge endspulled back from the node center by a fixed distance × scale: circle 35.2 px, square 37.4 px, diamond / triangle / hex 39.6 px, none 4 px. Image nodes use their exact outline.
Edge labelmax(10, 12 × scale) px, at most 28 characters, at the midpoint offset 10 px to one side
Image nodenatural size, or fitted into the box when a size is given; frame only when a shape is given; text below
Backgroundthe color at full opacity, filling the canvas
Automatic layoutunplaced nodes on a circle of radius 0.32 × the smaller canvas side, in sorted id order

Diagram state

The editor's URL fragment is h1= + base64url(UTF-8(JSON {"src": string, "pos": {id: [x, y]}})), or a 256t CID that names content on HashBin. Details are on Sharing & positions.