8. Tags
Readable Object Notation

Tags are shorthand for boolean-valued members of a map. Tags form a set of string names, each associated with a boolean value. They are not general-purpose sets of arbitrary values: names are represented as JSON object keys, which can only be strings. A tag group encloses key names between < and >. A name without a prefix gives that key the value true. An immediately preceding ! gives it the value false. Tags introduce no new value type and are distinct from the #-prefixed objects called tagged elements below.

RON
<foo bar !baz>
name Ada
→
Canonical RON
<bar !baz foo>
name Ada
→
{} JSON
{
  "foo": true,
  "bar": true,
  "baz": false,
  "name": "Ada"
}

The formatter collects all boolean members into one group before the other members. It sorts tags by their key names, without including the negation marker in the sort. The remaining members follow in key-name order.

Ordinary boolean members remain valid input. Input may contain multiple tag groups anywhere an object member can begin, including before, between, or after ordinary members. For example:

RON
<foo>
name Ada
age 36
baz false
<bar>
→
Canonical RON
<bar !baz foo>
age 36
name Ada
→
{} JSON
{
  "foo": true,
  "name": "Ada",
  "age": 36,
  "baz": false,
  "bar": true
}

Tag groups also balance names across rows when they exceed the width target. A group with attached comments uses separate lines so each comment stays with its name:

RON
<configuration development documentation experimental instrumentation observability performance production validation>
→
Canonical RON
<configuration development documentation
 experimental instrumentation observability
 performance production validation>
→
{} JSON
{
  "configuration": true,
  "development": true,
  "documentation": true,
  "experimental": true,
  "instrumentation": true,
  "observability": true,
  "performance": true,
  "production": true,
  "validation": true
}
RON
<tag00_longname tag01_longname tag02_longname tag03_longname tag04_longname tag05_longname tag06_longname tag07_longname tag08_longname tag09_longname tag10_longname>
→
Canonical RON
<tag00_longname tag01_longname tag02_longname tag03_longname
 tag04_longname tag05_longname tag06_longname tag07_longname
 tag08_longname tag09_longname tag10_longname>
→
{} JSON
{
  "tag00_longname": true,
  "tag01_longname": true,
  "tag02_longname": true,
  "tag03_longname": true,
  "tag04_longname": true,
  "tag05_longname": true,
  "tag06_longname": true,
  "tag07_longname": true,
  "tag08_longname": true,
  "tag09_longname": true,
  "tag10_longname": true
}

Duplicate names are errors both within a group and across groups or ordinary members. For example, <foo !foo> and {<foo> foo false} are invalid.

Table of Contents

8.1. Names and quoting ↑

Tag names are strings, including spellings such as true, null, or 42. Names are separated by whitespace, even when the group spans lines. Commas do not separate tags. The closing > ends the group rather than becoming part of a bare name. Use ordinary key quoting for names containing whitespace or delimiters, or beginning with a literal !. The negation marker goes outside the quoted name:

RON
<"display name" !"not active" "!literal">
→
Canonical RON
<"!literal" "display name" !"not active">
→
{} JSON
{
  "display name": true,
  "not active": false,
  "!literal": true
}

The literal ! in "!literal" is part of the key and participates in sorting. The negation marker before "not active" does not.

An empty key can be written "", or !"" for a false value. A negation marker without a name is invalid.

A tag group is recognized only where an object member can begin. An ordinary object key beginning with < must be quoted. Inside a bare string, after its first character, < and > are ordinary text, as in a<b.

8.2. Tag-only maps ↑

A tag group may also stand alone in value position: in a list, as a member value, or as a tagged element's payload. There it is a whole map whose members are its tags, so <a !b> reads as {a true b false}. The formatter writes every nonempty map whose members are all booleans this way, without braces. A map with any other member keeps its braces, and the empty map stays {}.

