From 5796f88da4e4127e62992786aa104ab55dcee06e Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 16 Sep 2025 12:54:49 +0200 Subject: [PATCH] Ast Editor --- ASTPlayground/ASTPlayground.dpr | 2 +- ASTPlayground/ASTPlayground.dproj | 2 +- ASTPlayground/MainForm.pas | 2 +- ...t.Visualizer.pas => Myc.Fmx.AstEditor.pas} | 202 ++++++++++++------ 4 files changed, 134 insertions(+), 74 deletions(-) rename ASTPlayground/{Myc.Ast.Visualizer.pas => Myc.Fmx.AstEditor.pas} (88%) diff --git a/ASTPlayground/ASTPlayground.dpr b/ASTPlayground/ASTPlayground.dpr index 086c243..5e384b2 100644 --- a/ASTPlayground/ASTPlayground.dpr +++ b/ASTPlayground/ASTPlayground.dpr @@ -8,7 +8,7 @@ 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.Fmx.AstEditor in 'Myc.Fmx.AstEditor.pas', Myc.Ast.ViewModel in '..\Src\AST\Myc.Ast.ViewModel.pas', Myc.Data.Value in 'Myc.Data.Value.pas', Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas'; diff --git a/ASTPlayground/ASTPlayground.dproj b/ASTPlayground/ASTPlayground.dproj index 4b373f7..769b924 100644 --- a/ASTPlayground/ASTPlayground.dproj +++ b/ASTPlayground/ASTPlayground.dproj @@ -139,7 +139,7 @@ - + diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index c27df51..f4c5415 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -21,7 +21,7 @@ uses FMX.ScrollBox, FMX.Memo, FMX.Controls.Presentation, - Myc.Ast.Visualizer, + Myc.Fmx.AstEditor, Myc.Data.Scalar, Myc.Data.Value, Myc.Ast.Nodes, diff --git a/ASTPlayground/Myc.Ast.Visualizer.pas b/ASTPlayground/Myc.Fmx.AstEditor.pas similarity index 88% rename from ASTPlayground/Myc.Ast.Visualizer.pas rename to ASTPlayground/Myc.Fmx.AstEditor.pas index 49c558c..2d87439 100644 --- a/ASTPlayground/Myc.Ast.Visualizer.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.pas @@ -1,4 +1,4 @@ -unit Myc.Ast.Visualizer; +unit Myc.Fmx.AstEditor; interface @@ -215,6 +215,8 @@ type FConnections: TList; FCurrentExec: TList; FMode: TVisualizationMode; + FResolvedIdentifierCache: TDictionary; + FOwnsCaches: Boolean; procedure FinalizeNodeLayout(const Node: TAuraNode); @@ -255,7 +257,8 @@ type const AStartPosition: TPointF; AConnections: TList; const ACurrentExec: TArray; - AMode: TVisualizationMode + AMode: TVisualizationMode; + AResolvedIdentifierCache: TDictionary = nil ); destructor Destroy; override; @@ -499,7 +502,7 @@ begin Height := 45; // The panel must be able to receive mouse events. - // HitTest := True; + // HitTest := True; // Clip children to the panel's bounds. ClipChildren := True; end; @@ -850,7 +853,8 @@ constructor TAstToAuraNodeVisitor.Create( const AStartPosition: TPointF; AConnections: TList; const ACurrentExec: TArray; - AMode: TVisualizationMode + AMode: TVisualizationMode; + AResolvedIdentifierCache: TDictionary ); begin inherited Create; @@ -863,10 +867,25 @@ begin FLastResult.LayoutNode := nil; FLastResult.OutputPin := nil; FMode := AMode; + + if Assigned(AResolvedIdentifierCache) then + begin + FResolvedIdentifierCache := AResolvedIdentifierCache; + FOwnsCaches := False; + end + else + begin + FResolvedIdentifierCache := TDictionary.Create; + FOwnsCaches := True; + end; end; destructor TAstToAuraNodeVisitor.Destroy; begin + if FOwnsCaches then + begin + FResolvedIdentifierCache.Free; + end; FCurrentExec.Free; inherited; end; @@ -889,10 +908,16 @@ begin if (Pos('exec.', control.TagString) = 1) or (Pos('data.', control.TagString) = 1) then begin // Use the pin's X position to determine if it's on the left or right side. - if control.Position.X < (Node.Width / 2) then - leftPins.Add(control) + if Pos('.in', control.TagString) > 0 then + begin + leftPins.Add(control); + control.Position.X := 0; + end else + begin rightPins.Add(control); + control.Position.X := Node.Width - cPinSize; + end; end; end; @@ -1128,47 +1153,54 @@ begin end; function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue; +var + details: string; + assignmentNode: TAuraNode; + control: TControl; begin - var details: String; if (FMode = vmControlFlow) and TryGetDescr(Node, details) then begin - var assignmentNode := CreateNodeControl('Assignment', details); + // Keep the simple view for control flow mode + assignmentNode := CreateNodeControl('Assignment', details); CreateEntry(assignmentNode); CreateExit(assignmentNode, ''); FinalizeNodeLayout(assignmentNode); end else begin - VisitOperatorNode( - [Node.Value], - function(const InputResults: TAuraNodeResultList): TAuraNodeResult - var - assignmentNode: TAuraNode; - inputPin: TControl; - valueResult: TAuraNodeResult; - outPin: TControl; // variable for the output pin + // Create the main container node for the assignment. + assignmentNode := CreateNodeControl('Assignment', Node.Identifier.Name); + + // Create a child visitor to render the value expression inside the assignment node. + // We pass an empty array for ACurrentExec so the child's execution flow doesn't connect to anything externally. + var childStartPos := TPointF.Create(FSpacing.X, assignmentNode.TitleFont.Size * 1.8 + FSpacing.Y); + var childVisitor := + TAstToAuraNodeVisitor.Create(FWorkspace, assignmentNode, childStartPos, FConnections, [], FMode, FResolvedIdentifierCache); + Node.Value.Accept(childVisitor); + + // Resize the parent assignmentNode to fit the child node(s). + var maxRight: Single := 0; + var maxBottom: Single := 0; + for control in assignmentNode.Controls do + begin + if control is TAuraNode then begin - valueResult := InputResults[0]; - assignmentNode := BuildNodeControl('Assignment', Node.Identifier.Name); + maxRight := Max(maxRight, control.Position.X + control.Width); + maxBottom := Max(maxBottom, control.Position.Y + control.Height); + end; + end; + assignmentNode.Width := Max(assignmentNode.Width, maxRight + FSpacing.X); + assignmentNode.Height := Max(assignmentNode.Height, maxBottom + FSpacing.Y); - CreateEntry(assignmentNode); - inputPin := CreateInput(assignmentNode, Node.Identifier.Name); - CreateExit(assignmentNode, ''); + CreateEntry(assignmentNode); + CreateExit(assignmentNode, ''); + // An assignment expression can be used in another expression, so it needs a data output. + FLastResult.OutputPin := CreateOutput(assignmentNode); - // Create the output pin before finalizing the layout. - outPin := CreateOutput(assignmentNode); + // Finalize the layout of the parent node to correctly position its own pins. + FinalizeNodeLayout(assignmentNode); - if Assigned(valueResult.OutputPin) then - FConnections.Add(TPinConnection.Create(valueResult.OutputPin, inputPin)); - - // Now finalize, all pins are present. - FinalizeNodeLayout(assignmentNode); - - Result.LayoutNode := assignmentNode; - Result.OutputPin := outPin; - end, - vaTop - ); + FCurrentPos.Y := assignmentNode.Position.Y + assignmentNode.Height + FSpacing.Y; end; Result := TDataValue.Void; end; @@ -1320,7 +1352,9 @@ begin // Step 2: Create child visitor and run the closure. // The child visitor starts its execution flow from the entry node's exit pin. - childVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray, FMode); + childVisitor := + TAstToAuraNodeVisitor + .Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray, FMode, FResolvedIdentifierCache); ChildVisitorProc(childVisitor); // Capture the result from the child visitor. @@ -1448,10 +1482,34 @@ begin end; function TAstToAuraNodeVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue; +var + existingResult: TAuraNodeResult; + identifierNode: TAuraNode; begin - var identifierNode := CreateNodeControl('Identifier', Node.Name); - FLastResult.OutputPin := CreateOutput(identifierNode); - FinalizeNodeLayout(identifierNode); + if Node.IsResolved then + begin + // Use the resolved address as the key. + if FResolvedIdentifierCache.TryGetValue(Node.Address, existingResult) then + begin + FLastResult := existingResult; + end + else + begin + identifierNode := CreateNodeControl('Identifier', Node.Name); + FLastResult.LayoutNode := identifierNode; + FLastResult.OutputPin := CreateOutput(identifierNode); + FinalizeNodeLayout(identifierNode); + FResolvedIdentifierCache.Add(Node.Address, FLastResult); + end; + end + else + begin + // Unresolved identifiers are not cached and drawn each time. + identifierNode := CreateNodeControl('Identifier', Node.Name); + FLastResult.LayoutNode := identifierNode; + FLastResult.OutputPin := CreateOutput(identifierNode); + FinalizeNodeLayout(identifierNode); + end; Result := TDataValue.Void; end; @@ -1838,12 +1896,14 @@ end; function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; var - outPin: TControl; + details: string; + varDeclNode: TAuraNode; + control: TControl; begin - var details: String; if (FMode = vmControlFlow) and TryGetDescr(Node, details) then begin - var varDeclNode := CreateNodeControl('VarDecl', details); + // Keep the simple view for control flow mode + varDeclNode := CreateNodeControl('VarDecl', details); CreateEntry(varDeclNode); CreateExit(varDeclNode, ''); FinalizeNodeLayout(varDeclNode); @@ -1852,49 +1912,49 @@ begin begin if Assigned(Node.Initializer) then begin - VisitOperatorNode( - [Node.Initializer], - function(const InputResults: TAuraNodeResultList): TAuraNodeResult - var - varDeclNode: TAuraNode; - inputPin: TControl; - initializerResult: TAuraNodeResult; - outPin: TControl; // variable for the output pin + // Create the main container node for the declaration. + varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); + + // Create a child visitor to render the initializer expression inside the node. + var childStartPos := TPointF.Create(FSpacing.X, varDeclNode.TitleFont.Size * 1.8 + FSpacing.Y); + var childVisitor := + TAstToAuraNodeVisitor.Create(FWorkspace, varDeclNode, childStartPos, FConnections, [], FMode, FResolvedIdentifierCache); + Node.Initializer.Accept(childVisitor); + + // Resize the parent node to fit the child node(s). + var maxRight: Single := 0; + var maxBottom: Single := 0; + for control in varDeclNode.Controls do + begin + if control is TAuraNode then begin - initializerResult := InputResults[0]; - varDeclNode := BuildNodeControl('VarDecl', Node.Identifier.Name); + maxRight := Max(maxRight, control.Position.X + control.Width); + maxBottom := Max(maxBottom, control.Position.Y + control.Height); + end; + end; + varDeclNode.Width := Max(varDeclNode.Width, maxRight + FSpacing.X); + varDeclNode.Height := Max(varDeclNode.Height, maxBottom + FSpacing.Y); - CreateEntry(varDeclNode); - inputPin := CreateInput(varDeclNode, 'Value'); - CreateExit(varDeclNode, ''); + CreateEntry(varDeclNode); + CreateExit(varDeclNode, ''); + // The new variable can be used in other expressions, so it needs a data output. + FLastResult.OutputPin := CreateOutput(varDeclNode); - // Create the output pin before finalizing the layout. - outPin := CreateOutput(varDeclNode); + // Finalize the layout of the parent node to correctly position its own pins. + FinalizeNodeLayout(varDeclNode); - if Assigned(initializerResult.OutputPin) then - FConnections.Add(TPinConnection.Create(initializerResult.OutputPin, inputPin)); - - // Now finalize, all pins are present. - FinalizeNodeLayout(varDeclNode); - Result.LayoutNode := varDeclNode; - Result.OutputPin := outPin; - end - ); + FCurrentPos.Y := varDeclNode.Position.Y + varDeclNode.Height + FSpacing.Y; end else begin // Case without initializer is a simple sequential node. - var varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); + varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); CreateEntry(varDeclNode); CreateExit(varDeclNode, ''); - // Create the output pin before finalizing the layout. - outPin := CreateOutput(varDeclNode); - FLastResult.OutputPin := outPin; - // Now finalize, all pins are present. + FLastResult.OutputPin := CreateOutput(varDeclNode); FinalizeNodeLayout(varDeclNode); end; end; - Result := TDataValue.Void; end;