joetjen.net
EN DE
Configuration language

Grammar

CASC is defined by a PEG grammar written in Aether, the grammar language of Ichor. Both parsers — Elixir and Praxis — are generated from it rather than written by hand.

Format 1.0

Statements

The rules below are quoted from priv/grammar/casc.aether in the Cooper repository, with comments removed. Upper-case rules are tokens, matched longest-first; lower-case rules are the syntax.

casc.aether
file := version_header statement* TRIVIA?
version_header := "#" "@" kw:IDENT "="? version_number

statement             := disabled_statement | conditional_statement | real_statement
disabled_statement    := "#" real_statement
conditional_statement := env_guard real_statement
env_guard             := "${?" name:IDENT "}"

real_statement := var_decl | for_statement | import_statement
                | sigil_kv_statement | delete_statement
var_decl         := "@" private:"*"? name:IDENT "="? value
import_statement := import_kw:IDENT path:import_path
import_path      := DQ_STRING | SQ_STRING
for_statement    := for_kw:IDENT binding ("," binding)*
                    ((from_kw:IDENT template:key_path as_kw:IDENT dest:key_path)
                     | (as_kw:IDENT dest:key_path))
                    block
binding          := "@" name:IDENT (in_kw:IDENT iterable:iterable_value)?

sigil_kv_statement := sigil:("~" | "+" | "-")? key_path "="? rhs
delete_statement   := "-" key_path
rhs   := block | value
block := "{" (statement ","?)* "}"

key_path    := key_segment ("." key_segment)*
key_segment := secret:"*"? seg
seg         := IDENT | DQ_STRING | SQ_STRING

Values and references

value := NIL_KW | TRUE_KW | FALSE_KW | INF_KW | DATETIME | DATE | TIME | IPV6 | IPV4
       | DURATION | BYTES | FLOAT | INTEGER | TRIPLE_STRING | DQ_STRING | SQ_STRING
       | list | tuple | at_ref | env_ref | config_ref | resolver_ref | tagged_ref
       | atom_value
list  := "[" (value ","?)* "]"
tuple := "(" (value ","?)* ")"
atom_value := colon_atom | IDENT
colon_atom := ":" atom_word
atom_word  := IDENT | NIL_KW | TRUE_KW | FALSE_KW | INF_KW

at_ref     := "@{" name:ref_name idx_suffix? ref_suffix? filters? "}"
env_ref    := "${" name:ref_name env_bracket? ref_suffix? filters? "}"
config_ref := "%{" path:key_path idx_suffix? ref_suffix? filters? "}"
ref_name   := IDENT | DQ_STRING
resolver_ref := raw:RESOLVER_REF_RAW
tagged_ref   := "!" name:IDENT "(" arg:value ")"
idx_suffix   := "[" INTEGER "]"
env_bracket  := "[" INTEGER? "]"
ref_suffix   := (":" "?" msg:DQ_STRING) | (":" "+" alt:value) | (":" default:value)
filters      := ("|" filter)+
filter       := name:IDENT (":" arg:filter_arg)?
filter_arg   := DQ_STRING | SQ_STRING

Tokens

COMMENT  := "#" !("@" | ALPHA | "*") (!"\n" .)*
IDENT    := ALPHA (ALNUM | "_" | "+" | "-")* ("?" | "!")?
INF_KW   := ("+" | "-")? "inf"

INTEGER  := ("+" | "-")? ( "0x" HEX (HEX | "_")* | "0o" [0-7] ([0-7] | "_")*
                         | "0b" [01] ([01] | "_")* | DIGIT (DIGIT | "_")* )
FLOAT    := ("+" | "-")? DIGIT (DIGIT | "_")*
             ( "." DIGIT (DIGIT | "_")* (("e" | "E") ("+" | "-")? DIGIT+)?
             | ("e" | "E") ("+" | "-")? DIGIT+ )

DATE     := DIGIT{4} "-" DIGIT{2} "-" DIGIT{2}
TIME     := DIGIT{2} ":" DIGIT{2} ":" DIGIT{2} ("." DIGIT+)?
DATETIME := DATE "T" TIME (("Z") | (("+" | "-") DIGIT{2} ":" DIGIT{2}))?

IPV4     := DIGIT{1,3} "." DIGIT{1,3} "." DIGIT{1,3} "." DIGIT{1,3} ("/" DIGIT{1,3})?
IPV6     := (HEX_GROUP (":" HEX_GROUP){7}
            | (HEX_GROUP (":" HEX_GROUP)*)? "::" (HEX_GROUP (":" HEX_GROUP)*)?)
            ("/" DIGIT{1,3})?

DURATION_UNIT := "ns" | "us" | "µs" | "ms" | "d" | "h" | "m" | "s"
DURATION      := DIGIT+ ("." DIGIT+)? DURATION_UNIT (DIGIT+ DURATION_UNIT)*
BYTE_UNIT     := "PiB"i | "TiB"i | "GiB"i | "MiB"i | "KiB"i
               | "PB"i | "TB"i | "GB"i | "MB"i | "kB"i | "B"i
BYTES         := DIGIT+ ("." DIGIT+)? BYTE_UNIT

TRIPLE_STRING := "\"\"\"" (!"\"\"\"" .)* "\"\"\""
DQ_STRING     := "\"" ("\\" . | !"\"" .)* "\""
SQ_STRING     := "'" (!"'" .)* "'"

Interpolation inside double-quoted strings has a grammar of its own (casc_interp.aether): a string is a sequence of text chunks and embedded @{…}, ${…}, %{…}, !{…} and !tag(…) references, each ending at its first closing brace or parenthesis.

Where the implementation differs

The specification is ahead of the reference implementation in a few places. Until they meet, these are the portable choices:

SpecificationCurrentlyWrite
Comments and blank lines may precede the headerParse errorHeader on line 1
key "value" without =Read as an importAlways =
A file’s own statements win over its importsStrictly by positionImports first
_ separators in durations (1_000ms)Mis-lexed1000ms, 1s
Interpolated quoted keys anywhereResolved only inside loopsUse them in loop bodies and destinations
A \ at the end of a line continues a bare valueNot implementedA triple-quoted string

List merge by key (~items[id] = […]) is reserved in the specification and deliberately not part of the language yet.