NDL Language

NDL (nested data language) is a data serialization and config language for representing nested structures.

// line comment
/* block comment /* with nesting */ */

scene {
    size { x 1920 y 1080 }
    camera.type "orthographic"
    layers [ {
        name "background"
        textures [ `background.png` `mask.png` ]
        scale { x 1.2 y 1.0 }
    } {
        name "foreground"
        enabled false
    } ]
}

Design principles

Goals:

Non-goals:

Specification

Keys are arbitrary strings, that may be represented either:

There are following types of values:

NDL document is a valid UTF-8 text containing exactly one value of any type. If this type is map, {} brackets must be omitted. Empty (or comment-only) document is an empty map. NDL document file extension is .ndl.

A key in the map may be written as dot-separated path, e.g. k1.k2.kN. In this case, nested maps are created for each path level, e.g. k1 {}, k1 { k2 {} }, etc. No whitespace is allowed between path parts and dots.

Formal grammar
document = trivia (pairs | array | primitive)? trivia eof
value = map | array | primitive
primitive = string | real | int | bool | null

map = '{' trivia pairs? trivia '}'
array = '[' trivia (value (separator value)*)? trivia ']'
pairs = pair (separator pair)*
pair = key_path separator value

key_path = key ('.' key)*
key = unquoted_key | quoted_key
unquoted_key = [a-zA-Z_][a-zA-Z0-9_-]* and not reserved_keyword
reserved_keyword = 'null' | 'true' | 'false' | 'inf' | 'nan'

string = raw_string | interpreted_string
raw_string = '`' raw_char* '`'
interpreted_string = '"' interpreted_char('"')* '"'
quoted_key = '\'' interpreted_char('\'')* '\''
raw_char = any character except '`'
interpreted_char(quote) = escape | any character except '\\' and quote
escape = '\\n' | '\\t' | '\\'' | '\\\"' | '\\\\' | unicode_escape
unicode_escape = '\\u{' hex_digit{1,6} '}'

int = decimal | hexadecimal | binary
decimal = -?(0|[1-9][0-9]*)
hexadecimal = -?0x[0-9A-Fa-f]+
binary = -?0b[01]+

real = decimal_dot | scientific | 'inf' | '-inf' | 'nan'
decimal_dot = -?(0|[1-9][0-9]*)\.[0-9]+
scientific = -?(0|[1-9][0-9]*)(\.[0-9]+)?[eE]-?(0|[1-9][0-9]*)

bool = 'true' | 'false'
null = 'null'

separator = (whitespace | comment)+
trivia = (whitespace | comment)*
comment = line_comment | block_comment
whitespace = (' ' | '\t' | line_break)+
line_comment = '//' (any character except line_break)*
line_break = '\r\n' | '\n' | '\r'
block_comment = '/*' (block_comment | any character not forming '/*' and '*/')* '*/'

After expanding key paths, maps at the same path are merged recursively. If multiple map values have the same path, they are merged. Map/non-map and non-map/non-map conflicts are invalid (NDL does not allow value overriding).

// speaking less formally, this is allowed
category.sub1 { key1 "val1" key2 "val2" }
category.sub2 { key1 "val1" key2 "val2" }
category { key "val" }

// and is interpreted as
category {
    sub1 { key1 "val1" key2 "val2" }
    sub2 { key1 "val1" key2 "val2" }
    key "val"
}

Dotted paths

Maps and arrays may contain values of any type, including other maps and arrays. For any value, it is possible to write a path to it - a sequence of map keys and array indices. NDL semantics allows to unambiguously represent a path to any value as a string, constructed as follows:

category { array [ { 'weird key' "val" } ] }
// a path to the value "val" here can be
// represented as category.array.0.'weird key'

This is a convenient way to refer to a value by its path, and is recommended to implement by libraries. Though, you can't write dotted paths with array indices in NDL code itself, due to ambiguity problems it would cause.

Style guide

It is recommended to format NDL documents as follows:

Note that style guide is not part of language specification, and therefore can be changed without bumping standard version.

Why another standard

You may have thought of the "how standards proliferate" meme. While there are many existing standards that do similar thing, NDL may work better for several use cases. Here's how it compares to existing popular languages:

After all, having more alternatives is never a bad thing. You aren't forced to use the new alternative - you choose it yourself when fits your needs better. If it doesn't, you don't use it, and its plain existence doesn't make worse.