std.text
Text ist die UTF-8-Ebene über den rohen Bytes. Sie validiert die Kodierung bei der Erzeugung und arbeitet auf Codepoints statt auf Bytes: CodepointCount, CodepointAt, SubstringCp zählen und schneiden dort, wo ein Zeichen anfängt — nicht mitten in einer Mehrbyte-Sequenz.
Die Unit braucht keine Unicode-Tabellen: UTF-8 zu prüfen und zu dekodieren ist reine Bytemuster-Arbeit. Alles, was Tabellen braucht — Normalisierung, volle Groß-/Kleinschreibung, Graphem-Segmentierung — liegt in eigenen Units, die man dazuholt:
| Aufgabe | Unit |
|---|---|
| Bytes, Besitz, Länge | std.strtype |
| UTF-8, Codepoints, Suchen/Schneiden/Splitten | std.text |
| Normalisierung NFC/NFD/NFKC/NFKD, Whitespace | std.unicode |
| Volle Groß-/Kleinschreibung, Caseless-Vergleich | std.unicode_case |
| Graphem-Cluster („was der Nutzer als ein Zeichen sieht„) | std.grapheme |
Intern speichert Lyx immer UTF-8. UTF-16 gibt es nur als Grenzformat (Windows-API, UTF-16-Dateien) — dafür die Konverter am Ende dieser Seite.
Autor: Andreas Röne
Copyright: 2024–2026 Andreas Röne
Quelle: std/text.lyx
Alle Beispiele und Angaben dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben stammen aus dem tatsächlichen Lauf.
Erzeugen und ausgeben
Text ist eine Klasse mit Referenzsemantik. Die freien Text*-Funktionen sind dünne Hüllen um die gleichnamigen Methoden — beide Schreibweisen sind gleichwertig.
import std.io;
import std.text;
fn Zeige(t: Text): void {
var p: pchar := t.ToPchar() as pchar;
PrintLn(p);
}
fn main(): int64 {
var t: Text := TextFromPchar("Größe"c);
PrintLn(IntToStr(t.ByteLength())); // 7 — Bytes
PrintLn(IntToStr(t.CodepointCount())); // 5 — Zeichen
PrintLn(IntToStr(t.IsValid())); // 1
Zeige(t.SubstringCp(0, 3)); // "Grö" — schneidet nach Codepoints
Zeige(t.AsciiUpper()); // "GRößE" — nur ASCII wird gewandelt
return 0;
}
Ausgabe:
7
5
1
Grö
GRößE
Die HilfsfunktionZeigeist kein Zufall.ToPchar()hat den Rückgabetypint64; einas pchardirekt im Argument vonPrintLnwird nicht übernommen und würde die Adresse als Zahl ausgeben. Der Wert muss erst in einepchar-Variable.
| Funktion | Beschreibung |
|---|---|
TextFromPchar(p: int64): Text | Aus NUL-terminiertem UTF-8 |
TextFromUtf8(ptr: int64, n: int64): Text | Aus n rohen Bytes |
Ungültige Bytes führen nicht zum Fehlschlag: Die Bytes werden gespeichert und IsValid() meldet 0. Geprüft wird auf korrekte Start-/Folgebytes, Overlong-Kodierungen, Surrogate und den Bereich bis U+10FFFF.
Methoden
Abfragen
| Signatur | Beschreibung |
|---|---|
ByteLength(): int64 | Länge in Bytes |
CodepointCount(): int64 | Anzahl der Codepoints |
IsValid(): int64 | 1, wenn der Inhalt gültiges UTF-8 ist |
Data(): int64 | Zeiger auf die Rohbytes (nicht NUL-terminiert) |
ByteAt(i: int64): int64 | Byte an Byte-Position i |
CodepointAt(idx: int64): int64 | Codepoint an Codepoint-Position idx |
ByteOffsetOfCodepoint(idx: int64): int64 | Byte-Offset, an dem Codepoint idx beginnt |
Ableiten und Vergleichen
| Signatur | Beschreibung |
|---|---|
Add(other: Text): Text / Concat | Verkettung als neues Text |
SubstringCp(startCp, countCp): Text | Ausschnitt nach Codepoint-Positionen |
Equals(other: Text): int64 | Byte-Inhaltsvergleich |
EqualsPchar(p: int64): int64 | Vergleich gegen pchar |
StartsWith(prefix: Text): int64 | Präfixtest |
Compare(other: Text): int64 | Lexikografisch: -1, 0, 1 |
ToPchar(): int64 | Frische NUL-terminierte Kopie |
Free() | Puffer freigeben |
Equalsvergleicht Bytes. „é“ als ein Codepoint (U+00E9) und „é„ alse+ Combining Acute (U+0301) sind damit ungleich, obwohl sie gleich aussehen. Wer das gleichsetzen will, normalisiert vorher —TextEqualsNormalizedinstd.unicode.
Suchen, Trimmen, Ersetzen, Splitten
| Signatur | Beschreibung |
|---|---|
Find(needle: Text): int64 | Byte-Offset des ersten Treffers, -1 wenn nicht gefunden |
FindCp(needle: Text): int64 | Codepoint-Position des ersten Treffers |
Contains(needle: Text): int64 | Enthaltenstest |
Trim(): Text | ASCII-Whitespace an beiden Enden entfernen |
Replace(old: Text, repl: Text): Text | Alle Vorkommen ersetzen |
SplitCount(sep: Text): int64 | Anzahl der Teile |
PartAt(sep: Text, idx: int64): Text | Einzelnes Teil — der bequeme Weg |
Split(sep, out: int64, maxParts: int64): int64 | Rohform: schreibt bis zu maxParts Feldblöcke à TEXT_PART_STRIDE Bytes nach out und liefert die Gesamtzahl der Teile zurück |
AsciiUpper(): Text / AsciiLower(): Text | Nur ASCII wandeln — Umlaute und Griechisch bleiben unangetastet |
import std.io;
import std.text;
fn Zeige(t: Text): void { var p: pchar := t.ToPchar() as pchar; PrintLn(p); }
fn main(): int64 {
var csv: Text := TextFromPchar("eins,zwei,drei"c);
var sep: Text := TextFromPchar(","c);
PrintLn("Teile: ", IntToStr(csv.SplitCount(sep)));
Print("Teil 0: "); Zeige(csv.PartAt(sep, 0));
Print("Teil 1: "); Zeige(csv.PartAt(sep, 1));
Print("Teil 2: "); Zeige(csv.PartAt(sep, 2));
var alt: Text := TextFromPchar("zwei"c);
var neu: Text := TextFromPchar("ZWEI"c);
Print("Replace: "); Zeige(csv.Replace(alt, neu));
var h: Text := TextFromPchar("Grüße aus Köln"c);
var n: Text := TextFromPchar("aus"c);
PrintLn("Find (Byte-Offset) = ", IntToStr(h.Find(n)));
PrintLn("FindCp (Codepoint-Pos) = ", IntToStr(h.FindCp(n)));
PrintLn("ByteLength / Codepoints = ", IntToStr(h.ByteLength()), " / ", IntToStr(h.CodepointCount()));
return 0;
}
Ausgabe:
Teile: 3
Teil 0: eins
Teil 1: zwei
Teil 2: drei
Replace: eins,ZWEI,drei
Find (Byte-Offset) = 8
FindCp (Codepoint-Pos) = 6
ByteLength / Codepoints = 17 / 14
An „Grüße aus Köln“ ist der Unterschied der beiden Suchfunktionen ablesbar: aus beginnt am Byte 8, aber am Codepoint 6 — dazwischen liegen die beiden Zweibyte-Zeichen ü und ß. Für Ausschnitte mit SubstringCp braucht man den Codepoint-Wert, für Zeigerrechnung auf Data() den Byte-Wert.
Split in der Rohform
SplitCount + PartAt durchläuft den Text je Aufruf erneut. Wer alle Teile auf einmal braucht, nimmt Split: es schreibt je Teil einen Block von TEXT_PART_STRIDE (24) Bytes nach out — Zeiger, Bytelänge, Gültigkeitsflag.
import std.io;
import std.text;
import std.alloc;
fn main(): int64 {
var csv: Text := TextFromPchar("eins,zwei,drei,vier"c);
var sep: Text := TextFromPchar(","c);
// Platz fuer zwei Teile — absichtlich zu wenig
var out: int64 := alloc(TEXT_PART_STRIDE * 2);
var gesamt: int64 := csv.Split(sep, out, 2);
PrintLn("Rueckgabe = ", IntToStr(gesamt), " (Gesamtzahl, nicht die geschriebenen)");
var i: int64 := 0;
while (i < 2) {
var d: int64 := peek64(out + i * TEXT_PART_STRIDE); // Zeiger auf Bytes
var bl: int64 := peek64(out + i * TEXT_PART_STRIDE + 8); // Bytelaenge
var vl: int64 := peek64(out + i * TEXT_PART_STRIDE + 16); // gueltiges UTF-8?
Print(" Teil ", IntToStr(i), ": ", IntToStr(bl), " Bytes, valid=", IntToStr(vl), " -> '");
var k: int64 := 0;
while (k < bl) { PrintChar(peek8(d + k)); k := k + 1; }
PrintLn("'");
i := i + 1;
}
if (gesamt > 2) { PrintLn("Puffer war zu klein: ", IntToStr(gesamt - 2), " Teile fehlen"); }
return 0;
}
Rueckgabe = 4 (Gesamtzahl, nicht die geschriebenen)
Teil 0: 4 Bytes, valid=1 -> 'eins'
Teil 1: 4 Bytes, valid=1 -> 'zwei'
Puffer war zu klein: 2 Teile fehlen
Split liefert die Gesamtzahl der Teile, auch wenn nur maxParts davon geschrieben wurden — der Rückgabewert taugt also zur Prüfung, ob der Puffer gereicht hat. Die Blöcke enthalten Zeiger in den Originaltext, keine Kopien; sie bleiben nur gültig, solange dieser lebt.
Codepoint-Hilfsfunktionen
| Signatur | Beschreibung |
|---|---|
CodepointUtf8Len(cp: int64): int64 | Bytes, die cp in UTF-8 belegt (1–4; 0 wenn ungültig) |
CodepointToUtf8(cp: int64, dest: int64): int64 | Codepoint nach dest kodieren; liefert die Bytezahl |
Speicher und Referenzsemantik
Text ist eine Klasse — eine Zuweisung kopiert nicht, sie teilt dasselbe Objekt:
var a: Text := TextFromPchar("Original"c);
var b: Text := a; // dasselbe Objekt, nicht eine Kopie
PrintLn(IntToStr((a.Data() == b.Data()) as int64)); // 1
a.Free();
PrintLn(IntToStr(b.ByteLength())); // 0 — b ist mit entwertet
PrintLn(IntToStr(b.Data())); // 0
Nach a.Free() ist auch b leer: Free gibt den Bytepuffer frei und setzt die Felder des gemeinsamen Objekts zurück. Ein Text darf also nur einmal freigegeben werden, und niemand darf ihn danach noch benutzen.
Alle ableitenden Methoden — Add, SubstringCp, Trim, Replace, AsciiUpper, AsciiLower, PartAt — legen dagegen einen eigenen Puffer an. Deren Ergebnisse sind unabhängig und müssen einzeln freigegeben werden, wenn sie nicht mehr gebraucht werden.
ToPchar() erzeugt jedes Mal eine frische, NUL-terminierte Kopie. In einer Schleife aufgerufen wächst der Speicherverbrauch entsprechend — den Rückgabewert also einmal in einer Variablen halten statt mehrfach abzurufen.
Grenzfälle
Keine Methode bricht bei Bereichsverletzung ab; jede hat einen definierten Rückgabewert.
| Aufruf | Ergebnis |
|---|---|
CodepointAt(idx) außerhalb, auch negativ | -1 |
ByteAt(i) außerhalb | 0 |
ByteOffsetOfCodepoint(idx) außerhalb | -1 |
SubstringCp(1, 99) — zu viele Zeichen verlangt | schneidet bis zum Ende ab (bc) |
SubstringCp(5, 2) — Start hinter dem Ende | leeres Text, ByteLength() == 0 |
TextFromPchar(„“) | leeres Text, IsValid() == 1 |
Find ohne Treffer | -1 |
TextFromUtf8 mit ungültigen Bytes | Text entsteht, IsValid() == 0, Bytes bleiben erhalten |
Der letzte Punkt ist der wichtigste: ungültiges UTF-8 führt nicht zum Fehlschlag. Wer fremde Daten einliest, muss IsValid() selbst prüfen — sonst arbeiten CodepointCount und SubstringCp auf kaputten Sequenzen weiter.
Operatoren
| Operator | Methode | Ergebnis |
|---|---|---|
a + b | Add | neues Text |
a == b, a != b | Eq/Ne | Byte-Inhaltsgleichheit |
a < b, a ⇐ b, a > b, a >= b | aus Compare | lexikografisch |
a[i] | Get | Codepoint an Position i (nicht Byte!) |
UTF-16 — nur an der Grenze
Für UTF-16-Dateien und die Windows-API. Innerhalb von Lyx wird nichts als UTF-16 gehalten.
| Signatur | Beschreibung |
|---|---|
TextFromUtf16(ptr, byteLen, endian): Text | UTF-16 → Text mit vorgegebener Byte-Reihenfolge |
TextFromUtf16Bom(ptr, byteLen): Text | Byte-Reihenfolge aus einem führenden BOM ableiten und ihn entfernen |
TextUtf16Length(t: Text): int64 | Benötigte Bytes ohne BOM (für einen BOM 2 dazu) |
TextToUtf16(t, dest, endian): int64 | Nach dest kodieren, ohne BOM |
TextToUtf16Bom(t, dest, endian): int64 | Mit vorangestelltem U+FEFF |
Zwei Fallen bei der UTF-16-Umwandlung:
* Ohne BOM liestTextFromUtf16BomBig Endian — so schreibt es RFC 2781 für schlichtes „UTF-16„ vor. Von Windows erzeugte Dateien ohne BOM sind aber in aller Regel Little Endian. Wenn die Byte-Reihenfolge bekannt ist,TextFromUtf16mit ausdrücklicher Angabe verwenden.
* Fehlerhafte Eingabe schlägt nicht fehl, sie wird ersetzt. Ein unpaariges High- oder Low-Surrogate, ein abgeschnittenes Paar am Pufferende und ein einzelnes überzähliges Byte werden je zu U+FFFD. Das hält das Ergebnis als gültiges UTF-8 — wer schlechte Eingaben ablehnen statt ersetzen will, prüft das Ergebnis auf U+FFFD.
Letzte Aktualisierung: 2026-08-13 — Beispiele gegen lyxc 1.0.17K verifiziert, Abschnitte zu Speicherverwaltung, Grenzfällen und der Split-Rohform ergänzt.
Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).