RON
list [{<b a>} {x true y false} {}]
member {active true}
nested {outer {inner {<on>}}}
prefixed #err {<integer overflow>}
mixed {<a> n 1}
→
Canonical RON
list [<a b>
      <x !y>
      {}]
member <active>
mixed {<a>
       n 1}
nested {outer {inner <on>}}
prefixed #err <integer overflow>
→
{} JSON
{
  "list": [
    {
      "b": true,
      "a": true
    },
    {
      "x": true,
      "y": false
    },
    {}
  ],
  "member": {
    "active": true
  },
  "nested": {
    "outer": {
      "inner": {
        "on": true
      }
    }
  },
  "prefixed": {
    "#err": {
      "integer": true,
      "overflow": true
    }
  },
  "mixed": {
    "a": true,
    "n": 1
  }
}
RON
list [<b a> <!c>]
member <active>
prefixed #err <overflow integer>
nested [[<a>] {k <b>}]
→
Canonical RON
list [<a b> <!c>]
member <active>
nested [[<a>]
        {k <b>}]
prefixed #err <integer overflow>
→
{} JSON
{
  "list": [
    {
      "b": true,
      "a": true
    },
    {
      "c": false
    }
  ],
  "member": {
    "active": true
  },
  "prefixed": {
    "#err": {
      "overflow": true,
      "integer": true
    }
  },
  "nested": [
    [
      {
        "a": true
      }
    ],
    {
      "k": {
        "b": true
      }
    }
  ]
}

A string that begins with < is therefore quoted:

RON
quoted ["<foo" "<" "<=" a<b]
→
Canonical RON
quoted ["<foo" "<" "<=" a<b]
→
{} JSON
{
  "quoted": [
    "<foo",
    "<",
    "<=",
    "a<b"
  ]
}

A tag-only map ends on the line where it opens. An empty group <> is invalid, since {} already writes the empty map.

8.3. Nested and empty objects ↑

Tags work in every object, including nested objects and objects containing only boolean members:

RON
person {
  <active !admin>
  name Ada
}
flags {<readable !writable>}
→
Canonical RON
flags <readable !writable>
person {<active !admin>
        name Ada}
→
{} JSON
{
  "person": {
    "active": true,
    "admin": false,
    "name": "Ada"
  },
  "flags": {
    "readable": true,
    "writable": false
  }
}

A tags-only document such as <active> represents an implicit outer object, just as ordinary members do. It is already in canonical form. An empty group <> contributes no members and is omitted by the formatter. An object with no members formats as {}.

For example, this input:

RON
<>
name Ada
→→
{} JSON
{
  "name": "Ada"
}

8.4. Comments ↑

Tag groups may span lines and contain comments using the ordinary comment rules. Comments are stored in the enclosing object's $comment member, using the key name without the negation marker. Formatting preserves whether a comment appears before a tag or inline after it, and keeps it with that key when tags are sorted. When the first tag has no leading comment, canonical output places it immediately after <; later tags and their comments align beneath it. If a comment must precede the first tag, < stays on a line of its own. A closing > follows the final tag unless a trailing comment requires it on the next line.

For example, this input:

RON
<
  !admin ; Access restrictions
  active
>
→
Canonical RON
<active
 !admin ; Access restrictions
>
→
{} JSON
{
  "$comment": "admin: Access restrictions",
  "admin": false,
  "active": true
}

If an earlier tag has a trailing comment and the final tag does not, the closing > follows the final tag:

RON
<
  !admin
  active ; Enabled by default
>
→
Canonical RON
<active ; Enabled by default
 !admin>
→
{} JSON
{
  "$comment": "active: Enabled by default",
  "admin": false,
  "active": true
}

A comment on a separate line immediately before !admin instead uses admin:: Access restrictions.

RON
<
  ; Enabled by default
  active
  !admin
>
→
Canonical RON
<
  ; Enabled by default
  active
  !admin>
→
{} JSON
{
  "$comment": "active:: Enabled by default",
  "active": true,
  "admin": false
}