Zum Inhalt

JDL – Modulsystem und FFI

Status: Draft
Geltungsbereich: Sprachebene (keine VM-Implementierungsdetails)


Warum Dieses Dokument Existiert

Sobald aus Sprachfeatures echte Projekte werden, entstehen zwei Druckpunkte:

  • Modulgrenzen müssen klar sein (Sichtbarkeit, Imports, API-Kontrolle).
  • FFI muss sicher eingebettet werden, ohne das Effektsystem zu verwässern.

Dieses Dokument fixiert genau diese Übergänge.


0. Priorität

Ergänzt:

  • 00-refinements.md
  • 01-grundlagen.md
  • 02-typsystem.md
  • 03-runtime.md

Bei Widerspruch gelten 00 bis 03.


1. Leitbild

  • modulare Struktur über Dateipfade,
  • klare Trennung zwischen service (Vertrags-/Abstraktionsebene) und extern (FFI-Deklaration),
  • eine einzige Service-Zuordnungsform: provide Service for Handler.

2. Modulsystem

2.1 Grundregeln

  • Dateipfad bestimmt Modulname (users/service.jdl -> users::service).
  • mod.jdl ist optionales API-Root.
  • Sichtbarkeit folgt der Namespace-Spec: privat (default), package, pub.
  • Zyklische Modulabhängigkeiten sind Compilerfehler.

2.2 Sichtbarkeit

// users/service.jdl
def validateEmail(email: str) -> bool { ... } // privat
package def normalizeEmail(email: str) -> str

pub def findUser(email: str) -> Result[User, UserError]
pub type User: struct { ... }

2.3 Importformen

import users::service
import users::service : User, findUser
import users::service : User as AppUser

Vollimport ist explizit, kein impliziter Wildcard-Trick.

2.4 mod.jdl als API-Schicht

// users/mod.jdl
pub import package::model   : User, UserId
pub import package::service : findUser, registerUser

Damit kann öffentliche API bewusst geformt werden.

Module unter einem internal-Teilbaum folgen zusätzlich der Namespace-Spec und sind außerhalb des aktuellen Packages/Plugins nicht importierbar.

2.5 Lokale Imports

Erlaubt für lokale Lesbarkeit, aber kein Ersatz für explizite Service-Parameter oder Effect[R, E, D]-Rückgabetypen.


3. Service vs. extern

3.1 Rollen

  • protocol: typbezogenes Verhalten,
  • service: Vertrags-/Abstraktionsebene, die über provide Service for Handler an konkrete Implementierungen gebunden wird,
  • extern: rohe FFI-Bindings.

3.2 Harte Trennung

  • extern-Blöcke sind nie selbst Service-Abstraktionen,
  • extern-Blöcke enthalten nur def-Deklarationen,
  • Domänenlogik/Fehlermapping im Handler (provide ... for ...),
  • implements existiert nicht.

3.3 Verbindliche Zuordnung

provide ServiceName for HandlerType { ... }

Nicht zulässig:

  • provide HandlerType for ServiceName
  • extern X : implements Y

4. FFI-Modell

FFI wird in drei Ebenen getrennt. Das ist keine ästhetische Spielerei, sondern Überlebensschutz gegen C-Bibliotheken, die Ownership-Regeln gerne in Kommentaren verstecken und void* als Weltanschauung verwenden.

NativeLayer    rohe ABI-/Symbolbeschreibung
BindingLayer   Ownership, Marshaling, Error-Mapping, Callback-Regeln
ServiceLayer   idiomatische JDL-Vertragsoberfläche

extern bleibt als kompakte Syntax erhalten, ist aber semantisch ein Native-/Binding-Blueprint, aus dem die FfiEngine Descriptoren erzeugt.

4.1 NativeLayer — rohe ABI-Beschreibung

type NativeLibrary : blueprint {
    soname: str
    abi:    AbiKind
}

type NativeFunction : blueprint {
    symbol: str
    input:  [NativeType]
    output: NativeType
    abi:    AbiKind
}

Ein extern-Block ist syntaktischer Zucker für eine NativeLibrary mit mehreren NativeFunction-Einträgen:

extern LibSQLite {
    def sqlite3_exec(db: OpaqueHandle, sql: CString, cb: Callback, userdata: OpaquePtr, err: Out[CString]) -> CInt
        :> CSymbol("sqlite3_exec")
} :> FFILink("sqlite3")

