====== 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 | ''[[lyx_-_programmiersprache:units:strtype|std.strtype]]'' |
| UTF-8, Codepoints, Suchen/Schneiden/Splitten | **std.text** |
| Normalisierung NFC/NFD/NFKC/NFKD, Whitespace | ''[[lyx_-_programmiersprache:units:unicode|std.unicode]]'' |
| Volle Groß-/Kleinschreibung, Caseless-Vergleich | ''[[lyx_-_programmiersprache:units:unicode_case|std.unicode_case]]'' |
| Graphem-Cluster („was der Nutzer als ein Zeichen sieht") | ''[[lyx_-_programmiersprache:units:grapheme|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 ''[[lyx_-_programmiersprache:units:unicode|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).