From 5593761551e331ae5369aabf426186381eda9f3f Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 29 Aug 2025 00:27:17 +0200 Subject: [PATCH] AST refactoring --- ASTPlayground/ASTPlayground.dproj | 2 +- ASTPlayground/MainForm.fmx | 25 ++++++ ASTPlayground/MainForm.pas | 130 +++++++++++++++++++++++++++++- Src/AST/Myc.Ast.Evaluator.pas | 52 +++++++++--- Src/AST/Myc.Ast.Printer.pas | 11 +++ Src/AST/Myc.Ast.pas | 110 +++++++++++++++++++------ 6 files changed, 294 insertions(+), 36 deletions(-) diff --git a/ASTPlayground/ASTPlayground.dproj b/ASTPlayground/ASTPlayground.dproj index ef8ced3..650a013 100644 --- a/ASTPlayground/ASTPlayground.dproj +++ b/ASTPlayground/ASTPlayground.dproj @@ -4,7 +4,7 @@ 20.3 FMX True - Release + Debug Win32 ASTPlayground 3 diff --git a/ASTPlayground/MainForm.fmx b/ASTPlayground/MainForm.fmx index ac48db6..2aae761 100644 --- a/ASTPlayground/MainForm.fmx +++ b/ASTPlayground/MainForm.fmx @@ -7,6 +7,8 @@ object Form1: TForm1 FormFactor.Width = 320 FormFactor.Height = 480 FormFactor.Devices = [Desktop] + OnCreate = FormCreate + OnDestroy = FormDestroy DesignerMasterStyle = 0 object Panel1: TPanel Align = Left @@ -71,6 +73,29 @@ object Form1: TForm1 TextSettings.Trimming = None OnClick = FibonacciButtonClick end + object CrerateTriggerExampleButton: TButton + Position.X = 24.000000000000000000 + Position.Y = 320.000000000000000000 + TabOrder = 9 + Text = 'TriggerTest' + TextSettings.Trimming = None + OnClick = CrerateTriggerExampleButtonClick + end + object DoTriggerButton: TButton + Position.X = 24.000000000000000000 + Position.Y = 350.000000000000000000 + TabOrder = 10 + Text = 'Trigger!' + TextSettings.Trimming = None + OnClick = DoTriggerButtonClick + object DoTrigger2Button: TButton + Position.Y = 30.000000000000000000 + TabOrder = 8 + Text = 'Trigger2' + TextSettings.Trimming = None + OnClick = DoTrigger2ButtonClick + end + end end object Memo1: TMemo Touch.InteractiveGestures = [Pan, LongTap, DoubleTap] diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index 7d3c968..0fefc3b 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -34,7 +34,15 @@ type RecursionButton: TButton; ShowScopeBox: TCheckBox; FibonacciButton: TButton; + CrerateTriggerExampleButton: TButton; + DoTriggerButton: TButton; + DoTrigger2Button: TButton; + procedure FormDestroy(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure CrerateTriggerExampleButtonClick(Sender: TObject); procedure DebugButtonClick(Sender: TObject); + procedure DoTrigger2ButtonClick(Sender: TObject); + procedure DoTriggerButtonClick(Sender: TObject); procedure FibonacciButtonClick(Sender: TObject); procedure PrettyPrintButtonClick(Sender: TObject); procedure RecursionButtonClick(Sender: TObject); @@ -43,6 +51,7 @@ type private // Stores the last AST generated by Test1 or Test2 button clicks. FLastAst: IAstNode; + FGScope: TExecutionScope; public { Public declarations } end; @@ -57,6 +66,16 @@ uses {$R *.fmx} +procedure TForm1.FormDestroy(Sender: TObject); +begin + FGScope.Free; +end; + +procedure TForm1.FormCreate(Sender: TObject); +begin + FGScope := TExecutionScope.Create(nil); +end; + procedure TForm1.DebugButtonClick(Sender: TObject); var scope: TExecutionScope; @@ -71,7 +90,7 @@ begin exit; end; - scope := TExecutionScope.Create(nil); + scope := TExecutionScope.Create(FGScope); try Memo1.Lines.Clear; Memo1.Lines.Add('--- Debug Evaluator Trace ---'); @@ -113,6 +132,8 @@ var result20, result30, result40: Int64; time20, time30, time40: Int64; begin + FGScope.Clear; + Memo1.Lines.Clear; Memo1.Lines.Add('--- Native Delphi Fibonacci Performance ---'); Memo1.Lines.Add('Calculating fib(30) and fib(40)...'); @@ -227,6 +248,8 @@ var result: TAstValue; sw: TStopwatch; begin + FGScope.Clear; + sw := TStopwatch.Create; Memo1.Lines.Clear; Memo1.Lines.Add('--- Recursive factorial(20) ---'); @@ -280,6 +303,8 @@ var result: TAstValue; sw: TStopwatch; begin + FGScope.Clear; + sw := TStopwatch.Create; Memo1.Lines.Clear; Memo1.Lines.Add('--- Simple AST Execution ---'); @@ -320,6 +345,8 @@ var result: TAstValue; sw: TStopwatch; begin + FGScope.Clear; + sw := TStopwatch.Create; Memo1.Lines.Clear; Memo1.Lines.Add('--- Factory Pattern Demo ---'); @@ -361,4 +388,105 @@ begin end; end; +procedure TForm1.CrerateTriggerExampleButtonClick(Sender: TObject); +var + lambdaAst: ILambdaExpressionNode; + visitor: IAstVisitor; + closureValue: TAstValue; +begin + FGScope.Clear; + + // Create an AST that gets a "tick" in DoTriggerButtonClick. + // This simulates a simple Blueprint-like event system. + // It maintains state via a persistent execution scope. + + Memo1.Lines.Clear; + Memo1.Lines.Add('--- Creating Trigger Blueprint ---'); + + // 1. Initialize the variable 'X' to 0 directly in the scope. + FGScope.SetValue('X', TAstValue.FromScalar(TScalar.FromInt64(0))); + + // 2. Create a lambda that takes the summand as an argument. + // AST for: (summand) => { X = X + summand; } + // Using the new Assignment node to modify 'X' in its parent scope. + lambdaAst := + TAst.LambdaExpr( + [TAst.Identifier('summand')], + TAst.Assign(TAst.Identifier('X'), TAst.BinaryExpr(TAst.Identifier('X'), boAdd, TAst.Identifier('summand'))) + ); + + // 3. Evaluate the lambda to create a closure and store it in the scope. + // The closure captures the scope where 'X' is defined. + visitor := TEvaluatorVisitor.Create(FGScope); + closureValue := lambdaAst.Accept(visitor); + FGScope.SetValue('tickHandler', closureValue); + + // FLastAst is not used for this parameterized example. + FLastAst := lambdaAst; + + Memo1.Lines.Add('Variable "X" initialized to 0 in persistent scope.'); + Memo1.Lines.Add('Blueprint function "tickHandler(summand)" created.'); + Memo1.Lines.Add('Click "Do Trigger" or "Do Trigger 2" to execute.'); +end; + +procedure TForm1.DoTriggerButtonClick(Sender: TObject); +var + visitor: IAstVisitor; + currentValue: TAstValue; + callAst: IFunctionCallNode; +begin + // A "tick" executes the stored AST using the persistent scope. + + // Create an AST to call the stored handler with argument 1. + // AST for: tickHandler(1) + callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(1))]); + + // Execute the call AST. + visitor := TEvaluatorVisitor.Create(FGScope); + callAst.Accept(visitor); + + FLastAst := callAst; + + // Get the updated value of 'X' from the scope and display it. + if FGScope.FindValue('X', currentValue) then + begin + Memo1.Lines.Add(Format('Tick(1)! New value of X: %s', [currentValue.ToString])); + end + else + begin + // This should not happen if setup was correct. + Memo1.Lines.Add('Error: Variable "X" not found in scope.'); + end; +end; + +procedure TForm1.DoTrigger2ButtonClick(Sender: TObject); +var + visitor: IAstVisitor; + currentValue: TAstValue; + callAst: IFunctionCallNode; +begin + // A "tick" that adds 2, using the same blueprint function. + + // Create an AST to call the stored handler with argument 2. + // AST for: tickHandler(2) + callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(2))]); + + FLastAst := callAst; + + // Execute the call AST. + visitor := TEvaluatorVisitor.Create(FGScope); + callAst.Accept(visitor); + + // Get the updated value of 'X' from the scope and display it. + if FGScope.FindValue('X', currentValue) then + begin + Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [currentValue.ToString])); + end + else + begin + // This should not happen if setup was correct. + Memo1.Lines.Add('Error: Variable "X" not found in scope.'); + end; +end; + end. diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index d644680..06876a4 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -12,8 +12,9 @@ uses type // TEvaluatorVisitor is the base implementation for evaluating an AST. TEvaluatorVisitor = class(TInterfacedObject, IAstVisitor) - protected + private FScope: TExecutionScope; + protected function IsTruthy(const AValue: TAstValue): Boolean; function CreateVisitorForScope(AScope: TExecutionScope): IAstVisitor; virtual; public @@ -27,6 +28,7 @@ type function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; virtual; function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; virtual; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; virtual; + function VisitAssignment(const Node: IAssignmentNode): TAstValue; virtual; end; // TDebugEvaluatorVisitor now overrides all visit methods for full tracing @@ -53,6 +55,7 @@ type function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; override; function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; override; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; override; + function VisitAssignment(const Node: IAssignmentNode): TAstValue; override; end; implementation @@ -66,22 +69,22 @@ type TClosureValue = class(TInterfacedObject, IEvaluatorClosure) private FBody: IExpressionNode; - FParameters: TList; + FParameters: TArray; FClosureScope: TExecutionScope; function GetBody: IExpressionNode; - function GetParameters: TList; + function GetParameters: TArray; function GetClosureScope: TExecutionScope; public - constructor Create(ABody: IExpressionNode; AParameters: TList; AClosureScope: TExecutionScope); + constructor Create(ABody: IExpressionNode; const AParameters: TArray; AClosureScope: TExecutionScope); end; { TClosureValue } -constructor TClosureValue.Create(ABody: IExpressionNode; AParameters: TList; AClosureScope: TExecutionScope); +constructor TClosureValue.Create(ABody: IExpressionNode; const AParameters: TArray; AClosureScope: TExecutionScope); begin inherited Create; FBody := ABody; - FParameters := AParameters; // Taking ownership of the list reference + FParameters := AParameters; FClosureScope := AClosureScope; end; @@ -95,7 +98,7 @@ begin Result := FClosureScope; end; -function TClosureValue.GetParameters: TList; +function TClosureValue.GetParameters: TArray; begin Result := FParameters; end; @@ -131,6 +134,22 @@ begin end; end; +function TEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; +var + varName: string; + value: TAstValue; +begin + // Evaluate the right-hand side of the assignment. + value := Node.Value.Accept(Self); + varName := Node.Identifier.Name; + + // Assign the value to the variable in the scope chain. + FScope.AssignValue(varName, value); + + // Assignment expressions return the assigned value. + Result := value; +end; + function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TAstValue; begin Result := TAstValue.FromScalar(Node.Value); @@ -152,7 +171,6 @@ var initValue: TAstValue; begin varName := Node.Identifier.Name; - FScope.SetValue(varName, TAstValue.Undefined); if Assigned(Node.Initializer) then initValue := Node.Initializer.Accept(Self) else @@ -188,9 +206,9 @@ begin end; closure := calleeValue.AsClosure; - if (arguments.Count <> closure.Parameters.Count) then + if (arguments.Count <> Length(closure.Parameters)) then begin - raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [closure.Parameters.Count, arguments.Count]); + raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [Length(closure.Parameters), arguments.Count]); end; callScope := TExecutionScope.Create(closure.ClosureScope); @@ -321,6 +339,7 @@ begin FLog := ALog; FIndentLevel := AInitialIndent; FShowScope := AShowScope; + ShowScope; end; function TDebugEvaluatorVisitor.CreateVisitorForScope(AScope: TExecutionScope): IAstVisitor; @@ -380,6 +399,18 @@ begin end; end; +function TDebugEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; +begin + AppendLine(Format('Assignment %s := {', [Node.Identifier.Name])); + Indent; + try + Result := inherited VisitAssignment(Node); + finally + Unindent; + end; + AppendLine(Format('} -> %s', [Result.ToString])); +end; + function TDebugEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TAstValue; begin AppendLine(Format('Constant (%s)', [Node.Value.ToString])); @@ -450,7 +481,6 @@ begin AppendLine('FunctionCall{'); Indent; try - ShowScope; Result := inherited VisitFunctionCall(Node); finally Unindent; diff --git a/Src/AST/Myc.Ast.Printer.pas b/Src/AST/Myc.Ast.Printer.pas index f203862..525d15f 100644 --- a/Src/AST/Myc.Ast.Printer.pas +++ b/Src/AST/Myc.Ast.Printer.pas @@ -30,6 +30,7 @@ type function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; + function VisitAssignment(const Node: IAssignmentNode): TAstValue; end; implementation @@ -192,4 +193,14 @@ begin Result := TAstValue.Undefined; end; +function TPrettyPrintVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; +begin + // Print the assignment node. + AppendLine(Format('Assignment (%s)', [Node.Identifier.Name])); + Indent; + Node.Value.Accept(Self); + Unindent; + Result := TAstValue.Undefined; +end; + end. diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index 6bf2cd9..745d1f1 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -42,6 +42,7 @@ type IFunctionCallNode = interface; IBlockExpressionNode = interface; IVariableDeclarationNode = interface; + IAssignmentNode = interface; // Represents an assignment to an existing variable. IEvaluatorClosure = interface; TExecutionScope = class; @@ -51,11 +52,11 @@ type IEvaluatorClosure = interface(IInterface) {$region 'private'} function GetBody: IExpressionNode; - function GetParameters: TList; + function GetParameters: TArray; function GetClosureScope: TExecutionScope; {$endregion} property Body: IExpressionNode read GetBody; - property Parameters: TList read GetParameters; + property Parameters: TArray read GetParameters; property ClosureScope: TExecutionScope read GetClosureScope; end; @@ -103,6 +104,7 @@ type function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; + function VisitAssignment(const Node: IAssignmentNode): TAstValue; end; // --- AST Node Interfaces (now using TAstValue) --- @@ -163,10 +165,10 @@ type ILambdaExpressionNode = interface(IExpressionNode) {$region 'private'} - function GetParameters: TList; + function GetParameters: TArray; function GetBody: IExpressionNode; {$endregion} - property Parameters: TList read GetParameters; + property Parameters: TArray read GetParameters; property Body: IExpressionNode read GetBody; end; @@ -197,6 +199,16 @@ type property Initializer: IExpressionNode read GetInitializer; end; + // An assignment expression that returns the assigned value. + IAssignmentNode = interface(IExpressionNode) + {$region 'private'} + function GetIdentifier: IIdentifierNode; + function GetValue: IExpressionNode; + {$endregion} + property Identifier: IIdentifierNode read GetIdentifier; + property Value: IExpressionNode read GetValue; + end; + // Record acting as a namespace for the factory functions. TAst = record class function Constant(AValue: TScalar): IConstantNode; static; @@ -211,10 +223,11 @@ type const ACondition: IExpressionNode; const AThenBranch, AElseBranch: IExpressionNode ): IIfExpressionNode; static; - class function LambdaExpr(const AParameters: array of IIdentifierNode; const ABody: IExpressionNode): ILambdaExpressionNode; static; + class function LambdaExpr(const AParameters: TArray; const ABody: IExpressionNode): ILambdaExpressionNode; static; class function FunctionCall(const ACallee: IExpressionNode; const AArguments: array of IExpressionNode): IFunctionCallNode; static; class function Block(const AExpressions: array of IExpressionNode): IBlockExpressionNode; static; class function VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IExpressionNode): IVariableDeclarationNode; static; + class function Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; static; end; // Manages the scope of execution, holding variables and their values. @@ -226,8 +239,12 @@ type public constructor Create(AParent: TExecutionScope = nil); destructor Destroy; override; + procedure Clear; function FindValue(const Name: string; out Value: TAstValue): Boolean; + // Sets or updates a value in the current scope. Used for declarations. procedure SetValue(const Name: string; const Value: TAstValue); + // Finds an existing variable in the scope chain and updates its value. Used for assignments. + procedure AssignValue(const Name: string; const Value: TAstValue); function Dump: string; end; @@ -304,13 +321,12 @@ type { TLambdaExpressionNode } TLambdaExpressionNode = class(TAstNode, ILambdaExpressionNode) private - FParameters: TList; + FParameters: TArray; FBody: IExpressionNode; - function GetParameters: TList; + function GetParameters: TArray; function GetBody: IExpressionNode; public - constructor Create(const AParameters: array of IIdentifierNode; const ABody: IExpressionNode); - destructor Destroy; override; + constructor Create(const AParameters: TArray; const ABody: IExpressionNode); function Accept(const Visitor: IAstVisitor): TAstValue; override; end; @@ -350,6 +366,18 @@ type function Accept(const Visitor: IAstVisitor): TAstValue; override; end; + { TAssignmentNode } + TAssignmentNode = class(TAstNode, IAssignmentNode) + private + FIdentifier: IIdentifierNode; + FValue: IExpressionNode; + function GetIdentifier: IIdentifierNode; + function GetValue: IExpressionNode; + public + constructor Create(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode); + function Accept(const Visitor: IAstVisitor): TAstValue; override; + end; + { TAstValue } class operator TAstValue.Initialize(out Dest: TAstValue); @@ -577,21 +605,11 @@ end; { TLambdaExpressionNode } -constructor TLambdaExpressionNode.Create(const AParameters: array of IIdentifierNode; const ABody: IExpressionNode); -var - param: IIdentifierNode; +constructor TLambdaExpressionNode.Create(const AParameters: TArray; const ABody: IExpressionNode); begin inherited Create; FBody := ABody; - FParameters := TList.Create; - for param in AParameters do - FParameters.Add(param); -end; - -destructor TLambdaExpressionNode.Destroy; -begin - FParameters.Free; - inherited; + FParameters := AParameters; end; function TLambdaExpressionNode.Accept(const Visitor: IAstVisitor): TAstValue; @@ -604,7 +622,7 @@ begin Result := FBody; end; -function TLambdaExpressionNode.GetParameters: TList; +function TLambdaExpressionNode.GetParameters: TArray; begin Result := FParameters; end; @@ -695,8 +713,37 @@ begin Result := FInitializer; end; +{ TAssignmentNode } + +constructor TAssignmentNode.Create(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode); +begin + inherited Create; + FIdentifier := AIdentifier; + FValue := AValue; +end; + +function TAssignmentNode.Accept(const Visitor: IAstVisitor): TAstValue; +begin + Result := Visitor.VisitAssignment(Self); +end; + +function TAssignmentNode.GetIdentifier: IIdentifierNode; +begin + Result := FIdentifier; +end; + +function TAssignmentNode.GetValue: IExpressionNode; +begin + Result := FValue; +end; + { TAst } +class function TAst.Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; +begin + Result := TAssignmentNode.Create(AIdentifier, AValue); +end; + class function TAst.Block(const AExpressions: array of IExpressionNode): IBlockExpressionNode; begin Result := TBlockExpressionNode.Create(AExpressions); @@ -727,7 +774,7 @@ begin Result := TIfExpressionNode.Create(ACondition, AThenBranch, AElseBranch); end; -class function TAst.LambdaExpr(const AParameters: array of IIdentifierNode; const ABody: IExpressionNode): ILambdaExpressionNode; +class function TAst.LambdaExpr(const AParameters: TArray; const ABody: IExpressionNode): ILambdaExpressionNode; begin Result := TLambdaExpressionNode.Create(AParameters, ABody); end; @@ -757,6 +804,23 @@ begin inherited Destroy; end; +procedure TExecutionScope.AssignValue(const Name: string; const Value: TAstValue); +begin + if FVariables.ContainsKey(Name) then + FVariables.AddOrSetValue(Name, Value) + else if Assigned(FParent) then + FParent.AssignValue(Name, Value) + else + raise Exception.CreateFmt('Cannot assign to undeclared variable "%s".', [Name]); +end; + +procedure TExecutionScope.Clear; +begin + FVariables.Clear; + if Assigned(FParent) then + FParent.Clear; +end; + procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); var pair: TPair;