Skip to content

DSL-Grammatik

Formale Referenz der CodeCharter-DSL. Was der Parser akzeptiert, in EBNF-naher Notation.

Verhältnis zu anderen Seiten: diese Seite ist der formale Grammatik-Anhang. Für den alltäglichen DSL-Einsatz (Datei-Aufbau, Query-Formen, Collections-Übersicht) gibt es die Syntax-Übersicht. Alle abfragbaren Properties aller Modell-Typen stehen im Prädikat-Katalog.

Diese Seite ist die formale Wahrheit. Wenn Sie unsicher sind, ob ein Konstrukt erlaubt ist, schauen Sie hier nach. Für den alltäglichen Sprachgebrauch gibt es die kompakte Syntax-Übersicht, die nach Anwendungsfall sortiert ist.

Datei-Struktur

<file>            ::= <directive>* <query>
<directive>       ::= '@' <name> <directive-value>
<directive-value> ::= <string-literal> | <identifier>
<query>           ::= <query-syntax> | <fluent-syntax>

Eine .ccr-Datei enthält null oder mehr Direktiven, gefolgt von genau einer Query. Der Parser lehnt nachfolgenden Inhalt nicht ab: alles nach der ersten Query wird stillschweigend ignoriert. Schreiben Sie deshalb nur eine Query pro Datei.

Direktiven

Direktiven setzen Metadaten. Alle sind optional. Unbekannte Direktiv-Namen werden ignoriert, damit Regeln zukunftskompatibel bleiben.

Direktive Pflicht Wert Beschreibung
@name nein string Menschenlesbarer Titel der Regel. Default: Dateiname ohne Endung
@severity nein enum info, warn (auch warning), error. Default warn; unbekannte Werte fallen ebenfalls auf warn zurück
@category nein string Frei wählbare Gruppe (z.B. Async, Design). Default General
@description nein string Eine Zeile, was die Regel prüft
@recommendation nein string Eine Zeile, wie man es behebt

Strings nutzen C-Escapes für \", \\, \n, \r, \t, \0. Unbekannte Escapes bleiben unverändert stehen, damit "Handler\d*$" für Regex-Patterns natürlich bleibt.

Query-Syntax (LINQ-Form)

<query-syntax>    ::= 'from' <id> 'in' <id>
                      ( <let-clause> | <where-clause> )*
                      <orderby-clause>?
                      'select' <expression>

<let-clause>      ::= 'let' <id> '=' <expression>
<where-clause>    ::= 'where' <expression>
<orderby-clause>  ::= 'orderby' <expression> ('asc' | 'desc')?

Zwei semantische Hinweise:

  • orderby wird vom Parser akzeptiert, hat in dieser Form aber derzeit keine Wirkung: die Ergebnisse behalten die Reihenfolge der Collection. Zum Sortieren nutzen Sie stattdessen die Fluent-Methoden OrderBy/OrderByDescending.
  • Alle let-Klauseln werden vor allen where-Klauseln ausgewertet, auch wenn sie im Quelltext gemischt stehen.

Beispiel:

from t in Types
where t.Kind == "Class"
let methodCount = t.Methods.Count
where methodCount > 20
orderby methodCount desc
select t

Fluent-Syntax (Method-Form)

<fluent-syntax> ::= <id> <method-call>+
<method-call>   ::= '.' <id> ( '(' ( <argument> (',' <argument>)* )? ')' )?
<argument>      ::= <expression> | <lambda>
<lambda>        ::= <id> '=>' <expression>

Argumente werden durch Kommas getrennt. Aufrufe ohne Argumente wie .Count(), .Any() oder .ToLower() funktionieren sowohl als Schritte der äußeren Fluent-Kette als auch innerhalb von Ausdrücken oder Lambda-Bodys, etwa in einer where-Bedingung: m.Name.ToLower() wird als Methodenaufruf geparst, nicht als Zugriff auf ein Property.

Beispiel:

Methods.Where(m => m.IsAsync && m.Parameters.Count > 5)

Bei reinen Filtern treffen beide Formen dieselben Entitäten, sie sind aber nicht vollständig austauschbar: Sortieren kann nur die Fluent-Form (siehe den Hinweis zu orderby oben), und bei einem Auswertungsfehler überspringt die LINQ-Form nur das betroffene Element, während die Fluent-Form die ganze Regel ohne Findings abbricht. Bei mehreren where-Bedingungen liest sich die LINQ-Form meist besser. Bei kurzen Filter-Ketten ist Fluent oft kompakter.

Ausdrucksgrammatik

Operatoren von festester zu lockerster Bindung:

  1. Primär: Literale, Identifier, ( expr )
  2. Member-Access und Method-Call: a.b, a.b(args)
  3. Unär: !expr, -expr
  4. Multiplikativ: *, /, %
  5. Additiv: +, -
  6. Vergleich: ==, !=, >, >=, <, <=
  7. Logisches UND: &&
  8. Logisches ODER: ||

Literale die der Lexer kennt: Integer, Float, String ("..."), true, false.

Ein Identifier (<id>) beginnt mit einem Buchstaben oder Unterstrich, gefolgt von beliebig vielen Buchstaben, Ziffern oder Unterstrichen.

null ist kein Lexer-Literal, sondern wird bei der Auswertung als spezieller Identifier aufgelöst. Vergleiche wie t.BaseType == null sind daher erlaubt.

Whitespace und Kommentare

  • Whitespace zwischen Tokens ist bedeutungslos.
  • Zeilenkommentare beginnen mit // und gehen bis Zeilenende.
  • Es gibt keine Block-Kommentare in .ccr-Dateien.

Was nicht geht

Die DSL ist absichtlich klein und seiteneffektfrei:

  • Kein File-IO aus einem Query-Body.
  • Kein mutabler State, jedes Property ist ein Read.
  • Kein async, Auswertung ist synchron über dem gecachten Code-Modell.

Wenn Ihnen ein Prädikat fehlt, melden Sie uns den Anwendungsfall bitte als kurze Anfrage, statt auf Reflection auszuweichen. Die DSL wächst absichtlich, nicht zufällig.

Wo geht's weiter