Old Docs added
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
# Projektplan: Refactoring der Compiler-Phasen
|
||||||
|
|
||||||
|
*Datum: 01.11.2025 16:30*
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Der aktuelle Compiler-Monolith (`TAstBinder`) wurde erfolgreich in logische Phasen aufgeteilt (Expand, Bind, TypeCheck, Lower, TCO). Dabei ist ein schwerwiegendes technisches Problem aufgetreten:
|
||||||
|
|
||||||
|
Die `TAstTransformer`-Basisklasse (in `Myc.Ast.Visitor.pas`) zerstört die spezialisierten `TBound...Node`-Typen während der Transformation. Wenn eine spätere Phase (z.B. `TAstLowerer`) einen Baum transformiert, werden die `TBoundFunctionCallNode`s (aus Phase 2) fälschlicherweise in `TFunctionCallNode`s (Basis-Typ) zurückverwandelt. Dies führt zu Abstürzen beim `as`-Casting in der nachfolgenden Phase (`TAstTCO`).
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Das System muss stabilisiert werden, indem der Typverlust im `TAstTransformer` behoben wird. Es gibt zwei konkurrierende Architekturen, um dieses Ziel zu erreichen.
|
||||||
|
|
||||||
|
## Ergebnis: Lösungs-Pfade
|
||||||
|
|
||||||
|
### Pfad 1: Pragmatische Lösung (Virtuelles Rebuild)
|
||||||
|
|
||||||
|
Dieser Ansatz repariert den `TAstTransformer`, behält aber die bestehende (unsaubere) Datenstruktur bei.
|
||||||
|
|
||||||
|
* **Strategie:** Wir behalten die "Gott-Objekt"-Knoten (`TBound...Node`), die Daten aus allen Phasen enthalten (`Address`, `StaticType`, `IsTailCall`). Wir reparieren den `TAstTransformer` (in `Myc.Ast.Visitor.pas`), indem wir virtuelle `Rebuild...`-Methoden (z.B. `RebuildFunctionCall`) einführen.
|
||||||
|
* **Implementierung:** Die `Visit...`-Methoden des Transformers rufen nicht mehr `TAst.FunctionCall` auf, sondern `Self.RebuildFunctionCall`. Alle unsere Phasen (Binder, Lowerer, TCO) überschreiben diese `Rebuild...`-Methoden und stellen sicher, dass der korrekte `TBound...Node`-Typ (unter Beibehaltung der Metadaten) neu erstellt wird.
|
||||||
|
* **Pro:**
|
||||||
|
* **Schnell:** Behebt den Absturz mit minimalem Eingriff.
|
||||||
|
* **Wenig Code:** Die Phasen müssen weiterhin nur die `Visit...`-Methoden überschreiben, die sie tatsächlich interessieren.
|
||||||
|
* **Contra:**
|
||||||
|
* **Architektur:** Die Datenstruktur bleibt "schmutzig". Implementierungsdetails bluten weiterhin durch (z.B. muss der `TAstBinder` (Phase 2) das Feld `IsTailCall` (Phase 5) initialisieren).
|
||||||
|
|
||||||
|
### Pfad 2: Saubere Architektur (Staged Data Layers)
|
||||||
|
|
||||||
|
Dieser Ansatz definiert für jede Phase eine eigene, unveränderliche Datenstruktur.
|
||||||
|
|
||||||
|
* **Strategie:** Wir verwerfen den `TAstTransformer`. Jede Compiler-Phase (Binder, TypeChecker, ...) wird ein reiner `IAstVisitor`.
|
||||||
|
* **Implementierung:**
|
||||||
|
1. `TAstBinder` (Phase 2) konsumiert `IAstNode` und produziert `IBoundNode` (enthält *nur* `Address`, `IsBoxed`).
|
||||||
|
2. `TTypeChecker` (Phase 3) konsumiert `IBoundNode` und produziert `ITypedNode` (enthält *zusätzlich* `StaticType`).
|
||||||
|
3. (usw. für Lowering und TCO)
|
||||||
|
* Die neuen Knoten (`TBoundNode`, `TTypedNode`) nutzen Aggregation und das `implements`-Schlüsselwort, um die Basis-Schnittstellen (z.B. `IIdentifierNode`) an den aggregierten Knoten der Vor-Phase zu delegieren.
|
||||||
|
* **Pro:**
|
||||||
|
* **Architektur:** Typsicher und sauber. Keine "blutenden" Implementierungsdetails. Daten sind zwischen den Phasen unveränderlich (immutable).
|
||||||
|
* **Robust:** Die Fehlerklasse (`as`-Cast-Fehler) wird eliminiert.
|
||||||
|
* **Contra:**
|
||||||
|
* **Aufwand:** Ein massives Refactoring.
|
||||||
|
* **Boilerplate:** Jede Phase (Binder, TypeChecker, ...) muss *alle* 20+ `Visit...`-Methoden implementieren, um den Baum von Typ `A` in Typ `B` zu überführen, selbst wenn 19 davon nur "Durchreicher" sind.
|
||||||
|
|
||||||
|
## TODO (Nächste Schritte für Pfad 1)
|
||||||
|
|
||||||
|
Gemäß deiner Entscheidung probieren wir **Pfad 1**.
|
||||||
|
|
||||||
|
1. **`Myc.Ast.Visitor.pas` (`TAstTransformer`)**:
|
||||||
|
* `VisitFunctionCall`, `VisitLambdaExpression`, `VisitVariableDeclaration` und `VisitRecordLiteral` (die Knoten, die `TBound...`-Typen haben) so umbauen, dass sie `virtual Rebuild...`-Methoden aufrufen.
|
||||||
|
2. **`Myc.Ast.Binding.pas` (`TAstBinder`)**:
|
||||||
|
* `Rebuild...`-Methoden überschreiben, um `TBound...Node`-Instanzen zu erzeugen (und `IsTailCall=False` zu setzen).
|
||||||
|
3. **`Myc.Ast.Lowering.pas` (`TAstLowerer`)**:
|
||||||
|
* `Rebuild...`-Methoden überschreiben, um `TBound...Node`-Instanzen zu erhalten (und `IsTailCall` vom Original zu kopieren).
|
||||||
|
4. **`Myc.Ast.Compiler.TCO.pas` (`TAstTCO`)**:
|
||||||
|
* `RebuildFunctionCall` überschreiben, um den `IsTailCall`-Status basierend auf dem `FIsTailStack` korrekt zu setzen.
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
### **Projektplan: "Magical & Safe" Concurrency-Modell**
|
||||||
|
**Datum:** 21. September 2025, 12:07
|
||||||
|
|
||||||
|
#### **Motivation**
|
||||||
|
Unser Interpreter besitzt ein solides Fundament für die single-threaded Ausführung. Moderne, datenintensive Anwendungen erfordern jedoch eine robuste und einfach zu handhabende Nebenläufigkeit. Traditionelle Locking-Mechanismen (`TCriticalSection`, Mutexe) sind komplex, extrem fehleranfällig (Race Conditions, Deadlocks) und skalieren schlecht. Ziel ist es, ein modernes Concurrency-Modell direkt in den Sprachkern zu integrieren, das diese Probleme per Design vermeidet.
|
||||||
|
|
||||||
|
#### **Ziel**
|
||||||
|
Wir erweitern die Sprache um ein Nebenläufigkeits-Modell, das drei Kernziele verfolgt:
|
||||||
|
|
||||||
|
1. **Sicherheit (Safe):** Es muss für den Programmierer unmöglich sein, durch konkurrierende Schreibzugriffe auf geteilten Zustand eine Race Condition zu erzeugen. Die Sprache garantiert die Atomarität von Zustandsänderungen.
|
||||||
|
|
||||||
|
2. **"Magie" (Magical):** Die Sicherheitsmechanismen sollen für den Programmierer **transparent** sein. Er schreibt weiterhin einfachen, sequenziellen Code mit `def` und `assign`. Der Compiler/Binder kümmert sich automatisch um die notwendigen Schutzmaßnahmen, ohne dass der Programmierer explizite Synchronisierungs-Primitive wie `swap!` oder `atom` verwenden muss.
|
||||||
|
|
||||||
|
3. **Performance:** Der durch die Sicherheitsmechanismen entstehende Overhead darf nur dort anfallen, wo er zwingend notwendig ist – also nur bei Zuständen, die tatsächlich geteilt *und* verändert werden.
|
||||||
|
|
||||||
|
#### **Geplante Umsetzung & Architektur**
|
||||||
|
Um diese Ziele zu erreichen, führen wir eine klare semantische Trennung zwischen unveränderlichen Werten und veränderlichen Identitäten ein und implementieren die "Magie" im Binder.
|
||||||
|
|
||||||
|
**1. Das Fundament: Unveränderliche Datenstrukturen (Values)**
|
||||||
|
Der Grundpfeiler für sichere Nebenläufigkeit ist, dass Werte (Values) per Definition unveränderlich (immutable) sind. Wenn Daten einmal erzeugt wurden, können sie sich niemals ändern.
|
||||||
|
|
||||||
|
* **Todo:** Implementierung einer Kernbibliothek von **persistenten, unveränderlichen Datenstrukturen** (insbesondere Vektoren und Maps), die "Structural Sharing" für effiziente "Updates" nutzen. Bestehende `Series` müssen entweder durch diese ersetzt oder angepasst werden.
|
||||||
|
|
||||||
|
**2. Veränderliche Identitäten (Refs)**
|
||||||
|
Der veränderliche Zustand wird über **Identitäten** (Referenzen) verwaltet. Eine Identität ist ein stabiler "Container", dessen enthaltener Wert atomar ausgetauscht werden kann. Dies entspricht exakt dem `atom`-Konzept von Clojure.
|
||||||
|
|
||||||
|
* **Todo:** Implementierung einer generischen `TAtom<T>`-Klasse in Delphi. Die `Swap`-Methode dieser Klasse wird **lock-frei** mittels einer **`TInterlocked.CompareExchange`-Retry-Schleife** implementiert, um maximale Performance und Skalierbarkeit zu gewährleisten.
|
||||||
|
|
||||||
|
**3. Die "Magie": Automatische Promotion im Binder**
|
||||||
|
Hier wird der "magische" Aspekt umgesetzt. Der `TAstBinder` wird um eine intelligente Zustandsanalyse erweitert.
|
||||||
|
|
||||||
|
* **Logik:**
|
||||||
|
1. Standardmäßig wird jede Variable als einfacher, performanter Wert im Scope-Speicher behandelt.
|
||||||
|
2. Der Binder analysiert die Verwendung von Variablen in untergeordneten Scopes (Closures).
|
||||||
|
3. Sobald der Binder einen **Schreibzugriff (`assign`)** auf eine **Upvalue** (eine Variable aus einem äußeren Scope) entdeckt, wird diese Variable im äußeren Scope automatisch "promotet": Ihr Speicherplatz wird von einem einfachen Wert zu einer Instanz der sicheren `TAtom<T>`-Klasse umgewandelt.
|
||||||
|
4. Alle nachfolgenden Lese- und Schreibzugriffe auf diese Variable (sowohl im äußeren als auch in allen inneren Scopes) werden vom Binder automatisch in sichere Aufrufe auf dem `TAtom` umgeschrieben.
|
||||||
|
|
||||||
|
* **Todo:** Erweiterung des `TAstBinder` um die Logik zur Erkennung von Upvalue-Schreibzugriffen, zur automatischen Promotion von Variablen und zur Umschreibung der entsprechenden AST-Zugriffsknoten.
|
||||||
|
|
||||||
|
---
|
||||||
|
### **Nächste Schritte (Todo-Liste)**
|
||||||
|
|
||||||
|
* [ ] Kernbibliothek für persistente, unveränderliche Datenstrukturen (Vector, Map) entwerfen und implementieren.
|
||||||
|
* [ ] Lock-freie `TAtom<T>`-Klasse als primäres Concurrency-Primitiv implementieren.
|
||||||
|
* [ ] `TAstBinder` um die "automatische Promotions"-Logik für Upvalues erweitern.
|
||||||
|
* [ ] Multi-threaded Unit-Tests schreiben, um die Korrektheit und Sicherheit des neuen Modells unter Last zu verifizieren.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
## 📝 Projektplan: Refactoring der AST-Visualisierung (Architekturfokus)
|
||||||
|
|
||||||
|
**Datum und Uhrzeit:** 03.11.2025 09:02:21
|
||||||
|
|
||||||
|
### 💡 Motivation: Entkopplung und Skalierbarkeit
|
||||||
|
|
||||||
|
Die **aktuelle Implementierung** des AST Visualisierers in FireMonkey basiert auf einem Design mit **starker Kopplung** zwischen der **semantischen Logik** (Layout-Anordnung) und der **visuellen Darstellung** (Farben, Rahmen). Dies äußert sich in:
|
||||||
|
|
||||||
|
1. **Visuelle Inflexibilität:** Die `TAuraNode.Paint`-Methode verwendet **Custom Drawing**, was die zentrale Steuerung visueller Eigenschaften (z.B. für einen Dark Mode oder Branding) über FMX Style-Dateien unmöglich macht.
|
||||||
|
2. **Architektonische Starrheit:** Die Ableitung einer FMX-Control-Klasse (`TAuraXxxNode`) für jeden AST-Knotentyp ist ein Verstoß gegen das **Open/Closed Principle** und macht die Einführung neuer Knotentypen unnötig aufwendig.
|
||||||
|
|
||||||
|
Ziel des Refactorings ist die Trennung dieser Bedenken, um ein robustes, skalierbares System zu schaffen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏛️ Architektur: Strategisches Schichtenmodell
|
||||||
|
|
||||||
|
Die neue Architektur ersetzt die Vererbung durch **Komposition** und verteilt die Verantwortung auf drei Schichten:
|
||||||
|
|
||||||
|
#### 1. FMX Style Aggregation (Visuelle Schicht)
|
||||||
|
|
||||||
|
Diese Schicht übernimmt die komplette **visuelle Gestaltung** des äußeren Rahmens.
|
||||||
|
|
||||||
|
* **`TAuraNode` (Control Shell):** Dient als generisches FMX-Control-Grundgerüst. Es erbt von `TStyledControl` und verwendet ein **Style-Lookup** basierend auf dem AST-Knotentyp (`constantstyle`, `ifexpressionstyle`, etc.).
|
||||||
|
* **Visuelle Eigenschaften:** Eigenschaften wie `BackgroundColor`, `BorderWidth` etc. sind nicht länger Felder in `TAuraNode`. Stattdessen werden sie über die **FMX Style Accessoren** (`GetStyleProperty`/`SetStyleProperty`) direkt in der geladenen FMX Style-Datei gespeichert und gelesen.
|
||||||
|
* **Style-Bindung:** Die `ApplyStyle`-Methode findet das primäre Style-Element (`TRectangle` mit `StyleName='background'`) und wendet die Style-Eigenschaften (Farbe, Dicke, Radius) auf dieses Element an, wodurch das Custom Drawing in `Paint` obsolet wird.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. Logik-Aggregation (`INodeViewModel` / Semantische Schicht)
|
||||||
|
|
||||||
|
Dieses Aggregat kapselt die gesamte Knoten-spezifische Logik (was einen `if`-Knoten von einem `lambda`-Knoten unterscheidet).
|
||||||
|
|
||||||
|
* **`INodeViewModel`:** Das zentrale **Strategie-Interface**. Es definiert die Schnittstellen **`Setup(HostNode, ...)`** zur Erstellung des Layouts und **`CreateAst(HostNode)`** zur Rekonstruktion des AST-Knotens aus dem visuellen Zustand.
|
||||||
|
* **`TNodeViewModelRegistry`:** Eine Factory-Klasse, die zur Laufzeit das korrekte `INodeViewModel`-Objekt (z.B. `TIfExpressionViewModel`) basierend auf dem `TAstNodeKind` des aktuellen Knotens instanziiert.
|
||||||
|
* **`TNodeViewModelBase`:** Bietet Helfer-Methoden (`AddLabel`, `AddExpr` mit Rekursion), die es der `Setup`-Methode ermöglichen, das semantische Layout (z.B. die vertikale Anordnung von "if", "then" und "else" Labels und Kind-Knoten) zu erstellen, ohne FMX-Interna zu duplizieren.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. Ergebnis (Zusammenfassung)
|
||||||
|
|
||||||
|
Die Architektur trennt die **statische visuelle Gestaltung** (FMX Style) von der **dynamischen Layout-Logik** (`INodeViewModel`). Die generische Klasse `TAuraNode` wird zum Host, der die visuelle Schicht steuert und die Layout-Erstellung an die Logik-Schicht delegiert.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Projektplan: Refactoring auf First-Class List Nodes
|
||||||
|
|
||||||
|
**Datum:** 29.11.2025 16:43
|
||||||
|
|
||||||
|
## 1. Motivation
|
||||||
|
Aktuell werden Listen von Elementen im AST (z. B. Parameter in `Lambda`, Argumente in `Call`, Felder in `RecordLiteral`) als native Arrays (`TArray<T>`) innerhalb des Eltern-Knotens gespeichert. Dies führt zu signifikanten Problemen bei der Entwicklung des Projectional Editors:
|
||||||
|
|
||||||
|
1. **Fehlende Adressierbarkeit:** Eine Liste als `TArray` hat keine Identität (`IAstIdentity`) und keine Position. Sie kann im Editor nicht als Ganzes selektiert, fokussiert oder hervorgehoben werden.
|
||||||
|
2. **Das "Leere-Liste"-Problem:** Wenn eine Liste leer ist, gibt es keinen visuellen Anker (wie einen Platzhalter zwischen Klammern), den der Benutzer anklicken kann, um das erste Element einzufügen.
|
||||||
|
3. **Inkonsistente UI-Logik:** Jeder Handler (`LambdaHandler`, `CallHandler`, etc.) muss derzeit selbstständig Logik für Klammern `()`, Trennzeichen `,` und Layout implementieren. Dies führt zu Code-Duplizierung.
|
||||||
|
4. **Komplexe Manipulation:** Operationen wie "Verschiebe Argument 2 an Position 1" oder "Lösche alle Parameter" sind schwierig umzusetzen, da die Logik fest im Eltern-Knoten verdrahtet ist und nicht an einen generischen Listen-Handler delegiert werden kann.
|
||||||
|
5. **Record-Felder:** Aktuell sind Key-Value-Paare (`TRecordFieldLiteral`) reine Records, keine AST-Nodes. Sie können daher nicht einzeln selektiert oder per Drag & Drop verschoben werden.
|
||||||
|
|
||||||
|
Um einen robusten, wartbaren Editor zu gewährleisten, muss das Prinzip **"Alles, was sichtbar und manipulierbar ist, muss ein AST-Knoten sein"** konsequent angewendet werden.
|
||||||
|
|
||||||
|
## 2. Ziel
|
||||||
|
Umbau der AST-Struktur und der Editor-Handler, um Listen und Record-Felder als eigenständige Knoten zu etablieren.
|
||||||
|
|
||||||
|
### Kernaufgaben:
|
||||||
|
1. **AST-Erweiterung (`Myc.Ast.Nodes`):**
|
||||||
|
* Einführung eines generischen Interfaces `INodeList<T: IAstNode>`.
|
||||||
|
* Einführung spezifischer Listen-Typen zur Wahrung der Typsicherheit: `IParameterList`, `IArgumentList`, `IRecordFieldList`.
|
||||||
|
* Einführung von `IRecordFieldNode` als Wrapper für Key-Value-Paare.
|
||||||
|
* Erweiterung des `TAstNodeKind` Enums.
|
||||||
|
|
||||||
|
2. **Anpassung der Factories & Visitor (`Myc.Ast` & `Myc.Ast.Visitor`):**
|
||||||
|
* Update der Factory-Methoden (z.B. `TAst.LambdaExpr`), um Listen-Nodes statt Arrays zu akzeptieren.
|
||||||
|
* Erweiterung des `IAstVisitor` um Methoden für die neuen Knotentypen.
|
||||||
|
|
||||||
|
3. **Generischer UI-Handler (`Myc.Fmx.AstEditor.Handlers`):**
|
||||||
|
* Implementierung von `TNodeListHandler<T>`, der das Rendering von Listen (Start-Zeichen, Trennzeichen, End-Zeichen, Layout) zentralisiert.
|
||||||
|
* Implementierung der `IEditableNodeHandler`-Logik im Listen-Handler (Hinzufügen neuer Elemente).
|
||||||
|
|
||||||
|
4. **Refactoring existierender Handler:**
|
||||||
|
* Vereinfachung von `TLambdaExpressionNodeHandler`, `TFunctionCallNodeHandler` und `TRecordLiteralNodeHandler` durch Delegation an den neuen `TNodeListHandler`.
|
||||||
|
|
||||||
|
## 3. Ergebnis
|
||||||
|
* **Architektonische Konsistenz:** Der AST spiegelt die logische Struktur der Sprache und die visuelle Struktur des Editors 1:1 wider.
|
||||||
|
* **Reduzierte Komplexität:** UI-Logik für Listen existiert nur noch einmal zentral im `TNodeListHandler`.
|
||||||
|
* **Erweiterte Funktionalität:** Listen können nun selektiert, kopiert und geleert werden. Leere Listen sind durch ihre Klammern als Drop-Target für neue Elemente nutzbar.
|
||||||
|
* **Typsicherheit:** Trotz generischer Implementierung im Editor bleibt die semantische Unterscheidung (Parameter vs. Argumente) im AST und Compiler erhalten.
|
||||||
|
|
||||||
|
## 4. Nächster Schritt
|
||||||
|
Implementierung der Änderungen in `Myc.Ast.Nodes` (Definition der Interfaces `INodeList`, `IParameterList`, `IArgumentList`, `IRecordFieldList`, `IRecordFieldNode`) und Anpassung der `TAstNodeKind` Enumeration.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
### Projektplan: Hybrid-Makro-System
|
||||||
|
**Datum:** 06.11.2025 14:39
|
||||||
|
|
||||||
|
#### Motivation
|
||||||
|
Die Implementierung von `stopwatch` als RTL-Funktion war unzureichend, da der Zugriff auf andere Laufzeit-Symbole (wie `print`) einen langsamen Laufzeit-Lookup (`FindSymbolAddress`) erfordert hätte. Die Umwandlung in ein Makro löst das Bindungs-Problem, wirft aber das Problem der Makro-Hygiene auf. Explizite Lösungen (wie `gensym` oder `$`-Suffixe) sind syntaktisch zu komplex, unleserlich und/oder unzureichend.
|
||||||
|
|
||||||
|
#### Ziel
|
||||||
|
Entwicklung eines impliziten, automatischen Makro-Systems. Dieses System muss **hygienisch für Definitionen** sein (um Kollisionen bei internen Variablen wie `start-time` zu verhindern), aber **unhygienisch für freie Symbole** (um kontext-abhängige Makros, die z.B. `*debug-mode*` lesen, zu ermöglichen).
|
||||||
|
|
||||||
|
Die Komplexität der Hygiene soll vollständig vom Makro-Autor in den Makro-Expander (`TExpansionVisitor`) verlagert werden.
|
||||||
|
|
||||||
|
#### Ergebnis
|
||||||
|
Ein Makro-System, bei dem:
|
||||||
|
1. **Automatische Hygiene:** Alle *Definitionen* (`def`, `fn`-Parameter etc.) innerhalb eines Makro-Templates (`quasiquote`) automatisch und eindeutig umbenannt werden (z.B. `start-time` -> `start-time_123`). Dies löst das Verschachtelungs- und Kollisionsproblem (`stopwatch` in `stopwatch`).
|
||||||
|
2. **Unhygienische Freie Symbole:** Alle *freien Symbole* (z.B. `print`, `timestamp`, `*debug-mode*`), die im Makro-Template *verwendet*, aber *nicht definiert* werden, unberührt bleiben. Diese werden (unhygienisch) vom `TAstBinder` im *Aufruf-Scope* des Benutzers aufgelöst.
|
||||||
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
Hier ist der vollständig aktualisierte Projektplan, der die Verwaltung des Caches als Environment-spezifische Aufgabe (statt global) korrekt abbildet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Monomorphisierung (Revidierter Plan v2)
|
||||||
|
|
||||||
|
09.11.2025 19:10
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Der aktuelle Compiler ist in seiner Optimierungsfähigkeit limitiert. Der `TAstLowerer`-Pass implementiert eine hartkodierte statische Spezialisierung ausschließlich für vordefinierte Operatoren (z.B. `(+)` -> `TBinaryExpressionNode`). Alle anderen Aufrufe, einschließlich der restlichen RTL (z.B. `Abs`) und sämtlicher benutzerdefinierter Funktionen, werden zur Laufzeit dynamisch über den `vkMethod`-Pfad aufgelöst. Dieser Pfad erfordert das Boxing von Werten in `TDataValue`-Wrapper und einen Scope-Lookup, was einen signifikanten Performance-Overhead darstellt, selbst wenn alle Typen zur Compile-Zeit bekannt wären.
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Implementierung einer **hybriden Monomorphisierungsstrategie** zur Compile-Zeit. Das Ziel ist die Eliminierung des `vkMethod`-Overheads für *alle* Aufrufe, deren Callee und Argumenttypen statisch auflösbar sind.
|
||||||
|
|
||||||
|
1. **Statische Spezialisierung:** Aufrufe an bekannte Funktionen (RTL oder User-Code) mit bekannten Argumenttypen werden zur Compile-Zeit an eine hochoptimierte, statisch gebundene Implementierung gemappt.
|
||||||
|
2. **Dynamischer Fallback:** Aufrufe, die nicht statisch aufgelöst werden können (z.B. Higher-Order-Functions, bei denen der Callee aus einer Variable stammt), nutzen weiterhin den bestehenden `vkMethod`-Pfad.
|
||||||
|
|
||||||
|
Diese Strategie macht die spezialisierten `TBinaryExpressionNode` und `TUnaryExpressionNode` sowie den `TAstLowerer`-Pass obsolet und ersetzt sie durch einen generalisierten und wesentlich leistungsfähigeren Mechanismus.
|
||||||
|
|
||||||
|
## Ergebnis & Implementierungsplan
|
||||||
|
|
||||||
|
### 1. Erweiterung: `IFunctionCallNode`
|
||||||
|
Anstatt einen neuen Knotentyp einzuführen, wird der bestehende `IFunctionCallNode` in `Myc.Ast.Nodes` erweitert, um *beide* Aufrufpfade (dynamisch und statisch) zu repräsentieren.
|
||||||
|
|
||||||
|
* Er erhält ein neues Property: **`StaticTarget: TDataValue.TFunc`**.
|
||||||
|
* **Dynamischer Pfad (Default):** Wenn **`StaticTarget = nil`**, wird der Knoten wie bisher über den `vkMethod`-Pfad ausgewertet (dynamischer Dispatch über den `Callee`-Knoten).
|
||||||
|
* **Statischer Pfad (Optimiert):** Wenn **`StaticTarget <> nil`**, ignoriert der Evaluator den `Callee`-Knoten und ruft stattdessen das **`StaticTarget`** direkt mit den ausgewerteten Argumenten auf.
|
||||||
|
* Die `TAst.FunctionCall`-Factory in `Myc.Ast.pas` wird um einen optionalen **`AStaticTarget`**-Parameter erweitert.
|
||||||
|
|
||||||
|
### 2. Neue Compiler-Phase: `TStaticSpecializer`
|
||||||
|
Ein neuer `TAstTransformer` namens `TStaticSpecializer` wird implementiert. Er läuft *nach* dem `TTypeChecker` (Phase 3) und *ersetzt* den `TAstLowerer` (Phase 4).
|
||||||
|
|
||||||
|
* **Konstruktor:** Der `TStaticSpecializer` erhält im Konstruktor eine Referenz auf den `IEnvironment`-spezifischen Monomorphisierungs-Cache (siehe Punkt 3).
|
||||||
|
* **`VisitFunctionCall`-Logik:** Dies ist die Kernmethode.
|
||||||
|
1. Sie prüft, ob der `Callee` des `IFunctionCallNode` ein `IIdentifierNode` ist (z.B. `+`, `Abs`, `my-func`).
|
||||||
|
2. Sie prüft, ob *alle* Argumenttypen (`newArgs[i].StaticType`) statisch bekannt sind (d.h. nicht `stUnknown`).
|
||||||
|
3. **Statischer Pfad (Ja):** Der `TStaticSpecializer` fragt den ihm übergebenen **Environment-Cache** nach einer spezialisierten `TDataValue.TFunc` ab. Bei Erfolg **klont** er den `IFunctionCallNode` (via CoW) und setzt dessen **`StaticTarget`**-Property auf die gefundene Funktion.
|
||||||
|
4. **Dynamischer Pfad (Nein):** Der `IFunctionCallNode` wird (ggf. geklont, falls Kindknoten sich änderten) mit **`StaticTarget = nil`** zurückgegeben.
|
||||||
|
|
||||||
|
### 3. Der Environment-spezifische Cache
|
||||||
|
Das **`IEnvironment`** verwaltet einen **Instanz-spezifischen** Cache (z.B. `TDictionary`), der bereits spezialisierte Funktionen vorhält. Dieser Cache ist *nicht* global oder statisch.
|
||||||
|
|
||||||
|
* Jede `IEnvironment`-Instanz (z.B. eine für Produktion, eine für Tests) hat ihren eigenen, isolierten Cache, der an ihre `RootScope` und RTL gebunden ist.
|
||||||
|
* Die `TEnvironment`-Implementierung (in `Myc.Ast.Environment.pas`) wird um dieses `TDictionary`-Feld erweitert.
|
||||||
|
* Der `TStaticSpecializer` erhält eine Referenz auf diesen Cache bei seiner Erstellung (z.B. über den Konstruktor).
|
||||||
|
* **Schlüssel:** `(Funktions-ID, TArray<IStaticType>)`.
|
||||||
|
* *Funktions-ID*: Ein eindeutiger Bezeichner für den Callee, gebilded aus der `TResolvedAddress`, die innerhalb des Environments einen eindeutigen Schlüssel darstellt.
|
||||||
|
* **Wert:** Die spezialisierte `TDataValue.TFunc`.
|
||||||
|
|
||||||
|
### 4. Cache-Miss-Strategie (Inlining & User-Code)
|
||||||
|
Wenn der `TStaticSpecializer` einen statisch auflösbaren Aufruf (z.B. `(my-func 10)`) findet, der noch nicht im **Environment-Cache** ist (Cache Miss):
|
||||||
|
|
||||||
|
1. Er holt den AST-Body der Zielfunktion (z.B. `(+ x x)` aus `(def my-func (fn [x] (+ x x)))`).
|
||||||
|
2. Er instanziiert diesen Body, indem er das Wissen über die Argumenttypen (z.B. `x = stOrdinal`) anwendet.
|
||||||
|
3. Er lässt diesen neuen, instanziierten AST-Body (`(+ <x:Ordinal> <x:Ordinal>)`) rekursiv durch die relevanten Compiler-Phasen laufen (mindestens `TypeCheck` und `Specialize`).
|
||||||
|
4. Der `Specialize`-Pass wandelt den Body (z.B. `(+ <x:Ordinal> <x:Ordinal>)`) rekursiv in einen *neuen* `IFunctionCallNode` um, dessen **`StaticTarget`** auf die RTL-Funktion `@TRtlFunctions.Add_Ordinal_Ordinal` zeigt.
|
||||||
|
5. Das Ergebnis (die `TDataValue.TFunc`, die den optimierten Body repräsentiert) wird im **Environment-Cache** gespeichert.
|
||||||
|
6. Der ursprüngliche `IFunctionCallNode` `(my-func 10)` wird durch einen Klon ersetzt, dessen **`StaticTarget`** auf die soeben kompilierte Funktion zeigt.
|
||||||
|
|
||||||
|
### 5. RTL-Erweiterung
|
||||||
|
* `Myc.Ast.RTL.Core` wird um statisch typisierte Implementierungen (z.B. `class function Add_Ordinal_Ordinal(A, B: Int64): Int64; static;`) erweitert.
|
||||||
|
* Die `TRtlRegistry` wird angepasst, um diese statischen Signaturen zu indizieren und als "Bootstrap" für den Monomorphisierungs-Cache bereitzustellen.
|
||||||
|
|
||||||
|
### 6. Evaluator-Anpassung (`TEvaluatorVisitor`)
|
||||||
|
* `VisitFunctionCall` wird modifiziert, um beide Pfade zu behandeln:
|
||||||
|
1. **Statischer Pfad:** `if Assigned(Node.StaticTarget) then`
|
||||||
|
* Wertet die Argument-Nodes aus (mit `Assert(arg.IsTyped)`).
|
||||||
|
* Marshallt die `TDataValue`-Ergebnisse direkt in die erwarteten nativen Typen.
|
||||||
|
* Ruft das **`Node.StaticTarget`** direkt auf.
|
||||||
|
* Wrappt das Ergebnis zurück in ein `TDataValue`.
|
||||||
|
* Ruft `HandleTCO` auf (falls das **`StaticTarget`** ein `recur` war).
|
||||||
|
2. **Dynamischer Pfad:** `else`
|
||||||
|
* Behält die bestehende Logik (`vkMethod`-Lookup) und die TCO-Thunk-Erzeugung bei (`if Node.IsTailCall then ...`).
|
||||||
|
* `VisitBinaryExpression` und `VisitUnaryExpression` werden entfernt.
|
||||||
|
|
||||||
|
### 7. Auswirkungen auf TCO (Tail Call Optimization)
|
||||||
|
* Die TCO bleibt für `recur` und *dynamische* `fn`-Aufrufe (die `IFunctionCallNode` mit **`StaticTarget = nil`** bleiben) voll funktionsfähig und erzeugt `TThunk`s.
|
||||||
|
* Ein `IFunctionCallNode` mit gesetztem **`StaticTarget`** (z.B. `(Abs x)`) in einer Tail-Position wird *nicht* per TCO optimiert. Er ist per Definition keine Rekursion. Der `TEvaluatorVisitor` führt ihn direkt aus und beendet damit korrekt die Trampolin-Schleife.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
* `IFunctionCallNode` in `Myc.Ast.Nodes` um **`StaticTarget: TDataValue.TFunc`** erweitern.
|
||||||
|
* `TFunctionCallNode` (Implementierungsklasse) um Feld, Konstruktorparameter und Getter erweitern.
|
||||||
|
* `TAst.FunctionCall`-Factory in `Myc.Ast.pas` um optionalen **`AStaticTarget`**-Parameter erweitern.
|
||||||
|
* `TAstTransformer.VisitFunctionCall` in `Myc.Ast.Visitor` anpassen, um **`StaticTarget`** bei CoW zu kopieren.
|
||||||
|
* `TBinaryExpressionNode`, `TUnaryExpressionNode` (und ihre `Visit...`-Methoden) aus allen Units (`Nodes`, `Visitor`, `Dumper`, `Evaluator`, `Lowerer`, `Json`, `Fmx.AstEditor.Node`) entfernen.
|
||||||
|
* `TAstLowerer` aus dem Kompilierungsprozess in `TEnvironment.Compile` entfernen.
|
||||||
|
* `Myc.Ast.RTL.Core` um statisch typisierte Funktionsvarianten für alle Operatoren und gängige Funktionen (Abs, Trunc etc.) ergänzen.
|
||||||
|
* `TRtlRegistry` erweitern, um diese statischen Signaturen zu indizieren.
|
||||||
|
* **`IEnvironment`** (und Implementierung) um einen Member für den Monomorphisierungs-Cache erweitern.
|
||||||
|
* `TStaticSpecializer` als neuen `TAstTransformer`-Pass implementieren.
|
||||||
|
* **`TStaticSpecializer`-Konstruktor** erweitern, um den Cache vom `IEnvironment` entgegenzunehmen.
|
||||||
|
* `TStaticSpecializer.VisitFunctionCall` mit der Logik für statische/dynamische Pfade implementieren (Klonen des `IFunctionCallNode` mit gesetztem **`StaticTarget`**).
|
||||||
|
* Monomorphisierungs-Cache (Lookup und "Cache Miss"-Rekursion) im `TStaticSpecializer` implementieren (unter Verwendung des **Environment-Caches**).
|
||||||
|
* `TEnvironment.Compile` aktualisieren, um den `TStaticSpecializer` anstelle des `TAstLowerer` aufzurufen.
|
||||||
|
* `TEvaluatorVisitor.VisitFunctionCall` modifizieren, um den statischen Pfad (`if Assigned(Node.StaticTarget)`) zu implementieren.
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
|
||||||
|
# Projektplan: Hybride Monomorphisierung
|
||||||
|
|
||||||
|
* **Datum:** 30.10.2025
|
||||||
|
* **Version:** 1.0
|
||||||
|
|
||||||
|
## 1. Motivation
|
||||||
|
|
||||||
|
Das aktuelle Typsystem (`Myc.Ast.Types`) ist mächtig, aber die Inferenz für Lambda-Parameter endet bei `stUnknown`. Dies erzwingt Laufzeit-Typüberprüfungen im `TEvaluatorVisitor` und verkompliziert AOT/JIT-Kompilierung.
|
||||||
|
|
||||||
|
## 2. Ziel
|
||||||
|
|
||||||
|
Implementierung einer **hybriden Monomorphisierungsstrategie** zur Compile-Zeit (innerhalb des `TAstBinder`).
|
||||||
|
|
||||||
|
1. **Statische Spezialisierung:** Alle Funktionsaufrufe, deren Callee *und* Argumenttypen zur Compile-Zeit statisch bekannt sind, werden spezialisiert. `stUnknown` wird für diese Pfade eliminiert.
|
||||||
|
2. **Dynamischer Fallback:** Alle dynamischen Aufrufe (z.B. Higher-Order-Funktionen, deren Callee aus einer Variable stammt) nutzen weiterhin den bestehenden dynamischen Pfad (`vkMethod` mit `TDataValue`-Wrapper).
|
||||||
|
3. **Optimierungs-Vorbereitung:** Der statisch spezialisierte AST (der "Entrypoint") dient als saubere Basis für optionale Optimierungen (z.B. LLVM-Codegen).
|
||||||
|
|
||||||
|
## 3. Ergebnis: Die Strategie
|
||||||
|
|
||||||
|
Die Implementierung erfordert eine Erweiterung des `TAstBinder`, um einen **Spezialisierungs-Cache** zu verwalten und bei Bedarf **rekursive Binding-Pässe** durchzuführen.
|
||||||
|
|
||||||
|
### Phase 1: Modifikation der Kern-Typen
|
||||||
|
|
||||||
|
1. **`TBoundLambdaExpressionNode` (Generic):**
|
||||||
|
* Muss seinen *originalen, ungebundenen* Body (`IAstNode`) behalten, um als Vorlage für das Re-Binding zu dienen.
|
||||||
|
* Führt einen **Spezialisierungs-Cache** (z.B. `TDictionary<TSignatureHash, ISpecializedBody>`).
|
||||||
|
2. **`ISpecializedBody` (Interface):**
|
||||||
|
* Repräsentiert einen erfolgreich monomorphisierten Entrypoint.
|
||||||
|
* `function GetBodyAst: IAstNode;`
|
||||||
|
* `function GetScopeDescriptor: IScopeDescriptor;`
|
||||||
|
* `function GetReturnType: IStaticType;`
|
||||||
|
3. **`TBoundFunctionCallNode`:**
|
||||||
|
* Benötigt ein neues Feld, um *entweder* den dynamischen Callee (wie bisher) *oder* den statischen `ISpecializedBody` (den Entrypoint) zu halten.
|
||||||
|
|
||||||
|
### Phase 2: Anpassung des `TAstBinder` (Trigger)
|
||||||
|
|
||||||
|
`TAstBinder.VisitFunctionCall` wird zur zentralen Weichenstellung:
|
||||||
|
|
||||||
|
1. Binde alle Argumente und ermittle ihre statischen Typen (die `Aufrufsignatur`).
|
||||||
|
2. Binde den `Callee`.
|
||||||
|
3. **Fallunterscheidung:**
|
||||||
|
* **Fall A (Dynamischer Aufruf):** Der `Callee` ist *nicht* als `TBoundLambdaExpressionNode` statisch bekannt (z.B. `(map (if flag f1 f2) ...)`).
|
||||||
|
* **Aktion:** Der Aufruf wird als dynamisch belassen (Fallback). Es wird lediglich geprüft, ob der `Callee` den Typ `stMethod` hat. Es findet keine Spezialisierung statt.
|
||||||
|
* **Fall B (Statischer Aufruf):** Der `Callee` ist ein `TBoundLambdaExpressionNode`.
|
||||||
|
* **Aktion:** Trigger die Monomorphisierung.
|
||||||
|
* Rufe `Callee.GetSpecialization(Aufrufsignatur)` auf.
|
||||||
|
* Der `TBoundFunctionCallNode` wird modifiziert und verweist direkt auf das zurückgegebene `ISpecializedBody`.
|
||||||
|
* Der `StaticType` des `TBoundFunctionCallNode` wird auf `ISpecializedBody.GetReturnType` gesetzt.
|
||||||
|
|
||||||
|
### Phase 3: Die Monomorphisierung (Der Re-Bind Pass)
|
||||||
|
|
||||||
|
Dies ist die Kernlogik (z.B. in `TBoundLambdaExpressionNode.GetSpecialization`):
|
||||||
|
|
||||||
|
1. **Cache-Lookup:** Prüfe, ob für die `Aufrufsignatur` bereits ein `ISpecializedBody` im Cache existiert.
|
||||||
|
2. **Cache-Miss (Re-Binding):**
|
||||||
|
* Erzeuge einen neuen, temporären `IScopeDescriptor` für den Sub-Bind-Prozess.
|
||||||
|
* **Populiere den Scope:** Definiere die Parameter-Namen (z.B. `[a b]`) in diesem Scope, aber verwende die *konkreten Typen* der `Aufrufsignatur` (z.B. `stFloat`, `stOrdinal`) anstelle von `stUnknown`.
|
||||||
|
* **Rekursiver Binder:** Erzeuge einen neuen `TAstBinder` (oder rufe den aktuellen re-entrant auf), der den *originalen, ungebundenen Body* des Lambdas mit diesem spezialisierten Scope bindet.
|
||||||
|
* **Fehlerbehandlung:** Schlägt dieser Sub-Bind-Prozess fehl (z.B. `ETypeException`, weil `a` (jetzt `stFloat`) mit einem `Text` addiert wird), ist der Aufruf ungültig -> **Compile-Fehler**.
|
||||||
|
* **Erfolg:** Das Ergebnis ist der spezialisierte `IAstNode` (Body) und der `IScopeDescriptor`. Erzeuge ein `ISpecializedBody`-Objekt, speichere es im Cache und gib es zurück.
|
||||||
|
|
||||||
|
### Phase 4: Anpassung des `TEvaluatorVisitor` (Ausführung)
|
||||||
|
|
||||||
|
`TEvaluatorVisitor.VisitFunctionCall` muss nun zwei Arten von Calls behandeln:
|
||||||
|
|
||||||
|
1. **Dynamischer Call:** `Callee` ist `vkMethod`. (Wie bisher: `(calleeValue.AsMethod)(argValues)`).
|
||||||
|
2. **Statischer/Monomorphisierter Call:** `Callee` ist ein `ISpecializedBody`.
|
||||||
|
* Erzeuge den `IExecutionScope` (via `ISpecializedBody.GetScopeDescriptor`).
|
||||||
|
* Populiere den Scope mit den (bereits evaluierten) `argValues`.
|
||||||
|
* Führe den `ISpecializedBody.GetBodyAst` direkt mit `Accept(Self)` aus. (TCO muss hier ebenfalls beachtet werden).
|
||||||
|
|
||||||
|
### Phase 5: LLVM-Optimierung (Optional)
|
||||||
|
|
||||||
|
Der in Phase 3 erzeugte `ISpecializedBody` ist der "corner case" für die Optimierung:
|
||||||
|
|
||||||
|
* Wenn ein `ISpecializedBody` erfolgreich erstellt wurde *und* dieser AST-Body die "harten" Kriterien erfüllt (keine dynamischen Internals), wird er als **Kandidat für LLVM-AOT/JIT** markiert.
|
||||||
|
* Ein LLVM-Backend kann diese Kandidaten aufnehmen, LLVM-IR generieren und den `ISpecializedBody` im Cache durch einen nativen Funktionspointer (den FFI-Entrypoint) ersetzen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. TODOs
|
||||||
|
|
||||||
|
* [ ] `TBoundLambdaExpressionNode` erweitern, um den originalen Body und den Spezialisierungs-Cache zu halten.
|
||||||
|
* [ ] `ISpecializedBody` (oder äquivalente Struktur) definieren.
|
||||||
|
* [ ] `TAstBinder.VisitFunctionCall` um die Weichenstellung (Fall A/B) erweitern.
|
||||||
|
* [ ] Den re-entranten Monomorphisierungs-Pass (Phase 3) implementieren.
|
||||||
|
* [ ] `TEvaluatorVisitor.VisitFunctionCall` für die Ausführung von `ISpecializedBody` anpassen.
|
||||||
|
* [ ] Typsystem (`TTypeRules`) auf Robustheit für die neuen, strikten Prüfungen im Sub-Bind-Pass testen.
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stream & Pipe Optimization
|
||||||
|
|
||||||
|
### 1. Global Series Deduplication (The "Registry" Pattern)
|
||||||
|
|
||||||
|
Die aktuelle Architektur erzeugt für jeden Pipe-Eingang einen eigenen Akkumulator. Bei komplexen Graphen mit identischen Datenquellen führt dies zu redundantem Speicherverbrauch.
|
||||||
|
|
||||||
|
* **Konzept:** Einführung einer **Global Series Registry**, die als Mediator zwischen `IStream` und `TPipeSource` fungiert.
|
||||||
|
* **Mechanismus:** Anstatt eine `TScalarSeries` privat zu instanziieren, fordert die `TPipeSource` eine Serie über einen Composite-Key `(IStream, IKeyword)` an.
|
||||||
|
* **Vorteil:** Wenn zehn Pipes den `Close`-Preis desselben Tickers beobachten, existiert im gesamten Arbeitsspeicher nur eine einzige Instanz der Daten-Historie.
|
||||||
|
* **Lookback-Management:** Die Registry überwacht die Anforderungen aller Konsumenten und stellt sicher, dass die geteilte Serie immer den **maximalen Lookback** aller beteiligten Pipes vorhält.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Local State Optimization (Ring-Buffer Series)
|
||||||
|
|
||||||
|
Für Berechnungen innerhalb der Lambda-Funktion (z.B. kurzfristige Delta-Vergleiche) sind die auf Chunks basierenden `TScalarSeries` aufgrund ihrer Verwaltungs-Overheads ineffizient.
|
||||||
|
|
||||||
|
* **Konzept:** Implementierung spezialisierter **Local Ring-Buffer**, die für ultrakurze Lookbacks (z.B. ) optimiert sind.
|
||||||
|
* **Mechanismus:** Ein statisch allozierter Speicherblock mit `truncate-on-add`-Logik. Im Gegensatz zu Chunks gibt es hier keine Heap-Fragmentation durch dynamisches Wachsen.
|
||||||
|
* **Scope:** Diese Strukturen sind "Lambda-Local". Sie werden nicht über die Registry geteilt, sondern dienen als privater, blitzschneller "Scratchpad-Speicher" für zustandsbehaftete Berechnungen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Signal Transmission & Pressure Relief
|
||||||
|
|
||||||
|
Das ständige Allokieren von `TArray<TScalar.TValue>` in der `Emit`-Methode erzeugt bei hohen Tick-Raten signifikanten Druck auf den Memory-Manager und das Reference-Counting.
|
||||||
|
|
||||||
|
* **Konzept:** **Allocation Pooling** oder **Buffer Slicing** für Signal-Payloads.
|
||||||
|
* **Mechanismus:** Einführung eines `TValueBufferPool`. Streams leihen sich ein Array für den `Emit`-Vorgang aus. Sobald der letzte Observer das Signal verarbeitet hat, kehrt das Array in den Pool zurück.
|
||||||
|
* **Alternative:** Nutzung von `Record`-basierten Memory-Slices, um Kopier-Operationen beim Erzeugen des Signals gänzlich zu eliminieren (**Zero-Copy**).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Runtime Adaptability (Dynamic Lookback)
|
||||||
|
|
||||||
|
Aktuell ist der Lookback einer Serie bei der Initialisierung festgeschrieben. In einem dynamischen System müssen Pipes jedoch zur Laufzeit hinzugeschaltet werden können.
|
||||||
|
|
||||||
|
* **Konzept:** **Elastic Series Buffers**.
|
||||||
|
* **Mechanismus:** Die `TScalarSeries` erhält die Fähigkeit, ihre Chunk-Kapazität dynamisch nach oben zu korrigieren, ohne die Integrität der bestehenden Indizes zu verletzen.
|
||||||
|
* **Motivation:** Dies ermöglicht "Hot-Swapping" von Logik-Komponenten. Eine neue Analyse-Pipe kann sich in einen laufenden Stream einklinken und eine Erweiterung der Historie anfordern, ohne dass das System neu gestartet werden muss.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Computational Economy (Lazy/Dirty Logic)
|
||||||
|
|
||||||
|
In tiefen Graphen werden oft Pipes getriggert, deren Eingangsdaten sich zwar im Cycle geändert haben, deren für die Berechnung relevante Werte aber identisch geblieben sind.
|
||||||
|
|
||||||
|
* **Konzept:** **Change-Detection & Lazy Evaluation**.
|
||||||
|
* **Mechanismus:** Jedes Signal erhält eine Versionierung. Pipes führen ihre Lambda nur aus, wenn sich die Versionen der Eingangs-Serien tatsächlich geändert haben oder wenn ein "Dirty"-Flag gesetzt ist.
|
||||||
|
* **Ziel:** Massive Einsparung von CPU-Zyklen in Szenarien, in denen viele Pipes auf hochfrequenten Streams hängen, aber nur selten Schwellenwerte überschritten werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Zusammenfassung der Architektur-Ziele
|
||||||
|
|
||||||
|
| Fokus | Methode | Resultat |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **Memory** | Global Registry | Eliminierung redundanter Daten-Historien. |
|
||||||
|
| **Latency** | Ring-Buffers | Beschleunigung lokaler Lambda-Berechnungen. |
|
||||||
|
| **Throughput** | Buffer Pooling | Minimierung von GC-Pausen und Allokations-Overhead. |
|
||||||
|
| **Agility** | Dynamic Lookback | Unterstützung von On-the-fly Rekonfiguration. |
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
### **Roadmap: Implementierung des Interaktiven Semantischen Editors**
|
||||||
|
|
||||||
|
**Leitprinzip:** Die Serialisierung ist kein nachträgliches Feature, sondern das Fundament. Das Datenformat wird zuerst vollständig definiert und implementiert. Die Visualisierungs- und Editierfunktionen bauen darauf auf.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **Phase 1: Das Fundament – Datenmodell & Persistenz**
|
||||||
|
|
||||||
|
**Ziel:** Eine robuste Serialisierungs-Engine zu schaffen, die den gesamten Zustand des Editors (AST, logische Metadaten, Instanz-Metadaten) verlustfrei speichern und laden kann, *bevor* eine einzige visuelle Komponente existiert.
|
||||||
|
|
||||||
|
1. **Definition der Kern-Datenstrukturen:**
|
||||||
|
* **TViewModelID**: Ein Int64-Typ als eindeutige, stabile ID für jede visuelle Knoteninstanz.
|
||||||
|
* **TVisualNodeViewModel**: Die Vermittlerklasse zwischen IAstNode und TAuraNode. Enthält eine Referenz auf den IAstNode und seine eigene TViewModelID.
|
||||||
|
* **TLogicalMetadata**: Record für Metadaten, die an einen IAstNode gebunden sind (z.B. semantische Farbcodierung).
|
||||||
|
* **TVisualInstanceMetadata**: Record für Metadaten, die an eine TViewModelID gebunden sind. **Hier wird die neue Anforderung verankert:**
|
||||||
|
* PositionOverride: TPointF
|
||||||
|
* IsCollapsed: Boolean
|
||||||
|
* VisualizationMode: (vmSyntactic, vmSemantic) // Definiert, wie DIESE Instanz ihre Kinder darstellt.
|
||||||
|
2. **Festlegung des finalen JSON-Formats:**
|
||||||
|
* Das JSON-Format wird von Anfang an so entworfen, dass es alle zukünftigen Anforderungen abbilden kann. Es besteht aus drei Hauptteilen:
|
||||||
|
1. **ast**: Der reine IAstNode-Baum. Zur Persistenz wird jedem IAstNode beim Speichern eine temporäre, datei-interne Integer-ID zugewiesen.
|
||||||
|
2. **logicalMetadata**: Ein Dictionary, das die temporären IAstNode-IDs auf ihre TLogicalMetadata abbildet.
|
||||||
|
3. **instanceMetadata**: Ein Dictionary, das die stabilen TViewModelIDs auf ihre TVisualInstanceMetadata abbildet (inklusive des neuen VisualizationMode).
|
||||||
|
3. **Implementierung des Serialisierungs- & Deserialisierungs-Backbones:**
|
||||||
|
* Entwicklung der Routinen, die einen IAstNode-Baum und die dazugehörigen Metadaten-Dictionaries entgegennehmen und eine JSON-Datei gemäß Schritt 1.2 erzeugen.
|
||||||
|
* Entwicklung der Gegenstücke, die eine solche JSON-Datei einlesen und die In-Memory-Strukturen (IAstNode-Baum, Dictionaries für Metadaten) vollständig und konsistent wiederherstellen.
|
||||||
|
* **Wichtig:** Diese Logik arbeitet komplett ohne UI-Komponenten.
|
||||||
|
|
||||||
|
**Ergebnis von Phase 1:** Eine voll funktionsfähige "headless" Lade- & Speicher-Bibliothek. Man kann einen Editor-Zustand programmatisch erzeugen, speichern, wieder laden und die Datenintegrität per Unit-Tests verifizieren. **Die Serialisierung ist damit vom ersten Tag an das stabilste Element der Architektur.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **Phase 2: Die Flexible Visualisierungs-Engine**
|
||||||
|
|
||||||
|
**Ziel:** Die in Phase 1 definierten Datenstrukturen sichtbar machen und den Wechsel zwischen syntaktischer und semantischer Darstellung ermöglichen.
|
||||||
|
|
||||||
|
1. **Der "ViewModel-Builder"-Visitor:**
|
||||||
|
* Dieser Visitor nimmt einen IAstNode (HAST) sowie die Metadaten entgegen und erzeugt den TVisualNodeViewModel-Graphen.
|
||||||
|
* Er arbeitet modus-abhängig, basierend auf dem VisualizationMode des Eltern-ViewModels:
|
||||||
|
* **Im vmSyntactic-Modus:** Erzeugt für jeden Kind-IAstNode eine neue, einzigartige TVisualNodeViewModel-Instanz mit einer neuen TViewModelID. Das Ergebnis ist ein Baum.
|
||||||
|
* **Im vmSemantic-Modus:** Nutzt nach dem obligatorischen Binding-Schritt einen TDictionary\<TResolvedAddress, TVisualNodeViewModel\>, um für bereits visualisierte semantische Entitäten das existierende ViewModel wiederzuverwenden. Das Ergebnis ist ein DAG.
|
||||||
|
2. **Die Layout- & Rendering-Engine (TAuraLayoutEngine):**
|
||||||
|
* Diese Engine nimmt den TVisualNodeViewModel-Graphen (der ein Baum oder DAG sein kann) und erzeugt die visuellen TAuraNode-Controls.
|
||||||
|
* Für jedes ViewModel liest sie die TVisualInstanceMetadata (über die TViewModelID) und wendet Position, Kollaps-Zustand etc. an.
|
||||||
|
* Sie muss in der Lage sein, die Verbindungen für eine DAG-Struktur korrekt zu zeichnen (d.h. Linien von mehreren Eltern zu einem Kind).
|
||||||
|
3. **Implementierung des Modus-Wechsels:**
|
||||||
|
* Schaffung einer UI-Aktion (z.B. Kontextmenü auf einem TAuraNode), um den VisualizationMode in den Metadaten einer ViewModel-Instanz zu ändern.
|
||||||
|
* Diese Änderung löst eine Aktualisierung aus: Der ViewModel-Builder wird für den betroffenen Teilbaum neu ausgeführt, und die Layout-Engine zeichnet den Bereich neu.
|
||||||
|
|
||||||
|
**Ergebnis von Phase 2:** Ein interaktiver Viewer. Projekte können geladen, in beiden Modi (syntaktisch/semantisch) dargestellt und per Knoten umgeschaltet werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **Phase 3: Der Interaktive Editor**
|
||||||
|
|
||||||
|
**Ziel:** Dem Benutzer die sichere und strukturierte Bearbeitung des Graphen zu ermöglichen.
|
||||||
|
|
||||||
|
1. **Implementierung des Command Patterns:**
|
||||||
|
* Jede Änderung am AST (Knoten hinzufügen, löschen, Eigenschaft ändern) wird als IEditorCommand mit Execute und Unexecute implementiert, um Undo/Redo zu ermöglichen.
|
||||||
|
* Ein Command modifiziert **immer nur das HAST-Modell**, niemals direkt das ViewModel oder die View.
|
||||||
|
2. **Entwicklung des Socket-basierten Controllers:**
|
||||||
|
* Implementierung der Logik, die Benutzerinteraktionen (Klick auf ein "Socket") in die Erzeugung und Ausführung des passenden Commands übersetzt.
|
||||||
|
3. **Etablierung des Update-Zyklus:**
|
||||||
|
* Nachdem ein Command das HAST-Modell erfolgreich modifiziert hat, wird der Update-Prozess angestoßen:
|
||||||
|
1. Der "ViewModel-Builder" läuft über den geänderten Teil des HAST. Er versucht dabei, existierende ViewModels (anhand ihrer IAstNode-Referenz) wiederzuverwenden, um deren stabile IDs und damit die UI-Zustände zu erhalten. Nur für neue IAstNodes werden neue ViewModels erzeugt.
|
||||||
|
2. Die TAuraLayoutEngine rendert die neuen TAuraNode-Controls.
|
||||||
|
|
||||||
|
**Ergebnis von Phase 3:** Ein voll funktionsfähiger, interaktiver Editor mit robustem Zustandsmanagement, flexibler Visualisierung und Undo/Redo-Funktionalität.
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Projektplan: "Single-Map-Argument" (SMA) Architektur
|
||||||
|
|
||||||
|
*Datum: 23. Oktober 2025*
|
||||||
|
|
||||||
|
## 1. Motivation: Die "Visuelle Sprache"
|
||||||
|
|
||||||
|
Das primäre Entwicklungsziel ist **nicht** eine Text-basierte Programmiersprache, sondern ein **visueller Editor**, in dem Logik durch das Kombinieren von Blöcken erstellt wird. Der Text-Parser (`Myc.Ast.Script`) ist lediglich eine sekundäre Repräsentation.
|
||||||
|
|
||||||
|
Für einen visuellen Editor ist syntaktische Komplexität Gift. Jede Ausnahme, jedes Schlüsselwort und jede alternative Syntax (wie `[]` vs. `()`) erfordert einen neuen, speziellen visuellen Block, was die Benutzeroberfläche überlädt und die Konsistenz bricht.
|
||||||
|
|
||||||
|
Das ultimative Ziel ist eine **100% einheitliche Syntax**, bei der es nur noch *eine* Art gibt, eine Operation auszudrücken: den Funktionsaufruf. In einer S-Expression-Welt ist die konsistenteste Form dafür `(Funktionsname Argumente...)`.
|
||||||
|
|
||||||
|
## 2. Ziel: Das "Single-Map-Argument" (SMA) Modell
|
||||||
|
|
||||||
|
Um die visuelle Darstellung auf das absolute Minimum zu reduzieren (ein Block für "Funktion" und ein Slot für "Argumente"), führen wir das **"Single-Map-Argument" (SMA) Modell** ein.
|
||||||
|
|
||||||
|
Es gibt nur noch *eine* gültige Aufrufkonvention:
|
||||||
|
`(Funktionsname {Argument-Map})`
|
||||||
|
|
||||||
|
Jede Funktion, egal ob nativ (`+`) oder benutzerdefiniert (`my-lambda`), wird mit einem einzigen Argument aufgerufen: einem **Map-Literal** (das zu einem `TScalarRecord` ausgewertet wird). Dieses Literal definiert die Argumente über Key-Value-Paare.
|
||||||
|
|
||||||
|
**Beispiele:**
|
||||||
|
|
||||||
|
* **Arithmetik:** `(+ {:x 1 :y 2})`
|
||||||
|
* **RTL-Funktion:** `(Abs {:value -10})`
|
||||||
|
* **Lambda-Definition:** `(fn my-adder ({:x1 1 :x2 2}) ...)`
|
||||||
|
* **Lambda-Aufruf:** `(my-adder {:x1 5})`
|
||||||
|
|
||||||
|
Dieses Design erfüllt die Anforderung an die visuelle Konsistenz perfekt. Ein "Aufruf"-Block im Editor hat immer nur zwei definierte Slots: den Namen (z.B. `+`) und ein Map-Literal (z.B. `{:x ..., :y ...}`).
|
||||||
|
|
||||||
|
## 3. Analyse & Performance-Strategie
|
||||||
|
|
||||||
|
Unsere Diskussion hat ergeben, dass dieses Modell zwar visuell perfekt, aber in einer naiven Implementierung performancetechnisch inakzeptabel wäre.
|
||||||
|
|
||||||
|
### Das Problem: Die naive Implementierung (Verworfen)
|
||||||
|
|
||||||
|
Eine naive Implementierung würde jeden Aufruf zur Laufzeit gleich behandeln:
|
||||||
|
1. Der `TEvaluator` wertet das Map-Literal `{:x 1 :y 2}` zu einem vollwertigen `TScalarRecord` aus (Heap-Allokation, Füllen einer Map/Dictionary-Struktur).
|
||||||
|
2. Er ruft die native `+` Funktion mit diesem *einen* `TScalarRecord`-Argument auf.
|
||||||
|
3. Die `+` Funktion (bzw. ihr Wrapper) müsste die Map parsen, die Keys `:x` und `:y` nachschlagen und die Werte extrahieren.
|
||||||
|
|
||||||
|
Für Operationen wie `+`, die millionenfach pro Sekunde aufgerufen werden, ist dieser Overhead (Heap-Allokation + Hashmap-Lookups) katastrophal und ein absoluter Showstopper.
|
||||||
|
|
||||||
|
### Die Lösung: Kompilierung von Aufrufsignaturen
|
||||||
|
|
||||||
|
Wir vermeiden diesen Overhead, indem wir den **`TAstBinder` (den "Compiler")** die "Dekonstruktion" der Argument-Maps zur Compile-Zeit durchführen lassen.
|
||||||
|
|
||||||
|
Wir implementieren eine **Drei-Pfade-Kompilierung** für `VisitFunctionCall`:
|
||||||
|
|
||||||
|
#### Pfad 1: "Fast Path" (Nativ / RTL)
|
||||||
|
|
||||||
|
Dieser Pfad optimiert alle Aufrufe an bekannte, fest verdrahtete RTL-Funktionen.
|
||||||
|
|
||||||
|
* **Aktion (Binder):**
|
||||||
|
1. Der `TAstBinder` erhält ein **statisches Registry nativer Signaturen** (z.B. `TDictionary<string, TArray<string>>`). Dieses mappt Namen auf *geordnete* Key-Listen: `'+' -> [':x', ':y']`, `'Abs' -> [':value']`.
|
||||||
|
2. Bei `(sub {:a 5 :b 3})` schlägt er `sub` nach. Treffer! Signatur ist `[':a', ':b']`.
|
||||||
|
3. Der Binder validiert das `IMapLiteralNode` (alle Keys da? unbekannte Keys?).
|
||||||
|
4. **Argument-Umschreibung (Rewriting):** Der Binder *ignoriert* die Map-Struktur und erzeugt einen neuen, *positionalen* `TArray<IAstNode>`: `[ (Node 5), (Node 3) ]`.
|
||||||
|
5. Er erzeugt einen `TBoundFunctionCallNode`, der auf die native `sub`-Funktion zeigt, aber das *neue positionale Array* als Argumentenliste enthält.
|
||||||
|
* **Aktion (Evaluator):**
|
||||||
|
1. Der Evaluator sieht einen normalen, positionalen Aufruf.
|
||||||
|
2. Er wertet die Argumente `5` und `3` aus und ruft die `TRtlFunctions.Subtract` direkt mit einem `TArray<TDataValue>` auf.
|
||||||
|
* **Ergebnis:** Keinerlei Map-Allokation zur Laufzeit. Maximale Performance.
|
||||||
|
|
||||||
|
#### Pfad 2: "Fast Path" (Direkte Lambda)
|
||||||
|
|
||||||
|
Dieser Pfad optimiert direkte Aufrufe an Lambdas, deren Definition dem Binder bereits bekannt ist.
|
||||||
|
|
||||||
|
* **Aktion (Binder):**
|
||||||
|
1. Bei `(fn my-adder ({:x1 1 :x2 2}) ...)` parst der Binder die Signatur `({:x1 1, :x2 2})`.
|
||||||
|
2. Er speichert diese Signatur-Metadaten (Keys und Default-Wert-Nodes) im `IScopeDescriptor` als Metadatum für die Variable `my-adder`.
|
||||||
|
3. Bei einem späteren Aufruf `(my-adder {:x1 5})`:
|
||||||
|
4. Der Binder schlägt `my-adder` im Scope nach. Treffer! Er findet die Variable *und* die gespeicherte Signatur.
|
||||||
|
5. **Argument-Umschreibung:** Er führt die *gleiche* Optimierung wie bei nativen Aufrufen durch. Er parst das `IMapLiteralNode`, füllt fehlende Keys mit den Default-Nodes (z.B. `:x2` -> `(Node 2)`) und erzeugt einen positionalen `TArray<IAstNode>`: `[ (Node 5), (Node 2) ]`.
|
||||||
|
6. Er erzeugt einen `TBoundFunctionCallNode`, der das positionale Array enthält.
|
||||||
|
* **Aktion (Evaluator):**
|
||||||
|
1. Der Evaluator wertet die Closure `my-adder` aus.
|
||||||
|
2. Er wertet die Argumente `5` und `2` aus.
|
||||||
|
3. Er ruft die Closure mit einem *positionalen* `TArray<TDataValue>` auf.
|
||||||
|
* **Ergebnis:** Auch hier: Keinerlei Map-Allokation zur Laufzeit.
|
||||||
|
|
||||||
|
#### Pfad 3: "Slow Path" (Dynamisch / Polymorph)
|
||||||
|
|
||||||
|
Dies ist der Fallback für alle Aufrufe, die der Binder zur Compile-Zeit *unmöglich* auflösen kann.
|
||||||
|
|
||||||
|
* **Szenarien:**
|
||||||
|
* **Funktionen höherer Ordnung (HOFs):** `(Map my-series (fn ({:item}) ...))` -> Die `Map`-Funktion *muss* die Lambda dynamisch aufrufen.
|
||||||
|
* **Indirekte Aufrufe:** `(fn call-it (func) (func {:a 1}))` -> `func` ist zur Compile-Zeit unbekannt.
|
||||||
|
* **Späte Bindung / Forward-Deklarationen.**
|
||||||
|
* **Aktion (Binder):**
|
||||||
|
1. Der Binder kann die Signatur nicht finden.
|
||||||
|
2. Er kann *nicht* optimieren. Er behandelt das `IMapLiteralNode` `{:a 1}` als regulären Wert.
|
||||||
|
3. Er erzeugt einen `TBoundFunctionCallNode` mit einem Argumenten-Array, das *nur dieses eine* `IMapLiteralNode` enthält.
|
||||||
|
* **Aktion (Evaluator):**
|
||||||
|
1. Der Evaluator *muss* nun das `IMapLiteralNode` zu einem echten `TScalarRecord` auswerten (der "Overhead" entsteht hier).
|
||||||
|
2. Er ruft die Closure (z.B. `func`) mit *einem* Argument auf (dem `TScalarRecord`).
|
||||||
|
3. Die Closure selbst muss die **Laufzeit-Destrukturierung** durchführen: Sie parst die Map, extrahiert die Keys (`:a`) und wendet ihre Defaults an.
|
||||||
|
* **Ergebnis:** Das System bleibt voll funktionsfähig und konsistent, nutzt aber den langsameren Pfad nur, wenn es semantisch unvermeidbar ist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. TODOs (Detailliert)
|
||||||
|
|
||||||
|
1. **Parser (`Myc.Ast.Script`)**
|
||||||
|
* [ ] `TLexer`: `tkLeftBracket`, `tkRightBracket` entfernen. `tkLBrace` (`{`), `tkRBrace` (`}`), `tkColon` (`:`) hinzufügen.
|
||||||
|
* [ ] `IAstNode`: `IKeywordNode` (für `:key`) und `IMapLiteralNode` (enthält `TArray<TPair<IKeywordNode, IAstNode>>`) definieren.
|
||||||
|
* [ ] `TParser`: `ParseExpression` erweitern, um `{...}` zu `IMapLiteralNode` und `:...` zu `IKeywordNode` zu parsen.
|
||||||
|
* [ ] `TParser`: `ParseList` anpassen. Die `fn`- und `defmacro`-Logik muss die Parameterliste jetzt als `IMapLiteralNode` (für die Destrukturierung) statt einer Liste von Identifiern parsen.
|
||||||
|
|
||||||
|
2. **Binder (`Myc.Ast.Binding`)**
|
||||||
|
* [ ] `TAstBinder.Create`: Das **Native Signature Registry** (`TDictionary<string, TArray<string>>`) initialisieren.
|
||||||
|
* [ ] `TAstBinder.VisitLambdaExpression`: Die `IMapLiteralNode`-Signatur parsen. Die Metadaten (geordnete Keys und Default-`IAstNode`s) müssen in der `TBoundLambdaExpressionNode` oder im `IScopeDescriptor` gespeichert werden.
|
||||||
|
* [ ] `TAstBinder.VisitFunctionCall`: Die **Kern-Drei-Pfade-Logik** implementieren:
|
||||||
|
1. Lookup im Native Registry (Pfad 1).
|
||||||
|
2. Lookup im `IScopeDescriptor` nach Lambda-Metadaten (Pfad 2).
|
||||||
|
3. Fallback auf "Slow Path" (Pfad 3).
|
||||||
|
* [ ] `TAstBinder`: Eine private `function RewriteArguments(const Signature: ...; const CallMap: IMapLiteralNode): TArray<IAstNode>` implementieren, die die "Fast Path"-Dekonstruktion durchführt.
|
||||||
|
|
||||||
|
3. **Evaluator (`Myc.Ast.Evaluator`)**
|
||||||
|
* [ ] `TEvaluatorVisitor`: `VisitMapLiteral` implementieren. Diese Methode wertet ein `IMapLiteralNode` zu einem `TDataValue (vkRecord)` aus (der "Slow Path"-Overhead).
|
||||||
|
* [ ] `TEvaluatorVisitor.VisitLambdaExpression`: Die *Laufzeit*-Destrukturierungslogik implementieren. Diese wird aktiv, wenn die Closure (TFunc) mit einem `TDataValue (vkRecord)` (Pfad 3) anstelle eines `TArray<TDataValue>` (Pfad 2) aufgerufen wird. (Benötigt Anpassung der Closure-Signatur oder -Logik).
|
||||||
|
|
||||||
|
4. **RTL (Anpassung & Registrierung)**
|
||||||
|
* [ ] `Myc.Ast.RTL.Core`: Die Implementierungen (`TRtlFunctions.Add` etc.) bleiben **unverändert**. Sie erwarten weiterhin `TArray<TDataValue>`, da der Binder für sie übersetzt.
|
||||||
|
* [ ] `Myc.Ast.RTL`: Die RTTI-Registrierung muss erweitert werden, um dem Binder die Signaturen bereitzustellen. `TRtlFunctionAttribute` könnte erweitert werden: `[TRtlFunction('+', ':x,:y')]`. Diese Infos füllen das Native Registry im Binder.
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
### **Projektplan: Transformation des AST-Visualisierers zum Interaktiven Editor**
|
||||||
|
|
||||||
|
* **Datum:** 08.09.2025 11:20
|
||||||
|
* **Motivation**
|
||||||
|
* Der aktuelle AST-Visualisierer ist ein leistungsfähiges Anzeigetool. Um ihn zu einem interaktiven Werkzeug für die Skript-Entwicklung, \-Analyse und \-Modifikation weiterzuentwickeln, muss eine robuste Architektur für Zustandsverwaltung, Bearbeitung und Persistenz geschaffen werden.
|
||||||
|
* **Ziel**
|
||||||
|
* Die Entwicklung einer flexiblen Editor-Architektur, die eine klare Trennung zwischen dem logischen AST-Modell und seiner visuellen Repräsentation gewährleistet. Das System muss komplexe Anforderungen erfüllen: Es soll optionale Darstellungsmodi (Baum- vs. Graphen-Ansicht), zwei Arten von Metadaten (logisch vs. instanzspezifisch) und eine hohe Datenintegrität bei internen (z.B. Umsortieren) und externen (z.B. Einfügen von LLM-Code) Bearbeitungen sicherstellen.
|
||||||
|
* **Ergebnis**
|
||||||
|
* Ein interaktiver, visueller AST-Editor mit einem hochentwickelten Zustandsmanagement. Die Architektur bietet eine nahtlose Benutzererfahrung, bei der Layout-Anpassungen und Zustände (z.B. eingeklappte Knoten) auch bei komplexen Operationen wie Refactoring oder dem Mergen von extern modifiziertem Code intelligent erhalten bleiben. Das System ist durch ein flexibles JSON-Format persistent und interoperabel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **TODO: Nächste Schritte**
|
||||||
|
|
||||||
|
1. **Fundament: ViewModel-Schicht und Stabile IDs einführen**
|
||||||
|
* **Beschreibung:** Das Kernstück der neuen Architektur schaffen. Eine TVisualNodeViewModel-Klasse wird als Vermittler zwischen dem IAstNode-Modell und der TAuraNode-Ansicht eingeführt.
|
||||||
|
* **Umsetzung:**
|
||||||
|
* Jede TVisualNodeViewModel-Instanz erhält beim Erstellen eine einzigartige, permanente und strukturunabhängige TViewModelID (z.B. Int64).
|
||||||
|
* Der TAstToAuraNodeVisitor wird so umgebaut, dass er primär einen Baum aus ViewModel-Objekten erzeugt, welcher die Hierarchie des AST widerspiegelt.
|
||||||
|
2. **Zustandsverwaltung: Metadaten-System implementieren**
|
||||||
|
* **Beschreibung:** Die getrennte Speicherung von logischen und instanzspezifischen Metadaten implementieren.
|
||||||
|
* **Umsetzung:**
|
||||||
|
* **Logische Metadaten:** Eine zentrale TDictionary\<IAstNode, TLogicalMetadata\> für Eigenschaften erstellen, die für alle Instanzen eines Knotens gelten (z.B. Farbkodierung).
|
||||||
|
* **Instanz-Metadaten:** Eine TDictionary\<TViewModelID, TVisualInstanceMetadata\> für Eigenschaften erstellen, die nur für eine bestimmte visuelle Instanz gelten (z.B. IsCollapsed, Position auf der Leinwand).
|
||||||
|
* Die Rendering-Logik anpassen, um beide Metadatentypen beim Zeichnen eines TAuraNode zu berücksichtigen.
|
||||||
|
3. **Persistenz: JSON-Serialisierung erweitern**
|
||||||
|
* **Beschreibung:** Das Speichern und Laden des gesamten Editor-Zustands ermöglichen.
|
||||||
|
* **Umsetzung:**
|
||||||
|
* Einen Serialisierungsprozess entwerfen, der drei getrennte Bereiche in der JSON-Datei ablegt:
|
||||||
|
1. Den reinen IAstNode-Baum.
|
||||||
|
2. Die logischen Metadaten, verknüpft über eine temporäre ID des IAstNode.
|
||||||
|
3. Die instanzspezifischen Metadaten, verknüpft über die stabile TViewModelID.
|
||||||
|
4. **Interaktion: Grundlegende Editierbarkeit herstellen**
|
||||||
|
* **Beschreibung:** Dem Benutzer erlauben, den AST-Graphen zu verändern (Knoten hinzufügen, löschen, umsortieren).
|
||||||
|
* **Umsetzung:**
|
||||||
|
* Das **Command Pattern** implementieren, bei dem jede Änderung eine Execute- und Unexecute-Methode hat (für Undo/Redo).
|
||||||
|
* Jeder Command ist dafür verantwortlich, sowohl das IAstNode-Modell als auch den TVisualNodeViewModel-Baum konsistent zu halten. Da die Metadaten an die stabilen IDs gekoppelt sind, bleiben sie bei diesen Operationen automatisch erhalten.
|
||||||
|
5. **Fortgeschrittene Interaktion: "Smart Paste" / Abgleich-Algorithmus**
|
||||||
|
* **Beschreibung:** Das intelligente Einfügen von extern veränderten AST-Teilbäumen ermöglichen.
|
||||||
|
* **Umsetzung:**
|
||||||
|
* Einen "Diff & Merge"-Algorithmus entwickeln. Beim Einfügen vergleicht dieser den neuen AST-Teilbaum mit dem alten.
|
||||||
|
* Bei äquivalenten Knoten werden die bestehenden TVisualNodeViewModel-Instanzen (samt ihrer IDs und Metadaten) wiederverwendet, um den visuellen Zustand zu erhalten. Nur bei echten Änderungen oder neuen Knoten werden neue ViewModels mit neuen IDs erzeugt.
|
||||||
|
6. **Optionale Ansicht: Eindeutige Knoten-Visualisierung ("Graph"-Modus)**
|
||||||
|
* **Beschreibung:** Den optionalen Modus implementieren, in dem jeder IAstNode nur einmal dargestellt wird.
|
||||||
|
* **Umsetzung:**
|
||||||
|
* Den TAstToAuraNodeVisitor um einen Modus erweitern. In diesem Modus führt er eine TDictionary\<IAstNode, TVisualNodeViewModel\>, um bereits erstellte ViewModels für einen IAstNode zu finden und wiederzuverwenden, anstatt neue zu erstellen. Stattdessen wird nur eine neue Verbindungslinie gezeichnet.
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
# Einführung von N-dimensionalen Tupel-Strukturen und Typ-Unification
|
||||||
|
|
||||||
|
**Datum:** 04.01.2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Motivation
|
||||||
|
|
||||||
|
In der bisherigen Architektur wurden Argumentlisten, Parameterlisten und Datenstrukturen (Records/Series) als getrennte Konzepte behandelt. Dies führt zu unnötigem Overhead durch "Boxing" (Einpacken von Werten in Objekte) und erschwert die statische Optimierung mathematischer Operationen.
|
||||||
|
|
||||||
|
**Das Ziel dieser Erweiterung:**
|
||||||
|
|
||||||
|
* **Vereinheitlichung:** Alles, was eine feste Sequenz von Daten ist, wird intern ein **Tupel**.
|
||||||
|
* **Performance:** Durch präzise statische Analyse der "Shape" (Form) und des Inhalts sollen Daten unboxed (als reine Value-Types) im Speicher liegen können.
|
||||||
|
* **Monomorphisierung:** Der Compiler soll hochoptimierten Maschinencode (SIMD) erzeugen können, sobald er erkennt, dass Datenstrukturen "rechteckig" und homogen sind.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architekturbeschreibung
|
||||||
|
|
||||||
|
### 2.1 Die Typ-Hierarchie (`IStaticType`)
|
||||||
|
|
||||||
|
Wir führen eine rekursive Inferenz-Logik im `TypeChecker` ein, die den am besten passenden statischen Typ ermittelt:
|
||||||
|
|
||||||
|
1. **`stTuple`**: Der Basisfall. Eine heterogene Sequenz fester Länge (z. B. `[1 "Text"]`). Jeder Slot hat seinen eigenen statischen Typ.
|
||||||
|
2. **`stVector`**: Ein Spezialfall des Tupels. Alle Elemente besitzen den **identischen** statischen Typ (Homogenität). Dies erlaubt den typsicheren Zugriff über variable Indizes.
|
||||||
|
3. **`stMatrix`**: Ein rekursiver Spezialfall des Vektors. Ein Vektor, dessen Elemente wiederum Vektoren oder Matrizen sind, sofern sie eine **identische Shape** (Dimensionen) aufweisen ("Rechteckigkeit").
|
||||||
|
|
||||||
|
### 2.2 Die "Scalar-Pure" Optimierung
|
||||||
|
|
||||||
|
Dies ist eine rein interne Optimierung des Spezialisierers:
|
||||||
|
|
||||||
|
* Wenn ein Typ (`stTuple`, `stVector`, `stMatrix`) ausschließlich statisch bekannte Typen (idealerweise Skalare wie `Ordinal` oder `Float`) enthält, wird er als **Value-Type** behandelt.
|
||||||
|
* **Monomorphisierter Pfad:** Der Spezialisierer erzeugt für diese Strukturen einen dedizierten Code-Pfad, der statt mit langsamen Objekt-Arrays mit flachen, gepackten Speicherblöcken arbeitet.
|
||||||
|
* **Kein implizites Casting:** Um die Inferenz stabil zu halten, findet keine automatische Umwandlung statt (z. B. wird ein `Ordinal` nicht automatisch zu `Float`, um einen Vektor zu erzwingen).
|
||||||
|
|
||||||
|
### 2.3 Unification (Vereinheitlichung der Konzepte)
|
||||||
|
|
||||||
|
Das Tupel-Konzept ersetzt mehrere bisherige Mechanismen:
|
||||||
|
|
||||||
|
* **Argument- & Parameterlisten:** Funktionsaufrufe werden semantisch als Übergabe eines Tupels behandelt. Dies ermöglicht hocheffiziente Registerübergabe.
|
||||||
|
* **Records:** Ein Record ist semantisch nur ein "Tagged Tuple". Die Daten liegen als Tupel vor, ein Keyword-Mapping sorgt lediglich für den namensbasierten Zugriff.
|
||||||
|
* **Multiple Returns:** Funktionen können nativ Tupel zurückgeben, was ohne zusätzliches Heap-Investment verarbeitet werden kann.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Skript-Beispiele und Inferenz
|
||||||
|
|
||||||
|
Die Syntax für alle Tupel-basierten Strukturen ist einheitlich `[...]`.
|
||||||
|
|
||||||
|
### A. Heterogene Strukturen (Tupel)
|
||||||
|
|
||||||
|
```script
|
||||||
|
(def tpl [1 3.14 "text"])
|
||||||
|
; Typ-Inferenz: stTuple<stOrdinal, stFloat, stText>
|
||||||
|
; Spezialisierung: Gepackter Record (Offset 0: Int64, Offset 8: Double, Offset 16: Ptr)
|
||||||
|
|
||||||
|
(get tpl 1) ; -> 3.14 (Der Compiler weiß statisch: Index 1 ist stFloat)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### B. Homogene Strukturen (Vektoren)
|
||||||
|
|
||||||
|
```script
|
||||||
|
(def v [10 20 30])
|
||||||
|
; Typ-Inferenz: stVector<stOrdinal, 3>
|
||||||
|
; Spezialisierung: Flaches Array von 3x Int64 (unboxed, SIMD-optimierbar)
|
||||||
|
|
||||||
|
(def i 2)
|
||||||
|
(get v i) ; -> 30 (Typsicher, da alle Elemente stOrdinal sind)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### C. Multidimensionale Strukturen (Matrizen)
|
||||||
|
|
||||||
|
Die Matrix-Invariante erfordert exakt gleiche Formen der Unterelemente.
|
||||||
|
|
||||||
|
```script
|
||||||
|
; 2D Matrix
|
||||||
|
(def mat2d [[1 2] [3 4]])
|
||||||
|
; Inferenz: stMatrix<stOrdinal, [2, 2]> (Rechteckig -> Optimierung aktiv)
|
||||||
|
|
||||||
|
; 3D Matrix
|
||||||
|
(def mat3d [[[1 2] [3 4]] [[5 6] [7 8]]])
|
||||||
|
; Inferenz: stMatrix<stOrdinal, [2, 2, 2]> (Linearisierter Speicherblock)
|
||||||
|
|
||||||
|
; Degradiertes Tupel (Shape-Mismatch)
|
||||||
|
(def mixed [[1 2] [3 4 5]])
|
||||||
|
; Inferenz: stVector<stTuple>
|
||||||
|
; Keine Matrix-Optimierung möglich, da Unter-Tupel Längen 2 und 3 haben.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### D. Records als Tupel-Sicht
|
||||||
|
|
||||||
|
```script
|
||||||
|
(def rec {:x 1, :y 0.3})
|
||||||
|
; Physisch: stTuple<stOrdinal, stFloat>
|
||||||
|
; Logisch: Record-Mapping {:x -> 0, :y -> 1}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Nächste Schritte
|
||||||
|
|
||||||
|
1. **`Myc.Ast.Types.pas`**:
|
||||||
|
* Erweitern von `TStaticTypeKind` und `IStaticType`.
|
||||||
|
* Implementierung von `TTupleType`, `TVectorType` und `TMatrixType`.
|
||||||
|
* Implementierung der rekursiven `IsScalarPure`-Prüfung.
|
||||||
|
(erledigt)
|
||||||
|
|
||||||
|
2. **`Myc.Ast.Compiler.TypeChecker.pas`**:
|
||||||
|
* Implementierung der Promotion-Kaskade (`stTuple` -> `stVector` -> `stMatrix`).
|
||||||
|
* Validierung der "Rechteckigkeit" bei geschachtelten Literalen.
|
||||||
|
|
||||||
|
|
||||||
|
3. **`Myc.Ast.Nodes.pas`**:
|
||||||
|
* Konsolidierung von `IArgumentList`, `IParameterList` etc. zu einer allgemeinen Tupel-Repräsentation.
|
||||||
|
|
||||||
|
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
# High-Performance Backend (Bytecode VM)
|
||||||
|
|
||||||
|
**Datum:** 21.11.2025
|
||||||
|
**Status:** Entwurf / Planungsphase
|
||||||
|
**Zielarchitektur:** Stack-based Virtual Machine (ähnlich Lua 5.x / Python)
|
||||||
|
|
||||||
|
## 1\. Motivation & Architektur-Ziele
|
||||||
|
|
||||||
|
* **Status Quo:** Der aktuelle AST-Evaluator ist mächtig, flexibel und ideal für Debugging, leidet aber unter "Pointer Chasing" (Cache Misses) und Rekursions-Overhead (CPU Stack Frames).
|
||||||
|
* **Ziel:** Maximale Ausführungsgeschwindigkeit für typisierte, numerische Operationen bei gleichzeitiger Beibehaltung der vollen Sprachflexibilität (Closures, dynamische Typen).
|
||||||
|
* **Strategie:** Implementierung einer linearen Stack-Maschine.
|
||||||
|
* **Compiler:** Transformiert den *spezialisierten* AST in ein flaches Array von Instruktionen.
|
||||||
|
* **VM:** Eine "Dispatch Loop", die Instruktionen abarbeitet und Delphi-Funktionen für komplexe Datentypen (Records, Series) als "Fernsteuerung" nutzt.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## 2\. Phasenplan
|
||||||
|
|
||||||
|
### Phase 1: Das Fundament (Datenstrukturen)
|
||||||
|
|
||||||
|
**Ziel:** Definition der binären Repräsentation des Codes und des Laufzeit-Stacks.
|
||||||
|
|
||||||
|
* **Task 1.1: Stack-Architektur definieren**
|
||||||
|
* Implementierung von `TStackSlot` als **Tagged Union** (Variant Record).
|
||||||
|
* Größe: Max. 16 Bytes (Alignment-freundlich).
|
||||||
|
* Typen: `skEmpty`, `skInt64`, `skDouble`, `skBoolean`, `skPointer` (für RefCounted Objekte).
|
||||||
|
* **Task 1.2: OpCodes definieren (`TOpCode`)**
|
||||||
|
* Kategorisierung in: Stack Ops, Arithmetik (Getrennt nach `_Int`, `_Flt`, `_Dyn`), Flow Control, Calls.
|
||||||
|
* **Task 1.3: Instruktions-Format (`TInstruction`)**
|
||||||
|
* Record mit `OpCode` (Byte/Enum) und Argumenten (`Arg1`, `Arg2`, `Arg3`: Integer).
|
||||||
|
* **Task 1.4: Container (`TBytecodeChunk`)**
|
||||||
|
* Klasse, die `TArray<TInstruction>`, `TArray<TDataValue>` (Constant Pool) und Metadaten (MaxStackSize) hält.
|
||||||
|
|
||||||
|
### Phase 2: Der Compiler (Core Arithmetic)
|
||||||
|
|
||||||
|
**Ziel:** Kompilierung einfacher mathematischer Ausdrücke ohne Kontrollfluss.
|
||||||
|
|
||||||
|
* **Task 2.1: Compiler-Gerüst (`TBytecodeCompiler`)**
|
||||||
|
* Implementierung als `TAstVisitor` (oder `IAstVisitor`).
|
||||||
|
* Verwaltung des `TBytecodeChunk`.
|
||||||
|
* Verwaltung einer virtuellen "Stack Height" zur Berechnung von `MaxStackSize`.
|
||||||
|
* **Task 2.2: Konstanten laden**
|
||||||
|
* Visitor für `VisitConstant`.
|
||||||
|
* Logik: Konstante im Pool suchen/einfügen $\to$ `opLdConst <Index>` emittieren.
|
||||||
|
* **Task 2.3: Arithmetik & Typ-Spezialisierung**
|
||||||
|
* Visitor für `FunctionCall` (Spezialfall: Binäre Operatoren).
|
||||||
|
* Nutzung der `IStaticType`-Informationen aus dem AST.
|
||||||
|
* Entscheidunglogik:
|
||||||
|
* Sind Operanden `Int64`? $\to$ `opAddInt`.
|
||||||
|
* Sind Operanden `Double`? $\to$ `opAddFlt`.
|
||||||
|
* Sonst $\to$ `opAdd` (Fallback).
|
||||||
|
|
||||||
|
### Phase 3: Die Virtual Machine (The Engine)
|
||||||
|
|
||||||
|
**Ziel:** Ausführung des in Phase 2 generierten Codes.
|
||||||
|
|
||||||
|
* **Task 3.1: Die VM-Klasse (`TVM`)**
|
||||||
|
* Aufbau des `OperandStack` (Array of `TStackSlot`).
|
||||||
|
* Register: `IP` (Instruction Pointer), `SP` (Stack Pointer), `BP` (Base/Frame Pointer).
|
||||||
|
* **Task 3.2: Dispatch Loop**
|
||||||
|
* Implementierung der `Run(Chunk)` Methode.
|
||||||
|
* Großes `case Instruction.OpCode of ...`.
|
||||||
|
* **Task 3.3: Implementierung der Core-OpCodes**
|
||||||
|
* `opLdConst`: Kopieren von Constant-Pool auf Stack.
|
||||||
|
* `opAddInt`: Roher Zugriff auf `Stack[SP].AsInt`. (Performance-kritisch\!).
|
||||||
|
* `opAdd`: Generischer Pfad (Unboxing, Operation, Boxing).
|
||||||
|
|
||||||
|
### Phase 4: Variablen & Kontrollfluss
|
||||||
|
|
||||||
|
**Ziel:** Unterstützung von `if`, lokalen Variablen und einfachen Schleifen (`recur`).
|
||||||
|
|
||||||
|
* **Task 4.1: Lokale Variablen**
|
||||||
|
* Mapping im Compiler: AST `SlotIndex` $\to$ Stack-Relativ-Index.
|
||||||
|
* OpCodes: `opLdLocal <Idx>`, `opStLocal <Idx>`.
|
||||||
|
* **Task 4.2: Sprünge (Jumps)**
|
||||||
|
* Compiler: Handling von `VisitIfExpression`.
|
||||||
|
* Logik: Emittieren von Platzhalter-Jumps, Patching der Sprungziele nach Generierung des Branches.
|
||||||
|
* OpCodes: `opJmp`, `opJmpFalse`.
|
||||||
|
* **Task 4.3: TCO / Recur**
|
||||||
|
* `VisitRecurNode`: Generierung von `Move` Instruktionen (Argumente an Position der Parameter kopieren) + `opJmp` zum Start der Funktion.
|
||||||
|
|
||||||
|
### Phase 5: Funktionen & Closures (Die Kür)
|
||||||
|
|
||||||
|
**Ziel:** First-Class Functions und Upvalue-Handling.
|
||||||
|
|
||||||
|
* **Task 5.1: Funktions-Prototypen**
|
||||||
|
* Erweiterung `TBytecodeChunk` um Sub-Chunks (Prototypen für innere Funktionen).
|
||||||
|
* **Task 5.2: Upvalue-Analyse Integration**
|
||||||
|
* Nutzung der Ergebnisse des Binders/UpvalueAnalyzers.
|
||||||
|
* Compiler muss wissen, welche Variable Stack-Local ist und welche ein Upvalue ist.
|
||||||
|
* **Task 5.3: Closure-Instanziierung**
|
||||||
|
* OpCode: `opClosure <ProtoIdx>`.
|
||||||
|
* VM: Erzeugt `TClosure` Objekt, sammelt "Capture"-Variablen vom Stack ein (Hoisting) und speichert sie im Closure-Objekt.
|
||||||
|
* **Task 5.4: Calls (`opCall`)**
|
||||||
|
* VM: Stack-Frame Management (Sichern von `BP`, `IP` auf dem Call-Stack).
|
||||||
|
* Umschalten des aktiven Chunks.
|
||||||
|
|
||||||
|
### Phase 6: Interop & komplexe Typen
|
||||||
|
|
||||||
|
**Ziel:** Brückenschlag zur existierenden Delphi-Logik.
|
||||||
|
|
||||||
|
* **Task 6.1: Native Calls (`opCallNative`)**
|
||||||
|
* Aufruf von RTL-Funktionen via Funktionszeiger.
|
||||||
|
* Konvertierung `TStackSlot` $\leftrightarrow$ Argumente.
|
||||||
|
* **Task 6.2: Records & Series**
|
||||||
|
* OpCodes: `opNewRecord`, `opSeriesAdd`.
|
||||||
|
* VM: Ruft direkt `TScalarRecord.Create` etc. auf. Hier wird keine Logik dupliziert, nur delegiert.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## 3\. Technische Eckpfeiler
|
||||||
|
|
||||||
|
### Das Datenmodell (VM Stack)
|
||||||
|
|
||||||
|
```pascal
|
||||||
|
type
|
||||||
|
TStackSlot = record
|
||||||
|
case Kind: TDataValueKind of
|
||||||
|
vkOrdinal: (AsInt: Int64);
|
||||||
|
vkFloat: (AsFloat: Double);
|
||||||
|
vkObj: (AsPtr: Pointer); // IInterface / TObject / String
|
||||||
|
end;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Die Optimierungs-Strategie
|
||||||
|
|
||||||
|
1. **Binder/TypeChecker:** Leisten die Vorarbeit (Auflösung von Namen zu Indizes, Typ-Inferenz).
|
||||||
|
2. **Compiler:** Entscheidet statisch über OpCodes (`ADD_INT` vs `ADD`).
|
||||||
|
3. **VM:** Führt "blind" und schnell aus. Typprüfungen nur im `_DYN` Pfad oder als Assert.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## 4\. Nächste Schritte (Todo)
|
||||||
|
|
||||||
|
1. [ ] Anlegen der Unit `Myc.Bytecode.Types` (Definition OpCodes, Instruction, StackSlot).
|
||||||
|
2. [ ] Implementierung `TBytecodeCompiler` (Skeleton: Nur Constants & Return).
|
||||||
|
3. [ ] Implementierung `TVM` (Skeleton: Stack setup, Dispatch loop für Const/Ret).
|
||||||
|
4. [ ] Erster Integrationstest: `42` kompiliert $\to$ VM führt aus $\to$ Resultat 42.
|
||||||
|
5. [ ] Erweiterung um `BinaryOp` (Add/Sub) inkl. Typ-Spezialisierung.
|
||||||
|
|
||||||
|
-----
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Projektplan: Myc Compiler & Visual Editor Integration
|
||||||
|
|
||||||
|
**Datum:** 28.11.2025 12:42
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
Der Compiler-Kern wurde erfolgreich auf eine robuste, immutable Architektur umgestellt ("Green Tree / Red Tree"). Durch die Einführung von `IAstIdentity` und einem Logging-basierten Fehlersystem ("Fail-Safe") gehen Metadaten während der Transformationen (Binder, TypeChecker, Optimierung) nicht mehr verloren.
|
||||||
|
Der nächste logische Schritt ist die **Integration dieser Intelligenz in den visuellen Editor**. Der Editor soll nicht nur dumme Blöcke darstellen, sondern Live-Feedback (Fehler, Typen) geben, indem er die stabilen Identitäten für den "Round-Trip" nutzt.
|
||||||
|
|
||||||
|
### Ziel
|
||||||
|
Herstellung einer **bidirektionalen Verbindung** zwischen dem visuellen `TAuraNode`-Graphen und dem logischen `IAstNode`-Baum.
|
||||||
|
1. **Rekonstruktion:** Der visuelle Baum erzeugt einen logischen AST (inkl. Mapping).
|
||||||
|
2. **Kompilierung:** Der Compiler verarbeitet den AST und liefert Fehler/Typen referenziert auf `IAstIdentity`.
|
||||||
|
3. **Visualisierung:** Der Editor projiziert diese Informationen zurück auf die entsprechenden UI-Knoten.
|
||||||
|
|
||||||
|
### Ergebnis (Status Quo)
|
||||||
|
* **Compiler Core:** Vollständig refaktoriert. Binder, TypeChecker, TCO und Specializer unterstützen `ICompilerLog` und nutzen "Poison Pills" (`Unknown`-Typen) statt Exceptions.
|
||||||
|
* **AST Architektur:** `IAstIdentity` trennt erfolgreich Syntax-Daten von Semantik. Factories (`TAst`) erzwingen den Erhalt der Provenance (Herkunft) bei Transformationen.
|
||||||
|
* **Editor Basis:** `Myc.Fmx.AstEditor.Node` wurde angepasst, um die neuen Factories zu nutzen. `ReconstructAst` funktioniert prinzipiell.
|
||||||
|
|
||||||
|
### Nächster Schritt: Implementierung der Editor-Intelligenz
|
||||||
|
|
||||||
|
Die Umsetzung erfolgt in drei logischen Phasen:
|
||||||
|
|
||||||
|
#### Phase 1: Visuelle Feedback-Mechanismen (View)
|
||||||
|
Erweiterung von `TAuraNode` um die Fähigkeit, Status-Informationen darzustellen, ohne die Kernlogik zu überladen.
|
||||||
|
* Implementierung von **Adorners/Decorators** für `TAuraNode`.
|
||||||
|
* Visualisierung von **Fehlern** (z.B. roter Rahmen, Warnsymbol, Tooltip mit Fehlermeldung).
|
||||||
|
* Visualisierung von **Typ-Informationen** (z.B. kleines Badge mit "Int64", "Series" etc.).
|
||||||
|
* Methoden zum Setzen und Löschen dieser Zustände (`SetErrorState`, `SetTypeInfo`, `ClearStatus`).
|
||||||
|
|
||||||
|
#### Phase 2: Mapping-Infrastruktur (Glue Code)
|
||||||
|
Implementierung der externen Verknüpfung zwischen Logik und UI, um den AST "rein" zu halten.
|
||||||
|
* Definition eines `TAstMapping`-Kontextes (enthält `Dictionary<IAstIdentity, TAuraNode>`).
|
||||||
|
* Anpassung des Rekonstruktions-Prozesses: Während `ReconstructAst` läuft, muss die Verbindung zwischen der (wiederverwendeten oder neuen) `Identity` und dem `TAuraNode` im Mapping registriert werden.
|
||||||
|
|
||||||
|
#### Phase 3: Der Editor-Controller (Brain)
|
||||||
|
Erstellung einer neuen Unit `Myc.Fmx.AstEditor.Controller`, die den Workflow steuert.
|
||||||
|
* Orchestrierung: `Workspace` -> `AST` -> `Compiler` -> `UI-Update`.
|
||||||
|
* Verarbeitung des `ICompilerLog`: Iteration über Fehler, Lookup im Mapping, Update der UI-Nodes.
|
||||||
|
* Verarbeitung des `TypedAst`: Extraktion von Typen für Mouse-Over oder permanente Anzeige.
|
||||||
|
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
|
||||||
|
# Architekturkonzept: Hybrid Projectional Financial Editor
|
||||||
|
|
||||||
|
## 1. Motivation und Zielsetzung
|
||||||
|
Entwicklung einer Entwicklungsumgebung für Finanzanalysen (Backtesting, Indikatoren), die die Vorteile zweier Welten vereint:
|
||||||
|
1. **Visuelle Programmierung (Blöcke):** Für die grobe Architektur, den Datenfluss und die Übersichtlichkeit. Vermeidet Syntaxfehler bei komplexen Verschachtelungen.
|
||||||
|
2. **Textuelle Programmierung (S-Expressions):** Für mathematische Formeln und Detail-Logik. Ermöglicht schnelle Eingabe und präzises Editieren für Experten.
|
||||||
|
|
||||||
|
Das System verhält sich **reaktiv** (wie "Strudel" für Musik): Änderungen am Code oder an Parametern führen sofort zur Neuberechnung und Aktualisierung der Charts ("Live Coding").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Kern-Architektur (MVC)
|
||||||
|
|
||||||
|
Die Anwendung folgt strikt dem Model-View-Controller Muster, um Rendering von Logik zu trennen.
|
||||||
|
|
||||||
|
### Model (Der AST & Environment)
|
||||||
|
* **Immutable AST:** Der Abstract Syntax Tree besteht aus unveränderlichen Interfaces (`IAstNode`). Jede Änderung erzeugt einen neuen Teilbaum.
|
||||||
|
* **Environment (`TAstEnvironment`):** Hält den Laufzeit-Zustand (Variablen, definierte Makros) und führt den Code aus.
|
||||||
|
* **Domain:** Spezialisierte Datentypen für Finanzen (`ISeries` für Zeitreihen, `TDecimal` für Währung).
|
||||||
|
|
||||||
|
### View (`TWorkspace` & `TEditorFrame`)
|
||||||
|
* **Aufgabe:** Rein visuelle Darstellung. Kennt keine Logik, nur `TControl`-Hierarchien.
|
||||||
|
* **Rendering:** Zeichnet Blöcke, Verbindungen und visuelle Container.
|
||||||
|
* **Input:** Leitet Maus- und Tastatur-Events (Drag & Drop) an den Controller weiter.
|
||||||
|
|
||||||
|
### Controller (`TAstEditorController`)
|
||||||
|
* **Aufgabe:** Das "Gehirn". Synchronisiert AST und View.
|
||||||
|
* **Zustandsverwaltung:** Hält den aktuellen validen AST (`FCurrentAst`).
|
||||||
|
* **Undo/Redo:** Speichert Snapshots des ASTs auf einem Stack (Memento Pattern).
|
||||||
|
* **Reaktivität:** Feuert `OnChange` bei jeder Modifikation, um die Ausführungspipeline anzustoßen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Der Hybride Editor-Ansatz
|
||||||
|
|
||||||
|
Das System entscheidet dynamisch, wie ein AST-Knoten dargestellt wird ("Smart Nodes").
|
||||||
|
|
||||||
|
### A. Die Makro-Ebene (Visuell)
|
||||||
|
Strukturelle Elemente werden als grafische Blöcke dargestellt:
|
||||||
|
* **Control Flow:** `If`, `Loop`, `Block` (do...end).
|
||||||
|
* **Definitionen:** `VarDecl`, `MacroDef`.
|
||||||
|
* **High-Level Funktionen:** `LoadCSV`, `Chart`, `Strategy`.
|
||||||
|
|
||||||
|
**Vorteil:** Der Benutzer erkennt die Topologie der Strategie auf einen Blick ("Code Folding" durch Visualisierung).
|
||||||
|
|
||||||
|
### B. Die Mikro-Ebene (Textuell / S-Expressions)
|
||||||
|
Mathematische Ausdrücke und Parameter werden als Text (Lisp-artige Syntax) dargestellt und editiert.
|
||||||
|
* Beispiel: Statt eines Baums aus 5 Boxen sieht der User ein Label: `(> Close (SMA Close 20))`.
|
||||||
|
|
||||||
|
### C. Drill-Down Editing (In-Place)
|
||||||
|
Der innovative Kern des Editors. Wenn ein User einen Knoten bearbeitet (Doppelklick), öffnet sich ein Texteditor über dem Knoten.
|
||||||
|
|
||||||
|
1. **Shallow Printing:** Der Editor zeigt nicht den gesamten tiefen Baum als Text, sondern nutzt **Platzhalter** (`#ID`) für komplexe Unter-Knoten.
|
||||||
|
* AST: `If(Condition, ThenBlock, ElseBlock)`
|
||||||
|
* Text: `(if #1 #2 #3)`
|
||||||
|
2. **Kontext:** Der Controller speichert in einem `TEditContext`, welcher AST-Knoten hinter `#1` steckt.
|
||||||
|
3. **Navigation:** Der User kann `#1` mit dem Cursor ansteuern und per Shortcut (z.B. `Ctrl+Space`) "in-place" expandieren.
|
||||||
|
* Text wird zu: `(if (> Close #4) #2 #3)`
|
||||||
|
4. **Commit:** Beim Bestätigen (Enter) wird der Text geparst (`TAstParser`). Die Platzhalter werden durch die originalen, unveränderten AST-Knoten aus dem Kontext ersetzt.
|
||||||
|
|
||||||
|
**Vorteil:** Maximale Effizienz. Man editiert nur das, was man ändern will. Der Rest des Baumes bleibt vor versehentlichen Syntaxfehlern geschützt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Reaktivität & Live Coding
|
||||||
|
|
||||||
|
Das System kompiliert und führt den Code permanent aus, nicht erst auf Knopfdruck.
|
||||||
|
|
||||||
|
### Reactive Pipeline
|
||||||
|
1. **Änderung:** User zieht einen Block oder ändert eine Zahl.
|
||||||
|
2. **Controller:** Baut neuen AST -> `OnChange`.
|
||||||
|
3. **Runner:** Führt `FEnv.Run(NewAst)` aus.
|
||||||
|
4. **UI Update:** Charts und Indikatoren werden neu gezeichnet.
|
||||||
|
|
||||||
|
### Number Scrubbing
|
||||||
|
Benutzer können Zahlenwerte (Konstanten) mit der Maus "ziehen" (drücken + ziehen).
|
||||||
|
* Der Controller aktualisiert den AST "live" (hochfrequent).
|
||||||
|
* Der Chart verändert sich flüssig während der Mausbewegung.
|
||||||
|
* Ermöglicht intuitives Finden von Parametern (z.B. "Welche SMA-Länge passt visuell am besten?").
|
||||||
|
|
||||||
|
### Code as UI (Widgets)
|
||||||
|
Der Code kann UI-Elemente zurückgeben, die im REPL-Output gerendert werden.
|
||||||
|
* `var len := Slider("Period", 10, 200)` erzeugt einen Schieberegler.
|
||||||
|
* Bewegt man den Regler, wird das Skript mit dem neuen Wert für `len` erneut ausgeführt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Technische Komponenten (Zusammenfassung)
|
||||||
|
|
||||||
|
| Komponente | Verantwortung |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **`TAstPrettyPrinter`** | Wandelt AST-Knoten in S-Expressions (`(func arg1 arg2)`). Unterstützt "Shallow Printing" mit Platzhaltern. |
|
||||||
|
| **`TAstParser`** | Wandelt S-Expressions zurück in AST-Knoten. Löst Platzhalter (`#ID`) über den `TEditContext` auf. |
|
||||||
|
| **`TEditContext`** | Hält die Referenzen zwischen Text-Tokens (`#1`) und echten `IAstNode`-Instanzen während des Editierens. |
|
||||||
|
| **`TRtlRegistry`** | Registriert Finanzfunktionen (`SMA`, `RSI`) und UI-Widgets (`Slider`, `Chart`). |
|
||||||
|
| **`TAstEditorController`** | Orchestriert Edit-Vorgänge. Führt "Path Copying" durch, um den immutablen Baum nach einem Text-Edit zu aktualisieren. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. User Workflow Beispiel
|
||||||
|
|
||||||
|
1. **Struktur bauen:** User zieht `LoadCSV`, `VarDecl` (für SMA) und `Chart` als Blöcke in den Workspace.
|
||||||
|
2. **Logik verfeinern:** User doppelklickt auf den Parameter des SMA.
|
||||||
|
3. **Text Edit:** Ein kleines Popup erscheint. User tippt `(* 20 2)`.
|
||||||
|
4. **Commit:** Aus dem Text wird ein Multiplikations-Knoten. Der Block zeigt nun `40` (oder die Formel).
|
||||||
|
5. **Analyse:** Der Chart zeigt sofort den SMA(40).
|
||||||
|
6. **Tuning:** User klickt auf die `20` im Text/Label, zieht die Maus nach rechts. Die Zahl steigt auf `25`. Der Chart aktualisiert sich in Echtzeit.
|
||||||
|
7. **Refactoring:** User merkt, er braucht Logik. Er klickt auf den SMA-Block und drückt `Ctrl+Space` (Wrap in...). Wählt `If`.
|
||||||
|
* Der SMA ist nun das "Then"-Kind eines neuen If-Blocks.
|
||||||
|
* Die Struktur wurde geändert, ohne Text kopieren zu müssen.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# **Projektplan: Visueller AST-Editor**
|
||||||
|
|
||||||
|
* **Datum:** 02.09.2025 18:17
|
||||||
|
|
||||||
|
### **Motivation**
|
||||||
|
|
||||||
|
Der bestehende AST-Visualizer soll zu einem vollwertigen, interaktiven Editor ausgebaut werden. Ziel ist es, dem Benutzer die Erstellung und Bearbeitung von ASTs auf eine rein visuelle, intuitive und fehlerresistente Weise zu ermöglichen. Eine Kernanforderung ist die Möglichkeit, AST-Strukturen von externen Tools, insbesondere LLMs, über ein JSON-Format zu importieren.
|
||||||
|
|
||||||
|
### **Ziel**
|
||||||
|
|
||||||
|
Die Entwicklung eines robusten visuellen Editors, bei dem der Abstract Syntax Tree (AST) zu jeder Zeit in einem syntaktisch validen Zustand ist. Der Benutzer soll durch kontextsensitive Aktionen angeleitet werden, anstatt durch freies "Verkabeln" Fehler machen zu können. Das Design muss eine saubere Trennung zwischen der logischen AST-Struktur und ihrer visuellen Repräsentation gewährleisten, um die Anbindung an externe Tools zu vereinfachen.
|
||||||
|
|
||||||
|
### **Ergebnis: Architekturentwurf**
|
||||||
|
|
||||||
|
Der Editor basiert auf einem Model-View-Controller-Ansatz mit einer strikten Trennung der Verantwortlichkeiten.
|
||||||
|
|
||||||
|
**1\. Kernarchitektur: Der reine AST als Model**
|
||||||
|
|
||||||
|
* **Source of Truth**: Der IAstNode-Baum ist das alleinige Model und die "Source of Truth". Er enthält ausschließlich die logische Struktur und die Beziehungen der Knoten untereinander.
|
||||||
|
* **Datenreinheit**: Das Model enthält keinerlei UI-spezifische Informationen wie Positionen, Farben, oder Zustände (z.B. "eingeklappt"). Diese Reinheit ist die Voraussetzung für eine einfache Serialisierung und die Interaktion mit externen Systemen.
|
||||||
|
* **Mapping**: Eine zentrale Controller-Klasse verwaltet die Zuordnung zwischen Model und View, idealerweise über ein TDictionary\<IAstNode, TAuraNode\>.
|
||||||
|
|
||||||
|
**2\. Layout-Engine: Deterministische Visualisierung (View \= f(AST))**
|
||||||
|
|
||||||
|
* **Grundprinzip**: Die gesamte visuelle Darstellung wird bei jeder Änderung prozedural und deterministisch aus dem Zustand des AST-Models generiert.
|
||||||
|
* **Layout-Algorithmus**: Der bestehende Ansatz aus dem TAstToAuraNodeVisitor wird formalisiert:
|
||||||
|
1. Für einen gegebenen Knoten werden zuerst rekursiv alle seine Input-Knoten (Kinder) von links nach rechts und oben nach unten positioniert.
|
||||||
|
2. Anschließend wird der Eltern-Knoten rechts von den Grenzen seiner Kinder platziert, typischerweise vertikal zentriert.
|
||||||
|
* **Metadaten-Overrides**: Manuelle Änderungen am Layout durch den Benutzer (z.B. das Verschieben eines Knotens) werden als optionale Overrides behandelt. Diese werden in einer vom Model getrennten Struktur (TDictionary\<IAstNode, TUIMetadata\>) gespeichert. Beim Layout-Prozess wird für jeden Knoten zuerst geprüft, ob ein solcher Override existiert; falls nicht, wird die Position algorithmisch berechnet.
|
||||||
|
|
||||||
|
**3\. Editier-Paradigma: Socket-basierte, geführte Bearbeitung**
|
||||||
|
|
||||||
|
* **Ziel: "Always Valid AST"**: Jede vom Benutzer durchgeführte Aktion überführt den AST von einem validen Zustand in einen neuen validen Zustand. Syntaxfehler durch den Benutzer werden durch das Design ausgeschlossen.
|
||||||
|
* **Sockets statt Palette**: Unverbundene Input-Pins dienen als "Sockets" und sind die primären Interaktionspunkte zum Erweitern des Baumes. Es gibt keine globale Palette, aus der beliebige Knoten auf eine leere Fläche gezogen werden können.
|
||||||
|
* **Kontextsensitive Aktionen**: Ein Klick auf einen Socket öffnet ein Popup-Menü, das ausschließlich Aktionen und Knotentypen anbietet, die an dieser Stelle syntaktisch zulässig sind.
|
||||||
|
* **Refactoring-Operationen**: Die Manipulation bestehender Knoten erfolgt durch gezielte Befehle wie "Ersetzen durch...", "Löschen" (setzt auf Socket zurück) oder "Umschließen mit...". Drag & Drop dient dem Umordnen von Sequenzen oder dem Verschieben ganzer, valider Teilbäume in einen kompatiblen Socket.
|
||||||
|
|
||||||
|
**4\. Serialisierung & LLM-Integration: Das duale Clipboard**
|
||||||
|
|
||||||
|
* **Zwei Anwendungsfälle**: Es wird zwischen der internen Benutzererfahrung und dem externen Datenaustausch unterschieden.
|
||||||
|
* **Standard-Clipboard (Strg+C / Strg+V)**: Für die nahtlose Arbeit des Benutzers innerhalb des Editors.
|
||||||
|
* **Kopieren**: Serialisiert den ausgewählten AST-Teilbaum **inklusive** der UI-Metadaten (Layout-Overrides).
|
||||||
|
* **Einfügen**: Deserialisiert das Paket und reproduziert den visuellen Zustand 1:1.
|
||||||
|
* **Logik-Clipboard (via Kontextmenü)**: Für den robusten Austausch mit LLMs und anderen Tools.
|
||||||
|
* **"Logik als JSON kopieren"**: Serialisiert den AST-Teilbaum **ohne** jegliche UI-Metadaten in ein pures, logisches JSON-Format.
|
||||||
|
* **"Logik aus JSON einfügen"**: Deserialisiert ein pures JSON. Eventuell vorhandene, fremde Metadaten werden tolerant ignoriert. Nach dem Einfügen wird der neue Teilbaum durch die Layout-Engine automatisch positioniert.
|
||||||
|
|
||||||
|
**5\. Undo/Redo: Das Command Pattern**
|
||||||
|
|
||||||
|
* Jede modifizierende Aktion (Knoten erstellen, verbinden, Eigenschaft ändern) wird als IEditorCommand-Objekt mit Execute- und Unexecute-Methoden implementiert. Ein Command-Manager verwaltet die Undo/Redo-Stacks.
|
||||||
|
|
||||||
|
### **TODO: Nächste Schritte**
|
||||||
|
|
||||||
|
1. **Architektur-Refactoring**:
|
||||||
|
* Entkopplung der IAstNode-Struktur von der TAuraNode-View.
|
||||||
|
* Einführung einer Controller-Klasse, die das Mapping (TDictionary) und die Interaktionslogik verwaltet.
|
||||||
|
2. **Layout-Engine implementieren**:
|
||||||
|
* Formalisierung des deterministischen Layout-Algorithmus in einer wiederverwendbaren Einheit.
|
||||||
|
* Implementierung des Metadaten-Override-Systems.
|
||||||
|
3. **Command Pattern implementieren**:
|
||||||
|
* Definition der IEditorCommand-Schnittstelle.
|
||||||
|
* Implementierung der grundlegenden Command-Klassen (CreateNode, DeleteNode, ConnectNodes, SetProperty).
|
||||||
|
* Aufbau eines TCommandManager für die Verwaltung der Undo/Redo-Historie.
|
||||||
|
4. **Controller-Logik entwickeln**:
|
||||||
|
* Implementierung der Socket-Interaktion (Klick-Handler).
|
||||||
|
* Logik zur dynamischen Erzeugung der kontextsensitiven Menüs.
|
||||||
|
* Anbindung der Benutzeraktionen an das Command-System.
|
||||||
|
5. **Duales Clipboard-System umsetzen**:
|
||||||
|
* Entwicklung der JSON-Serialisierungs- und Deserialisierungsroutinen für beide Formate (mit und ohne Metadaten).
|
||||||
|
* Implementierung der entsprechenden UI-Aktionen.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
### **Projektplan: Visueller Editor für Handelsstrategien (Revision 1\)**
|
||||||
|
|
||||||
|
* **Datum:** 08.09.2025 14:20
|
||||||
|
|
||||||
|
#### **Motivation**
|
||||||
|
|
||||||
|
Ziel ist die Schaffung eines Systems, das es Fachexperten ermöglicht, die **Logik und den Datenfluss** von Handelsstrategien visuell zu entwerfen. Statt der reinen Syntax wird der **semantische Graph** abgebildet, um Redundanzen zu eliminieren und die tatsächlichen Beziehungen zwischen den Variablen und Operationen in den Vordergrund zu stellen. Dies schafft ein intuitiveres Verständnis der Strategie-Logik.
|
||||||
|
|
||||||
|
#### **Ziel**
|
||||||
|
|
||||||
|
Die Entwicklung einer Architektur, die den **semantisch korrekten Graphen** einer Strategie visualisiert. Dies erfordert eine klare Abfolge der Verarbeitungsschritte: Zuerst muss eine semantische Analyse (Binding) des rohen Abstract Syntax Tree (AST) erfolgen. Erst auf Basis dieses angereicherten, semantischen Modells wird der visuelle Graph (ein Directed Acyclic Graph, DAG) erzeugt, in dem semantisch identische Entitäten (z.B. Verwendungen derselben Variable) zu einem einzigen visuellen Knoten zusammengefasst werden.
|
||||||
|
|
||||||
|
#### **Ergebnis: Architekturentwurf (Revision 1\)**
|
||||||
|
|
||||||
|
Die Architektur wird angepasst, um den semantischen Graphen als "Source of Truth" für die Visualisierung zu nutzen.
|
||||||
|
|
||||||
|
1. **Semantische Analyse als Voraussetzung:** Der Bind-Prozess ist der obligatorische erste Schritt vor jeder Visualisierung. Er analysiert den AST und reichert die Knoten, insbesondere die Identifier, mit semantischen Adressinformationen (TResolvedAddress) an.
|
||||||
|
2. **Der Builder (TAstToViewModelVisitor):** Der Visitor wird intelligent. Er übersetzt den AST nicht mehr 1:1, sondern erzeugt einen TVisualNodeViewModel-Graphen (DAG). Dazu nutzt er einen internen Cache (TDictionary\<TResolvedAddress, TVisualNodeViewModel\>), um bereits erstellte ViewModels für semantisch identische Identifier wiederzuverwenden.
|
||||||
|
3. **Das Modell (TVisualNodeViewModel):** Die Struktur der ViewModels ist nicht länger ein Baum, sondern ein DAG, da ein Knoten (z.B. für eine Variable) nun von mehreren Elternknoten referenziert werden kann.
|
||||||
|
4. **Die Ansicht (TAuraLayoutEngine):** Die Layout-Engine muss in der Lage sein, die resultierende DAG-Struktur korrekt darzustellen, inklusive der Verbindungen von mehreren Eltern zu einem Kind.
|
||||||
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# **Projektplan: Visuelles System für Handelsstrategien**
|
||||||
|
|
||||||
|
* **Datum:** 02.09.2025 18:47
|
||||||
|
|
||||||
|
### **Motivation**
|
||||||
|
|
||||||
|
Ziel ist die Schaffung eines Systems, das es Fachexperten im Finanzbereich (z.B. Tradern, Analysten) ohne Programmierkenntnisse ermöglicht, komplexe Handelsstrategien interaktiv zu entwerfen, zu visualisieren, zu debuggen und zu backtesten. Die traditionelle Hürde der textbasierten Programmierung soll durch einen rein visuellen, geführten Ansatz eliminiert werden. Das System muss erweiterbar sein und eine robuste Schnittstelle für den Import von Logik aus externen Quellen wie LLMs bieten.
|
||||||
|
|
||||||
|
### **Ziel**
|
||||||
|
|
||||||
|
Die Entwicklung einer dualen Systemarchitektur, die eine intuitive, fehlerresistente Entwicklungsumgebung von einer hochperformanten Backtesting-Engine trennt. Der Benutzer interagiert mit einer High-Level-Repräsentation seiner Strategie (HAST), die für die Ausführung in eine optimierte Low-Level-Repräsentation (CAST) übersetzt wird. Dies ermöglicht eine reichhaltige Debugging-Erfahrung bei gleichzeitig maximaler Performance für datenintensive Backtests.
|
||||||
|
|
||||||
|
### **Ergebnis: Detaillierter Architekturentwurf**
|
||||||
|
|
||||||
|
Die Architektur besteht aus zwei primären Ausführungsmodi (**Debug-Modus** und **Backtest-Modus**), die auf unterschiedlichen Repräsentationen des AST und spezialisierten Engines operieren.
|
||||||
|
|
||||||
|
**1\. Der High-Level AST (HAST) \- Die Welt des Benutzers**
|
||||||
|
|
||||||
|
* **Rolle**: Die alleinige "Source of Truth" für die visuelle Darstellung und die interaktive Bearbeitung. Der HAST ist die Repräsentation, die der Benutzer sieht und manipuliert.
|
||||||
|
* **Struktur**: Ein Baum aus IAstNode-Interfaces. Er enthält eine Mischung aus primitiven Knoten (die direkt einer nativen Operation entsprechen) und zusammengesetzten Knoten (vom Benutzer erstellte "Funktionen" oder Sub-Graphen).
|
||||||
|
* **Editor-Interaktion**: Der visuelle Editor arbeitet ausschließlich auf dem HAST. Die Bearbeitung ist strukturell und geführt:
|
||||||
|
* **Sockets**: Unverbundene Input-Pins sind die einzigen Stellen, an denen der Baum erweitert werden kann.
|
||||||
|
* **Kontext-sensitive Menüs**: Ein Klick auf ein Socket bietet nur syntaktisch zulässige Knoten und Aktionen an, was Fehler von vornherein verhindert.
|
||||||
|
* **Gültigkeit**: Der HAST befindet sich zu jedem Zeitpunkt in einem strukturell validen Zustand.
|
||||||
|
* **Erweiterbarkeit**: Benutzer können neue HAST-Knoten durch visuelle Komposition erstellen ("Zu Funktion zusammenfassen"). Dies ist der primäre Mechanismus zur Schaffung von Wiederverwendbarkeit und Abstraktion für den Endanwender.
|
||||||
|
|
||||||
|
**2\. Der Core AST (CAST) \- Die Welt der Engine**
|
||||||
|
|
||||||
|
* **Rolle**: Eine optimierte, "flache" Zwischenrepräsentation der Strategie, die speziell für die High-Performance-Engine konzipiert ist. Man kann sie als den "Maschinencode" des Systems betrachten.
|
||||||
|
* **Struktur**: Ein Graph, der ausschließlich aus einem minimalen Satz von primitiven Knoten besteht. Jeder CAST-Knoten entspricht einer direkten, in nativem Delphi-Code implementierten, hochoptimierten Operation. Konzepte wie "Benutzerfunktion" oder "Sub-Graph" existieren auf dieser Ebene nicht mehr.
|
||||||
|
|
||||||
|
**3\. Die zwei Ausführungs-Engines**
|
||||||
|
|
||||||
|
* **Engine A: Der HAST-Interpreter (Der Debugger)**
|
||||||
|
* **Modus**: Wird im interaktiven **Debug-Modus** verwendet. (Dies entspricht dem aktuell existierenden Evaluator).
|
||||||
|
* **Funktionsweise**: Arbeitet direkt auf dem HAST. Er ist langsamer, da er die Logik für das "Betreten" und "Verlassen" von zusammengesetzten Knoten (Funktionsaufrufe) zur Laufzeit interpretieren muss.
|
||||||
|
* **Features**: Eng mit der UI gekoppelt, um eine reichhaltige Debugging-Erfahrung zu ermöglichen: Visuelle Hervorhebung des aktuellen Knotens, Breakpoints, Step-Into/Over/Out, Live-Inspektion der Daten auf den Verbindungen und detailliertes Logging.
|
||||||
|
* **Engine B: Der CAST-Evaluator (Der Backtester)**
|
||||||
|
* **Modus**: Wird im **Backtest-Modus** für die Massenverarbeitung von Daten verwendet.
|
||||||
|
* **Funktionsweise**: Arbeitet ausschließlich auf dem CAST. Er wird durch einen vorgeschalteten **HAST \-\> CAST Expander** (ein spezieller Visitor) gespeist, der den HAST in den optimierten CAST übersetzt.
|
||||||
|
* **Features**: Eine "Headless"-Engine ohne UI-Anbindung. Ihre einzige Aufgabe ist die maximale Ausführungsgeschwindigkeit. Sie kennt keine Breakpoints oder detailliertes Logging und gibt am Ende nur das finale Ergebnis (z.B. Trade-Listen, Performance-Metriken) zurück.
|
||||||
|
|
||||||
|
**4\. Serialisierung & Externe Integration**
|
||||||
|
|
||||||
|
* **Duales Clipboard**: Um sowohl die interne Usability als auch die externe Anbindung optimal zu unterstützen, werden zwei Clipboard-Mechanismen implementiert.
|
||||||
|
* **Standard-Clipboard (Strg+C/V)**: Kopiert den HAST-Teilbaum **inklusive** der optionalen UI-Metadaten (manuelle Knotenpositionen etc.), um ein perfektes visuelles Duplikat für den Benutzer zu erstellen.
|
||||||
|
* **Logik-Clipboard (via Kontextmenü)**: Kopiert/einfügt einen **puren HAST** als JSON ohne jegliche UI-Metadaten. Dies ist die saubere, robuste Schnittstelle für die Interaktion mit LLMs und anderen Tools.
|
||||||
|
|
||||||
|
### **TODO: Roadmap für die Implementierung**
|
||||||
|
|
||||||
|
1. **Fundament (Editor & HAST)**
|
||||||
|
* Finalisierung der IAstNode-Struktur für den HAST.
|
||||||
|
* Implementierung des Socket-basierten Editier-Controllers mit kontextsensitiven Menüs.
|
||||||
|
* Umsetzung des Command Patterns für alle AST-modifizierenden Aktionen (Undo/Redo).
|
||||||
|
* Implementierung der visuellen "Zu Funktion zusammenfassen"-Logik.
|
||||||
|
* Aufbau des separaten Speichers für UI-Metadaten-Overrides.
|
||||||
|
2. **Engine 1 (HAST-Interpreter / Debugger)**
|
||||||
|
* Ausbau des bestehenden Evaluators zum vollwertigen HAST-Interpreter.
|
||||||
|
* Tiefe Integration mit der UI zur Realisierung der Debugging-Features (Breakpoints, Step-Logik, Daten-Hover etc.).
|
||||||
|
3. **Engine 2 (CAST-Evaluator / Backtester)**
|
||||||
|
* Definition des minimalen Satzes an primitiven Knoten für den CAST.
|
||||||
|
* Implementierung der hochperformanten, nativen Delphi-Operationen für jeden CAST-Knoten.
|
||||||
|
* Entwicklung des HAST-zu-CAST-Expander-Visitors.
|
||||||
|
* Erstellung des "headless" CAST-Evaluators, der den CAST entgegennimmt und die Ergebnisse zurückliefert.
|
||||||
|
4. **Werkzeuge & Integration**
|
||||||
|
* Implementierung der beiden JSON-Serialisierungs-Routinen (mit/ohne Metadaten).
|
||||||
|
* Integration der Clipboard-Aktionen in die UI.
|
||||||
|
* Aufbau der "Standardbibliothek" mit nützlichen, vordefinierten HAST-Knoten.
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
# White Paper: A Hybrid Approach to Macro Hygiene
|
||||||
|
|
||||||
|
**Date:** 09.11.2025
|
||||||
|
**Status:** Final
|
||||||
|
|
||||||
|
## 1\. Executive Summary
|
||||||
|
|
||||||
|
The macro system is a cornerstone of the compiler, enabling powerful syntactic abstraction. However, designing a macro system requires solving the fundamental conflict between **safety** (preventing accidental variable conflicts) and **power** (allowing macros to interact with their calling context).
|
||||||
|
|
||||||
|
This document outlines the rationale for the implemented hybrid macro system. This system is designed to provide the best of both worlds without burdening the macro author with manual hygiene management (such as `gensym` or special syntax).
|
||||||
|
|
||||||
|
Our system operates on two simple, deterministic rules:
|
||||||
|
|
||||||
|
1. **Automatic Hygiene for Definitions:** All symbols *defined* within a macro template are automatically renamed to be unique, preventing conflicts with user code or nested macro calls.
|
||||||
|
2. **Unhygienic Fallback for Free Symbols:** All *free symbols* (those used but not defined within the template) are left untouched. They are resolved by the binder in the **call-site scope** (the scope where the macro was invoked).
|
||||||
|
|
||||||
|
This hybrid model ensures that internal macro variables are always safe, while simultaneously permitting powerful, context-aware macros.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## 2\. The Core Challenge: Safety vs. Context
|
||||||
|
|
||||||
|
A macro, by definition, injects code into a foreign scope. This creates two distinct, opposing requirements.
|
||||||
|
|
||||||
|
### Scenario A: The Need for Safety (Hygienic Definitions)
|
||||||
|
|
||||||
|
This is the classic hygiene problem. A macro must manage its own internal state without interfering with the user's code.
|
||||||
|
|
||||||
|
Consider a simple `stopwatch` macro:
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(* Macro Definition *)
|
||||||
|
(defmacro stopwatch [body]
|
||||||
|
`(do
|
||||||
|
(def start-time (timestamp))
|
||||||
|
(def result ~body)
|
||||||
|
(print "Time: " (- (timestamp) start-time))
|
||||||
|
result))
|
||||||
|
|
||||||
|
(* User Code *)
|
||||||
|
(def start-time "Important User Data")
|
||||||
|
(stopwatch (expensive-call))
|
||||||
|
(print start-time)
|
||||||
|
```
|
||||||
|
|
||||||
|
A naive (fully unhygienic) expansion would redefine the user's `start-time` variable, corrupting their program. This is unacceptable. The macro's internal variables (`start-time`, `result`) must be **hygienic**—that is, isolated from the call-site scope.
|
||||||
|
|
||||||
|
### Scenario B: The Need for Context (Unhygienic Free Symbols)
|
||||||
|
|
||||||
|
Macros derive their power from interacting with the context in which they are called. A macro may need to read variables from the user's scope.
|
||||||
|
|
||||||
|
Consider a `debug-print` macro:
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(* Macro Definition *)
|
||||||
|
(defmacro debug-print [msg]
|
||||||
|
`(if *debug-mode*
|
||||||
|
(print msg)))
|
||||||
|
|
||||||
|
(* User Code *)
|
||||||
|
(do
|
||||||
|
(def *debug-mode* true) ; User-defined context variable
|
||||||
|
(debug-print "Test message")
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, the macro *must* access the user's `*debug-mode*` variable from the call-site scope. A "fully hygienic" system (which binds *all* symbols to the macro's *definition-site scope*) would fail, as it would be unable to see the user's local `*debug-mode*` variable.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## 3\. Rejected Alternatives
|
||||||
|
|
||||||
|
To solve this conflict, several common designs were considered and rejected for failing one of the two core scenarios.
|
||||||
|
|
||||||
|
* **Fully Unhygienic (The `$` Suffix):** This approach makes all symbols unhygienic by default and requires the author to manually mark internal variables (e.g., `start-time$`) for special handling. This inverts the desired default (safety) and fails to solve nested macro conflicts.
|
||||||
|
* **Fully Hygienic (Strict Academic):** This system binds *all* symbols (defined or free) to the macro's definition-site scope. This perfectly solves Scenario A but makes Scenario B impossible.
|
||||||
|
* **Explicit Gensym (The Lisp Way):** This requires the macro author to manually generate unique symbols (`(let [start-sym (gensym)] ...)`). This is syntactically complex, error-prone, and places an unnecessary burden on the author.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## 4\. The Implemented Solution: Hybrid Hygiene
|
||||||
|
|
||||||
|
Our system resolves the conflict by treating definitions and free variables differently. The logic is handled entirely by the macro expander, requiring no special syntax from the macro author.
|
||||||
|
|
||||||
|
### Rule 1: Automatic Renaming of Definitions
|
||||||
|
|
||||||
|
When the macro expander processes a template, it performs a pre-pass to identify all symbols being *defined*. This includes `def` forms, `fn` parameters, and `let` bindings.
|
||||||
|
|
||||||
|
* For each defined symbol (e.g., `start-time`), the expander generates a unique, internal-only name (e.g., `start-time_G123`).
|
||||||
|
* It stores this in an expansion-local rename map.
|
||||||
|
* It then replaces all occurrences of that symbol *within the template* with the new name.
|
||||||
|
|
||||||
|
This automatically and transparently solves **Scenario A**. The `stopwatch` macro's `start-time` becomes `start-time_G123` and cannot possibly conflict with the user's `start-time`. This also solves the nested macro problem, as each expansion generates new unique names.
|
||||||
|
|
||||||
|
### Rule 2: Unhygienic Fallback for Free Symbols
|
||||||
|
|
||||||
|
Any symbol in the macro template that is *not* part of a definition (a "free symbol") is left untouched by the expander.
|
||||||
|
|
||||||
|
* In `stopwatch`, the symbols `do`, `timestamp`, `print`, and `-` are free symbols.
|
||||||
|
* In `debug-print`, the symbols `if`, `*debug-mode*`, and `print` are free symbols.
|
||||||
|
|
||||||
|
The expander passes these symbols directly to the next compiler stage (the binder). The binder then resolves them, as it would any normal code, within the **call-site scope**.
|
||||||
|
|
||||||
|
This solves **Scenario B**. The binder finds `*debug-mode*` in the user's `do` block, exactly as intended.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## 5\. Conclusion and Trade-Offs
|
||||||
|
|
||||||
|
This hybrid design provides "implicit hygiene" for the common case (internal variables) while defaulting to "unhygienic" behavior for external symbols, which provides maximum power and flexibility.
|
||||||
|
|
||||||
|
The primary trade-off of this design is that **typographical errors in free symbols are caught late**.
|
||||||
|
|
||||||
|
For example, if the `stopwatch` macro misspelled `print` as `prnit`:
|
||||||
|
|
||||||
|
1. The expander would see `prnit` as a free symbol (Rule 2) and leave it untouched.
|
||||||
|
2. The binder would then fail to find `prnit` in the *user's* call-site scope.
|
||||||
|
|
||||||
|
The resulting error (`Undefined symbol: prnit`) will point to the user's code where `stopwatch` was *called*, not the macro *definition*. This is a minor, acceptable trade-off for a system that achieves both safety and power without syntactic overhead.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
Absolut. Hier ist der Projektplan, der unsere Ergebnisse zusammenfasst.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### Projektplan: Thread-sicheres Zustandsmodell
|
||||||
|
|
||||||
|
* **Datum:** 30. September 2025
|
||||||
|
* **Uhrzeit:** 13:09
|
||||||
|
|
||||||
|
#### Motivation
|
||||||
|
|
||||||
|
Ziel ist eine einfache, visuell darstellbare und inhärent threadsichere Skriptsprache. Eine rein funktionale Herangehensweise erwies sich als unpraktisch. Stattdessen wird ein pragmatischer Ansatz nach dem Vorbild von Clojure verfolgt: Daten sind standardmäßig immutable, aber es gibt explizite, sichere Werkzeuge zur Verwaltung von veränderlichem Zustand.
|
||||||
|
|
||||||
|
#### Ziel
|
||||||
|
|
||||||
|
Die Sprache soll eine klare Trennung zwischen unveränderlichen **Werten** und veränderlichen **Identitäten** (Variablen) haben. Jede Zustandsänderung muss explizit und atomar sein, um Race Conditions per Design auszuschließen. Die Implementierung soll dabei möglichst einfach und performant (lock-free) sein.
|
||||||
|
|
||||||
|
#### Ergebnis
|
||||||
|
|
||||||
|
Wir haben eine elegante, lock-freie Lösung erarbeitet, die auf einer zentralen Regel basiert: Der Typ (`FKind`) einer `TDataValue` ist nach der Initialisierung **immutable**.
|
||||||
|
|
||||||
|
1. **Atomarität in `TDataValue`:** Die atomaren Operationen (`Reset`, `CompareAndSet`) werden direkt als Instanzmethoden auf dem `TDataValue`-Record implementiert. Dies ist möglich, weil die `FKind`-Immutabilität die TOCTOU-Race-Condition verhindert, die eine solche Implementierung sonst unsicher machen würde.
|
||||||
|
2. **Selektives Capturing bleibt:** Der `IValueCell`-Mechanismus in der `Scope`-Unit wird beibehalten. Seine entscheidende Rolle ist nicht die Atomarität, sondern die Speicheroptimierung, indem Closures nur die Referenzen auf die Upvalues halten, die sie wirklich benötigen, und nicht den gesamten Parent-Scope.
|
||||||
|
3. **Separation of Concerns:** `TDataValue` weiß, *wie* man seinen Zustand atomar ändert. `IValueCell` ist die notwendige Indirektion, um Closures und Speichermanagement korrekt abzubilden.
|
||||||
|
|
||||||
|
#### Todo
|
||||||
|
|
||||||
|
* [ ] Die atomaren Instanzmethoden (`Reset`, `CompareAndSet`) in `Myc.Data.Value` finalisieren.
|
||||||
|
* [ ] Die `IValueCell`-Implementierung in `Myc.Ast.Scope` so anpassen, dass sie die neuen atomaren Instanzmethoden von `TDataValue` aufruft.
|
||||||
|
* [ ] Neue RTL-Funktionen `reset!`, `compare-and-set!` und das abgeleitete `swap!` erstellen, die im Evaluator auf den `IValueCell`-Instanzen operieren.
|
||||||
|
* [ ] Das alte `assign`-Schlüsselwort aus Parser, Binder und Evaluator entfernen.
|
||||||
|
* [ ] Bestehende Tests (`CreateSMA`, `FailingUpvalueButtonClick` etc.) auf die Verwendung von `reset!` oder `swap!` anstelle von `assign` umstellen.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Projekt-Protokoll: Weiterentwicklung des Sprachdesigns
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **Datum:** 08. Oktober 2025
|
||||||
|
|
||||||
|
* **Zeit:** 13:22 CEST
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Die bestehende Skriptsprache bietet grundlegende funktionale und imperative Konstrukte, aber die Verwaltung von veränderlichem Zustand (`def` in Verbindung mit `assign`) ist unsicher, insbesondere im Hinblick auf Nebenläufigkeit. Das primäre Motiv für eine Weiterentwicklung ist die Schaffung einer Sprache, die **aus sich selbst heraus sicher ("safe by design")** ist, ohne dabei die Komplexität von Low-Level-Konzepten wie manueller Speicherverwaltung oder einem vollständigen Borrow-Checker (wie in Rust) einzuführen. Die Sprache soll auch für Programmier-Anfänger **intuitiv und leicht verständlich** bleiben.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Das Ziel ist es, ein Sprachdesign zu entwickeln, das eine klare und sichere Handhabung von Zustand erzwingt. Die Kernziele des neuen Designs sind:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. **Sicherheit durch Design:** Race Conditions durch geteilten, veränderlichen Zustand sollen zur Compile-Zeit erkannt und verhindert werden.
|
||||||
|
|
||||||
|
2. **Klare Trennung von Zustandsarten:** Es soll eine unmissverständliche Unterscheidung zwischen temporärem, lokalem Zustand und persistentem, geteiltem Zustand geben.
|
||||||
|
|
||||||
|
3. **Intuitive Semantik:** Die Regeln der Sprache sollen den Entwickler aktiv zu sicheren und korrekten Mustern anleiten ("Pit of Success"). "Magisches" oder unvorhersehbares Verhalten des Compilers soll vermieden werden.
|
||||||
|
|
||||||
|
4. **Pragmatismus:** Ein bekannter, imperativer Programmierstil soll für lokale Logik weiterhin möglich sein, um die Lernkurve flach zu halten.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Ergebnis: Das finale Sprachdesign
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Wir haben uns auf ein pragmatisches Hybrid-Modell geeinigt, das funktionale Sicherheit mit imperativem Komfort kombiniert. Es basiert auf drei Säulen:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1. Sicherer globaler Zustand: `def` erzeugt Atome
|
||||||
|
|
||||||
|
Der `def`-Befehl wird modifiziert. Er dient ausschließlich zur Definition von Zustand, der potenziell über Funktionsgrenzen hinweg geteilt wird. Um dies von Natur aus sicher zu machen, erzeugt `def` nicht mehr einen einfachen, veränderlichen "Slot", sondern immer einen **atomaren Container**, der den Wert umschließt. Jede Zustandsänderung muss über explizite, threadsichere atomare Operationen (wie `swap!` oder `reset!`) erfolgen. Dies eliminiert alle Race Conditions bei globalem Zustand.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2. Veränderlicher lokaler Zustand: `let` mit `assign`
|
||||||
|
|
||||||
|
Ein neues `let`-Konstrukt wird eingeführt, das sich an Clojure orientiert, um einen neuen lexikalischen Gültigkeitsbereich zu schaffen. Im Gegensatz zu Clojure sind die in `let` definierten Bindungen jedoch **standardmäßig veränderlich** und können über `assign` modifiziert werden. Dies erlaubt Entwicklern, für rein lokale Algorithmen (z.B. Schleifen mit Zählern) einen vertrauten, imperativen Stil zu verwenden, wo Threadsicherheit keine Rolle spielt.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3. Die Sicherheitsbrücke: Statische Analyse im Binder
|
||||||
|
|
||||||
|
Dies ist die zentrale Innovation, die beide Welten sicher miteinander verbindet. Der Compiler (speziell der Binder) führt eine **Escape Analysis** für Closures durch, um die missbräuchliche Freigabe von unsicherem, lokalem Zustand zu verhindern. Dabei gilt eine einzige, einfache Regel:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
> **Eine Closure, die eine veränderliche let-Variable fängt, darf nicht aus dem Gültigkeitsbereich der Funktion zurückgegeben (oder global gespeichert) werden, in der diese Variable definiert wurde.**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* Wenn eine solche "zustandsbehaftete" Closure nur lokal verwendet wird (z.B. in einem `Map`), ist der Code gültig.
|
||||||
|
|
||||||
|
* Wenn versucht wird, eine solche Closure zurückzugeben oder einem globalen `def` zuzuweisen, erzeugt der Binder einen **Compile-Fehler**.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Dieser Fehler ist ein Feature, kein Mangel. Er zwingt den Entwickler, eine bewusste Entscheidung zu treffen: Entweder er strukturiert seinen Code um, oder er verwendet für den Zustand, der geteilt werden muss, explizit ein sicheres Primitiv (indem er die lokale Variable selbst zu einem `(atom ...)` macht).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Dieses Design vermeidet die Komplexität eines Borrow Checkers und die Unvorhersehbarkeit von "Compiler-Magie", indem es eine klare, leicht verständliche Regel zur Compile-Zeit durchsetzt.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Nächste Schritte (Todo)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. **Binder anpassen:** Implementierung der Escape Analysis für Closures. Der Binder muss `let`-Variablen, ihre Veränderung durch `assign` und das Fangen durch Closures nachverfolgen.
|
||||||
|
|
||||||
|
2. **Parser erweitern:** Hinzufügen der Syntax für das neue `let`-Statement.
|
||||||
|
|
||||||
|
3. **`def`-Implementierung ändern:** Die Laufzeitlogik für `def` muss so angepasst werden, dass sie atomare Container anstelle von rohen Werten verwaltet.
|
||||||
|
|
||||||
|
4. **RTL erweitern:** Die Laufzeit-Bibliothek muss um Funktionen zur Interaktion mit Atomen erweitert werden (z.B. `atom`, `deref`, `swap!`, `reset!`).
|
||||||
|
|
||||||
|
5. **Compiler-Fehlermeldung implementieren:** Eine klare und hilfreiche Fehlermeldung für den Fall entwerfen, dass die "Sicherheitsbrücke"-Regel verletzt wird.
|
||||||
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Projekt-Protokoll: Finalisierung des Sprachdesigns für Zustand
|
||||||
|
|
||||||
|
* **Datum:** 09. Oktober 2025
|
||||||
|
* **Zeit:** 12:26 CEST
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Aufbauend auf dem initialen Entwurf eines sicheren Zustands-Modells geht es in diesem Schritt um die Konkretisierung und Verfeinerung des Sprachdesigns. Die Diskussion hat gezeigt, dass die Wahl der Schlüsselwörter (`def`, `let`) und die exakte Definition der Scope-Regeln entscheidend für die Intuivität und Sicherheit der Sprache sind. Ziel dieses Dokuments ist es, die finalen Entscheidungen festzuhalten.
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
1. **Etablierung unmissverständlicher Schlüsselwörter:** Die finalen Namen für die drei Zustands-Konstrukte (`atom`, `var`, `let`) sollen deren Semantik klar und deutlich widerspiegeln.
|
||||||
|
2. **Festlegung der Scope-Regeln:** Die Rolle des `(do)`-Blocks als primärer Scope-erzeugender Mechanismus für lokalen Zustand wird formalisiert.
|
||||||
|
3. **Definition der Unveränderlichkeits-Garantie:** Die Regeln für die Compile-Zeit-Überprüfung der Unveränderlichkeit von `let`-Bindungen werden festgelegt.
|
||||||
|
|
||||||
|
## Ergebnis: Das finale Drei-Säulen-Modell für Zustand
|
||||||
|
|
||||||
|
Wir haben uns auf ein klares Modell mit drei spezialisierten Konstrukten für die Zustandsverwaltung geeinigt, das maximale Sicherheit bei gleichzeitig hoher Flexibilität und Verständlichkeit bietet.
|
||||||
|
|
||||||
|
### 1. Die drei Konstrukte
|
||||||
|
|
||||||
|
Die folgende Tabelle fasst die Eigenschaften der drei finalen Konstrukte zusammen:
|
||||||
|
|
||||||
|
| Konstrukt | Geltungsbereich | Veränderlichkeit | Threadsicherheit |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **`atom`** | Global / Geteilt | Veränderlich (atomar) | **Ja (Design-Ziel)** |
|
||||||
|
| **`var`** | Lokal (`do`-Block) | Veränderlich | Nein (by Design) |
|
||||||
|
| **`let`** | Lokal (`do`-Block) | **Unveränderlich** | Ja (da unveränderlich) |
|
||||||
|
|
||||||
|
### 2. Syntaktische Form
|
||||||
|
|
||||||
|
Alle drei Konstrukte folgen einer einheitlichen, einfachen Syntax für die Deklaration und optionale Initialisierung:
|
||||||
|
|
||||||
|
* `(atom symbol initial-value)`
|
||||||
|
* `(var symbol initial-value)`
|
||||||
|
* `(let symbol initial-value)`
|
||||||
|
|
||||||
|
### 3. Implikation für `(do)`
|
||||||
|
|
||||||
|
Der `(do)`-Block (`IBlockExpressionNode`) wird zum **zentralen und einzigen Mechanismus für die Erzeugung lexikalischer Geltungsbereiche** für lokalen Zustand (`var` und `let`). Jedes Vorkommen von `(do ...)` öffnet einen neuen Scope, der nach Abarbeitung des Blocks wieder zerstört wird. Dies ermöglicht ein intuitives, imperativ anmutendes Programmieren mit klar definierten Lebenszeiten für lokale Variablen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nächste Schritte (Todo)
|
||||||
|
|
||||||
|
1. **Parser anpassen:**
|
||||||
|
* Die Schlüsselwörter `def` und `let` werden durch `atom`, `var` und `let` ersetzt.
|
||||||
|
* Alle drei Konstrukte werden initial zum selben `IVariableDeclarationNode` geparst. Eine Unterscheidung, um welches Konstrukt es sich handelt, wird im Binder getroffen.
|
||||||
|
|
||||||
|
2. **Binder anpassen (Kernaufgabe):**
|
||||||
|
* **`do`-Scoping:** `VisitBlockExpression` wird so erweitert, dass es bei jedem Aufruf einen neuen Scope (`EnterScope`/`ExitScope`) verwaltet.
|
||||||
|
* **Symbol-Verfolgung:** Der Binder muss für jede deklarierte Variable speichern, ob sie via `atom`, `var` oder `let` erzeugt wurde.
|
||||||
|
* **Unveränderlichkeits-Prüfung:** Beim Besuch eines `(assign ...)`-Knotens (`VisitAssignment`) muss der Binder prüfen, ob die Zielvariable als `let` deklariert wurde. Wenn ja, wird ein **Compile-Fehler** ausgelöst.
|
||||||
|
* **Escape Analysis verfeinern:** Die Sicherheitsprüfung wird so angepasst, dass sie nur noch bei Closures greift, die eine **`var`-Variable** fangen. Closures, die `let`-Variablen fangen, sind immer sicher.
|
||||||
|
|
||||||
|
3. **RTL & Evaluator anpassen:**
|
||||||
|
* Die Laufzeitlogik für `def` wird zur Implementierung für `(atom ...)` und erzeugt einen atomaren Container.
|
||||||
|
* Die RTL wird um die Funktionen `deref`, `reset!` und `swap!` erweitert.
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
-----
|
||||||
|
|
||||||
|
### Projektplan: Atome und das "Builder/Snapshot"-Pattern
|
||||||
|
|
||||||
|
* **Datum:** 09. Oktober 2025
|
||||||
|
* **Uhrzeit:** 16:07
|
||||||
|
|
||||||
|
#### Motivation
|
||||||
|
|
||||||
|
Die bisherige Diskussion hat zwei grundlegende Anforderungen an die Zustandsverwaltung herausgearbeitet:
|
||||||
|
|
||||||
|
1. **Allgemeine Sicherheit:** Für generische Datenstrukturen (Maps, Vektoren) bieten persistente Datenstrukturen durch ihre Immutabilität und lock-freie Lesbarkeit die höchste Sicherheit und Flexibilität bei konkurrierendem Zugriff.
|
||||||
|
2. **Spezialisierte Performance:** Für bestimmte Anwendungsfälle (z.B. große, append-only Time-Series) ist die Performance einer In-Place-Mutation überlegen, aber das Teilen des Zustands zwischen Threads erfordert eine sichere Synchronisation.
|
||||||
|
|
||||||
|
Die Motivation ist daher, ein **standardisiertes, generisches Muster** zu definieren, das die rohe Performance einer mutierbaren "Arbeitskopie" mit der Sicherheit von unveränderlichen, lesbaren "Snapshots" für die konkurrierende Analyse kombiniert.
|
||||||
|
|
||||||
|
#### Ziel
|
||||||
|
|
||||||
|
1. Etablierung des **"Builder/Snapshot"-Patterns** als idiomatisches Kernkonzept der Sprache für den Umgang mit hoch-performantem, geteiltem, mutablem Zustand.
|
||||||
|
2. Definition von **generischen Basis-Interfaces** (`IBuilder`, `IImmutable`), die dieses Pattern im Typensystem verankern.
|
||||||
|
3. Nahtlose Integration dieses Patterns mit den bestehenden `atom`- (für geteilten Zustand) und `var`- (für lokalen Zustand) Konstrukten.
|
||||||
|
|
||||||
|
#### Ergebnis
|
||||||
|
|
||||||
|
Das Ergebnis ist ein klares, zweigleisiges Modell für die Zustandsverwaltung, das auf dem generischen "Builder/Snapshot"-Pattern aufbaut.
|
||||||
|
|
||||||
|
**1. Das generische Pattern: `IBuilder` / `IImmutable`**
|
||||||
|
|
||||||
|
Wir definieren zwei konzeptionelle Basis-Interfaces, die den Vertrag des Patterns abbilden:
|
||||||
|
|
||||||
|
* `IImmutable`: Ein **Marker-Interface**, das einen sicheren, unveränderlichen "Produkt"- oder Snapshot-Typ kennzeichnet.
|
||||||
|
* `IBuilder`: Ein Interface, das einen "Builder" kennzeichnet. Es definiert die Fähigkeit, ein `IImmutable`-Produkt zu erzeugen.
|
||||||
|
|
||||||
|
**2. Die Spezialisierung (Beispiel: `Series`-Interfaces)**
|
||||||
|
|
||||||
|
Konkrete Datenstrukturen wie die `Series` spezialisieren diese generischen Rollen.
|
||||||
|
|
||||||
|
```delphi
|
||||||
|
// --- Generische Basis-Interfaces ---
|
||||||
|
|
||||||
|
// Kennzeichnet ein sicheres, unveränderliches Lese-Objekt (Snapshot).
|
||||||
|
IImmutable = interface(IInterface)
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Kennzeichnet ein mutierbares Builder-Objekt, das Snapshots von sich erzeugen kann.
|
||||||
|
IBuilder = interface(IInterface)
|
||||||
|
function CreateSnapshot: IImmutable;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Spezialisierte Interfaces für die 'Series' ---
|
||||||
|
|
||||||
|
// ISeries ist die spezialisierte, lesbare Form von IImmutable.
|
||||||
|
ISeries = interface(IImmutable)
|
||||||
|
function GetCount: Int64;
|
||||||
|
function GetItems(Idx: Integer): TScalar;
|
||||||
|
property Count: Int64 read GetCount;
|
||||||
|
property Items[Idx: Integer]: TScalar read GetItems; default;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// IWriteableSeries ist der spezialisierte Builder.
|
||||||
|
// Wichtig: Er implementiert den IBuilder-Vertrag, indem er eine CreateSnapshot-Methode
|
||||||
|
// anbietet, die den spezialisierten Snapshot-Typ ISeries zurückgibt.
|
||||||
|
IWriteableSeries = interface(IBuilder)
|
||||||
|
function GetCount: Int64;
|
||||||
|
property Count: Int64 read GetCount;
|
||||||
|
procedure Add(const Item: TScalar.TValue; Lookback: Int64 = -1);
|
||||||
|
function CreateSnapshot: ISeries; // Covarianter Return-Type zum Basis-Interface
|
||||||
|
end;
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Die Anwendungsbeispiele in der Sprache**
|
||||||
|
|
||||||
|
Das Pattern wird auf zwei Arten verwendet, je nach Kontext (`atom` oder `var`). Die RTL-Funktionen arbeiten mit den generischen `IBuilder`- und `IImmutable`-Konzepten.
|
||||||
|
|
||||||
|
**Beispiel 1: Geteilter Zustand (via `atom`)**
|
||||||
|
|
||||||
|
Der `atom` schützt den `IBuilder` vor konkurrierenden Schreibzugriffen.
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(*
|
||||||
|
* Szenario: Ein Ticker-Modul (Thread A) empfängt Live-Daten,
|
||||||
|
* während eine UI (Thread B) die Daten zur Analyse anfordert.
|
||||||
|
*)
|
||||||
|
|
||||||
|
; Ein globaler 'atom' wird erstellt. Er hält die einzige Instanz des Builders.
|
||||||
|
(atom live-ticker (create-series-builder))
|
||||||
|
|
||||||
|
; -- Thread A (Produzent) --
|
||||||
|
; Modifiziert den Builder sicher über eine generische, synchronisierte RTL-Funktion,
|
||||||
|
; die auf jedem IBuilder in einem Atom funktioniert.
|
||||||
|
(builder-update! live-ticker series-add new-tick-value)
|
||||||
|
|
||||||
|
; -- Thread B (Konsument) --
|
||||||
|
; Fordert einen sicheren, unveränderlichen Snapshot an.
|
||||||
|
; Die generische 'snapshot'-Funktion ruft intern 'CreateSnapshot' auf.
|
||||||
|
(let chart-data (snapshot live-ticker))
|
||||||
|
|
||||||
|
; 'chart-data' ist nun eine sichere ISeries (IImmutable), die ohne Locks gelesen werden kann.
|
||||||
|
(calculate-indicators chart-data)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Beispiel 2: Lokaler Zustand (via `var`)**
|
||||||
|
|
||||||
|
Dies ist der "Maschinenraum"-Modus für maximale Single-Thread-Performance.
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(*
|
||||||
|
* Szenario: Innerhalb einer Funktion wird eine komplexe Datenreihe
|
||||||
|
* temporär aufgebaut, um ein einmaliges Ergebnis zu berechnen.
|
||||||
|
*)
|
||||||
|
(do
|
||||||
|
; Erzeuge einen lokalen, unsynchronisierten Builder.
|
||||||
|
(var local-builder (create-series-builder))
|
||||||
|
|
||||||
|
; Befülle den Builder direkt und ohne Lock-Overhead über typspezifische,
|
||||||
|
; unsynchronisierte RTL-Funktionen.
|
||||||
|
(repeat 1000000
|
||||||
|
(series-add! local-builder (get-next-value)))
|
||||||
|
|
||||||
|
; Erzeuge einen finalen Snapshot für die Weiterverarbeitung.
|
||||||
|
(let final-data (series-create-snapshot local-builder))
|
||||||
|
|
||||||
|
; Gib das Ergebnis der Analyse zurück.
|
||||||
|
(run-analysis-on final-data))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Todo
|
||||||
|
|
||||||
|
* [ ] Generische `IBuilder`- und `IImmutable`-Interfaces in Delphi definieren.
|
||||||
|
* [ ] Finale `ISeries`- und `IWriteableSeries`-Interfaces (als Spezialisierungen) implementieren.
|
||||||
|
* [ ] Eine konkrete `TSeries`-Klasse implementieren, die `IWriteableSeries` implementiert.
|
||||||
|
* [ ] Die generischen, synchronisierten RTL-Funktionen `(builder-update! ...)` und `(snapshot ...)` für `atom`s erstellen, die auf `IBuilder` operieren.
|
||||||
|
* [ ] Die spezifischen, unsynchronisierten RTL-Funktionen `(series-add! ...)` und `(series-create-snapshot ...)` für die `TSeries`-Implementierung erstellen.
|
||||||
|
* [ ] Unit-Tests für beide Anwendungsfälle schreiben.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
### Von "Guaranteed Safety" zu "Guided Safety"
|
||||||
|
|
||||||
|
Die Lösung ist nicht, die Sicherheitsanalyse abzuschaffen, sondern ihre Konsequenz zu ändern. Der Compiler sollte nicht als sturer Torwächter agieren, sondern als intelligenter Assistent, der den Nutzer aufklärt und ihm eine bewusste Entscheidung ermöglicht.
|
||||||
|
|
||||||
|
**Der neue Ansatz:**
|
||||||
|
1. Der Compiler **erkennt** das potenziell unsichere Muster (eine Closure fängt eine `var`-Variable und verlässt ihren Scope) genau wie bisher.
|
||||||
|
2. Statt eines harten **Fehlers** erzeugt er eine detaillierte **Warnung**.
|
||||||
|
3. Diese Warnung erklärt das Risiko präzise und für Laien verständlich: "Diese Funktion ist nicht threadsicher. Für isolierte Berechnungen wie Backtests ist das in Ordnung. Wenn Sie sie aber zwischen Threads teilen, kann es zu Fehlern kommen."
|
||||||
|
4. Die Warnung schlägt die Lösung vor: "Wenn Sie dies beabsichtigen, kennzeichnen Sie die Funktion explizit als nicht-threadsicher, indem Sie `fn` durch `fn!` ersetzen."
|
||||||
|
5. Der Nutzer ändert `fn` zu `fn!`. Die Warnung verschwindet. Der Nutzer hat gelernt, eine bewusste Entscheidung getroffen und kann weiterarbeiten, ohne seinen Code komplett umstrukturieren zu müssen.
|
||||||
|
|
||||||
|
Dies erreicht das Beste aus beiden Welten: Sicherheit wird nicht aufgegeben, sondern der Nutzer wird über die Risiken aufgeklärt und muss die Verantwortung explizit übernehmen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Ich fasse das Ergebnis in einem neuen Projektplan-Eintrag zusammen.
|
||||||
|
|
||||||
|
### Projektplan: "Guided Safety" und der `fn!`-Modifikator
|
||||||
|
|
||||||
|
* **Datum:** 09. Oktober 2025
|
||||||
|
* **Uhrzeit:** 17:47
|
||||||
|
|
||||||
|
#### Motivation
|
||||||
|
|
||||||
|
Die strikte "safe-by-design"-Garantie, die das "Escaping" von `var`-Variablen durch einen Compiler-Fehler verhindert, erweist sich für die Zielgruppe der programmiertechnisch unerfahrenen Domänen-Experten als zu restriktiv. Sie zwingt zur Umstrukturierung von Code, der im primären Anwendungsfall (isolierte Single-Thread-Berechnungen wie Backtests) sowohl korrekt als auch performant ist. Dies führt zu Frustration und mindert die Akzeptanz der Sprache.
|
||||||
|
|
||||||
|
#### Ziel
|
||||||
|
|
||||||
|
Das Sicherheitsmodell der Sprache wird von einer rigiden **"Guaranteed Safety"** zu einer benutzerfreundlichen **"Guided Safety"** weiterentwickelt. Der Compiler soll den Nutzer auf potenzielle Gefahren hinweisen, ihn aufklären und ihm ermöglichen, eine explizite, informierte Entscheidung zu treffen, anstatt ihn mit einem harten Fehler zu blockieren.
|
||||||
|
|
||||||
|
#### Ergebnis
|
||||||
|
|
||||||
|
1. **Compiler-Warnung statt Fehler:** Der Binder wird so angepasst, dass das Fangen einer `var`-Variable durch eine "escapende" Closure nicht mehr zu einem Compiler-Fehler, sondern zu einer detaillierten **Warnung** führt.
|
||||||
|
|
||||||
|
2. **Einführung von `fn!`:** Ein neuer Funktions-Deklarations-Syntax `(fn! ...)` wird eingeführt. Dieser Modifikator signalisiert dem Compiler: "Der Programmierer hat die Warnung zur Kenntnis genommen und deklariert diese Closure absichtlich als potenziell nicht-threadsicher." Das Vorhandensein von `fn!` unterdrückt die entsprechende Compiler-Warnung.
|
||||||
|
|
||||||
|
3. **Pädagogische Fehlermeldung:** Die Warnmeldung wird so formuliert, dass sie den Sachverhalt erklärt und direkt die Lösung (`fn!` verwenden) vorschlägt. Beispiel:
|
||||||
|
> **Warnung:** Die Funktion erfasst die veränderliche Variable 'sum', die außerhalb dieses Bereichs definiert wurde. Dies macht die Funktion nicht threadsicher. Für isolierte Berechnungen (z.B. in einem Backtest) ist dies unproblematisch und performant. Um zu bestätigen, dass dies beabsichtigt ist, ändern Sie `(fn ...)` zu `(fn! ...)`."
|
||||||
|
|
||||||
|
4. **Beibehaltung der sicheren Muster:** Die bestehenden, sicheren Muster (`atom` für geteilten Zustand, `var` mit zustandslosen Funktionen) bleiben die empfohlenen Standardlösungen. `fn!` dient als explizite, bewusste Abweichung für den gut verstandenen Performance-Fall.
|
||||||
|
|
||||||
|
#### Todo
|
||||||
|
|
||||||
|
* [ ] Die Logik im `Binder` anpassen, um eine Warnung anstelle eines Fehlers für "escapende `var`-Variablen" zu generieren.
|
||||||
|
* [ ] Den `Parser` um die Erkennung des `fn!`-Schlüsselworts erweitern.
|
||||||
|
* [ ] Die Binder-Logik erweitern, sodass `fn!` die Warnung unterdrückt.
|
||||||
|
* [ ] Die exakte Formulierung der neuen Warnmeldung implementieren.
|
||||||
|
* [ ] Die Dokumentation aktualisieren, um das "Guided Safety"-Konzept und die korrekte Verwendung von `fn`, `fn!` und `atom` zu erklären.
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
Die bisherige Diskussion hat sich um die Frage gedreht: "Was ist theoretisch sicher?". Du hast die Perspektive entscheidend auf die Frage gelenkt: "**Was ist praktisch nützlich und führt zu einer hohen Akzeptanz bei der Zielgruppe?**"
|
||||||
|
|
||||||
|
Ein System, das einen Domänen-Experten zwingt, ein funktionierendes, intuitives Muster in ein kompliziertes, für ihn abstraktes Muster umzubauen, nur um einen Compiler zufriedenzustellen, hat seinen Zweck verfehlt. Der Nutzer wird frustriert aufgeben.
|
||||||
|
|
||||||
|
Deine Schlussfolgerung ist daher richtig: Die "safe by design"-Garantie in ihrer strengsten Form (als harter Compiler-Fehler) ist für diesen Anwendungsfall ein Hindernis. Wir müssen das Design weiterentwickeln.
|
||||||
|
|
||||||
|
---
|
||||||
|
### Von "Guaranteed Safety" zu "Guided Safety"
|
||||||
|
|
||||||
|
Die Lösung ist nicht, die Sicherheitsanalyse abzuschaffen, sondern ihre Konsequenz zu ändern. Der Compiler sollte nicht als sturer Torwächter agieren, sondern als intelligenter Assistent, der den Nutzer aufklärt und ihm eine bewusste Entscheidung ermöglicht.
|
||||||
|
|
||||||
|
**Der neue Ansatz:**
|
||||||
|
1. Der Compiler **erkennt** das potenziell unsichere Muster (eine Closure fängt eine `var`-Variable und verlässt ihren Scope) genau wie bisher.
|
||||||
|
2. Statt eines harten **Fehlers** erzeugt er eine detaillierte **Warnung**.
|
||||||
|
3. Diese Warnung erklärt das Risiko präzise und für Laien verständlich: "Diese Funktion ist nicht threadsicher. Für isolierte Berechnungen wie Backtests ist das in Ordnung. Wenn Sie sie aber zwischen Threads teilen, kann es zu Fehlern kommen."
|
||||||
|
4. Die Warnung schlägt die Lösung vor: "Wenn Sie dies beabsichtigen, kennzeichnen Sie die Funktion explizit als nicht-threadsicher, indem Sie `fn` durch `fn!` ersetzen."
|
||||||
|
5. Der Nutzer ändert `fn` zu `fn!`. Die Warnung verschwindet. Der Nutzer hat gelernt, eine bewusste Entscheidung getroffen und kann weiterarbeiten, ohne seinen Code komplett umstrukturieren zu müssen.
|
||||||
|
|
||||||
|
Dies erreicht das Beste aus beiden Welten: Sicherheit wird nicht aufgegeben, sondern der Nutzer wird über die Risiken aufgeklärt und muss die Verantwortung explizit übernehmen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Ich fasse das Ergebnis in einem neuen Projektplan-Eintrag zusammen.
|
||||||
|
|
||||||
|
### Projektplan: "Guided Safety" und der `fn!`-Modifikator
|
||||||
|
|
||||||
|
* **Datum:** 09. Oktober 2025
|
||||||
|
* **Uhrzeit:** 17:47
|
||||||
|
|
||||||
|
#### Motivation
|
||||||
|
|
||||||
|
Die strikte "safe-by-design"-Garantie, die das "Escaping" von `var`-Variablen durch einen Compiler-Fehler verhindert, erweist sich für die Zielgruppe der programmiertechnisch unerfahrenen Domänen-Experten als zu restriktiv. Sie zwingt zur Umstrukturierung von Code, der im primären Anwendungsfall (isolierte Single-Thread-Berechnungen wie Backtests) sowohl korrekt als auch performant ist. Dies führt zu Frustration und mindert die Akzeptanz der Sprache.
|
||||||
|
|
||||||
|
#### Ziel
|
||||||
|
|
||||||
|
Das Sicherheitsmodell der Sprache wird von einer rigiden **"Guaranteed Safety"** zu einer benutzerfreundlichen **"Guided Safety"** weiterentwickelt. Der Compiler soll den Nutzer auf potenzielle Gefahren hinweisen, ihn aufklären und ihm ermöglichen, eine explizite, informierte Entscheidung zu treffen, anstatt ihn mit einem harten Fehler zu blockieren.
|
||||||
|
|
||||||
|
#### Ergebnis
|
||||||
|
|
||||||
|
1. **Compiler-Warnung statt Fehler:** Der Binder wird so angepasst, dass das Fangen einer `var`-Variable durch eine "escapende" Closure nicht mehr zu einem Compiler-Fehler, sondern zu einer detaillierten **Warnung** führt.
|
||||||
|
|
||||||
|
2. **Einführung von `fn!`:** Ein neuer Funktions-Deklarations-Syntax `(fn! ...)` wird eingeführt. Dieser Modifikator signalisiert dem Compiler: "Der Programmierer hat die Warnung zur Kenntnis genommen und deklariert diese Closure absichtlich als potenziell nicht-threadsicher." Das Vorhandensein von `fn!` unterdrückt die entsprechende Compiler-Warnung.
|
||||||
|
|
||||||
|
3. **Pädagogische Fehlermeldung:** Die Warnmeldung wird so formuliert, dass sie den Sachverhalt erklärt und direkt die Lösung (`fn!` verwenden) vorschlägt. Beispiel:
|
||||||
|
> **Warnung:** Die Funktion erfasst die veränderliche Variable 'sum', die außerhalb dieses Bereichs definiert wurde. Dies macht die Funktion nicht threadsicher. Für isolierte Berechnungen (z.B. in einem Backtest) ist dies unproblematisch und performant. Um zu bestätigen, dass dies beabsichtigt ist, ändern Sie `(fn ...)` zu `(fn! ...)`."
|
||||||
|
|
||||||
|
4. **Beibehaltung der sicheren Muster:** Die bestehenden, sicheren Muster (`atom` für geteilten Zustand, `var` mit zustandslosen Funktionen) bleiben die empfohlenen Standardlösungen. `fn!` dient als explizite, bewusste Abweichung für den gut verstandenen Performance-Fall.
|
||||||
|
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
> Jetzt weiß der Compiler, ob eine Closure unsafe ist. Kann er sonst noch was mit dieser Information anfangen?
|
||||||
|
|
||||||
|
|
||||||
|
Ja, absolut. Das ist eine hervorragende Frage, die den Kern von modernem Compiler-Design trifft. Die Information, ob eine Closure potenziell unsicher ist, ist weit mehr als nur ein Schalter zum Unterdrücken einer Warnung – es ist ein wertvolles **Metadatum**, eine semantische Information, die das gesamte Ökosystem nutzen kann.
|
||||||
|
|
||||||
|
Stell dir das `!` bei `fn!` wie einen **"Gefahrgut"-Aufkleber** auf einem Paket vor. Es sagt nicht nur dem Absender "Vorsicht", sondern informiert die gesamte Lieferkette (Compiler, Laufzeitumgebung, andere Entwickler) darüber, wie dieses Paket zu behandeln ist.
|
||||||
|
|
||||||
|
Hier sind konkrete, extrem nützliche Dinge, die der Compiler und die Laufzeitumgebung mit dieser Information tun können:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
### 1\. Laufzeit-Überprüfung im Debug-Modus 💥
|
||||||
|
|
||||||
|
Das ist der größte Gewinn. Wenn der Compiler weiß, dass eine `fn!`-Closure erzeugt wird, kann er speziellen Code für den Debug-Build generieren:
|
||||||
|
|
||||||
|
1. **Beim Erstellen der Closure:** Die Laufzeitumgebung speichert die `ThreadID` des erstellenden Threads direkt in der Closure-Struktur.
|
||||||
|
2. **Bei jedem Aufruf der Closure:** Die Laufzeitumgebung prüft: `if CurrentThreadID != CapturedThreadID then`.
|
||||||
|
3. **Wenn die IDs nicht übereinstimmen**, wird sofort eine **harte Exception** ausgelöst mit einer extrem klaren Fehlermeldung:
|
||||||
|
> "Laufzeitfehler: Die nicht-threadsichere Funktion, die in Thread A (ID: 1234) erstellt wurde, wurde fälschlicherweise von Thread B (ID: 5678) aufgerufen. Dies hätte zu einer Race Condition geführt."
|
||||||
|
|
||||||
|
**Der Vorteil ist gigantisch:** Ein unvorhersehbarer, schwer zu findender Heisenbug (eine Race Condition, die nur manchmal auftritt) wird in einen **100% reproduzierbaren, sofortigen Absturz** mit perfekter Fehlerbeschreibung umgewandelt. Allein dieses Feature macht `fn!` von einer "gefährlichen" zu einer extrem nützlichen, fehlertoleranten Abstraktion.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
### 2\. Erweiterte Statische Analyse (Linting) 🧐
|
||||||
|
|
||||||
|
Der Compiler (oder ein separates Linter-Tool) kann diese Information für intelligentere Code-Prüfungen nutzen. Er kann die "Gefahrgut-Pakete" verfolgen:
|
||||||
|
|
||||||
|
Angenommen, es gibt eine Funktion `(parallel-map ...)` in der Standardbibliothek, die dafür bekannt ist, ihre Arbeits-Funktion auf mehreren Threads auszuführen. Die Signatur dieser Funktion kann intern vermerken, dass sie nur threadsichere Closures akzeptiert.
|
||||||
|
|
||||||
|
Wenn ein Entwickler nun versucht, Folgendes zu schreiben:
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(let my-unsafe-sma (create-sma-unsafe 20)) ; Gibt eine fn!-Closure zurück
|
||||||
|
(parallel-map my-unsafe-sma my-big-data-list)
|
||||||
|
```
|
||||||
|
|
||||||
|
Kann der Compiler an dieser Stelle eine noch spezifischere und ernstere **Warnung oder sogar einen Fehler** ausgeben:
|
||||||
|
|
||||||
|
> "Typenkonflikt: `parallel-map` erwartet eine threadsichere Funktion, aber `my-unsafe-sma` wurde mit `fn!` als nicht-threadsicher deklariert. Dies führt garantiert zu Datenkorruption."
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
### 3\. Automatische API-Dokumentation 📖
|
||||||
|
|
||||||
|
Ein Dokumentations-Generator kann die `fn!`-Metadaten auslesen und automatisch Warnhinweise in die generierte HTML-Doku einfügen. Wenn jemand die Dokumentation für deine `CreateSMA`-Funktion liest, könnte dort stehen:
|
||||||
|
|
||||||
|
**`CreateSMA(len: Integer): Function`**
|
||||||
|
|
||||||
|
> Erzeugt eine Funktion zur Berechnung eines gleitenden Durchschnitts.
|
||||||
|
>
|
||||||
|
> ⚠️ **Thread-Sicherheit:** Diese Funktion gibt eine optimierte, nicht-threadsichere Funktion (`fn!`) zurück. Das Ergebnis darf unter keinen Umständen zwischen verschiedenen Threads geteilt werden.
|
||||||
|
|
||||||
|
Dadurch wird der API-Vertrag für jeden sofort ersichtlich, ohne dass man den Quellcode lesen muss.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
### Fazit
|
||||||
|
|
||||||
|
Die explizite Kennzeichnung mit `fn!` ist also kein Rückschritt in der Sicherheit. Im Gegenteil, es ist eine **semantische Anreicherung**. Sie verwandelt das Konzept von einer reinen Warnungs-Unterdrückung in einen expliziten **Vertrag**, den die gesamte Werkzeugkette – Compiler, Debugger, Linter, Doku-Generator – versteht und nutzen kann, um den Entwickler noch besser und intelligenter zu leiten.
|
||||||
Reference in New Issue
Block a user