Reference this for the Layers EBNF grammar rules.
(* EBNF - Layers *)
(* ===== LEXICAL GRAMMAR =====
Defines tokens from characters.
- Horizontal whitespace and comments are discarded.
- Leading indentation is emitted as INDENT/DEDENT;
line ends as NEWLINE.
- If a token begins with a digit, or a `-` followed
by a digit, it is a number.
- `true` and `false` are reserved for bool, never
lexed as a word.
- Lines containing only whitespace and/or comments
produce no tokens.
- Parser accepts CRLF and LF; serializer emits LF.
- At end of file, a final NEWLINE is emitted if the
last line lacks one, followed by DEDENTs until
indentation returns to column zero. Lastly, an
ENDFILE is emitted. *)
bin = "0" | "1" ;
dec = bin | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
hex = dec | "a" | "b" | "c" | "d" | "e" | "f" |
"A" | "B" | "C" | "D" | "E" | "F" ;
alphanum = hex | "g" | "h" | "i" | "j" | "k" | "l" |
"G" | "H" | "I" | "J" | "K" | "L" |
"m" | "n" | "o" | "p" | "q" | "r" | "s" |
"M" | "N" | "O" | "P" | "Q" | "R" | "S" |
"t" | "u" | "v" | "w" | "x" | "y" | "z" |
"T" | "U" | "V" | "W" | "X" | "Y" | "Z" ;
number = [ "-" ], dec, { dec }, [".", dec, { dec } ] ;
any-char = ? any Unicode scalar value ? ;
lf-char = ? U+000A LINE FEED ? ;
cr-char = ? U+000D CARRIAGE RETURN ? ;
string-char = any-char - ( '"' | "\" | lf-char | cr-char ) ;
escape = "\", ( '"' | "\" | "n" | "t" ) ;
string = '"', { string-char | escape }, '"' ;
color = "#", hex, hex, hex, [ hex ] |
"#", hex, hex, hex, hex, hex, hex, [ hex, hex ] ;
wordchar = alphanum | "-" | "_" ;
word = wordchar, { wordchar } ;
bool = "true" | "false" ;
comment = "//", { any-char - ( lf-char | cr-char ) } ;
NEWLINE = [ cr-char ], lf-char ;
INDENT = ? increase in leading-space count ? ;
DEDENT = ? decrease in leading-space count ? ;
ENDFILE = ? end of input ? ;
(* ===== SYNTACTIC GRAMMAR =====
- Adjacent words in a name are joined with single spaces,
- EXCEPT empty words in expansion where there's nothing
to join.
- Specifically, an item is the name of either a layer or
an attribute.
- A reference is shaped like a declaration whose segments
are plain words; expansion in references is invalid. *)
words = word, { word } ;
expansion = "(", [ words ], { ",", [ words ] }, ")" ;
expandable = word | expansion ;
name = expandable, { expandable } ;
traversal = name, { "/", name }, "/" ;
group = name, "." ;
item = name ;
declaration = [ traversal ], [ group ], item ;
reference = [ words, { "/", words }, "/" ], [ words, "." ], words ;
value = number | string | color | bool ;
expression = value | reference ;
conditions = "@", word, { "&", word } ;
state = conditions, ":", expression ;
attr = declaration, ":", expression,
( [ state, { state } ], NEWLINE |
NEWLINE, INDENT, state, NEWLINE, { state, NEWLINE }, DEDENT ) ;
group-set = [ traversal ], group, NEWLINE, INDENT, attr, { attr }, DEDENT ;
state-set = conditions, ":", NEWLINE,
INDENT, ( attr | group-set ), { ( attr | group-set ) }, DEDENT ;
statement = layer | attr | group-set | state-set ;
definition = NEWLINE, INDENT, statement, { statement }, DEDENT ;
layer = declaration, [ "<<" , reference ], ( NEWLINE | ":", definition ) ;
file = { statement }, ENDFILE ;