joetjen.net
EN DE
Konfigurationssprache

Grammatik

CASC ist durch eine PEG-Grammatik in Aether definiert, der Grammatiksprache von Ichor. Beide Parser — Elixir und Praxis — werden daraus erzeugt statt von Hand geschrieben.

Format 1.0

Anweisungen

Die folgenden Regeln stammen aus priv/grammar/casc.aether im Cooper-Repository, ohne Kommentare. Regeln in Großbuchstaben sind Tokens, längste Übereinstimmung zuerst; Regeln in Kleinbuchstaben sind die 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

Werte und Verweise

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     := "'" (!"'" .)* "'"

Die Interpolation in doppelt gequoteten Zeichenketten hat eine eigene Grammatik (casc_interp.aether): Eine Zeichenkette ist eine Folge von Textstücken und eingebetteten Verweisen @{…}, ${…}, %{…}, !{…} und !tag(…), die jeweils an ihrer ersten schließenden Klammer enden.

Wo die Implementierung abweicht

An einigen Stellen ist die Spezifikation der Referenzimplementierung voraus. Bis beide sich treffen, sind dies die portablen Entscheidungen:

SpezifikationDerzeitSchreibweise
Kommentare und Leerzeilen dürfen vor der Kopfzeile stehenParse-FehlerKopfzeile in Zeile 1
key "value" ohne =Wird als Import gelesenImmer =
Eigene Anweisungen gewinnen gegen ImportsStreng nach PositionImports zuerst
_ als Trenner in Zeitdauern (1_000ms)Falsch zerlegt1000ms, 1s
Interpolierte Schlüssel überallNur in Schleifen aufgelöstNur in Schleifenrumpf und -ziel verwenden
Ein \ am Zeilenende setzt einen nackten Wert fortNicht implementiertEine dreifach gequotete Zeichenkette

Das Mischen von Listen nach Schlüssel (~items[id] = […]) ist in der Spezifikation vorgemerkt und bewusst noch nicht Teil der Sprache.