====== 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).