====== std.edi.core ======
EDIFACT-Interchange-Engine — das Fundament aller ''std.edi.*''-Units. Implementiert die vollständige Umschlag-Hierarchie (UNA / UNB / UNZ / UNG / UNE / UNH / UNT), den Segment-Tokenizer mit Escape-Behandlung, laufende Sequenznummern und Duplikatserkennung pro Partner.
Alle anderen EDI-Units importieren ''std.edi.core'' und setzen die hier definierten Structs und Konstanten voraus.
import std.edi.core;
import std.alloc;
fn main(): int64 {
// 1. Kontext anlegen - EdiCtxInit setzt die UNOA-Standardtrennzeichen
var ctx: int64 := alloc(EDI_CTX_SIZE);
EdiCtxInit(ctx);
// Absender/Empfaenger als Zeiger + Laenge (kein Kopieren)
poke64(ctx + EDI_CTX_SENDER, "4012345000009"c as int64);
poke64(ctx + EDI_CTX_SNDLEN, 13);
poke64(ctx + EDI_CTX_RCVR, "4098765000004"c as int64);
poke64(ctx + EDI_CTX_RCVLEN, 13);
poke64(ctx + EDI_CTX_ICREF, "IC000042"c as int64);
poke64(ctx + EDI_CTX_ICREFLEN, 8);
poke64(ctx + EDI_CTX_DATE, "20260813"c as int64);
poke64(ctx + EDI_CTX_TIME, "0930"c as int64);
// 2. State fuer Sequenznummern und Duplikatserkennung
var state: int64 := alloc(EDI_STATE_SIZE);
EdiStateInit(state);
var seq: int64 := EdiSeqNext(state, "4098765000004"c);
// 3. Umschlag schreiben
var out: int64 := alloc(4096);
var pos: int64 := EdiUnbWrite(ctx, out, 4096);
PrintLn(IntToStr(pos)); // 62 = Laenge des UNB-Segments
PrintLn(IntToStr(seq)); // 1 = erste Sequenznummer fuer diesen Partner
free(out, 4096);
free(state, EDI_STATE_SIZE);
free(ctx, EDI_CTX_SIZE);
return 0;
}
----
===== Imports =====
* ''std.alloc''
----
===== Konstanten & Standardtrennzeichen =====
Wenn kein UNA-Segment vorhanden ist, gelten folgende Standardwerte (UNOA-Zeichensatz):
^ Konstante ^ Wert ^ Bedeutung ^
| ''EDI_DEF_COMP'' | '':'' (58) | Komponenten-Trenner |
| ''EDI_DEF_DATA'' | ''+'' (43) | Datenelement-Trenner |
| ''EDI_DEF_DEC'' | ''.'' (46) | Dezimalzeichen |
| ''EDI_DEF_REL'' | ''?'' (63) | Release-Zeichen (Escape) |
| ''EDI_DEF_SEG'' | '''' (39) | Segmentabschluss |
Zeichensatz-Codes:
^ Konstante ^ Bedeutung ^
| ''EDI_CHARSET_UNOA'' | A–Z, 0–9 + wenige Sonderzeichen |
| ''EDI_CHARSET_UNOB'' | Latin-1 / ISO 8859-1 |
| ''EDI_CHARSET_UNOC'' | UTF-8 |
Fehlercodes:
^ Konstante ^ Bedeutung ^
| ''EDI_OK'' | Kein Fehler |
| ''EDI_ERR_TRUNC'' | Puffer zu klein |
| ''EDI_ERR_NOUNA'' | Kein UNA-Segment gefunden |
| ''EDI_ERR_SYNTAX'' | Syntaxfehler im Segment |
| ''EDI_ERR_DUP'' | Doppelte Interchange-Referenz erkannt |
| ''EDI_ERR_OVERFLOW'' | Zu viele Partner-Slots belegt |
| ''EDI_ERR_PARTNER'' | Partner nicht gefunden |
----
===== Structs =====
Alle Structs werden vom Caller alloziert. Größen sind über Konstanten verfügbar.
==== EdiUna (EDI_UNA_SIZE = 48 Bytes) ====
Hält die Trennzeichen aus dem UNA-Segment oder die Standardwerte.
^ Offset-Konstante ^ Offset ^ Inhalt ^
| ''EDI_UNA_COMP'' | 0 | Komponenten-Trenner (int64) |
| ''EDI_UNA_DATA'' | 8 | Datenelement-Trenner (int64) |
| ''EDI_UNA_DEC'' | 16 | Dezimalzeichen (int64) |
| ''EDI_UNA_REL'' | 24 | Release-Zeichen (int64) |
| ''EDI_UNA_RES'' | 32 | Reserviert (int64) |
| ''EDI_UNA_SEG'' | 40 | Segmentabschluss (int64) |
==== EdiCtx — Interchange-Kontext (EDI_CTX_SIZE = 128 Bytes) ====
^ Offset-Konstante ^ Inhalt ^
| ''EDI_CTX_SENDER / EDI_CTX_SNDLEN'' | Absender-ID (Zeiger + Länge) |
| ''EDI_CTX_RCVR / EDI_CTX_RCVLEN'' | Empfänger-ID (Zeiger + Länge) |
| ''EDI_CTX_ICREF / EDI_CTX_ICREFLEN'' | Interchange-Referenz (ICR) |
| ''EDI_CTX_DATE / EDI_CTX_TIME'' | Datum (YYYYMMDD oder YYMMDD) / Zeit (HHMM) |
| ''EDI_CTX_CHARSET'' | Zeichensatz-Code (UNOA/UNOB/UNOC) |
| ''EDI_CTX_TESTFLAG'' | 0 = Produktion, 1 = Testmodus |
| ''EDI_CTX_COMP / DATA / DEC / REL / SEG'' | Trennzeichen (0 = Standard) |
| ''EDI_CTX_MSGCOUNT'' | Anzahl Nachrichten im Interchange |
==== EdiSegCtx — Segment-Iterator (EDI_SEGCTX_SIZE = 40 Bytes) ====
Hält die aktuelle Position im Lesepuffer. Wird von ''EdiSegCtxInit'' initialisiert und von ''EdiSegmentNext'' fortgeschrieben.
==== EdiSeg — Segment-Ergebnis (EDI_SEG_SIZE = 16 Bytes) ====
^ Offset-Konstante ^ Inhalt ^
| ''EDI_SEG_PTR'' | Zeiger in den Originalpuffer (Zero-Copy) |
| ''EDI_SEG_LEN'' | Byte-Länge ohne Segmentabschluss |
==== EdiElem — Element/Komponenten-Slot (EDI_ELEM_SIZE = 16 Bytes) ====
^ Offset-Konstante ^ Inhalt ^
| ''EDI_ELEM_PTR'' | Zeiger in den Originalpuffer |
| ''EDI_ELEM_LEN'' | Byte-Länge des Elements/der Komponente |
==== EdiState — Partner-State (EDI_STATE_SIZE = 16 776 Bytes) ====
Speichert pro Partner (max. 16) die laufende Sequenznummer und einen Ring-Buffer von 128 ICR-Hashes zur Duplikatserkennung.
----
===== Funktionen =====
==== Initialisierung ====
^ Signatur ^ Beschreibung ^
| ''EdiCtxInit(ctx: int64): void'' | Nullt alle Felder des Interchange-Kontexts |
| ''EdiStateInit(state: int64): void'' | Nullt alle Felder des Partner-State |
==== UNA lesen / schreiben ====
^ Signatur ^ Beschreibung ^
| ''EdiUnaRead(buf: int64, bufLen: int64, out: int64): int64'' | Liest UNA-Segment aus ''buf''; füllt ''out'' (EDI_UNA_SIZE). Kein UNA → Standardwerte. Gibt ''EDI_OK'' zurück. |
| ''EdiUnaWrite(ctx: int64, out: int64, outMax: int64): int64'' | Schreibt UNA-Segment mit den im Kontext gesetzten Trennzeichen; gibt geschriebene Bytes zurück. |
==== Segment-Tokenizer ====
^ Signatur ^ Beschreibung ^
| ''EdiSegCtxInit(sctx: int64, una: int64, startPos: int64): void'' | Initialisiert Iterator ab Byte-Position ''startPos'' mit Trennzeichen aus ''una'' |
| ''EdiSegmentNext(buf: int64, bufLen: int64, sctx: int64, seg: int64): int64'' | Holt nächstes Segment. Gibt 1 (gefunden) oder 0 (Ende) zurück. Zeiger in ''seg'' zeigen in ''buf'' (Zero-Copy). |
| ''EdiElementSplit(seg: int64, out: int64, maxOut: int64): int64'' | Spaltet Segment in Datenelemente auf; befüllt ''out''-Array (je EDI_ELEM_SIZE). Gibt Anzahl zurück. |
| ''EdiComponentSplit(ptr: int64, len: int64, out: int64, maxOut: int64): int64'' | Spaltet ein Datenelement in Komponenten auf. Gibt Anzahl zurück. |
| ''EdiUnescape(src: int64, srcLen: int64, dst: int64, dstMax: int64): int64'' | Entfernt Release-Zeichen (''?'') aus einem String. Gibt Länge des Ergebnisses zurück. |
==== UNB / UNZ — Interchange-Umschlag ====
^ Signatur ^ Beschreibung ^
| ''EdiUnbRead(buf: int64, bufLen: int64, ctx: int64): int64'' | Parst UNB-Segment; füllt Absender/Empfänger/ICR/Datum ins ''ctx''. Gibt ''EDI_OK'' oder Fehlercode. |
| ''EdiUnbWrite(ctx: int64, out: int64, outMax: int64): int64'' | Schreibt UNB-Segment; gibt geschriebene Bytes zurück. |
| ''EdiUnzWrite(ctx: int64, msgCount: int64, out: int64, outMax: int64): int64'' | Schreibt UNZ-Trailer (Interchange-Ende). |
==== UNG / UNE — Gruppen-Umschlag ====
^ Signatur ^ Beschreibung ^
| ''EdiUngWrite(ctx: int64, msgType: int64, grpRef: int64, out: int64, outMax: int64): int64'' | Schreibt UNG-Gruppen-Header |
| ''EdiUneWrite(ctx: int64, msgCount: int64, grpRef: int64, out: int64, outMax: int64): int64'' | Schreibt UNE-Gruppen-Trailer |
==== UNH / UNT — Nachrichten-Umschlag ====
^ Signatur ^ Beschreibung ^
| ''EdiUnhWrite(ctx: int64, msgRef: int64, msgType: int64, out: int64, outMax: int64): int64'' | Schreibt UNH-Nachrichten-Header |
| ''EdiUntWrite(ctx: int64, segCount: int64, msgRef: int64, out: int64, outMax: int64): int64'' | Schreibt UNT-Nachrichten-Trailer |
==== Sequenznummern & Duplikatserkennung ====
^ Signatur ^ Beschreibung ^
| ''EdiSeqNext(state: int64, partnerKey: int64): int64'' | Erhöht Sequenznummer für diesen Partner; gibt nächste Nummer zurück |
| ''EdiDuplicateCheck(state: int64, partnerKey: int64, icRef: int64): int64'' | Prüft ob ICR bereits bekannt; gibt ''1'' (Duplikat) oder ''0'' (neu). Trägt neue ICRs automatisch ein (Ring-Buffer mit 128 Einträgen). |
----
===== Codebeispiel — eingehende Nachricht zerlegen =====
import std.edi.core;
import std.alloc;
import std.string;
fn Feld(ptr: int64, len: int64): pchar {
var b: int64 := alloc(len + 1);
var i: int64 := 0;
while (i < len) { poke8(b + i, peek8(ptr + i)); i := i + 1; }
poke8(b + len, 0);
return b as pchar;
}
fn main(): int64 {
var msg: pchar := "UNA:+.? 'UNB+UNOA:1+SENDER01+RCVR0001+20260813:0930+IC77'LIN+1++04012345678901:SRV'QTY+21:24'PRI+AAA:19.99:CP'UNZ+1+IC77'"c;
var buf: int64 := msg as int64;
var len: int64 := StrLen(msg);
// 1. Trennzeichen aus UNA - fehlt es, gelten die Standardwerte (EDI_ERR_NOUNA)
var una: int64 := alloc(EDI_UNA_SIZE);
var start: int64 := 0;
if (EdiUnaRead(buf, len, una) == EDI_OK) { start := 9; }
PrintLn(StrConcat("Datenelementtrenner: ", IntToStr(peek64(una + EDI_UNA_DATA))));
// 2. Segmente durchlaufen
var sctx: int64 := alloc(EDI_SEGCTX_SIZE);
EdiSegCtxInit(sctx, una, start);
var seg: int64 := alloc(EDI_SEG_SIZE);
var elems: int64 := alloc(16 * EDI_ELEM_SIZE);
var comps: int64 := alloc(8 * EDI_ELEM_SIZE);
while (EdiSegmentNext(buf, len, sctx, seg) == 1) {
var segPtr: int64 := peek64(seg + EDI_SEG_PTR);
var n: int64 := EdiElementSplit(seg, elems, 16);
// Das 3-Zeichen-Tag steht am Segmentanfang; EdiElementSplit ueberspringt es.
Print(StrConcat(Feld(segPtr, 3), ": "));
Print(StrConcat(IntToStr(n), " Datenelemente"));
if (peek8(segPtr) == 76 && peek8(segPtr + 1) == 73) {
// LIN+1++GTIN:SRV -> Element 2 traegt die GTIN als erste Komponente
var e2p: int64 := peek64(elems + 2 * EDI_ELEM_SIZE + EDI_ELEM_PTR);
var e2l: int64 := peek64(elems + 2 * EDI_ELEM_SIZE + EDI_ELEM_LEN);
var nc: int64 := EdiComponentSplit(e2p, e2l, comps, 8);
Print(StrConcat(" - GTIN ", Feld(peek64(comps + EDI_ELEM_PTR), peek64(comps + EDI_ELEM_LEN))));
}
PrintLn("");
}
return 0;
}
Datenelementtrenner: 43
UNB: 5 Datenelemente
LIN: 3 Datenelemente - GTIN 04012345678901
QTY: 1 Datenelemente
PRI: 1 Datenelemente
UNZ: 2 Datenelemente
----
===== Hinweise =====
* **Zero-Copy-Parsing**: ''EdiSegmentNext'', ''EdiElementSplit'' und ''EdiComponentSplit'' setzen Zeiger in den Originalpuffer — keine Kopien. Der Puffer muss während der gesamten Verarbeitung gültig bleiben.
* **Caller alloziert**: Alle Structs (UNA, CTX, SEGCTX, SEG, Elem-Arrays) werden vom Caller alloziert und freigegeben. Die Größenkonstanten (''EDI_*_SIZE'') geben die benötigte Byte-Anzahl an.
* **Escape-Behandlung**: ''EdiSegmentNext'' behandelt das Release-Zeichen (Standard: ''?'') korrekt — es schützt das darauffolgende Trennzeichen vor Interpretation. Explizite Daten müssen danach durch ''EdiUnescape'' bereinigt werden.
* **Duplikatserkennung**: ''EdiDuplicateCheck'' nutzt einen Ring-Buffer mit 128 Hash-Einträgen pro Partner (max. 16 Partner). Bei mehr als 128 ICRs pro Partner kann ein alter ICR wieder als "neu" erscheinen.
* **''EdiUnbRead'' erwartet genau ein Segment.** Der übergebene Bereich muss bei ''UNB'' beginnen und mit dem Segmentzeichen enden — sonst schleppt die Interchange-Referenz das Folgesegment mit. Praktisch: erst ''EdiSegmentNext'', dann ''EdiUnbRead(peek64(seg + EDI_SEG_PTR), peek64(seg + EDI_SEG_LEN), ctx)''.
* **''msgRef'', ''msgType'' und ''grpRef'' sind Zeiger auf Text**, obwohl sie als ''int64'' deklariert sind: ''EdiUnhWrite(ctx, "1"c, "ORDERS"c, out, max)''. Eine echte Zahl an dieser Stelle wird als Adresse gelesen und stürzt ab.
* **Tag und Elemente**: ''EdiElementSplit'' überspringt das Drei-Zeichen-Tag — Element 0 ist bereits das erste Datenelement. Das Tag selbst steht in den ersten drei Bytes ab ''EDI_SEG_PTR''.
* **Eigene Trennzeichen wirken nur im Iterator.** ''EdiSegmentNext'' arbeitet mit den Werten aus ''EdiSegCtxInit''; ''EdiElementSplit'' und ''EdiComponentSplit'' rechnen dagegen fest mit ''+'', '':'' und ''?''. Bei abweichendem UNA müssen Elemente von Hand zerlegt werden.
* **''EDI_ERR_NOUNA'' ist kein Abbruchgrund**: ''EdiUnaRead'' hat dann die Standardtrennzeichen eingetragen; der Startversatz ist 0 statt 9.
----
===== Quelldatei =====
^ Unit ^ Datei ^
| ''std.edi.core'' | ''std/edi/core.lyx'' |
Letzte Aktualisierung: 2026-08-13 — beide Codebeispiele lauffähig gemacht und ausgeführt (lyxc 1.0.17K); Hinweise zu ''EdiUnbRead'' (genau ein Segment), Zeiger-Parametern bei UNH/UNT/UNG, Tag-Behandlung in ''EdiElementSplit'' und den festen Trennzeichen der Split-Funktionen 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).