diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index e1ae840..3e46067 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -193,16 +193,19 @@ begin [TAst.Identifier('n')], TAst.IfExpr( TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))), - TAst.Identifier('n'), - TAst.BinaryExpr( - TAst.FunctionCall( - TAst.Identifier('fib'), - [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] - ), - boAdd, - TAst.FunctionCall( - TAst.Identifier('fib'), - [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(2)))] + TAst.VarDecl(TAst.Identifier('Result'), TAst.Identifier('n')), + TAst.VarDecl( + TAst.Identifier('Result'), + TAst.BinaryExpr( + TAst.FunctionCall( + TAst.Identifier('fib'), + [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] + ), + boAdd, + TAst.FunctionCall( + TAst.Identifier('fib'), + [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(2)))] + ) ) ) ) @@ -274,17 +277,25 @@ begin TAst.Identifier('factorial'), TAst.LambdaExpr( [TAst.Identifier('n')], - TAst.IfExpr( - TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))), - TAst.Constant(TScalar.FromInt64(1)), - TAst.BinaryExpr( - TAst.Identifier('n'), - boMultiply, - TAst.FunctionCall( - TAst.Identifier('factorial'), - [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] + TAst.Block( + [ + TAst.VarDecl(TAst.Identifier('Result'), TAst.Constant(TScalar.FromDouble(0))), + TAst.IfExpr( + TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))), + TAst.Assign(TAst.Identifier('Result'), TAst.Constant(TScalar.FromInt64(1))), + TAst.Assign( + TAst.Identifier('Result'), + TAst.BinaryExpr( + TAst.Identifier('n'), + boMultiply, + TAst.FunctionCall( + TAst.Identifier('factorial'), + [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] + ) + ) + ) ) - ) + ] ) ) ), @@ -365,11 +376,14 @@ begin TAst.Identifier('createStrategyInstance'), TAst.LambdaExpr( [TAst.Identifier('offset')], - TAst.Block( - [ - TAst.VarDecl(TAst.Identifier('baseValue'), TAst.Constant(TScalar.FromInt64(100))), - TAst.BinaryExpr(TAst.Identifier('baseValue'), boAdd, TAst.Identifier('offset')) - ] + Tast.VarDecl( + TAst.Identifier('Result'), + TAst.Block( + [ + TAst.VarDecl(TAst.Identifier('baseValue'), TAst.Constant(TScalar.FromInt64(100))), + TAst.BinaryExpr(TAst.Identifier('baseValue'), boAdd, TAst.Identifier('offset')) + ] + ) ) ) ), diff --git a/ASTPlayground/Myc.Ast.Visualizer.pas b/ASTPlayground/Myc.Ast.Visualizer.pas index 843ac04..267949b 100644 --- a/ASTPlayground/Myc.Ast.Visualizer.pas +++ b/ASTPlayground/Myc.Ast.Visualizer.pas @@ -16,7 +16,6 @@ uses DraggablePanel; type - // ... TPinConnection, TPinShape, TPinAlignment, TAuraWorkspace ... TPinConnection = record OutputPin: TControl; InputPin: TControl; @@ -35,6 +34,7 @@ type TAuraNodeList = TList; TCreateParentNodeProc = reference to function(const InputNodes: TAuraNodeList): TAuraNode; TVerticalAlignment = (vaCenter, vaTop); + private const // Pin Visuals @@ -50,6 +50,7 @@ type // Specific Node Heights cDefaultNodeHeight = 45; // Default height for simple nodes like BinaryExpr cIfNodeHeight = 60; // Taller height for If nodes with more pins + private FWorkspace: TAuraWorkspace; FParentControl: TControl; @@ -57,9 +58,14 @@ type FSpacing: TPointF; FLastNode: TAuraNode; FConnections: TList; - FCurrentExec: TControl; + FCurrentExec: TList; - function VisitContainerNode(const Title, Details: string; const ChildVisitorProc: TChildVisitorProc): TAuraNode; + function VisitContainerNode( + const Title, Details: string; + const InPin, OutPin: String; + IsExec: Boolean; + const ChildVisitorProc: TChildVisitorProc + ): TAuraNode; function VisitOperatorNode( const InputExpressions: TArray; const CreateParentProc: TCreateParentNodeProc; @@ -79,7 +85,7 @@ type 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 CreateEntry(ParentNode: TAuraNode; OffsetY: Single = 0; connect: Boolean = true): TControl; function CreateExit(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl; function FindPinByTag(ParentNode: TAuraNode; const Tag: string): TControl; @@ -89,8 +95,11 @@ type AWorkspace: TAuraWorkspace; AParentControl: TControl; const AStartPosition: TPointF; - AConnections: TList + AConnections: TList; + const ACurrentExec: TArray ); + destructor Destroy; override; + // Public access to the collected connections property Connections: TList read FConnections; @@ -172,7 +181,8 @@ constructor TAstToAuraNodeVisitor.Create( AWorkspace: TAuraWorkspace; AParentControl: TControl; const AStartPosition: TPointF; - AConnections: TList + AConnections: TList; + const ACurrentExec: TArray ); begin inherited Create; @@ -181,6 +191,13 @@ begin FCurrentPos := AStartPosition; FSpacing := TPointF.Create(20, 10); // Horizontal indent, Vertical spacing FConnections := AConnections; + FCurrentExec := TList.Create(ACurrentExec); +end; + +destructor TAstToAuraNodeVisitor.Destroy; +begin + FCurrentExec.Free; + inherited; end; function TAstToAuraNodeVisitor.BuildNodeControl(const Title, Details: string): TAuraNode; @@ -244,11 +261,16 @@ begin Result := CreatePin(ParentNode, psCircle, paRight, cDataPinColor, 'data.out', OffsetY); end; -function TAstToAuraNodeVisitor.CreateEntry(ParentNode: TAuraNode; OffsetY: Single = 0): TControl; +function TAstToAuraNodeVisitor.CreateEntry(ParentNode: TAuraNode; OffsetY: Single = 0; connect: Boolean = true): TControl; begin Result := CreatePin(ParentNode, psTriangle, paLeft, cExecPinColor, 'exec.in', OffsetY); - if FCurrentExec <> nil then - FConnections.Add(TPinConnection.Create(FCurrentExec, Result)); + if connect then + begin + // Connect all open exits to this new entry + for var curr in FCurrentExec do + FConnections.Add(TPinConnection.Create(curr, Result)); + FCurrentExec.Clear; + end; end; function TAstToAuraNodeVisitor.CreateExit(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl; @@ -258,7 +280,7 @@ begin cap := '.' + cap; Result := CreatePin(ParentNode, psTriangle, paRight, cExecPinColor, 'exec.out' + cap, OffsetY); - FCurrentExec := Result; + FCurrentExec.Add(Result); end; function TAstToAuraNodeVisitor.CreatePin( @@ -342,9 +364,9 @@ begin valueNode := InputNodes[0]; assignmentNode := BuildNodeControl('Assignment', Node.Identifier.Name); - CreateEntry(assignmentNode, 0); + CreateEntry(assignmentNode, -8); CreateExit(assignmentNode, '', 0); - inputPin := CreateInput(assignmentNode, Node.Identifier.Name, 0); + inputPin := CreateInput(assignmentNode, Node.Identifier.Name, 8); ConnectData(inputPin, valueNode); Result := assignmentNode; @@ -387,10 +409,16 @@ var blockNode: TAuraNode; begin // Use the helper to create and populate the container node. + // InPin and OutPin set: block acts as "virtual execution scope" with entry and exit-nodes + // InPin and OutPin empty: block acts as a simple container blockNode := VisitContainerNode( 'Block', '', + // 'enter', 'exit', + '', + '', + true, procedure(const Visitor: IAstVisitor) var expression: IExpressionNode; @@ -418,7 +446,12 @@ begin Result := TAstValue.Void; end; -function TAstToAuraNodeVisitor.VisitContainerNode(const Title, Details: string; const ChildVisitorProc: TChildVisitorProc): TAuraNode; +function TAstToAuraNodeVisitor.VisitContainerNode( + const Title, Details: string; + const InPin, OutPin: String; + IsExec: Boolean; + const ChildVisitorProc: TChildVisitorProc +): TAuraNode; var containerNode: TAuraNode; childVisitor: IAstVisitor; @@ -427,16 +460,46 @@ var maxBottom: Single; control: TControl; begin + const pinNodeHeight = cPinSize + cVerticalPadding; + // Step 1: Create container node containerNode := BuildNodeControl(Title, Details); containerNode.Position.Point := FCurrentPos; Result := containerNode; // Set result early + childStartPos := + TPointF.Create(FSpacing.X, FSpacing.Y + max(IfThen(InPin <> '', pinNodeHeight, 0), IfThen(Title <> '', cVerticalPadding, 0))); + + var currExec := FCurrentExec.ToArray; + if InPin <> '' then + begin + // Create a small entry node to define the start of the execution flow inside the container. + 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, 0); + var entryExitPin := CreateExit(entryNode, '', 0); + entryPin.Position.Y := containerNode.AbsoluteToLocal(entryNode.LocalToAbsolute(entryExitPin.Position.Point)).Y; + end + else + begin + FCurrentExec.Clear; + CreateExit(entryNode, '', 0); + end; + end; + // 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); + // The child visitor starts its execution flow from the entry node's exit pin. + childVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray); ChildVisitorProc(childVisitor); + FCurrentExec.Clear; + FCurrentExec.AddRange((childVisitor as TAstToAuraNodeVisitor).FCurrentExec); + // Step 3: Adjust the size of the container node to fit children. maxRight := 0; maxBottom := 0; @@ -448,9 +511,28 @@ begin 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); + containerNode.Height := Max(containerNode.Height, maxBottom + FSpacing.Y + IfThen(OutPin <> '', pinNodeHeight, 0)); + + if OutPin <> '' then + begin + var exitNode := BuildNodeControl(OutPin, ''); + exitNode.Parent := containerNode; + exitNode.Position.Point := TPointF.Create(containerNode.Width - exitNode.Width, containerNode.Height - pinNodeHeight); + exitNode.Height := pinNodeHeight; + var exitPin := CreateEntry(exitNode, 0); + + if not IsExec then + begin + FCurrentExec.Clear; + FCurrentExec.AddRange(currExec); + end + else + CreateExit(containerNode, '', 0).Position.Y := + containerNode.AbsoluteToLocal(exitNode.LocalToAbsolute(exitPin.Position.Point)).Y; + end end; function TAstToAuraNodeVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; @@ -509,63 +591,64 @@ end; function TAstToAuraNodeVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue; begin - CreateNodeControl('Identifier', Node.Name); + var identifierNode := CreateNodeControl('Identifier', Node.Name); // Add a data output pin on the right side. - CreateOutput(FLastNode, 0); + CreateOutput(identifierNode, 0); Result := TAstValue.Void; end; function TAstToAuraNodeVisitor.VisitIfExpression(const Node: IIfExpressionNode): TAstValue; -var - ifNode: TAuraNode; - conditionInputPin, thenOutputPin, elseOutputPin: TControl; - startPosition: TPointF; - thenEndY, elseEndY: Single; begin - startPosition := FCurrentPos; + var startPosition := FCurrentPos; // Use helper for the condition part - VisitOperatorNode( - [Node.Condition], - function(const InputNodes: TAuraNodeList): TAuraNode - var - condNode: TAuraNode; - begin - condNode := InputNodes[0]; + var ifNode := + VisitOperatorNode( + [Node.Condition], + function(const InputNodes: TAuraNodeList): TAuraNode + var + condNode: TAuraNode; + conditionInputPin: TControl; + begin + condNode := InputNodes[0]; - 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; + Result := BuildNodeControl('If', ''); + Result.Height := cIfNodeHeight; + CreateEntry(Result, -12); - ConnectData(conditionInputPin, condNode); - Result := ifNode; - end - ); + conditionInputPin := CreateInput(Result, 'Condition', 12); + + ConnectData(conditionInputPin, condNode); + end + ); var conditionEndY := FCurrentPos.Y; // Visit the 'Then' and 'Else' execution branches, placing them to the right of the 'If' node. var branchStartX := ifNode.Position.X + ifNode.Width + FSpacing.X; + var execPathes := TList.Create; + // Then branch + FCurrentExec.Clear; + CreateExit(ifNode, 'Then', -12); FCurrentPos.X := branchStartX; FCurrentPos.Y := startPosition.Y; Node.ThenBranch.Accept(Self); - thenEndY := FCurrentPos.Y; + var thenEndY := FCurrentPos.Y; + FLastNode := ifNode; + execPathes.AddRange(FCurrentExec); // Else branch (if it exists) - elseEndY := thenEndY; + var elseEndY := thenEndY; if Assigned(Node.ElseBranch) then begin + FCurrentExec.Clear; + CreateExit(ifNode, 'Else', 12); FCurrentPos.X := branchStartX; FCurrentPos.Y := thenEndY; Node.ElseBranch.Accept(Self); elseEndY := FCurrentPos.Y; + execPathes.AddRange(FCurrentExec); end; // Finalize visitor state. @@ -573,6 +656,11 @@ begin FCurrentPos.Y := Max(conditionEndY, elseEndY); FLastNode := ifNode; + // After a branch, the execution flow has diverged. There is no single + // execution pin to continue from, so we set it to nil. + FCurrentExec.Clear; + FCurrentExec.AddRange(execPathes); + Result := TAstValue.Void; end; @@ -593,7 +681,15 @@ begin paramStr := 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); + lambdaNode := + VisitContainerNode( + 'Lambda', + paramStr, + 'call', + 'return', + false, + procedure(const Visitor: IAstVisitor) begin Node.Body.Accept(Visitor); end + ); CreateOutput(lambdaNode, 0); @@ -710,7 +806,7 @@ procedure TAuraWorkspace.BuildTree(const Root: IAstNode; const Position: TPointF begin var connections := TList.Create; try - Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections)); + Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections, nil)); FConnections := FConnections + connections.ToArray; finally connections.Free; @@ -759,8 +855,7 @@ begin // Draw a Bezier curve with horizontal tangents at start and end points. path := TPathData.Create; try - deltaX := endPoint.X - startPoint.X; - controlOffset := max(30, abs(deltaX) / 2); + controlOffset := max(25, 0.5 * endPoint.Distance(startPoint)); controlPoint1 := TPointF.Create(startPoint.X + controlOffset, startPoint.Y); controlPoint2 := TPointF.Create(endPoint.X - controlOffset, endPoint.Y);