Muon spec

muon, mu object notation

Mu object notation, Muon, is a permissive textual format for structured data, which is also a substrate for describing Mu source code via additional higher-level parsing. Muon is a superset of Mu source code.

Muon structure is designed to reflect human visual perception of source code and to be loosely compatible with other structured formats (e.g. json).

Its top-level entity is a "sequence", <seq>. A standalone Muon file is a <seq> (maybe empty one).

whitespace and newline handling

Muon is indentation-insensitive, but it is sensitive to newlines, \n. Any ebnf rules defined below:

Allowed whitespace characters are (\x20), \r, \t.

\r\n in muon text is treated as a whitespace character \r, followed by <sep>. \r\n <-> \n conversions do not change the parsed muon structure.

To convert multi-line representation into one-line representation (e.g. for minification):

grammar

<seq>

A <seq> consists of zero or more "chains", <chain>, separated by separator <sep> (can be a newline or a comma interchangeably, to format long chains as lines and short chains as a comma-separated lists in one line).

Zero chains is a valid empty seq.

Preceding/trailing/duplicate separators are valid, to allow for blank lines visually, hanging commas in lists, newline before EOF, etc. The content of a sequence is defined by chains inside of it, whereas separators only delimit chains. Chains by their definition are non-empty, but empty space between <sep> is allowed and does not produce a chain.

(,,,)      // no chains inside
(a b, c)   // two chains: `a b`, `c`
(, ,,a b, , c,,)  // same

In source code, a sequence corresponds to the inner content of a module, a tuple, a block, a list, etc.

<sep> ::= "\n" | ","
<seq> ::= <chain>? (<sep> <chain>?)*

<chain>

A chain, <chain> is a juxtaposition of one or more <item>s. If literal concatenation changes meaning, items should be separated by one or more whitespace, otherwise zero or more whitespace is allowed between items.

Visually, a chain is a "sentence" of visually atomic "items", juxtaposition of <item>s.

A chain cannot be empty (but a place between two <sep> in a <seq> can be, this does not produce a chain).

In source code, a chain is roughly an "expression"/"statement". Source code can treat multiple adjacent chains as one expression (e.g. to allow visual separation of declaration and definition of a function, to spread long expressions) according to simple unambiguous rules.

<chain> ::= <item>+

<item>

An item is either a visually atomic entity like a number/string literal, a symbolic name, or something clearly delineated by an opening and a closing element, e.g. it may be:

A prefixed item is a leading sigil (& or .) immediately followed (no whitespace) by any <item>. Muon assigns it no meaning — it is structure only, just like a : (<punct>) or -1 (<sym>) — leaving the interpretation to higher levels (e.g. Elly uses &<item> to introduce a binding and .<item> for a symbol / projection; see elly-spec.md). The . sigil applies only when the dot is glued to an item; a dot not followed by an item is a <punct> (see <prefixed> and <punct> below).

<item> ::= 
  | <comm>     (* comments are valid items and are preserved *)
  | <str>      (* string literals *)
  | <sym>      (* numbers, names, +/-/. operators, etc *)
  | <punct>    (* punctuation standing between items, e.g. ":" *)
  | <tuple>    (* parenthesis-wrapped <seq> *)
  | <list>     (* bracket-wrapped <seq> *)
  | <block>    (* brace-wrapped <seq> *)
  | <prefixed> (* an item marked with a leading sigil *)

<prefixed>

A prefixed item is a sigil directly attached to an item. The sigils are & and .. There is no whitespace between the sigil and the item.

The two sigils differ in how they handle a dangling sigil (one not directly followed by an item):

(* no whitespace between sigil and item *)
<prefixed> ::= <sigil> <item>
<sigil>    ::= "&" | "."   (* "." only when right-glued to an item; else <punct> *)

<comm>

Comments are valid atomic items which preserve their inner unicode textual content literally. They are preserved at the Muon level and usually are ignored by higher level semantics. This still allows to reuse them in tools like formatters.

Visually, comments are C-style // one-line and /*-*/ multiline comments.

Comments are not nested:

<comm> ::=
  | "//" (any character except "\n")*
  | "/*" (any character sequence without "*/") "*/"

Newlines inside multiline comments are not <sep> and are preserved.

<str>

String literals are wrapped in ". They contain any unescaped Unicode characters (except a literal newline or unescaped ") or escape sequences starting with \.

