From b972b05a07728470e9242da11b284abb8eae3b5b Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 16 Sep 2025 19:18:40 +0200 Subject: [PATCH] Ast control refactoring --- ASTPlayground/MainForm.pas | 3 +- ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas | 19 +- ASTPlayground/Myc.Fmx.AstEditor.pas | 545 ++++++++++-------- Src/AST/Myc.Ast.Nodes.pas | 9 +- Src/AST/Myc.Ast.pas | 12 +- 5 files changed, 324 insertions(+), 264 deletions(-) diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index a15df52..7c5015c 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -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; diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas b/ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas index 0f89af0..229989f 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas @@ -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; + rootDescriptor: IScopeDescriptor; begin - var connections := TList.Create; + connections := TList.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; diff --git a/ASTPlayground/Myc.Fmx.AstEditor.pas b/ASTPlayground/Myc.Fmx.AstEditor.pas index 1b73f60..9ab2aaa 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.pas @@ -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; 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; FCurrentExec: TList; FMode: TVisualizationMode; - FResolvedIdentifierCache: TDictionary; - FOwnsCaches: Boolean; + FSlotCache: TArray; + 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; 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; const ACurrentExec: TArray; AMode: TVisualizationMode; - AResolvedIdentifierCache: TDictionary = nil + AParent: TAstToAuraNodeVisitor; + ACurrentLambda: ILambdaExpressionNode; + ADescriptor: IScopeDescriptor ); destructor Destroy; override; - - // Public access to the collected connections property Connections: TList read FConnections; { IAstVisitor } @@ -149,7 +137,9 @@ constructor TAstToAuraNodeVisitor.Create( AConnections: TList; const ACurrentExec: TArray; AMode: TVisualizationMode; - AResolvedIdentifierCache: TDictionary + 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.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 := [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; diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index fa4a7b0..8af6eb1 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -42,6 +42,8 @@ type SlotIndex: Integer; constructor Create(AKind: TAddressKind; AScopeDepth, ASlotIndex: Integer); class operator Initialize(out Dest: TResolvedAddress); + public + class operator Equal(const A: TResolvedAddress; B: TResolvedAddress): Boolean; end; IValueCell = interface @@ -112,12 +114,10 @@ type IIdentifierNode = interface(IAstNode) {$region 'private'} - function GetIsResolved: Boolean; function GetAddress: TResolvedAddress; function GetName: string; {$endregion} property Name: string read GetName; - property IsResolved: Boolean read GetIsResolved; property Address: TResolvedAddress read GetAddress; end; @@ -269,6 +269,11 @@ begin SlotIndex := ASlotIndex; end; +class operator TResolvedAddress.Equal(const A: TResolvedAddress; B: TResolvedAddress): Boolean; +begin + Result := (A.Kind = B.Kind) and (A.ScopeDepth = B.ScopeDepth) and (A.SlotIndex = B.SlotIndex); +end; + class operator TResolvedAddress.Initialize(out Dest: TResolvedAddress); begin Dest.Kind := akUnresolved; diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index b5bb407..b524b27 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -112,12 +112,10 @@ type // --- Annotation fields added for the binder --- FResolvedAddress: TResolvedAddress; function GetName: string; - function GetIsResolved: Boolean; inline; function GetAddress: TResolvedAddress; inline; public constructor Create(AName: string); function Accept(const Visitor: IAstVisitor): TDataValue; override; - property IsResolved: Boolean read GetIsResolved; property Name: string read FName; property Address: TResolvedAddress read GetAddress; end; @@ -437,12 +435,6 @@ begin Result := Visitor.VisitIdentifier(Self); end; -function TIdentifierNode.GetIsResolved: Boolean; -begin - // An identifier is resolved if its kind is not unresolved anymore. - Result := FResolvedAddress.Kind <> akUnresolved; -end; - function TIdentifierNode.GetName: string; begin Result := FName; @@ -450,8 +442,6 @@ end; function TIdentifierNode.GetAddress: TResolvedAddress; begin - if not IsResolved then - raise EInvalidOpException.Create('Identifier not bound'); Result := FResolvedAddress; end; @@ -867,7 +857,7 @@ var upvalueIndex: Integer; begin identNode := Node as TIdentifierNode; - if identNode.IsResolved then + if identNode.Address.Kind <> akUnresolved then Exit; if (FCurrentDescriptor as TScopeDescriptor).FindSymbol(identNode.Name, depth, idx) then