This page describes the source-code formatting conventions used across MPL sources. The rules here are about visual layout and idiomatic expression choice, not about language semantics.
Closing a scope with ), ], or } ends one logical unit. Subsequent code belongs to an outer scope and represents a new step. Insert a blank line after any line that closes a scope, unless the very next line also closes a scope.
Correct:
[
body
] loop
next step
Correct (consecutive closers, no blank required):
] if
] loop
] &&
Incorrect (missing blank line):
[
body
] loop
next step
A closing bracket followed by a control operator on the same line (for example ] if, ] loop, ] when) is still a scope-closing line for this rule. It must be followed by a blank line or by another closing bracket, never directly by a non-closing statement.
When consecutive lines form a table of analogous items, align them into columns. Alignment is required whenever it is achievable by inserting spaces; do not leave items visually scattered when a small amount of padding would put them in columns.
The following kinds of tables require column alignment:
use imports: pad each quoted name so the trailing use is in one column.:) is in one column, and pad short right-hand sides so any trailing shared keyword or terminator is in one column."Array.Array" use
"Span.Span" use
"algorithm.between" use
"control.Cond" use
Every quoted name is padded to the same width so use appears in one column.
isDecimal: ["0" "9" between];
isHex: [(@isDecimal ["A" "F" between]) meetsAny];
toDecimal: [.data 48n8 -];
toHex: [.data dup 65n8 < [48n8] [55n8] if -];
Each name is padded so : and the opening [ of each body appear in their own columns.
For predicate-action pairs in a dispatch construct, align three columns:
=, ~, or similar).(
[char "" = ] ["Unterminated escape sequence" fail]
[char "\"" = ] ["\"" @textValue.cat skip]
[char "\\" = ] ["\\" @textValue.cat skip]
[char "n" = ] ["\n" @textValue.cat skip]
[char "«" = ] ["«" @textValue.cat skip]
[char isHex ~] ["Invalid escape sequence" fail]
) cond0
When the action chain itself varies in length between rows (for example when some rows perform two checks and others perform four), pad the shorter chains so a shared downstream column (such as [isDelimiter ~] ||, the error arm, or the terminating if) aligns with the longer rows. Pad inside the descriptor brackets rather than after them.
Within any aligned block, never carry more than one column of spaces between two aligned columns. If several consecutive columns are spaces in every row, compress that run to a single separator column. Each row may then have its own internal run of spaces before the separator to reach the aligned column width.
(
[char "8" =] [skip ~ [isDelimiter ~] || ["short" fail] [0x7Fi8 AstNode.INT8 processInteger] if]
[char "1" =] [skip ~ [char "6" = ~] || [skip ~] || [isDelimiter ~] || ["short" fail] [0x7FFFi16 AstNode.INT16 processInteger] if]
[char "3" =] [skip ~ [char "2" = ~] || [skip ~] || [isDelimiter ~] || ["short" fail] [0x7FFFFFFF AstNode.INT32 processInteger] if]
) cond0
Between the hex constant and AstNode.INTn, only one column is all-spaces across every row; the varying space count on each side of that separator is the internal padding that each row needs to reach the aligned column's width.
Do not insert trailing spaces to pad a bare item inside brackets when there is no column to align against it. The default arm of a dispatch takes just its action with no padding.
(
"INVALID" # An invalid class, used as a default value
"BLOCK" # A single code block
"COND" # An 8-bit conditional value
"INT8" # A generic 8-bit two's-complement integer
"INT16" # A generic 16-bit two's-complement integer
)
Each value is padded so the trailing # comment appears in one column. This applies whenever several consecutive lines each end in a comment about that line's item.
Express common two-branch patterns with short-circuit operators rather than full conditionals when possible.
| Intent | Prefer | Instead of |
|---|---|---|
Run body only if X is not TRUE. |
X [body] || |
X [TRUE] [body] if |
Run body only if X is TRUE and propagate a FALSE otherwise. |
X [body] && |
X [body] [FALSE] if |
Prefer the shorter form when the body is small and the intent is a plain short-circuit. Fall back to an explicit if when both arms carry substantial code.
When a step tests a compound condition and chooses between an error/no-op branch and a work branch, place the shorter error/no-op branch first, attached to the condition. The condition is then phrased so that TRUE means "error".
Shape:
anyFailure [short-path] [work-path] if
When combining several sub-checks, build the failure condition with ~ on each sub-term and combine the terms with ||, so the chain evaluates to TRUE on any failure:
A ~ [B ~] || [C ~] || [short-path] [work-path] if
Avoid ending a &&-chain with a trailing ~. Swap the if arms and invert the chain to ||-of-negations instead.
Incorrect:
A [B] && [C] && ~ [short-path] [work-path] if
Correct:
A ~ [B ~] || [C ~] || [short-path] [work-path] if
When a branch carries substantial body, expand the dispatch onto multiple lines rather than packing everything onto one line. Nest each arm so the opening [ ends its line and the closing ] sits on its own line at the arm's indent level.
Incorrect (too compressed):
X [Y [char ":" =] || [short] [work with multiple statements] if] &&
Correct:
X [
Y [char ":" =] || [
short
] [
work
with multiple
statements
] if
] &&
Inside a multi-line block, put a trailing single-token loop/dispatch signal on its own line rather than appending it to a dense statement line. This makes the block's return value visible at a glance.
Incorrect:
[
statement
another
TRUE flag FALSE
] ||
Correct:
[
statement
another
TRUE flag
FALSE
] ||
When a value is compared against several alternatives, write a membership check against a small tuple rather than a chain of equalities combined with logical operators. This mirrors the intent and stays short when the set grows.
Incorrect:
char "a" = [char "b" =] || [char "c" =] ||
Correct:
("a" "b" "c") (char) contains
Parser and semantic-compiler error messages follow a fixed shape so they read uniformly regardless of the specific diagnostic.
<who> <what>[[,] <extra>]
<who> is the phenomenon in question: a noun phrase naming the construct (for example Text literal, Number, Real number, Line, Name, Member access, or Module) or a literal character quoted as «X».<what> is the verb phrase that describes the defect (for example is unterminated, overflows, starts with zero, has invalid suffix, is closed by «]»).<extra> is optional detail added to clarify the defect (for example expected «"», after «.», in hex escape). Precede with a comma only when English grammar requires it — typically before a participial/clausal continuation like expected X. Plain prepositional phrases (in X, after X) take no comma."Text literal is unterminated, expected hex digit"
"Text literal has non-hex character «G» in hex escape"
"Real number has exponent that starts with zero"
"Real number has non-digit character «a» after «.»"
"Number starts with zero"
"Number has hex digit «A» in decimal integer"
"Number overflows"
"Line ends with space"
"Line contains carriage return"
"«(» is closed by «]»"
"«(» is unterminated"
"Member access has no name after «@»"
Real number has exponent that starts with zero, not Exponent starts with zero.<who>, quoted as «X».is not allowed, cannot have); the error context already implies wrongness.«[» inside a name).Include the offending character in the message when it speeds diagnosis. Use assembleString with « and » as visual quotes around the literal character:
("Name has unexpected character «" char "»") assembleString fail
For the » character itself — where «»» would be visually ambiguous — quote with ASCII double quotes:
""»" has no matching opener"
When a construct ends prematurely (for example EOF before an expected terminator), use the shape X is unterminated[, expected Y] where Y names the specific thing that was expected next:
"Text literal is unterminated, expected hex digit"
"Text literal is unterminated, expected escape character"
"Text literal is unterminated, expected «"»"
"Text literal is unterminated, expected "»""
"«(» is unterminated"
For missing sub-parts inside a node, use has no X after «Y»:
"Member access has no name after «@»"
"Member access has no name after «!»"