From 7c531c12072695f8b51950eeccafdc31acfe97ed Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sat, 30 Aug 2025 16:52:09 +0200 Subject: [PATCH] AST development --- ASTPlayground/DraggablePanel.pas | 21 +- ASTPlayground/Myc.Ast.Visualizer.pas | 825 +++++++++++++-------------- 2 files changed, 411 insertions(+), 435 deletions(-) diff --git a/ASTPlayground/DraggablePanel.pas b/ASTPlayground/DraggablePanel.pas index 91c1047..99013fd 100644 --- a/ASTPlayground/DraggablePanel.pas +++ b/ASTPlayground/DraggablePanel.pas @@ -14,10 +14,7 @@ uses FMX.Objects; type - /// - /// A panel that can contain other controls and can be moved by dragging its background. - /// It custom-paints its own border and title. - /// + // A movable panel with a custom-painted border and title. TAuraNode = class(TStyledControl) private // Flag to indicate if the control is currently being dragged. @@ -190,45 +187,45 @@ end; procedure TAuraNode.Paint; var - LRect, LTitleRect: TRectF; + rect, titleRect: TRectF; begin inherited; // Allow styled painting to occur first (if any) // Custom painting for the border if (FBorderWidth > 0) and (FBorderColor <> TAlphaColors.Null) then begin - LRect := TRectF.Create(0, 0, Width, Height); + rect := TRectF.Create(0, 0, Width, Height); // Inflate inwards so the border is fully visible within the control's bounds - LRect.Inflate(-FBorderWidth / 2, -FBorderWidth / 2); + rect.Inflate(-FBorderWidth / 2, -FBorderWidth / 2); Canvas.Stroke.Kind := TBrushKind.Solid; Canvas.Stroke.Color := FBorderColor; Canvas.Stroke.Thickness := FBorderWidth; // Use DrawRoundRect to support both sharp and rounded corners - Canvas.DrawRect(LRect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round); + Canvas.DrawRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round); end; // Draw the title if not FTitle.IsEmpty then begin // Define the rectangle for the title at the top of the control - LTitleRect := TRectF.Create(0, FBorderWidth, Width, FBorderWidth + FTitleFont.Size * 1.8); + titleRect := TRectF.Create(0, FBorderWidth, Width, FBorderWidth + FTitleFont.Size * 1.8); Canvas.Font.Assign(FTitleFont); Canvas.Fill.Color := FTitleFontColor; // Draw the text centered horizontally and vertically within the title rectangle - Canvas.FillText(LTitleRect, FTitle, False, 1.0, [], TTextAlign.Center, TTextAlign.Center); + Canvas.FillText(titleRect, FTitle, False, 1.0, [], TTextAlign.Center, TTextAlign.Center); end; end; procedure TAuraNode.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); begin inherited; - if (Button = TMouseButton.mbLeft) then + if Button = TMouseButton.mbLeft then begin - if (ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl) then + if ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl then begin FIsDragging := True; FDownPos := TPointF.Create(X, Y); diff --git a/ASTPlayground/Myc.Ast.Visualizer.pas b/ASTPlayground/Myc.Ast.Visualizer.pas index 98498ba..843ac04 100644 --- a/ASTPlayground/Myc.Ast.Visualizer.pas +++ b/ASTPlayground/Myc.Ast.Visualizer.pas @@ -29,6 +29,13 @@ type TAuraWorkspace = class; TAstToAuraNodeVisitor = class(TInterfacedObject, IAstVisitor) + public + type + TChildVisitorProc = reference to procedure(const Visitor: IAstVisitor); + TAuraNodeList = TList; + TCreateParentNodeProc = reference to function(const InputNodes: TAuraNodeList): TAuraNode; + TVerticalAlignment = (vaCenter, vaTop); + private const // Pin Visuals cPinSize = 10; @@ -51,22 +58,31 @@ type FLastNode: TAuraNode; FConnections: TList; FCurrentExec: TControl; - function CreateNodeControl(const ATitle, ADetails: string): TAuraNode; + + function VisitContainerNode(const Title, Details: string; const ChildVisitorProc: TChildVisitorProc): TAuraNode; + function VisitOperatorNode( + const InputExpressions: TArray; + const CreateParentProc: TCreateParentNodeProc; + Alignment: TVerticalAlignment = vaCenter + ): TAuraNode; + + function BuildNodeControl(const Title, Details: string): TAuraNode; + function CreateNodeControl(const Title, Details: string): TAuraNode; function CreatePin( - AParentNode: TAuraNode; - AShape: TPinShape; - AAlignment: TPinAlignment; - AColor: TAlphaColor; - const ATag: string; - AOffsetY: Single = 0 + ParentNode: TAuraNode; + Shape: TPinShape; + Alignment: TPinAlignment; + Color: TAlphaColor; + const Tag: string; + OffsetY: Single = 0 ): TShape; - function CreateInput(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl; - function CreateOutput(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl; - function CreateEntry(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl; - function CreateExit(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl; + function CreateInput(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl; + function CreateOutput(ParentNode: TAuraNode; OffsetY: Single = 0): TControl; + function CreateEntry(ParentNode: TAuraNode; OffsetY: Single = 0): TControl; + function CreateExit(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl; - function FindPinByTag(AParentNode: TAuraNode; const ATag: string): TControl; + function FindPinByTag(ParentNode: TAuraNode; const Tag: string): TControl; function ConnectData(InputPin: TControl; OutputNode: TAuraNode): TControl; public constructor Create( @@ -167,6 +183,34 @@ begin FConnections := AConnections; end; +function TAstToAuraNodeVisitor.BuildNodeControl(const Title, Details: string): TAuraNode; +var + fullTitle: string; + textWidth: Single; + newWidth: Single; + measureCanvas: TCanvas; +begin + Result := TAuraNode.Create(FParentControl); + Result.Parent := FParentControl; + + // Build the full title string first + fullTitle := Title; + if not Details.IsEmpty then + fullTitle := fullTitle + ': ' + Details; + Result.Title := fullTitle; + + // Adjust the width so that the text is completely visible. + measureCanvas := TCanvasManager.MeasureCanvas; + if Assigned(measureCanvas) then + begin + measureCanvas.Font.Assign(Result.TitleFont); + textWidth := measureCanvas.TextWidth(Result.Title); + newWidth := textWidth + (2 * cHorizontalPadding); + // Ensure the new width is not smaller than the default width of the node. + Result.Width := Max(Result.Width, newWidth); + end; +end; + function TAstToAuraNodeVisitor.ConnectData(InputPin: TControl; OutputNode: TAuraNode): TControl; begin Result := FindPinByTag(OutputNode, 'data.out'); @@ -174,367 +218,291 @@ begin FConnections.Add(TPinConnection.Create(Result, InputPin)); end; -function TAstToAuraNodeVisitor.CreateInput(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl; +function TAstToAuraNodeVisitor.CreateInput(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl; begin - var cap := ACaption; + var cap := Caption; if cap <> '' then cap := '.' + cap; - Result := CreatePin(AParentNode, psCircle, paLeft, cDataPinColor, 'data.in' + cap, AOffsetY); - Result.Hint := ACaption; - Result.ShowHint := ACaption <> ''; + Result := CreatePin(ParentNode, psCircle, paLeft, cDataPinColor, 'data.in' + cap, OffsetY); + Result.Hint := Caption; + Result.ShowHint := Caption <> ''; end; -function TAstToAuraNodeVisitor.CreateOutput(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl; +function TAstToAuraNodeVisitor.CreateNodeControl(const Title, Details: string): TAuraNode; begin - Result := CreatePin(AParentNode, psCircle, paRight, cDataPinColor, 'data.out', AOffsetY); -end; - -function TAstToAuraNodeVisitor.CreateEntry(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl; -begin - Result := CreatePin(AParentNode, psTriangle, paLeft, cExecPinColor, 'exec.in', AOffsetY); - if FCurrentExec <> nil then - FConnections.Add(TPinConnection.Create(FCurrentExec, Result)); -end; - -function TAstToAuraNodeVisitor.CreateExit(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl; -begin - var cap := ACaption; - if cap <> '' then - cap := '.' + cap; - Result := CreatePin(AParentNode, psTriangle, paRight, cExecPinColor, 'exec.out' + cap, AOffsetY); - - FCurrentExec := Result; -end; - -function TAstToAuraNodeVisitor.CreateNodeControl(const ATitle, ADetails: string): TAuraNode; -var - LFullTitle: string; - LTextWidth: Single; - LNewWidth: Single; - LMeasureCanvas: TCanvas; -begin - Result := TAuraNode.Create(FParentControl); - Result.Parent := FParentControl; - - // Build the full title string first - LFullTitle := ATitle; - if not ADetails.IsEmpty then - LFullTitle := LFullTitle + ': ' + ADetails; - Result.Title := LFullTitle; - + Result := BuildNodeControl(Title, Details); Result.Position.Point := FCurrentPos; - // Adjust the width so that the text and pins are completely visible. - LMeasureCanvas := TCanvasManager.MeasureCanvas; - if Assigned(LMeasureCanvas) then - begin - LMeasureCanvas.Font.Assign(Result.TitleFont); - LTextWidth := LMeasureCanvas.TextWidth(Result.Title); - LNewWidth := LTextWidth + (2 * cHorizontalPadding); - // Ensure the new width is not smaller than the default width of the node. - Result.Width := Max(Result.Width, LNewWidth); - end; - // Advance vertical position for the next node FCurrentPos.Y := FCurrentPos.Y + Result.Height + FSpacing.Y; FLastNode := Result; end; +function TAstToAuraNodeVisitor.CreateOutput(ParentNode: TAuraNode; OffsetY: Single = 0): TControl; +begin + Result := CreatePin(ParentNode, psCircle, paRight, cDataPinColor, 'data.out', OffsetY); +end; + +function TAstToAuraNodeVisitor.CreateEntry(ParentNode: TAuraNode; OffsetY: Single = 0): TControl; +begin + Result := CreatePin(ParentNode, psTriangle, paLeft, cExecPinColor, 'exec.in', OffsetY); + if FCurrentExec <> nil then + FConnections.Add(TPinConnection.Create(FCurrentExec, Result)); +end; + +function TAstToAuraNodeVisitor.CreateExit(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl; +begin + var cap := Caption; + if cap <> '' then + cap := '.' + cap; + Result := CreatePin(ParentNode, psTriangle, paRight, cExecPinColor, 'exec.out' + cap, OffsetY); + + FCurrentExec := Result; +end; + function TAstToAuraNodeVisitor.CreatePin( - AParentNode: TAuraNode; - AShape: TPinShape; - AAlignment: TPinAlignment; - AColor: TAlphaColor; - const ATag: string; - AOffsetY: Single = 0 + ParentNode: TAuraNode; + Shape: TPinShape; + Alignment: TPinAlignment; + Color: TAlphaColor; + const Tag: string; + OffsetY: Single = 0 ): TShape; var - LPosX, LPosY: Single; + posX, posY: Single; begin // Calculate vertical position (centered + offset) - LPosY := (AParentNode.Height / 2) - (cPinSize / 2) + AOffsetY; + posY := (ParentNode.Height / 2) - (cPinSize / 2) + OffsetY; // Calculate horizontal position - if (AAlignment = paLeft) then - LPosX := 0 + if Alignment = paLeft then + posX := 0 else // paRight - LPosX := AParentNode.Width - cPinSize; + posX := ParentNode.Width - cPinSize; // Create the shape - case AShape of + case Shape of psCircle: begin - var LCircle := TEllipse.Create(AParentNode); - LCircle.Fill.Color := AColor; - LCircle.Stroke.Kind := TBrushKind.None; - Result := LCircle; + var circle := TEllipse.Create(ParentNode); + circle.Fill.Color := Color; + circle.Stroke.Kind := TBrushKind.None; + Result := circle; end; psTriangle: begin - var LTriangle := TPath.Create(AParentNode); - LTriangle.Data.Data := Format('M 0 0 L %d %d L 0 %d Z', [cPinSize, cPinSize div 2, cPinSize]); - LTriangle.Fill.Color := AColor; - LTriangle.Stroke.Kind := TBrushKind.None; - Result := LTriangle; + var triangle := TPath.Create(ParentNode); + triangle.Data.Data := Format('M 0 0 L %d %d L 0 %d Z', [cPinSize, cPinSize div 2, cPinSize]); + triangle.Fill.Color := Color; + triangle.Stroke.Kind := TBrushKind.None; + Result := triangle; end; else Result := nil; - Exit; + exit; end; // Common properties - Result.Parent := AParentNode; - Result.SetBounds(LPosX, LPosY, cPinSize, cPinSize); + Result.Parent := ParentNode; + Result.SetBounds(posX, posY, cPinSize, cPinSize); Result.HitTest := true; // Use TagString to identify the control as a pin and describe its function. - Result.TagString := ATag; + Result.TagString := Tag; end; -function TAstToAuraNodeVisitor.FindPinByTag(AParentNode: TAuraNode; const ATag: string): TControl; +function TAstToAuraNodeVisitor.FindPinByTag(ParentNode: TAuraNode; const Tag: string): TControl; var - LControl: TControl; + control: TControl; begin Result := nil; - if not Assigned(AParentNode) then - Exit; + if not Assigned(ParentNode) then + exit; - for LControl in AParentNode.Controls do + for control in ParentNode.Controls do begin - if (LControl.TagString = ATag) then + if control.TagString = Tag then begin - Result := LControl; - Exit; + Result := control; + exit; end; end; end; function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; -var - LAssignmentNode, LValueNode: TAuraNode; - LInputPin: TControl; - LStartPosition: TPointF; begin - LStartPosition := FCurrentPos; + VisitOperatorNode( + [Node.Value], + function(const InputNodes: TAuraNodeList): TAuraNode + var + assignmentNode, valueNode: TAuraNode; + inputPin: TControl; + begin + valueNode := InputNodes[0]; + assignmentNode := BuildNodeControl('Assignment', Node.Identifier.Name); - // Visit the value expression first - Node.Value.Accept(Self); - LValueNode := FLastNode; - - // Reposition for the assignment node - FCurrentPos.X := LValueNode.Position.X + LValueNode.Width + FSpacing.X; - FCurrentPos.Y := LValueNode.Position.Y; - - // Create the assignment node and its pins - LAssignmentNode := CreateNodeControl('Assignment', Node.Identifier.Name); - CreateEntry(LAssignmentNode, 0); - CreateExit(LAssignmentNode, '', 0); - LInputPin := CreateInput(LAssignmentNode, Node.Identifier.Name, 0); - - // Connect the value to the assignment - ConnectData(LInputPin, LValueNode); - - // Finalize visitor state - FCurrentPos.X := LStartPosition.X; - FCurrentPos.Y := Max(LValueNode.Position.Y + LValueNode.Height, LAssignmentNode.Position.Y + LAssignmentNode.Height) + FSpacing.Y; - FLastNode := LAssignmentNode; + CreateEntry(assignmentNode, 0); + CreateExit(assignmentNode, '', 0); + inputPin := CreateInput(assignmentNode, Node.Identifier.Name, 0); + ConnectData(inputPin, valueNode); + Result := assignmentNode; + end, + vaTop + ); Result := TAstValue.Void; end; function TAstToAuraNodeVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue; -var - LBinaryExprNode, LLeftNode, LRightNode: TAuraNode; - LInputPin1, LInputPin2: TControl; - LStartPosition: TPointF; - LRightEndY, LMaxChildWidth: Single; begin - LStartPosition := FCurrentPos; + VisitOperatorNode( + [Node.Left, Node.Right], + function(const InputNodes: TAuraNodeList): TAuraNode + var + binaryExprNode, leftNode, rightNode: TAuraNode; + inputPin1, inputPin2: TControl; + begin + leftNode := InputNodes[0]; + rightNode := InputNodes[1]; - // First, visit the child nodes to create their visual representation - // Left branch - Node.Left.Accept(Self); - LLeftNode := FLastNode; + binaryExprNode := BuildNodeControl(Node.Operator.ToString, ''); + binaryExprNode.TitleFont.Size := 20; - // Right branch starts below the left branch - Node.Right.Accept(Self); - LRightNode := FLastNode; - LRightEndY := FCurrentPos.Y; + inputPin1 := CreateInput(binaryExprNode, '', -8); // Caption is in Hint + inputPin2 := CreateInput(binaryExprNode, '', 8); + CreateOutput(binaryExprNode, 0); - // Now, create the node for the binary operation itself - LMaxChildWidth := Max(LLeftNode.Position.X + LLeftNode.Width, LRightNode.Position.X + LRightNode.Width); - FCurrentPos.X := LMaxChildWidth + FSpacing.X; - - // Vertically center the binary expression node between its children - var LTotalChildHeight := LRightEndY - LStartPosition.Y; - var LNodeCenterY := LStartPosition.Y + (LTotalChildHeight / 2); - FCurrentPos.Y := LNodeCenterY - (cDefaultNodeHeight / 2); - - LBinaryExprNode := CreateNodeControl(Node.Operator.ToString, ''); - LBinaryExprNode.TitleFont.Size := 20; - - // Create pins for the binary expression node - LInputPin1 := CreateInput(LBinaryExprNode, LLeftNode.Name, -8); - LInputPin2 := CreateInput(LBinaryExprNode, LRightNode.Name, 8); - CreateOutput(LBinaryExprNode, 0); - - // Connect the output of the left child to the first input of the binary node - ConnectData(LInputPin1, LLeftNode); - - // Connect the output of the right child to the second input of the binary node - ConnectData(LInputPin2, LRightNode); - - // Finalize visitor state for the next node at this level - FCurrentPos.X := LStartPosition.X; - FCurrentPos.Y := Max(LRightEndY, LBinaryExprNode.Position.Y + LBinaryExprNode.Height + FSpacing.Y); - FLastNode := LBinaryExprNode; + ConnectData(inputPin1, leftNode); + ConnectData(inputPin2, rightNode); + Result := binaryExprNode; + end + ); Result := TAstValue.Void; end; function TAstToAuraNodeVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; var - Expression: IExpressionNode; - LBlockNode: TAuraNode; - LChildVisitor: IAstVisitor; - LChildStartPos: TPointF; - LMaxRight: Single; - LMaxBottom: Single; - LControl: TControl; + blockNode: TAuraNode; begin - // A block node visually encloses all its child nodes. - // Create the container node for the block. Its size will be adjusted later. - LBlockNode := TAuraNode.Create(FParentControl); - LBlockNode.Parent := FParentControl; - LBlockNode.Title := 'Block'; - LBlockNode.Position.Point := FCurrentPos; + // Use the helper to create and populate the container node. + blockNode := + VisitContainerNode( + 'Block', + '', + procedure(const Visitor: IAstVisitor) + var + expression: IExpressionNode; + begin + for expression in Node.Expressions do + expression.Accept(Visitor); + end + ); - // Create a new visitor for the child nodes. The children will be parented - // to LBlockNode and positioned relative to it, starting below the title. - LChildStartPos := TPointF.Create(FSpacing.X, FSpacing.Y + LBlockNode.TitleFont.Size * 1.8); - LChildVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, LBlockNode, LChildStartPos, FConnections); - - // Visit all expressions within the block using the new visitor. - for Expression in Node.Expressions do - begin - Expression.Accept(LChildVisitor); - end; - - // Adjust the size of the block node to encompass all its children. - LMaxRight := 0; - LMaxBottom := 0; - for LControl in LBlockNode.Controls do - begin - if (LControl is TAuraNode) then - begin - LMaxRight := Max(LMaxRight, LControl.Position.X + LControl.Width); - LMaxBottom := Max(LMaxBottom, LControl.Position.Y + LControl.Height); - end; - end; - - // Set the final size with some internal padding. - LBlockNode.Width := Max(LBlockNode.Width, LMaxRight + FSpacing.X); - LBlockNode.Height := Max(LBlockNode.Height, LMaxBottom + FSpacing.Y); - - // Update the current Y position of the main visitor to be below the now correctly sized block node. - FCurrentPos.Y := LBlockNode.Position.Y + LBlockNode.Height + FSpacing.Y; - FLastNode := LBlockNode; + // Update the state of the main visitor + FCurrentPos.Y := blockNode.Position.Y + blockNode.Height + FSpacing.Y; + FLastNode := blockNode; Result := TAstValue.Void; end; function TAstToAuraNodeVisitor.VisitConstant(const Node: IConstantNode): TAstValue; var - LConstantNode: TAuraNode; + constantNode: TAuraNode; begin // A constant has a data output pin. // Data outputs are round and on the right side of the box. - LConstantNode := CreateNodeControl('Constant', Node.Value.ToString); - CreateOutput(LConstantNode, 0); + constantNode := CreateNodeControl('Constant', Node.Value.ToString); + CreateOutput(constantNode, 0); Result := TAstValue.Void; end; +function TAstToAuraNodeVisitor.VisitContainerNode(const Title, Details: string; const ChildVisitorProc: TChildVisitorProc): TAuraNode; +var + containerNode: TAuraNode; + childVisitor: IAstVisitor; + childStartPos: TPointF; + maxRight: Single; + maxBottom: Single; + control: TControl; +begin + // Step 1: Create container node + containerNode := BuildNodeControl(Title, Details); + containerNode.Position.Point := FCurrentPos; + Result := containerNode; // Set result early + + // Step 2: Create child visitor and run the closure. + childStartPos := TPointF.Create(FSpacing.X, FSpacing.Y + containerNode.TitleFont.Size * 1.8); + childVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, containerNode, childStartPos, FConnections); + ChildVisitorProc(childVisitor); + + // Step 3: Adjust the size of the container node to fit children. + 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; + // The width must be at least the title width, and large enough for children. + containerNode.Width := Max(containerNode.Width, maxRight + FSpacing.X); + containerNode.Height := Max(containerNode.Height, maxBottom + FSpacing.Y); +end; + function TAstToAuraNodeVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; var - LFuncCallNode: TAuraNode; - LInputResultNodes: TList; - LInputNode: IExpressionNode; - LStartPosition: TPointF; - LEndY, LMaxChildWidth: Single; - i: Integer; - LCalleePin: TControl; - LArgPin: array of TControl; - LCurrentInputOffsetY, LCurrentOutputOffsetY: Single; + inputExpressions: TArray; begin - LStartPosition := FCurrentPos; - LInputResultNodes := TList.Create; - try - // 1. Visit all input nodes first (Callee + Arguments) and collect their visual representations. - Node.Callee.Accept(Self); - LInputResultNodes.Add(FLastNode); + SetLength(inputExpressions, 1 + Node.Arguments.Count); + inputExpressions[0] := Node.Callee; + for var i := 0 to Node.Arguments.Count - 1 do + inputExpressions[i + 1] := Node.Arguments[i]; - for LInputNode in Node.Arguments do + VisitOperatorNode( + inputExpressions, + function(const InputNodes: TAuraNodeList): TAuraNode + var + funcCallNode: TAuraNode; + calleePin: TControl; + argPin: array of TControl; + currentInputOffsetY, currentOutputOffsetY: Single; + i: Integer; begin - LInputNode.Accept(Self); - LInputResultNodes.Add(FLastNode); - end; - LEndY := FCurrentPos.Y; + funcCallNode := BuildNodeControl('FunctionCall', ''); - // 2. Determine the layout bounds of the input nodes. - LMaxChildWidth := 0; - for var LNode in LInputResultNodes do - LMaxChildWidth := Max(LMaxChildWidth, LNode.Position.X + LNode.Width); + var numInputPins := 2 + Node.Arguments.Count; + var numOutputPins := 2; + var maxPins := Max(numInputPins, numOutputPins); + funcCallNode.Height := cVerticalPadding * 2 + (maxPins - 1) * cPinOffsetY; - // 3. Position and create the 'FunctionCall' node to the right of the inputs. - FCurrentPos.X := LMaxChildWidth + FSpacing.X; + currentInputOffsetY := -(numInputPins - 1) / 2.0 * cPinOffsetY; + CreateEntry(funcCallNode, currentInputOffsetY); + currentInputOffsetY := currentInputOffsetY + cPinOffsetY; + calleePin := CreateInput(funcCallNode, 'Callee', currentInputOffsetY); + currentInputOffsetY := currentInputOffsetY + cPinOffsetY; + SetLength(argPin, Node.Arguments.Count); + for i := 0 to Node.Arguments.Count - 1 do + begin + argPin[i] := CreateInput(funcCallNode, 'Arg' + i.ToString, currentInputOffsetY); + currentInputOffsetY := currentInputOffsetY + cPinOffsetY; + end; - // Calculate dynamic height based on the number of pins - var LNumInputPins := 2 + Node.Arguments.Count; - var LNumOutputPins := 2; - var LMaxPins := Max(LNumInputPins, LNumOutputPins); - var LNodeHeight := cVerticalPadding + (LMaxPins - 1) * cPinOffsetY; + currentOutputOffsetY := -(numOutputPins - 1) / 2.0 * cPinOffsetY; + CreateExit(funcCallNode, '', currentOutputOffsetY); + currentOutputOffsetY := currentOutputOffsetY + cPinOffsetY; + CreateOutput(funcCallNode, currentOutputOffsetY); - var LTotalInputHeight := LEndY - LStartPosition.Y; - var LNodeCenterY := LStartPosition.Y + (LTotalInputHeight / 2); - FCurrentPos.Y := LNodeCenterY - (LNodeHeight / 2); + ConnectData(calleePin, InputNodes[0]); + for i := 0 to Node.Arguments.Count - 1 do + ConnectData(argPin[i], InputNodes[i + 1]); - LFuncCallNode := CreateNodeControl('FunctionCall', ''); - LFuncCallNode.Height := LNodeHeight; - - // 4. Create all necessary pins on the 'FunctionCall' node with proper spacing. - // Left side (Inputs) - LCurrentInputOffsetY := -(LNumInputPins - 1) / 2.0 * cPinOffsetY; - CreateEntry(LFuncCallNode, LCurrentInputOffsetY); - LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY; - LCalleePin := CreateInput(LFuncCallNode, LInputResultNodes[0].Name, LCurrentInputOffsetY); - LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY; - SetLength(LArgPin, Node.Arguments.Count); - for i := 0 to Node.Arguments.Count - 1 do - begin - LArgPin[i] := CreateInput(LFuncCallNode, LInputResultNodes[i + 1].Name + i.ToString, LCurrentInputOffsetY); - LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY; - end; - - // Right side (Outputs) - LCurrentOutputOffsetY := -(LNumOutputPins - 1) / 2.0 * cPinOffsetY; - CreateExit(LFuncCallNode, '', LCurrentOutputOffsetY); - LCurrentOutputOffsetY := LCurrentOutputOffsetY + cPinOffsetY; - CreateOutput(LFuncCallNode, LCurrentOutputOffsetY); - - // 5. Connect the inputs. - ConnectData(LCalleePin, LInputResultNodes[0]); - for i := 0 to Node.Arguments.Count - 1 do - ConnectData(LArgPin[i], LInputResultNodes[i + 1]); - - finally - LInputResultNodes.Free; - end; - - // 6. Finalize visitor state. - FCurrentPos.X := LStartPosition.X; - FCurrentPos.Y := Max(LEndY, LFuncCallNode.Position.Y + LFuncCallNode.Height + FSpacing.Y); - FLastNode := LFuncCallNode; + Result := funcCallNode; + end + ); Result := TAstValue.Void; end; @@ -549,123 +517,148 @@ end; function TAstToAuraNodeVisitor.VisitIfExpression(const Node: IIfExpressionNode): TAstValue; var - LIfNode, LConditionNode, LThenNode, LElseNode: TAuraNode; - LConditionInputPin, LThenOutputPin, LElseOutputPin: TControl; - LStartPosition: TPointF; - LConditionEndY, LThenEndY, LElseEndY, LMaxChildWidth: Single; + ifNode: TAuraNode; + conditionInputPin, thenOutputPin, elseOutputPin: TControl; + startPosition: TPointF; + thenEndY, elseEndY: Single; begin - LStartPosition := FCurrentPos; + startPosition := FCurrentPos; - // Place the input node for the condition to the left of the 'If' node. - Node.Condition.Accept(Self); - LConditionNode := FLastNode; - LConditionEndY := FCurrentPos.Y; + // Use helper for the condition part + VisitOperatorNode( + [Node.Condition], + function(const InputNodes: TAuraNodeList): TAuraNode + var + condNode: TAuraNode; + begin + condNode := InputNodes[0]; - // Position and create the 'If' node to the right of the condition's subtree. - LMaxChildWidth := LConditionNode.Position.X + LConditionNode.Width; - FCurrentPos.X := LMaxChildWidth + FSpacing.X; - FCurrentPos.Y := LStartPosition.Y + (LConditionNode.Position.Y + LConditionNode.Height / 2) - LStartPosition.Y - (cIfNodeHeight / 2); + ifNode := BuildNodeControl('If', ''); + ifNode.Height := cIfNodeHeight; + CreateEntry(ifNode, -12); + conditionInputPin := CreateInput(ifNode, 'Condition', 12); + thenOutputPin := CreateExit(ifNode, 'Then', -12); + if Assigned(Node.ElseBranch) then + elseOutputPin := CreateExit(ifNode, 'Else', 12) + else + elseOutputPin := nil; - LIfNode := CreateNodeControl('If', ''); - LIfNode.Height := cIfNodeHeight; - CreateEntry(LIfNode, -12); - LConditionInputPin := CreateInput(LIfNode, 'Condition', 12); - LThenOutputPin := CreateExit(LIfNode, 'Then', -12); - if Assigned(Node.ElseBranch) then - LElseOutputPin := CreateExit(LIfNode, 'Else', 12) - else - LElseOutputPin := nil; - - // Connect the condition's output to the 'If' node's data input. - ConnectData(LConditionInputPin, LConditionNode); + ConnectData(conditionInputPin, condNode); + Result := ifNode; + end + ); + var conditionEndY := FCurrentPos.Y; // Visit the 'Then' and 'Else' execution branches, placing them to the right of the 'If' node. - var LBranchStartX := LIfNode.Position.X + LIfNode.Width + FSpacing.X; + var branchStartX := ifNode.Position.X + ifNode.Width + FSpacing.X; // Then branch - FCurrentPos.X := LBranchStartX; - FCurrentPos.Y := LStartPosition.Y; + FCurrentPos.X := branchStartX; + FCurrentPos.Y := startPosition.Y; Node.ThenBranch.Accept(Self); - LThenNode := FLastNode; - LThenEndY := FCurrentPos.Y; - // ConnectData(FindPinByTag(LThenNode, 'Pin.Exec.In'), LIfNode, 'Pin.Exec.Out.Then'); + thenEndY := FCurrentPos.Y; // Else branch (if it exists) - LElseEndY := LThenEndY; + elseEndY := thenEndY; if Assigned(Node.ElseBranch) then begin - FCurrentPos.X := LBranchStartX; - FCurrentPos.Y := LThenEndY; + FCurrentPos.X := branchStartX; + FCurrentPos.Y := thenEndY; Node.ElseBranch.Accept(Self); - LElseNode := FLastNode; - LElseEndY := FCurrentPos.Y; - // ConnectData(FindPinByTag(LElseNode, 'Pin.Exec.In'), LIfNode, 'Pin.Exec.Out.Else'); + elseEndY := FCurrentPos.Y; end; // Finalize visitor state. - FCurrentPos.X := LStartPosition.X; - FCurrentPos.Y := Max(LConditionEndY, LElseEndY); - FLastNode := LIfNode; + FCurrentPos.X := startPosition.X; + FCurrentPos.Y := Max(conditionEndY, elseEndY); + FLastNode := ifNode; Result := TAstValue.Void; end; function TAstToAuraNodeVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue; var - ParamStr: String; - LLambdaNode: TAuraNode; - LChildVisitor: IAstVisitor; - LChildStartPos: TPointF; - LMaxRight, LMaxBottom: Single; - LControl: TControl; + paramStr: String; + lambdaNode: TAuraNode; i: Integer; begin // Prepare the parameter string for the title - ParamStr := '('; + paramStr := '('; if Length(Node.Parameters) > 0 then begin - ParamStr := ParamStr + Node.Parameters[0].Name; + paramStr := paramStr + Node.Parameters[0].Name; for i := 1 to High(Node.Parameters) do - ParamStr := ParamStr + ', ' + Node.Parameters[i].Name; + paramStr := paramStr + ', ' + Node.Parameters[i].Name; end; - ParamStr := ParamStr + ')'; + paramStr := paramStr + ')'; - // Create the container node for the lambda. Its size will be adjusted later. - LLambdaNode := CreateNodeControl('Lambda', ParamStr); + // Use the helper to create and populate the container node. + lambdaNode := VisitContainerNode('Lambda', paramStr, procedure(const Visitor: IAstVisitor) begin Node.Body.Accept(Visitor); end); - // Create a new visitor for the child nodes (the lambda body). - // The children will be parented to LLambdaNode and positioned relative to it. - LChildStartPos := TPointF.Create(FSpacing.X, FSpacing.Y + LLambdaNode.TitleFont.Size * 1.8); - LChildVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, LLambdaNode, LChildStartPos, FConnections); + CreateOutput(lambdaNode, 0); - // Visit the body expression using the new visitor. - Node.Body.Accept(LChildVisitor); - - // Adjust the size of the lambda node to encompass its body. - LMaxRight := 0; - LMaxBottom := 0; - for LControl in LLambdaNode.Controls do - begin - if (LControl is TAuraNode) then - begin - LMaxRight := Max(LMaxRight, LControl.Position.X + LControl.Width); - LMaxBottom := Max(LMaxBottom, LControl.Position.Y + LControl.Height); - end; - end; - - // Set the final size with some internal padding. - LLambdaNode.Width := Max(LLambdaNode.Width, LMaxRight + FSpacing.X); - LLambdaNode.Height := Max(LLambdaNode.Height, LMaxBottom + FSpacing.Y); - - // Add a data output pin for the resulting closure. - CreateOutput(LLambdaNode, 0); - - // Update the current Y position of the main visitor to be below the now correctly sized lambda node. - FCurrentPos.Y := LLambdaNode.Position.Y + LLambdaNode.Height + FSpacing.Y; + // Update the state of the main visitor + FCurrentPos.Y := lambdaNode.Position.Y + lambdaNode.Height + FSpacing.Y; + FLastNode := lambdaNode; Result := TAstValue.Void; end; +function TAstToAuraNodeVisitor.VisitOperatorNode( + const InputExpressions: TArray; + const CreateParentProc: TCreateParentNodeProc; + Alignment: TVerticalAlignment +): TAuraNode; +var + inputResultNodes: TAuraNodeList; + inputNode: IExpressionNode; + startPosition: TPointF; + endY, maxChildWidth: Single; + parentNode: TAuraNode; +begin + startPosition := FCurrentPos; + inputResultNodes := TAuraNodeList.Create; + try + // 1. Visit all input nodes and collect their visual representations. + for inputNode in InputExpressions do + begin + inputNode.Accept(Self); + inputResultNodes.Add(FLastNode); + end; + endY := FCurrentPos.Y; + + // 2. Determine the layout bounds of the input nodes. + maxChildWidth := 0; + for var node in inputResultNodes do + maxChildWidth := Max(maxChildWidth, node.Position.X + node.Width); + + // 3. Let the closure create the parent node (without positioning). + parentNode := CreateParentProc(inputResultNodes); + Result := parentNode; + + // 4. Now position the parent node to the right of the inputs. + var parentY: Single; + if Alignment = vaCenter then + begin + var totalInputHeight := endY - startPosition.Y; + var nodeCenterY := startPosition.Y + (totalInputHeight / 2); + parentY := nodeCenterY - (parentNode.Height / 2); + end + else // vaTop + begin + parentY := startPosition.Y; + end; + parentNode.Position.Point := TPointF.Create(maxChildWidth + FSpacing.X, parentY); + + // 5. Finalize visitor state for the next operation. + FCurrentPos.X := startPosition.X; + FCurrentPos.Y := Max(endY, parentNode.Position.Y + parentNode.Height + FSpacing.Y); + FLastNode := parentNode; + finally + inputResultNodes.Free; + end; +end; + function TAstToAuraNodeVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue; begin CreateNodeControl('UnaryExpr', Node.Operator.ToString); @@ -679,49 +672,35 @@ begin end; function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; -var - LVarDeclNode, LInitializerNode: TAuraNode; - LStartPosition: TPointF; - LInitializerEndY, LVarDeclNodeEndY: Single; - LInputPin: TControl; begin - LStartPosition := FCurrentPos; - LInitializerEndY := LStartPosition.Y; - LInitializerNode := nil; - if Assigned(Node.Initializer) then begin - Node.Initializer.Accept(Self); + VisitOperatorNode( + [Node.Initializer], + function(const InputNodes: TAuraNodeList): TAuraNode + var + varDeclNode, initializerNode: TAuraNode; + inputPin: TControl; + begin + initializerNode := InputNodes[0]; + varDeclNode := BuildNodeControl('VarDecl', Node.Identifier.Name); - LInitializerNode := FLastNode; - LInitializerEndY := FCurrentPos.Y; + CreateEntry(varDeclNode, -8); + CreateExit(varDeclNode, '', -8); + inputPin := CreateInput(varDeclNode, 'Value', 8); + ConnectData(inputPin, initializerNode); - // Position the VarDecl node dynamically to the right of the initializer's visual tree. - FCurrentPos.X := LInitializerNode.Position.X + LInitializerNode.Width + FSpacing.X; - FCurrentPos.Y := LStartPosition.Y; - end; - - LVarDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); - LVarDeclNodeEndY := FCurrentPos.Y; - - CreateEntry(LVarDeclNode, -8); - CreateExit(LVarDeclNode, '', -8); - - if Assigned(Node.Initializer) then + Result := varDeclNode; + end + ); + end + else begin - LInputPin := CreateInput(LVarDeclNode, LInitializerNode.Name, 8); - ConnectData(LInputPin, LInitializerNode); - - // Vertically center the VarDecl node relative to the initializer's visual tree. - var LInitializerHeight := LInitializerEndY - LStartPosition.Y; - LVarDeclNode.Position.Y := LStartPosition.Y + (LInitializerHeight / 2) - (LVarDeclNode.Height / 2); - LVarDeclNodeEndY := LVarDeclNode.Position.Y + LVarDeclNode.Height + FSpacing.Y; - - FCurrentPos.Y := Max(LInitializerEndY, LVarDeclNodeEndY); - FCurrentPos.X := LStartPosition.X; + // Case without initializer is a simple sequential node. + var varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); + CreateEntry(varDeclNode, 0); + CreateExit(varDeclNode, '', 0); end; - FLastNode := LVarDeclNode; - Result := TAstValue.Void; end; @@ -729,12 +708,12 @@ end; procedure TAuraWorkspace.BuildTree(const Root: IAstNode; const Position: TPointF); begin - var Connections := TList.Create; + var connections := TList.Create; try - Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, Connections)); - FConnections := FConnections + Connections.ToArray; + Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections)); + FConnections := FConnections + connections.ToArray; finally - Connections.Free; + connections.Free; end; end; @@ -746,11 +725,11 @@ end; procedure TAuraWorkspace.Paint; var - LConnection: TPinConnection; - LStartPoint, LEndPoint, LPinCenter: TPointF; - LPath: TPathData; - LControlPoint1, LControlPoint2: TPointF; - LDeltaX, LControlOffset: Single; + connection: TPinConnection; + startPoint, endPoint, pinCenter: TPointF; + path: TPathData; + controlPoint1, controlPoint2: TPointF; + deltaX, controlOffset: Single; begin inherited; @@ -758,40 +737,40 @@ begin Canvas.Stroke.Thickness := 2; // Gehe durch alle gespeicherten Verbindungen und zeichne sie - for LConnection in FConnections do + for connection in FConnections do begin // Hole die absoluten Koordinaten der Pin-Mittelpunkte - LPinCenter := TPointF.Create(LConnection.OutputPin.Width / 2, LConnection.OutputPin.Height / 2); - var LAbsoluteStart := LConnection.OutputPin.LocalToAbsolute(LPinCenter); + pinCenter := TPointF.Create(connection.OutputPin.Width / 2, connection.OutputPin.Height / 2); + var absoluteStart := connection.OutputPin.LocalToAbsolute(pinCenter); - LPinCenter := TPointF.Create(LConnection.InputPin.Width / 2, LConnection.InputPin.Height / 2); - var LAbsoluteEnd := LConnection.InputPin.LocalToAbsolute(LPinCenter); + pinCenter := TPointF.Create(connection.InputPin.Width / 2, connection.InputPin.Height / 2); + var absoluteEnd := connection.InputPin.LocalToAbsolute(pinCenter); // Rechne sie in die lokalen Koordinaten der PaintBox um - LStartPoint := AbsoluteToLocal(LAbsoluteStart); - LEndPoint := AbsoluteToLocal(LAbsoluteEnd); + startPoint := AbsoluteToLocal(absoluteStart); + endPoint := AbsoluteToLocal(absoluteEnd); - var str := LConnection.InputPin.TagString; + var str := connection.InputPin.TagString; if Pos('data', str) = 0 then Canvas.Stroke.Color := TAstToAuraNodeVisitor.cExecPinColor else Canvas.Stroke.Color := TAstToAuraNodeVisitor.cDataPinColor; // Draw a Bezier curve with horizontal tangents at start and end points. - LPath := TPathData.Create; + path := TPathData.Create; try - LDeltaX := LEndPoint.X - LStartPoint.X; - LControlOffset := Abs(LDeltaX) / 2; + deltaX := endPoint.X - startPoint.X; + controlOffset := max(30, abs(deltaX) / 2); - LControlPoint1 := TPointF.Create(LStartPoint.X + LControlOffset, LStartPoint.Y); - LControlPoint2 := TPointF.Create(LEndPoint.X - LControlOffset, LEndPoint.Y); + controlPoint1 := TPointF.Create(startPoint.X + controlOffset, startPoint.Y); + controlPoint2 := TPointF.Create(endPoint.X - controlOffset, endPoint.Y); - LPath.MoveTo(LStartPoint); - LPath.CurveTo(LControlPoint1, LControlPoint2, LEndPoint); + path.MoveTo(startPoint); + path.CurveTo(controlPoint1, controlPoint2, endPoint); - Canvas.DrawPath(LPath, 1.0); + Canvas.DrawPath(path, 1.0); finally - LPath.Free; + path.Free; end; end; end;