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.Nodes in '..\Src\AST\Myc.Ast.Nodes.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.Data.Value in 'Myc.Data.Value.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.Nodes.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="Myc.Data.Value.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
+1 -1
View File
@@ -21,7 +21,7 @@ uses
FMX.ScrollBox,
FMX.Memo,
FMX.Controls.Presentation,
Myc.Ast.Visualizer,
Myc.Fmx.AstEditor,
Myc.Data.Scalar,
Myc.Data.Value,
Myc.Ast.Nodes,
@@ -1,4 +1,4 @@
unit Myc.Ast.Visualizer;
unit Myc.Fmx.AstEditor;
interface
@@ -215,6 +215,8 @@ type
FConnections: TList<TPinConnection>;
FCurrentExec: TList<TControl>;
FMode: TVisualizationMode;
FResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult>;
FOwnsCaches: Boolean;
procedure FinalizeNodeLayout(const Node: TAuraNode);
@@ -255,7 +257,8 @@ type
const AStartPosition: TPointF;
AConnections: TList<TPinConnection>;
const ACurrentExec: TArray<TControl>;
AMode: TVisualizationMode
AMode: TVisualizationMode;
AResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult> = nil
);
destructor Destroy; override;
@@ -850,7 +853,8 @@ constructor TAstToAuraNodeVisitor.Create(
const AStartPosition: TPointF;
AConnections: TList<TPinConnection>;
const ACurrentExec: TArray<TControl>;
AMode: TVisualizationMode
AMode: TVisualizationMode;
AResolvedIdentifierCache: TDictionary<TResolvedAddress, TAuraNodeResult>
);
begin
inherited Create;
@@ -863,10 +867,25 @@ begin
FLastResult.LayoutNode := nil;
FLastResult.OutputPin := nil;
FMode := AMode;
if Assigned(AResolvedIdentifierCache) then
begin
FResolvedIdentifierCache := AResolvedIdentifierCache;
FOwnsCaches := False;
end
else
begin
FResolvedIdentifierCache := TDictionary<TResolvedAddress, TAuraNodeResult>.Create;
FOwnsCaches := True;
end;
end;
destructor TAstToAuraNodeVisitor.Destroy;
begin
if FOwnsCaches then
begin
FResolvedIdentifierCache.Free;
end;
FCurrentExec.Free;
inherited;
end;
@@ -889,10 +908,16 @@ 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)
if Pos('.in', control.TagString) > 0 then
begin
leftPins.Add(control);
control.Position.X := 0;
end
else
begin
rightPins.Add(control);
control.Position.X := Node.Width - cPinSize;
end;
end;
end;
@@ -1128,47 +1153,54 @@ begin
end;
function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
var
details: string;
assignmentNode: TAuraNode;
control: TControl;
begin
var details: String;
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
begin
var assignmentNode := CreateNodeControl('Assignment', details);
// Keep the simple view for control flow mode
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
// Create the main container node for the assignment.
assignmentNode := CreateNodeControl('Assignment', Node.Identifier.Name);
// Create a child visitor to render the value expression inside the assignment node.
// We pass an empty array for ACurrentExec so the child's execution flow doesn't connect to anything externally.
var childStartPos := TPointF.Create(FSpacing.X, assignmentNode.TitleFont.Size * 1.8 + FSpacing.Y);
var childVisitor :=
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
valueResult := InputResults[0];
assignmentNode := BuildNodeControl('Assignment', Node.Identifier.Name);
if control is TAuraNode then
begin
maxRight := Max(maxRight, control.Position.X + control.Width);
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);
inputPin := CreateInput(assignmentNode, Node.Identifier.Name);
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.
outPin := CreateOutput(assignmentNode);
if Assigned(valueResult.OutputPin) then
FConnections.Add(TPinConnection.Create(valueResult.OutputPin, inputPin));
// Now finalize, all pins are present.
// Finalize the layout of the parent node to correctly position its own pins.
FinalizeNodeLayout(assignmentNode);
Result.LayoutNode := assignmentNode;
Result.OutputPin := outPin;
end,
vaTop
);
FCurrentPos.Y := assignmentNode.Position.Y + assignmentNode.Height + FSpacing.Y;
end;
Result := TDataValue.Void;
end;
@@ -1320,7 +1352,9 @@ begin
// 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);
childVisitor :=
TAstToAuraNodeVisitor
.Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray, FMode, FResolvedIdentifierCache);
ChildVisitorProc(childVisitor);
// Capture the result from the child visitor.
@@ -1448,10 +1482,34 @@ begin
end;
function TAstToAuraNodeVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
var
existingResult: TAuraNodeResult;
identifierNode: TAuraNode;
begin
var identifierNode := CreateNodeControl('Identifier', Node.Name);
if Node.IsResolved then
begin
// 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;
end;
@@ -1838,12 +1896,14 @@ end;
function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
var
outPin: TControl;
details: string;
varDeclNode: TAuraNode;
control: TControl;
begin
var details: String;
if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
begin
var varDeclNode := CreateNodeControl('VarDecl', details);
// Keep the simple view for control flow mode
varDeclNode := CreateNodeControl('VarDecl', details);
CreateEntry(varDeclNode);
CreateExit(varDeclNode, '');
FinalizeNodeLayout(varDeclNode);
@@ -1852,49 +1912,49 @@ begin
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
// Create the main container node for the declaration.
varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
// Create a child visitor to render the initializer expression inside the node.
var childStartPos := TPointF.Create(FSpacing.X, varDeclNode.TitleFont.Size * 1.8 + FSpacing.Y);
var childVisitor :=
TAstToAuraNodeVisitor.Create(FWorkspace, varDeclNode, childStartPos, FConnections, [], FMode, FResolvedIdentifierCache);
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
initializerResult := InputResults[0];
varDeclNode := BuildNodeControl('VarDecl', Node.Identifier.Name);
if control is TAuraNode then
begin
maxRight := Max(maxRight, control.Position.X + control.Width);
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);
inputPin := CreateInput(varDeclNode, 'Value');
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.
outPin := CreateOutput(varDeclNode);
if Assigned(initializerResult.OutputPin) then
FConnections.Add(TPinConnection.Create(initializerResult.OutputPin, inputPin));
// Now finalize, all pins are present.
// Finalize the layout of the parent node to correctly position its own pins.
FinalizeNodeLayout(varDeclNode);
Result.LayoutNode := varDeclNode;
Result.OutputPin := outPin;
end
);
FCurrentPos.Y := varDeclNode.Position.Y + varDeclNode.Height + FSpacing.Y;
end
else
begin
// Case without initializer is a simple sequential node.
var varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
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.
FLastResult.OutputPin := CreateOutput(varDeclNode);
FinalizeNodeLayout(varDeclNode);
end;
end;
Result := TDataValue.Void;
end;