std.regex — Reguläre Ausdrücke

Zurück zur Unit-Übersicht

Reguläre Ausdrücke mit einer rekursiven Backtracking-Engine: Zeichenklassen, Quantoren, Anker, Alternation und verschachtelte Gruppen. Sucht in einem pchar, liefert Trefferpositionen und kann Treffer in einen Zielpuffer ersetzen.

Einsatzbereiche: Eingabeprüfung, Log-Auswertung, Textextraktion, Protokollanalyse.

Autor: Andreas Röne
Copyright: 2024–2025 Andreas Röne
Quelle: std/regex.lyx

<WRAP info> Seit lyxc 1.0.20F arbeitet die Unit vollständig (#1466, #1467, #1468, #1378). Die drei früher hier verzeichneten Mängel sind behoben — nachgemessen:

RegexReplaceAlloc("[0-9]", "a1b2c3", "#")  →  a#b#c#
RegexReplace(...)                          →  3   (zaehlt die Treffer)
RegexMatch("^a.c$", "abc")                 →  1
RegexMatchEx("^ABC$", "abc", 1)            →  1   (ignore-case wirkt)
RegexMatchEx("^ABC$", "abc", 0)            →  0

Zu beachten bleibt die Arbeitsteilung der Namen: RegexReplaceAlloc liefert die ersetzte Zeichenkette, RegexReplace die Anzahl der Ersetzungen. Wer den Text braucht, nimmt die Alloc-Fassung oder RegexReplaceInto mit eigenem Zielpuffer. </WRAP>

Import

import std.regex;


Unterstützte Syntax

Alle Angaben mit lyxc 1.0.21A nachgeprüft.

Konstrukt Bedeutung Beispiel
. ein beliebiges Zeichen a.c passt auf abc
* null- oder mehrmals ab*c passt auf ac und abbbc
+ ein- oder mehrmals ab+c passt nicht auf ac
? null- oder einmal ab?c passt auf ac
{n} genau n-mal a{2} passt nicht auf a
{n,} mindestens n-mal a{3,} braucht drei a
{n,m} n- bis m-mal [a-z]{2,3}
[…] Zeichenklasse, Bereiche mit - [a-z0-9]
[&#94;…] negierte Klasse [&#94;0-9]
&#94; Textanfang &#94;GET
$ Textende \.txt$
| Alternation foo|bar
(…) Gruppe, auch verschachtelt und mit Quantor (ab)+c, (a(b|c))d
\d \w \s Ziffer, Wortzeichen, Leerraum \d{4}-\d{2}-\d{2}
\. \$ Sonderzeichen maskieren a\.c passt nur auf a.c

Ein leeres Muster passt an Position 0. Ein Muster mit doppeltem Quantor (a) findet nichts, statt einen Fehler zu melden. Nicht unterstützt: Rückwärtsreferenzen (\1), Lookahead/Lookbehind, nicht-gierige Quantoren (*?), benannte Gruppen, Unicode-Klassen. Zeichenklassen arbeiten byteweise — bei UTF-8 passt . auf ein Byte, nicht auf ein Zeichen. —- ===== Funktionen ===== ==== Verwendbare API ==== ^ Signatur ^ Beschreibung ^ | RegexSearch(pattern, text: pchar): int64 | Position des ersten Treffers, oder -1 | | RegexMatch(pattern, text: pchar): bool | Ob es irgendwo einen Treffer gibt — kein Vollstring-Vergleich | | RegexSearchSpan(pattern, text: pchar, startOut, endOut: int64): bool | Schreibt Start und Ende des Treffers per poke64 in zwei Puffer | | RegexFindAll(pattern, text: pchar, out: int64, maxMatches: int64): int64 | Alle nicht überlappenden Treffer; out nimmt je 8 Byte einen pchar-Zeiger auf | | RegexReplaceInto(dest, pattern, text, replacement: pchar): int64 | Schreibt den ersetzten Text nach dest, liefert die Zahl der Ersetzungen | Match, Search und Replace sind Kurznamen derselben Funktionen; _RegexMatch, _RegexSearch und _RegexReplace die internen Fassungen dahinter. RegexMatch ist kein Test auf vollständige Übereinstimmung: RegexMatch(„abc“, „xxabcxx“) liefert true. Wer den ganzen Text prüfen will, verankert das Muster selbst mit &#94;…$. ==== Hilfsfunktionen für Zeichenklassen ==== ^ Signatur ^ Beschreibung ^ | isAlpha(c: int64): int64 | Buchstabe | | isDigit(c: int64): int64 | Ziffer | | isAlnum(c: int64): int64 | Buchstabe oder Ziffer | | isWhitespace(c: int64): int64 | Leerraum | | isWordChar(c: int64): int64 | Wortzeichen (Buchstabe, Ziffer, Unterstrich) | Alle liefern 1 oder 0 als int64, nicht bool. ==== Interna ==== atomLen, atomMatches, matchHere und regexSearchFrom sind der Kern der Engine. Sie sind öffentlich, gehören aber nicht zur beabsichtigten Schnittstelle — ihre Signaturen können sich ändern. ==== Zustand der früher defekten Funktionen ==== ^ Signatur ^ Zustand (1.1.2E nachgemessen) ^ | RegexReplace/Replace/RegexReplaceEx | ersetzen; RegexReplace liefert die Anzahl, RegexReplaceAlloc/Into den Text (#1466) | | RegexMatchEx/RegexSearchEx/ReplaceEx | flags wirken: IGNORE_CASE → 1, ANCHORED → −1 (#1467), MULTILINE seit #1564 | | RegexCaptureCount/Start/End/Text | liefern echte Werte: (a)(b) auf zab ergibt CaptureCount = 3, Gruppe 1 von 1 bis 2 (#1468) | | RegexSearchCompiled/MatchCompiled/ReplaceCompiled und die *Ex-Varianten | benutzbar, seit RegexCompile den Bytecode erzeugt (#1563) | ==== Konstanten ==== ^ Name ^ Wert ^ Bedeutung ^ | REGEX_FLAG_IGNORE_CASE | 1 | Groß-/Kleinschreibung ignorieren | | REGEX_FLAG_ANCHORED | 2 | nur am Textanfang suchen | | REGEX_FLAG_MULTILINE | 4 | Anker an Zeilengrenzen | | REGEX_MAX_CAPTURE_SLOTS | 32 | Obergrenze für Capture-Gruppen | | REGEX_STACK_MAX | 256 | Tiefe des Backtracking-Stapels | <box tip> > Alle drei Flags wirken. Frühere Fassungen dieser Seite nannten sie wirkungslos; mit 1.0.21A einzeln nachgemessen: > >

> RegexMatchEx("^ABC$", "abc", 0)                        → false
> RegexMatchEx("^ABC$", "abc", REGEX_FLAG_IGNORE_CASE)   → true
>
> RegexSearchEx("b", "ab", 0)                            → 1  (gefunden)
> RegexSearchEx("b", "ab", REGEX_FLAG_ANCHORED)          → -1 (nur am Anfang)
>
> RegexSearchEx("^zweite", "erste\nzweite", 0)                       → -1
> RegexSearchEx("^zweite", "erste\nzweite", REGEX_FLAG_MULTILINE)    → 6
> 
> > Die Flags werden mit | kombiniert und den *Ex-Fassungen der Funktionen übergeben. </box> —- ===== Beispiele ===== ==== Muster prüfen ====
import std.regex;

fn pruefe(pat: pchar, txt: pchar): void {
    var pos: int64 := RegexSearch(pat, txt);
    if (pos >= 0) { PrintLn("  '", pat, "' passt auf '", txt, "' ab Position ", IntToStr(pos)); }
    else          { PrintLn("  '", pat, "' passt nicht auf '", txt, "'"); }
}

fn main(): int64 {
    PrintLn("Zeichenklassen und Quantoren:");
    pruefe("[0-9]+", "Bestellung 4711");
    pruefe("\\d{4}-\\d{2}-\\d{2}", "am 2026-08-13 geliefert");
    pruefe("[a-z]+@[a-z]+\\.[a-z]{2,3}", "mail: [email protected]");

    PrintLn("Anker:");
    pruefe("^GET ", "GET /index.html");
    pruefe("^GET ", "POST /form");
    pruefe("\\.txt$", "bericht.txt");

    PrintLn("Alternation und Gruppen:");
    pruefe("(GET|POST|PUT) /", "PUT /api/v1");
    pruefe("(ab)+c", "xxababc");

    PrintLn("Escapes:");
    pruefe("a\\.c", "a.c");
    pruefe("a\\.c", "abc");
    return 0;
}
Ausgabe:
Zeichenklassen und Quantoren:
  '[0-9]+' passt auf 'Bestellung 4711' ab Position 11
  '\d{4}-\d{2}-\d{2}' passt auf 'am 2026-08-13 geliefert' ab Position 3
  '[a-z]+@[a-z]+\.[a-z]{2,3}' passt auf 'mail: [email protected]' ab Position 6
Anker:
  '^GET ' passt auf 'GET /index.html' ab Position 0
  '^GET ' passt nicht auf 'POST /form'
  '\.txt$' passt auf 'bericht.txt' ab Position 7
Alternation und Gruppen:
  '(GET|POST|PUT) /' passt auf 'PUT /api/v1' ab Position 0
  '(ab)+c' passt auf 'xxababc' ab Position 2
Escapes:
  'a\.c' passt auf 'a.c' ab Position 0
  'a\.c' passt nicht auf 'abc'
Im Lyx-Quelltext braucht jeder Backslash des Musters einen zweiten:
„\\d{4}“ ergibt das Muster \d{4}. ==== Treffer finden, ausmessen und ersetzen ====
import std.regex;
import std.alloc;

fn main(): int64 {
    var log: pchar := "10.0.0.1 - 200 - 10.0.0.7 - 404 - 192.168.1.9 - 500";

    // Alle Treffer auf einmal
    var out: int64 := alloc(16 * 8);
    var n: int64 := RegexFindAll("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", log, out, 16);
    PrintLn("gefundene IP-Adressen: ", IntToStr(n));
    var i: int64 := 0;
    while (i < n) {
        PrintLn("  ", peek64(out + i * 8) as pchar);
        i := i + 1;
    }

    // Position und Ende eines einzelnen Treffers
    var sb: int64 := alloc(8);
    var eb: int64 := alloc(8);
    if (RegexSearchSpan("[0-9]{3}", log, sb, eb)) {
        PrintLn("erster Statuscode: Zeichen ", IntToStr(peek64(sb)), " bis ", IntToStr(peek64(eb)));
    }

    // Ersetzen — nur RegexReplaceInto ersetzt tatsaechlich
    var dest: int64 := alloc(256);
    var anzahl: int64 := RegexReplaceInto(dest as pchar, "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", log, "<IP>");
    PrintLn("ersetzt: ", IntToStr(anzahl));
    PrintLn("Ergebnis: ", dest as pchar);
    return 0;
}
Ausgabe:
gefundene IP-Adressen: 3
  10.0.0.1
  10.0.0.7
  192.168.1.9
erster Statuscode: Zeichen 11 bis 14
ersetzt: 3
Ergebnis: <IP> - 200 - <IP> - 404 - <IP> - 500
Zwei Dinge zur Speicherverwaltung:
RegexFindAll legt jeden Treffer als eigenen Puffer an — der Aufrufer gibt sie mit free(zeiger, laenge + 1) einzeln frei. RegexReplaceInto prüft die Größe von dest nicht; der Puffer muss groß genug für den Ergebnistext sein. —- ===== Was nicht funktioniert ===== Geprüft mit lyxc 1.0.21A. ==== RegexReplace ersetzt nicht (#1466) ====
var text: pchar := "foo bar foo baz foo";
var n: int64 := RegexReplace("foo", text, "XXX");
PrintLn("Rueckgabe = ", IntToStr(n));
PrintLn("Text danach: '", text, "'");
Rueckgabe = 3
Text danach: 'foo bar foo baz foo'
Der Parameter
replacement wird im Rumpf nie verwendet; die Funktion zählt lediglich die Treffer. Als Zähler ist sie brauchbar — als Ersetzung nicht. Für tatsächliche Ersetzungen RegexReplaceInto mit Zielpuffer verwenden. ==== Flags und Capture-Gruppen ==== Beides arbeitet. Mit 1.1.2E nachgemessen:
RegexMatchEx("ABC", "abc", REGEX_FLAG_IGNORE_CASE)   = 1
RegexSearchEx("abc", "xxabc", REGEX_FLAG_ANCHORED)   = -1
RegexSearchEx("^zweite", "erste\nzweite", REGEX_FLAG_MULTILINE) = 6
RegexSearch("(a)(b)", "zab")  = 1
RegexCaptureCount()           = 3      (Gruppe 0 ist der Gesamttreffer)
RegexCaptureStart(1)/End(1)   = 1 / 2
REGEX_FLAG_MULTILINE wurde bis #1564 entgegengenommen, aber nicht ausgewertet. Jetzt gilt $ auch unmittelbar vor einem \n, und &#94; bekommt jede Stelle hinter einem \n als Ansatzpunkt — die Verankerung bleibt dabei erhalten (&#94;[a-z]+$ findet weiterhin nicht allo in Hallo). ==== Die kompilierte API ist benutzbar (#1563) ==== Der fehlende Erzeuger für das Bytecode-Programm ist da: RegexCompile(pattern: pchar, progOut: pchar, progMax: int64): int64 übersetzt ein Muster und liefert die Länge des Programms — 0 bei unübersetzbarem Muster oder zu kleinem Puffer. Unterstützt wird derselbe Sprachumfang wie in der Muster-API.
var prog: int64 := alloc(4096);
var n: int64 := RegexCompile("[0-9]+"c, prog as pchar, 4096);
PrintLn(IntToStr(n));                                                    // 54
PrintLn(IntToStr(RegexSearchCompiled(prog as pchar, n, "abc 4711"c)));   // 4
Das
r„…“-Literal ist dafür weiterhin nicht geeignet: es liefert den Rohtext des Musters, keinen Bytecode. Der Bytecode kommt ausschließlich aus RegexCompile. ==== Weitere Grenzen ==== * Byteweise Verarbeitung: . und Zeichenklassen arbeiten auf Bytes. Bei UTF-8 zählt . ein Byte eines mehrbyteigen Zeichens; [ä-ö] funktioniert nicht. * Backtracking ohne Schutz: Muster wie (a+)+b auf einer langen Kette von a können sehr lange laufen. REGEX_STACK_MAX begrenzt die Stapeltiefe, nicht die Laufzeit — Muster aus fremder Quelle sind damit ein Risiko. * Muster einmal übersetzen: Die Muster-API wertet das Muster bei jedem Aufruf neu aus. In heißen Schleifen lohnt RegexCompile einmal vorab und danach RegexSearchCompiled. —- ===== Verwandte Units ===== * std.stringStrFind, StrSub, StrSplit'' für einfache Fälle ohne Regex * std.validate — fertige Prüfungen für IBAN, ISO-Codes und Ähnliches * std.text — Textverarbeitung auf höherer Ebene