Escape sequences are: \", \n, \t, \r, \\. Any other character after \ is currently a syntax error, open to changes in the future.

<str> ::= "\"" (any utf-8 character except unescaped '"' and literal newline)* "\""

TODO: check json strings compatibility TODO: \u{NNNN}, \xNN encoding?

<sym>

Symbols are any (non-whitespace) sequences of one or more of:

A symchar is exactly a character that can occur inside a single atomic literal — a number or an identifier. That is why -, + are symchars (-1, +123) but : and . are not: neither occurs inside a literal, so : is a <punct> (below) and . is a sigil-or-punct (see <prefixed> and <punct>). A consequence is that a . breaks a symbol: foo.bar is not one symbol but the chain foo .bar (<sym> then a .-prefixed item), and 3.14 is the chain 3 .14.

They can encode:

(* no whitespace *)
<sym> ::= <symchar>+
<symchar> ::= "0".."9" | "A".."Z" | "a".."z"
            | `_` | `-` | `+`

TODO: +, - genuinely belong to both classes (they are operators too), but they stay symchars so numeric literals stay whole. A consequence is that a token cannot straddle the two classes by maximal munch: :+ is <punct> : then <sym> +, and a future -> would not munch as one token. Cross-class multi-char tokens (like ->, or / once it joins a class) would be handled as explicit lexical exceptions, the way // and /* already are. (. used to be a symchar too, for 3.14 / foo.bar; it has been split out as a sigil/punct — float-literal notation is deferred until Elly grows a Num type.)

<punct>

Punctuation, <punct>, is a maximal run of one or more punctchars — non- whitespace characters that stand between items rather than occurring inside a literal. The punctchars are : and =. Like <sym>, Muon assigns <punct> no meaning: they are just structure, read by higher levels (e.g. Elly reads key : value as a map entry, x = 1 as a binding, or a future => as an arrow; see elly-spec.md).

Punctuation is maximal munch like <sym>, so :: and == are each a single <punct> (write : : / = = for two), and — since : and = share the class — := munches as one punct too. Future punctchars (>, …) would let => fall out the same way, with no per-operator rule.

A . is also a <punct>, but a special one: it is a standalone single-character punct and is not a punctchar, so it never joins the :/= run (:. is <punct> : then <punct> ., not one token). A . is this punct only when it is not glued to an item; a . directly followed by an item is instead the sigil of a <prefixed> (see <prefixed>). Muon gives the . punct no meaning either — a higher level (Elly) reads a spaced . as an application / composition combinator.

(* no whitespace *)
<punct> ::= <punctchar>+ | "."
<punctchar> ::= ":" | "="    (* reserved to grow: > | ~ ! ? ... *)

<tuple>

A tuple is a <seq> wrapped in parenthesis. It's used for grouping chains together. Its exact semantics is defined by higher levels (e.g. in Elly).

Empty tuple, ()/( )/(,,)/etc, is valid and wraps an empty sequence.

Since the content of a tuple is a <seq>, they can spread over multiple lines:

(a, (b, c))  
// is equivalent to
(a
(b,c))
// is equivalent to
(
  a
  (b, c)
)
// is equivalent to
(
  a
  (
    b
    c
  )
)

Definition:

<tuple> ::= "(" <seq> ")"

<list>

A list is a <seq> wrapped in brackets. Like <tuple>, it only groups chains; Muon assigns it no meaning, leaving it to higher levels (e.g. Elly reads a list as a map's computed key or, juxtaposed after a map, as bracket access; see docs/elly-spec.md).

Empty list, []/[ ]/[,,]/etc, is valid and wraps an empty sequence. Its content being a <seq>, a list can spread over multiple lines exactly like a tuple. Since juxtaposition without whitespace is allowed after an item, m[k] is a chain of <sym> <list> (mirroring f(x) as <sym> <tuple>).

<list> ::= "[" <seq> "]"

<block>

A block is a <seq> wrapped in braces. Like <tuple> and <list>, it only groups chains; Muon assigns it no meaning (e.g. Elly reads a block as a map literal; see docs/elly-spec.md).

Empty block, {}/{ }/{,,}/etc, is valid and wraps an empty sequence, and it follows the same multi-line and juxtaposition rules as a tuple and a list.

<block> ::= "{" <seq> "}"

json compatibility

Requires TODOs: