Ast control refactoring

This commit is contained in:
Michael Schimmel
2025-09-16 19:18:40 +02:00
parent 230c4b51bf
commit b972b05a07
5 changed files with 324 additions and 264 deletions
+2 -1
View File
@@ -380,7 +380,8 @@ begin
var visu := TVisualizationMode.vmDetailed;
if FlowOnlyBox.IsChecked then
visu := TVisualizationMode.vmControlFlow;
FWorkspace.BuildTree(FLastAst, TPointF.Create(X, Y), visu);
FWorkspace.BuildTree(FLastAst, TAst.Bind(FLastAst, FGScope), TPointF.Create(X, Y), visu);
end;
end;
+15 -4
View File
@@ -13,6 +13,7 @@ uses
FMX.Controls,
FMX.Graphics,
FMX.Objects,
Myc.Ast,
Myc.Ast.Nodes;
type
@@ -42,7 +43,7 @@ type
procedure DblClick; override;
public
constructor Create(AOwner: TComponent); override;
procedure BuildTree(const Root: IAstNode; const Position: TPointF; AMode: TVisualizationMode);
procedure BuildTree(const RootNode: IAstNode; const RootScope: IExecutionScope; const Position: TPointF; AMode: TVisualizationMode);
function GetChildrenMatrix(var Matrix: TMatrix; var Simple: Boolean): Boolean; override;
function Zoom(Factor: Single): Boolean;
@@ -105,11 +106,21 @@ begin
FZoom := 1.0;
end;
procedure TAuraWorkspace.BuildTree(const Root: IAstNode; const Position: TPointF; AMode: TVisualizationMode);
procedure TAuraWorkspace.BuildTree(
const RootNode: IAstNode;
const RootScope: IExecutionScope;
const Position: TPointF;
AMode: TVisualizationMode
);
var
connections: TList<TPinConnection>;
rootDescriptor: IScopeDescriptor;
begin
var connections := TList<TPinConnection>.Create;
connections := TList<TPinConnection>.Create;
try
Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections, nil, AMode));
// Create the scope descriptor from the execution scope provided by the binder.
rootDescriptor := TAst.CreateDescriptor(RootScope);
RootNode.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections, nil, AMode, nil, nil, rootDescriptor));
FConnections := FConnections + connections.ToArray;
finally
connections.Free;
+299 -246
View File
@@ -9,12 +9,14 @@ uses
System.Types,
System.Math.Vectors,
System.Generics.Collections,
System.Generics.Defaults,
FMX.Types,
FMX.Controls,
FMX.Objects,
FMX.Graphics,
Myc.Data.Value,
Myc.Ast.Nodes,
Myc.Ast.Scope,
Myc.Fmx.AstEditor.Node,
Myc.Fmx.AstEditor.Workspace;
@@ -31,27 +33,22 @@ type
TAstToAuraNodeVisitor = class(TInterfacedObject, IAstVisitor)
public
type
// Holds the result of a node visitation, separating layout and connection concerns.
TAuraNodeResult = record
LayoutNode: TAuraNode;
OutputPin: TControl;
end;
TAuraNodeResultList = TList<TAuraNodeResult>;
TChildVisitorProc = reference to procedure(const Visitor: IAstVisitor);
// The lambda now returns the full result, including the output pin reference.
TCreateParentNodeProc = reference to function(const InputNodes: TAuraNodeResultList): TAuraNodeResult;
TVerticalAlignment = (vaCenter, vaTop);
private
const
// Node Layout
cHorizontalPadding = 25; // Padding inside the node title bar
cPinOffsetY = 18; // Vertical distance between pins
cVerticalPadding = 10; // General vertical padding inside container nodes
// Specific Node Heights
cDefaultNodeHeight = 25; // Default height for simple nodes like BinaryExpr
cIfNodeHeight = 40; // Taller height for If nodes with more pins
cHorizontalPadding = 25;
cPinOffsetY = 18;
cVerticalPadding = 10;
cDefaultNodeHeight = 25;
cIfNodeHeight = 40;
private
FWorkspace: TAuraWorkspace;
@@ -62,24 +59,17 @@ type
FConnections: TList<TPinConnection>;
FCurrentExec: TList<TControl>;
FMode: TVisualizationMode;
FResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult>;
FOwnsCaches: Boolean;
FSlotCache: TArray<TAuraNodeResult>;
FParentVisitor: TAstToAuraNodeVisitor;
FCurrentLambda: ILambdaExpressionNode;
procedure FinalizeNodeLayout(const Node: TAuraNode);
function VisitContainerNode(
const Title, Details: string;
const InPin, OutPin: String;
IsExec: Boolean;
HasResult: Boolean;
const ChildVisitorProc: TChildVisitorProc
): TAuraNode;
function FindNodeForAddress(const Address: TResolvedAddress; out NodeResult: TAuraNodeResult): Boolean;
function VisitOperatorNode(
const InputExpressions: TArray<IAstNode>;
const CreateParentProc: TCreateParentNodeProc;
Alignment: TVerticalAlignment = vaCenter
): TAuraNode;
function BuildNodeControl(const Title, Details: string): TAuraNode;
function CreateNodeControl(const Title, Details: string): TAuraNode;
function CreatePin(
@@ -89,12 +79,10 @@ type
Color: TAlphaColor;
const Tag: string
): TShape;
function CreateInput(ParentNode: TAuraNode; const Caption: string): TControl;
function CreateOutput(ParentNode: TAuraNode): TControl;
function CreateEntry(ParentNode: TAuraNode; connect: Boolean = true): TControl;
function CreateExit(ParentNode: TAuraNode; const Caption: string): TControl;
function TryGetDescr(const Node: IAstNode; out Text: String): Boolean;
public
@@ -105,11 +93,11 @@ type
AConnections: TList<TPinConnection>;
const ACurrentExec: TArray<TControl>;
AMode: TVisualizationMode;
AResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult> = nil
AParent: TAstToAuraNodeVisitor;
ACurrentLambda: ILambdaExpressionNode;
ADescriptor: IScopeDescriptor
);
destructor Destroy; override;
// Public access to the collected connections
property Connections: TList<TPinConnection> read FConnections;
{ IAstVisitor }
@@ -149,7 +137,9 @@ constructor TAstToAuraNodeVisitor.Create(
AConnections: TList<TPinConnection>;
const ACurrentExec: TArray<TControl>;
AMode: TVisualizationMode;
AResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult>
AParent: TAstToAuraNodeVisitor;
ACurrentLambda: ILambdaExpressionNode;
ADescriptor: IScopeDescriptor
);
begin
inherited Create;
@@ -162,25 +152,15 @@ begin
FLastResult.LayoutNode := nil;
FLastResult.OutputPin := nil;
FMode := AMode;
if Assigned(AResolvedIdentifierCache) then
begin
FResolvedIdentifierCache := AResolvedIdentifierCache;
FOwnsCaches := False;
end
else
begin
FResolvedIdentifierCache := TDictionary<TResolvedAddress, TAuraNodeResult>.Create;
FOwnsCaches := True;
end;
FParentVisitor := AParent;
FCurrentLambda := ACurrentLambda;
// Size the array based on the number of slots in the scope descriptor.
if Assigned(ADescriptor) then
SetLength(FSlotCache, ADescriptor.SlotCount);
end;
destructor TAstToAuraNodeVisitor.Destroy;
begin
if FOwnsCaches then
begin
FResolvedIdentifierCache.Free;
end;
FCurrentExec.Free;
inherited;
end;
@@ -327,12 +307,10 @@ var
posX, posY: Single;
begin
posY := 0;
if Alignment = paLeft then
posX := 0
else // paRight
posX := ParentNode.Width - cPinSize;
case Shape of
psCircle:
begin
@@ -353,7 +331,6 @@ begin
Result := nil;
exit;
end;
Result.Parent := ParentNode;
Result.SetBounds(posX, posY, cPinSize, cPinSize);
Result.HitTest := true;
@@ -388,7 +365,6 @@ begin
inputs := [Node.Series, Node.Value, Node.Lookback]
else
inputs := [Node.Series, Node.Value];
VisitOperatorNode(
inputs,
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
@@ -398,27 +374,22 @@ begin
begin
addNode := BuildNodeControl('Add to Series', '');
CreateEntry(addNode);
seriesPin := CreateInput(addNode, 'Series');
valuePin := CreateInput(addNode, 'Value');
if Assigned(InputResults[0].OutputPin) then
FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, seriesPin));
if Assigned(InputResults[1].OutputPin) then
FConnections.Add(TPinConnection.Create(InputResults[1].OutputPin, valuePin));
if InputResults.Count > 2 then
begin
lookbackPin := CreateInput(addNode, 'Lookback');
if Assigned(InputResults[2].OutputPin) then
FConnections.Add(TPinConnection.Create(InputResults[2].OutputPin, lookbackPin));
end;
CreateExit(addNode, '');
FinalizeNodeLayout(addNode);
Result.LayoutNode := addNode;
Result.OutputPin := nil; // No data output
Result.OutputPin := nil;
end,
vaTop
);
@@ -426,47 +397,6 @@ begin
Result := TDataValue.Void;
end;
function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
var
details: string;
assignmentNode: TAuraNode;
control: TControl;
begin
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
begin
assignmentNode := CreateNodeControl('Assignment', details);
CreateEntry(assignmentNode);
CreateExit(assignmentNode, '');
FinalizeNodeLayout(assignmentNode);
end
else
begin
assignmentNode := CreateNodeControl('Assignment', Node.Identifier.Name);
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);
var maxRight: Single := 0;
var maxBottom: Single := 0;
for control in assignmentNode.Controls do
begin
if control is TAuraNode then
begin
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);
CreateExit(assignmentNode, '');
FLastResult.OutputPin := CreateOutput(assignmentNode);
FinalizeNodeLayout(assignmentNode);
FCurrentPos.Y := assignmentNode.Position.Y + assignmentNode.Height + FSpacing.Y;
end;
Result := TDataValue.Void;
end;
function TAstToAuraNodeVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
begin
var exprStr: String;
@@ -510,32 +440,54 @@ end;
function TAstToAuraNodeVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
var
blockNode: TAuraNode;
childLastResult: TAuraNodeResult;
oldParentControl: TControl;
oldPos: TPointF;
lastResult: TAuraNodeResult;
begin
childLastResult.LayoutNode := nil;
childLastResult.OutputPin := nil;
blockNode :=
VisitContainerNode(
'',
'',
'',
'',
true,
false,
procedure(const Visitor: IAstVisitor)
var
expression: IAstNode;
// Create a visual container for the block, but do NOT create a new scope/visitor.
blockNode := BuildNodeControl('', '');
blockNode.Frameless := True;
blockNode.Position.Point := FCurrentPos;
// Temporarily retarget the visitor to build nodes inside the block container.
oldParentControl := FParentControl;
oldPos := FCurrentPos;
try
FParentControl := blockNode;
FCurrentPos := TPointF.Create(FSpacing.X, FSpacing.Y);
Result := TDataValue.Void;
for var expression in Node.Expressions do
begin
Result := expression.Accept(Self);
// The result of the block is the result of the last expression.
// We need to capture the last FLastResult before it's overwritten.
lastResult := FLastResult;
end;
var maxRight: Single := 0;
var maxBottom: Single := 0;
for var control in blockNode.Controls do
begin
if control is TAuraNode then
begin
for expression in Node.Expressions do
expression.Accept(Visitor);
childLastResult := (Visitor as TAstToAuraNodeVisitor).FLastResult;
end
);
blockNode.Frameless := true;
FCurrentPos.Y := blockNode.Position.Y + blockNode.Height + FSpacing.Y;
maxRight := Max(maxRight, control.Position.X + control.Width);
maxBottom := Max(maxBottom, control.Position.Y + control.Height);
end;
end;
blockNode.Width := Max(blockNode.Width, maxRight + FSpacing.X);
blockNode.Height := Max(blockNode.Height, maxBottom + FSpacing.Y);
finally
// Restore the visitor's state.
FParentControl := oldParentControl;
FCurrentPos := oldPos;
FCurrentPos.Y := blockNode.Position.Y + blockNode.Height + FSpacing.Y;
end;
FLastResult.LayoutNode := blockNode;
FLastResult.OutputPin := childLastResult.OutputPin;
Result := TDataValue.Void;
// The output pin of a block is the output pin of its last expression.
FLastResult.OutputPin := lastResult.OutputPin;
end;
function TAstToAuraNodeVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
@@ -548,94 +500,6 @@ begin
Result := TDataValue.Void;
end;
function TAstToAuraNodeVisitor.VisitContainerNode(
const Title, Details: string;
const InPin, OutPin: String;
IsExec: Boolean;
HasResult: Boolean;
const ChildVisitorProc: TChildVisitorProc
): TAuraNode;
var
containerNode: TAuraNode;
childVisitor: IAstVisitor;
childStartPos: TPointF;
maxRight: Single;
maxBottom: Single;
control: TControl;
childLastResult: TAuraNodeResult;
begin
const pinNodeHeight = cPinSize + cVerticalPadding;
containerNode := BuildNodeControl(Title, Details);
containerNode.Position.Point := FCurrentPos;
Result := containerNode;
childStartPos :=
TPointF.Create(FSpacing.X, FSpacing.Y + max(IfThen(InPin <> '', pinNodeHeight, 0), IfThen(Title <> '', cVerticalPadding, 0)));
var currExec := FCurrentExec.ToArray;
if InPin <> '' then
begin
var entryNode := BuildNodeControl(InPin, '');
entryNode.Parent := containerNode;
entryNode.Position.Point := TPointF.Create(0, 0);
entryNode.Height := pinNodeHeight;
if IsExec then
begin
var entryPin := CreateEntry(containerNode);
var entryExitPin := CreateExit(entryNode, '');
FinalizeNodeLayout(entryNode);
entryPin.Position.Y := containerNode.AbsoluteToLocal(entryNode.LocalToAbsolute(entryExitPin.Position.Point)).Y;
end
else
begin
FCurrentExec.Clear;
CreateExit(entryNode, '');
FinalizeNodeLayout(entryNode);
end;
end;
childVisitor :=
TAstToAuraNodeVisitor
.Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray, FMode, FResolvedIdentifierCache);
ChildVisitorProc(childVisitor);
childLastResult := (childVisitor as TAstToAuraNodeVisitor).FLastResult;
FCurrentExec.Clear;
FCurrentExec.AddRange((childVisitor as TAstToAuraNodeVisitor).FCurrentExec);
maxRight := 0;
maxBottom := 0;
for control in containerNode.Controls do
begin
if control is TAuraNode then
begin
maxRight := Max(maxRight, control.Position.X + control.Width);
maxBottom := Max(maxBottom, control.Position.Y + control.Height);
end;
end;
containerNode.Width := Max(containerNode.Width, maxRight + FSpacing.X);
containerNode.Height := Max(containerNode.Height, maxBottom + FSpacing.Y);
if OutPin <> '' then
begin
var exitNode := BuildNodeControl(OutPin, '');
exitNode.Parent := containerNode;
exitNode.Height := pinNodeHeight;
var exitPin := CreateEntry(exitNode);
if HasResult and Assigned(childLastResult.OutputPin) then
begin
var dataResultPin := CreateInput(exitNode, 'Result');
FConnections.Add(TPinConnection.Create(childLastResult.OutputPin, dataResultPin));
end;
FinalizeNodeLayout(exitNode);
containerNode.Height := Max(containerNode.Height, maxBottom + FSpacing.Y + exitNode.Height);
exitNode.Position.Point := TPointF.Create(containerNode.Width - exitNode.Width, containerNode.Height - exitNode.Height);
if not IsExec then
begin
FCurrentExec.Clear;
FCurrentExec.AddRange(currExec);
end
else
CreateExit(containerNode, '').Position.Y := containerNode.AbsoluteToLocal(exitNode.LocalToAbsolute(exitPin.Position.Point)).Y;
end;
FinalizeNodeLayout(containerNode);
end;
function TAstToAuraNodeVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
var
seriesNode: TAuraNode;
@@ -702,19 +566,21 @@ var
existingResult: TAuraNodeResult;
identifierNode: TAuraNode;
begin
if Node.IsResolved then
if Node.Address.Kind <> akUnresolved then
begin
if FResolvedIdentifierCache.TryGetValue(Node.Address, existingResult) then
// FindNodeForAddress now handles all cases: local cache, on-demand parameter creation, and recursive parent/upvalue lookup.
if FindNodeForAddress(Node.Address, existingResult) then
begin
FLastResult := existingResult;
end
else
begin
identifierNode := CreateNodeControl('Identifier', Node.Name);
// If the lookup still fails, it's a genuine unresolved variable.
identifierNode := CreateNodeControl('Identifier (unresolved!)', Node.Name);
identifierNode.BorderColor := TAlphaColors.Red;
FLastResult.LayoutNode := identifierNode;
FLastResult.OutputPin := CreateOutput(identifierNode);
FinalizeNodeLayout(identifierNode);
FResolvedIdentifierCache.Add(Node.Address, FLastResult);
end;
end
else
@@ -838,12 +704,56 @@ begin
Result := TDataValue.Void;
end;
function TAstToAuraNodeVisitor.FindNodeForAddress(const Address: TResolvedAddress; out NodeResult: TAuraNodeResult): Boolean;
var
originalAddress: TResolvedAddress;
targetVisitor: TAstToAuraNodeVisitor;
i: Integer;
begin
if (Address.Kind = akUpvalue) and Assigned(FCurrentLambda) then
begin
if Address.SlotIndex < Length(FCurrentLambda.Upvalues) then
begin
originalAddress := FCurrentLambda.Upvalues[Address.SlotIndex];
if Assigned(FParentVisitor) then
exit(FParentVisitor.FindNodeForAddress(originalAddress, NodeResult));
end;
exit(False);
end;
if (Address.Kind = akLocalOrParent) then
begin
targetVisitor := Self;
for i := 1 to Address.ScopeDepth do
begin
if not Assigned(targetVisitor) then
exit(False);
targetVisitor := targetVisitor.FParentVisitor;
end;
if Assigned(targetVisitor) and (Address.SlotIndex < Length(targetVisitor.FSlotCache)) then
begin
NodeResult := targetVisitor.FSlotCache[Address.SlotIndex];
// An entry is valid if a layout node has been created for it.
exit(Assigned(NodeResult.LayoutNode));
end;
end;
Result := False;
end;
function TAstToAuraNodeVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
var
paramStr: String;
lambdaNode: TAuraNode;
i: Integer;
childVisitor: TAstToAuraNodeVisitor;
childStartPos: TPointF;
maxRight, maxBottom: Single;
control: TControl;
childLastResult: TAuraNodeResult;
entryNode, exitNode: TAuraNode;
begin
// Parameter string for the title
paramStr := '(';
if Length(Node.Parameters) > 0 then
begin
@@ -852,20 +762,84 @@ begin
paramStr := paramStr + ', ' + Node.Parameters[i].Name;
end;
paramStr := paramStr + ')';
lambdaNode :=
VisitContainerNode(
#$03BB,
paramStr,
'call',
'return',
false,
true,
procedure(const Visitor: IAstVisitor) begin Node.Body.Accept(Visitor); end
);
// --- Inlined logic from former VisitContainerNode ---
// 1. Create container node for the lambda.
lambdaNode := BuildNodeControl(#$03BB, paramStr);
lambdaNode.Position.Point := FCurrentPos;
// 2. Create internal 'call' entry point.
const pinNodeHeight = cPinSize + cVerticalPadding;
entryNode := BuildNodeControl('call', '');
entryNode.Parent := lambdaNode;
entryNode.Position.Point := TPointF.Create(0, 0);
entryNode.Height := pinNodeHeight;
// A lambda expression is a value, it doesn't connect to the parent execution flow.
// Its internal flow starts with the exit of the 'call' node.
var internalExec: TArray<TControl> := [CreateExit(entryNode, '')];
FinalizeNodeLayout(entryNode);
// 3. Create child visitor for the new scope.
childStartPos := TPointF.Create(FSpacing.X, FSpacing.Y + pinNodeHeight);
childVisitor :=
TAstToAuraNodeVisitor
.Create(FWorkspace, lambdaNode, childStartPos, FConnections, internalExec, FMode, Self, Node, Node.ScopeDescriptor);
FCurrentExec.Clear;
// 4. Let the child visitor create parameter nodes and visit the lambda body.
for var param in Node.Parameters do
begin
if param.Address.Kind <> akUnresolved then
begin
var paramNode := childVisitor.CreateNodeControl('Parameter', param.Name);
var paramResult: TAuraNodeResult;
paramResult.LayoutNode := paramNode;
paramResult.OutputPin := childVisitor.CreateOutput(paramNode);
childVisitor.FinalizeNodeLayout(paramNode);
childVisitor.FSlotCache[param.Address.SlotIndex] := paramResult;
end;
end;
Node.Body.Accept(childVisitor);
childLastResult := childVisitor.FLastResult;
// 5. Resize container to fit all internally generated nodes.
maxRight := 0;
maxBottom := 0;
for control in lambdaNode.Controls do
begin
if control is TAuraNode then
begin
maxRight := Max(maxRight, control.Position.X + control.Width);
maxBottom := Max(maxBottom, control.Position.Y + control.Height);
end;
end;
lambdaNode.Width := Max(lambdaNode.Width, maxRight + FSpacing.X);
lambdaNode.Height := Max(lambdaNode.Height, maxBottom + FSpacing.Y);
// 6. Create internal 'return' exit point.
exitNode := BuildNodeControl('return', '');
exitNode.Parent := lambdaNode;
exitNode.Height := pinNodeHeight;
// Connect the execution flow from the lambda body to the 'return' node's entry.
childVisitor.CreateEntry(exitNode, true);
// Connect the data flow (the result of the last expression) to the 'return' node.
if Assigned(childLastResult.OutputPin) then
begin
var dataResultPin := childVisitor.CreateInput(exitNode, 'Result');
FConnections.Add(TPinConnection.Create(childLastResult.OutputPin, dataResultPin));
end;
childVisitor.FinalizeNodeLayout(exitNode);
lambdaNode.Height := Max(lambdaNode.Height, maxBottom + FSpacing.Y + exitNode.Height);
exitNode.Position.Point := TPointF.Create(lambdaNode.Width - exitNode.Width, lambdaNode.Height - exitNode.Height);
// 7. Finalize the main visitor state.
FCurrentPos.Y := lambdaNode.Position.Y + lambdaNode.Height + FSpacing.Y;
FLastResult.LayoutNode := lambdaNode;
FLastResult.OutputPin := CreateOutput(lambdaNode);
FLastResult.OutputPin := CreateOutput(lambdaNode); // The lambda itself is an expression that yields a value.
FinalizeNodeLayout(lambdaNode);
Result := TDataValue.Void;
end;
@@ -1048,10 +1022,74 @@ begin
Result := TDataValue.Void;
end;
function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
var
details: string;
assignmentNode: TAuraNode;
oldParentControl: TControl;
oldPos: TPointF;
control: TControl;
begin
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
begin
assignmentNode := CreateNodeControl('Assignment', details);
CreateEntry(assignmentNode);
CreateExit(assignmentNode, '');
FinalizeNodeLayout(assignmentNode);
end
else
begin
// 1. Container-Knoten erstellen.
assignmentNode := BuildNodeControl('Assignment', Node.Identifier.Name);
assignmentNode.Position.Point := FCurrentPos;
// 2. Visitor temporär auf den Container als Parent umleiten.
oldParentControl := FParentControl;
oldPos := FCurrentPos;
try
FParentControl := assignmentNode;
FCurrentPos := TPointF.Create(FSpacing.X, assignmentNode.TitleFont.Size * 1.8 + FSpacing.Y);
// 3. Den Zuweisungswert mit dem *aktuellen* Visitor (Self) verarbeiten. Es wird kein neuer Scope erzeugt.
Node.Value.Accept(Self);
// 4. Container-Größe an den Inhalt anpassen.
var maxRight: Single := 0;
var maxBottom: Single := 0;
for control in assignmentNode.Controls do
begin
if control is TAuraNode then
begin
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);
finally
// 5. Visitor-Zustand wiederherstellen.
FParentControl := oldParentControl;
FCurrentPos := oldPos;
end;
// 6. Pins anbringen und Layout finalisieren.
CreateEntry(assignmentNode);
CreateExit(assignmentNode, '');
FLastResult.OutputPin := CreateOutput(assignmentNode);
FinalizeNodeLayout(assignmentNode);
// 7. FCurrentPos für den nächsten Knoten korrekt setzen.
FCurrentPos.Y := assignmentNode.Position.Y + assignmentNode.Height + FSpacing.Y;
end;
Result := TDataValue.Void;
end;
function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
var
details: string;
varDeclNode: TAuraNode;
oldParentControl: TControl;
oldPos: TPointF;
control: TControl;
begin
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
@@ -1063,39 +1101,54 @@ begin
end
else
begin
// 1. Container-Knoten erstellen.
varDeclNode := BuildNodeControl('VarDecl', Node.Identifier.Name);
varDeclNode.Position.Point := FCurrentPos;
if Assigned(Node.Initializer) then
begin
varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
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);
var maxRight: Single := 0;
var maxBottom: Single := 0;
for control in varDeclNode.Controls do
begin
if control is TAuraNode then
// 2. Visitor temporär auf den Container als Parent umleiten.
oldParentControl := FParentControl;
oldPos := FCurrentPos;
try
FParentControl := varDeclNode;
FCurrentPos := TPointF.Create(FSpacing.X, varDeclNode.TitleFont.Size * 1.8 + FSpacing.Y);
// 3. Den Initialisierungsausdruck mit dem *aktuellen* Visitor (Self) verarbeiten.
Node.Initializer.Accept(Self);
// 4. Container-Größe an den Inhalt anpassen.
var maxRight: Single := 0;
var maxBottom: Single := 0;
for control in varDeclNode.Controls do
begin
maxRight := Max(maxRight, control.Position.X + control.Width);
maxBottom := Max(maxBottom, control.Position.Y + control.Height);
if control is TAuraNode then
begin
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);
finally
// 5. Visitor-Zustand wiederherstellen.
FParentControl := oldParentControl;
FCurrentPos := oldPos;
end;
varDeclNode.Width := Max(varDeclNode.Width, maxRight + FSpacing.X);
varDeclNode.Height := Max(varDeclNode.Height, maxBottom + FSpacing.Y);
CreateEntry(varDeclNode);
CreateExit(varDeclNode, '');
FLastResult.OutputPin := CreateOutput(varDeclNode);
FinalizeNodeLayout(varDeclNode);
FCurrentPos.Y := varDeclNode.Position.Y + varDeclNode.Height + FSpacing.Y;
end
else
begin
varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
CreateEntry(varDeclNode);
CreateExit(varDeclNode, '');
FLastResult.OutputPin := CreateOutput(varDeclNode);
FinalizeNodeLayout(varDeclNode);
end;
// 6. Pins anbringen und Layout finalisieren.
CreateEntry(varDeclNode);
CreateExit(varDeclNode, '');
FLastResult.OutputPin := CreateOutput(varDeclNode);
FinalizeNodeLayout(varDeclNode);
// 7. FCurrentPos für den nächsten Knoten korrekt setzen.
FCurrentPos.Y := varDeclNode.Position.Y + varDeclNode.Height + FSpacing.Y;
// 8. Die neue Variable im Cache des aktuellen Scopes registrieren.
if Node.Identifier.Address.Kind <> akUnresolved then
FSlotCache[Node.Identifier.Address.SlotIndex] := FLastResult;
end;
Result := TDataValue.Void;
end;