diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas index 2c38674..4399689 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas @@ -1388,9 +1388,6 @@ begin end; procedure TAuraIfExpressionNode.SetupNode; -var - visu: IAstVisualizer; - lbl: TLabel; begin BeginUpdate; try diff --git a/ASTPlayground/Myc.Fmx.AstVisualizer.pas b/ASTPlayground/Myc.Fmx.AstVisualizer.pas deleted file mode 100644 index 6c6d4fd..0000000 --- a/ASTPlayground/Myc.Fmx.AstVisualizer.pas +++ /dev/null @@ -1,1330 +0,0 @@ -unit Myc.Fmx.AstVisualizer deprecated; - -interface - -uses - System.SysUtils, - System.Classes, - System.UITypes, - System.Types, - System.Math.Vectors, - System.Generics.Collections, - System.Generics.Defaults, - FMX.Types, - FMX.Controls, - FMX.Objects, - FMX.Graphics, - FMX.StdCtrls, - Myc.Data.Value, - Myc.Ast.Nodes, - Myc.Ast.Scope, - Myc.Ast.Types, - Myc.Ast.Visitor, - Myc.Fmx.AstEditor.Node, - Myc.Fmx.AstEditor.Workspace; - -type - // Visits the AST and creates visual TAuraNode representations on a TAuraWorkspace. - TAstVisualizer = class(TAstVisitor, IAstVisualizer) - private - FCurrentPos: TPointF; - FExprDepth: Integer; - FWorkspace: TAuraWorkspace; - FNodeVerticalGap: Single; - FParentControl: TControl; - // Helper to create and initialize a TAuraNode for a given AST node. - function CreateNode(const Node: IAstNode): TAuraNode; - function AddLabel(const ParentNode: TAuraNode; const Txt: String): TLabel; - function AddTitleLabel(const ParentNode: TAuraNode; const Title: string): Single; - // Adjusts the size of the parent node to fit its children. - procedure AdjustParentNodeSize(const ParentNode: TAuraNode; const Children: TArray); - // Generic processing logic for AST nodes with children. Returns the created TAuraNode. - function ProcessStub(const ATitle: string; const ANode: IAstNode; const AChildren: TArray): TAuraNode; - - // Calls Accept on the node and returns the resulting TAuraNode. - function CallAccept(const Node: IAstNode): TAuraNode; - function GetCurrentPos: TPointF; - function GetExprDepth: Integer; - function GetParentControl: TControl; - procedure SetCurrentPos(const Value: TPointF); - procedure SetExprDepth(const Value: Integer); - procedure SetParentControl(const Value: TControl); - protected - // Visitor implementations for each AST node type. - function VisitConstant(const Node: IConstantNode): TDataValue; override; - function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; - function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; - function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; - function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; - function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override; - function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override; - function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override; - function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; override; - function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override; - function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override; - function VisitAssignment(const Node: IAssignmentNode): TDataValue; override; - function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; override; - function VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; override; - function VisitUnquote(const Node: IUnquoteNode): TDataValue; override; - function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; override; - function VisitIndexer(const Node: IIndexerNode): TDataValue; override; - function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override; - function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; override; - function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; override; - function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override; - function VisitRecurNode(const Node: IRecurNode): TDataValue; override; - - public - constructor Create( - AWorkspace: TAuraWorkspace; - AParentControl: TControl; - const AStartPosition: TPointF; - ANodeVerticalGap: Single = 15; - AExprDepth: Integer = 0 - ); - destructor Destroy; override; - - function Clone: IAstVisualizer; - - class function CreateVisitor(AWorkspace: TAuraWorkspace; const StartPosition: TPointF): IAstVisitor; - - property CurrentPos: TPointF read GetCurrentPos write SetCurrentPos; - property ExprDepth: Integer read GetExprDepth write SetExprDepth; - property ParentControl: TControl read GetParentControl write SetParentControl; - end; - -implementation - -uses - System.Math, - System.StrUtils, - FMX.Platform, - FMX.Layouts, // For TLayout - Myc.Data.Scalar, - Myc.Ast.Binding, // For TBound...Node classes - Myc.Fmx.AstEditor.Text; - -const - cChildAreaTopPadding = 5; - cNodeVerticalGap = 10; // Reduced gap - -{ TAstVisualizer } - -constructor TAstVisualizer.Create( - AWorkspace: TAuraWorkspace; - AParentControl: TControl; - const AStartPosition: TPointF; - ANodeVerticalGap: Single = 15; - AExprDepth: Integer = 0 -); -begin - inherited Create; - FWorkspace := AWorkspace; - FParentControl := AParentControl; - FCurrentPos := AStartPosition; - FNodeVerticalGap := ANodeVerticalGap; - FExprDepth := AExprDepth; -end; - -destructor TAstVisualizer.Destroy; -begin - inherited; -end; - -function TAstVisualizer.AddLabel(const ParentNode: TAuraNode; const Txt: String): TLabel; -begin - Result := TLabel.Create(ParentNode); // Owner is the node itself - Result.Parent := ParentNode; - - Result.Position.Point := TPoint.Create(cNodePadding, cNodePadding); - Result.Margins.Left := cNodePadding; - Result.Margins.Right := cNodePadding; - Result.Margins.Top := cTitleTopPadding; - Result.Margins.Bottom := cTitleBottomPadding; - Result.WordWrap := false; - Result.AutoSize := True; // Let label determine its own size initially (do NOT use Align, because it will override AutoSize) - Result.HitTest := False; // Title label should not intercept clicks intended for the node - - Result.StyledSettings := Result.StyledSettings - [TStyledSetting.Style]; // Tell FMX not to manage Font.Style via styles - - Result.Text := Txt; - Result.ApplyStyleLookup; -end; - -class function TAstVisualizer.CreateVisitor(AWorkspace: TAuraWorkspace; const StartPosition: TPointF): IAstVisitor; -begin - Result := TAstVisualizer.Create(AWorkspace, AWorkspace, StartPosition); -end; - -// Creates a TAuraNode, sets its parent and position. Title is added separately. -function TAstVisualizer.CreateNode(const Node: IAstNode): TAuraNode; -begin - Result := TAuraNode.Create(FWorkspace); // Owner is always the workspace - Result.Parent := FParentControl; // Set visual parent - Result.Position.Point := FCurrentPos; - // Set a default minimum size, will be adjusted later - Result.Width := cMinNodeWidth; - Result.Height := cMinNodeHeight; - - var cn: cardinal := $ea - (7 * FExprDepth); - var c: cardinal := $ff000000 or (cn shl 16) or (cn shl 8) or cn; - - Result.BackgroundColor := c; - // Store the original AST node reference if needed later - // Result.TagObject := TObject(Node); -end; - -// Creates and adds a TLabel for the title to the given ParentNode. -// Returns the calculated height of the title area (label + margins). -function TAstVisualizer.AddTitleLabel(const ParentNode: TAuraNode; const Title: string): Single; -var - titleLabel: TLabel; -begin - Result := 0; - if Title.IsEmpty then - Exit; - - titleLabel := AddLabel(ParentNode, Title); - titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold]; - - // Force immediate resize calculation for height measurement (usually not needed with Align=Top + AutoSize) - // titleLabel.ApplyStyleLookup; titleLabel.Realign; - Result := titleLabel.Height + titleLabel.Margins.Top + titleLabel.Margins.Bottom; -end; - -// Adjusts the parent node's size after all children have been added, -// considering both labels and child AuraNodes. -procedure TAstVisualizer.AdjustParentNodeSize(const ParentNode: TAuraNode; const Children: TArray); -var - i: Integer; - maxLabelWidth: Single; - maxChildNodeWidth: Single; - totalChildHeight: Single; - childControl: TControl; - isLabel: Boolean; - requiredWidth: Single; -begin - maxLabelWidth := 0; - maxChildNodeWidth := 0; - totalChildHeight := cNodePadding; // Start with top padding - - // Iterate through FMX children of the ParentNode to calculate required size - for i := 0 to ParentNode.ControlsCount - 1 do - begin - childControl := ParentNode.Controls[i]; - isLabel := childControl is TLabel; - - // Calculate required width for this child (including its margins) - requiredWidth := childControl.Width + childControl.Margins.Left + childControl.Margins.Right; - - if isLabel then - begin - maxLabelWidth := Max(maxLabelWidth, requiredWidth); - end - // Also consider TAuraNode children for width calculation, assuming they also have padding - else if childControl is TAuraNode then - begin - // Child nodes are positioned starting at cNodePadding, so their width relative to parent border is Width + cNodePadding - // We compare against the required width including parent padding - maxChildNodeWidth := Max(maxChildNodeWidth, childControl.Width); - end; - - // Height calculation based on the last child's bottom edge relative to parent's top - totalChildHeight := Max(totalChildHeight, childControl.Position.Y + childControl.Height + childControl.Margins.Bottom); - end; - - // Set parent size including padding - // Ensure minimum size is met AND the widest label fits AND the widest child node fits - ParentNode.Width := - Max( - cMinNodeWidth, // At least min width - Max( - maxLabelWidth, // At least widest label width + padding (already included in label width calc) - maxChildNodeWidth + 2 * cNodePadding - ) - ); // At least widest child node width + padding - - // Adjust height based on the bottom-most control + bottom padding - if ParentNode.ControlsCount > 0 then - ParentNode.Height := Max(cMinNodeHeight, totalChildHeight + cNodePadding) - else // Empty node - ParentNode.Height := cMinNodeHeight; - -end; - -// Calls Accept on the node and returns the resulting TAuraNode. -function TAstVisualizer.CallAccept(const Node: IAstNode): TAuraNode; -var - DataValue: TDataValue; -begin - if not Assigned(Node) then - Exit(nil); // Handle nil input node gracefully - - DataValue := Node.Accept(Self); - - // Check if the result is actually a TAuraNode before casting - if DataValue.Kind = TDataValueKind.vkGeneric then - begin - // It's recommended to add a type check here if possible, - // although AsGeneric doesn't inherently support it. - // Assuming the visitor correctly returns TAuraNode wrapped in TDataValue. - Result := DataValue.AsGeneric; - end - else - begin - // The visitor returned something unexpected. - // Decide how to handle this: raise an exception, return nil, log an error? - // For now, return nil as the caller might handle it. - Result := nil; - // Consider: Log.d('TAstVisualizer.CallAccept', 'Expected TAuraNode, got ' + DataValue.Kind.ToString); - // Consider: raise EMyAstError.Create('Visitor did not return a TAuraNode'); - end; -end; - -function TAstVisualizer.Clone: IAstVisualizer; -begin - Result := TAstVisualizer.Create(FWorkspace, FParentControl, FCurrentPos, FNodeVerticalGap, FExprDepth); -end; - -function TAstVisualizer.GetCurrentPos: TPointF; -begin - Result := FCurrentPos; -end; - -function TAstVisualizer.GetExprDepth: Integer; -begin - Result := FExprDepth; -end; - -function TAstVisualizer.GetParentControl: TControl; -begin - Result := FParentControl; -end; - -// Generic processing logic for AST nodes with children. Returns the created TAuraNode. -function TAstVisualizer.ProcessStub(const ATitle: string; const ANode: IAstNode; const AChildren: TArray): TAuraNode; -var - auraNode: TAuraNode; - savedParent: TControl; - savedPos: TPointF; - child: IAstNode; - titleLabelHeight: Single; -begin - auraNode := CreateNode(ANode); // Create node without title - auraNode.BeginUpdate; - try - // Add Title Label - titleLabelHeight := AddTitleLabel(auraNode, ATitle); - - savedParent := FParentControl; - savedPos := FCurrentPos; - FParentControl := auraNode; - // Start position for children inside the new node, below the title - FCurrentPos := TPointF.Create(cNodePadding, titleLabelHeight + cChildAreaTopPadding); - - // Visit children - for child in AChildren do - begin - if Assigned(child) then // Ensure child node exists - {childNode :=} - CallAccept(child); // Use CallAccept, store result - // childNode is not explicitly used here for layout *after* the call, - // but its creation and positioning happened *within* CallAccept. - end; - - FParentControl := savedParent; // Restore parent - FCurrentPos := savedPos; // Restore position for siblings - - AdjustParentNodeSize(auraNode, AChildren); - finally - auraNode.EndUpdate; - end; - // Move current position down for the next sibling node in the outer scope - // Use the actual size of the created node - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; - Result := auraNode; // Return the created node -end; - -procedure TAstVisualizer.SetCurrentPos(const Value: TPointF); -begin - FCurrentPos := Value; -end; - -procedure TAstVisualizer.SetExprDepth(const Value: Integer); -begin - FExprDepth := Value; -end; - -procedure TAstVisualizer.SetParentControl(const Value: TControl); -begin - FParentControl := Value; -end; - -// --- Visitor Implementations --- - -function TAstVisualizer.VisitConstant(const Node: IConstantNode): TDataValue; -begin - var auraNode := TAuraConstantNode.CreateConstantNode(FWorkspace, Node, Self); - - // Move current position down for the next sibling node - FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap; - - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -function TAstVisualizer.VisitIdentifier(const Node: IIdentifierNode): TDataValue; -var - auraNode: TAuraIdentifierNode; // Use specific type -begin - // Create the specific node type using its factory - auraNode := TAuraIdentifierNode.CreateIdentifierNode(FWorkspace, Node, Self); - - // Move current position down for the next sibling node - FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap; - - Result := TDataValue.FromGeneric(auraNode); // Return as generic TAuraNode -end; - -function TAstVisualizer.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; -var - auraNode: TAuraBinaryExpressionNode; // Use specific type - savedPos: TPointF; -begin - // Store position before creating the node - savedPos := FCurrentPos; - - // Create the specific node type using its factory - auraNode := TAuraBinaryExpressionNode.CreateBinaryExpressionNode(FWorkspace, Node, Self); - - // Move current position down for the next sibling node - // Use the position saved *before* creating this node, plus the node's final height - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; - - Result := TDataValue.FromGeneric(auraNode); // Return as generic TAuraNode -end; - -function TAstVisualizer.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; -var - auraNode: TAuraNode; -begin - auraNode := ProcessStub('Unary Op: ' + Node.Operator.ToString, Node, [Node.Right]); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitIfExpression(const Node: IIfExpressionNode): TDataValue; -var - children: TArray; - auraNode: TAuraNode; -begin - children := [Node.Condition, Node.ThenBranch]; - if Assigned(Node.ElseBranch) then - children := children + [Node.ElseBranch]; - auraNode := ProcessStub('If', Node, children); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; -var - auraNode: TAuraNode; - conditionNode: TAuraNode; - qMarkLabel: TLabel; - thenNode: TAuraNode; - colonLabel: TLabel; - elseNode: TAuraNode; - savedParent: TControl; - savedPos: TPointF; - currentX: Single; // Track horizontal position - maxHeightInRow: Single; // Track max height for row alignment - yPos: Single; // Y position for elements in this row -begin - // Create the main node, frameless - auraNode := CreateNode(Node); - auraNode.BeginUpdate; - inc(FExprDepth); - try - auraNode.Frameless := True; - - // Start position for children inside the node - currentX := cNodePadding; - yPos := cNodePadding; // Initial Y position - maxHeightInRow := 0; - - // 1. Condition Node - savedParent := FParentControl; - savedPos := FCurrentPos; - FParentControl := auraNode; - FCurrentPos := TPointF.Create(currentX, yPos); - conditionNode := CallAccept(Node.Condition); - if Assigned(conditionNode) then - begin - currentX := conditionNode.Position.X + conditionNode.Width + conditionNode.Margins.Right + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, conditionNode.Height + conditionNode.Margins.Top + conditionNode.Margins.Bottom); - end; - - // 2. Question Mark Label - qMarkLabel := AddLabel(auraNode, '?'); - qMarkLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := qMarkLabel.Position.X + qMarkLabel.Width + qMarkLabel.Margins.Right + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, qMarkLabel.Height + qMarkLabel.Margins.Top + qMarkLabel.Margins.Bottom); - - // 3. Then Branch Node - // FParentControl is already auraNode - FCurrentPos := TPointF.Create(currentX, yPos); - thenNode := CallAccept(Node.ThenBranch); - if Assigned(thenNode) then - begin - currentX := thenNode.Position.X + thenNode.Width + thenNode.Margins.Right + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, thenNode.Height + thenNode.Margins.Top + thenNode.Margins.Bottom); - end; - - // 4. Colon Label - colonLabel := AddLabel(auraNode, ':'); - colonLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := colonLabel.Position.X + colonLabel.Width + colonLabel.Margins.Right + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, colonLabel.Height + colonLabel.Margins.Top + colonLabel.Margins.Bottom); - - // 5. Else Branch Node - // FParentControl is already auraNode - FCurrentPos := TPointF.Create(currentX, yPos); - elseNode := CallAccept(Node.ElseBranch); - if Assigned(elseNode) then - begin - currentX := elseNode.Position.X + elseNode.Width + elseNode.Margins.Right; - maxHeightInRow := Max(maxHeightInRow, elseNode.Height + elseNode.Margins.Top + elseNode.Margins.Bottom); - end; - - FParentControl := savedParent; - FCurrentPos := savedPos; - - // --- Adjust parent size --- - auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); - auraNode.Height := Max(cMinNodeHeight, maxHeightInRow + 2 * cNodePadding); - - // --- Optional Vertical Centering --- - var centerOffsetY: Single := (auraNode.Height / 2); - if Assigned(conditionNode) then - conditionNode.Position.Y := - centerOffsetY - - (conditionNode.Height + conditionNode.Margins.Top + conditionNode.Margins.Bottom) / 2 - + conditionNode.Margins.Top; - if Assigned(qMarkLabel) then - qMarkLabel.Position.Y := - centerOffsetY - (qMarkLabel.Height + qMarkLabel.Margins.Top + qMarkLabel.Margins.Bottom) / 2 + qMarkLabel.Margins.Top; - if Assigned(thenNode) then - thenNode.Position.Y := - centerOffsetY - (thenNode.Height + thenNode.Margins.Top + thenNode.Margins.Bottom) / 2 + thenNode.Margins.Top; - if Assigned(colonLabel) then - colonLabel.Position.Y := - centerOffsetY - (colonLabel.Height + colonLabel.Margins.Top + colonLabel.Margins.Bottom) / 2 + colonLabel.Margins.Top; - if Assigned(elseNode) then - elseNode.Position.Y := - centerOffsetY - (elseNode.Height + elseNode.Margins.Top + elseNode.Margins.Bottom) / 2 + elseNode.Margins.Top; - // --- End Optional Vertical Centering --- - - finally - dec(FExprDepth); - auraNode.EndUpdate; - end; - // Move current position down for the next sibling node in the outer scope - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; -var - auraNode: TAuraNode; - savedParent: TControl; - savedPos: TPointF; - savedExprDepth: Integer; - i: Integer; - paramLabel: TLabel; - lambdaLabel: TLabel; // Label for the lambda symbol - titleParamHeight: Single; // Height required for the combined title/param line - currentX: Single; // Horizontal position tracker for title/params - maxHeightInTitleRow: Single; // Max height in the first row - lambdaSymbol: string; - bodyNode: TAuraNode; -begin - lambdaSymbol := WideChar($03BB); // Unicode for Greek Small Letter Lambda - auraNode := CreateNode(Node); // Create standard framed node - auraNode.BeginUpdate; - try - auraNode.Frameless := False; - auraNode.BackgroundColor := $090000ff; - - // --- Create Title/Parameter Line --- - currentX := cNodePadding; - maxHeightInTitleRow := 0; - var yPos := cNodePadding; // Y position for the first row - - // 1. Lambda Symbol Label (Title) - lambdaLabel := AddLabel(auraNode, lambdaSymbol); - lambdaLabel.Position.Point := TPointF.Create(currentX, yPos); - lambdaLabel.Font.Style := lambdaLabel.Font.Style + [TFontStyle.fsBold]; // Make symbol bold - currentX := lambdaLabel.Position.X + lambdaLabel.Width + lambdaLabel.Margins.Right + cNodePadding / 2; // Add small gap - maxHeightInTitleRow := Max(maxHeightInTitleRow, lambdaLabel.Height + lambdaLabel.Margins.Top + lambdaLabel.Margins.Bottom); - - // 2. Add Parameter Labels (inline) - if High(Node.Parameters) >= 0 then - begin - // Add parenthesis - var openParen := AddLabel(auraNode, '('); - openParen.Font.Style := openParen.Font.Style - [TFontStyle.fsBold]; // Normal style - openParen.Position.Point := TPointF.Create(currentX, yPos); - currentX := openParen.Position.X + openParen.Width + openParen.Margins.Right + cNodePadding / 2; - maxHeightInTitleRow := Max(maxHeightInTitleRow, openParen.Height + openParen.Margins.Top + openParen.Margins.Bottom); - - for i := 0 to High(Node.Parameters) do - begin - paramLabel := AddLabel(auraNode, Node.Parameters[i].Name); - paramLabel.Font.Style := paramLabel.Font.Style - [TFontStyle.fsBold]; // Normal style - paramLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := - paramLabel.Position.X - + paramLabel.Width - + paramLabel.Margins.Right - + IfThen(i < High(Node.Parameters), cNodePadding / 2, 0); // Gap or no gap - maxHeightInTitleRow := Max(maxHeightInTitleRow, paramLabel.Height + paramLabel.Margins.Top + paramLabel.Margins.Bottom); - - // Add comma if not the last parameter - if i < High(Node.Parameters) then - begin - var commaLabel := AddLabel(auraNode, ','); - commaLabel.Font.Style := commaLabel.Font.Style - [TFontStyle.fsBold]; // Normal style - commaLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := commaLabel.Position.X + commaLabel.Width + commaLabel.Margins.Right + cNodePadding / 2; - maxHeightInTitleRow := Max(maxHeightInTitleRow, commaLabel.Height + commaLabel.Margins.Top + commaLabel.Margins.Bottom); - end; - end; - - // Add closing parenthesis - var closeParen := AddLabel(auraNode, ')'); - closeParen.Font.Style := closeParen.Font.Style - [TFontStyle.fsBold]; // Normal style - closeParen.Position.Point := TPointF.Create(currentX, yPos); - maxHeightInTitleRow := Max(maxHeightInTitleRow, closeParen.Height + closeParen.Margins.Top + closeParen.Margins.Bottom); - end - else - begin - // No parameters, add empty () for clarity - var emptyParens := AddLabel(auraNode, '()'); - emptyParens.Font.Style := emptyParens.Font.Style - [TFontStyle.fsBold]; - emptyParens.Position.Point := TPointF.Create(currentX, yPos); - maxHeightInTitleRow := Max(maxHeightInTitleRow, emptyParens.Height + emptyParens.Margins.Top + emptyParens.Margins.Bottom); - end; - - titleParamHeight := maxHeightInTitleRow; // Total height needed for the first row - - // --- Optional Vertical Centering for Title/Param Row --- - var centerOffsetYTitleRow: Single := yPos + titleParamHeight / 2; - for i := 0 to auraNode.ControlsCount - 1 do // Iterate controls created so far for the title row - begin - var ctrl := auraNode.Controls[i]; - ctrl.Position.Y := centerOffsetYTitleRow - (ctrl.Height + ctrl.Margins.Top + ctrl.Margins.Bottom) / 2 + ctrl.Margins.Top; - end; - // --- End Centering --- - - // 3. Visit Body - savedParent := FParentControl; - savedPos := FCurrentPos; - savedExprDepth := FExprDepth; - try - FParentControl := auraNode; // Body node is child of auraNode - // Start position for the body, below the title/parameter line - FCurrentPos := - TPointF.Create(cNodePadding, yPos + titleParamHeight + cChildAreaTopPadding); // Start below the calculated height - FExprDepth := 0; - - bodyNode := CallAccept(Node.Body); // Visit the body, get the node - - bodyNode.Frameless := false; - finally - FParentControl := savedParent; // Restore parent for siblings - FCurrentPos := savedPos; // Restore position for siblings - FExprDepth := savedExprDepth; - end; - - AdjustParentNodeSize(auraNode, [Node.Body]); // Adjust size based on title, params, and body - finally - auraNode.EndUpdate; - end; - // Move current position down for the next sibling node - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -function TAstVisualizer.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; -var - auraNode: TAuraNode; - savedParent: TControl; - savedPos: TPointF; - i: Integer; - calleeNode: TAuraNode; // Use TAuraNode - argNode: TAuraNode; // Use TAuraNode - parenLabel, commaLabel: TLabel; - // Layout variables - yPos: Single; // Current vertical position for the row - currentX: Single; // Horizontal position tracker - maxHeightInRow: Single; // Max height for row alignment - argStartIndex: Integer; // Index of first argument-related control -begin - auraNode := CreateNode(Node); // Create standard framed node - auraNode.Frameless := False; // Function calls generally have a frame - auraNode.BeginUpdate; - inc(FExprDepth); - try - yPos := cNodePadding; // Start directly with padding - currentX := cNodePadding; - maxHeightInRow := 0; - - // --- Visit Callee --- - savedParent := FParentControl; - savedPos := FCurrentPos; - FParentControl := auraNode; - FCurrentPos := TPointF.Create(currentX, yPos); // Position for callee - - calleeNode := CallAccept(Node.Callee); // Create callee's visual node - - if Assigned(calleeNode) then - begin - // Position callee explicitly (redundant if FCurrentPos worked, but safe) - calleeNode.Position.Point := TPointF.Create(currentX, yPos); - currentX := calleeNode.Position.X + calleeNode.Width + calleeNode.Margins.Right + cNodePadding / 2; // Update X pos - maxHeightInRow := Max(maxHeightInRow, calleeNode.Height + calleeNode.Margins.Top + calleeNode.Margins.Bottom); - end - else - currentX := cNodePadding; // Start parens from left if no callee - - // --- Add Arguments (Horizontal Layout) --- - - // Add opening parenthesis - parenLabel := AddLabel(auraNode, '('); - parenLabel.Font.Style := parenLabel.Font.Style - [TFontStyle.fsBold]; // Normal style - parenLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := parenLabel.Position.X + parenLabel.Width + parenLabel.Margins.Right + cNodePadding / 2; - maxHeightInRow := Max(maxHeightInRow, parenLabel.Height + parenLabel.Margins.Top + parenLabel.Margins.Bottom); - - for i := 0 to High(Node.Arguments) do - begin - // Set position for the argument node - FCurrentPos := TPointF.Create(currentX, yPos); - - // FParentControl is already auraNode - argNode := CallAccept(Node.Arguments[i]); // Create argument node - - if Assigned(argNode) then - begin - // Position it correctly (redundant, but safe) - argNode.Position.Point := TPointF.Create(currentX, yPos); - - currentX := - argNode.Position.X - + argNode.Width - + argNode.Margins.Right - + IfThen(i < High(Node.Arguments), cNodePadding / 2, 0); // Gap or no gap after arg - maxHeightInRow := Max(maxHeightInRow, argNode.Height + argNode.Margins.Top + argNode.Margins.Bottom); - - // Add comma if not the last argument - if i < High(Node.Arguments) then - begin - commaLabel := AddLabel(auraNode, ','); - commaLabel.Font.Style := commaLabel.Font.Style - [TFontStyle.fsBold]; // Normal style - commaLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := commaLabel.Position.X + commaLabel.Width + commaLabel.Margins.Right + cNodePadding / 2; - maxHeightInRow := Max(maxHeightInRow, commaLabel.Height + commaLabel.Margins.Top + commaLabel.Margins.Bottom); - end; - end - else // Should not happen - begin - currentX := currentX + cMinNodeWidth + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, cMinNodeHeight); - end; - end; - - // Add closing parenthesis - parenLabel := AddLabel(auraNode, ')'); - parenLabel.Font.Style := parenLabel.Font.Style - [TFontStyle.fsBold]; // Normal style - parenLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := parenLabel.Position.X + parenLabel.Width + parenLabel.Margins.Right; // Final X position - maxHeightInRow := Max(maxHeightInRow, parenLabel.Height + parenLabel.Margins.Top + parenLabel.Margins.Bottom); - - // --- Vertical Centering for the single row --- - var centerOffsetYRow: Single := yPos + maxHeightInRow / 2; - argStartIndex := 0; // Start check from first control - for i := argStartIndex to auraNode.ControlsCount - 1 do // Iterate ALL controls in the row - begin - var ctrl := auraNode.Controls[i]; - // Adjust Y based on calculated center and control's own height/margins - ctrl.Position.Y := centerOffsetYRow - (ctrl.Height + ctrl.Margins.Top + ctrl.Margins.Bottom) / 2 + ctrl.Margins.Top; - end; - // --- End Centering --- - yPos := yPos + maxHeightInRow; // Update yPos based on row height for final size calculation - - FParentControl := savedParent; // Restore parent for siblings - FCurrentPos := savedPos; // Restore position for siblings - - // --- Adjust Size --- - auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); // Width determined by final currentX - auraNode.Height := Max(cMinNodeHeight, yPos + cNodePadding); // Height determined by final yPos - - finally - dec(FExprDepth); - auraNode.EndUpdate; - end; - // Move current position down for the next sibling node - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -function TAstVisualizer.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; -var - auraNode: TAuraNode; -begin - // Visualize the expanded body - auraNode := ProcessStub('Macro Expansion', Node, [Node.ExpandedBody]); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; -var - auraNode: TAuraNode; - savedParent: TControl; - savedPos: TPointF; - expr: IAstNode; - prefixLabel: TLabel; - prefixText: string; - i: Integer; - prefixWidth: Single; // Width needed only for the 'return ' label - maxChildWidth: Single; // To calculate overall width - yPos: Single; // Current vertical position for the row - currentXOffset: Single; // Horizontal offset for the expression node - returnLabel: TLabel; // Label for measuring 'return ' - exprNode: TAuraNode; -begin - // Create the main node, frameless and without title - auraNode := CreateNode(Node); - auraNode.BeginUpdate; - try - auraNode.Frameless := True; - - savedParent := FParentControl; - savedPos := FCurrentPos; // Store original position for siblings - FParentControl := auraNode; - // Start position for children inside the node - yPos := cNodePadding; - maxChildWidth := 0; // Max width including prefix + expression - - // --- Determine width needed for 'return ' prefix --- - returnLabel := AddLabel(auraNode, 'return '); // Create temporarily to measure - prefixWidth := returnLabel.Width + returnLabel.Margins.Left + returnLabel.Margins.Right + cNodePadding; // Width + gap - returnLabel.Free; // Free temporary label - // --- End prefix width calculation --- - - // --- Visit children --- - for i := 0 to High(Node.Expressions) do - begin - expr := Node.Expressions[i]; - prefixLabel := nil; // Initialize prefixLabel for this iteration - - // 1. Create Prefix Label ONLY for the last statement - if i = High(Node.Expressions) then - begin - prefixText := 'return '; - prefixLabel := AddLabel(auraNode, prefixText); - prefixLabel.Position.Point := TPointF.Create(cNodePadding, yPos); // Place prefix label - currentXOffset := cNodePadding + prefixWidth; // Start expression node after the 'return ' prefix space - end - else - begin - prefixText := ''; // No prefix for other statements - currentXOffset := cNodePadding; // Start expression node directly at the padding edge - end; - - // 2. Visit Expression Node - // Set position for the expression node - FCurrentPos := TPointF.Create(currentXOffset, yPos); - - exprNode := CallAccept(expr); // This creates the node/label for the expression - - // Get the created control and update max width/height - if Assigned(exprNode) then - begin - var currentExprHeight := exprNode.Height + exprNode.Margins.Top + exprNode.Margins.Bottom; - var currentPrefixHeight: Single := 0; - if Assigned(prefixLabel) then - currentPrefixHeight := prefixLabel.Height + prefixLabel.Margins.Top + prefixLabel.Margins.Bottom; - - var rowHeight := Max(currentExprHeight, currentPrefixHeight); - - // --- Optional Vertical Centering within the row --- - if Assigned(prefixLabel) then - prefixLabel.Position.Y := yPos + (rowHeight / 2) - currentPrefixHeight / 2 + prefixLabel.Margins.Top; - exprNode.Position.Y := - yPos + (rowHeight / 2) - currentExprHeight / 2 + exprNode.Margins.Top; // Position expr node correctly - // --- End Vertical Centering --- - - // Update max width needed for the block node - maxChildWidth := Max(maxChildWidth, exprNode.Position.X + exprNode.Width + exprNode.Margins.Right); - - // Update Y position for the next row - yPos := yPos + rowHeight + FNodeVerticalGap; // Move down for the next statement - - end - else // Only prefix label exists (shouldn't happen if expression is valid) - begin - if Assigned(prefixLabel) then - begin - yPos := yPos + prefixLabel.Height + prefixLabel.Margins.Top + prefixLabel.Margins.Bottom + FNodeVerticalGap; - maxChildWidth := Max(maxChildWidth, prefixLabel.Position.X + prefixLabel.Width + prefixLabel.Margins.Right); - end - else - begin - yPos := yPos + cMinNodeHeight + FNodeVerticalGap; // Add minimal height if no control added - maxChildWidth := Max(maxChildWidth, cMinNodeWidth); - end; - end; - - end; - // --- Children Visited --- - - FParentControl := savedParent; // Restore parent for siblings - FCurrentPos := savedPos; // Restore position for siblings - - // --- Adjust parent size --- - auraNode.Width := Max(cMinNodeWidth, maxChildWidth + cNodePadding); // Total width + right padding - if auraNode.ControlsCount > 0 then // Check if anything was added - auraNode.Height := - Max(cMinNodeHeight, yPos - FNodeVerticalGap + cNodePadding) // Use final yPos (minus last gap) + bottom padding - else - auraNode.Height := cMinNodeHeight; // Ensure minimal height if block is empty - - finally - auraNode.EndUpdate; - end; - // Move current position down for the next sibling node in the outer scope - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -function TAstVisualizer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; -var - auraNode: TAuraNode; - varLabel: TLabel; - nameLabel: TLabel; - opLabel: TLabel; - initNode: TAuraNode; // Use TAuraNode - savedParent: TControl; - savedPos: TPointF; - currentX: Single; // Track horizontal position - maxHeightInRow: Single; // Track max height for row alignment - yPos: Single; // Y position for elements in this row -begin - // Create the main node, frameless - auraNode := CreateNode(Node); - auraNode.BeginUpdate; - try - auraNode.Frameless := true; - - // Start position for children inside the node - currentX := cNodePadding; - yPos := cNodePadding; // Initial Y position - maxHeightInRow := 0; - initNode := nil; - - // 1. "var" Label - varLabel := AddLabel(auraNode, 'var'); - varLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := varLabel.Position.X + varLabel.Width + varLabel.Margins.Right + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, varLabel.Height + varLabel.Margins.Top + varLabel.Margins.Bottom); - - // 2. Identifier Label - nameLabel := AddLabel(auraNode, Node.Identifier.Name); - nameLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := nameLabel.Position.X + nameLabel.Width + nameLabel.Margins.Right + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, nameLabel.Height + nameLabel.Margins.Top + nameLabel.Margins.Bottom); - - opLabel := nil; - if Assigned(Node.Initializer) then - begin - // 3. Operator Label (only if initializer exists) - opLabel := AddLabel(auraNode, ':='); - opLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := opLabel.Position.X + opLabel.Width + opLabel.Margins.Right + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, opLabel.Height + opLabel.Margins.Top + opLabel.Margins.Bottom); - - // 4. Initializer Expression Node (Visited Recursively) - savedParent := FParentControl; - savedPos := FCurrentPos; // Store original position for siblings - FParentControl := auraNode; // Children are added to this node - FCurrentPos := TPointF.Create(currentX, yPos); // Set position for the child node - - initNode := CallAccept(Node.Initializer); // Create the node/label - - if Assigned(initNode) then - begin - currentX := initNode.Position.X + initNode.Width + initNode.Margins.Right; - maxHeightInRow := Max(maxHeightInRow, initNode.Height + initNode.Margins.Top + initNode.Margins.Bottom); - end; - - FParentControl := savedParent; // Restore parent for siblings - FCurrentPos := savedPos; // Restore position for siblings - end; - - // --- Adjust parent size --- - auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); // Total width needed + right padding - auraNode.Height := Max(cMinNodeHeight, maxHeightInRow + 2 * cNodePadding); // Max height + top/bottom padding - - // --- Optional Vertical Centering --- - var centerOffsetY: Single := (auraNode.Height / 2); - if Assigned(varLabel) then - varLabel.Position.Y := - centerOffsetY - (varLabel.Height + varLabel.Margins.Top + varLabel.Margins.Bottom) / 2 + varLabel.Margins.Top; - if Assigned(nameLabel) then - nameLabel.Position.Y := - centerOffsetY - (nameLabel.Height + nameLabel.Margins.Top + nameLabel.Margins.Bottom) / 2 + nameLabel.Margins.Top; - if Assigned(opLabel) then - opLabel.Position.Y := centerOffsetY - (opLabel.Height + opLabel.Margins.Top + opLabel.Margins.Bottom) / 2 + opLabel.Margins.Top; - if Assigned(initNode) then - initNode.Position.Y := - centerOffsetY - (initNode.Height + initNode.Margins.Top + initNode.Margins.Bottom) / 2 + initNode.Margins.Top; - // --- End Optional Vertical Centering --- - - finally - auraNode.EndUpdate; - end; - // Move current position down for the next sibling node in the outer scope - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -function TAstVisualizer.VisitAssignment(const Node: IAssignmentNode): TDataValue; -var - auraNode: TAuraNode; - identifierLabel: TLabel; - operatorLabel: TLabel; - valueNode: TAuraNode; // Use TAuraNode - savedParent: TControl; - savedPos: TPointF; - currentX: Single; // Track horizontal position - maxHeightInRow: Single; // Track max height for row alignment - labelYPos: Single; // Y position for labels in this row - valueYPos: Single; // Y position for the value node in this row -begin - // Create the main node, frameless as it primarily serves as a container - auraNode := CreateNode(Node); - auraNode.BeginUpdate; - try - auraNode.Frameless := true; - - // Start position for children inside the node - currentX := cNodePadding; - labelYPos := cNodePadding; // Initial Y position for labels - maxHeightInRow := 0; - - // 1. Identifier Label - identifierLabel := AddLabel(auraNode, Node.Identifier.Name); - identifierLabel.Position.Point := TPointF.Create(currentX, labelYPos); - currentX := - identifierLabel.Position.X - + identifierLabel.Width - + identifierLabel.Margins.Right - + cNodePadding; // Update X pos after adding Margins.Right and gap - maxHeightInRow := Max(maxHeightInRow, identifierLabel.Height + identifierLabel.Margins.Top + identifierLabel.Margins.Bottom); - - // 2. Operator Label - operatorLabel := AddLabel(auraNode, ':='); - operatorLabel.Position.Point := TPointF.Create(currentX, labelYPos); - currentX := operatorLabel.Position.X + operatorLabel.Width + operatorLabel.Margins.Right + cNodePadding; // Update X pos - maxHeightInRow := Max(maxHeightInRow, operatorLabel.Height + operatorLabel.Margins.Top + operatorLabel.Margins.Bottom); - - // 3. Expression Node (Visited Recursively) - savedParent := FParentControl; - savedPos := FCurrentPos; // Store original position for siblings - FParentControl := auraNode; // Children are added to this node - valueYPos := cNodePadding; // Align top of value node with top of labels - FCurrentPos := TPointF.Create(currentX, valueYPos); // Set position for the child node - - valueNode := CallAccept(Node.Value); // This creates the node/label for the expression - - // Get the created control (it's the last one added to auraNode) - if Assigned(valueNode) then - begin - // Update currentX based on the right edge of the value control - currentX := valueNode.Position.X + valueNode.Width + valueNode.Margins.Right; // Added margin - // Update maxHeightInRow considering the value control's height and potential margins - maxHeightInRow := Max(maxHeightInRow, valueNode.Height + valueNode.Margins.Top + valueNode.Margins.Bottom); - end; - - FParentControl := savedParent; // Restore parent for siblings - FCurrentPos := savedPos; // Restore position for siblings - - // --- Adjust parent size --- - auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); // Total width needed + right padding - auraNode.Height := Max(cMinNodeHeight, maxHeightInRow + 2 * cNodePadding); // Max height + top/bottom padding - - // --- Optional Vertical Centering --- - var centerOffsetY: Single := (auraNode.Height / 2); - if Assigned(identifierLabel) then - identifierLabel.Position.Y := - centerOffsetY - - (identifierLabel.Height + identifierLabel.Margins.Top + identifierLabel.Margins.Bottom) / 2 - + identifierLabel.Margins.Top; - if Assigned(operatorLabel) then - operatorLabel.Position.Y := - centerOffsetY - - (operatorLabel.Height + operatorLabel.Margins.Top + operatorLabel.Margins.Bottom) / 2 - + operatorLabel.Margins.Top; - if Assigned(valueNode) then - valueNode.Position.Y := - centerOffsetY - (valueNode.Height + valueNode.Margins.Top + valueNode.Margins.Bottom) / 2 + valueNode.Margins.Top; - // --- End Optional Vertical Centering --- - - finally - auraNode.EndUpdate; - end; - // Move current position down for the next sibling node in the outer scope - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -function TAstVisualizer.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; -var - i: Integer; - paramStr: string; - auraNode: TAuraNode; -begin - paramStr := ''; - for i := 0 to High(Node.Parameters) do - begin - if i > 0 then - paramStr := paramStr + ', '; - paramStr := paramStr + Node.Parameters[i].Name; - end; - auraNode := ProcessStub('Macro Def: ' + Node.Name.Name + ' (' + paramStr + ')', Node, [Node.Body]); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; -var - auraNode: TAuraNode; -begin - auraNode := ProcessStub('Quasiquote', Node, [Node.Expression]); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitUnquote(const Node: IUnquoteNode): TDataValue; -var - auraNode: TAuraNode; -begin - auraNode := ProcessStub('Unquote', Node, [Node.Expression]); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; -var - auraNode: TAuraNode; -begin - auraNode := ProcessStub('Unquote-Splicing', Node, [Node.Expression]); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitIndexer(const Node: IIndexerNode): TDataValue; -var - auraNode: TAuraNode; -begin - auraNode := ProcessStub('Indexer []', Node, [Node.Base, Node.Index]); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; -var - auraNode: TAuraNode; -begin - auraNode := ProcessStub('Member Access: .' + Node.Member.Name, Node, [Node.Base]); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; -var - auraNode: TAuraNode; - labelHeight: Single; -begin - // Leaf node - auraNode := CreateNode(Node); - auraNode.BeginUpdate; - try - labelHeight := AddTitleLabel(auraNode, 'Create Series: ' + Node.Definition); - // Adjust size based on label size - if auraNode.ControlsCount > 0 then - begin - var Lbl := auraNode.Controls[0] as TLabel; - auraNode.Width := Max(cMinNodeWidth, Lbl.Width + Lbl.Margins.Left + Lbl.Margins.Right); - auraNode.Height := Max(cMinNodeHeight, labelHeight + cNodePadding); - end - else - AdjustParentNodeSize(auraNode, []); - - finally - auraNode.EndUpdate; - end; - FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap; - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -function TAstVisualizer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; -var - children: TArray; - auraNode: TAuraNode; -begin - children := [Node.Value]; - if Assigned(Node.Lookback) then - children := children + [Node.Lookback]; - auraNode := ProcessStub('Add Item to: ' + Node.Series.Name, Node, children); - Result := TDataValue.FromGeneric(auraNode); -end; - -function TAstVisualizer.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; -var - auraNode: TAuraNode; - labelHeight: Single; -begin - // Leaf node - auraNode := CreateNode(Node); - auraNode.BeginUpdate; - try - labelHeight := AddTitleLabel(auraNode, 'Length of: ' + Node.Series.Name); - // Adjust size based on label size - if auraNode.ControlsCount > 0 then - begin - var Lbl := auraNode.Controls[0] as TLabel; - auraNode.Width := Max(cMinNodeWidth, Lbl.Width + Lbl.Margins.Left + Lbl.Margins.Right); - auraNode.Height := Max(cMinNodeHeight, labelHeight + cNodePadding); - end - else - AdjustParentNodeSize(auraNode, []); - - finally - auraNode.EndUpdate; - end; - FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap; - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -function TAstVisualizer.VisitRecurNode(const Node: IRecurNode): TDataValue; -var - auraNode: TAuraNode; - savedParent: TControl; - savedPos: TPointF; - i: Integer; - recurLabel: TLabel; - argNode: TAuraNode; // Use TAuraNode - parenLabel, commaLabel: TLabel; - // Layout variables - yPos: Single; // Current vertical position for the row - currentX: Single; // Horizontal position tracker - maxHeightInRow: Single; // Max height for row alignment - argStartIndex: Integer; // Index of first argument-related control -begin - // Create the main node, frameless - auraNode := CreateNode(Node); - auraNode.BeginUpdate; - inc(FExprDepth); - try - auraNode.Frameless := True; - - // Start position for children inside the node - currentX := cNodePadding; - yPos := cNodePadding; // Initial Y position - maxHeightInRow := 0; - - // 1. "recur" Label - recurLabel := AddLabel(auraNode, WideChar($03BB)); - recurLabel.Font.Style := recurLabel.Font.Style + [TFontStyle.fsBold]; // Make keyword bold - recurLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := recurLabel.Position.X + recurLabel.Width + recurLabel.Margins.Right + cNodePadding / 2; // Update X pos - maxHeightInRow := Max(maxHeightInRow, recurLabel.Height + recurLabel.Margins.Top + recurLabel.Margins.Bottom); - - // --- Add Arguments (Horizontal Layout) --- - - // Add opening parenthesis - parenLabel := AddLabel(auraNode, '('); - parenLabel.Font.Style := parenLabel.Font.Style - [TFontStyle.fsBold]; // Normal style - parenLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := parenLabel.Position.X + parenLabel.Width + parenLabel.Margins.Right + cNodePadding / 2; - maxHeightInRow := Max(maxHeightInRow, parenLabel.Height + parenLabel.Margins.Top + parenLabel.Margins.Bottom); - - savedParent := FParentControl; - savedPos := FCurrentPos; - FParentControl := auraNode; // Arguments are children of auraNode - - for i := 0 to High(Node.Arguments) do - begin - // Set position for the argument node - FCurrentPos := TPointF.Create(currentX, yPos); - - argNode := CallAccept(Node.Arguments[i]); // Create argument node - - if Assigned(argNode) then - begin - // Position it correctly (redundant, but safe) - argNode.Position.Point := TPointF.Create(currentX, yPos); - - currentX := - argNode.Position.X - + argNode.Width - + argNode.Margins.Right - + IfThen(i < High(Node.Arguments), cNodePadding / 2, 0); // Gap or no gap after arg - maxHeightInRow := Max(maxHeightInRow, argNode.Height + argNode.Margins.Top + argNode.Margins.Bottom); - - // Add comma if not the last argument - if i < High(Node.Arguments) then - begin - commaLabel := AddLabel(auraNode, ','); - commaLabel.Font.Style := commaLabel.Font.Style - [TFontStyle.fsBold]; // Normal style - commaLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := commaLabel.Position.X + commaLabel.Width + commaLabel.Margins.Right + cNodePadding / 2; - maxHeightInRow := Max(maxHeightInRow, commaLabel.Height + commaLabel.Margins.Top + commaLabel.Margins.Bottom); - end; - end - else // Should not happen - begin - currentX := currentX + cMinNodeWidth + cNodePadding; - maxHeightInRow := Max(maxHeightInRow, cMinNodeHeight); - end; - end; - - // Add closing parenthesis - parenLabel := AddLabel(auraNode, ')'); - parenLabel.Font.Style := parenLabel.Font.Style - [TFontStyle.fsBold]; // Normal style - parenLabel.Position.Point := TPointF.Create(currentX, yPos); - currentX := parenLabel.Position.X + parenLabel.Width + parenLabel.Margins.Right; // Final X position - maxHeightInRow := Max(maxHeightInRow, parenLabel.Height + parenLabel.Margins.Top + parenLabel.Margins.Bottom); - - // --- Vertical Centering for the single row --- - var centerOffsetYRow: Single := yPos + maxHeightInRow / 2; - argStartIndex := 0; // Start check from first control ("recur") - for i := argStartIndex to auraNode.ControlsCount - 1 do // Iterate ALL controls in the row - begin - var ctrl := auraNode.Controls[i]; - // Adjust Y based on calculated center and control's own height/margins - ctrl.Position.Y := centerOffsetYRow - (ctrl.Height + ctrl.Margins.Top + ctrl.Margins.Bottom) / 2 + ctrl.Margins.Top; - end; - // --- End Centering --- - yPos := yPos + maxHeightInRow; // Update yPos based on row height for final size calculation - - FParentControl := savedParent; // Restore parent for siblings - FCurrentPos := savedPos; // Restore position for siblings - - // --- Adjust Size --- - auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); // Width determined by final currentX - auraNode.Height := Max(cMinNodeHeight, yPos + cNodePadding); // Height determined by final yPos - - finally - dec(FExprDepth); - auraNode.EndUpdate; - end; - // Move current position down for the next sibling node - FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y - Result := TDataValue.FromGeneric(auraNode); // Return the created node -end; - -end. diff --git a/Doc/Monomorphisierung.md b/Doc/Monomorphisierung.md new file mode 100644 index 0000000..baf629c --- /dev/null +++ b/Doc/Monomorphisierung.md @@ -0,0 +1,89 @@ + +# Projektplan: Hybride Monomorphisierung + +* **Datum:** 30.10.2025 +* **Version:** 1.0 + +## 1. Motivation + +Das aktuelle Typsystem (`Myc.Ast.Types`) ist mächtig, aber die Inferenz für Lambda-Parameter endet bei `stUnknown`. Dies erzwingt Laufzeit-Typüberprüfungen im `TEvaluatorVisitor` und verkompliziert AOT/JIT-Kompilierung. + +## 2. Ziel + +Implementierung einer **hybriden Monomorphisierungsstrategie** zur Compile-Zeit (innerhalb des `TAstBinder`). + +1. **Statische Spezialisierung:** Alle Funktionsaufrufe, deren Callee *und* Argumenttypen zur Compile-Zeit statisch bekannt sind, werden spezialisiert. `stUnknown` wird für diese Pfade eliminiert. +2. **Dynamischer Fallback:** Alle dynamischen Aufrufe (z.B. Higher-Order-Funktionen, deren Callee aus einer Variable stammt) nutzen weiterhin den bestehenden dynamischen Pfad (`vkMethod` mit `TDataValue`-Wrapper). +3. **Optimierungs-Vorbereitung:** Der statisch spezialisierte AST (der "Entrypoint") dient als saubere Basis für optionale Optimierungen (z.B. LLVM-Codegen). + +## 3. Ergebnis: Die Strategie + +Die Implementierung erfordert eine Erweiterung des `TAstBinder`, um einen **Spezialisierungs-Cache** zu verwalten und bei Bedarf **rekursive Binding-Pässe** durchzuführen. + +### Phase 1: Modifikation der Kern-Typen + +1. **`TBoundLambdaExpressionNode` (Generic):** + * Muss seinen *originalen, ungebundenen* Body (`IAstNode`) behalten, um als Vorlage für das Re-Binding zu dienen. + * Führt einen **Spezialisierungs-Cache** (z.B. `TDictionary`). +2. **`ISpecializedBody` (Interface):** + * Repräsentiert einen erfolgreich monomorphisierten Entrypoint. + * `function GetBodyAst: IAstNode;` + * `function GetScopeDescriptor: IScopeDescriptor;` + * `function GetReturnType: IStaticType;` +3. **`TBoundFunctionCallNode`:** + * Benötigt ein neues Feld, um *entweder* den dynamischen Callee (wie bisher) *oder* den statischen `ISpecializedBody` (den Entrypoint) zu halten. + +### Phase 2: Anpassung des `TAstBinder` (Trigger) + +`TAstBinder.VisitFunctionCall` wird zur zentralen Weichenstellung: + +1. Binde alle Argumente und ermittle ihre statischen Typen (die `Aufrufsignatur`). +2. Binde den `Callee`. +3. **Fallunterscheidung:** + * **Fall A (Dynamischer Aufruf):** Der `Callee` ist *nicht* als `TBoundLambdaExpressionNode` statisch bekannt (z.B. `(map (if flag f1 f2) ...)`). + * **Aktion:** Der Aufruf wird als dynamisch belassen (Fallback). Es wird lediglich geprüft, ob der `Callee` den Typ `stMethod` hat. Es findet keine Spezialisierung statt. + * **Fall B (Statischer Aufruf):** Der `Callee` ist ein `TBoundLambdaExpressionNode`. + * **Aktion:** Trigger die Monomorphisierung. + * Rufe `Callee.GetSpecialization(Aufrufsignatur)` auf. + * Der `TBoundFunctionCallNode` wird modifiziert und verweist direkt auf das zurückgegebene `ISpecializedBody`. + * Der `StaticType` des `TBoundFunctionCallNode` wird auf `ISpecializedBody.GetReturnType` gesetzt. + +### Phase 3: Die Monomorphisierung (Der Re-Bind Pass) + +Dies ist die Kernlogik (z.B. in `TBoundLambdaExpressionNode.GetSpecialization`): + +1. **Cache-Lookup:** Prüfe, ob für die `Aufrufsignatur` bereits ein `ISpecializedBody` im Cache existiert. +2. **Cache-Miss (Re-Binding):** + * Erzeuge einen neuen, temporären `IScopeDescriptor` für den Sub-Bind-Prozess. + * **Populiere den Scope:** Definiere die Parameter-Namen (z.B. `[a b]`) in diesem Scope, aber verwende die *konkreten Typen* der `Aufrufsignatur` (z.B. `stFloat`, `stOrdinal`) anstelle von `stUnknown`. + * **Rekursiver Binder:** Erzeuge einen neuen `TAstBinder` (oder rufe den aktuellen re-entrant auf), der den *originalen, ungebundenen Body* des Lambdas mit diesem spezialisierten Scope bindet. + * **Fehlerbehandlung:** Schlägt dieser Sub-Bind-Prozess fehl (z.B. `ETypeException`, weil `a` (jetzt `stFloat`) mit einem `Text` addiert wird), ist der Aufruf ungültig -> **Compile-Fehler**. + * **Erfolg:** Das Ergebnis ist der spezialisierte `IAstNode` (Body) und der `IScopeDescriptor`. Erzeuge ein `ISpecializedBody`-Objekt, speichere es im Cache und gib es zurück. + +### Phase 4: Anpassung des `TEvaluatorVisitor` (Ausführung) + +`TEvaluatorVisitor.VisitFunctionCall` muss nun zwei Arten von Calls behandeln: + +1. **Dynamischer Call:** `Callee` ist `vkMethod`. (Wie bisher: `(calleeValue.AsMethod)(argValues)`). +2. **Statischer/Monomorphisierter Call:** `Callee` ist ein `ISpecializedBody`. + * Erzeuge den `IExecutionScope` (via `ISpecializedBody.GetScopeDescriptor`). + * Populiere den Scope mit den (bereits evaluierten) `argValues`. + * Führe den `ISpecializedBody.GetBodyAst` direkt mit `Accept(Self)` aus. (TCO muss hier ebenfalls beachtet werden). + +### Phase 5: LLVM-Optimierung (Optional) + +Der in Phase 3 erzeugte `ISpecializedBody` ist der "corner case" für die Optimierung: + +* Wenn ein `ISpecializedBody` erfolgreich erstellt wurde *und* dieser AST-Body die "harten" Kriterien erfüllt (keine dynamischen Internals), wird er als **Kandidat für LLVM-AOT/JIT** markiert. +* Ein LLVM-Backend kann diese Kandidaten aufnehmen, LLVM-IR generieren und den `ISpecializedBody` im Cache durch einen nativen Funktionspointer (den FFI-Entrypoint) ersetzen. + +--- + +## 4. TODOs + +* [ ] `TBoundLambdaExpressionNode` erweitern, um den originalen Body und den Spezialisierungs-Cache zu halten. +* [ ] `ISpecializedBody` (oder äquivalente Struktur) definieren. +* [ ] `TAstBinder.VisitFunctionCall` um die Weichenstellung (Fall A/B) erweitern. +* [ ] Den re-entranten Monomorphisierungs-Pass (Phase 3) implementieren. +* [ ] `TEvaluatorVisitor.VisitFunctionCall` für die Ausführung von `ISpecializedBody` anpassen. +* [ ] Typsystem (`TTypeRules`) auf Robustheit für die neuen, strikten Prüfungen im Sub-Bind-Pass testen. \ No newline at end of file