AST refactoring

This commit is contained in:
Michael Schimmel
2025-08-28 16:26:48 +02:00
parent 9419f5cd03
commit 9a5f2c1b1d
10 changed files with 866 additions and 810 deletions
+112 -90
View File
@@ -18,11 +18,10 @@ uses
FMX.ScrollBox,
FMX.Memo,
FMX.Controls.Presentation,
Myc.Data.Types,
Myc.Data.POD,
Myc.Ast,
Myc.Ast.Scope,
Myc.Ast.Evaluator,
Myc.Ast.Printer;
Myc.Ast.Printer; // Added for TExecutionScope
type
TForm1 = class(TForm)
@@ -53,13 +52,17 @@ var
implementation
uses
System.Diagnostics; // For TStopwatch
{$R *.fmx}
procedure TForm1.DebugButtonClick(Sender: TObject);
var
scope: TExecutionScope;
visitor: IAstVisitor;
result: IDataValue;
result: TAstValue;
sw: TStopwatch;
begin
if not Assigned(FLastAst) then
begin
@@ -73,13 +76,15 @@ begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Debug Evaluator Trace ---');
// Create the DEBUG visitor, passing the Memo's Lines as the log output
visitor := TDebugEvaluatorVisitor.Create(scope, Memo1.Lines, ShowScopeBox.IsChecked, 0);
sw := TStopwatch.StartNew;
result := FLastAst.Accept(visitor);
sw.Stop;
Memo1.Lines.Add('-----------------------------');
Memo1.Lines.Add('Final script result: ' + result.AsString);
Memo1.Lines.Add(Format('Final script result: %s', [result.ToString]));
Memo1.Lines.Add(Format('Execution time: %d ms', [sw.ElapsedMilliseconds]));
Memo1.Lines.Add('');
Memo1.Lines.Add('(AST structure stored. Click "Pretty Print" to view.)');
@@ -89,17 +94,59 @@ begin
end;
procedure TForm1.FibonacciButtonClick(Sender: TObject);
function NativeFib(n: Integer): Int64;
begin
// The identical, naive recursive algorithm in native Delphi code.
if (n < 2) then
Result := n
else
Result := NativeFib(n - 1) + NativeFib(n - 2);
end;
var
scope: TExecutionScope;
visitor: IAstVisitor;
root: IExpressionNode;
result: IDataValue;
result: TAstValue;
sw: TStopwatch;
result20, result30, result40: Int64;
time20, time30, time40: Int64;
begin
// This test defines and calls a deeply recursive fibonacci function.
// var fib = func(n) {
// if (n < 2) then n else fib(n - 1) + fib(n - 2)
// };
// fib(10);
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Native Delphi Fibonacci Performance ---');
Memo1.Lines.Add('Calculating fib(30) and fib(40)...');
Application.ProcessMessages; // Update UI before blocking
// --- Calculate fib(30) ---
sw := TStopwatch.StartNew;
result20 := NativeFib(20);
sw.Stop;
time20 := sw.ElapsedMilliseconds;
// --- Calculate fib(30) ---
sw := TStopwatch.StartNew;
result30 := NativeFib(30);
sw.Stop;
time30 := sw.ElapsedMilliseconds;
// --- Calculate fib(40) ---
sw.Reset;
sw.Start;
result40 := NativeFib(40);
sw.Stop;
time40 := sw.ElapsedMilliseconds;
sw.Reset;
Memo1.Lines.Add('');
Memo1.Lines.Add(Format('fib(20) = %d (calculated in %d ms)', [result20, time20]));
Memo1.Lines.Add(Format('fib(30) = %d (calculated in %d ms)', [result30, time30]));
Memo1.Lines.Add(Format('fib(40) = %d (calculated in %d ms)', [result40, time40]));
Memo1.Lines.Add('');
Memo1.Lines.Add('');
Memo1.Lines.Add('--- Recursive fib with AST---');
sw.Start;
root :=
TAst.Block(
[
@@ -108,44 +155,36 @@ begin
TAst.LambdaExpr(
[TAst.Identifier('n')],
TAst.IfExpr(
// Condition: n < 2
TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TDataType.Ordinal.CreateValue(2))),
// Then branch: n
TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))),
TAst.Identifier('n'),
// Else branch: fib(n - 1) + fib(n - 2)
TAst.BinaryExpr(
// fib(n - 1)
TAst.FunctionCall(
TAst.Identifier('fib'),
[TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TDataType.Ordinal.CreateValue(1)))]
[TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))]
),
boAdd,
// fib(n - 2)
TAst.FunctionCall(
TAst.Identifier('fib'),
[TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TDataType.Ordinal.CreateValue(2)))]
[TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(2)))]
)
)
)
)
),
// Call the function
TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(TDataType.Ordinal.CreateValue(10))])
TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(TScalar.FromInt64(30))])
]
);
FLastAst := root;
scope := TExecutionScope.Create(nil);
try
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Recursive fib(10) ---');
visitor := TEvaluatorVisitor.Create(scope);
result := root.Accept(visitor);
Memo1.Lines.Add('Result: ' + result.AsString); // Should be 55
sw.Stop;
Memo1.Lines.Add(Format('Result: fib(30) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
Memo1.Lines.Add('');
Memo1.Lines.Add('(AST structure stored. Click "Pretty Print" or "Debug" to view.)');
finally
scope.Free;
end;
@@ -154,8 +193,8 @@ end;
procedure TForm1.PrettyPrintButtonClick(Sender: TObject);
var
visitor: TPrettyPrintVisitor;
sw: TStopwatch;
begin
// This button now prints the AST that was stored in FLastAst.
Memo1.Lines.Clear;
Memo1.Lines.Add('--- AST Pretty Print ---');
@@ -168,8 +207,12 @@ begin
visitor := TPrettyPrintVisitor.Create;
try
sw := TStopwatch.StartNew;
FLastAst.Accept(visitor);
sw.Stop;
Memo1.Lines.Add(visitor.GetResult);
Memo1.Lines.Add(Format('(AST rendered in %d ms)', [sw.ElapsedMilliseconds]));
finally
// Visitor is an interfaced object and managed automatically.
end;
@@ -180,13 +223,14 @@ var
scope: TExecutionScope;
visitor: IAstVisitor;
root: IExpressionNode;
result: IDataValue;
result: TAstValue;
sw: TStopwatch;
begin
// This test defines and calls a recursive factorial function.
// var factorial = func(n) {
// if (n < 2) then 1 else n * factorial(n - 1)
// };
// factorial(5);
sw := TStopwatch.Create;
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Recursive factorial(20) ---');
sw.Start;
root :=
TAst.Block(
[
@@ -195,39 +239,33 @@ begin
TAst.LambdaExpr(
[TAst.Identifier('n')],
TAst.IfExpr(
// Condition: n < 2
TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TDataType.Ordinal.CreateValue(2))),
// Then branch: 1
TAst.Constant(TDataType.Ordinal.CreateValue(1)),
// Else branch: n * factorial(n - 1)
TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))),
TAst.Constant(TScalar.FromInt64(1)),
TAst.BinaryExpr(
TAst.Identifier('n'),
boMultiply,
TAst.FunctionCall(
TAst.Identifier('factorial'), // Recursive call
[TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TDataType.Ordinal.CreateValue(1)))]
TAst.Identifier('factorial'),
[TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))]
)
)
)
)
),
// Call the function
TAst.FunctionCall(TAst.Identifier('factorial'), [TAst.Constant(TDataType.Ordinal.CreateValue(6))])
TAst.FunctionCall(TAst.Identifier('factorial'), [TAst.Constant(TScalar.FromInt64(20))])
]
);
FLastAst := root;
scope := TExecutionScope.Create(nil);
try
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Recursive factorial(6) ---');
visitor := TEvaluatorVisitor.Create(scope);
result := root.Accept(visitor);
Memo1.Lines.Add('Result: ' + result.AsString);
sw.Stop;
Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
Memo1.Lines.Add('');
Memo1.Lines.Add('(AST structure stored. Click "Pretty Print" or "Debug" to view.)');
finally
scope.Free;
end;
@@ -238,42 +276,34 @@ var
root: IAstNode;
scope: TExecutionScope;
visitor: IAstVisitor;
result: IDataValue;
result: TAstValue;
sw: TStopwatch;
begin
// With the new expression-oriented AST, the entire logic can be
// represented as a single block expression. The block itself
// evaluates to the value of its last expression.
sw := TStopwatch.Create;
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Simple AST Execution ---');
sw.Start;
root :=
TAst.Block(
[
// var a := 10; (this expression returns void)
TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(TDataType.Ordinal.CreateValue(10))),
// var b := a * 2; (this expression also returns void)
TAst.VarDecl(
TAst.Identifier('b'),
TAst.BinaryExpr(TAst.Identifier('a'), boMultiply, TAst.Constant(TDataType.Ordinal.CreateValue(2)))
),
// a + b; (this is the last expression, its value becomes the block's value)
TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(TScalar.FromInt64(10))),
TAst.VarDecl(TAst.Identifier('b'), TAst.BinaryExpr(TAst.Identifier('a'), boMultiply, TAst.Constant(TScalar.FromInt64(2)))),
TAst.BinaryExpr(TAst.Identifier('a'), boAdd, TAst.Identifier('b'))
]
);
// Store the generated AST for the pretty printer.
FLastAst := root;
// Evaluate the entire AST with a single call
scope := TExecutionScope.Create(nil);
try
visitor := TEvaluatorVisitor.Create(scope);
result := root.Accept(visitor);
sw.Stop;
// Display the result
Memo1.Lines.Clear;
Memo1.Lines.Add('AST execution result:');
if Assigned(result) then
Memo1.Lines.Add(result.AsString)
if not result.IsUndefined then
Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]))
else
Memo1.Lines.Add('<no result>');
Memo1.Lines.Add(Format('<undefined result> (calculated in %d ms)', [sw.ElapsedMilliseconds]));
Memo1.Lines.Add('');
Memo1.Lines.Add('(AST structure stored. Click "Pretty Print" to view.)');
finally
@@ -286,53 +316,45 @@ var
scope: TExecutionScope;
visitor: IAstVisitor;
root: IExpressionNode;
result: IDataValue;
result: TAstValue;
sw: TStopwatch;
begin
// The entire logic is now encapsulated in a single AST.
// This script defines a factory function, then calls it twice.
sw := TStopwatch.Create;
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Factory Pattern Demo ---');
sw.Start;
root :=
TAst.Block(
[
// 1. Define the factory and assign it to a variable 'createStrategyInstance'
TAst.VarDecl(
TAst.Identifier('createStrategyInstance'),
TAst.LambdaExpr(
[TAst.Identifier('offset')], // The factory parameter
[TAst.Identifier('offset')],
TAst.Block(
[
// The body of the factory defines the strategy logic
TAst.VarDecl(TAst.Identifier('baseValue'), TAst.Constant(TDataType.Ordinal.CreateValue(100))),
TAst.VarDecl(TAst.Identifier('baseValue'), TAst.Constant(TScalar.FromInt64(100))),
TAst.BinaryExpr(TAst.Identifier('baseValue'), boAdd, TAst.Identifier('offset'))
]
)
)
),
// 2. Call the factory with the first parameter set.
// The result of this expression (120) is calculated but discarded by the block.
TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(TDataType.Ordinal.CreateValue(20))]),
// 3. Call the factory with the second parameter set.
// As this is the last expression, its result (155) becomes the result of the entire block.
TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(TDataType.Ordinal.CreateValue(55))])
TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(TScalar.FromInt64(20))]),
TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(TScalar.FromInt64(55))])
]
);
// Store the complete AST for the pretty printer.
FLastAst := root;
// Evaluate the AST.
scope := TExecutionScope.Create(nil);
try
visitor := TEvaluatorVisitor.Create(scope);
Memo1.Lines.Clear;
Memo1.Lines.Add('Factory Pattern Demo (Single AST):');
result := root.Accept(visitor);
sw.Stop;
Memo1.Lines.Add('The entire script has been executed.');
Memo1.Lines.Add('Result of the final expression: ' + result.AsString);
Memo1.Lines.Add(Format('Result of the final expression: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
Memo1.Lines.Add('');
Memo1.Lines.Add('(AST structure stored. Click "Pretty Print" to view.)');
finally
scope.Free;
end;