====== Module und Import-System ======
Jede Lyx-Quelldatei ist gleichzeitig ein **Modul** (in Lyx-Terminologie: eine **Unit**). Die Unit-Deklaration am Dateianfang legt den Namensraum fest. ''import'' lädt eine andere Unit und macht ihre öffentlichen Symbole sichtbar. ''pub'' steuert, welche Symbole exportiert werden.
Das System ist bewusst einfach: keine Pakete, keine Versionsverwaltung im Sprachkern, keine zyklischen Importe. Eine Unit = eine Datei = ein Namensraum.
→ [[lyx_-_programmiersprache:sprache:syntax|Syntax-Referenz]] · [[lyx_-_programmiersprache:erste-schritte|Erste Schritte]] · [[lyx_-_programmiersprache:units|Standard Library]]
----
===== 1. Unit-Deklaration =====
Jede ''.lyx''-Datei beginnt mit einer ''unit''-Deklaration:
unit my_module;
**Konventionen:**
* Der Unit-Name entspricht dem Dateinamen ohne ''.lyx''-Endung
* Unterverzeichnisse werden durch Punkte getrennt: Datei ''net/http_client.lyx'' → ''unit net.http_client;''
* Erlaubte Zeichen: Kleinbuchstaben, Ziffern, Unterstriche. Keine Großbuchstaben, keine Bindestriche.
* Die ''unit''-Zeile steht als **erste** Zeile der Datei, vor jedem Import und jedem Kommentar.
// Datei: sensors/temp_reader.lyx
unit sensors.temp_reader;
import std.io;
import std.math;
> **Diese Punkte sind Konventionen, keine geprüften Regeln.** Der Import löst allein über den **Dateipfad** auf; die ''unit''-Zeile wird geparst, aber nicht ausgewertet. Ein abweichender Name (''unit ganz.anderer.name;''), ein Großbuchstabe, ein Kommentar oder Import davor — und selbst eine ganz fehlende ''unit''-Zeile — werden anstandslos übersetzt.
>
> Halten Sie die Konvention trotzdem ein: Sie ist die einzige Stelle, an der die Datei ihre eigene Modulzugehörigkeit benennt, und beim Verschieben von Dateien läuft sonst still auseinander, was zusammengehört.
> ([[https://github.com/SEOLizer/LyX-Compiler/issues/1237|Issue #1237]])
----
===== 2. Import-Syntax =====
Imports folgen direkt auf die Unit-Deklaration, vor allen anderen Deklarationen:
unit my_app;
// Standardbibliothek
import std.io;
import std.string;
import std.net.socket;
// Eigene Units
import sensors.temp_reader;
import utils.math_helpers;
* Die Import-Reihenfolge spielt keine Rolle für den Compiler.
* Mehrere Imports der gleichen Unit sind erlaubt, aber redundant.
* Zirkuläre Importe (A importiert B, B importiert A) sind **nicht erlaubt**: ''sema error: zyklischer Import erkannt: vv.cb → vv.ca''.
* Ein Selbstimport (''unit vv.se;'' mit ''import vv.se;'') wird dagegen ohne Meldung übersetzt — er ist wirkungslos, aber kein Fehler.
==== Qualifizierte Namen ====
Nach dem Import sind alle ''pub''-Symbole der Ziel-Unit direkt (unqualifiziert) sichtbar:
import std.string;
fn Greet(name: pchar): void {
var upper: pchar := alloc(256);
StrToUpper(upper, name); // kein "string." Prefix nötig
Print(upper);
}
Wenn zwei importierte Units dasselbe Symbol exportieren, entsteht eine **Namenskollision**. Der Compiler meldet das als Fehler. Lösung: Einen der Importe weglassen oder die Symbole umbenennen (derzeit kein Alias-Import).
----
===== 3. Sichtbarkeit mit pub =====
Standardmäßig ist alles **privat** — nur innerhalb der eigenen Unit sichtbar. ''pub'' macht ein Symbol exportierbar:
> **Seit lyxc 1.0.11: Sichtbarkeit wird beim Import durchgesetzt.** Frühere Compiler-Versionen werteten ''pub'' beim Import nicht aus — unit-private Symbole waren faktisch global sichtbar. Code, der versehentlich ein privates Symbol einer fremden Unit benutzt hat, übersetzt jetzt nicht mehr. Die Lösung ist, das Symbol in der anbietenden Unit als ''pub'' zu markieren, wenn es tatsächlich Schnittstelle sein soll.
>
> Private Symbole beenden die Suche nicht, sondern werden übersprungen: Ein ''import std.alloc; alloc(16)'' findet weiterhin das öffentliche ''alloc'', auch wenn eine andere importierte Unit ein gleichnamiges privates besitzt. Gemeldet wird erst, wenn es überhaupt keine öffentliche Fassung gibt.
==== Funktionen ====
unit math_utils;
// Öffentlich — von anderen Units importierbar
pub fn Square(x: int64): int64 {
return x * x;
}
// Privat — nur innerhalb math_utils sichtbar
fn SquareInternal(x: int64): int64 {
return x * x;
}
==== Konstanten ====
unit constants;
pub con PI: f64 := 3.14159265358979;
pub con MAX_CONN: int64 := 1024;
con INTERNAL_BUF: int64 := 4096; // privat
==== Variablen ====
unit config;
pub var LogLevel: int64 := 2; // Von außen lesbar und schreibbar
var _internalState: int64 := 0; // Privat
> Öffentliche Variablen (''pub var'') sind selten sinnvoll — sie erzeugen globalen Zustand der von jeder importierenden Unit verändert werden kann. Bevorzuge ''pub fn Get...()'' / ''pub fn Set...()'' Accessoren.
==== Typen, Enums, Structs, Klassen ====
unit geometry;
// Öffentlicher Typ — andere Units können Point verwenden
pub type Point = struct {
x: f64;
y: f64;
};
// Öffentliches Enum
pub enum Axis { X, Y, Z }
// Privater Hilfstyp — nur intern
type InternalMatrix = struct { data: int64; };
> **Ein Typ in einer öffentlichen Signatur muss selbst ''pub'' sein.** Wird ''InternalMatrix'' im Rückgabetyp oder Parameter einer ''pub fn'' verwendet, kann die importierende Unit den Aufruf nicht übersetzen: ''sema error: 'InternalMatrix' ist in Unit 'geometry' nicht pub''.
> **Die Prüfung hängt daran, dass der Typ ausgeschrieben wird.** Sie greift bei ''var m: InternalMatrix := Mach();'' — nicht aber bei ''var m := Mach();''. Die Kurzform übersetzt anstandslos, und der Wert ist seit lyxc 1.0.15E auch brauchbar: Der nicht-''pub'' Typ wandert damit ungeprüft über die Unit-Grenze.
>
> Ein zweiter Fall bleibt: ''var g := a;'' mit einer Struct-**Variablen** als Initialisierer übernimmt die Referenzsemantik nicht — ''g.a := 9'' lässt ''a.a'' unverändert, während ''var g: S := a;'' korrekt auf dasselbe Objekt zeigt. Schreiben Sie den Typ bei Structs aus.
==== Ausnahme: FFI-Bindungen ====
''extern fn … link''-Deklarationen brauchen kein ''pub''. Bei Units, deren Zweck gerade das Anbieten von FFI-Bindungen ist, bleiben sie ohne Sichtbarkeitsmarkierung nutzbar — ''std/audio/alsa.lyx'' etwa trägt an keiner ''snd_pcm_*''-Zeile ein ''pub''.
----
===== 4. Namensraum und Kollisionen =====
Lyx hat keine explizite Namespace-Syntax — jede Unit **ist** ihr Namensraum. Nach dem Import sind alle ''pub''-Symbole direkt im lokalen Scope sichtbar.
import std.db.sqlite; // exportiert: SQLiteOpen, SQLiteClose, SQLITE_ROW, ...
import std.db.postgres; // exportiert: PGConnect, PGClose, PGQuery, ...
// Kein Konflikt — unterschiedliche Präfixe per Konvention (SQLite* vs. PG*)
var db: int64 := SQLiteOpen("/data/app.db");
var conn: int64 := PGConnect("localhost", 5432, "mydb", "user", "pass");
Die Standardbibliothek nutzt überwiegend **Unit-spezifische Präfixe** als Konvention, um Kollisionen ohne Namespace-Qualifier zu vermeiden:
^ Unit ^ Prefix-Konvention ^ Beispiel ^
| ''std.db.sqlite'' | ''SQLite*'' | ''SQLiteOpen'', ''SQLITE_ROW'' |
| ''std.db.postgres'' | ''PG*'' | ''PGConnect'', ''PGQuery'' |
| ''std.pdf.builder'' | ''Pdf*'' | ''PdfCreate'', ''PdfSave'' |
| ''std.net.socket'' | ''TCP*'' / ''UDP*'' / ''Socket*'' | ''TCPListenerNew'', ''UDPSocketSendTo'' |
| ''std.string'' | ''Str*'' / ''Char*'' | ''StrFind'', ''CharToUpper'' |
| ''std.crypto.aes'' | ''AES*'' | ''AESEncryptBlock'' |
Eigene Units sollten dasselbe Muster verwenden — kurzer, eindeutiger Präfix vor allen öffentlichen Symbolen.
> **In der Standardbibliothek ist die Konvention nicht durchgehalten.** 110 der rund 5100 öffentlichen Funktionen werden von mehr als einer Unit exportiert; **31 Unit-Paare lassen sich deshalb nicht gemeinsam importieren**. Betroffen sind unter anderem naheliegende Kombinationen:
>
> ^ Paar ^ kollidierende Namen ^
> | ''std.math'' + ''std.stats'' | ''Clamp64'', ''InRange64'' |
> | ''std.fs'' + ''std.yaml'' bzw. ''std.ini'' | ''WriteFile'' |
> | ''std.ini'' + ''std.yaml'' | 15 Namen, u. a. ''ParseString'', ''GetBool'' |
> | ''std.base64'' + ''std.url'' | ''Encode'', ''Decode'', ''IsValid'' |
> | ''std.math'' + ''std.ml'' | ''AbsF64'', ''ExpF64'', ''LogF64'', ''SqrtF64'' |
>
> Der Fehler entsteht bereits beim **Import** — das kollidierende Symbol muss nicht einmal verwendet werden. Einen Ausweg gibt es derzeit nicht: Alias-Import (''%%import std.math as m;%%'') und qualifizierter Aufruf (''%%std.math.Clamp64(…)%%'') existieren beide nicht, und ''restrict'' ändert nichts.
>
> Bis zur Klärung bleibt nur, eine der beiden Units zu meiden.
> ([[https://github.com/SEOLizer/LyX-Compiler/issues/1262|Issue #1262]])
----
===== 5. Standard Library importieren =====
Die Standardbibliothek liegt im ''std.''-Namespace. Alle wichtigen Units:
// Basisfunktionalität
import std.io; // Print, PrintLn, ReadLine, ...
import std.string; // StrFind, StrReplace, StringBuilder, ...
import std.alloc; // malloc, free_mem, ...
import std.math; // Sin, Cos, Sqrt, Floor, ...
import std.result; // Result
// Netzwerk
import std.net.socket; // TCP, UDP, Unix Sockets
import std.net.http; // HTTP/1.1 Client
import std.net.dns; // DNS-Auflösung
// Datenbanken
import std.db.sqlite; // SQLite3
import std.db.postgres; // PostgreSQL
import std.db.redis; // Redis
// PDF
import std.pdf; // PDF erstellen (High-Level-Einstiegspunkt)
// Crypto
import std.crypto.aes; // AES-Verschlüsselung
import std.hash; // Hashing
// System
import std.fs; // Dateisystem
import std.process; // Prozessverwaltung
import std.thread; // Threads
import std.time; // Zeitfunktionen
import std.env; // Kommandozeilenargumente
Alle verfügbaren Units: → [[lyx_-_programmiersprache:units|Standard Library — vollständige Übersicht]]
----
===== 6. Eigene Multi-Unit-Projekte strukturieren =====
Eine typische Projektstruktur mit mehreren Units:
my_project/
├── main.lyx → unit main;
├── config.lyx → unit config;
├── db/
│ ├── connection.lyx → unit db.connection;
│ └── queries.lyx → unit db.queries;
└── api/
├── handler.lyx → unit api.handler;
└── response.lyx → unit api.response;
// db/connection.lyx
unit db.connection;
import std.db.postgres;
pub con DB_HOST: pchar := "localhost";
pub con DB_PORT: int64 := 5432;
pub fn Connect(dbName: pchar): int64 {
var conn: int64 := PGConnect(DB_HOST, DB_PORT, dbName, "app", "secret");
if (conn == 0 || PGIsConnected(conn) == 0) { return 0; }
return conn;
}
// api/handler.lyx
unit api.handler;
import std.io;
import std.net.http;
import db.connection; // eigene Unit — relativer Pfad
pub fn HandleGetUsers(conn: int64): void {
// conn kommt aus db.connection.Connect(...)
var res: int64 := PGQuery(conn, "SELECT id, name FROM users");
// ...
}
// main.lyx
unit main;
import std.io;
import db.connection;
import api.handler;
pub fn main(): int64 {
var conn: int64 := Connect("myapp");
if (conn == 0) {
PrintLn("Datenbankverbindung fehlgeschlagen");
return 1;
}
HandleGetUsers(conn);
PGClose(conn);
return 0;
}
----
===== 7. Suchpfad und Compilation =====
Der Compiler sucht Units in dieser Reihenfolge:
- Das Verzeichnis der aktuell compilierten Datei
- Alle Verzeichnisse in ''--include-path'' (''lyxc --include-path=/my/libs ...'')
- Das Standard-Library-Verzeichnis (automatisch, ''std.*''-Units)
# Einfacher Build
lyxc main.lyx -o my_app
# Mehrere Include-Pfade
lyxc main.lyx --include-path=./libs --include-path=/opt/lyx/contrib -o my_app
# Alle .lyx-Dateien angeben (bei kleinen Projekten)
lyxc main.lyx db/connection.lyx api/handler.lyx -o my_app
==== Vorkompilierte Units (*.lyu) im Suchpfad ====
Neben ''.lyx''-Quellen kann im Suchpfad eine vorkompilierte ''.lyu'' liegen (erzeugt mit ''%%lyxc --compile-unit quelle.lyx -o quelle.lyu%%''). Für die Auflösung gilt eine einfache Regel:
- **Zuerst die ''.lyx''.** Liegen beide nebeneinander, gewinnt die Quelle — immer.
- **Die ''.lyu'' nur als Rückfall**, wenn keine Quelle gefunden wurde.
lyxc main.lyx --trace-imports -o app
[import] mathe -> mathe.lyx # Quelle vorhanden: sie gewinnt
[import] mathe : mathe.lyx nicht gefunden, versuche .lyu # Rückfall
[import] mathe -> mathe.lyu
**Eine ''.lyu'' allein ist keine benutzbare Unit** ([[https://github.com/SEOLizer/LyX-Compiler/issues/2014|#2014]], nachgemessen mit lyxc 1.2.5C). Sie trägt eine Namensliste — Symbole mit Typ —, aber **keinen Code, keine Struktur und keine Konstantenwerte**.
Fehlt die Quelle, bricht die Übersetzung inzwischen deutlich ab:
error: codegen: Quelle der importierten Unit nicht lesbar: mathe.lyx
Das ist ein Fortschritt gegenüber dem früheren Verhalten ([[https://github.com/SEOLizer/LyX-Compiler/issues/1966|#1966]]), wo eine Konstante still 0 wurde. An der Sache ändert es nichts: **wer Units weitergibt, gibt die ''.lyx'' mit.** Die ''.lyu'' ist eine Bauprobe und eine Inhaltsangabe (''%%--%%unit-info''), kein Bibliotheksformat.
Seit [[https://github.com/SEOLizer/LyX-Compiler/issues/1971|#1971]] trägt das Format immerhin eingebettete Ressourcen mit — siehe [[lyx_-_programmiersprache:units:res|std.res]].
==== Architekturebene im Suchpfad ====
Seit dem ersten Schritt zu [[https://github.com/SEOLizer/LyX-Compiler/issues/2014|#2014]] sucht der Compiler ''.lyu''-Dateien zusätzlich unter einer **Architekturebene**. Er baut aus seinem ''%%--%%target'' genau **einen** Pfad und probiert den — kein Durchprobieren, keine Rangfolge zwischen Architekturen und damit kein Fall, in dem er die falsche erwischt:
^ ''%%--%%target'' ^ gesuchter Pfad ^
| ''arm64'' | ''arm64/std/io.lyu'' |
| ''riscv'' | ''riscv64/std/io.lyu'' |
| ohne Angabe | ''x86_64/std/io.lyu'' |
Erst wenn kein Suchweg mit Architekturebene aufgeht, wird der bisherige Pfad **ohne** sie probiert. Die Ebene gilt für alle fünf Suchwege — relativ, Paketwurzeln, ''-I'', ''%%--%%std-path'' und Installationspfad.
**Am Verhalten ändert das noch nichts.** Die Ablage ist für den Fall vorbereitet, dass eine ''.lyu'' später Objektcode trägt; heute wird die Quelle weiterhin gebraucht. Legt man eine ''.lyu'' unter ''x86_64/'' ab und entfernt die ''.lyx'', findet der Compiler die Datei zwar, bricht aber im Codegen ab.
----
===== 7a. Was der Compiler über Unit-Grenzen prüft =====
Mehrere Prüfungen endeten früher an der Unit-Grenze und lieferten dort still ''0'' statt einer Meldung. Seit lyxc 1.1.0A/1.1.1C greifen sie auch bei **importierten** Units:
^ Fall ^ Verhalten heute ^ Issue ^
| Unbekanntes Struct-Feld einer importierten Unit | ''unknown field'' — Feld- und Methodennamen werden beim Import in eine Seitentabelle geschrieben | [[https://github.com/SEOLizer/LyX-Compiler/issues/1573|#1573]] |
| Doppelt definierte private ''fn'' in einer importierten Unit | wird gemeldet; vorher band der Aufruf an die erste Definition, die Argumentzahl blieb ungeprüft | [[https://github.com/SEOLizer/LyX-Compiler/issues/1567|#1567]] |
| Struct-Parameter über Modulgrenzen | Typprüfung; ''int64'' statt Struct wird abgewiesen | [[https://github.com/SEOLizer/LyX-Compiler/issues/1606|#1606]] |
| Falsche Argumentzahl bei Methoden importierter Klassen | wird gemeldet | [[https://github.com/SEOLizer/LyX-Compiler/issues/1626|#1626]] |
| Klasse aus fremder Unit ohne ''new'' | wird abgewiesen (''new'' oder ''null'' angeben) | [[https://github.com/SEOLizer/LyX-Compiler/issues/1570|#1570]] |
| Zeichenketten-Parameter der Standardbibliothek | als ''pchar'' deklariert — der frühere Cast ''%%"text"c as int64%%'' entfällt an 337 Aufrufstellen | [[https://github.com/SEOLizer/LyX-Compiler/issues/1613|#1613]] |
| Unbekanntes Feld einer Klasse, deren Basisklasse aus einer anderen Unit stammt | ''unknown field'' — vorher still angenommen | [[https://github.com/SEOLizer/LyX-Compiler/issues/1666|#1666]] |
==== Gleichnamige private Funktionen in zwei Units ====
Eine nicht-''pub'' Funktion ist außerhalb ihrer Unit unsichtbar; ihr Name ist damit frei wählbar. Bis lyxc 1.1.2H stimmte das nicht: zwei importierte Units mit je einer privaten ''helper''-Funktion teilten sich am Ende **eine** Definition, ausgewählt über die Reihenfolge der ''import''-Zeilen. Die Stelligkeit wurde dabei nicht geprüft — fehlende Argumente kamen vom Stack ([[https://github.com/SEOLizer/LyX-Compiler/issues/1655|#1655]]). Bei ''pub'' wurde derselbe Fall immer schon korrekt als ''mehrdeutiges Symbol'' gemeldet.
// m/a.lyx
unit m.a;
import std.io;
fn helper(x: int64, y: int64, z: int64): void { PrintLn("A.helper"); }
pub fn CallA(): void { helper(1, 2, 3); }
// m/b.lyx
unit m.b;
import std.io;
fn helper(v: int64): void { PrintLn("B.helper"); }
pub fn CallB(): void { helper(42); }
Ein Programm, das beide Units importiert und ''CallA()'' sowie ''CallB()'' ruft, gibt seit 1.1.2J ''A.helper'' und ''B.helper'' aus — jede Unit ruft ihre eigene Fassung.
Praktische Folge: **eine grüne Übersetzung sagt heute mehr aus als früher.** Wer eine ältere Fassung eines Projekts erstmals mit 1.1.x übersetzt, bekommt deshalb unter Umständen neue Meldungen — sie zeigen Fehler, die vorher stumm blieben.
----
===== 8. Best Practices =====
Empfehlungen für die Strukturierung von Lyx-Projekten:
^ Situation ^ Empfehlung ^
| Namenskollision zwischen Units | Präfix-Konvention einhalten (''MyUnit*'') |
| Globaler Zustand | Lieber ''pub fn Get()'' / ''pub fn Set()'' als ''pub var'' |
| Zyklische Abhängigkeit | Unit aufteilen — gemeinsame Typen in eine separate ''types''-Unit |
| Große Units (> 500 Zeilen) | In Unterunits aufteilen: ''mylib.core'', ''mylib.helpers'', ... |
| Init-Code beim Import | Nicht in Unit-Scope — stattdessen ''pub fn Init()'' explizit aufrufen |
| Interne Hilfsfunktionen | Privat lassen — nur was wirklich von außen gebraucht wird mit ''pub'' |
→ [[lyx_-_programmiersprache:sprache:syntax|Vollständige Syntax-Referenz]]\\
→ [[lyx_-_programmiersprache:erste-schritte|Erste Schritte — erstes Programm]]\\
→ [[lyx_-_programmiersprache:tools:compiler-parameter|Compiler-Parameter]]
Letzte Aktualisierung: 2026-09-05 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1966|#1966]], gemessen mit lyxc 1.2.2B) — Abschnitt „Vorkompilierte Units (*.lyu) im Suchpfad" ergänzt: die ''.lyx'' gewinnt immer, die ''.lyu'' ist nur Rückfall und trägt weder Code noch Konstantenwerte.
Vorherige letzte Aktualisierung: 2026-08-19 — private Namenskollision zwischen Units ([[https://github.com/SEOLizer/LyX-Compiler/issues/1655|#1655]]) und die Feldprüfung über Unit-Grenzen ([[https://github.com/SEOLizer/LyX-Compiler/issues/1666|#1666]]) ergänzt; Beispiele mit ''lyxc 1.1.3I'' übersetzt und ausgeführt.
Vorherige Aktualisierung: 2026-08-18 — Abschnitt „Was der Compiler über Unit-Grenzen prüft" ergänzt (#1573, #1567, #1606, #1626, #1570, #1613).
Vorherige Aktualisierung: 2026-08-11
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).