Zum Inhalt

JDL — Primitives Spec

Status: Draft
Geltungsbereich: Sprachebene / Stdlib-Oberfläche
Abhängigkeiten: 02-typsystem, 08-generators-und-collectors, 09a-spec-span-interpolation


1. Numerische Typen

1.1 Typenset

Die folgenden numerischen Typen sind normative Sprachprimitive. Sie werden vom Compiler als eigenständige Typen behandelt und direkt auf VM-Immediates abgebildet (siehe 11-jadevalue-und-register-layout.md).

Vorzeichenbehaftet: i8, i16, i32, i64

Vorzeichenlos: u8, u16, u32, u64

Gleitkomma: f32, f64

Plattformgröße (eigenständige Primitive): isize, usize

isize und usize sind keine Typaliase auf i64/u64. Sie sind eigenständige nominale Typen. Auf 64-Bit-Plattformen sind Lossless-Casts zu i64/u64 via CastTo in der Stdlib definiert und werden implizit angewendet. Auf anderen Plattformen gilt dieselbe Regel für die entsprechende Zielgröße. Kein Code darf von einer impliziten Identität zwischen usize und u64 ausgehen.

Nicht im Core: i128, u128 — kein VM-Primitiv, kein Core-Sprachkonstrukt. Können als Stdlib-Typen auf Basis von u64-Paaren realisiert werden, falls benötigt.

1.2 bool

bool ist kein numerischer Typ. Konversionen zwischen bool und Integer-Typen sind Stdlib-Konventionen via CastTo und unterliegen der Orphan-Regel. Die Sprache schweigt dazu normativ.

1.3 Overflow-Semantik

Die Overflow-Semantik von Add, Sub und Mul für primitive Integer-Typen ist eine Entscheidung der Stdlib-Protokollimplementierungen. Offen — ausstehend bis zur Protokoll-Spec.

1.4 Float-Spezifika

NaN-Semantik, Infinity, sowie die Frage welche Protokolle (Equatable, Comparable) für f32 und f64 implementiert werden, sind Stdlib-Entscheidungen. Die Sprache schreibt keine spezifische Implementierung vor. Offen — ausstehend bis zur Protokoll-Spec.


2. Literal-Syntax

2.1 Typinferenz

Numerische Literale sind typenlos und werden vom Kontext inferiert. Wenn kein Kontext einen Typ vorgibt, gelten folgende Fallbacks:

  • Ganzzahl-Literal → i64
  • Gleitkomma-Literal → f64

2.2 Suffixe

Ein Literal kann einen expliziten Typ-Suffix tragen:

42u8
3.14f32
1000i64
255u8

Der Suffix überschreibt die Inferenz und den Fallback.

2.3 Basis-Präfixe

Ganzzahl-Literale unterstützen folgende Präfixe:

  • 0x — hexadezimal
  • 0b — binär
  • 0o — oktal

Präfixe und Suffixe sind kombinierbar: 0xFFu8, 0b1010_1010i32.

2.4 Underscore-Separator

Underscores dürfen innerhalb von Literalen als visuelle Trenner verwendet werden.

Erlaubt: 1_000_000, 0xFF_00_FF, 0b1010_1010
Verboten: führender Underscore (_42), abschließender Underscore (42_), Underscore direkt nach Präfix (0x_FF).

2.5 Gleitkomma-Literale

Ein Gleitkomma-Literal erfordert zwingend einen Dezimalpunkt. Wissenschaftliche Notation ist erlaubt, aber nur in Kombination mit einem Dezimalpunkt.

Valide: 1.0, 3.14, 1.0e10, 1.5e-3, 1.0E10
Fehler: 1e10 — kein Dezimalpunkt, kein valides Float-Literal

Der Lexer entscheidet anhand des Dezimalpunkts eindeutig ob ein Literal ein Integer oder ein Float ist. Kein Lookahead auf e/E nötig.


3. Unit-Literale

Unit-Literale sind Methodenaufrufe auf numerischen Literalen:

5000.ms
1.5.s
100.px

Der Lexer behandelt IntLiteral.Identifier und FloatLiteral.Identifier als Method-Call. Der Parser lowert 5000.ms zu (5000).ms() — einem normalen Methodenaufruf auf dem Literal-Wert.

Unit-Methoden sind normale Methoden die in der Stdlib via provide i64 { ... } (o.ä.) definiert werden. Die Orphan-Regel gilt: nur der Modul-Besitzer eines Typs darf direkte Methoden via provide T { } definieren. Für primitive Typen ist das die Stdlib. User-Code kann keine Unit-Methoden auf Primitiven definieren.

Kein sprachliches Sonderwissen über spezifische Units (ms, s, px, ...). Die Menge der verfügbaren Unit-Methoden ist keine geschlossene Sprachliste, sondern eine Frage der Stdlib-Oberfläche.


4. Typen-Hierarchie der Primitives

4.1 Unit

Unit ist der Typ mit genau einem Bewohner. () ist ein Alias für Unit. Die textuelle Form Unit wird bevorzugt. Beide Schreibweisen sind normativ gleichwertig.

Funktionen ohne sinnvollen Rückgabewert geben Unit zurück: def log(msg: str) -> Unit

4.2 Never

Never ist der Bottom-Typ — kein Bewohner, keine Instanz ist konstruierbar. Funktionen vom Typ Never kehren nie zurück (Endlosschleife, Panic, divergente Berechnung). Ein Wert vom Typ Never ist zu jedem anderen Typ kompatibel.

4.3 bool

bool hat genau zwei Bewohner: true und false. Kein numerischer Typ.


5. Collections

5.1 Sprachkonstrukte mit eigener Syntax

