Zeichenketten in Lyx gibt es auf zwei Ebenen: pchar — eine nullterminierte Bytefolge, schnell und roh — und Text aus std.text, das UTF-8 versteht und Codepoints, Grapheme und Normalisierung kennt. Diese Seite zeigt, wann welche Ebene richtig ist, welche Funktionen tragen und wo die Fallen liegen.
→ std.string · std.text · std.unicode · std.grapheme · std.regex
pchar + std.string | Text + std.text |
|
|---|---|---|
| Einheit | Byte | Codepoint / Graphem |
| Speicher | Aufrufer stellt Zielpuffer | Unit verwaltet, TextFree gibt frei |
| Länge | Bytes bis zur 0 | TextByteLength und TextCodepointCount |
| Umlaute, Emoji | zählt Bytes, schneidet mitten hinein | korrekt |
| Groß-/Kleinschreibung | nur ASCII | voll (std.unicode_case) |
| Geschwindigkeit | maximal | eine Indirektion mehr |
Faustregel: Protokolle und Dateiformate mit pchar, Benutzertexte mit Text. Ein HTTP-Header, ein Dateiname, ein Schlüssel in einer Konfiguration — das sind Bytefolgen. Ein Name, eine Adresse, eine Eingabe aus einem Formular — das ist Text, und dort kostet Byte-Denken Datenverlust.
var s: pchar := "hallo"c; // Literal mit c-Suffix: pchar
var t: Text := TextFromPchar(s as int64); // dieselbe Folge als Text
import std.io;
import std.alloc;
import std.string;
fn main(): int64 {
PrintLn(IntToStr(StrFind("hallo welt"c, "welt"c))); // 6
PrintBoolLn(StrContains("hallo welt"c, "wel"c)); // true
PrintLn(IntToStr(StrCount("a,b,c,d"c, ","c))); // 3
PrintLn(StrTrim(" text "c)); // text
var d1: int64 := alloc(256);
PrintLn(StrSubstring(d1 as pchar, "hallo welt"c, 6, 4)); // welt
var d2: int64 := alloc(256);
PrintLn(StrReplace(d2 as pchar, "a-b-c-d"c, "-"c, "+"c)); // a+b+c+d
PrintLn(StrPadLeft("7"c, 3, 48)); // 007 (48 = '0')
PrintLn(StrRepeat("ab"c, 3)); // ababab
return 0;
}
Das Muster: Zielpuffer zuerst. Funktionen, die eine neue Zeichenkette erzeugen (StrSubstring, StrReplace, StrToUpper, StrTrimWhitespace), nehmen den Zielpuffer als erstes Argument und geben ihn auch zurück. Der Aufrufer besorgt den Speicher und muss ihn groß genug wählen — geprüft wird nichts.
| Aufgabe | Funktion |
|---|---|
| Suchen | StrFind, StrIndexOf, StrLastIndexOfChar, StrContains, StrCount |
| Ausschneiden | StrSubstring |
| Ersetzen | StrReplace |
| Trimmen | StrTrim (in place), StrTrimWhitespace (in Zielpuffer) |
| Vergleichen | StrCmp (0 = gleich) |
| Umwandeln | StrToInt, StrToInt64, StrToF64, Int64ToStr |
| Aufbereiten | StrPadLeft, StrPadRight, StrRepeat, StrReverse |
| Groß/klein | StrToUpper, StrToLower, CharToUpper, StrFirstCharToUpper |
StrToUpperundStrToLowerarbeiten nur auf ASCII.StrToUpper(dest, „straße“)liefertSTRAßE— dasßbleibt stehen, ebenso jeder Umlaut, jedes Akzentzeichen, jedes kyrillische Zeichen. Für alles jenseits von A–Z istTextToUpperFullausstd.unicode_casezuständig (Abschnitt 4).
var parts: int64 := alloc(8 * 8); // Platz fuer 8 Zeiger
var n: int64 := StrSplit("a,b,c"c, ","c, parts, 8); // n = 3
var i: int64 := 0;
while (i < n) {
var teil: pchar := peek64(parts + i * 8) as pchar;
PrintLn(teil); // a, b, c
i := i + 1;
}
Der Rückgabewert ist die Anzahl der Teile, das Zielarray hält die Zeiger. Bis lyxc 1.0.16F verdeckte ein gleichnamiger Compiler-Builtin diese Funktion und lieferte statt der Anzahl eine Adresse; seit 1.0.16F entscheidet die Argumentzahl (#1311).
Text kennt den Unterschied zwischen einem Byte, einem Codepoint und einem sichtbaren Zeichen.
import std.io;
import std.text;
fn main(): int64 {
var t: Text := TextFromPchar("Größe: 42 €"c as int64);
Print("Bytes: "); PrintLn(IntToStr(TextByteLength(t))); // 15
Print("Codepoints: "); PrintLn(IntToStr(TextCodepointCount(t))); // 11
Print("gueltig: "); PrintLn(IntToStr(TextIsValid(t))); // 1
var n: Text := TextFromPchar("42"c as int64);
Print("enthaelt: "); PrintLn(IntToStr(TextContains(t, n))); // 1
Print("Position: "); PrintLn(IntToStr(TextFind(t, n))); // 9 (Bytes!)
var up: Text := TextAsciiUpper(t);
var p: pchar := TextToPchar(up) as pchar;
PrintLn(p); // GRößE: 42 €
TextFree(up);
TextFree(n);
TextFree(t);
return 0;
}
15 Bytes, 11 Codepoints: ö kostet zwei Bytes, € drei. Wer mit pchar bei Zeichen 5 schneidet, zerteilt womöglich ein Zeichen und erzeugt ungültiges UTF-8.
| Aufgabe | Funktion |
|---|---|
| Erzeugen | TextFromPchar, TextFromUtf8, TextFromUtf16, TextFromUtf16Bom |
| Messen | TextByteLength, TextCodepointCount, TextIsValid |
| Zugreifen | TextCodepointAt, TextByteAt, TextByteOffsetOfCodepoint |
| Ausschneiden | TextSubstringCp — nach Codepoints, nicht nach Bytes |
| Suchen | TextFind (Byte-Offset), TextFindCp (Codepoint-Index), TextContains |
| Ändern | TextConcat, TextReplace, TextTrim |
| Zerlegen | TextSplit, TextSplitCount, TextPartAt |
| Vergleichen | TextEquals, TextCompare, TextStartsWith |
| Hinausgeben | TextToPchar, TextToUtf16, TextToUtf16Bom |
| Freigeben | TextFree |
TextFind liefert einen Byte-Offset, TextFindCp einen Codepoint-Index. Beide zu verwechseln ist der häufigste Fehler in UTF-8-Code. Wer den Fund an TextSubstringCp weitergibt, braucht TextFindCp.
Jeder Text ist eine Allokation. TextFree gehört auf jedes Zwischenergebnis — TextAsciiUpper, TextConcat, TextReplace erzeugen jeweils einen neuen.
import std.io;
import std.text;
import std.unicode;
import std.unicode_case;
import std.grapheme;
fn main(): int64 {
var t: Text := TextFromPchar("Größe"c as int64);
var up: Text := TextToUpperFull(t);
var p: pchar := TextToPchar(up) as pchar;
PrintLn(p); // GRÖßE
var a: Text := TextFromPchar("STRASSE"c as int64);
var b: Text := TextFromPchar("strasse"c as int64);
Print("Fold-gleich: "); PrintLn(IntToStr(TextEqualsFold(a, b))); // 1
var nfc: Text := TextToNFC(t);
var nfd: Text := TextToNFD(t);
Print("NFC Bytes: "); PrintLn(IntToStr(TextByteLength(nfc))); // 7
Print("NFD Bytes: "); PrintLn(IntToStr(TextByteLength(nfd))); // 8
Print("gleich: "); PrintLn(IntToStr(TextEqualsNormalized(nfc, nfd))); // 1
var emoji: Text := TextFromPchar("a👍🏽b"c as int64);
Print("Codepoints: "); PrintLn(IntToStr(TextCodepointCount(emoji))); // 4
Print("Grapheme: "); PrintLn(IntToStr(TextGraphemeCount(emoji))); // 3
return 0;
}
ö ein Codepoint, in NFD sind es zwei (o + Trema). Zwei Zeichenketten, die auf dem Bildschirm identisch aussehen, sind byteweise verschieden. TextEqualsNormalized vergleicht trotzdem richtig.👍🏽 ist Daumen plus Hautton-Modifikator: zwei Codepoints, ein sichtbares Zeichen. Wer Eingaben in der Länge begrenzt oder eine Zeile umbricht, muss mit Graphemen rechnen, nicht mit Codepoints.TextEqualsFold ist der richtige Vergleich für „gleich, egal wie geschrieben„. TextToLowerFull und danach vergleichen ist es nicht: Case-Folding kennt Sonderfälle, die eine Kleinschreibung nicht abdeckt.| Zweck | Funktion |
|---|---|
| Voll-Unicode Groß/Klein | TextToUpperFull, TextToLowerFull, UnicodeToUpperFull |
| Case-insensitiver Vergleich | TextEqualsFold, TextCompareFold, TextContainsFold |
| Normalisierung | TextToNFC, TextToNFD, TextToNFKC, TextToNFKD |
| Vergleich über Formen hinweg | TextEqualsNormalized, TextCompareNormalized |
| Grapheme | TextGraphemeCount, TextGraphemeAt, TextGraphemeByteOffset |
| Unicode-Leerraum trimmen | TextTrimUnicode, TextTrimStartUnicode, TextTrimEndUnicode |
TextTrimUnicode entfernt auch geschütztes Leerzeichen und schmales Leerzeichen — TextTrim kennt nur ASCII-Leerraum. Bei Formulareingaben ist das der Unterschied zwischen „Eingabe leer“ und „Eingabe sieht leer aus„.
ßwird vonTextToUpperFullnicht zuSS.GrößeergibtGRÖßEstattGRÖSSE. Umlaute werden korrekt umgesetzt; nur die Fälle, in denen ein Zeichen zu mehreren wird, fehlen. Für Anzeigezwecke unkritisch, für Sortierschlüssel und VergleicheTextEqualsFoldverwenden statt selbst großzuschreiben.
TextEquals) — für Schlüssel, IDs, Protokollfelder. Schnell, exakt.TextEqualsNormalized) — für alles, was aus verschiedenen Quellen kommt: eine Datei vom Mac liegt in NFD vor, dieselbe Eingabe aus einem Webformular in NFC.TextEqualsFold) — für Benutzernamen, Suchbegriffe, Anmeldedaten.
std.regex ist ein eigener, kleiner Matcher — kein PCRE. Der abgedeckte Sprachumfang ist begrenzt, und die Grenzen sind nicht offensichtlich, deshalb hier gemessen:
import std.io;
import std.alloc;
import std.regex;
fn main(): int64 {
PrintBoolLn(RegexMatch("^abc$"c, "abc"c)); // true
PrintBoolLn(RegexMatch("^abc$"c, "abcd"c)); // false
PrintLn(IntToStr(RegexSearch("[0-9]+"c, "Preis: 42 Euro"c))); // 7
var d: int64 := alloc(256);
var n: int64 := RegexReplaceInto(d as pchar, "[0-9]+"c, "a1b22c"c, "#"c, 0);
var s: pchar := d as pchar;
Print("Ersetzungen: "); PrintLn(IntToStr(n)); // 2
PrintLn(s); // a#b#c
return 0;
}
| Konstrukt | Zustand (1.0.16F) |
|---|---|
Zeichenklassen [a-z], [^0-9] | ✅ |
Quantoren +, *, ?, {n} | ✅ |
Anker ^, $ | ✅ |
Escapes \d, \w, \s | ✅ |
Gruppen (…) | ✅ |
Alternation a|b | ✅ |
Captures RegexCaptureText, RegexCaptureCount | ❌ — siehe unten |
| Rückwärtsreferenzen, Lookahead | ❌ |
Die Gruppen matchen, die Captures nicht.(…)unda|barbeiten seit dem stdlib-Stand80479041korrekt — aber der Inhalt einer Gruppe ist nicht auslesbar: nach einem erfolgreichenRegexMatch("([a-z]+)@([a-z]+)", "user@example")liefertRegexCaptureCount()den Wert0undRegexCaptureTexteine leere Zeichenkette.
Für „passt das Muster?“ ist das ohne Belang. Wer Teile herausziehen will, sucht die Trennstelle mitStrIndexOfCharund schneidet mitStrSubstring.
(Issue #1313)
Praktische Folge: std.regex trägt für Suchen (RegexSearch), Ersetzen (RegexReplaceInto) und Prüfen (RegexMatch mit ^…$). Für fest umrissene Formate — E-Mail, Datum, IBAN, Kontonummer — ist std.validate trotzdem die bessere Wahl: geprüfte Algorithmen samt Prüfziffer statt eines Musters, das Syntax mit Gültigkeit verwechselt.
→ Validierungs-Guide für IBAN, EAN, VIN und zwanzig weitere geprüfte Formate.
var s: pchar := puffer as pchar;
PrintLn(s); // richtig
PrintLn(satz.name); // pchar-FELD: gibt die ADRESSE aus
Ein pchar-Feld eines Structs wird als Zahl ausgegeben; über eine Variable geht es richtig. Die Typumwandlung im Argument war bis lyxc 1.0.15G ebenso betroffen und arbeitet seit 1.0.16F korrekt.
Keine der Str-Funktionen prüft die Zielgröße. Bei StrReplace kann das Ergebnis länger als die Eingabe sein — ein Puffer in Eingabegröße reicht nicht. Faustregel: Eingabelänge × Ersatzlänge ÷ Suchlänge, plus Reserve.
Was aus dem Netz oder aus einer Datei kommt, ist nicht nullterminiert. Vor jeder Str-Funktion selbst setzen:
poke8(buf + n, 0);
Und deshalb nur bufsize - 1 Byte einlesen.
StrSubstring(dest, src, 6, 4) zählt Bytes. Bei ASCII fällt das nie auf, bei Größe schneidet es mitten in das ö. Sobald Benutzertext im Spiel ist: Text verwenden und TextSubstringCp.
| Angelegt mit | Freigegeben mit |
|---|---|
alloc(n) | free(ptr, n) |
TextFromPchar, TextFromUtf8, jede Text-Operation | TextFree |
StrJoin, StrRepeat, StrPadLeft (eigene Allokation) | free mit der Ergebnislänge |
| Bereich | Zustand |
|---|---|
std.string — Suchen, Schneiden, Ersetzen, Trimmen, Padding | ✅ |
std.string — StrSplit | ✅ ab lyxc 1.0.16F |
std.string — StrToUpper/StrToLower | ⚠️ nur ASCII |
std.text — Text, Codepoints, Suchen, Zerlegen, UTF-16 | ✅ |
std.unicode — Normalisierung NFC/NFD/NFKC/NFKD | ✅ |
std.unicode_case — Fold, Full-Case | ✅ (ß → SS fehlt) |
std.grapheme — Graphemzählung und -zugriff | ✅ |
std.regex — Klassen, Quantoren, Escapes, Anker, Gruppen, Alternation | ✅ |
std.regex — Captures auslesen (RegexCaptureText) | ❌ liefert leer, RegexCaptureCount ist 0 |
Alle Codebeispiele dieser Seite sind mit lyxc 1.0.16F übersetzt und ausgeführt worden; die angegebenen Ausgaben sind die tatsächlichen.