Ast Editor

This commit is contained in:
Michael Schimmel
2025-09-16 12:54:49 +02:00
parent 469f2dc1f2
commit 5796f88da4
4 changed files with 134 additions and 74 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ uses
Myc.Ast.Printer in '..\Src\AST\Myc.Ast.Printer.pas', Myc.Ast.Printer in '..\Src\AST\Myc.Ast.Printer.pas',
Myc.Ast.Nodes in '..\Src\AST\Myc.Ast.Nodes.pas', Myc.Ast.Nodes in '..\Src\AST\Myc.Ast.Nodes.pas',
Myc.Ast.Scope in '..\Src\AST\Myc.Ast.Scope.pas', Myc.Ast.Scope in '..\Src\AST\Myc.Ast.Scope.pas',
Myc.Ast.Visualizer in 'Myc.Ast.Visualizer.pas', Myc.Fmx.AstEditor in 'Myc.Fmx.AstEditor.pas',
Myc.Ast.ViewModel in '..\Src\AST\Myc.Ast.ViewModel.pas', Myc.Ast.ViewModel in '..\Src\AST\Myc.Ast.ViewModel.pas',
Myc.Data.Value in 'Myc.Data.Value.pas', Myc.Data.Value in 'Myc.Data.Value.pas',
Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas'; Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas';
+1 -1
View File
@@ -139,7 +139,7 @@
<DCCReference Include="..\Src\AST\Myc.Ast.Printer.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.Printer.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Nodes.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.Nodes.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Scope.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.Scope.pas"/>
<DCCReference Include="Myc.Ast.Visualizer.pas"/> <DCCReference Include="Myc.Fmx.AstEditor.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.ViewModel.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.ViewModel.pas"/>
<DCCReference Include="Myc.Data.Value.pas"/> <DCCReference Include="Myc.Data.Value.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
+1 -1
View File
@@ -21,7 +21,7 @@ uses
FMX.ScrollBox, FMX.ScrollBox,
FMX.Memo, FMX.Memo,
FMX.Controls.Presentation, FMX.Controls.Presentation,
Myc.Ast.Visualizer, Myc.Fmx.AstEditor,
Myc.Data.Scalar, Myc.Data.Scalar,
Myc.Data.Value, Myc.Data.Value,
Myc.Ast.Nodes, Myc.Ast.Nodes,
@@ -1,4 +1,4 @@
unit Myc.Ast.Visualizer; unit Myc.Fmx.AstEditor;
interface interface
@@ -215,6 +215,8 @@ type
FConnections: TList<TPinConnection>; FConnections: TList<TPinConnection>;
FCurrentExec: TList<TControl>; FCurrentExec: TList<TControl>;
FMode: TVisualizationMode; FMode: TVisualizationMode;
FResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult>;
FOwnsCaches: Boolean;
procedure FinalizeNodeLayout(const Node: TAuraNode); procedure FinalizeNodeLayout(const Node: TAuraNode);
@@ -255,7 +257,8 @@ type
const AStartPosition: TPointF; const AStartPosition: TPointF;
AConnections: TList<TPinConnection>; AConnections: TList<TPinConnection>;
const ACurrentExec: TArray<TControl>; const ACurrentExec: TArray<TControl>;
AMode: TVisualizationMode AMode: TVisualizationMode;
AResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult> = nil
); );
destructor Destroy; override; destructor Destroy; override;
@@ -499,7 +502,7 @@ begin
Height := 45; Height := 45;
// The panel must be able to receive mouse events. // The panel must be able to receive mouse events.
// HitTest := True; // HitTest := True;
// Clip children to the panel's bounds. // Clip children to the panel's bounds.
ClipChildren := True; ClipChildren := True;
end; end;
@@ -850,7 +853,8 @@ constructor TAstToAuraNodeVisitor.Create(
const AStartPosition: TPointF; const AStartPosition: TPointF;
AConnections: TList<TPinConnection>; AConnections: TList<TPinConnection>;
const ACurrentExec: TArray<TControl>; const ACurrentExec: TArray<TControl>;
AMode: TVisualizationMode AMode: TVisualizationMode;
AResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult>
); );
begin begin
inherited Create; inherited Create;
@@ -863,10 +867,25 @@ begin
FLastResult.LayoutNode := nil; FLastResult.LayoutNode := nil;
FLastResult.OutputPin := nil; FLastResult.OutputPin := nil;
FMode := AMode; FMode := AMode;
if Assigned(AResolvedIdentifierCache) then
begin
FResolvedIdentifierCache := AResolvedIdentifierCache;
FOwnsCaches := False;
end
else
begin
FResolvedIdentifierCache := TDictionary<TResolvedAddress, TAuraNodeResult>.Create;
FOwnsCaches := True;
end;
end; end;
destructor TAstToAuraNodeVisitor.Destroy; destructor TAstToAuraNodeVisitor.Destroy;
begin begin
if FOwnsCaches then
begin
FResolvedIdentifierCache.Free;
end;
FCurrentExec.Free; FCurrentExec.Free;
inherited; inherited;
end; end;
@@ -889,10 +908,16 @@ begin
if (Pos('exec.', control.TagString) = 1) or (Pos('data.', control.TagString) = 1) then if (Pos('exec.', control.TagString) = 1) or (Pos('data.', control.TagString) = 1) then
begin begin
// Use the pin's X position to determine if it's on the left or right side. // 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 if Pos('.in', control.TagString) > 0 then
leftPins.Add(control) begin
leftPins.Add(control);
control.Position.X := 0;
end
else else
begin
rightPins.Add(control); rightPins.Add(control);
control.Position.X := Node.Width - cPinSize;
end;
end; end;
end; end;
@@ -1128,47 +1153,54 @@ begin
end; end;
function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue; function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
var
details: string;
assignmentNode: TAuraNode;
control: TControl;
begin begin
var details: String;
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
begin begin
var assignmentNode := CreateNodeControl('Assignment', details); // Keep the simple view for control flow mode
assignmentNode := CreateNodeControl('Assignment', details);
CreateEntry(assignmentNode); CreateEntry(assignmentNode);
CreateExit(assignmentNode, ''); CreateExit(assignmentNode, '');
FinalizeNodeLayout(assignmentNode); FinalizeNodeLayout(assignmentNode);
end end
else else
begin begin
VisitOperatorNode( // Create the main container node for the assignment.
[Node.Value], assignmentNode := CreateNodeControl('Assignment', Node.Identifier.Name);
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
var // Create a child visitor to render the value expression inside the assignment node.
assignmentNode: TAuraNode; // We pass an empty array for ACurrentExec so the child's execution flow doesn't connect to anything externally.
inputPin: TControl; var childStartPos := TPointF.Create(FSpacing.X, assignmentNode.TitleFont.Size * 1.8 + FSpacing.Y);
valueResult: TAuraNodeResult; var childVisitor :=
outPin: TControl; // variable for the output pin TAstToAuraNodeVisitor.Create(FWorkspace, assignmentNode, childStartPos, FConnections, [], FMode, FResolvedIdentifierCache);
Node.Value.Accept(childVisitor);
// Resize the parent assignmentNode to fit the child node(s).
var maxRight: Single := 0;
var maxBottom: Single := 0;
for control in assignmentNode.Controls do
begin
if control is TAuraNode then
begin begin
valueResult := InputResults[0]; maxRight := Max(maxRight, control.Position.X + control.Width);
assignmentNode := BuildNodeControl('Assignment', Node.Identifier.Name); maxBottom := Max(maxBottom, control.Position.Y + control.Height);
end;
end;
assignmentNode.Width := Max(assignmentNode.Width, maxRight + FSpacing.X);
assignmentNode.Height := Max(assignmentNode.Height, maxBottom + FSpacing.Y);
CreateEntry(assignmentNode); CreateEntry(assignmentNode);
inputPin := CreateInput(assignmentNode, Node.Identifier.Name); CreateExit(assignmentNode, '');
CreateExit(assignmentNode, ''); // An assignment expression can be used in another expression, so it needs a data output.
FLastResult.OutputPin := CreateOutput(assignmentNode);
// Create the output pin before finalizing the layout. // Finalize the layout of the parent node to correctly position its own pins.
outPin := CreateOutput(assignmentNode); FinalizeNodeLayout(assignmentNode);
if Assigned(valueResult.OutputPin) then FCurrentPos.Y := assignmentNode.Position.Y + assignmentNode.Height + FSpacing.Y;
FConnections.Add(TPinConnection.Create(valueResult.OutputPin, inputPin));
// Now finalize, all pins are present.
FinalizeNodeLayout(assignmentNode);
Result.LayoutNode := assignmentNode;
Result.OutputPin := outPin;
end,
vaTop
);
end; end;
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
@@ -1320,7 +1352,9 @@ begin
// Step 2: Create child visitor and run the closure. // Step 2: Create child visitor and run the closure.
// The child visitor starts its execution flow from the entry node's exit pin. // The child visitor starts its execution flow from the entry node's exit pin.
childVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray, FMode); childVisitor :=
TAstToAuraNodeVisitor
.Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray, FMode, FResolvedIdentifierCache);
ChildVisitorProc(childVisitor); ChildVisitorProc(childVisitor);
// Capture the result from the child visitor. // Capture the result from the child visitor.
@@ -1448,10 +1482,34 @@ begin
end; end;
function TAstToAuraNodeVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue; function TAstToAuraNodeVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
var
existingResult: TAuraNodeResult;
identifierNode: TAuraNode;
begin begin
var identifierNode := CreateNodeControl('Identifier', Node.Name); if Node.IsResolved then
FLastResult.OutputPin := CreateOutput(identifierNode); begin
FinalizeNodeLayout(identifierNode); // Use the resolved address as the key.
if FResolvedIdentifierCache.TryGetValue(Node.Address, existingResult) then
begin
FLastResult := existingResult;
end
else
begin
identifierNode := CreateNodeControl('Identifier', Node.Name);
FLastResult.LayoutNode := identifierNode;
FLastResult.OutputPin := CreateOutput(identifierNode);
FinalizeNodeLayout(identifierNode);
FResolvedIdentifierCache.Add(Node.Address, FLastResult);
end;
end
else
begin
// Unresolved identifiers are not cached and drawn each time.
identifierNode := CreateNodeControl('Identifier', Node.Name);
FLastResult.LayoutNode := identifierNode;
FLastResult.OutputPin := CreateOutput(identifierNode);
FinalizeNodeLayout(identifierNode);
end;
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
@@ -1838,12 +1896,14 @@ end;
function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
var var
outPin: TControl; details: string;
varDeclNode: TAuraNode;
control: TControl;
begin begin
var details: String;
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
begin begin
var varDeclNode := CreateNodeControl('VarDecl', details); // Keep the simple view for control flow mode
varDeclNode := CreateNodeControl('VarDecl', details);
CreateEntry(varDeclNode); CreateEntry(varDeclNode);
CreateExit(varDeclNode, ''); CreateExit(varDeclNode, '');
FinalizeNodeLayout(varDeclNode); FinalizeNodeLayout(varDeclNode);
@@ -1852,49 +1912,49 @@ begin
begin begin
if Assigned(Node.Initializer) then if Assigned(Node.Initializer) then
begin begin
VisitOperatorNode( // Create the main container node for the declaration.
[Node.Initializer], varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
function(const InputResults: TAuraNodeResultList): TAuraNodeResult
var // Create a child visitor to render the initializer expression inside the node.
varDeclNode: TAuraNode; var childStartPos := TPointF.Create(FSpacing.X, varDeclNode.TitleFont.Size * 1.8 + FSpacing.Y);
inputPin: TControl; var childVisitor :=
initializerResult: TAuraNodeResult; TAstToAuraNodeVisitor.Create(FWorkspace, varDeclNode, childStartPos, FConnections, [], FMode, FResolvedIdentifierCache);
outPin: TControl; // variable for the output pin Node.Initializer.Accept(childVisitor);
// Resize the parent node to fit the child node(s).
var maxRight: Single := 0;
var maxBottom: Single := 0;
for control in varDeclNode.Controls do
begin
if control is TAuraNode then
begin begin
initializerResult := InputResults[0]; maxRight := Max(maxRight, control.Position.X + control.Width);
varDeclNode := BuildNodeControl('VarDecl', Node.Identifier.Name); maxBottom := Max(maxBottom, control.Position.Y + control.Height);
end;
end;
varDeclNode.Width := Max(varDeclNode.Width, maxRight + FSpacing.X);
varDeclNode.Height := Max(varDeclNode.Height, maxBottom + FSpacing.Y);
CreateEntry(varDeclNode); CreateEntry(varDeclNode);
inputPin := CreateInput(varDeclNode, 'Value'); CreateExit(varDeclNode, '');
CreateExit(varDeclNode, ''); // The new variable can be used in other expressions, so it needs a data output.
FLastResult.OutputPin := CreateOutput(varDeclNode);
// Create the output pin before finalizing the layout. // Finalize the layout of the parent node to correctly position its own pins.
outPin := CreateOutput(varDeclNode); FinalizeNodeLayout(varDeclNode);
if Assigned(initializerResult.OutputPin) then FCurrentPos.Y := varDeclNode.Position.Y + varDeclNode.Height + FSpacing.Y;
FConnections.Add(TPinConnection.Create(initializerResult.OutputPin, inputPin));
// Now finalize, all pins are present.
FinalizeNodeLayout(varDeclNode);
Result.LayoutNode := varDeclNode;
Result.OutputPin := outPin;
end
);
end end
else else
begin begin
// Case without initializer is a simple sequential node. // Case without initializer is a simple sequential node.
var varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
CreateEntry(varDeclNode); CreateEntry(varDeclNode);
CreateExit(varDeclNode, ''); CreateExit(varDeclNode, '');
// Create the output pin before finalizing the layout. FLastResult.OutputPin := CreateOutput(varDeclNode);
outPin := CreateOutput(varDeclNode);
FLastResult.OutputPin := outPin;
// Now finalize, all pins are present.
FinalizeNodeLayout(varDeclNode); FinalizeNodeLayout(varDeclNode);
end; end;
end; end;
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;