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.
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.
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_STRINGWerte 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_STRINGTokens
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:
| Spezifikation | Derzeit | Schreibweise |
|---|---|---|
| Kommentare und Leerzeilen dürfen vor der Kopfzeile stehen | Parse-Fehler | Kopfzeile in Zeile 1 |
key "value" ohne = | Wird als Import gelesen | Immer = |
| Eigene Anweisungen gewinnen gegen Imports | Streng nach Position | Imports zuerst |
_ als Trenner in Zeitdauern (1_000ms) | Falsch zerlegt | 1000ms, 1s |
| Interpolierte Schlüssel überall | Nur in Schleifen aufgelöst | Nur in Schleifenrumpf und -ziel verwenden |
Ein \ am Zeilenende setzt einen nackten Wert fort | Nicht implementiert | Eine dreifach gequotete Zeichenkette |
Das Mischen von Listen nach Schlüssel (~items[id] = […]) ist in der Spezifikation vorgemerkt und bewusst noch nicht Teil der Sprache.