Ast editor unit refactoring

This commit is contained in:
Michael Schimmel
2025-11-28 13:45:33 +01:00
parent 13b2ef3bf0
commit 833ce8aada
11 changed files with 2063 additions and 2643 deletions
File diff suppressed because one or more lines are too long
+5 -4
View File
@@ -12,9 +12,6 @@ uses
Myc.Ast.RTL in '..\Src\AST\Myc.Ast.RTL.pas',
Myc.Ast.Dumper in '..\Src\AST\Myc.Ast.Dumper.pas',
Myc.Ast.RTL.Core in '..\Src\AST\Myc.Ast.RTL.Core.pas',
Myc.Fmx.AstEditor.Node in 'Myc.Fmx.AstEditor.Node.pas',
Myc.Fmx.AstEditor.Workspace in 'Myc.Fmx.AstEditor.Workspace.pas',
Myc.Fmx.AstEditor.Text in 'Myc.Fmx.AstEditor.Text.pas',
Myc.Utils in '..\Src\Myc.Utils.pas',
Myc.Ast.Script in '..\Src\AST\Myc.Ast.Script.pas',
Myc.Ast.Types in '..\Src\AST\Myc.Ast.Types.pas',
@@ -28,7 +25,11 @@ uses
Myc.Ast.Environment in '..\Src\AST\Myc.Ast.Environment.pas',
Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas',
Myc.Ast.Analysis.Purity in '..\Src\AST\Myc.Ast.Analysis.Purity.pas',
Myc.Ast.Identities in '..\Src\AST\Myc.Ast.Identities.pas';
Myc.Ast.Identities in '..\Src\AST\Myc.Ast.Identities.pas',
Myc.Fmx.AstEditor.Core in '..\Src\AST\Myc.Fmx.AstEditor.Core.pas',
Myc.Fmx.AstEditor.Handlers in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.pas',
Myc.Fmx.AstEditor.Visualizer in '..\Src\AST\Myc.Fmx.AstEditor.Visualizer.pas',
Myc.Fmx.AstEditor.Workspace in '..\Src\AST\Myc.Fmx.AstEditor.Workspace.pas';
{$R *.res}
+4 -3
View File
@@ -142,9 +142,6 @@
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Dumper.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.Core.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Node.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Workspace.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Text.pas"/>
<DCCReference Include="..\Src\Myc.Utils.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Script.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Types.pas"/>
@@ -159,6 +156,10 @@
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Analysis.Purity.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Identities.pas"/>
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Core.pas"/>
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.pas"/>
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Visualizer.pas"/>
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Workspace.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
+1 -1
View File
@@ -32,7 +32,7 @@ uses
Myc.Ast.Script,
FMX.Layouts,
FMX.Objects,
Myc.Fmx.AstEditor.Node,
Myc.Fmx.AstEditor.Core,
Myc.Fmx.AstEditor.Workspace,
FMX.DialogService,
FMX.ListView.Types,
File diff suppressed because it is too large Load Diff
-265
View File
@@ -1,265 +0,0 @@
unit Myc.Fmx.AstEditor.Text;
interface
uses
System.SysUtils,
Myc.Data.Value,
Myc.Data.Scalar,
Myc.Ast.Visitor,
Myc.Ast.Nodes;
type
// This visitor converts an AST expression subtree into a single string.
// It inherits from the generic visitor, returning a 'string' for each node.
TAstToTextVisitor = class(TAstVisitor<string>)
protected
function VisitConstant(const Node: IConstantNode): string; override;
function VisitIdentifier(const Node: IIdentifierNode): string; override;
function VisitKeyword(const Node: IKeywordNode): string; override;
function VisitIfExpression(const Node: IIfExpressionNode): string; override;
function VisitTernaryExpression(const Node: ITernaryExpressionNode): string; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): string; override;
function VisitMacroDefinition(const Node: IMacroDefinitionNode): string; override;
function VisitFunctionCall(const Node: IFunctionCallNode): string; override;
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): string; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): string; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): string; override;
function VisitAssignment(const Node: IAssignmentNode): string; override;
function VisitIndexer(const Node: IIndexerNode): string; override;
function VisitMemberAccess(const Node: IMemberAccessNode): string; override;
function VisitQuasiquote(const Node: IQuasiquoteNode): string; override;
function VisitUnquote(const Node: IUnquoteNode): string; override;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): string; override;
function VisitCreateSeries(const Node: ICreateSeriesNode): string; override;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): string; override;
function VisitSeriesLength(const Node: ISeriesLengthNode): string; override;
function VisitRecurNode(const Node: IRecurNode): string; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): string; override;
end;
implementation
uses
System.Classes, // For TStringBuilder
Myc.Data.Keyword;
{ TAstToTextVisitor }
function TAstToTextVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): string;
var
seriesStr, valueStr, lookbackStr: string;
begin
seriesStr := Accept(Node.Series);
valueStr := Accept(Node.Value);
lookbackStr := '';
if Assigned(Node.Lookback) then
lookbackStr := ', ' + Accept(Node.Lookback);
Result := Format('%s.add(%s%s)', [seriesStr, valueStr, lookbackStr]);
end;
function TAstToTextVisitor.VisitAssignment(const Node: IAssignmentNode): string;
begin
Result := Accept(Node.Target) + ' := ' + Accept(Node.Value);
end;
function TAstToTextVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): string;
begin
Result := '{...}';
end;
function TAstToTextVisitor.VisitConstant(const Node: IConstantNode): string;
begin
Result := Node.Value.ToString;
end;
function TAstToTextVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): string;
begin
Result := 'new series(' + Node.Definition + ')';
end;
function TAstToTextVisitor.VisitFunctionCall(const Node: IFunctionCallNode): string;
var
i: Integer;
sb: TStringBuilder;
calleeStr: string;
begin
calleeStr := Accept(Node.Callee);
sb := TStringBuilder.Create;
try
sb.Append(calleeStr);
sb.Append('(');
for i := 0 to High(Node.Arguments) do
begin
sb.Append(Accept(Node.Arguments[i]));
if i < High(Node.Arguments) then
sb.Append(', ');
end;
sb.Append(')');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TAstToTextVisitor.VisitMacroExpansionNode(const Node: IMacroExpansionNode): string;
begin
// To represent a macro expansion as a single line of text, the most
// informative representation is the original call itself. We can simply
// delegate to the VisitFunctionCall implementation to format it.
Result := VisitFunctionCall(Node.CallNode);
end;
function TAstToTextVisitor.VisitRecurNode(const Node: IRecurNode): string;
var
i: Integer;
sb: TStringBuilder;
begin
sb := TStringBuilder.Create;
try
sb.Append('recur(');
for i := 0 to High(Node.Arguments) do
begin
sb.Append(Accept(Node.Arguments[i]));
if i < High(Node.Arguments) then
sb.Append(', ');
end;
sb.Append(')');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TAstToTextVisitor.VisitIdentifier(const Node: IIdentifierNode): string;
begin
Result := Node.Name;
end;
function TAstToTextVisitor.VisitIfExpression(const Node: IIfExpressionNode): string;
begin
Result := Accept(Node.Condition);
end;
function TAstToTextVisitor.VisitIndexer(const Node: IIndexerNode): string;
var
baseStr, indexStr: string;
begin
baseStr := Accept(Node.Base);
indexStr := Accept(Node.Index);
Result := Format('%s[%s]', [baseStr, indexStr]);
end;
function TAstToTextVisitor.VisitKeyword(const Node: IKeywordNode): string;
begin
Result := ':' + Node.Value.Name;
end;
function TAstToTextVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): string;
var
i: Integer;
sb: TStringBuilder;
begin
sb := TStringBuilder.Create;
try
sb.Append(WideChar($03BB) + '('); // Lambda char
if Length(Node.Parameters) > 0 then
begin
for i := 0 to High(Node.Parameters) do
begin
sb.Append(Node.Parameters[i].Name);
if i < High(Node.Parameters) then
sb.Append(', ');
end;
end;
sb.Append(') => {...}');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TAstToTextVisitor.VisitMacroDefinition(const Node: IMacroDefinitionNode): string;
var
i: Integer;
sb: TStringBuilder;
begin
// Added text representation for macro definitions.
sb := TStringBuilder.Create;
try
sb.Append('defmacro ');
sb.Append(Node.Name.Name);
sb.Append('(');
if Length(Node.Parameters) > 0 then
begin
for i := 0 to High(Node.Parameters) do
begin
sb.Append(Node.Parameters[i].Name);
if i < High(Node.Parameters) then
sb.Append(', ');
end;
end;
sb.Append(') => {...}');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TAstToTextVisitor.VisitMemberAccess(const Node: IMemberAccessNode): string;
var
baseStr: string;
begin
baseStr := Accept(Node.Base);
Result := Format('%s.%s', [baseStr, Node.Member.Value.Name]);
end;
function TAstToTextVisitor.VisitQuasiquote(const Node: IQuasiquoteNode): string;
begin
Result := '`' + Accept(Node.Expression);
end;
function TAstToTextVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): string;
begin
Result := '{...}';
end;
function TAstToTextVisitor.VisitUnquote(const Node: IUnquoteNode): string;
begin
Result := '~' + Accept(Node.Expression);
end;
function TAstToTextVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): string;
begin
Result := '~@' + Accept(Node.Expression);
end;
function TAstToTextVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): string;
var
seriesStr: string;
begin
seriesStr := Accept(Node.Series);
Result := Format('length(%s)', [seriesStr]);
end;
function TAstToTextVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): string;
begin
var condStr := Accept(Node.Condition);
var thenStr := Accept(Node.ThenBranch);
var elseStr := Accept(Node.ElseBranch);
Result := Format('(%s ? %s : %s)', [condStr, thenStr, elseStr]);
end;
function TAstToTextVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): string;
var
initStr: string;
begin
if Assigned(Node.Initializer) then
initStr := ' := ' + Accept(Node.Initializer)
else
initStr := '';
Result := 'var ' + Accept(Node.Target) + initStr;
end;
end.
@@ -1,244 +0,0 @@
unit Myc.Fmx.AstEditor.Workspace;
interface
uses
System.SysUtils,
System.Classes,
System.Types,
System.UITypes,
System.Math.Vectors,
System.Generics.Collections,
FMX.Types,
FMX.Controls,
FMX.Graphics,
FMX.Objects,
Myc.Ast,
Myc.Ast.Nodes,
Myc.Ast.Scope,
Myc.Ast.Compiler.Binder;
const
cDataPinColor = TAlphaColors.Dodgerblue;
cExecPinColor = TAlphaColors.Lightgreen;
type
TPinConnection = record
OutputPin: TControl;
InputPin: TControl;
constructor Create(AOutputPin, AInputPin: TControl);
end;
TAuraWorkspace = class(TStyledControl)
private
FConnections: TArray<TPinConnection>;
FPanning: TSizeF;
FIsPanning: Boolean;
FLastPanPos: TPointF;
FZoom: Single;
protected
procedure Paint; override;
procedure DoDeleteChildren; 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;
procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override;
procedure DblClick; override;
public
constructor Create(AOwner: TComponent); override;
procedure Build(const RootNode: IAstNode; const Position: TPointF);
function GetChildrenMatrix(var Matrix: TMatrix; var Simple: Boolean): Boolean; override;
function Zoom(Factor: Single): Boolean;
published
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;
implementation
uses
System.Math,
FMX.Platform,
Myc.Fmx.AstEditor.Node;
{ TPinConnection }
constructor TPinConnection.Create(AOutputPin, AInputPin: TControl);
begin
OutputPin := AOutputPin;
InputPin := AInputPin;
end;
{ TAuraWorkspace }
constructor TAuraWorkspace.Create(AOwner: TComponent);
begin
inherited;
FZoom := 1.0;
end;
procedure TAuraWorkspace.Build(const RootNode: IAstNode; const Position: TPointF);
begin
var visu := TAstVisualizer.Create(Self, Self, 0) as IAstVisualizer;
visu.CallAccept(RootNode);
end;
procedure TAuraWorkspace.DoDeleteChildren;
begin
FConnections := nil;
inherited;
end;
function TAuraWorkspace.GetChildrenMatrix(var Matrix: TMatrix; var Simple: Boolean): Boolean;
begin
Matrix := TMatrix.CreateScaling(FZoom, FZoom) * TMatrix.CreateTranslation(FPanning.cx, FPanning.cy);
Simple := (FZoom = 1.0) and (FPanning.cx = 0) and (FPanning.cy = 0);
Result := true;
end;
procedure TAuraWorkspace.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
inherited;
if (Button = TMouseButton.mbLeft) and (ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl) then
begin
FIsPanning := True;
FLastPanPos := TPointF.Create(X, Y);
Cursor := crHandPoint;
end;
end;
procedure TAuraWorkspace.MouseMove(Shift: TShiftState; X, Y: Single);
var
delta: TPointF;
begin
inherited;
if FIsPanning then
begin
delta := TPointF.Create(X - FLastPanPos.X, Y - FLastPanPos.Y);
FPanning.cx := FPanning.cx + delta.X;
FPanning.cy := FPanning.cy + delta.Y;
FLastPanPos := TPointF.Create(X, Y);
RecalcAbsolute;
RecalcUpdateRect;
Repaint;
end;
end;
procedure TAuraWorkspace.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
inherited;
if (Button = TMouseButton.mbLeft) and (FIsPanning) then
begin
FIsPanning := False;
Cursor := crDefault;
end;
end;
procedure TAuraWorkspace.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
begin
inherited;
Handled := Zoom(IfThen(WheelDelta > 0, FZoom * 1.1, FZoom / 1.1));
end;
procedure TAuraWorkspace.DblClick;
begin
inherited;
Zoom(1.0);
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;
for connection in FConnections do
begin
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);
startPoint := AbsoluteToLocal(absoluteStart);
endPoint := AbsoluteToLocal(absoluteEnd);
var str := connection.InputPin.TagString;
if Pos('data', str) = 0 then
Canvas.Stroke.Color := cExecPinColor
else
Canvas.Stroke.Color := cDataPinColor;
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;
function TAuraWorkspace.Zoom(Factor: Single): Boolean;
var
MouseService: IFMXMouseService;
begin
Result := TPlatformServices.Current.SupportsPlatformService(IFMXMouseService, MouseService);
if Result then
begin
var MousePos := ScreenToLocal(MouseService.GetMousePos);
Factor := Max(0.2, Min(3.0, Factor));
FPanning.cx := MousePos.X * (1 - Factor / FZoom) + FPanning.cx * (Factor / FZoom);
FPanning.cy := MousePos.Y * (1 - Factor / FZoom) + FPanning.cy * (Factor / FZoom);
FZoom := Factor;
RecalcAbsolute;
RecalcUpdateRect;
Repaint;
end;
end;
end.