1778 lines
62 KiB
ObjectPascal
1778 lines
62 KiB
ObjectPascal
unit Myc.Ast.Visualizer;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Classes,
|
|
System.UITypes,
|
|
System.Types,
|
|
System.Generics.Collections,
|
|
FMX.Types,
|
|
FMX.Controls,
|
|
FMX.Objects,
|
|
FMX.Graphics,
|
|
Myc.Ast.Nodes;
|
|
|
|
type
|
|
TPinConnection = record
|
|
OutputPin: TControl;
|
|
InputPin: TControl;
|
|
constructor Create(AOutputPin, AInputPin: TControl);
|
|
end;
|
|
|
|
TPinShape = (psCircle, psTriangle);
|
|
TPinAlignment = (paLeft, paRight);
|
|
|
|
// New enum to select the visualization style.
|
|
TVisualizationMode = (vmDetailed, vmControlFlow);
|
|
|
|
// A movable panel with a custom-painted border and title.
|
|
TAuraNode = class(TStyledControl)
|
|
private
|
|
// Flag to indicate if the control is currently being dragged.
|
|
FIsDragging: Boolean;
|
|
// Stores the mouse coordinates at the beginning of the drag operation.
|
|
FDownPos: TPointF;
|
|
// Properties for the custom-drawn border.
|
|
FBorderColor: TAlphaColor;
|
|
FBorderWidth: Single;
|
|
FBorderRadius: Single;
|
|
// Properties for the title
|
|
FTitle: string;
|
|
FTitleFont: TFont;
|
|
FTitleFontColor: TAlphaColor;
|
|
// If true, the node is drawn without a border and with a transparent background.
|
|
FFrameless: Boolean;
|
|
|
|
procedure SetBorderColor(const Value: TAlphaColor);
|
|
procedure SetBorderWidth(const Value: Single);
|
|
procedure SetBorderRadius(const Value: Single);
|
|
procedure SetTitle(const Value: string);
|
|
procedure SetTitleFont(const Value: TFont);
|
|
procedure SetTitleFontColor(const Value: TAlphaColor);
|
|
procedure TitleFontChanged(Sender: TObject);
|
|
procedure SetFrameless(const Value: Boolean);
|
|
protected
|
|
procedure Paint; override;
|
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
|
procedure MouseMove(Shift: TShiftState; X, Y: Single); override;
|
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
published
|
|
// Custom border properties
|
|
property BorderColor: TAlphaColor read FBorderColor write SetBorderColor;
|
|
property BorderWidth: Single read FBorderWidth write SetBorderWidth;
|
|
property BorderRadius: Single read FBorderRadius write SetBorderRadius;
|
|
|
|
// Title properties
|
|
property Title: string read FTitle write SetTitle;
|
|
property TitleFont: TFont read FTitleFont write SetTitleFont;
|
|
property TitleFontColor: TAlphaColor read FTitleFontColor write SetTitleFontColor;
|
|
|
|
// Behavior properties
|
|
property Frameless: Boolean read FFrameless write SetFrameless;
|
|
|
|
// Standard control properties
|
|
property Align;
|
|
property Anchors;
|
|
property ClipChildren default True;
|
|
property Cursor default crDefault;
|
|
property DragMode default TDragMode.dmManual;
|
|
property Enabled;
|
|
property Height;
|
|
property HelpContext;
|
|
property HelpKeyword;
|
|
property HelpType;
|
|
property Hint;
|
|
property HitTest default True;
|
|
property Locked;
|
|
property Margins;
|
|
property Opacity;
|
|
property Padding;
|
|
property PopupMenu;
|
|
property Position;
|
|
property RotationAngle;
|
|
property RotationCenter;
|
|
property Scale;
|
|
property Size;
|
|
property StyleLookup;
|
|
property Visible;
|
|
property Width;
|
|
property OnClick;
|
|
property OnDblClick;
|
|
property OnMouseDown;
|
|
property OnMouseMove;
|
|
property OnMouseUp;
|
|
property OnMouseWheel;
|
|
property OnMouseEnter;
|
|
property OnMouseLeave;
|
|
end;
|
|
|
|
TAuraWorkspace = class(TStyledControl)
|
|
private
|
|
FConnections: TArray<TPinConnection>;
|
|
protected
|
|
procedure Paint; override;
|
|
procedure DoDeleteChildren; override;
|
|
public
|
|
procedure BuildTree(const Root: IAstNode; const Position: TPointF; AMode: TVisualizationMode);
|
|
|
|
published
|
|
// Standard control properties
|
|
property Align;
|
|
property Anchors;
|
|
property ClipChildren default True;
|
|
property Cursor default crDefault;
|
|
property DragMode default TDragMode.dmManual;
|
|
property Enabled;
|
|
property Height;
|
|
property HelpContext;
|
|
property HelpKeyword;
|
|
property HelpType;
|
|
property Hint;
|
|
property HitTest default True;
|
|
property Locked;
|
|
property Margins;
|
|
property Opacity;
|
|
property Padding;
|
|
property PopupMenu;
|
|
property Position;
|
|
property RotationAngle;
|
|
property RotationCenter;
|
|
property Scale;
|
|
property Size;
|
|
property StyleLookup;
|
|
property Visible;
|
|
property Width;
|
|
property OnClick;
|
|
property OnDblClick;
|
|
property OnMouseDown;
|
|
property OnMouseMove;
|
|
property OnMouseUp;
|
|
property OnMouseWheel;
|
|
property OnMouseEnter;
|
|
property OnMouseLeave;
|
|
end;
|
|
|
|
TAstToAuraNodeVisitor = class(TInterfacedObject, IAstVisitor)
|
|
public
|
|
type
|
|
// Holds the result of a node visitation, separating layout and connection concerns.
|
|
TAuraNodeResult = record
|
|
LayoutNode: TAuraNode;
|
|
OutputPin: TControl;
|
|
end;
|
|
TAuraNodeResultList = TList<TAuraNodeResult>;
|
|
TChildVisitorProc = reference to procedure(const Visitor: IAstVisitor);
|
|
// The lambda now returns the full result, including the output pin reference.
|
|
TCreateParentNodeProc = reference to function(const InputNodes: TAuraNodeResultList): TAuraNodeResult;
|
|
TVerticalAlignment = (vaCenter, vaTop);
|
|
|
|
private
|
|
const
|
|
// Pin Visuals
|
|
cPinSize = 10;
|
|
cDataPinColor = TAlphaColors.Dodgerblue;
|
|
cExecPinColor = TAlphaColors.Lightgreen;
|
|
|
|
// Node Layout
|
|
cHorizontalPadding = 25; // Padding inside the node title bar
|
|
cPinOffsetY = 18; // Vertical distance between pins
|
|
cVerticalPadding = 10; // General vertical padding inside container nodes
|
|
|
|
// Specific Node Heights
|
|
cDefaultNodeHeight = 25; // Default height for simple nodes like BinaryExpr
|
|
cIfNodeHeight = 40; // Taller height for If nodes with more pins
|
|
|
|
private
|
|
FWorkspace: TAuraWorkspace;
|
|
FParentControl: TControl;
|
|
FCurrentPos: TPointF;
|
|
FSpacing: TPointF;
|
|
FLastResult: TAuraNodeResult;
|
|
FConnections: TList<TPinConnection>;
|
|
FCurrentExec: TList<TControl>;
|
|
FMode: TVisualizationMode;
|
|
|
|
procedure FinalizeNodeLayout(const Node: TAuraNode);
|
|
|
|
function VisitContainerNode(
|
|
const Title, Details: string;
|
|
const InPin, OutPin: String;
|
|
IsExec: Boolean;
|
|
HasResult: Boolean;
|
|
const ChildVisitorProc: TChildVisitorProc
|
|
): TAuraNode;
|
|
function VisitOperatorNode(
|
|
const InputExpressions: TArray<IExpressionNode>;
|
|
const CreateParentProc: TCreateParentNodeProc;
|
|
Alignment: TVerticalAlignment = vaCenter
|
|
): TAuraNode;
|
|
|
|
function BuildNodeControl(const Title, Details: string): TAuraNode;
|
|
function CreateNodeControl(const Title, Details: string): TAuraNode;
|
|
function CreatePin(
|
|
ParentNode: TAuraNode;
|
|
Shape: TPinShape;
|
|
Alignment: TPinAlignment;
|
|
Color: TAlphaColor;
|
|
const Tag: string
|
|
): TShape;
|
|
|
|
function CreateInput(ParentNode: TAuraNode; const Caption: string): TControl;
|
|
function CreateOutput(ParentNode: TAuraNode): TControl;
|
|
function CreateEntry(ParentNode: TAuraNode; connect: Boolean = true): TControl;
|
|
function CreateExit(ParentNode: TAuraNode; const Caption: string): TControl;
|
|
|
|
function TryGetDescr(const Node: IAstNode; out Text: String): Boolean;
|
|
|
|
public
|
|
constructor Create(
|
|
AWorkspace: TAuraWorkspace;
|
|
AParentControl: TControl;
|
|
const AStartPosition: TPointF;
|
|
AConnections: TList<TPinConnection>;
|
|
const ACurrentExec: TArray<TControl>;
|
|
AMode: TVisualizationMode
|
|
);
|
|
destructor Destroy; override;
|
|
|
|
// Public access to the collected connections
|
|
property Connections: TList<TPinConnection> read FConnections;
|
|
|
|
{ IAstVisitor }
|
|
function VisitConstant(const Node: IConstantNode): TAstValue;
|
|
function VisitIdentifier(const Node: IIdentifierNode): TAstValue;
|
|
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
|
|
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
|
|
function VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
|
|
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
|
|
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
|
|
function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
|
|
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
|
|
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
|
|
function VisitAssignment(const Node: IAssignmentNode): TAstValue;
|
|
function VisitIndexer(const Node: IIndexerNode): TAstValue;
|
|
function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue;
|
|
function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue;
|
|
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue;
|
|
function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.Math,
|
|
System.StrUtils,
|
|
Myc.Data.Scalar;
|
|
|
|
type
|
|
// This visitor converts an AST expression subtree into a single string.
|
|
TAstToTextVisitor = class(TInterfacedObject, IAstVisitor)
|
|
public
|
|
{ IAstVisitor }
|
|
function VisitConstant(const Node: IConstantNode): TAstValue;
|
|
function VisitIdentifier(const Node: IIdentifierNode): TAstValue;
|
|
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
|
|
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
|
|
function VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
|
|
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
|
|
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
|
|
function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
|
|
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
|
|
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
|
|
function VisitAssignment(const Node: IAssignmentNode): TAstValue;
|
|
function VisitIndexer(const Node: IIndexerNode): TAstValue;
|
|
function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue;
|
|
function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue;
|
|
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue;
|
|
function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue;
|
|
end;
|
|
|
|
{ TAstToTextVisitor }
|
|
|
|
function TAstToTextVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue;
|
|
var
|
|
seriesStr, valueStr, lookbackStr: string;
|
|
begin
|
|
seriesStr := Node.Series.Accept(Self).AsText;
|
|
valueStr := Node.Value.Accept(Self).AsText;
|
|
lookbackStr := '';
|
|
if Assigned(Node.Lookback) then
|
|
lookbackStr := ', ' + Node.Lookback.Accept(Self).AsText;
|
|
Result := TAstValue.FromText(Format('%s.add(%s%s)', [seriesStr, valueStr, lookbackStr]));
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue;
|
|
begin
|
|
Result := TAstValue.FromText(Node.Identifier.Name + ' := ' + Node.Value.Accept(Self).AsText);
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
|
|
begin
|
|
var leftStr := Node.Left.Accept(Self).AsText;
|
|
var rightStr := Node.Right.Accept(Self).AsText;
|
|
Result := TAstValue.FromText('(' + leftStr + ' ' + Node.Operator.ToString + ' ' + rightStr + ')');
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
|
|
begin
|
|
Result := TAstValue.FromText('{...}');
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitConstant(const Node: IConstantNode): TAstValue;
|
|
begin
|
|
Result := TAstValue.FromText(Node.Value.ToString);
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue;
|
|
begin
|
|
Result := TAstValue.FromText('new series(' + Node.Definition + ')');
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
|
|
var
|
|
i: Integer;
|
|
sb: TStringBuilder;
|
|
calleeStr: string;
|
|
begin
|
|
calleeStr := Node.Callee.Accept(Self).AsText;
|
|
|
|
sb := TStringBuilder.Create;
|
|
try
|
|
sb.Append(calleeStr);
|
|
sb.Append('(');
|
|
for i := 0 to Node.Arguments.Count - 1 do
|
|
begin
|
|
sb.Append(Node.Arguments[i].Accept(Self).AsText);
|
|
if i < Node.Arguments.Count - 1 then
|
|
sb.Append(', ');
|
|
end;
|
|
sb.Append(')');
|
|
Result := TAstValue.FromText(sb.ToString);
|
|
finally
|
|
sb.Free;
|
|
end;
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue;
|
|
begin
|
|
Result := TAstValue.FromText(Node.Name);
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
|
|
begin
|
|
Result := TAstValue.FromText(Node.Condition.Accept(Self).AsText);
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue;
|
|
var
|
|
baseStr, indexStr: string;
|
|
begin
|
|
baseStr := Node.Base.Accept(Self).AsText;
|
|
indexStr := Node.Index.Accept(Self).AsText;
|
|
Result := TAstValue.FromText(Format('%s[%s]', [baseStr, indexStr]));
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
|
|
var
|
|
i: Integer;
|
|
sb: TStringBuilder;
|
|
begin
|
|
sb := TStringBuilder.Create;
|
|
try
|
|
sb.Append(#$03BB + '(');
|
|
if Length(Node.Parameters) > 0 then
|
|
begin
|
|
for i := 0 to High(Node.Parameters) do
|
|
begin
|
|
// Correctly append the parameter name from the node.
|
|
sb.Append(Node.Parameters[i].Name);
|
|
if i < High(Node.Parameters) then
|
|
sb.Append(', ');
|
|
end;
|
|
end;
|
|
sb.Append(') => {...}');
|
|
Result := TAstValue.FromText(sb.ToString);
|
|
finally
|
|
sb.Free;
|
|
end;
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue;
|
|
var
|
|
baseStr: string;
|
|
begin
|
|
baseStr := Node.Base.Accept(Self).AsText;
|
|
Result := TAstValue.FromText(Format('%s.%s', [baseStr, Node.Member.Name]));
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue;
|
|
var
|
|
seriesStr: string;
|
|
begin
|
|
// Get the string representation of the series identifier by visiting the node.
|
|
seriesStr := Node.Series.Accept(Self).AsText;
|
|
Result := TAstValue.FromText(Format('length(%s)', [seriesStr]));
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
|
|
begin
|
|
var condStr := Node.Condition.Accept(Self).AsText;
|
|
var thenStr := Node.ThenBranch.Accept(Self).AsText;
|
|
var elseStr := Node.ElseBranch.Accept(Self).AsText;
|
|
Result := TAstValue.FromText(Format('(%s ? %s : %s)', [condStr, thenStr, elseStr]));
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
|
|
begin
|
|
Result := TAstValue.FromText(Node.Operator.ToString + ' ' + Node.Right.Accept(Self).AsText);
|
|
end;
|
|
|
|
function TAstToTextVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
|
|
var
|
|
initStr: string;
|
|
begin
|
|
if Assigned(Node.Initializer) then
|
|
initStr := ' := ' + Node.Initializer.Accept(Self).AsText
|
|
else
|
|
initStr := '';
|
|
Result := TAstValue.FromText('var ' + Node.Identifier.Name + initStr);
|
|
end;
|
|
|
|
{ TPinConnection }
|
|
|
|
constructor TPinConnection.Create(AOutputPin, AInputPin: TControl);
|
|
begin
|
|
OutputPin := AOutputPin;
|
|
InputPin := AInputPin;
|
|
end;
|
|
|
|
{ TAuraNode }
|
|
|
|
constructor TAuraNode.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FIsDragging := False;
|
|
|
|
// Default border settings
|
|
FBorderColor := TAlphaColors.Gray;
|
|
FBorderWidth := 1;
|
|
FBorderRadius := 9; // Sharp corners by default
|
|
|
|
// Default title settings
|
|
FTitle := 'Node';
|
|
FTitleFont := TFont.Create;
|
|
FTitleFont.Family := 'Segoe UI';
|
|
FTitleFont.Size := 11;
|
|
FTitleFont.Style := [TFontStyle.fsBold];
|
|
FTitleFont.OnChanged := TitleFontChanged;
|
|
FTitleFontColor := TAlphaColors.Black;
|
|
|
|
// Default state for frameless mode
|
|
FFrameless := False;
|
|
|
|
Width := 80;
|
|
Height := 45;
|
|
|
|
// The panel must be able to receive mouse events.
|
|
HitTest := True;
|
|
// Clip children to the panel's bounds.
|
|
ClipChildren := True;
|
|
end;
|
|
|
|
destructor TAuraNode.Destroy;
|
|
begin
|
|
FTitleFont.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TAuraNode.Paint;
|
|
var
|
|
rect, titleRect: TRectF;
|
|
effectiveBorderWidth: Single;
|
|
begin
|
|
inherited; // Allow styled painting to occur first (if any)
|
|
|
|
if FFrameless then
|
|
begin
|
|
Canvas.Fill.Kind := TBrushKind.Solid;
|
|
Canvas.Fill.Color := $20C0C0C0; // Transparent Silver
|
|
Canvas.Stroke.Kind := TBrushKind.None;
|
|
|
|
// In frameless mode, draw a transparent background and ignore the border.
|
|
Canvas.FillRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0);
|
|
effectiveBorderWidth := 0;
|
|
end
|
|
else
|
|
begin
|
|
// Custom painting for the border
|
|
if (FBorderWidth > 0) and (FBorderColor <> TAlphaColors.Null) then
|
|
begin
|
|
rect := TRectF.Create(0, 0, Width, Height);
|
|
// Inflate inwards so the border is fully visible within the control's bounds
|
|
rect.Inflate(-FBorderWidth / 2, -FBorderWidth / 2);
|
|
|
|
Canvas.Stroke.Kind := TBrushKind.Solid;
|
|
Canvas.Stroke.Color := FBorderColor;
|
|
Canvas.Stroke.Thickness := FBorderWidth;
|
|
|
|
if FFrameless then
|
|
begin
|
|
Canvas.Fill.Kind := TBrushKind.Solid;
|
|
Canvas.Fill.Color := $20C0C0C0; // Transparent Silver
|
|
Canvas.FillRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
|
|
end
|
|
else
|
|
Canvas.DrawRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
|
|
end;
|
|
effectiveBorderWidth := FBorderWidth;
|
|
end;
|
|
|
|
// Draw the title
|
|
if not FTitle.IsEmpty then
|
|
begin
|
|
// Define the rectangle for the title at the top of the control
|
|
titleRect := TRectF.Create(0, effectiveBorderWidth, Width, effectiveBorderWidth + FTitleFont.Size * 1.8);
|
|
|
|
Canvas.Font.Assign(FTitleFont);
|
|
Canvas.Fill.Color := FTitleFontColor;
|
|
|
|
// Draw the text centered horizontally and vertically within the title rectangle
|
|
Canvas.FillText(titleRect, FTitle, False, 1.0, [], TTextAlign.Center, TTextAlign.Center);
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.SetBorderColor(const Value: TAlphaColor);
|
|
begin
|
|
if FBorderColor <> Value then
|
|
begin
|
|
FBorderColor := Value;
|
|
Repaint;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.SetBorderRadius(const Value: Single);
|
|
begin
|
|
if FBorderRadius <> Value then
|
|
begin
|
|
FBorderRadius := Value;
|
|
Repaint;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.SetBorderWidth(const Value: Single);
|
|
begin
|
|
if FBorderWidth <> Value then
|
|
begin
|
|
FBorderWidth := Value;
|
|
Repaint;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.SetFrameless(const Value: Boolean);
|
|
begin
|
|
if FFrameless <> Value then
|
|
begin
|
|
FFrameless := Value;
|
|
Repaint;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.SetTitle(const Value: string);
|
|
begin
|
|
if FTitle <> Value then
|
|
begin
|
|
FTitle := Value;
|
|
Repaint;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.SetTitleFont(const Value: TFont);
|
|
begin
|
|
FTitleFont.Assign(Value);
|
|
end;
|
|
|
|
procedure TAuraNode.SetTitleFontColor(const Value: TAlphaColor);
|
|
begin
|
|
if FTitleFontColor <> Value then
|
|
begin
|
|
FTitleFontColor := Value;
|
|
Repaint;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.TitleFontChanged(Sender: TObject);
|
|
begin
|
|
Repaint;
|
|
end;
|
|
|
|
procedure TAuraNode.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
|
begin
|
|
inherited;
|
|
if Button = TMouseButton.mbLeft then
|
|
begin
|
|
if ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl then
|
|
begin
|
|
FIsDragging := True;
|
|
FDownPos := TPointF.Create(X, Y);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.MouseMove(Shift: TShiftState; X, Y: Single);
|
|
begin
|
|
inherited;
|
|
if FIsDragging then
|
|
begin
|
|
var deltaX := X - FDownPos.X;
|
|
var deltaY := Y - FDownPos.Y;
|
|
|
|
Position.X := Position.X + deltaX;
|
|
Position.Y := Position.Y + deltaY;
|
|
|
|
if ParentControl <> nil then
|
|
ParentControl.Repaint;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraNode.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
|
begin
|
|
inherited;
|
|
if (Button = TMouseButton.mbLeft) and (FIsDragging) then
|
|
begin
|
|
FIsDragging := False;
|
|
end;
|
|
end;
|
|
|
|
{ TAuraWorkspace }
|
|
|
|
procedure TAuraWorkspace.BuildTree(const Root: IAstNode; const Position: TPointF; AMode: TVisualizationMode);
|
|
begin
|
|
var connections := TList<TPinConnection>.Create;
|
|
try
|
|
Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections, nil, AMode));
|
|
FConnections := FConnections + connections.ToArray;
|
|
finally
|
|
connections.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TAuraWorkspace.DoDeleteChildren;
|
|
begin
|
|
FConnections := nil;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TAuraWorkspace.Paint;
|
|
var
|
|
connection: TPinConnection;
|
|
startPoint, endPoint, pinCenter: TPointF;
|
|
path: TPathData;
|
|
controlPoint1, controlPoint2: TPointF;
|
|
controlOffset: Single;
|
|
begin
|
|
inherited;
|
|
|
|
Canvas.Stroke.Kind := TBrushKind.Solid;
|
|
Canvas.Stroke.Thickness := 2;
|
|
|
|
// Go through all stored connections and draw them
|
|
for connection in FConnections do
|
|
begin
|
|
// Get the absolute coordinates of the pin centers
|
|
pinCenter := TPointF.Create(connection.OutputPin.Width / 2, connection.OutputPin.Height / 2);
|
|
var absoluteStart := connection.OutputPin.LocalToAbsolute(pinCenter);
|
|
|
|
pinCenter := TPointF.Create(connection.InputPin.Width / 2, connection.InputPin.Height / 2);
|
|
var absoluteEnd := connection.InputPin.LocalToAbsolute(pinCenter);
|
|
|
|
// Convert them to the local coordinates of the workspace
|
|
startPoint := AbsoluteToLocal(absoluteStart);
|
|
endPoint := AbsoluteToLocal(absoluteEnd);
|
|
|
|
var str := connection.InputPin.TagString;
|
|
if Pos('data', str) = 0 then
|
|
Canvas.Stroke.Color := TAstToAuraNodeVisitor.cExecPinColor
|
|
else
|
|
Canvas.Stroke.Color := TAstToAuraNodeVisitor.cDataPinColor;
|
|
|
|
// Draw a Bezier curve with horizontal tangents at start and end points.
|
|
path := TPathData.Create;
|
|
try
|
|
controlOffset := max(25, 0.5 * endPoint.Distance(startPoint));
|
|
|
|
controlPoint1 := TPointF.Create(startPoint.X + controlOffset, startPoint.Y);
|
|
controlPoint2 := TPointF.Create(endPoint.X - controlOffset, endPoint.Y);
|
|
|
|
path.MoveTo(startPoint);
|
|
path.CurveTo(controlPoint1, controlPoint2, endPoint);
|
|
|
|
Canvas.DrawPath(path, 1.0);
|
|
finally
|
|
path.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TAstToAuraNodeVisitor }
|
|
|
|
constructor TAstToAuraNodeVisitor.Create(
|
|
AWorkspace: TAuraWorkspace;
|
|
AParentControl: TControl;
|
|
const AStartPosition: TPointF;
|
|
AConnections: TList<TPinConnection>;
|
|
const ACurrentExec: TArray<TControl>;
|
|
AMode: TVisualizationMode
|
|
);
|
|
begin
|
|
inherited Create;
|
|
FWorkspace := AWorkspace;
|
|
FParentControl := AParentControl;
|
|
FCurrentPos := AStartPosition;
|
|
FSpacing := TPointF.Create(20, 10); // Horizontal indent, Vertical spacing
|
|
FConnections := AConnections;
|
|
FCurrentExec := TList<TControl>.Create(ACurrentExec);
|
|
FLastResult.LayoutNode := nil;
|
|
FLastResult.OutputPin := nil;
|
|
FMode := AMode;
|
|
end;
|
|
|
|
destructor TAstToAuraNodeVisitor.Destroy;
|
|
begin
|
|
FCurrentExec.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TAstToAuraNodeVisitor.FinalizeNodeLayout(const Node: TAuraNode);
|
|
var
|
|
control: TControl;
|
|
leftPins, rightPins: TList<TControl>;
|
|
numLeftPins, numRightPins, maxPins: Integer;
|
|
reqHeight: Single;
|
|
i: Integer;
|
|
totalHeight, startY: Single;
|
|
begin
|
|
leftPins := TList<TControl>.Create;
|
|
rightPins := TList<TControl>.Create;
|
|
try
|
|
// 1. Collect all pins and sort them by alignment
|
|
for control in Node.Controls do
|
|
begin
|
|
if (Pos('exec.', control.TagString) = 1) or (Pos('data.', control.TagString) = 1) then
|
|
begin
|
|
// Use the pin's X position to determine if it's on the left or right side.
|
|
if control.Position.X < (Node.Width / 2) then
|
|
leftPins.Add(control)
|
|
else
|
|
rightPins.Add(control);
|
|
end;
|
|
end;
|
|
|
|
numLeftPins := leftPins.Count;
|
|
numRightPins := rightPins.Count;
|
|
maxPins := Max(numLeftPins, numRightPins);
|
|
|
|
if maxPins = 0 then
|
|
exit;
|
|
|
|
// 2. Calculate and set new node height.
|
|
// It must accommodate the pins plus vertical padding.
|
|
reqHeight := cVerticalPadding + (maxPins - 1) * cPinOffsetY + cVerticalPadding;
|
|
Node.Height := Max(Node.Height, reqHeight);
|
|
Node.Height := Max(cDefaultNodeHeight, Node.Height); // Ensure minimum height
|
|
|
|
// 3. Position left pins. They are arranged centered on the node.
|
|
if numLeftPins > 0 then
|
|
begin
|
|
totalHeight := (numLeftPins - 1) * cPinOffsetY;
|
|
startY := (Node.Height / 2) - (totalHeight / 2.0);
|
|
for i := 0 to numLeftPins - 1 do
|
|
leftPins[i].Position.Y := startY + i * cPinOffsetY - (cPinSize / 2);
|
|
end;
|
|
|
|
// 4. Position right pins.
|
|
if numRightPins > 0 then
|
|
begin
|
|
totalHeight := (numRightPins - 1) * cPinOffsetY;
|
|
startY := (Node.Height / 2) - (totalHeight / 2.0);
|
|
for i := 0 to numRightPins - 1 do
|
|
rightPins[i].Position.Y := startY + i * cPinOffsetY - (cPinSize / 2);
|
|
end;
|
|
|
|
finally
|
|
leftPins.Free;
|
|
rightPins.Free;
|
|
end;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.BuildNodeControl(const Title, Details: string): TAuraNode;
|
|
var
|
|
fullTitle: string;
|
|
textWidth: Single;
|
|
newWidth: Single;
|
|
measureCanvas: TCanvas;
|
|
begin
|
|
Result := TAuraNode.Create(FParentControl);
|
|
Result.Parent := FParentControl;
|
|
|
|
// Build the full title string first
|
|
fullTitle := Title;
|
|
if not Details.IsEmpty then
|
|
fullTitle := fullTitle + ': ' + Details;
|
|
Result.Title := fullTitle;
|
|
|
|
// Adjust the width so that the text is completely visible.
|
|
measureCanvas := TCanvasManager.MeasureCanvas;
|
|
if Assigned(measureCanvas) then
|
|
begin
|
|
measureCanvas.Font.Assign(Result.TitleFont);
|
|
textWidth := measureCanvas.TextWidth(Result.Title);
|
|
newWidth := textWidth + (2 * cHorizontalPadding);
|
|
// Ensure the new width is not smaller than the default width of the node.
|
|
Result.Width := Max(Result.Width, newWidth);
|
|
end;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.CreateInput(ParentNode: TAuraNode; const Caption: string): TControl;
|
|
begin
|
|
var cap := Caption;
|
|
if cap <> '' then
|
|
cap := '.' + cap;
|
|
Result := CreatePin(ParentNode, psCircle, paLeft, cDataPinColor, 'data.in' + cap);
|
|
Result.Hint := Caption;
|
|
Result.ShowHint := Caption <> '';
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.CreateNodeControl(const Title, Details: string): TAuraNode;
|
|
begin
|
|
Result := BuildNodeControl(Title, Details);
|
|
Result.Position.Point := FCurrentPos;
|
|
|
|
// Advance vertical position for the next node
|
|
FCurrentPos.Y := FCurrentPos.Y + Result.Height + FSpacing.Y;
|
|
|
|
FLastResult.LayoutNode := Result;
|
|
FLastResult.OutputPin := nil; // Default: a new node has no specific data output pin
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.CreateOutput(ParentNode: TAuraNode): TControl;
|
|
begin
|
|
Result := CreatePin(ParentNode, psCircle, paRight, cDataPinColor, 'data.out');
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.CreateEntry(ParentNode: TAuraNode; connect: Boolean = true): TControl;
|
|
begin
|
|
Result := CreatePin(ParentNode, psTriangle, paLeft, cExecPinColor, 'exec.in');
|
|
if connect then
|
|
begin
|
|
// Connect all open exits to this new entry
|
|
for var curr in FCurrentExec do
|
|
FConnections.Add(TPinConnection.Create(curr, Result));
|
|
FCurrentExec.Clear;
|
|
end;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.CreateExit(ParentNode: TAuraNode; const Caption: string): TControl;
|
|
begin
|
|
var cap := Caption;
|
|
if cap <> '' then
|
|
cap := '.' + cap;
|
|
Result := CreatePin(ParentNode, psTriangle, paRight, cExecPinColor, 'exec.out' + cap);
|
|
|
|
FCurrentExec.Add(Result);
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.CreatePin(
|
|
ParentNode: TAuraNode;
|
|
Shape: TPinShape;
|
|
Alignment: TPinAlignment;
|
|
Color: TAlphaColor;
|
|
const Tag: string
|
|
): TShape;
|
|
var
|
|
posX, posY: Single;
|
|
begin
|
|
// Vertical position is now set later by FinalizeNodeLayout.
|
|
// Set a temporary Y position.
|
|
posY := 0;
|
|
|
|
// Calculate horizontal position
|
|
if Alignment = paLeft then
|
|
posX := 0
|
|
else // paRight
|
|
posX := ParentNode.Width - cPinSize;
|
|
|
|
// Create the shape
|
|
case Shape of
|
|
psCircle:
|
|
begin
|
|
var circle := TEllipse.Create(ParentNode);
|
|
circle.Fill.Color := Color;
|
|
circle.Stroke.Kind := TBrushKind.None;
|
|
Result := circle;
|
|
end;
|
|
psTriangle:
|
|
begin
|
|
var triangle := TPath.Create(ParentNode);
|
|
triangle.Data.Data := Format('M 0 0 L %d %d L 0 %d Z', [cPinSize, cPinSize div 2, cPinSize]);
|
|
triangle.Fill.Color := Color;
|
|
triangle.Stroke.Kind := TBrushKind.None;
|
|
Result := triangle;
|
|
end;
|
|
else
|
|
Result := nil;
|
|
exit;
|
|
end;
|
|
|
|
// Common properties
|
|
Result.Parent := ParentNode;
|
|
Result.SetBounds(posX, posY, cPinSize, cPinSize);
|
|
Result.HitTest := true;
|
|
|
|
// Use TagString to identify the control as a pin and describe its function.
|
|
Result.TagString := Tag;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.TryGetDescr(const Node: IAstNode; out Text: String): Boolean;
|
|
begin
|
|
if FMode <> vmControlFlow then
|
|
exit(false);
|
|
|
|
var textVisitor: IAstVisitor := TAstToTextVisitor.Create;
|
|
Text := Node.Accept(textVisitor).AsText;
|
|
Result := Pos('{', Text) = 0;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue;
|
|
var
|
|
details: string;
|
|
inputs: TArray<IExpressionNode>;
|
|
begin
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
|
|
begin
|
|
var addNode := CreateNodeControl('Add to Series', details);
|
|
CreateEntry(addNode);
|
|
CreateExit(addNode, '');
|
|
FinalizeNodeLayout(addNode);
|
|
end
|
|
else
|
|
begin
|
|
if Assigned(Node.Lookback) then
|
|
inputs := [Node.Series, Node.Value, Node.Lookback]
|
|
else
|
|
inputs := [Node.Series, Node.Value];
|
|
|
|
VisitOperatorNode(
|
|
inputs,
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
addNode: TAuraNode;
|
|
seriesPin, valuePin, lookbackPin: TControl;
|
|
begin
|
|
addNode := BuildNodeControl('Add to Series', '');
|
|
CreateEntry(addNode);
|
|
|
|
seriesPin := CreateInput(addNode, 'Series');
|
|
valuePin := CreateInput(addNode, 'Value');
|
|
|
|
if Assigned(InputResults[0].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, seriesPin));
|
|
if Assigned(InputResults[1].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[1].OutputPin, valuePin));
|
|
|
|
if InputResults.Count > 2 then
|
|
begin
|
|
lookbackPin := CreateInput(addNode, 'Lookback');
|
|
if Assigned(InputResults[2].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[2].OutputPin, lookbackPin));
|
|
end;
|
|
|
|
CreateExit(addNode, '');
|
|
|
|
FinalizeNodeLayout(addNode);
|
|
Result.LayoutNode := addNode;
|
|
Result.OutputPin := nil; // No data output
|
|
end,
|
|
vaTop
|
|
);
|
|
end;
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue;
|
|
begin
|
|
var details: String;
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
|
|
begin
|
|
var assignmentNode := CreateNodeControl('Assignment', details);
|
|
CreateEntry(assignmentNode);
|
|
CreateExit(assignmentNode, '');
|
|
FinalizeNodeLayout(assignmentNode);
|
|
end
|
|
else
|
|
begin
|
|
VisitOperatorNode(
|
|
[Node.Value],
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
assignmentNode: TAuraNode;
|
|
inputPin: TControl;
|
|
valueResult: TAuraNodeResult;
|
|
outPin: TControl; // variable for the output pin
|
|
begin
|
|
valueResult := InputResults[0];
|
|
assignmentNode := BuildNodeControl('Assignment', Node.Identifier.Name);
|
|
|
|
CreateEntry(assignmentNode);
|
|
inputPin := CreateInput(assignmentNode, Node.Identifier.Name);
|
|
CreateExit(assignmentNode, '');
|
|
|
|
// Create the output pin before finalizing the layout.
|
|
outPin := CreateOutput(assignmentNode);
|
|
|
|
if Assigned(valueResult.OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(valueResult.OutputPin, inputPin));
|
|
|
|
// Now finalize, all pins are present.
|
|
FinalizeNodeLayout(assignmentNode);
|
|
|
|
Result.LayoutNode := assignmentNode;
|
|
Result.OutputPin := outPin;
|
|
end,
|
|
vaTop
|
|
);
|
|
end;
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
|
|
begin
|
|
var exprStr: String;
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, exprStr) then
|
|
begin
|
|
var exprNode := CreateNodeControl('Expression', exprStr);
|
|
FLastResult.OutputPin := CreateOutput(exprNode); // Provide an output for potential connection
|
|
FinalizeNodeLayout(exprNode);
|
|
end
|
|
else
|
|
begin
|
|
VisitOperatorNode(
|
|
[Node.Left, Node.Right],
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
binaryExprNode: TAuraNode;
|
|
inputPin1, inputPin2: TControl;
|
|
leftResult, rightResult: TAuraNodeResult;
|
|
outPin: TControl;
|
|
begin
|
|
leftResult := InputResults[0];
|
|
rightResult := InputResults[1];
|
|
|
|
binaryExprNode := BuildNodeControl(Node.Operator.ToString, '');
|
|
binaryExprNode.TitleFont.Size := 20;
|
|
|
|
inputPin1 := CreateInput(binaryExprNode, ''); // Caption is in Hint
|
|
inputPin2 := CreateInput(binaryExprNode, '');
|
|
outPin := CreateOutput(binaryExprNode);
|
|
|
|
if Assigned(leftResult.OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(leftResult.OutputPin, inputPin1));
|
|
if Assigned(rightResult.OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(rightResult.OutputPin, inputPin2));
|
|
|
|
FinalizeNodeLayout(binaryExprNode);
|
|
Result.LayoutNode := binaryExprNode;
|
|
Result.OutputPin := outPin;
|
|
end
|
|
);
|
|
end;
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
|
|
var
|
|
blockNode: TAuraNode;
|
|
childLastResult: TAuraNodeResult;
|
|
begin
|
|
childLastResult.LayoutNode := nil;
|
|
childLastResult.OutputPin := nil;
|
|
|
|
// Use the helper to create and populate the container node.
|
|
blockNode :=
|
|
VisitContainerNode(
|
|
'', // No title for blocks
|
|
'',
|
|
'',
|
|
'',
|
|
true,
|
|
false,
|
|
procedure(const Visitor: IAstVisitor)
|
|
var
|
|
expression: IExpressionNode;
|
|
begin
|
|
for expression in Node.Expressions do
|
|
expression.Accept(Visitor);
|
|
|
|
// Capture the full result of the last expression inside the block.
|
|
childLastResult := (Visitor as TAstToAuraNodeVisitor).FLastResult;
|
|
end
|
|
);
|
|
|
|
// Blocks are drawn without title and border, with a transparent fill.
|
|
blockNode.Frameless := true;
|
|
|
|
// Update the state of the main visitor
|
|
FCurrentPos.Y := blockNode.Position.Y + blockNode.Height + FSpacing.Y;
|
|
FLastResult.LayoutNode := blockNode; // The layout node is the container itself.
|
|
FLastResult.OutputPin := childLastResult.OutputPin; // The connection pin is from the internal node.
|
|
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitConstant(const Node: IConstantNode): TAstValue;
|
|
var
|
|
constantNode: TAuraNode;
|
|
begin
|
|
// In both modes, a constant is a simple node with an output.
|
|
constantNode := CreateNodeControl('Constant', Node.Value.ToString);
|
|
FLastResult.OutputPin := CreateOutput(constantNode);
|
|
FinalizeNodeLayout(constantNode);
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitContainerNode(
|
|
const Title, Details: string;
|
|
const InPin, OutPin: String;
|
|
IsExec: Boolean;
|
|
HasResult: Boolean;
|
|
const ChildVisitorProc: TChildVisitorProc
|
|
): TAuraNode;
|
|
var
|
|
containerNode: TAuraNode;
|
|
childVisitor: IAstVisitor;
|
|
childStartPos: TPointF;
|
|
maxRight: Single;
|
|
maxBottom: Single;
|
|
control: TControl;
|
|
childLastResult: TAuraNodeResult;
|
|
begin
|
|
const pinNodeHeight = cPinSize + cVerticalPadding;
|
|
|
|
// Step 1: Create container node
|
|
containerNode := BuildNodeControl(Title, Details);
|
|
containerNode.Position.Point := FCurrentPos;
|
|
Result := containerNode; // Set result early
|
|
|
|
childStartPos :=
|
|
TPointF.Create(FSpacing.X, FSpacing.Y + max(IfThen(InPin <> '', pinNodeHeight, 0), IfThen(Title <> '', cVerticalPadding, 0)));
|
|
|
|
var currExec := FCurrentExec.ToArray;
|
|
if InPin <> '' then
|
|
begin
|
|
// Create a small entry node to define the start of the execution flow inside the container.
|
|
var entryNode := BuildNodeControl(InPin, '');
|
|
entryNode.Parent := containerNode;
|
|
entryNode.Position.Point := TPointF.Create(0, 0);
|
|
entryNode.Height := pinNodeHeight;
|
|
|
|
if IsExec then
|
|
begin
|
|
var entryPin := CreateEntry(containerNode);
|
|
var entryExitPin := CreateExit(entryNode, '');
|
|
FinalizeNodeLayout(entryNode);
|
|
entryPin.Position.Y := containerNode.AbsoluteToLocal(entryNode.LocalToAbsolute(entryExitPin.Position.Point)).Y;
|
|
end
|
|
else
|
|
begin
|
|
FCurrentExec.Clear;
|
|
CreateExit(entryNode, '');
|
|
FinalizeNodeLayout(entryNode);
|
|
end;
|
|
end;
|
|
|
|
// Step 2: Create child visitor and run the closure.
|
|
// The child visitor starts its execution flow from the entry node's exit pin.
|
|
childVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray, FMode);
|
|
ChildVisitorProc(childVisitor);
|
|
|
|
// Capture the result from the child visitor.
|
|
childLastResult := (childVisitor as TAstToAuraNodeVisitor).FLastResult;
|
|
|
|
FCurrentExec.Clear;
|
|
FCurrentExec.AddRange((childVisitor as TAstToAuraNodeVisitor).FCurrentExec);
|
|
|
|
// Step 3: Adjust the size of the container node to fit children.
|
|
maxRight := 0;
|
|
maxBottom := 0;
|
|
for control in containerNode.Controls do
|
|
begin
|
|
if control is TAuraNode then
|
|
begin
|
|
maxRight := Max(maxRight, control.Position.X + control.Width);
|
|
maxBottom := Max(maxBottom, control.Position.Y + control.Height);
|
|
end;
|
|
end;
|
|
|
|
// The width must be at least the title width, and large enough for children.
|
|
containerNode.Width := Max(containerNode.Width, maxRight + FSpacing.X);
|
|
containerNode.Height := Max(containerNode.Height, maxBottom + FSpacing.Y);
|
|
|
|
if OutPin <> '' then
|
|
begin
|
|
var exitNode := BuildNodeControl(OutPin, '');
|
|
exitNode.Parent := containerNode;
|
|
exitNode.Height := pinNodeHeight;
|
|
var exitPin := CreateEntry(exitNode);
|
|
|
|
// Create and connect a data pin if the container returns a value.
|
|
if HasResult and Assigned(childLastResult.OutputPin) then
|
|
begin
|
|
var dataResultPin := CreateInput(exitNode, 'Result');
|
|
FConnections.Add(TPinConnection.Create(childLastResult.OutputPin, dataResultPin));
|
|
end;
|
|
|
|
FinalizeNodeLayout(exitNode);
|
|
|
|
// Enlarge for the exit node
|
|
containerNode.Height := Max(containerNode.Height, maxBottom + FSpacing.Y + exitNode.Height);
|
|
|
|
exitNode.Position.Point := TPointF.Create(containerNode.Width - exitNode.Width, containerNode.Height - exitNode.Height);
|
|
|
|
if not IsExec then
|
|
begin
|
|
FCurrentExec.Clear;
|
|
FCurrentExec.AddRange(currExec);
|
|
end
|
|
else
|
|
CreateExit(containerNode, '').Position.Y := containerNode.AbsoluteToLocal(exitNode.LocalToAbsolute(exitPin.Position.Point)).Y;
|
|
end;
|
|
|
|
FinalizeNodeLayout(containerNode);
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue;
|
|
var
|
|
seriesNode: TAuraNode;
|
|
begin
|
|
// This node creates a new series. It has no inputs, only a data output.
|
|
// The definition is a string literal, so it's part of the node's title.
|
|
seriesNode := CreateNodeControl('Create Series', Node.Definition);
|
|
FLastResult.OutputPin := CreateOutput(seriesNode);
|
|
FinalizeNodeLayout(seriesNode);
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
|
|
var
|
|
inputExpressions: TArray<IExpressionNode>;
|
|
begin
|
|
var details: String;
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
|
|
begin
|
|
var funcCallNode := CreateNodeControl('FunctionCall', details);
|
|
CreateEntry(funcCallNode);
|
|
FLastResult.OutputPin := CreateOutput(funcCallNode); // Function calls can return values
|
|
CreateExit(funcCallNode, '');
|
|
FinalizeNodeLayout(funcCallNode);
|
|
end
|
|
else
|
|
begin
|
|
SetLength(inputExpressions, 1 + Node.Arguments.Count);
|
|
inputExpressions[0] := Node.Callee;
|
|
for var i := 0 to Node.Arguments.Count - 1 do
|
|
inputExpressions[i + 1] := Node.Arguments[i];
|
|
|
|
VisitOperatorNode(
|
|
inputExpressions,
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
funcCallNode: TAuraNode;
|
|
calleePin: TControl;
|
|
argPin: array of TControl;
|
|
i: Integer;
|
|
outPin: TControl;
|
|
begin
|
|
funcCallNode := BuildNodeControl('FunctionCall', '');
|
|
|
|
CreateEntry(funcCallNode);
|
|
calleePin := CreateInput(funcCallNode, 'Callee');
|
|
|
|
SetLength(argPin, Node.Arguments.Count);
|
|
for i := 0 to Node.Arguments.Count - 1 do
|
|
argPin[i] := CreateInput(funcCallNode, 'Arg' + i.ToString);
|
|
|
|
CreateExit(funcCallNode, '');
|
|
outPin := CreateOutput(funcCallNode);
|
|
|
|
if Assigned(InputResults[0].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, calleePin));
|
|
for i := 0 to Node.Arguments.Count - 1 do
|
|
if Assigned(InputResults[i + 1].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[i + 1].OutputPin, argPin[i]));
|
|
|
|
FinalizeNodeLayout(funcCallNode);
|
|
Result.LayoutNode := funcCallNode;
|
|
Result.OutputPin := outPin;
|
|
end
|
|
);
|
|
end;
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue;
|
|
begin
|
|
var identifierNode := CreateNodeControl('Identifier', Node.Name);
|
|
FLastResult.OutputPin := CreateOutput(identifierNode);
|
|
FinalizeNodeLayout(identifierNode);
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
|
|
var
|
|
startPosition: TPointF;
|
|
ifNode: TAuraNode;
|
|
conditionEndY: Single;
|
|
branchStartX: Single;
|
|
execPathes: TList<TControl>;
|
|
thenEndY, elseEndY: Single;
|
|
begin
|
|
startPosition := FCurrentPos;
|
|
|
|
var condStr: String;
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, condStr) then
|
|
begin
|
|
ifNode := CreateNodeControl('If', condStr);
|
|
ifNode.Height := cIfNodeHeight;
|
|
end
|
|
else
|
|
begin
|
|
// Use helper for the condition part
|
|
ifNode :=
|
|
VisitOperatorNode(
|
|
[Node.Condition],
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
conditionInputPin: TControl;
|
|
condResult: TAuraNodeResult;
|
|
ifNode: TAuraNode;
|
|
begin
|
|
condResult := InputResults[0];
|
|
ifNode := BuildNodeControl('If', '');
|
|
|
|
conditionInputPin := CreateInput(ifNode, 'Condition');
|
|
|
|
if Assigned(condResult.OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(condResult.OutputPin, conditionInputPin));
|
|
|
|
// Do not finalize here, more pins will be added.
|
|
Result.LayoutNode := ifNode;
|
|
Result.OutputPin := nil;
|
|
end
|
|
);
|
|
end;
|
|
CreateEntry(ifNode);
|
|
conditionEndY := FCurrentPos.Y;
|
|
|
|
// Visit the 'Then' and 'Else' execution branches, placing them to the right of the 'If' node.
|
|
branchStartX := ifNode.Position.X + ifNode.Width + FSpacing.X;
|
|
execPathes := TList<TControl>.Create;
|
|
try
|
|
// Then branch
|
|
FCurrentExec.Clear;
|
|
CreateExit(ifNode, 'Then');
|
|
FCurrentPos.X := branchStartX;
|
|
FCurrentPos.Y := startPosition.Y;
|
|
Node.ThenBranch.Accept(Self);
|
|
thenEndY := FCurrentPos.Y;
|
|
FLastResult.LayoutNode := ifNode;
|
|
execPathes.AddRange(FCurrentExec);
|
|
|
|
// Else branch (if it exists)
|
|
elseEndY := thenEndY;
|
|
FCurrentExec.Clear;
|
|
CreateExit(ifNode, 'Else');
|
|
if Assigned(Node.ElseBranch) then
|
|
begin
|
|
FCurrentPos.X := branchStartX;
|
|
FCurrentPos.Y := thenEndY;
|
|
Node.ElseBranch.Accept(Self);
|
|
elseEndY := FCurrentPos.Y;
|
|
end;
|
|
execPathes.AddRange(FCurrentExec);
|
|
|
|
// Finalize layout now that all pins are added.
|
|
FinalizeNodeLayout(ifNode);
|
|
|
|
// Finalize visitor state.
|
|
FCurrentPos.X := startPosition.X;
|
|
FCurrentPos.Y := Max(conditionEndY, elseEndY);
|
|
FLastResult.LayoutNode := ifNode;
|
|
// This node is for control-flow only and never produces a data output.
|
|
FLastResult.OutputPin := nil;
|
|
|
|
// After a branch, the execution flow has diverged.
|
|
FCurrentExec.Clear;
|
|
FCurrentExec.AddRange(execPathes);
|
|
finally
|
|
execPathes.Free;
|
|
end;
|
|
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue;
|
|
var
|
|
details: String;
|
|
begin
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
|
|
begin
|
|
var indexerNode := CreateNodeControl('Expression', details);
|
|
FLastResult.OutputPin := CreateOutput(indexerNode);
|
|
FinalizeNodeLayout(indexerNode);
|
|
end
|
|
else
|
|
begin
|
|
VisitOperatorNode(
|
|
[Node.Base, Node.Index],
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
indexerNode: TAuraNode;
|
|
basePin, indexPin, outPin: TControl;
|
|
begin
|
|
indexerNode := BuildNodeControl('[]', '');
|
|
indexerNode.TitleFont.Size := 20;
|
|
|
|
basePin := CreateInput(indexerNode, 'Base');
|
|
indexPin := CreateInput(indexerNode, 'Index');
|
|
outPin := CreateOutput(indexerNode);
|
|
|
|
if Assigned(InputResults[0].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, basePin));
|
|
if Assigned(InputResults[1].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[1].OutputPin, indexPin));
|
|
|
|
FinalizeNodeLayout(indexerNode);
|
|
Result.LayoutNode := indexerNode;
|
|
Result.OutputPin := outPin;
|
|
end
|
|
);
|
|
end;
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
|
|
var
|
|
paramStr: String;
|
|
lambdaNode: TAuraNode;
|
|
i: Integer;
|
|
begin
|
|
// Prepare the parameter string for the title
|
|
paramStr := '(';
|
|
if Length(Node.Parameters) > 0 then
|
|
begin
|
|
paramStr := paramStr + Node.Parameters[0].Name;
|
|
for i := 1 to High(Node.Parameters) do
|
|
paramStr := paramStr + ', ' + Node.Parameters[i].Name;
|
|
end;
|
|
paramStr := paramStr + ')';
|
|
|
|
// Use the helper to create and populate the container node.
|
|
lambdaNode :=
|
|
VisitContainerNode(
|
|
#$03BB,
|
|
paramStr,
|
|
'call',
|
|
'return',
|
|
false,
|
|
true,
|
|
procedure(const Visitor: IAstVisitor) begin Node.Body.Accept(Visitor); end
|
|
);
|
|
|
|
// Update the state of the main visitor
|
|
FCurrentPos.Y := lambdaNode.Position.Y + lambdaNode.Height + FSpacing.Y;
|
|
|
|
FLastResult.LayoutNode := lambdaNode;
|
|
FLastResult.OutputPin := CreateOutput(lambdaNode);
|
|
|
|
// Re-finalize layout after adding the data output pin.
|
|
FinalizeNodeLayout(lambdaNode);
|
|
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue;
|
|
var
|
|
details: String;
|
|
begin
|
|
// Implements the visualization for member access (e.g., series.field).
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
|
|
begin
|
|
var memberNode := CreateNodeControl('Expression', details);
|
|
FLastResult.OutputPin := CreateOutput(memberNode);
|
|
FinalizeNodeLayout(memberNode);
|
|
end
|
|
else
|
|
begin
|
|
VisitOperatorNode(
|
|
[Node.Base],
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
memberAccessNode: TAuraNode;
|
|
basePin, outPin: TControl;
|
|
begin
|
|
memberAccessNode := BuildNodeControl('Get ' + Node.Member.Name, '');
|
|
|
|
basePin := CreateInput(memberAccessNode, 'Base');
|
|
outPin := CreateOutput(memberAccessNode);
|
|
|
|
if Assigned(InputResults[0].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, basePin));
|
|
|
|
FinalizeNodeLayout(memberAccessNode);
|
|
Result.LayoutNode := memberAccessNode;
|
|
Result.OutputPin := outPin;
|
|
end
|
|
);
|
|
end;
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitOperatorNode(
|
|
const InputExpressions: TArray<IExpressionNode>;
|
|
const CreateParentProc: TCreateParentNodeProc;
|
|
Alignment: TVerticalAlignment
|
|
): TAuraNode;
|
|
var
|
|
inputResults: TAuraNodeResultList;
|
|
inputNode: IExpressionNode;
|
|
startPosition: TPointF;
|
|
endY, maxChildWidth: Single;
|
|
parentNode: TAuraNode;
|
|
createResult: TAuraNodeResult;
|
|
begin
|
|
startPosition := FCurrentPos;
|
|
inputResults := TAuraNodeResultList.Create;
|
|
try
|
|
// 1. Visit all input nodes and collect their visual representations.
|
|
for inputNode in InputExpressions do
|
|
begin
|
|
inputNode.Accept(Self);
|
|
inputResults.Add(FLastResult);
|
|
end;
|
|
endY := FCurrentPos.Y;
|
|
|
|
// 2. Determine the layout bounds of the input nodes.
|
|
maxChildWidth := 0;
|
|
for var res in inputResults do
|
|
if Assigned(res.LayoutNode) then
|
|
maxChildWidth := Max(maxChildWidth, res.LayoutNode.Position.X + res.LayoutNode.Width);
|
|
|
|
// 3. Let the closure create the parent node (which also finalizes its layout).
|
|
createResult := CreateParentProc(inputResults);
|
|
parentNode := createResult.LayoutNode;
|
|
Result := parentNode;
|
|
|
|
// 4. Now position the parent node to the right of the inputs.
|
|
var parentY: Single;
|
|
if Alignment = vaCenter then
|
|
begin
|
|
var totalInputHeight := endY - startPosition.Y;
|
|
var nodeCenterY := startPosition.Y + (totalInputHeight / 2);
|
|
parentY := nodeCenterY - (parentNode.Height / 2);
|
|
end
|
|
else // vaTop
|
|
begin
|
|
parentY := startPosition.Y;
|
|
end;
|
|
parentNode.Position.Point := TPointF.Create(maxChildWidth + FSpacing.X, parentY);
|
|
|
|
// 5. Finalize visitor state for the next operation.
|
|
FCurrentPos.Y := Max(endY, parentNode.Position.Y + parentNode.Height + FSpacing.Y);
|
|
FLastResult.LayoutNode := parentNode;
|
|
FLastResult.OutputPin := createResult.OutputPin;
|
|
finally
|
|
inputResults.Free;
|
|
end;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue;
|
|
begin
|
|
// Use the standard helper for operator-style nodes.
|
|
VisitOperatorNode(
|
|
[Node.Series], // The single input for this operator is the series identifier.
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
lengthNode: TAuraNode;
|
|
seriesPin, outPin: TControl;
|
|
begin
|
|
// Create the visual node for the 'length' operation.
|
|
lengthNode := BuildNodeControl('Series Length', '');
|
|
|
|
// Create an input pin for the series and an output pin for the result.
|
|
seriesPin := CreateInput(lengthNode, 'Series');
|
|
outPin := CreateOutput(lengthNode);
|
|
|
|
// Connect the output of the visited series identifier to our input pin.
|
|
if Assigned(InputResults[0].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, seriesPin));
|
|
|
|
// Finalize the node's layout to correctly position the pins.
|
|
FinalizeNodeLayout(lengthNode);
|
|
|
|
// The result of this visitation is the new node and its output pin.
|
|
Result.LayoutNode := lengthNode;
|
|
Result.OutputPin := outPin;
|
|
end
|
|
);
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
|
|
begin
|
|
var exprStr: String;
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, exprStr) then
|
|
begin
|
|
var ternaryNode := CreateNodeControl('Expression', exprStr);
|
|
FLastResult.OutputPin := CreateOutput(ternaryNode);
|
|
FinalizeNodeLayout(ternaryNode);
|
|
end
|
|
else
|
|
begin
|
|
VisitOperatorNode(
|
|
[Node.Condition, Node.ThenBranch, Node.ElseBranch],
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
ternaryNode: TAuraNode;
|
|
condPin, thenPin, elsePin, outPin: TControl;
|
|
begin
|
|
ternaryNode := BuildNodeControl('?', '');
|
|
ternaryNode.TitleFont.Size := 20;
|
|
|
|
condPin := CreateInput(ternaryNode, 'Condition');
|
|
thenPin := CreateInput(ternaryNode, 'Then');
|
|
elsePin := CreateInput(ternaryNode, 'Else');
|
|
outPin := CreateOutput(ternaryNode);
|
|
|
|
if Assigned(InputResults[0].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, condPin));
|
|
if Assigned(InputResults[1].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[1].OutputPin, thenPin));
|
|
if Assigned(InputResults[2].OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(InputResults[2].OutputPin, elsePin));
|
|
|
|
FinalizeNodeLayout(ternaryNode);
|
|
Result.LayoutNode := ternaryNode;
|
|
Result.OutputPin := outPin;
|
|
end
|
|
);
|
|
end;
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
|
|
begin
|
|
var exprStr: String;
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, exprStr) then
|
|
begin
|
|
var exprNode := CreateNodeControl('Expression', exprStr);
|
|
FLastResult.OutputPin := CreateOutput(exprNode);
|
|
FinalizeNodeLayout(exprNode);
|
|
end
|
|
else
|
|
begin
|
|
VisitOperatorNode(
|
|
[Node.Right],
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
unaryExprNode: TAuraNode;
|
|
inputPin: TControl;
|
|
rightResult: TAuraNodeResult;
|
|
outPin: TControl;
|
|
begin
|
|
rightResult := InputResults[0];
|
|
unaryExprNode := BuildNodeControl(Node.Operator.ToString, '');
|
|
unaryExprNode.TitleFont.Size := 20;
|
|
|
|
inputPin := CreateInput(unaryExprNode, '');
|
|
outPin := CreateOutput(unaryExprNode);
|
|
|
|
if Assigned(rightResult.OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(rightResult.OutputPin, inputPin));
|
|
|
|
FinalizeNodeLayout(unaryExprNode);
|
|
Result.LayoutNode := unaryExprNode;
|
|
Result.OutputPin := outPin;
|
|
end
|
|
);
|
|
end;
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
|
|
var
|
|
outPin: TControl;
|
|
begin
|
|
var details: String;
|
|
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
|
|
begin
|
|
var varDeclNode := CreateNodeControl('VarDecl', details);
|
|
CreateEntry(varDeclNode);
|
|
CreateExit(varDeclNode, '');
|
|
FinalizeNodeLayout(varDeclNode);
|
|
end
|
|
else
|
|
begin
|
|
if Assigned(Node.Initializer) then
|
|
begin
|
|
VisitOperatorNode(
|
|
[Node.Initializer],
|
|
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
|
|
var
|
|
varDeclNode: TAuraNode;
|
|
inputPin: TControl;
|
|
initializerResult: TAuraNodeResult;
|
|
outPin: TControl; // variable for the output pin
|
|
begin
|
|
initializerResult := InputResults[0];
|
|
varDeclNode := BuildNodeControl('VarDecl', Node.Identifier.Name);
|
|
|
|
CreateEntry(varDeclNode);
|
|
inputPin := CreateInput(varDeclNode, 'Value');
|
|
CreateExit(varDeclNode, '');
|
|
|
|
// Create the output pin before finalizing the layout.
|
|
outPin := CreateOutput(varDeclNode);
|
|
|
|
if Assigned(initializerResult.OutputPin) then
|
|
FConnections.Add(TPinConnection.Create(initializerResult.OutputPin, inputPin));
|
|
|
|
// Now finalize, all pins are present.
|
|
FinalizeNodeLayout(varDeclNode);
|
|
Result.LayoutNode := varDeclNode;
|
|
Result.OutputPin := outPin;
|
|
end
|
|
);
|
|
end
|
|
else
|
|
begin
|
|
// Case without initializer is a simple sequential node.
|
|
var varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
|
|
CreateEntry(varDeclNode);
|
|
CreateExit(varDeclNode, '');
|
|
// Create the output pin before finalizing the layout.
|
|
outPin := CreateOutput(varDeclNode);
|
|
FLastResult.OutputPin := outPin;
|
|
// Now finalize, all pins are present.
|
|
FinalizeNodeLayout(varDeclNode);
|
|
end;
|
|
end;
|
|
|
|
Result := TAstValue.Void;
|
|
end;
|
|
|
|
end.
|