the “sn” config file format

short for “string notation”. Read this for some of the design thoughts.

Currently exists in a half-finished recursive descent form in season2 but the fancy features are unused, I’m not confident enough to deploy the whole thing in a real mod

lexing

Part of what makes sn weird is that it has unquoted raw strings and whitespace is permitted inside them. The complexity for that lives in the lexer.

Normal tokens are {: LCURLY, }: RCURLY, [: LBRACK, ]: RBRACK, =: EQ, ,: COMMA, NEWLINE, and EOF. There is additionally a STRING token, tokenizable in two different ways:

TODO how did commas work again, I think in the recursive-descent parser they were “parsed as whitespace” but only in arrays

parsing

Pretend this is well-specified.

Value ::= STRING | Obj | Array
Delim ::= COMMA | NEWLINE | EOF

Obj ::= LCURLY Delim* ObjValues? RCURLY
ObjValues ::= STRING EQ Value? Delim* (Delim ObjValues)?

Array ::= LBRACK Delim* ArrayItems? RBRACK
ArrayItems ::= Value Delim* (Delim ArrayItems)?

File ::= Delim* ObjValues?

It’s awfully generous with delimiters.

In objvalues, if a value isn’t parsed it’s treated as "". This allows “blanking out” options to set them to the empty string (todo finetune this rule?)

The key, =, and value have to be on the same line. I might allow NEWLINE* before the equal sign but it can’t go after because of the “blanking out” rule

Parsing without a “parser generator” is straightforward but maybe a little awkward

Two STRINGs can never be adjacent in this grammar

TODO: consider ObjValues ::= STRING (EQ Value | Obj | Array) ..., making the equal-sign optional if you’re defining an object or array. Sub-blocks look nice with this scheme (it’s groovy-ish) but could be error-prone.

blanking out rule

It’s impossible to support both of these:

the “i want a linebreak before my long message”

motd = {
  message = 
  "Hello welcome to my server"
  
  subtitle =
  "Enjoy your stay! Visit our website..."
}

the “remove options by erasing their value”

filters {
  animal =
  vegetable =
  mineral =
  "dealer's choice" =
}