GitHub
teilweisepackages/parser-combinator/src

Parser Combinator

@ralphschuler/parser-combinator

Baut komplexe String- und Binärparser aus kleinen zustandsbehafteten Parsern und kombinierbaren Erfolgs- oder Fehlerwerten.

parsingbacktrackingbinary

01 · Problem

Wofür braucht man das?

Protokolle und Grammatiken brauchen Sequenz, Alternativen, Wiederholung und gute Fehlerpositionen, ohne einen monolithischen handgeschriebenen Parser zu erzeugen.

02 · Denkmodell

Das mentale Modell

Ein Parser ist eine pure Funktion von Inputzustand zu Erfolg plus neuem Offset oder Fehler plus Position. Kombinatoren verkabeln diese Zustandsübergänge.

Im Repository

Das umfangreiche Modul arbeitet bytebasiert mit DataView, Backtracking, User-State und vielen Kombinatoren. Mehrere Typen sind falsch, Views verlieren Offset/Länge und Wiederholung kann bei Parsern ohne Fortschritt endlos laufen.

03 · Kontrollfluss

Was passiert in welcher Reihenfolge?

  1. Input als unveränderliche Byteview plus aktuellen Offset modellieren.
  2. Primitive lesen genau einen Vertrag und liefern neuen Zustand oder erwartetes Token.
  3. Sequenz reicht Erfolg weiter; Alternative startet vom ursprünglichen Zustand.
  4. Bei mehreren Fehlern die weiteste Position und erwartete Tokens zusammenführen.
  5. parseAll kombiniert den Zielparser mit einem expliziten End-of-input-Parser.

04 · Bauteile

Die entscheidenden Verträge

Parser.map / chain / ap Transformiert Resultate oder wählt den nächsten zustandsabhängigen Parser.
sequenceOf / choice / many / exactly Kombiniert Reihenfolge, Alternativen und Wiederholung.
char / str / regex / digit / letter Stringprimitive auf einem UTF-8-Byteinput.
lookAhead / possibly / recursiveParser Kontrolliert Konsum, Optionalität und rekursive Grammatiken.

05 · Build it yourself

Selbst implementieren

Beginne mit einem minimalen Result-Typ und map/flatMap/orElse. Wiederholung und Komfortsyntax kommen erst nach korrekter Fortschritts- und Fehlersemantik.

  1. Definiere State, Success und Failure mit explizitem Offset.
  2. Implementiere map, flatMap und orElse ohne den Eingabestate zu mutieren.
  3. Breche many ab, wenn ein erfolgreicher Parser den Offset nicht erhöht.
minimal.ts · unabhängig vom Package
type State = { input: Uint8Array; offset: number };
type Result<T> =
  | { ok: true; value: T; state: State }
  | { ok: false; offset: number; expected: Set<string> };

class Parser<T> {
  constructor(readonly parse: (state: State) => Result<T>) {}
  map<U>(fn: (value: T) => U) {
    return new Parser<U>(state => {
      const result = this.parse(state);
      return result.ok ? { ...result, value: fn(result.value) } : result;
    });
  }
}

06 · Verifizieren

Was du testen solltest

  • Sequenz, Alternative und Backtracking erhalten den korrekten ursprünglichen oder fortgeschrittenen Offset.
  • many erkennt einen erfolgreichen Nullfortschritt und terminiert mit einem definierten Fehler.
  • UTF-8, TypedArray-Slices, Inputende und der weiteste kombinierte Fehler werden geprüft.

07 · Grenzen

Kompromisse und Stolperfallen

  • Vollständiges Backtracking kann exponentiell werden; commit/cut begrenzt bewusste Alternativen.
  • Bytepositionen sind für Binärdaten korrekt, für Nutzerfehler in Unicode-Texten aber erklärungsbedürftig.
  • Ein gemeinsamer Parser für Text und Binärdaten erhöht Flexibilität und Typkomplexität zugleich.
Wichtig

Jeder Wiederholungskombinator braucht eine Fortschrittsinvariante: Erfolg ohne Inputverbrauch darf nicht erneut in derselben Schleife laufen.

08 · Weiterdenken

Quellcode und Nachbarn

Originalcode auf GitHub ansehen