Semantisch entsteht daraus ein NativeLibraryDescriptor mit FfiCallDescriptor-Einträgen.

4.2 BindingLayer — Ownership, Marshaling, Fehler

Der BindingLayer beschreibt, wie rohe ABI-Realität in Jade-Semantik überführt wird.

type NativeBinding : blueprint {
    library:    NativeLibrary
    functions:  [NativeFunction]
    ownership:  [OwnershipRule]
    marshaling: [MarshalRule]
    errors:     [ErrorMapping]
    callbacks:  [CallbackRule]
}

Diese Ebene modelliert unter anderem:

  • out- und inout-Parameter,
  • nullable Pointer,
  • borrowed/owned Handles,
  • opaque Handles,
  • errno und Return-Code-Fehler,
  • Callback + userdata,
  • Thread-Affinity,
  • blockierende Calls,
  • optionale oder versionierte Symbole,
  • custom Allocator-Regeln.

4.3 ServiceLayer — idiomatische JDL-API

Services beschreiben den Vertrag, den User-Code sieht. Sie referenzieren keine nativen Symbole direkt.

service Db {
    def query(sql: str) -> Result[RowSet, DbError]
}

type SqliteHandler: struct { db: DbHandle }

provide Db for SqliteHandler {
    def query(self, sql: str) -> Result[RowSet, DbError] {
        LibSQLite.sqlite3_exec(self.db, sql, Callback.none, OpaquePtr.null, Out.none)
            |> Result.mapErr(DbError.from)
    }
}

Die Service-Schicht ist die Effect-relevante Grenze. Ein Effect[R, E, D] referenziert D = Db, nicht LibSQLite.sqlite3_exec.

4.4 Ring-0-Bridge und Descriptoren

Die FfiEngine validiert NativeLayer und BindingLayer und erzeugt Descriptoren:

NativeLibraryDescriptor
FfiCallDescriptor
MarshalDescriptor
OwnershipDescriptor
ErrorMappingDescriptor
CallbackDescriptor

Die Ring-0-JadeValue-Bridge führt diese Descriptoren mechanisch aus: dlopen, dlsym, ABI-Call, Register-/JadeValue-Marshalling, Handle-Pinning, Trap-Grenzen. Sie entscheidet nicht, was ein SQLite-Fehler bedeutet oder wem ein Pointer gehört.

4.5 Syntaxkonsistenz

def innerhalb von extern-Blöcken, nicht fn.

4.6 FFI-Typsicherheit

Erlaubt:

  • primitive Skalare,
  • ptr[T] nur an expliziten FFI-Grenzen,
  • opaque Handles,
  • ABI-kompatible Struct/Enum-Repräsentationen,
  • explizite Native-/Binding-Blueprints.

Nicht erlaubt:

  • captured Closures,
  • arena-gebundene Typen über falsche Grenzen,
  • lokale Share-Typen über unzulässige Übergänge,
  • unklare generische ABI-Signaturen,
  • Effects die native Symbole direkt als Dependency exportieren.

5. Normative Invarianten

  1. Service-Zuordnung nur via provide Service for Handler.
  2. implements ist kein Sprachbestandteil.
  3. extern-Blöcke sind keine Service-Abstraktionen und werden nicht direkt als Vertragsoberfläche verwendet.
  4. extern-Blöcke enthalten keine Domänenlogik.
  5. FFI-Einstieg benutzt def innerhalb von extern-Blöcken.
  6. Symbolbindung läuft über Refinements (:> CSymbol(...)) oder über NativeFunction.symbol.
  7. Library-Linkage läuft über :> FFILink(...) auf dem extern-Block oder über NativeLibrary.soname.
  8. extern desugared zu Native-/Binding-Blueprints und erzeugt FFI-Descriptoren.
  9. Services sind die Effect-relevante Grenze; Effects referenzieren Services, nicht native Symbole.
  10. Die JadeValue Bridge bleibt Ring 0 und führt ausschließlich mechanisch aus.

6. Offene Punkte (Phase 2)

  • Callback-Brücken,
  • detaillierte ABI/Calling-Convention-Policies,
  • formale repr: C-Regeln,
  • Paketmanager-/Versionsauflösung außerhalb Sprachkern.