Inhaltsverzeichnis

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 Hilfsfunktion Zeige ist kein Zufall. ToPchar() hat den Rückgabetyp int64; ein as pchar direkt im Argument von PrintLn wird nicht übernommen und würde die Adresse als Zahl ausgeben. Der Wert muss erst in eine pchar-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
Equals vergleicht Bytes. „é“ als ein Codepoint (U+00E9) und „é„ als e + Combining Acute (U+0301) sind damit ungleich, obwohl sie gleich aussehen. Wer das gleichsetzen will, normalisiert vorher — TextEqualsNormalized in std.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 liest TextFromUtf16Bom Big 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, TextFromUtf16 mit 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).