1398 lines
62 KiB
ObjectPascal
1398 lines
62 KiB
ObjectPascal
unit Myc.Fmx.AstVisualizer;
|
|
|
|
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)
|
|
private
|
|
FWorkspace: TAuraWorkspace;
|
|
FParentControl: TControl; // The current parent FMX control for newly created nodes
|
|
FCurrentPos: TPointF; // Current position for the next node relative to FParentControl
|
|
FNodeVerticalGap: Single; // Vertical gap between nodes
|
|
FExprDepth: Integer;
|
|
|
|
// 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<IAstNode>);
|
|
// Generic processing logic for AST nodes with children. Returns the created TAuraNode.
|
|
function ProcessStub(const ATitle: string; const ANode: IAstNode; const AChildren: TArray<IAstNode>): TAuraNode;
|
|
|
|
// Calls Accept on the node and returns the resulting TAuraNode.
|
|
function CallAccept(const Node: IAstNode): TAuraNode;
|
|
|
|
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);
|
|
destructor Destroy; override;
|
|
|
|
class function CreateVisitor(AWorkspace: TAuraWorkspace; const StartPosition: TPointF): IAstVisitor;
|
|
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
|
|
cNodePadding = 10; // Reduced padding
|
|
cTitleTopPadding = 2;
|
|
cTitleBottomPadding = 4;
|
|
cChildAreaTopPadding = 5;
|
|
cNodeVerticalGap = 10; // Reduced gap
|
|
cMinNodeWidth = 10;
|
|
cMinNodeHeight = 10;
|
|
|
|
{ TAstVisualizer }
|
|
|
|
constructor TAstVisualizer.Create(AWorkspace: TAuraWorkspace; AParentControl: TControl; const AStartPosition: TPointF);
|
|
begin
|
|
inherited Create;
|
|
FWorkspace := AWorkspace;
|
|
FParentControl := AParentControl;
|
|
FCurrentPos := AStartPosition;
|
|
FNodeVerticalGap := 15; // Vertical gap between child nodes
|
|
FExprDepth := 0;
|
|
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<IAstNode>);
|
|
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<TAuraNode>;
|
|
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;
|
|
|
|
// Generic processing logic for AST nodes with children. Returns the created TAuraNode.
|
|
function TAstVisualizer.ProcessStub(const ATitle: string; const ANode: IAstNode; const AChildren: TArray<IAstNode>): TAuraNode;
|
|
var
|
|
auraNode: TAuraNode;
|
|
savedParent: TControl;
|
|
savedPos: TPointF;
|
|
child: IAstNode;
|
|
titleLabelHeight: Single;
|
|
childNode: TAuraNode; // To potentially use the return value
|
|
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;
|
|
|
|
// --- Visitor Implementations ---
|
|
|
|
function TAstVisualizer.VisitConstant(const Node: IConstantNode): TDataValue;
|
|
var
|
|
auraNode: TAuraNode;
|
|
valueStr: string;
|
|
isConcise: Boolean;
|
|
begin
|
|
valueStr := Node.Value.ToString;
|
|
// Check if it's a number or a single-line text and not excessively long
|
|
isConcise := ((Node.Value.Kind = TDataValueKind.vkScalar) or (Pos(sLineBreak, valueStr) = 0)) and (Length(valueStr) < 40);
|
|
|
|
auraNode := CreateNode(Node); // Create node without title
|
|
auraNode.BeginUpdate;
|
|
try
|
|
if isConcise then
|
|
begin
|
|
// Display value directly in a centered label, frameless node
|
|
auraNode.Frameless := true;
|
|
AddLabel(auraNode, valueStr);
|
|
end
|
|
else
|
|
begin
|
|
// Use standard framed node with Title label + Value label
|
|
auraNode.Frameless := False;
|
|
|
|
// Title Label
|
|
var titleLabelHeight := AddTitleLabel(auraNode, 'Constant');
|
|
|
|
// Value Label
|
|
var valueLabel := AddLabel(auraNode, valueStr);
|
|
valueLabel.Position.Y := valueLabel.Position.Y + titleLabelHeight;
|
|
valueLabel.AutoSize := False; // ***Important: Disable AutoSize when Align=Client***
|
|
valueLabel.WordWrap := True; // Allow wrapping for long text
|
|
end;
|
|
|
|
AdjustParentNodeSize(auraNode, []); // Adjust based on labels' sizes
|
|
finally
|
|
auraNode.EndUpdate;
|
|
end;
|
|
// Move current position down for the next sibling node
|
|
FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap;
|
|
Result := TDataValue.FromGeneric<TAuraNode>(auraNode); // Return the created node
|
|
end;
|
|
|
|
function TAstVisualizer.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
|
|
var
|
|
auraNode: TAuraNode;
|
|
begin
|
|
// Leaf node, direct creation without ProcessStub
|
|
auraNode := CreateNode(Node);
|
|
auraNode.Frameless := True; // Make identifiers frameless like concise constants
|
|
auraNode.BeginUpdate;
|
|
try
|
|
auraNode.Frameless := true;
|
|
AddLabel(auraNode, Node.Name);
|
|
AdjustParentNodeSize(auraNode, []); // Fallback just in case
|
|
finally
|
|
auraNode.EndUpdate;
|
|
end;
|
|
FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap;
|
|
Result := TDataValue.FromGeneric<TAuraNode>(auraNode); // Return the created node
|
|
end;
|
|
|
|
function TAstVisualizer.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
|
|
var
|
|
auraNode: TAuraNode;
|
|
leftNode: TAuraNode; // Use TAuraNode directly
|
|
operatorLabel: TLabel;
|
|
rightNode: TAuraNode; // Use TAuraNode directly
|
|
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 as it primarily serves as a container
|
|
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;
|
|
leftNode := nil;
|
|
rightNode := nil;
|
|
|
|
// 1. Left 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
|
|
|
|
leftNode := CallAccept(Node.Left); // Use CallAccept and store result
|
|
|
|
if Assigned(leftNode) then
|
|
begin
|
|
// Update currentX based on the right edge of the left control + gap
|
|
currentX := leftNode.Position.X + leftNode.Width + leftNode.Margins.Right + cNodePadding;
|
|
maxHeightInRow := Max(maxHeightInRow, leftNode.Height + leftNode.Margins.Top + leftNode.Margins.Bottom);
|
|
end;
|
|
|
|
// 2. Operator Label
|
|
operatorLabel := AddLabel(auraNode, Node.Operator.ToString);
|
|
operatorLabel.Position.Point := TPointF.Create(currentX, yPos); // Position next to left operand
|
|
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. Right Expression Node (Visited Recursively)
|
|
// FParentControl is already auraNode
|
|
FCurrentPos := TPointF.Create(currentX, yPos); // Set position for the child node
|
|
|
|
rightNode := CallAccept(Node.Right); // Use CallAccept and store result
|
|
|
|
if Assigned(rightNode) then
|
|
begin
|
|
// Update currentX based on the right edge of the right control
|
|
currentX := rightNode.Position.X + rightNode.Width + rightNode.Margins.Right;
|
|
maxHeightInRow := Max(maxHeightInRow, rightNode.Height + rightNode.Margins.Top + rightNode.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(leftNode) then
|
|
leftNode.Position.Y :=
|
|
centerOffsetY - (leftNode.Height + leftNode.Margins.Top + leftNode.Margins.Bottom) / 2 + leftNode.Margins.Top;
|
|
if Assigned(operatorLabel) then
|
|
operatorLabel.Position.Y :=
|
|
centerOffsetY
|
|
- (operatorLabel.Height + operatorLabel.Margins.Top + operatorLabel.Margins.Bottom) / 2
|
|
+ operatorLabel.Margins.Top;
|
|
if Assigned(rightNode) then
|
|
rightNode.Position.Y :=
|
|
centerOffsetY - (rightNode.Height + rightNode.Margins.Top + rightNode.Margins.Bottom) / 2 + rightNode.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; // Use savedPos.Y for vertical alignment with siblings
|
|
Result := TDataValue.FromGeneric<TAuraNode>(auraNode); // Return the created node
|
|
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<TAuraNode>(auraNode);
|
|
end;
|
|
|
|
function TAstVisualizer.VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
|
|
var
|
|
children: TArray<IAstNode>;
|
|
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<TAuraNode>(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;
|
|
conditionNode := nil;
|
|
thenNode := nil;
|
|
elseNode := nil;
|
|
|
|
// 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<TAuraNode>(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);
|
|
currentX := closeParen.Position.X + closeParen.Width + closeParen.Margins.Right + cNodePadding / 2; // Add final gap
|
|
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);
|
|
currentX := emptyParens.Position.X + emptyParens.Width + emptyParens.Margins.Right + cNodePadding / 2;
|
|
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<TAuraNode>(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;
|
|
calleeNode := nil;
|
|
|
|
// --- 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<TAuraNode>(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<TAuraNode>(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;
|
|
prefixWidth := 0;
|
|
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<TAuraNode>(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);
|
|
|
|
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<TAuraNode>(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;
|
|
valueNode := nil;
|
|
|
|
// 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<TAuraNode>(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<TAuraNode>(auraNode);
|
|
end;
|
|
|
|
function TAstVisualizer.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue;
|
|
var
|
|
auraNode: TAuraNode;
|
|
begin
|
|
auraNode := ProcessStub('Quasiquote', Node, [Node.Expression]);
|
|
Result := TDataValue.FromGeneric<TAuraNode>(auraNode);
|
|
end;
|
|
|
|
function TAstVisualizer.VisitUnquote(const Node: IUnquoteNode): TDataValue;
|
|
var
|
|
auraNode: TAuraNode;
|
|
begin
|
|
auraNode := ProcessStub('Unquote', Node, [Node.Expression]);
|
|
Result := TDataValue.FromGeneric<TAuraNode>(auraNode);
|
|
end;
|
|
|
|
function TAstVisualizer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue;
|
|
var
|
|
auraNode: TAuraNode;
|
|
begin
|
|
auraNode := ProcessStub('Unquote-Splicing', Node, [Node.Expression]);
|
|
Result := TDataValue.FromGeneric<TAuraNode>(auraNode);
|
|
end;
|
|
|
|
function TAstVisualizer.VisitIndexer(const Node: IIndexerNode): TDataValue;
|
|
var
|
|
auraNode: TAuraNode;
|
|
begin
|
|
auraNode := ProcessStub('Indexer []', Node, [Node.Base, Node.Index]);
|
|
Result := TDataValue.FromGeneric<TAuraNode>(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<TAuraNode>(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<TAuraNode>(auraNode); // Return the created node
|
|
end;
|
|
|
|
function TAstVisualizer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
|
var
|
|
children: TArray<IAstNode>;
|
|
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<TAuraNode>(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<TAuraNode>(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<TAuraNode>(auraNode); // Return the created node
|
|
end;
|
|
|
|
end.
|