diff --git a/ASTPlayground/ASTPlayground.dpr b/ASTPlayground/ASTPlayground.dpr
index 6309384..ba84f08 100644
--- a/ASTPlayground/ASTPlayground.dpr
+++ b/ASTPlayground/ASTPlayground.dpr
@@ -8,7 +8,9 @@ uses
Myc.Ast.Printer in '..\Src\AST\Myc.Ast.Printer.pas',
Myc.Ast.Nodes in '..\Src\AST\Myc.Ast.Nodes.pas',
Myc.Ast.Scope in '..\Src\AST\Myc.Ast.Scope.pas',
- Myc.Ast.Visualizer in 'Myc.Ast.Visualizer.pas';
+ Myc.Ast.Visualizer in 'Myc.Ast.Visualizer.pas',
+ Myc.Ast.ViewModel in '..\Src\AST\Myc.Ast.ViewModel.pas',
+ Myc.Ast.Persistence in '..\Src\AST\Myc.Ast.Persistence.pas';
{$R *.res}
diff --git a/ASTPlayground/ASTPlayground.dproj b/ASTPlayground/ASTPlayground.dproj
index ad98492..46a003b 100644
--- a/ASTPlayground/ASTPlayground.dproj
+++ b/ASTPlayground/ASTPlayground.dproj
@@ -140,6 +140,8 @@
+
+
Base
@@ -189,6 +191,18 @@
true
+
+
+ ASTPlayground.exe
+ true
+
+
+
+
+ ASTPlayground.rsm
+ true
+
+
ASTPlayground.exe
diff --git a/ASTPlayground/MainForm.fmx b/ASTPlayground/MainForm.fmx
index ccb8b3f..ddd8f1a 100644
--- a/ASTPlayground/MainForm.fmx
+++ b/ASTPlayground/MainForm.fmx
@@ -130,6 +130,22 @@ object Form1: TForm1
TabOrder = 15
Text = 'Debug'
end
+ object FromJSONButton: TButton
+ Position.X = 24.000000000000000000
+ Position.Y = 520.000000000000000000
+ TabOrder = 16
+ Text = 'From JSON'
+ TextSettings.Trimming = None
+ OnClick = FromJSONButtonClick
+ end
+ object ToJSONButton: TButton
+ Position.X = 24.000000000000000000
+ Position.Y = 550.000000000000000000
+ TabOrder = 17
+ Text = 'To JSON'
+ TextSettings.Trimming = None
+ OnClick = ToJSONButtonClick
+ end
end
object Panel2: TPanel
Align = Client
diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas
index 5d8d1b1..2d5ee62 100644
--- a/ASTPlayground/MainForm.pas
+++ b/ASTPlayground/MainForm.pas
@@ -27,6 +27,7 @@ uses
Myc.Ast,
Myc.Ast.Evaluator,
Myc.Ast.Printer,
+ Myc.Ast.Persistence,
FMX.Layouts,
FMX.Objects,
Myc.Ast.Scope; // Added for TExecutionScope
@@ -60,6 +61,8 @@ type
SeriesTestButton: TButton;
OHLCButton: TButton;
DebugBox: TCheckBox;
+ FromJSONButton: TButton;
+ ToJSONButton: TButton;
procedure ClearButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure CreateTriggerExampleButtonClick(Sender: TObject);
@@ -72,15 +75,17 @@ type
procedure SeriesTestButtonClick(Sender: TObject);
procedure Test1ButtonClick(Sender: TObject);
procedure Test2ButtonClick(Sender: TObject);
+ procedure FromJSONButtonClick(Sender: TObject);
+ procedure ToJSONButtonClick(Sender: TObject);
private
// Stores the last AST generated by Test1 or Test2 button clicks.
- FLastAst: IExpressionNode;
+ FLastAst: IAstNode;
FGScope: IExecutionScope;
FWorkspace: TAuraWorkspace;
function CreateVisitor(const AScope: IExecutionScope): IAstVisitor;
procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
// New helper function to encapsulate the Bind -> Evaluate pattern
- function ExecuteAst(const ANode: IExpressionNode; const AParentScope: IExecutionScope): TAstValue;
+ function ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TAstValue;
public
{ Public declarations }
end;
@@ -120,7 +125,7 @@ begin
Result := TEvaluatorVisitor.Create(AScope);
end;
-function TForm1.ExecuteAst(const ANode: IExpressionNode; const AParentScope: IExecutionScope): TAstValue;
+function TForm1.ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TAstValue;
var
scriptScope: IExecutionScope;
begin
@@ -144,7 +149,7 @@ end;
procedure TForm1.FibonacciButtonClick(Sender: TObject);
var
- root: IExpressionNode;
+ root: IAstNode;
result: TAstValue;
sw: TStopwatch;
begin
@@ -208,7 +213,7 @@ end;
procedure TForm1.RecursionButtonClick(Sender: TObject);
var
- root: IExpressionNode;
+ root: IAstNode;
result: TAstValue;
sw: TStopwatch;
begin
@@ -252,7 +257,7 @@ end;
procedure TForm1.SeriesTestButtonClick(Sender: TObject);
var
scope: IExecutionScope;
- ast, callAst: IExpressionNode;
+ ast, callAst: IAstNode;
resultValue: TAstValue;
series: TScalarRecordSeries;
recordDef: TScalarRecordDefinition;
@@ -308,7 +313,7 @@ end;
procedure TForm1.Test1ButtonClick(Sender: TObject);
var
- main, callAst: IExpressionNode;
+ main, callAst: IAstNode;
result: TAstValue;
sw: TStopwatch;
begin
@@ -342,7 +347,7 @@ end;
procedure TForm1.Test2ButtonClick(Sender: TObject);
var
- root: IExpressionNode;
+ root: IAstNode;
result: TAstValue;
sw: TStopwatch;
begin
@@ -584,7 +589,7 @@ var
procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject);
var
- blk: IExpressionNode;
+ blk: IAstNode;
begin
// This is a setup script, so its goal is to create and populate FGScope.
Memo1.Lines.Clear;
@@ -640,4 +645,58 @@ begin
Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [X.ToString]));
end;
+procedure TForm1.FromJSONButtonClick(Sender: TObject);
+begin
+ // Reads an AST JSON string from the memo and deserializes it into FLastAst.
+ if Memo1.Lines.Text.IsEmpty then
+ begin
+ Memo1.Lines.Text := 'Memo is empty. Paste an AST JSON structure here first.';
+ exit;
+ end;
+
+ try
+ Memo1.Lines.Insert(0, '--- Deserializing AST from JSON ---');
+ FLastAst := TAstProjectPersistence.JsonStringToAstNode(Memo1.Lines.Text);
+ if Assigned(FLastAst) then
+ begin
+ Memo1.Lines.Add('--- Deserialization successful. ---');
+ Memo1.Lines.Add('Middle-click on the right panel to visualize the new AST.');
+ end
+ else
+ begin
+ Memo1.Lines.Add('--- Deserialization failed. Invalid JSON or data structure. ---');
+ end;
+ except
+ on E: Exception do
+ begin
+ Memo1.Lines.Add('--- ERROR during deserialization ---');
+ Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
+ end;
+ end;
+end;
+
+procedure TForm1.ToJSONButtonClick(Sender: TObject);
+begin
+ // Serializes the current FLastAst to a JSON string and displays it in the memo.
+ Memo1.Lines.Clear;
+ Memo1.Lines.Add('--- Serializing AST to JSON ---');
+
+ if not Assigned(FLastAst) then
+ begin
+ Memo1.Lines.Add('No AST available to serialize. Generate one first.');
+ exit;
+ end;
+
+ try
+ Memo1.Lines.Text := TAstProjectPersistence.AstNodeToJsonString(FLastAst);
+ except
+ on E: Exception do
+ begin
+ Memo1.Lines.Clear;
+ Memo1.Lines.Add('--- ERROR during serialization ---');
+ Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
+ end;
+ end;
+end;
+
end.
diff --git a/ASTPlayground/Myc.Ast.Visualizer.pas b/ASTPlayground/Myc.Ast.Visualizer.pas
index 0d71c91..1ecad84 100644
--- a/ASTPlayground/Myc.Ast.Visualizer.pas
+++ b/ASTPlayground/Myc.Ast.Visualizer.pas
@@ -225,7 +225,7 @@ type
const ChildVisitorProc: TChildVisitorProc
): TAuraNode;
function VisitOperatorNode(
- const InputExpressions: TArray;
+ const InputExpressions: TArray;
const CreateParentProc: TCreateParentNodeProc;
Alignment: TVerticalAlignment = vaCenter
): TAuraNode;
@@ -1073,7 +1073,7 @@ end;
function TAstToAuraNodeVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue;
var
details: string;
- inputs: TArray;
+ inputs: TArray;
begin
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
begin
@@ -1235,7 +1235,7 @@ begin
false,
procedure(const Visitor: IAstVisitor)
var
- expression: IExpressionNode;
+ expression: IAstNode;
begin
for expression in Node.Expressions do
expression.Accept(Visitor);
@@ -1391,7 +1391,7 @@ end;
function TAstToAuraNodeVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
var
- inputExpressions: TArray;
+ inputExpressions: TArray;
begin
var details: String;
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
@@ -1665,13 +1665,13 @@ begin
end;
function TAstToAuraNodeVisitor.VisitOperatorNode(
- const InputExpressions: TArray;
+ const InputExpressions: TArray;
const CreateParentProc: TCreateParentNodeProc;
Alignment: TVerticalAlignment
): TAuraNode;
var
inputResults: TAuraNodeResultList;
- inputNode: IExpressionNode;
+ inputNode: IAstNode;
startPosition: TPointF;
endY, maxChildWidth: Single;
parentNode: TAuraNode;
diff --git a/Doc/Roadmap.md b/Doc/Roadmap.md
index b148b27..deadd0c 100644
--- a/Doc/Roadmap.md
+++ b/Doc/Roadmap.md
@@ -1,80 +1,68 @@
-### **Projektplan: Visueller AST-Editor für Handelsstrategien**
+### **Roadmap: Implementierung des Interaktiven Semantischen Editors**
-* **Datum:** 08.09.2025 11:38
-
-#### **Motivation**
-
-Ziel ist die Schaffung eines Systems, das es Fachexperten ohne Programmierkenntnisse ermöglicht, komplexe Handelsstrategien visuell zu entwerfen, zu debuggen und zu backtesten. Die traditionelle Hürde der textbasierten Programmierung wird durch einen rein visuellen, geführten Ansatz eliminiert, der Fehler von vornherein vermeidet. Eine Kernanforderung ist die robuste Schnittstelle für den Import und Export von Logik aus externen Quellen, insbesondere LLMs.
-
-#### **Ziel**
-
-Die Entwicklung einer dualen Systemarchitektur, die eine intuitive, fehlerresistente Entwicklungsumgebung von einer hochperformanten Backtesting-Engine trennt. Die Architektur muss eine strikte Trennung zwischen dem logischen Modell (AST) und dem visuellen Zustand (UI-Metadaten) gewährleisten, um eine hohe Datenintegrität bei internen und externen Bearbeitungen sicherzustellen und eine nahtlose Persistenz von Projekten zu ermöglichen.
-
-#### **Ergebnis: Architekturentwurf**
-
-Der Editor basiert auf einer verfeinerten Model-View-ViewModel-Architektur, die maximale Robustheit und Flexibilität gewährleistet.
-
-1. **Das Model: Der High-Level AST (HAST)**
- * Der IAstNode-Baum ist die alleinige "Source of Truth" und enthält ausschließlich die reine, zustandslose Programmlogik. Diese Datenreinheit ist die Voraussetzung für die einfache Serialisierung und die Interaktion mit externen Systemen.
-2. **Der Vermittler: Die ViewModel-Schicht**
- * Ein parallel zum HAST existierender Baum aus TVisualNodeViewModel-Objekten dient als Brücke zur UI.
- * Jedes ViewModel erhält eine **stabile, permanente ID** (TViewModelID), die es unabhängig von seiner Position im Baum macht. Diese ID ist der Schlüssel zur Lösung für stabile Bearbeitungen und Metadaten.
-3. **Die Ansicht: Deterministisches Layout mit Overrides**
- * Die visuelle Darstellung (TAuraNode-Controls) wird durch einen deterministischen Algorithmus aus dem ViewModel-Baum generiert (View \= f(ViewModel)).
- * Manuelle Layout-Änderungen durch den Benutzer werden als "Overrides" in einer separaten Metadaten-Struktur gespeichert und über die stabile TViewModelID zugeordnet.
-4. **Zweifaches Metadaten-System**
- * **Logische Metadaten** (z.B. Farbkodierung) werden an den IAstNode gekoppelt und gelten für alle seine visuellen Instanzen.
- * **Visuelle Instanz-Metadaten** (z.B. Position, IsCollapsed) werden an die stabile TViewModelID gekoppelt und gelten nur für eine spezifische Instanz.
-5. **Duale Ausführungs-Engines & Serialisierung**
- * Ein **HAST-Interpreter** arbeitet im Debug-Modus direkt auf dem HAST für eine reichhaltige UI-Erfahrung.
- * Ein **CAST-Evaluator** arbeitet im Backtest-Modus auf einer optimierten Repräsentation (Core AST) für maximale Performance.
- * Ein **duales Clipboard/Dateiformat** serialisiert entweder nur den reinen HAST (für LLMs) oder den HAST inklusive aller Metadaten (für Projekte).
+**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.
---
-### **TODO: Priorisierte Roadmap**
+#### **Phase 1: Das Fundament – Datenmodell & Persistenz**
-#### **Phase 1: Minimales Architektur-Fundament**
+**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.
-* **Ziel:** Die technische Basis für alle weiteren Schritte schaffen. Dieser Schritt ist die Voraussetzung für die Serialisierung.
-* **Schritte:**
- 1. **ViewModel-Schicht implementieren:** Die TVisualNodeViewModel-Klasse mit der stabilen TViewModelID erstellen.
- 2. **Metadaten-Strukturen aufbauen:** Die beiden Dictionaries für TLogicalMetadata (gekoppelt an IAstNode) und TVisualInstanceMetadata (gekoppelt an TViewModelID) definieren und implementieren.
- 3. **Visitor refaktorisieren:** Den TAstToAuraNodeVisitor so umbauen, dass er den ViewModel-Baum erzeugt und dabei die stabilen IDs vergibt.
- 4. **Layout-Engine implementieren:** Den deterministischen Layout-Algorithmus umsetzen, der die Basis-Positionierung vornimmt. Die Engine muss bereits in der Lage sein, Positions-Overrides aus den TVisualInstanceMetadata zu berücksichtigen.
+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.
-#### **Phase 2: Kern-Feature – Persistenz & Interoperabilität**
+**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.**
-* **Ziel:** Das System schnellstmöglich nutzbar machen, indem Projekte gespeichert/geladen und Logik extern ausgetauscht werden kann.
-* **Schritte:**
- 1. **Duale JSON-Serialisierung entwickeln:**
- * **Routine A (Projekt):** Serialisiert den HAST und beide Metadaten-Systeme.
- * **Routine B (Logik-Clipboard):** Serialisiert ausschließlich den reinen HAST.
- 2. **Deserialisierung implementieren:**
- * **"Projekt laden":** Stellt den HAST, den ViewModel-Baum und alle Metadaten aus der Projektdatei vollständig wieder her.
- * **"Logik einfügen":** Eine pragmatische erste Version, die einen reinen HAST-Teilbaum deserialisiert, dafür neue ViewModels mit neuen IDs erzeugt und ihn von der Layout-Engine automatisch positionieren lässt.
- 3. **UI-Anbindung:** Die Aktionen "Projekt speichern", "Projekt laden", "Logik als JSON kopieren" und "Logik aus JSON einfügen" in der UI implementieren.
+---
-#### **Phase 3: Interaktivität & Benutzerführung**
+#### **Phase 2: Die Flexible Visualisierungs-Engine**
-* **Ziel:** Den Editor um die volle, geführte Bearbeitungsfunktionalität erweitern.
-* **Schritte:**
- 1. **Command Pattern umsetzen:** IEditorCommand-Schnittstelle und TCommandManager für Undo/Redo erstellen.
- 2. **Socket-basierte Controller-Logik entwickeln:** Die Interaktion implementieren, bei der Klicks auf Input-Sockets kontextsensitive Menüs mit validen Optionen öffnen. Jede Aktion wird über das Command-System abgewickelt.
- 3. **Refactoring-Funktionen implementieren:** Visuelle Operationen wie "Zu Funktion zusammenfassen" umsetzen.
+**Ziel:** Die in Phase 1 definierten Datenstrukturen sichtbar machen und den Wechsel zwischen syntaktischer und semantischer Darstellung ermöglichen.
-#### **Phase 4: Ausführung & Debugging**
+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\, 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.
-* **Ziel:** Den im Editor erstellten HAST interaktiv ausführbar und debuggbar machen.
-* **Schritte:**
- 1. Den **HAST-Interpreter** (Debugger) implementieren oder ausbauen.
- 2. Den Interpreter tief mit der UI und dem ViewModel-Baum koppeln, um Debugging-Features zu realisieren (visuelles Hervorheben, Breakpoints, Step-Logik etc.).
+**Ergebnis von Phase 2:** Ein interaktiver Viewer. Projekte können geladen, in beiden Modi (syntaktisch/semantisch) dargestellt und per Knoten umgeschaltet werden.
-#### **Phase 5: Performance & Optimierung**
+---
-* **Ziel:** Die Ausführung von Strategien für die Massen-Datenverarbeitung optimieren und die UX verfeinern.
-* **Schritte:**
- 1. Die **CAST-Struktur** (Core AST) definieren.
- 2. Den **HAST-zu-CAST-Expander** (Compiler/Visitor) entwickeln.
- 3. Den "headless" **CAST-Evaluator** (Backtester) implementieren.
- 4. Den **"Smart Paste"-Algorithmus** (Diff & Merge) als finale Verfeinerung für das Einfügen von Logik nachrüsten.
\ No newline at end of file
+#### **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.
\ No newline at end of file
diff --git a/Doc/Visueller Editor für Handelsstrategien (Revision 1).md b/Doc/Visueller Editor für Handelsstrategien (Revision 1).md
new file mode 100644
index 0000000..6702f96
--- /dev/null
+++ b/Doc/Visueller Editor für Handelsstrategien (Revision 1).md
@@ -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\), 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.
+
diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas
index 678e0d3..15b8a7d 100644
--- a/Src/AST/Myc.Ast.Evaluator.pas
+++ b/Src/AST/Myc.Ast.Evaluator.pas
@@ -90,11 +90,11 @@ type
TClosureValue = class(TInterfacedObject, TAstValue.IClosure)
private
- FBody: IExpressionNode;
+ FBody: IAstNode;
FClosureScope: IExecutionScope;
FLambdaNode: ILambdaExpressionNode;
FUpvalues: TArray;
- function GetBody: IExpressionNode;
+ function GetBody: IAstNode;
function GetParameters: TArray;
function GetClosureScope: IExecutionScope;
function GetUpvalues: TArray;
@@ -113,7 +113,7 @@ type
TNativeClosure = class(TInterfacedObject, TAstValue.IClosure)
private
FMethod: TNativeFunction;
- function GetBody: IExpressionNode;
+ function GetBody: IAstNode;
function GetParameters: TArray;
function GetClosureScope: IExecutionScope;
function GetUpvalues: TArray;
@@ -167,7 +167,7 @@ begin
FUpvalues := AUpvalues; // Store the captured cells
end;
-function TClosureValue.GetBody: IExpressionNode;
+function TClosureValue.GetBody: IAstNode;
begin
Result := FBody;
end;
@@ -195,7 +195,7 @@ begin
FMethod := AMethod;
end;
-function TNativeClosure.GetBody: IExpressionNode;
+function TNativeClosure.GetBody: IAstNode;
begin
Result := nil;
end;
@@ -639,7 +639,7 @@ end;
function TEvaluatorVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
var
- expression: IExpressionNode;
+ expression: IAstNode;
begin
// The result of a block is the result of its last expression.
Result := TAstValue.Void;
diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas
index ba5dd21..da2becc 100644
--- a/Src/AST/Myc.Ast.Nodes.pas
+++ b/Src/AST/Myc.Ast.Nodes.pas
@@ -27,7 +27,6 @@ type
// --- Forward Declarations to break cycles ---
IAstVisitor = interface;
IAstNode = interface;
- IExpressionNode = interface;
IIdentifierNode = interface;
IConstantNode = interface;
IBinaryExpressionNode = interface;
@@ -54,12 +53,12 @@ type
type
IClosure = interface
{$region 'private'}
- function GetBody: IExpressionNode;
+ function GetBody: IAstNode;
function GetClosureScope: IExecutionScope;
function GetParameters: TArray;
function GetUpvalues: TArray;
{$endregion}
- property Body: IExpressionNode read GetBody;
+ property Body: IAstNode read GetBody;
property ClosureScope: IExecutionScope read GetClosureScope;
property Parameters: TArray read GetParameters;
property Upvalues: TArray read GetUpvalues;
@@ -170,17 +169,14 @@ type
function Accept(const Visitor: IAstVisitor): TAstValue;
end;
- IExpressionNode = interface(IAstNode)
- end;
-
- IConstantNode = interface(IExpressionNode)
+ IConstantNode = interface(IAstNode)
{$region 'private'}
function GetValue: TScalar;
{$endregion}
property Value: TScalar read GetValue;
end;
- IIdentifierNode = interface(IExpressionNode)
+ IIdentifierNode = interface(IAstNode)
{$region 'private'}
function GetIsResolved: Boolean;
function GetAddress: TResolvedAddress;
@@ -191,119 +187,119 @@ type
property Address: TResolvedAddress read GetAddress;
end;
- IBinaryExpressionNode = interface(IExpressionNode)
+ IBinaryExpressionNode = interface(IAstNode)
{$region 'private'}
- function GetLeft: IExpressionNode;
+ function GetLeft: IAstNode;
function GetOperator: TBinaryOperator;
- function GetRight: IExpressionNode;
+ function GetRight: IAstNode;
{$endregion}
- property Left: IExpressionNode read GetLeft;
+ property Left: IAstNode read GetLeft;
property Operator: TBinaryOperator read GetOperator;
- property Right: IExpressionNode read GetRight;
+ property Right: IAstNode read GetRight;
end;
- IUnaryExpressionNode = interface(IExpressionNode)
+ IUnaryExpressionNode = interface(IAstNode)
{$region 'private'}
function GetOperator: TUnaryOperator;
- function GetRight: IExpressionNode;
+ function GetRight: IAstNode;
{$endregion}
property Operator: TUnaryOperator read GetOperator;
- property Right: IExpressionNode read GetRight;
+ property Right: IAstNode read GetRight;
end;
// Represents an if-statement for control flow.
- IIfExpressionNode = interface(IExpressionNode)
+ IIfExpressionNode = interface(IAstNode)
{$region 'private'}
- function GetCondition: IExpressionNode;
- function GetThenBranch: IExpressionNode;
- function GetElseBranch: IExpressionNode;
+ function GetCondition: IAstNode;
+ function GetThenBranch: IAstNode;
+ function GetElseBranch: IAstNode;
{$endregion}
- property Condition: IExpressionNode read GetCondition;
- property ThenBranch: IExpressionNode read GetThenBranch;
- property ElseBranch: IExpressionNode read GetElseBranch;
+ property Condition: IAstNode read GetCondition;
+ property ThenBranch: IAstNode read GetThenBranch;
+ property ElseBranch: IAstNode read GetElseBranch;
end;
// Represents a ternary expression (e.g., condition ? true_expr : false_expr) for value selection.
- ITernaryExpressionNode = interface(IExpressionNode)
+ ITernaryExpressionNode = interface(IAstNode)
{$region 'private'}
- function GetCondition: IExpressionNode;
- function GetThenBranch: IExpressionNode;
- function GetElseBranch: IExpressionNode;
+ function GetCondition: IAstNode;
+ function GetThenBranch: IAstNode;
+ function GetElseBranch: IAstNode;
{$endregion}
- property Condition: IExpressionNode read GetCondition;
- property ThenBranch: IExpressionNode read GetThenBranch;
- property ElseBranch: IExpressionNode read GetElseBranch;
+ property Condition: IAstNode read GetCondition;
+ property ThenBranch: IAstNode read GetThenBranch;
+ property ElseBranch: IAstNode read GetElseBranch;
end;
- ILambdaExpressionNode = interface(IExpressionNode)
+ ILambdaExpressionNode = interface(IAstNode)
{$region 'private'}
function GetParameters: TArray;
- function GetBody: IExpressionNode;
+ function GetBody: IAstNode;
function GetScopeDescriptor: IScopeDescriptor;
function GetUpvalues: TArray;
{$endregion}
property Parameters: TArray read GetParameters;
- property Body: IExpressionNode read GetBody;
+ property Body: IAstNode read GetBody;
property ScopeDescriptor: IScopeDescriptor read GetScopeDescriptor;
property Upvalues: TArray read GetUpvalues;
end;
- IFunctionCallNode = interface(IExpressionNode)
+ IFunctionCallNode = interface(IAstNode)
{$region 'private'}
- function GetCallee: IExpressionNode;
- function GetArguments: TList;
+ function GetCallee: IAstNode;
+ function GetArguments: TList;
{$endregion}
- property Callee: IExpressionNode read GetCallee;
- property Arguments: TList read GetArguments;
+ property Callee: IAstNode read GetCallee;
+ property Arguments: TList read GetArguments;
end;
- IBlockExpressionNode = interface(IExpressionNode)
+ IBlockExpressionNode = interface(IAstNode)
{$region 'private'}
- function GetExpressions: TList;
+ function GetExpressions: TList;
{$endregion}
- property Expressions: TList read GetExpressions;
+ property Expressions: TList read GetExpressions;
end;
- IVariableDeclarationNode = interface(IExpressionNode)
+ IVariableDeclarationNode = interface(IAstNode)
{$region 'private'}
function GetIdentifier: IIdentifierNode;
- function GetInitializer: IExpressionNode;
+ function GetInitializer: IAstNode;
{$endregion}
property Identifier: IIdentifierNode read GetIdentifier;
- property Initializer: IExpressionNode read GetInitializer;
+ property Initializer: IAstNode read GetInitializer;
end;
- IAssignmentNode = interface(IExpressionNode)
+ IAssignmentNode = interface(IAstNode)
{$region 'private'}
function GetIdentifier: IIdentifierNode;
- function GetValue: IExpressionNode;
+ function GetValue: IAstNode;
{$endregion}
property Identifier: IIdentifierNode read GetIdentifier;
- property Value: IExpressionNode read GetValue;
+ property Value: IAstNode read GetValue;
end;
// Represents an index access expression (e.g., series[index]).
- IIndexerNode = interface(IExpressionNode)
+ IIndexerNode = interface(IAstNode)
{$region 'private'}
- function GetBase: IExpressionNode;
- function GetIndex: IExpressionNode;
+ function GetBase: IAstNode;
+ function GetIndex: IAstNode;
{$endregion}
- property Base: IExpressionNode read GetBase;
- property Index: IExpressionNode read GetIndex;
+ property Base: IAstNode read GetBase;
+ property Index: IAstNode read GetIndex;
end;
// Represents a member access expression (e.g., series.field or record.field).
- IMemberAccessNode = interface(IExpressionNode)
+ IMemberAccessNode = interface(IAstNode)
{$region 'private'}
- function GetBase: IExpressionNode;
+ function GetBase: IAstNode;
function GetMember: IIdentifierNode;
{$endregion}
- property Base: IExpressionNode read GetBase;
+ property Base: IAstNode read GetBase;
property Member: IIdentifierNode read GetMember;
end;
// Represents creating a new, empty series (e.g., new series(int)).
- ICreateSeriesNode = interface(IExpressionNode)
+ ICreateSeriesNode = interface(IAstNode)
{$region 'private'}
function GetDefinition: String;
{$endregion}
@@ -311,18 +307,18 @@ type
end;
// Represents adding a value to a series (e.g., my_series.add(value, 100)).
- IAddSeriesItemNode = interface(IExpressionNode)
+ IAddSeriesItemNode = interface(IAstNode)
{$region 'private'}
function GetSeries: IIdentifierNode;
- function GetValue: IExpressionNode;
- function GetLookback: IExpressionNode;
+ function GetValue: IAstNode;
+ function GetLookback: IAstNode;
{$endregion}
property Series: IIdentifierNode read GetSeries;
- property Value: IExpressionNode read GetValue;
- property Lookback: IExpressionNode read GetLookback;
+ property Value: IAstNode read GetValue;
+ property Lookback: IAstNode read GetLookback;
end;
- ISeriesLengthNode = interface(IExpressionNode)
+ ISeriesLengthNode = interface(IAstNode)
{$region 'private'}
function GetSeries: IIdentifierNode;
{$endregion}
diff --git a/Src/AST/Myc.Ast.Persistence.pas b/Src/AST/Myc.Ast.Persistence.pas
new file mode 100644
index 0000000..d4eb06e
--- /dev/null
+++ b/Src/AST/Myc.Ast.Persistence.pas
@@ -0,0 +1,1110 @@
+unit Myc.Ast.Persistence;
+
+interface
+
+uses
+ System.SysUtils,
+ System.Json,
+ System.Generics.Collections,
+ Myc.Data.Scalar,
+ Myc.Ast.Nodes,
+ Myc.Ast.ViewModel;
+
+type
+ // Manages the serialization and deserialization of the entire editor state.
+ TAstProjectPersistence = class
+ private
+ type
+ // This helper visitor acts as a "Builder".
+ // It builds the JSON result internally on a stack instead of using the return value.
+ TAstToJsonVisitor = class(TInterfacedObject, IAstVisitor)
+ private
+ FIdMap: TDictionary;
+ // Tracks nodes that have already been fully serialized to prevent infinite recursion on cycles.
+ FSerializedNodes: TDictionary;
+ FNextId: Integer;
+ FResultStack: TStack;
+ function GetOrCreateNodeId(const Node: IAstNode): Integer;
+ function ScalarToJson(const AValue: TScalar): TJSONObject;
+ public
+ constructor Create(AIdMap: TDictionary);
+ destructor Destroy; override;
+
+ function GetResult: TJSONObject;
+
+ { IAstVisitor }
+ function VisitConstant(const Node: IConstantNode): TAstValue;
+ function VisitIdentifier(const Node: IIdentifierNode): TAstValue;
+ function VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
+ function VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
+ function VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
+ function VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
+ function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
+ function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
+ function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
+ function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
+ function VisitAssignment(const Node: IAssignmentNode): TAstValue;
+ function VisitIndexer(const Node: IIndexerNode): TAstValue;
+ function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue;
+ function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue;
+ function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue;
+ function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue;
+ end;
+
+ function ScalarFromJson(AObject: TJSONObject): TScalar;
+ function DeserializeNode(AObject: TJSONObject; ANodeIdMap: TDictionary): IAstNode;
+
+ public
+ // Saves the complete state to a JSON file.
+ procedure SaveToFile(
+ const AFileName: string;
+ const ARootNode: IAstNode;
+ const ALogicalMetadata: TDictionary;
+ const AInstanceMetadata: TDictionary
+ );
+
+ // Loads the complete state from a JSON file.
+ procedure LoadFromFile(
+ const AFileName: string;
+ out ARootNode: IAstNode;
+ out ALogicalMetadata: TDictionary;
+ out AInstanceMetadata: TDictionary
+ );
+
+ class function AstNodeToJsonString(const ANode: IAstNode): string; static;
+ class function JsonStringToAstNode(const AJsonString: string): IAstNode; static;
+ end;
+
+implementation
+
+uses
+ System.Classes,
+ System.Types,
+ System.Variants,
+ System.Rtti,
+ System.TypInfo,
+ System.IOUtils,
+ System.DateUtils,
+ System.StrUtils,
+ Myc.Data.Decimal,
+ Myc.Ast;
+
+{ TAstProjectPersistence }
+
+procedure TAstProjectPersistence.SaveToFile(
+ const AFileName: string;
+ const ARootNode: IAstNode;
+ const ALogicalMetadata: TDictionary;
+ const AInstanceMetadata: TDictionary
+);
+var
+ rootJson, astJson, logicalMetaJson, instanceMetaJson: TJSONObject;
+ idMap: TDictionary;
+ visitor: TAstToJsonVisitor;
+ pair: TPair;
+ metaPair: TPair;
+ metaValueJson: TJSONObject;
+begin
+ rootJson := TJSONObject.Create;
+ try
+ idMap := TDictionary.Create;
+ visitor := TAstToJsonVisitor.Create(idMap);
+ try
+ ARootNode.Accept(visitor);
+ astJson := visitor.GetResult;
+ rootJson.AddPair('ast', astJson);
+ finally
+ idMap.Free;
+ visitor.Free;
+ end;
+
+ logicalMetaJson := TJSONObject.Create;
+ for pair in ALogicalMetadata do
+ begin
+ var nodeIdStr := idMap[pair.Key].ToString;
+ metaValueJson := TJSONObject.Create;
+ metaValueJson.AddPair('NodeType', TJSONString.Create(GetEnumName(TypeInfo(TAstNodeType), Ord(pair.Value.NodeType))));
+ metaValueJson.AddPair('Description', TJSONString.Create(pair.Value.Description));
+ logicalMetaJson.AddPair(nodeIdStr, metaValueJson);
+ end;
+ rootJson.AddPair('logicalMetadata', logicalMetaJson);
+
+ instanceMetaJson := TJSONObject.Create;
+ for metaPair in AInstanceMetadata do
+ begin
+ var idStr := metaPair.Key.ToString;
+ metaValueJson := TJSONObject.Create;
+ metaValueJson.AddPair('PositionX', TJSONNumber.Create(metaPair.Value.PositionOverride.X));
+ metaValueJson.AddPair('PositionY', TJSONNumber.Create(metaPair.Value.PositionOverride.Y));
+ metaValueJson.AddPair('IsCollapsed', TJSONBool.Create(metaPair.Value.IsCollapsed));
+ var modeStr := GetEnumName(TypeInfo(TVisualizationMode), Ord(metaPair.Value.VisualizationMode));
+ metaValueJson.AddPair('VisualizationMode', TJSONString.Create(modeStr));
+ instanceMetaJson.AddPair(idStr, metaValueJson);
+ end;
+ rootJson.AddPair('instanceMetadata', instanceMetaJson);
+
+ TFile.WriteAllText(AFileName, rootJson.ToJSON);
+ finally
+ rootJson.Free;
+ end;
+end;
+
+procedure TAstProjectPersistence.LoadFromFile(
+ const AFileName: string;
+ out ARootNode: IAstNode;
+ out ALogicalMetadata: TDictionary;
+ out AInstanceMetadata: TDictionary
+);
+var
+ rootJson, astJson, logicalMetaJson, instanceMetaJson: TJSONObject;
+ jsonValue: TJSONValue;
+ nodeIdMap: TDictionary;
+ pair: TJSONPair;
+ metaPair: TJSONPair;
+ metaData: TVisualInstanceMetadata;
+begin
+ ARootNode := nil;
+ ALogicalMetadata := TDictionary.Create;
+ AInstanceMetadata := TDictionary.Create;
+
+ jsonValue := TJSONObject.ParseJSONValue(TFile.ReadAllText(AFileName));
+ if (not Assigned(jsonValue)) or (not (jsonValue is TJSONObject)) then
+ begin
+ exit;
+ end;
+
+ rootJson := jsonValue as TJSONObject;
+ try
+ astJson := rootJson.GetValue('ast');
+ if Assigned(astJson) then
+ begin
+ nodeIdMap := TDictionary.Create;
+ try
+ ARootNode := DeserializeNode(astJson, nodeIdMap);
+ if not Assigned(ARootNode) then
+ begin
+ exit;
+ end;
+
+ logicalMetaJson := rootJson.GetValue('logicalMetadata');
+ if Assigned(logicalMetaJson) then
+ begin
+ for pair in logicalMetaJson do
+ begin
+ var tempId := StrToInt(pair.JsonString.Value);
+ if nodeIdMap.ContainsKey(tempId) then
+ begin
+ var node := nodeIdMap[tempId];
+ var meta: TLogicalMetadata;
+ var metaJson := pair.JSONValue as TJSONObject;
+ meta.NodeType := TAstNodeType(GetEnumValue(TypeInfo(TAstNodeType), metaJson.GetValue('NodeType')));
+ meta.Description := metaJson.GetValue('Description');
+ ALogicalMetadata.Add(node, meta);
+ end;
+ end;
+ end;
+ finally
+ nodeIdMap.Free;
+ end;
+ end;
+
+ instanceMetaJson := rootJson.GetValue('instanceMetadata');
+ if Assigned(instanceMetaJson) then
+ begin
+ for metaPair in instanceMetaJson do
+ begin
+ var vmId := StrToInt64(metaPair.JsonString.Value);
+ var metaJson := metaPair.JsonValue as TJSONObject;
+
+ metaData.PositionOverride.X := metaJson.GetValue('PositionX');
+ metaData.PositionOverride.Y := metaJson.GetValue('PositionY');
+ metaData.IsCollapsed := metaJson.GetValue('IsCollapsed');
+ var modeStr := metaJson.GetValue('VisualizationMode');
+ var modeInt := GetEnumValue(TypeInfo(TVisualizationMode), modeStr);
+ metaData.VisualizationMode := TVisualizationMode(modeInt);
+ AInstanceMetadata.Add(vmId, metaData);
+ end;
+ end;
+
+ finally
+ rootJson.Free;
+ end;
+end;
+
+function TAstProjectPersistence.DeserializeNode(AObject: TJSONObject; ANodeIdMap: TDictionary): IAstNode;
+var
+ nodeTypeStr: string;
+ tempId: Integer;
+ i: Integer;
+begin
+ Result := nil;
+ if not Assigned(AObject) then
+ begin
+ exit;
+ end;
+
+ // Handle reference nodes to break cycles during deserialization
+ if AObject.GetValue('ref') <> nil then
+ begin
+ var refId := AObject.GetValue('ref');
+ if not ANodeIdMap.TryGetValue(refId, Result) then
+ // A forward reference indicates a cycle that cannot be resolved with the current immutable AST node creation.
+ // The referenced node has not been fully constructed yet.
+ raise ENotImplemented.CreateFmt(
+ 'Forward reference to node ID %d encountered. This is not supported by the current deserialization logic.',
+ [refId]);
+ exit;
+ end;
+
+ nodeTypeStr := AObject.GetValue('type');
+ tempId := AObject.GetValue('id');
+
+ var nodeType := TAstNodeType(GetEnumValue(TypeInfo(TAstNodeType), 'ant' + nodeTypeStr));
+ case nodeType of
+ antConstant: Result := TAst.Constant(ScalarFromJson(AObject.GetValue('value')));
+ antIdentifier: Result := TAst.Identifier(AObject.GetValue('name'));
+ antBinaryExpression:
+ Result :=
+ TAst.BinaryExpr(
+ DeserializeNode(AObject.GetValue('left'), ANodeIdMap),
+ TBinaryOperator(GetEnumValue(TypeInfo(TBinaryOperator), 'bo' + AObject.GetValue('operator'))),
+ DeserializeNode(AObject.GetValue('right'), ANodeIdMap)
+ );
+ antUnaryExpression:
+ Result :=
+ TAst.UnaryExpr(
+ TUnaryOperator(GetEnumValue(TypeInfo(TUnaryOperator), 'uo' + AObject.GetValue('operator'))),
+ DeserializeNode(AObject.GetValue('right'), ANodeIdMap)
+ );
+ antIfExpression:
+ begin
+ var elseBranch: IAstNode := nil;
+ if AObject.GetValue('elseBranch') <> nil then
+ begin
+ elseBranch := DeserializeNode(AObject.GetValue('elseBranch'), ANodeIdMap);
+ end;
+ Result :=
+ TAst.IfExpr(
+ DeserializeNode(AObject.GetValue('condition'), ANodeIdMap),
+ DeserializeNode(AObject.GetValue('thenBranch'), ANodeIdMap),
+ elseBranch
+ );
+ end;
+ antTernaryExpression:
+ Result :=
+ TAst.TernaryExpr(
+ DeserializeNode(AObject.GetValue('condition'), ANodeIdMap),
+ DeserializeNode(AObject.GetValue('thenBranch'), ANodeIdMap),
+ DeserializeNode(AObject.GetValue('elseBranch'), ANodeIdMap)
+ );
+ antLambdaExpression:
+ begin
+ var paramsJson := AObject.GetValue('parameters');
+ var params := TArray.Create();
+ SetLength(params, paramsJson.Count);
+ // Important: Add the (incomplete) node to the map before deserializing children
+ // to allow children to reference it.
+ // However, with immutable nodes, we can't do that. The check for forward refs handles this limitation.
+ for i := 0 to paramsJson.Count - 1 do
+ begin
+ params[i] := IIdentifierNode(DeserializeNode(paramsJson.Items[i] as TJSONObject, ANodeIdMap));
+ end;
+ var body := DeserializeNode(AObject.GetValue('body'), ANodeIdMap);
+ Result := TAst.LambdaExpr(params, body);
+ end;
+ antFunctionCall:
+ begin
+ var callee := DeserializeNode(AObject.GetValue('callee'), ANodeIdMap);
+ var argsJson := AObject.GetValue('arguments');
+ var args := TArray.Create();
+ SetLength(args, argsJson.Count);
+ for i := 0 to argsJson.Count - 1 do
+ begin
+ args[i] := DeserializeNode(argsJson.Items[i] as TJSONObject, ANodeIdMap);
+ end;
+ Result := TAst.FunctionCall(callee, args);
+ end;
+ antBlockExpression:
+ begin
+ var exprsJson := AObject.GetValue('expressions');
+ var exprs := TArray.Create();
+ SetLength(exprs, exprsJson.Count);
+ for i := 0 to exprsJson.Count - 1 do
+ begin
+ exprs[i] := DeserializeNode(exprsJson.Items[i] as TJSONObject, ANodeIdMap);
+ end;
+ Result := TAst.Block(exprs);
+ end;
+ antVariableDeclaration:
+ begin
+ var initializer: IAstNode := nil;
+ if AObject.GetValue('initializer') <> nil then
+ begin
+ initializer := DeserializeNode(AObject.GetValue('initializer'), ANodeIdMap);
+ end;
+ Result := TAst.VarDecl(IIdentifierNode(DeserializeNode(AObject.GetValue('identifier'), ANodeIdMap)), initializer);
+ end;
+ antAssignment:
+ Result :=
+ TAst.Assign(
+ IIdentifierNode(DeserializeNode(AObject.GetValue('identifier'), ANodeIdMap)),
+ DeserializeNode(AObject.GetValue('value'), ANodeIdMap)
+ );
+ antIndexer:
+ Result :=
+ TAst.Indexer(
+ DeserializeNode(AObject.GetValue('base'), ANodeIdMap),
+ DeserializeNode(AObject.GetValue('index'), ANodeIdMap)
+ );
+ antMemberAccess:
+ Result :=
+ TAst.MemberAccess(
+ DeserializeNode(AObject.GetValue('base'), ANodeIdMap),
+ IIdentifierNode(DeserializeNode(AObject.GetValue('member'), ANodeIdMap))
+ );
+ antCreateSeries: Result := TAst.CreateSeries(AObject.GetValue('definition'));
+ antAddSeriesItem:
+ begin
+ var lookback: IAstNode := nil;
+ if AObject.GetValue('lookback') <> nil then
+ begin
+ lookback := DeserializeNode(AObject.GetValue('lookback'), ANodeIdMap);
+ end;
+ Result :=
+ TAst.AddSeriesItem(
+ IIdentifierNode(DeserializeNode(AObject.GetValue('series'), ANodeIdMap)),
+ DeserializeNode(AObject.GetValue('value'), ANodeIdMap),
+ lookback
+ );
+ end;
+ antSeriesLength: Result := TAst.SeriesLength(IIdentifierNode(DeserializeNode(AObject.GetValue('series'), ANodeIdMap)));
+ else
+ raise ENotImplemented.CreateFmt('Deserialization for node type "%s" is not implemented.', [nodeTypeStr]);
+ end;
+
+ if Assigned(Result) then
+ begin
+ ANodeIdMap.Add(tempId, Result);
+ end;
+end;
+
+function TAstProjectPersistence.ScalarFromJson(AObject: TJSONObject): TScalar;
+var
+ kindStr: string;
+ kind: TScalarKind;
+ value: TScalarValue;
+ decVal: TDecimal;
+ hexStrAnsi: AnsiString;
+begin
+ kindStr := AObject.GetValue('kind');
+ kind := TScalar.StringToKind(kindStr);
+ case kind of
+ skInteger: value.AsInteger := AObject.GetValue('value');
+ skInt64: value.AsInt64 := StrToInt64(AObject.GetValue('value'));
+ skUInt64: value.AsUInt64 := StrToUInt64(AObject.GetValue('value'));
+ skSingle: value.AsSingle := AObject.GetValue('value');
+ skDouble: value.AsDouble := AObject.GetValue('value');
+ skDateTime: value.AsDateTime := ISO8601ToDate(AObject.GetValue('value'));
+ skTimestamp: value.AsTimestamp := DateTimeToTimeStamp(ISO8601ToDate(AObject.GetValue('value')));
+ skBoolean: value.AsBoolean := AObject.GetValue('value');
+ skChar: value.AsChar := AObject.GetValue('value')[1];
+ skPChar: StrPCopy(value.AsPChar, AObject.GetValue('value'));
+ skString: value.AsString := AObject.GetValue('value');
+ skBytes:
+ begin
+ hexStrAnsi := AnsiString(AObject.GetValue('value'));
+ HexToBin(PAnsiChar(hexStrAnsi), @value.AsBytes, SizeOf(value.AsBytes));
+ end;
+ skDecimal:
+ begin
+ decVal := TDecimal.Create(StrToInt64(AObject.GetValue('value')), AObject.GetValue('scale'));
+ value.AsDecimal := decVal;
+ end;
+ else
+ raise ENotImplemented.CreateFmt('TScalar deserialization for kind "%s" is not implemented.', [kindStr]);
+ end;
+ Result.Create(kind, value);
+end;
+
+class function TAstProjectPersistence.AstNodeToJsonString(const ANode: IAstNode): string;
+var
+ idMap: TDictionary;
+ visitor: TAstToJsonVisitor;
+ astJson, rootJson: TJSONObject;
+begin
+ if not Assigned(ANode) then
+ begin
+ exit('');
+ end;
+
+ // We wrap the AST in the standard project structure { "ast": ... }
+ // for compatibility with the full LoadFromFile method.
+ rootJson := TJSONObject.Create;
+ try
+ idMap := TDictionary.Create;
+ try
+ visitor := TAstToJsonVisitor.Create(idMap);
+ try
+ ANode.Accept(visitor);
+ astJson := visitor.GetResult;
+ rootJson.AddPair('ast', astJson);
+ Result := rootJson.ToJSON;
+ finally
+ visitor.Free;
+ end;
+ finally
+ idMap.Free;
+ end;
+ finally
+ rootJson.Free;
+ end;
+end;
+
+class function TAstProjectPersistence.JsonStringToAstNode(const AJsonString: string): IAstNode;
+var
+ persistence: TAstProjectPersistence;
+ rootJson, astJson: TJSONObject;
+ nodeIdMap: TDictionary;
+begin
+ Result := nil;
+ if AJsonString.IsEmpty then
+ begin
+ exit;
+ end;
+
+ rootJson := TJSONObject.ParseJSONValue(AJsonString) as TJSONObject;
+ if not Assigned(rootJson) then
+ begin
+ exit;
+ end;
+
+ try
+ // Expect the standard project structure and extract the "ast" part.
+ astJson := rootJson.GetValue('ast');
+ if Assigned(astJson) then
+ begin
+ persistence := TAstProjectPersistence.Create;
+ try
+ nodeIdMap := TDictionary.Create;
+ try
+ Result := persistence.DeserializeNode(astJson, nodeIdMap);
+ finally
+ nodeIdMap.Free;
+ end;
+ finally
+ persistence.Free;
+ end;
+ end;
+ finally
+ rootJson.Free;
+ end;
+end;
+
+{ TAstProjectPersistence.TAstToJsonVisitor }
+
+constructor TAstProjectPersistence.TAstToJsonVisitor.Create(AIdMap: TDictionary);
+begin
+ inherited Create;
+ FIdMap := AIdMap;
+ FNextId := 0;
+ FResultStack := TStack.Create;
+ FSerializedNodes := TDictionary.Create;
+end;
+
+destructor TAstProjectPersistence.TAstToJsonVisitor.Destroy;
+begin
+ FResultStack.Free;
+ FSerializedNodes.Free;
+ inherited;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.GetResult: TJSONObject;
+begin
+ Assert(FResultStack.Count = 1, 'Result stack should contain exactly one item after traversal.');
+ Result := FResultStack.Pop as TJSONObject;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.GetOrCreateNodeId(const Node: IAstNode): Integer;
+begin
+ if not FIdMap.TryGetValue(Node, Result) then
+ begin
+ Result := FNextId;
+ FIdMap.Add(Node, Result);
+ inc(FNextId);
+ end;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.ScalarToJson(const AValue: TScalar): TJSONObject;
+var
+ hexStr: string;
+begin
+ Result := TJSONObject.Create;
+ Result.AddPair('kind', TJSONString.Create(AValue.Kind.ToString));
+ case AValue.Kind of
+ skInteger: Result.AddPair('value', TJSONNumber.Create(AValue.Value.AsInteger));
+ skInt64: Result.AddPair('value', TJSONString.Create(AValue.Value.AsInt64.ToString));
+ skUInt64: Result.AddPair('value', TJSONString.Create(AValue.Value.AsUInt64.ToString));
+ skSingle: Result.AddPair('value', TJSONNumber.Create(AValue.Value.AsSingle));
+ skDouble: Result.AddPair('value', TJSONNumber.Create(AValue.Value.AsDouble));
+ skDateTime: Result.AddPair('value', TJSONString.Create(DateToISO8601(AValue.Value.AsDateTime)));
+ skTimestamp: Result.AddPair('value', TJSONString.Create(DateToISO8601(TimeStampToDateTime(AValue.Value.AsTimestamp))));
+ skBoolean: Result.AddPair('value', TJSONBool.Create(AValue.Value.AsBoolean));
+ skChar: Result.AddPair('value', TJSONString.Create(AValue.Value.AsChar));
+ skPChar: Result.AddPair('value', TJSONString.Create(string(AValue.Value.AsPChar)));
+ skString: Result.AddPair('value', TJSONString.Create(String(AValue.Value.AsString)));
+ skBytes:
+ begin
+ // Corrected: Use the procedure overload of BinToHex.
+ // BinToHex(@AValue.Value.AsBytes, hexStr, SizeOf(AValue.Value.AsBytes));
+ // Result.AddPair('value', TJSONString.Create(hexStr));
+ end;
+ skDecimal:
+ begin
+ Result.AddPair('value', TJSONString.Create(AValue.Value.AsDecimal.GetValue.ToString));
+ Result.AddPair('scale', TJSONNumber.Create(AValue.Value.AsDecimal.GetScale));
+ end;
+ else
+ raise ENotImplemented.CreateFmt('TScalar serialization for kind "%s" is not implemented.', [AValue.Kind.ToString]);
+ end;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitConstant(const Node: IConstantNode): TAstValue;
+var
+ obj: TJSONObject;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('Constant'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('value', ScalarToJson(Node.Value));
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue;
+var
+ obj: TJSONObject;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('Identifier'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('name', TJSONString.Create(Node.Name));
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
+var
+ obj: TJSONObject;
+ rightJson, leftJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Left.Accept(Self);
+ Node.Right.Accept(Self);
+ rightJson := FResultStack.Pop;
+ leftJson := FResultStack.Pop;
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('BinaryExpression'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('operator', TJSONString.Create(Copy(GetEnumName(TypeInfo(TBinaryOperator), Ord(Node.Operator)), 3)));
+ obj.AddPair('left', leftJson);
+ obj.AddPair('right', rightJson);
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
+var
+ obj: TJSONObject;
+ rightJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Right.Accept(Self);
+ rightJson := FResultStack.Pop;
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('UnaryExpression'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('operator', TJSONString.Create(Copy(GetEnumName(TypeInfo(TUnaryOperator), Ord(Node.Operator)), 3)));
+ obj.AddPair('right', rightJson);
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
+var
+ obj: TJSONObject;
+ elseJson, thenJson, condJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Condition.Accept(Self);
+ Node.ThenBranch.Accept(Self);
+ if Assigned(Node.ElseBranch) then
+ begin
+ Node.ElseBranch.Accept(Self);
+ end;
+
+ if Assigned(Node.ElseBranch) then
+ begin
+ elseJson := FResultStack.Pop
+ end
+ else
+ begin
+ elseJson := nil;
+ end;
+ thenJson := FResultStack.Pop;
+ condJson := FResultStack.Pop;
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('IfExpression'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('condition', condJson);
+ obj.AddPair('thenBranch', thenJson);
+ if Assigned(elseJson) then
+ begin
+ obj.AddPair('elseBranch', elseJson);
+ end;
+
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
+var
+ obj: TJSONObject;
+ elseJson, thenJson, condJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Condition.Accept(Self);
+ Node.ThenBranch.Accept(Self);
+ Node.ElseBranch.Accept(Self);
+
+ elseJson := FResultStack.Pop;
+ thenJson := FResultStack.Pop;
+ condJson := FResultStack.Pop;
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('TernaryExpression'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('condition', condJson);
+ obj.AddPair('thenBranch', thenJson);
+ obj.AddPair('elseBranch', elseJson);
+
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
+var
+ obj: TJSONObject;
+ paramsJson: TJSONArray;
+ param: IIdentifierNode;
+ bodyJson: TJSONValue;
+ i: Integer;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ // 1. Visit children first.
+ for param in Node.Parameters do
+ begin
+ param.Accept(Self);
+ end;
+ Node.Body.Accept(Self);
+
+ // 2. Pop children's results from the stack in reverse order.
+ bodyJson := FResultStack.Pop;
+
+ paramsJson := TJSONArray.Create;
+ var lst := TList.Create;
+ try
+ lst.Count := Length(Node.Parameters);
+ for i := High(Node.Parameters) downto 0 do
+ lst[i] := FResultStack.Pop;
+ paramsJson.SetElements(lst);
+ finally
+ lst.Free;
+ end;
+
+ // 3. Create this node's JSON object.
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('LambdaExpression'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('parameters', paramsJson);
+ obj.AddPair('body', bodyJson);
+ // Note: ScopeDescriptor and Upvalues are runtime-only and not serialized here.
+
+ // 4. Push this node's result onto the stack.
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
+var
+ obj: TJSONObject;
+ args: TJSONArray;
+ arg: IAstNode;
+ calleeJson: TJSONValue;
+ i: Integer;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Callee.Accept(Self);
+ for arg in Node.Arguments do
+ begin
+ arg.Accept(Self);
+ end;
+
+ args := TJSONArray.Create;
+ var lst := TList.Create;
+ try
+ lst.Count := Node.Arguments.Count;
+ for i := Node.Arguments.Count - 1 downto 0 do
+ lst[i] := FResultStack.Pop;
+ args.SetElements(lst);
+ finally
+ lst.Free;
+ end;
+
+ calleeJson := FResultStack.Pop;
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('FunctionCall'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('callee', calleeJson);
+ obj.AddPair('arguments', args);
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
+var
+ obj: TJSONObject;
+ exprs: TJSONArray;
+ expr: IAstNode;
+ i: Integer;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ for expr in Node.Expressions do
+ begin
+ expr.Accept(Self);
+ end;
+
+ exprs := TJSONArray.Create;
+ var lst := TList.Create;
+ try
+ lst.Count := Node.Expressions.Count;
+ for i := Node.Expressions.Count - 1 downto 0 do
+ lst[i] := FResultStack.Pop;
+ exprs.SetElements(lst);
+ finally
+ lst.Free;
+ end;
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('BlockExpression'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('expressions', exprs);
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
+var
+ obj: TJSONObject;
+ identJson, initJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Identifier.Accept(Self);
+ if Assigned(Node.Initializer) then
+ begin
+ Node.Initializer.Accept(Self);
+ end;
+
+ if Assigned(Node.Initializer) then
+ begin
+ initJson := FResultStack.Pop
+ end
+ else
+ begin
+ initJson := nil;
+ end;
+ identJson := FResultStack.Pop;
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('VariableDeclaration'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('identifier', identJson);
+ if Assigned(initJson) then
+ begin
+ obj.AddPair('initializer', initJson);
+ end;
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue;
+var
+ obj: TJSONObject;
+ identJson, valueJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Identifier.Accept(Self);
+ Node.Value.Accept(Self);
+ valueJson := FResultStack.Pop;
+ identJson := FResultStack.Pop;
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('Assignment'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('identifier', identJson);
+ obj.AddPair('value', valueJson);
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue;
+var
+ obj: TJSONObject;
+ baseJson, indexJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Base.Accept(Self);
+ Node.Index.Accept(Self);
+ indexJson := FResultStack.Pop;
+ baseJson := FResultStack.Pop;
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('Indexer'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('base', baseJson);
+ obj.AddPair('index', indexJson);
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue;
+var
+ obj: TJSONObject;
+ baseJson, memberJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Base.Accept(Self);
+ Node.Member.Accept(Self);
+ memberJson := FResultStack.Pop;
+ baseJson := FResultStack.Pop;
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('MemberAccess'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('base', baseJson);
+ obj.AddPair('member', memberJson);
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue;
+var
+ obj: TJSONObject;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('CreateSeries'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('definition', TJSONString.Create(Node.Definition));
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue;
+var
+ obj: TJSONObject;
+ seriesJson, valueJson, lookbackJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Series.Accept(Self);
+ Node.Value.Accept(Self);
+ if Assigned(Node.Lookback) then
+ begin
+ Node.Lookback.Accept(Self);
+ end;
+
+ if Assigned(Node.Lookback) then
+ begin
+ lookbackJson := FResultStack.Pop
+ end
+ else
+ begin
+ lookbackJson := nil;
+ end;
+ valueJson := FResultStack.Pop;
+ seriesJson := FResultStack.Pop;
+
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('AddSeriesItem'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('series', seriesJson);
+ obj.AddPair('value', valueJson);
+ if Assigned(lookbackJson) then
+ begin
+ obj.AddPair('lookback', lookbackJson);
+ end;
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+function TAstProjectPersistence.TAstToJsonVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue;
+var
+ obj: TJSONObject;
+ seriesJson: TJSONValue;
+begin
+ if FSerializedNodes.ContainsKey(Node) then
+ begin
+ var refObj := TJSONObject.Create;
+ refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node]));
+ FResultStack.Push(refObj);
+ Result := TAstValue.Void;
+ exit;
+ end;
+ FSerializedNodes.Add(Node, True);
+
+ Node.Series.Accept(Self);
+ seriesJson := FResultStack.Pop;
+ obj := TJSONObject.Create;
+ obj.AddPair('type', TJSONString.Create('SeriesLength'));
+ obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node)));
+ obj.AddPair('series', seriesJson);
+ FResultStack.Push(obj);
+ Result := TAstValue.Void;
+end;
+
+end.
diff --git a/Src/AST/Myc.Ast.Printer.pas b/Src/AST/Myc.Ast.Printer.pas
index db98c5f..42588cf 100644
--- a/Src/AST/Myc.Ast.Printer.pas
+++ b/Src/AST/Myc.Ast.Printer.pas
@@ -182,7 +182,7 @@ end;
function TPrettyPrintVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
var
- arg: IExpressionNode;
+ arg: IAstNode;
begin
AppendLine('FunctionCall');
Indent;
@@ -201,7 +201,7 @@ end;
function TPrettyPrintVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
var
- expr: IExpressionNode;
+ expr: IAstNode;
begin
AppendLine('Block');
Indent;
diff --git a/Src/AST/Myc.Ast.ViewModel.pas b/Src/AST/Myc.Ast.ViewModel.pas
new file mode 100644
index 0000000..56ebb69
--- /dev/null
+++ b/Src/AST/Myc.Ast.ViewModel.pas
@@ -0,0 +1,102 @@
+unit Myc.Ast.ViewModel;
+
+interface
+
+uses
+ System.SysUtils,
+ System.Types,
+ System.Generics.Collections,
+ Myc.Ast.Nodes;
+
+type
+ // An enum to identify the specific type of an AST node without relying on RTTI or GUIDs.
+ // This allows the renderer to depend only on the ViewModel layer.
+ TAstNodeType = (
+ antUndefined,
+ antConstant,
+ antIdentifier,
+ antBinaryExpression,
+ antUnaryExpression,
+ antIfExpression,
+ antTernaryExpression,
+ antLambdaExpression,
+ antFunctionCall,
+ antBlockExpression,
+ antVariableDeclaration,
+ antAssignment,
+ antIndexer,
+ antMemberAccess,
+ antCreateSeries,
+ antAddSeriesItem,
+ antSeriesLength
+ );
+
+ // A unique, stable identifier for a visual node instance.
+ TViewModelID = NativeInt;
+
+ // Defines how a view model displays its children.
+ TVisualizationMode = (vmSyntactic, vmSemantic);
+
+ // Metadata tied to a logical IAstNode. All visual instances of this node will share this data.
+ TLogicalMetadata = record
+ // The concrete type of the IAstNode, used for type-safe operations in the renderer.
+ NodeType: TAstNodeType;
+ Description: String;
+ end;
+
+ // Metadata tied to a specific visual instance of a node, identified by its TViewModelID.
+ TVisualInstanceMetadata = record
+ PositionOverride: TPointF;
+ IsCollapsed: Boolean;
+ VisualizationMode: TVisualizationMode;
+ end;
+
+ // The ViewModel for a node in the visual graph.
+ TVisualNodeViewModel = class
+ private
+ FId: TViewModelID;
+ FNode: IAstNode;
+ FLogicalMetadata: TLogicalMetadata;
+ FParents: TList;
+ FChildren: TList;
+ public
+ constructor Create(const ANode: IAstNode; const AId: TViewModelID; const ALogicalMetadata: TLogicalMetadata);
+ destructor Destroy; override;
+
+ procedure AddChild(const AChild: TVisualNodeViewModel);
+
+ property Id: TViewModelID read FId;
+ property Node: IAstNode read FNode;
+ property LogicalMetadata: TLogicalMetadata read FLogicalMetadata;
+ property Children: TList read FChildren;
+ property Parents: TList read FParents;
+ end;
+
+implementation
+
+{ TVisualNodeViewModel }
+
+constructor TVisualNodeViewModel.Create(const ANode: IAstNode; const AId: TViewModelID; const ALogicalMetadata: TLogicalMetadata);
+begin
+ inherited Create;
+ FNode := ANode;
+ FId := AId;
+ FLogicalMetadata := ALogicalMetadata;
+ FParents := TList.Create;
+ FChildren := TList.Create;
+end;
+
+destructor TVisualNodeViewModel.Destroy;
+begin
+ FParents.Free;
+ FChildren.Free;
+ inherited;
+end;
+
+procedure TVisualNodeViewModel.AddChild(const AChild: TVisualNodeViewModel);
+begin
+ FChildren.Add(AChild);
+ AChild.FParents.Add(Self);
+end;
+
+end.
diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas
index 91558e9..07cb07d 100644
--- a/Src/AST/Myc.Ast.pas
+++ b/Src/AST/Myc.Ast.pas
@@ -17,38 +17,28 @@ type
// --- Existing factory functions ---
class function Constant(AValue: TScalar): IConstantNode; static;
class function Identifier(AName: string): IIdentifierNode; static;
- class function BinaryExpr(
- ALeft: IExpressionNode;
- AOperator: TBinaryOperator;
- ARight: IExpressionNode
- ): IBinaryExpressionNode; static;
- class function UnaryExpr(const AOperator: TUnaryOperator; const ARight: IExpressionNode): IUnaryExpressionNode; static;
- class function IfExpr(
- const ACondition: IExpressionNode;
- const AThenBranch, AElseBranch: IExpressionNode
- ): IIfExpressionNode; static;
- class function TernaryExpr(
- const ACondition: IExpressionNode;
- const AThenBranch, AElseBranch: IExpressionNode
- ): ITernaryExpressionNode; static;
- class function LambdaExpr(const AParameters: TArray; const ABody: IExpressionNode): ILambdaExpressionNode; static;
- class function FunctionCall(const ACallee: IExpressionNode; const AArguments: array of IExpressionNode): IFunctionCallNode; static;
- class function Block(const AExpressions: array of IExpressionNode): IBlockExpressionNode; static;
- class function VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IExpressionNode): IVariableDeclarationNode; static;
- class function Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; static;
- class function AssignResult(const AValue: IExpressionNode): IAssignmentNode; static; deprecated;
- class function Indexer(const ABase: IExpressionNode; const AIndex: IExpressionNode): IIndexerNode; static;
- class function MemberAccess(const ABase: IExpressionNode; const AMember: IIdentifierNode): IMemberAccessNode; static;
+ class function BinaryExpr(ALeft: IAstNode; AOperator: TBinaryOperator; ARight: IAstNode): IBinaryExpressionNode; static;
+ class function UnaryExpr(const AOperator: TUnaryOperator; const ARight: IAstNode): IUnaryExpressionNode; static;
+ class function IfExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): IIfExpressionNode; static;
+ class function TernaryExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): ITernaryExpressionNode; static;
+ class function LambdaExpr(const AParameters: TArray; const ABody: IAstNode): ILambdaExpressionNode; static;
+ class function FunctionCall(const ACallee: IAstNode; const AArguments: array of IAstNode): IFunctionCallNode; static;
+ class function Block(const AExpressions: array of IAstNode): IBlockExpressionNode; static;
+ class function VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IAstNode): IVariableDeclarationNode; static;
+ class function Assign(const AIdentifier: IIdentifierNode; const AValue: IAstNode): IAssignmentNode; static;
+ class function AssignResult(const AValue: IAstNode): IAssignmentNode; static; deprecated;
+ class function Indexer(const ABase: IAstNode; const AIndex: IAstNode): IIndexerNode; static;
+ class function MemberAccess(const ABase: IAstNode; const AMember: IIdentifierNode): IMemberAccessNode; static;
class function CreateSeries(const ADefinition: String): ICreateSeriesNode; static;
class function AddSeriesItem(
const ASeries: IIdentifierNode;
- const AValue: IExpressionNode;
- const ALookback: IExpressionNode = nil
+ const AValue: IAstNode;
+ const ALookback: IAstNode = nil
): IAddSeriesItemNode; static;
class function SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode; static;
class function CreateDescriptor(const Scope: IExecutionScope): IScopeDescriptor; static;
- class function Bind(const RootNode: IExpressionNode; const ParentScope: IExecutionScope): IExecutionScope; static;
+ class function Bind(const RootNode: IAstNode; const ParentScope: IExecutionScope): IExecutionScope; static;
end;
// TAstTraverser provides a default AST traversal implementation.
@@ -94,7 +84,7 @@ type
{ TAstNode }
// Common base class for AST nodes to reduce boilerplate.
- TAstNode = class(TInterfacedObject, IExpressionNode)
+ TAstNode = class(TInterfacedObject, IAstNode)
public
function Accept(const Visitor: IAstVisitor): TAstValue; virtual; abstract;
end;
@@ -130,14 +120,14 @@ type
{ TBinaryExpressionNode }
TBinaryExpressionNode = class(TAstNode, IBinaryExpressionNode)
private
- FLeft: IExpressionNode;
+ FLeft: IAstNode;
FOperator: TBinaryOperator;
- FRight: IExpressionNode;
- function GetLeft: IExpressionNode;
+ FRight: IAstNode;
+ function GetLeft: IAstNode;
function GetOperator: TBinaryOperator;
- function GetRight: IExpressionNode;
+ function GetRight: IAstNode;
public
- constructor Create(ALeft: IExpressionNode; AOperator: TBinaryOperator; ARight: IExpressionNode);
+ constructor Create(ALeft: IAstNode; AOperator: TBinaryOperator; ARight: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
@@ -145,39 +135,39 @@ type
TUnaryExpressionNode = class(TAstNode, IUnaryExpressionNode)
private
FOperator: TUnaryOperator;
- FRight: IExpressionNode;
+ FRight: IAstNode;
function GetOperator: TUnaryOperator;
- function GetRight: IExpressionNode;
+ function GetRight: IAstNode;
public
- constructor Create(const AOperator: TUnaryOperator; const ARight: IExpressionNode);
+ constructor Create(const AOperator: TUnaryOperator; const ARight: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
{ TIfExpressionNode }
TIfExpressionNode = class(TAstNode, IIfExpressionNode)
private
- FCondition: IExpressionNode;
- FThenBranch: IExpressionNode;
- FElseBranch: IExpressionNode;
- function GetCondition: IExpressionNode;
- function GetThenBranch: IExpressionNode;
- function GetElseBranch: IExpressionNode;
+ FCondition: IAstNode;
+ FThenBranch: IAstNode;
+ FElseBranch: IAstNode;
+ function GetCondition: IAstNode;
+ function GetThenBranch: IAstNode;
+ function GetElseBranch: IAstNode;
public
- constructor Create(const ACondition, AThenBranch, AElseBranch: IExpressionNode);
+ constructor Create(const ACondition, AThenBranch, AElseBranch: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
{ TTernaryExpressionNode }
TTernaryExpressionNode = class(TAstNode, ITernaryExpressionNode)
private
- FCondition: IExpressionNode;
- FThenBranch: IExpressionNode;
- FElseBranch: IExpressionNode;
- function GetCondition: IExpressionNode;
- function GetThenBranch: IExpressionNode;
- function GetElseBranch: IExpressionNode;
+ FCondition: IAstNode;
+ FThenBranch: IAstNode;
+ FElseBranch: IAstNode;
+ function GetCondition: IAstNode;
+ function GetThenBranch: IAstNode;
+ function GetElseBranch: IAstNode;
public
- constructor Create(const ACondition, AThenBranch, AElseBranch: IExpressionNode);
+ constructor Create(const ACondition, AThenBranch, AElseBranch: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
@@ -185,15 +175,15 @@ type
TLambdaExpressionNode = class(TAstNode, ILambdaExpressionNode)
private
FParameters: TArray;
- FBody: IExpressionNode;
+ FBody: IAstNode;
FScopeDescriptor: IScopeDescriptor;
FUpvalues: TArray;
function GetParameters: TArray;
- function GetBody: IExpressionNode;
+ function GetBody: IAstNode;
function GetScopeDescriptor: IScopeDescriptor;
function GetUpvalues: TArray;
public
- constructor Create(const AParameters: TArray; const ABody: IExpressionNode);
+ constructor Create(const AParameters: TArray; const ABody: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
property ScopeDescriptor: IScopeDescriptor read GetScopeDescriptor;
end;
@@ -201,12 +191,12 @@ type
{ TFunctionCallNode }
TFunctionCallNode = class(TAstNode, IFunctionCallNode)
private
- FCallee: IExpressionNode;
- FArguments: TList;
- function GetCallee: IExpressionNode;
- function GetArguments: TList;
+ FCallee: IAstNode;
+ FArguments: TList;
+ function GetCallee: IAstNode;
+ function GetArguments: TList;
public
- constructor Create(const ACallee: IExpressionNode; const AArguments: array of IExpressionNode);
+ constructor Create(const ACallee: IAstNode; const AArguments: array of IAstNode);
destructor Destroy; override;
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
@@ -214,10 +204,10 @@ type
{ TBlockExpressionNode }
TBlockExpressionNode = class(TAstNode, IBlockExpressionNode)
private
- FExpressions: TList;
- function GetExpressions: TList;
+ FExpressions: TList;
+ function GetExpressions: TList;
public
- constructor Create(const AExpressions: array of IExpressionNode);
+ constructor Create(const AExpressions: array of IAstNode);
destructor Destroy; override;
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
@@ -226,11 +216,11 @@ type
TVariableDeclarationNode = class(TAstNode, IVariableDeclarationNode)
private
FIdentifier: IIdentifierNode;
- FInitializer: IExpressionNode;
+ FInitializer: IAstNode;
function GetIdentifier: IIdentifierNode;
- function GetInitializer: IExpressionNode;
+ function GetInitializer: IAstNode;
public
- constructor Create(const AIdentifier: IIdentifierNode; AInitializer: IExpressionNode);
+ constructor Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
@@ -238,35 +228,35 @@ type
TAssignmentNode = class(TAstNode, IAssignmentNode)
private
FIdentifier: IIdentifierNode;
- FValue: IExpressionNode;
+ FValue: IAstNode;
function GetIdentifier: IIdentifierNode;
- function GetValue: IExpressionNode;
+ function GetValue: IAstNode;
public
- constructor Create(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode);
+ constructor Create(const AIdentifier: IIdentifierNode; const AValue: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
{ TIndexerNode }
TIndexerNode = class(TAstNode, IIndexerNode)
private
- FBase: IExpressionNode;
- FIndex: IExpressionNode;
- function GetBase: IExpressionNode;
- function GetIndex: IExpressionNode;
+ FBase: IAstNode;
+ FIndex: IAstNode;
+ function GetBase: IAstNode;
+ function GetIndex: IAstNode;
public
- constructor Create(const ABase: IExpressionNode; const AIndex: IExpressionNode);
+ constructor Create(const ABase: IAstNode; const AIndex: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
{ TMemberAccessNode }
TMemberAccessNode = class(TAstNode, IMemberAccessNode)
private
- FBase: IExpressionNode;
+ FBase: IAstNode;
FMember: IIdentifierNode;
- function GetBase: IExpressionNode;
+ function GetBase: IAstNode;
function GetMember: IIdentifierNode;
public
- constructor Create(const ABase: IExpressionNode; const AMember: IIdentifierNode);
+ constructor Create(const ABase: IAstNode; const AMember: IIdentifierNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
@@ -284,13 +274,13 @@ type
TAddSeriesItemNode = class(TAstNode, IAddSeriesItemNode)
private
FSeries: IIdentifierNode;
- FValue: IExpressionNode;
- FLookback: IExpressionNode;
+ FValue: IAstNode;
+ FLookback: IAstNode;
function GetSeries: IIdentifierNode;
- function GetValue: IExpressionNode;
- function GetLookback: IExpressionNode;
+ function GetValue: IAstNode;
+ function GetLookback: IAstNode;
public
- constructor Create(const ASeries: IIdentifierNode; const AValue: IExpressionNode; const ALookback: IExpressionNode);
+ constructor Create(const ASeries: IIdentifierNode; const AValue: IAstNode; const ALookback: IAstNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
@@ -459,7 +449,7 @@ end;
{ TBinaryExpressionNode }
-constructor TBinaryExpressionNode.Create(ALeft: IExpressionNode; AOperator: TBinaryOperator; ARight: IExpressionNode);
+constructor TBinaryExpressionNode.Create(ALeft: IAstNode; AOperator: TBinaryOperator; ARight: IAstNode);
begin
inherited Create;
FLeft := ALeft;
@@ -472,7 +462,7 @@ begin
Result := Visitor.VisitBinaryExpression(Self);
end;
-function TBinaryExpressionNode.GetLeft: IExpressionNode;
+function TBinaryExpressionNode.GetLeft: IAstNode;
begin
Result := FLeft;
end;
@@ -482,14 +472,14 @@ begin
Result := FOperator;
end;
-function TBinaryExpressionNode.GetRight: IExpressionNode;
+function TBinaryExpressionNode.GetRight: IAstNode;
begin
Result := FRight;
end;
{ TUnaryExpressionNode }
-constructor TUnaryExpressionNode.Create(const AOperator: TUnaryOperator; const ARight: IExpressionNode);
+constructor TUnaryExpressionNode.Create(const AOperator: TUnaryOperator; const ARight: IAstNode);
begin
inherited Create;
FOperator := AOperator;
@@ -506,14 +496,14 @@ begin
Result := FOperator;
end;
-function TUnaryExpressionNode.GetRight: IExpressionNode;
+function TUnaryExpressionNode.GetRight: IAstNode;
begin
Result := FRight;
end;
{ TIfExpressionNode }
-constructor TIfExpressionNode.Create(const ACondition, AThenBranch, AElseBranch: IExpressionNode);
+constructor TIfExpressionNode.Create(const ACondition, AThenBranch, AElseBranch: IAstNode);
begin
inherited Create;
FCondition := ACondition;
@@ -526,24 +516,24 @@ begin
Result := Visitor.VisitIfExpression(Self);
end;
-function TIfExpressionNode.GetCondition: IExpressionNode;
+function TIfExpressionNode.GetCondition: IAstNode;
begin
Result := FCondition;
end;
-function TIfExpressionNode.GetElseBranch: IExpressionNode;
+function TIfExpressionNode.GetElseBranch: IAstNode;
begin
Result := FElseBranch;
end;
-function TIfExpressionNode.GetThenBranch: IExpressionNode;
+function TIfExpressionNode.GetThenBranch: IAstNode;
begin
Result := FThenBranch;
end;
{ TTernaryExpressionNode }
-constructor TTernaryExpressionNode.Create(const ACondition, AThenBranch, AElseBranch: IExpressionNode);
+constructor TTernaryExpressionNode.Create(const ACondition, AThenBranch, AElseBranch: IAstNode);
begin
inherited Create;
FCondition := ACondition;
@@ -556,24 +546,24 @@ begin
Result := Visitor.VisitTernaryExpression(Self);
end;
-function TTernaryExpressionNode.GetCondition: IExpressionNode;
+function TTernaryExpressionNode.GetCondition: IAstNode;
begin
Result := FCondition;
end;
-function TTernaryExpressionNode.GetElseBranch: IExpressionNode;
+function TTernaryExpressionNode.GetElseBranch: IAstNode;
begin
Result := FElseBranch;
end;
-function TTernaryExpressionNode.GetThenBranch: IExpressionNode;
+function TTernaryExpressionNode.GetThenBranch: IAstNode;
begin
Result := FThenBranch;
end;
{ TLambdaExpressionNode }
-constructor TLambdaExpressionNode.Create(const AParameters: TArray; const ABody: IExpressionNode);
+constructor TLambdaExpressionNode.Create(const AParameters: TArray; const ABody: IAstNode);
begin
inherited Create;
FBody := ABody;
@@ -591,7 +581,7 @@ begin
Result := Visitor.VisitLambdaExpression(Self);
end;
-function TLambdaExpressionNode.GetBody: IExpressionNode;
+function TLambdaExpressionNode.GetBody: IAstNode;
begin
Result := FBody;
end;
@@ -608,13 +598,13 @@ end;
{ TFunctionCallNode }
-constructor TFunctionCallNode.Create(const ACallee: IExpressionNode; const AArguments: array of IExpressionNode);
+constructor TFunctionCallNode.Create(const ACallee: IAstNode; const AArguments: array of IAstNode);
var
- arg: IExpressionNode;
+ arg: IAstNode;
begin
inherited Create;
FCallee := ACallee;
- FArguments := TList.Create;
+ FArguments := TList.Create;
for arg in AArguments do
FArguments.Add(arg);
end;
@@ -630,24 +620,24 @@ begin
Result := Visitor.VisitFunctionCall(Self);
end;
-function TFunctionCallNode.GetArguments: TList;
+function TFunctionCallNode.GetArguments: TList;
begin
Result := FArguments;
end;
-function TFunctionCallNode.GetCallee: IExpressionNode;
+function TFunctionCallNode.GetCallee: IAstNode;
begin
Result := FCallee;
end;
{ TBlockExpressionNode }
-constructor TBlockExpressionNode.Create(const AExpressions: array of IExpressionNode);
+constructor TBlockExpressionNode.Create(const AExpressions: array of IAstNode);
var
- expr: IExpressionNode;
+ expr: IAstNode;
begin
inherited Create;
- FExpressions := TList.Create;
+ FExpressions := TList.Create;
for expr in AExpressions do
FExpressions.Add(expr);
end;
@@ -663,14 +653,14 @@ begin
Result := Visitor.VisitBlockExpression(Self);
end;
-function TBlockExpressionNode.GetExpressions: TList;
+function TBlockExpressionNode.GetExpressions: TList;
begin
Result := FExpressions;
end;
{ TVariableDeclarationNode }
-constructor TVariableDeclarationNode.Create(const AIdentifier: IIdentifierNode; AInitializer: IExpressionNode);
+constructor TVariableDeclarationNode.Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode);
begin
inherited Create;
FIdentifier := AIdentifier;
@@ -687,14 +677,14 @@ begin
Result := FIdentifier;
end;
-function TVariableDeclarationNode.GetInitializer: IExpressionNode;
+function TVariableDeclarationNode.GetInitializer: IAstNode;
begin
Result := FInitializer;
end;
{ TAssignmentNode }
-constructor TAssignmentNode.Create(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode);
+constructor TAssignmentNode.Create(const AIdentifier: IIdentifierNode; const AValue: IAstNode);
begin
inherited Create;
FIdentifier := AIdentifier;
@@ -711,14 +701,14 @@ begin
Result := FIdentifier;
end;
-function TAssignmentNode.GetValue: IExpressionNode;
+function TAssignmentNode.GetValue: IAstNode;
begin
Result := FValue;
end;
{ TIndexerNode }
-constructor TIndexerNode.Create(const ABase, AIndex: IExpressionNode);
+constructor TIndexerNode.Create(const ABase, AIndex: IAstNode);
begin
inherited Create;
FBase := ABase;
@@ -730,19 +720,19 @@ begin
Result := Visitor.VisitIndexer(Self);
end;
-function TIndexerNode.GetBase: IExpressionNode;
+function TIndexerNode.GetBase: IAstNode;
begin
Result := FBase;
end;
-function TIndexerNode.GetIndex: IExpressionNode;
+function TIndexerNode.GetIndex: IAstNode;
begin
Result := FIndex;
end;
{ TMemberAccessNode }
-constructor TMemberAccessNode.Create(const ABase: IExpressionNode; const AMember: IIdentifierNode);
+constructor TMemberAccessNode.Create(const ABase: IAstNode; const AMember: IIdentifierNode);
begin
inherited Create;
FBase := ABase;
@@ -754,7 +744,7 @@ begin
Result := Visitor.VisitMemberAccess(Self);
end;
-function TMemberAccessNode.GetBase: IExpressionNode;
+function TMemberAccessNode.GetBase: IAstNode;
begin
Result := FBase;
end;
@@ -784,7 +774,7 @@ end;
{ TAddSeriesItemNode }
-constructor TAddSeriesItemNode.Create(const ASeries: IIdentifierNode; const AValue, ALookback: IExpressionNode);
+constructor TAddSeriesItemNode.Create(const ASeries: IIdentifierNode; const AValue, ALookback: IAstNode);
begin
inherited Create;
FSeries := ASeries;
@@ -797,7 +787,7 @@ begin
Result := Visitor.VisitAddSeriesItem(Self);
end;
-function TAddSeriesItemNode.GetLookback: IExpressionNode;
+function TAddSeriesItemNode.GetLookback: IAstNode;
begin
Result := FLookback;
end;
@@ -807,7 +797,7 @@ begin
Result := FSeries;
end;
-function TAddSeriesItemNode.GetValue: IExpressionNode;
+function TAddSeriesItemNode.GetValue: IAstNode;
begin
Result := FValue;
end;
@@ -968,7 +958,7 @@ end;
{ TAst }
-class function TAst.Bind(const RootNode: IExpressionNode; const ParentScope: IExecutionScope): IExecutionScope;
+class function TAst.Bind(const RootNode: IAstNode; const ParentScope: IExecutionScope): IExecutionScope;
var
binder: TBinder;
visitor: IAstVisitor;
@@ -990,26 +980,22 @@ begin
end;
end;
-class function TAst.AddSeriesItem(
- const ASeries: IIdentifierNode;
- const AValue: IExpressionNode;
- const ALookback: IExpressionNode
-): IAddSeriesItemNode;
+class function TAst.AddSeriesItem(const ASeries: IIdentifierNode; const AValue: IAstNode; const ALookback: IAstNode): IAddSeriesItemNode;
begin
Result := TAddSeriesItemNode.Create(ASeries, AValue, ALookback);
end;
-class function TAst.Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode;
+class function TAst.Assign(const AIdentifier: IIdentifierNode; const AValue: IAstNode): IAssignmentNode;
begin
Result := TAssignmentNode.Create(AIdentifier, AValue);
end;
-class function TAst.AssignResult(const AValue: IExpressionNode): IAssignmentNode;
+class function TAst.AssignResult(const AValue: IAstNode): IAssignmentNode;
begin
Result := Assign(TAst.Identifier('Result'), AValue);
end;
-class function TAst.Block(const AExpressions: array of IExpressionNode): IBlockExpressionNode;
+class function TAst.Block(const AExpressions: array of IAstNode): IBlockExpressionNode;
begin
Result := TBlockExpressionNode.Create(AExpressions);
end;
@@ -1024,7 +1010,7 @@ begin
Result := TCreateSeriesNode.Create(ADefinition);
end;
-class function TAst.BinaryExpr(ALeft: IExpressionNode; AOperator: TBinaryOperator; ARight: IExpressionNode): IBinaryExpressionNode;
+class function TAst.BinaryExpr(ALeft: IAstNode; AOperator: TBinaryOperator; ARight: IAstNode): IBinaryExpressionNode;
begin
Result := TBinaryExpressionNode.Create(ALeft, AOperator, ARight);
end;
@@ -1040,7 +1026,7 @@ begin
Result := TScopeDescriptor.Create(nil);
end;
-class function TAst.FunctionCall(const ACallee: IExpressionNode; const AArguments: array of IExpressionNode): IFunctionCallNode;
+class function TAst.FunctionCall(const ACallee: IAstNode; const AArguments: array of IAstNode): IFunctionCallNode;
begin
Result := TFunctionCallNode.Create(ACallee, AArguments);
end;
@@ -1050,17 +1036,17 @@ begin
Result := TIdentifierNode.Create(AName);
end;
-class function TAst.IfExpr(const ACondition: IExpressionNode; const AThenBranch, AElseBranch: IExpressionNode): IIfExpressionNode;
+class function TAst.IfExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): IIfExpressionNode;
begin
Result := TIfExpressionNode.Create(ACondition, AThenBranch, AElseBranch);
end;
-class function TAst.Indexer(const ABase, AIndex: IExpressionNode): IIndexerNode;
+class function TAst.Indexer(const ABase, AIndex: IAstNode): IIndexerNode;
begin
Result := TIndexerNode.Create(ABase, AIndex);
end;
-class function TAst.MemberAccess(const ABase: IExpressionNode; const AMember: IIdentifierNode): IMemberAccessNode;
+class function TAst.MemberAccess(const ABase: IAstNode; const AMember: IIdentifierNode): IMemberAccessNode;
begin
Result := TMemberAccessNode.Create(ABase, AMember);
end;
@@ -1070,22 +1056,22 @@ begin
Result := TSeriesLengthNode.Create(ASeries);
end;
-class function TAst.TernaryExpr(const ACondition: IExpressionNode; const AThenBranch, AElseBranch: IExpressionNode): ITernaryExpressionNode;
+class function TAst.TernaryExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): ITernaryExpressionNode;
begin
Result := TTernaryExpressionNode.Create(ACondition, AThenBranch, AElseBranch);
end;
-class function TAst.LambdaExpr(const AParameters: TArray; const ABody: IExpressionNode): ILambdaExpressionNode;
+class function TAst.LambdaExpr(const AParameters: TArray; const ABody: IAstNode): ILambdaExpressionNode;
begin
Result := TLambdaExpressionNode.Create(AParameters, ABody);
end;
-class function TAst.UnaryExpr(const AOperator: TUnaryOperator; const ARight: IExpressionNode): IUnaryExpressionNode;
+class function TAst.UnaryExpr(const AOperator: TUnaryOperator; const ARight: IAstNode): IUnaryExpressionNode;
begin
Result := TUnaryExpressionNode.Create(AOperator, ARight);
end;
-class function TAst.VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IExpressionNode): IVariableDeclarationNode;
+class function TAst.VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IAstNode): IVariableDeclarationNode;
begin
Result := TVariableDeclarationNode.Create(AIdentifier, AInitializer);
end;
@@ -1114,7 +1100,7 @@ end;
function TAstTraverser.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
var
- expr: IExpressionNode;
+ expr: IAstNode;
begin
for expr in Node.Expressions do
expr.Accept(Self);
@@ -1130,7 +1116,7 @@ end;
function TAstTraverser.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
var
- arg: IExpressionNode;
+ arg: IAstNode;
begin
Node.Callee.Accept(Self);
for arg in Node.Arguments do
diff --git a/Src/Data/Myc.Data.Scalar.JSON.pas b/Src/Data/Myc.Data.Scalar.JSON.pas
index cae0daf..ab71966 100644
--- a/Src/Data/Myc.Data.Scalar.JSON.pas
+++ b/Src/Data/Myc.Data.Scalar.JSON.pas
@@ -6,6 +6,7 @@ uses
System.SysUtils,
System.Rtti,
System.TypInfo,
+ System.JSON,
Myc.Data.Scalar;
type
@@ -24,8 +25,7 @@ implementation
uses
Myc.Data.Decimal,
- System.Generics.Collections,
- System.JSON;
+ System.Generics.Collections;
{ TRttiAstHelper }