Die folgenden Collection-Typen sind Sprachkonstrukte — der Compiler kennt ihre Syntax, der Parser behandelt sie speziell.

Dynamisches Array: [T]

  • Wächst zur Laufzeit
  • Übergabe an Funktionen ist eine Kopie (kein implizites Borrowing)
  • Indexzugriff: arr[0]
  • Range-Indexzugriff: arr[1..3]1..3 ist ein Range[i32], Ergebnis ist [T] (Kopie)
val names: [str] = ["Alice", "Bob", "Charlie"]
var list: [str] = []
list.push("Dave")

Fixgröße-Array: [T; N]

  • Größe ist compile-time konstant
  • N ist ein Literal-Typ
val rgb: [u8; 3] = [255u8, 128u8, 0u8]

Tuple: (T, U, V)

  • Heterogen, geordnet, feste Länge
  • Feldzugriff via .0, .1, .2, ...
  • Destrukturierung: val (lat, lon) = pos
val pos: (f64, f64) = (52.52, 13.405)
val (lat, lon) = pos

Normativ: (T) — ein Tuple mit einem Element — ist syntaktisch identisch mit dem gruppierten Ausdruck T. Der Parser akzeptiert (T) nur in zwei Kontexten: Gruppierung in Ausdrücken/Typen, oder als Beginn einer Funktionssignatur wenn -> folgt. (T) als eigenständiger Tuple-Typ existiert nicht und führt zu einem Parserfehler in anderen Kontexten.

5.2 Stdlib-Typen ohne Literal-Syntax

Die folgenden Typen sind reine Stdlib-Typen. Sie haben keine sprachlich privilegierte Literal-Syntax.

Map[K, V]

Konstruktion via Collector-Sugar aus Tuple-Generator:

val scores = [("Alice", 42), ("Bob", 17)] |> {}
// oder explizit:
val scores = Map.from([("Alice", 42), ("Bob", 17)])

Blueprint-Literale decken den Fall compile-time-fixer benannter Felder ab und ersetzen damit "anonyme Dict-Literale" für den häufigsten Anwendungsfall.

Set[T]

Set[T] erfordert T: Hashable + Equatable. Das ist eine normative Constraint — kein Set[T] ohne diese Protokollerfüllung ist konstruierbar.

Konstruktion via Collector (Phase 2 des Generator/Collector-Modells):

val unique = names |> Set.from

Kein Set-Literal, keine eigene Syntax.

5.3 Nicht vorgesehen

Anonyme Structs — nicht vorgesehen. JDL hat konsequent nominale Typen. Anonyme Structs würden strukturelle Typisierung erfordern und mit dem Coherence-Modell kollidieren.

Strukturelle Typen — nicht vorgesehen. Pattern Matching auf Feldern (Destrukturierung) ist kein struktureller Typ, sondern ein lokales Parser-/Compiler-Feature auf nominalen Typen.


6. Pattern Matching — Ausstehende Normierung

Die folgenden Pattern-Matching-Features sind architektonisch beschlossen aber noch nicht normativ spezifiziert. Sie werden in einer eigenen Spec-Sektion festgehalten.

Feld-Destrukturierung — Matching auf Feldern eines nominalen Typs ohne Typ-Äquivalenz zu behaupten:

match user {
    | { name, age } => ...
}

Guard-Syntax — Boolean-Guard nach Destrukturierung:

match user {
    | { age } if age >= 18 => ...
    | { age }              => ...
}

Typ-Patterns in TypeFns — Shape-Matching auf Typ-Ebene:

typefn Flatten[T] =
    match T {
        | [Inner] => Flatten[Inner]
        | _       => T
    }

Protocol-Constraint-Matching:

typefn Serialize[T] =
    match T {
        | T: Numeric    => NumberSerializer[T]
        | T: Stringable => StringSerializer[T]
        | _             => RawSerializer[T]
    }

Literal-Typ-Matching:

typefn StatusMessage[Code: i32] =
    match Code {
        | 200 => OkMessage
        | 404 => NotFoundMessage
        | _   => GenericMessage
    }

Normative Beziehung zwischen Literal-Typen und Enums:

Enums in JDL sind konzeptuell Literal-Typ-Unions auf Wert-Ebene:

type HttpSuccessCode = 200 | 201 | 204

Literal-Typ-Matching in typefn ist die strukturell äquivalente Form auf Typ-Ebene. Beide Mechanismen sind dasselbe Prinzip auf unterschiedlichen Ebenen:

Ebene Konstrukt Discriminant
Wert-Ebene match value { \| 200 => ... } Laufzeitwert
Typ-Ebene typefn F[C: i32] = match C { \| 200 => ... } Compile-Zeit-Typ-Parameter

Ein Typ-Parameter der gegen Literal-Arme gematcht wird, ist zur Compile-Zeit vollständig aufgelöst. Das Ergebnis ist ein statisch bekannter Typ, kein dynamischer Dispatch. typefn-Matching auf Literal-Typen ist damit die Grundlage für ausdrucksstarke APIs die den Typ des Ergebnisses vom Wert eines Parameters abhängig machen — ohne Laufzeit-Overhead und ohne Compiler-Magie.

Normative Exhaustiveness-Regeln für alle Pattern-Formen: ausstehend.


7. Offene Punkte

Thema Status
Overflow-Semantik Add/Sub/Mul für Integer Offen — ausstehend bis Protokoll-Spec
Float: Equatable/Comparable, NaN-Verhalten Offen — ausstehend bis Protokoll-Spec
Move-Semantik für Collections Offen — ausstehend bis Memory-Policy-Spec
Pattern Matching — normative Spec Ausstehend — eigene Spec-Sektion
Set-Collector-Sugar (\|> {set} o.ä.) Phase 2 — ausstehend