|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
unit Myc.Fmx.AstVisualizer;
|
|
|
|
|
unit Myc.Fmx.AstVisualizer deprecated;
|
|
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
|
@@ -25,14 +25,13 @@ uses
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
// Visits the AST and creates visual TAuraNode representations on a TAuraWorkspace.
|
|
|
|
|
TAstVisualizer = class(TAstVisitor)
|
|
|
|
|
TAstVisualizer = class(TAstVisitor, IAstVisualizer)
|
|
|
|
|
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
|
|
|
|
|
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;
|
|
|
|
@@ -44,7 +43,12 @@ type
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
@@ -71,10 +75,22 @@ type
|
|
|
|
|
function VisitRecurNode(const Node: IRecurNode): TDataValue; override;
|
|
|
|
|
|
|
|
|
|
public
|
|
|
|
|
constructor Create(AWorkspace: TAuraWorkspace; AParentControl: TControl; const AStartPosition: TPointF);
|
|
|
|
|
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
|
|
|
|
@@ -89,24 +105,25 @@ uses
|
|
|
|
|
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);
|
|
|
|
|
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 := 15; // Vertical gap between child nodes
|
|
|
|
|
FExprDepth := 0;
|
|
|
|
|
FNodeVerticalGap := ANodeVerticalGap;
|
|
|
|
|
FExprDepth := AExprDepth;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
destructor TAstVisualizer.Destroy;
|
|
|
|
@@ -264,6 +281,26 @@ begin
|
|
|
|
|
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<IAstNode>): TAuraNode;
|
|
|
|
|
var
|
|
|
|
@@ -308,157 +345,62 @@ begin
|
|
|
|
|
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;
|
|
|
|
|
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);
|
|
|
|
|
var auraNode := TAuraConstantNode.CreateConstantNode(FWorkspace, Node, Self);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
auraNode: TAuraIdentifierNode; // Use specific type
|
|
|
|
|
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;
|
|
|
|
|
// 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<TAuraNode>(auraNode); // Return the created node
|
|
|
|
|
|
|
|
|
|
Result := TDataValue.FromGeneric<TAuraNode>(auraNode); // Return as generic TAuraNode
|
|
|
|
|
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;
|
|
|
|
|
auraNode: TAuraBinaryExpressionNode; // Use specific type
|
|
|
|
|
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;
|
|
|
|
|
// Store position before creating the node
|
|
|
|
|
savedPos := FCurrentPos;
|
|
|
|
|
|
|
|
|
|
// Start position for children inside the node
|
|
|
|
|
currentX := cNodePadding;
|
|
|
|
|
yPos := cNodePadding; // Initial Y position
|
|
|
|
|
maxHeightInRow := 0;
|
|
|
|
|
// Create the specific node type using its factory
|
|
|
|
|
auraNode := TAuraBinaryExpressionNode.CreateBinaryExpressionNode(FWorkspace, Node, Self);
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
Result := TDataValue.FromGeneric<TAuraNode>(auraNode); // Return as generic TAuraNode
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
function TAstVisualizer.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue;
|
|
|
|
|