AST function purity analysis
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -26,7 +26,8 @@ uses
|
|||||||
Myc.Ast.Compiler.Macros in '..\Src\AST\Myc.Ast.Compiler.Macros.pas',
|
Myc.Ast.Compiler.Macros in '..\Src\AST\Myc.Ast.Compiler.Macros.pas',
|
||||||
Myc.Ast.Compiler.Specializer in '..\Src\AST\Myc.Ast.Compiler.Specializer.pas',
|
Myc.Ast.Compiler.Specializer in '..\Src\AST\Myc.Ast.Compiler.Specializer.pas',
|
||||||
Myc.Ast.Environment in '..\Src\AST\Myc.Ast.Environment.pas',
|
Myc.Ast.Environment in '..\Src\AST\Myc.Ast.Environment.pas',
|
||||||
Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas';
|
Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas',
|
||||||
|
Myc.Ast.Analysis.Purity in '..\Src\AST\Myc.Ast.Analysis.Purity.pas';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
<DCCReference Include="..\Src\AST\Myc.Ast.Compiler.Specializer.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Ast.Compiler.Specializer.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Ast.Environment.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Ast.Environment.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
|
||||||
|
<DCCReference Include="..\Src\AST\Myc.Ast.Analysis.Purity.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
@@ -212,6 +213,12 @@
|
|||||||
<Overwrite>true</Overwrite>
|
<Overwrite>true</Overwrite>
|
||||||
</Platform>
|
</Platform>
|
||||||
</DeployFile>
|
</DeployFile>
|
||||||
|
<DeployFile LocalName="Win64\Debug\ASTPlayground.rsm" Configuration="Debug" Class="DebugSymbols">
|
||||||
|
<Platform Name="Win64">
|
||||||
|
<RemoteName>ASTPlayground.rsm</RemoteName>
|
||||||
|
<Overwrite>true</Overwrite>
|
||||||
|
</Platform>
|
||||||
|
</DeployFile>
|
||||||
<DeployFile LocalName="Win64\Release\ASTPlayground.exe" Configuration="Release" Class="ProjectOutput">
|
<DeployFile LocalName="Win64\Release\ASTPlayground.exe" Configuration="Release" Class="ProjectOutput">
|
||||||
<Platform Name="Win64">
|
<Platform Name="Win64">
|
||||||
<RemoteName>ASTPlayground.exe</RemoteName>
|
<RemoteName>ASTPlayground.exe</RemoteName>
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ object Form1: TForm1
|
|||||||
'Expanded'
|
'Expanded'
|
||||||
'Bound'
|
'Bound'
|
||||||
'Specialized')
|
'Specialized')
|
||||||
ItemIndex = 0
|
ItemIndex = 3
|
||||||
Position.X = 672.000000000000000000
|
Position.X = 672.000000000000000000
|
||||||
Position.Y = 576.000000000000000000
|
Position.Y = 576.000000000000000000
|
||||||
Size.Width = 104.000000000000000000
|
Size.Width = 104.000000000000000000
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ type
|
|||||||
procedure RTLListViewChange(Sender: TObject);
|
procedure RTLListViewChange(Sender: TObject);
|
||||||
private
|
private
|
||||||
FCurrUnboundAst: IAstNode;
|
FCurrUnboundAst: IAstNode;
|
||||||
FCurrExec: TDataValue.TFunc;
|
FCurrExec: TCompiledFunction;
|
||||||
FEnvironment: TAstEnvironment;
|
FEnvironment: TAstEnvironment;
|
||||||
FWorkspace: TAuraWorkspace;
|
FWorkspace: TAuraWorkspace;
|
||||||
FScriptUpdate: Boolean;
|
FScriptUpdate: Boolean;
|
||||||
@@ -262,7 +262,7 @@ end;
|
|||||||
function TForm1.ExecuteAst(const ANode: IAstNode): TDataValue;
|
function TForm1.ExecuteAst(const ANode: IAstNode): TDataValue;
|
||||||
begin
|
begin
|
||||||
FCurrUnboundAst := ANode;
|
FCurrUnboundAst := ANode;
|
||||||
FCurrExec := nil; // Clear previous
|
FCurrExec := Default(TCompiledFunction);
|
||||||
|
|
||||||
// 1. Set strategy based on UI
|
// 1. Set strategy based on UI
|
||||||
if DebugBox.IsChecked then
|
if DebugBox.IsChecked then
|
||||||
@@ -273,12 +273,21 @@ begin
|
|||||||
try
|
try
|
||||||
// Wrap in Lambda to compile
|
// Wrap in Lambda to compile
|
||||||
var funcDef := TAst.LambdaExpr([], ANode);
|
var funcDef := TAst.LambdaExpr([], ANode);
|
||||||
FCurrExec := FEnvironment.Compile(funcDef).Func;
|
FCurrExec := FEnvironment.Compile(funcDef);
|
||||||
Result := FCurrExec([]);
|
|
||||||
|
if Assigned(FCurrExec.Func) then
|
||||||
|
begin
|
||||||
|
Memo1.Lines.Add(
|
||||||
|
Format('Compiled. Signature: %s, IsPure=%s', [FCurrExec.StaticType.ToString, BoolToStr(FCurrExec.IsPure, true)])
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := FCurrExec.Func([]);
|
||||||
|
|
||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
|
FCurrExec.Func := nil;
|
||||||
Memo1.Lines.Add('--- ERROR ---');
|
Memo1.Lines.Add('--- ERROR ---');
|
||||||
Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
|
Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
|
||||||
Result := TDataValue.Void;
|
Result := TDataValue.Void;
|
||||||
@@ -645,7 +654,7 @@ begin
|
|||||||
FCurrUnboundAst := converter.Deserialize(jsonObj);
|
FCurrUnboundAst := converter.Deserialize(jsonObj);
|
||||||
|
|
||||||
// Run the full pipeline via environment
|
// Run the full pipeline via environment
|
||||||
FCurrExec := FEnvironment.Compile(FCurrUnboundAst).Func;
|
FCurrExec := FEnvironment.Compile(FCurrUnboundAst);
|
||||||
|
|
||||||
Memo1.Lines.Add('AST deserialized and bound successfully from JSON.');
|
Memo1.Lines.Add('AST deserialized and bound successfully from JSON.');
|
||||||
Memo1.Lines.Add('You can now visualize it (Middle Mouse Click) or pretty-print it.');
|
Memo1.Lines.Add('You can now visualize it (Middle Mouse Click) or pretty-print it.');
|
||||||
@@ -655,7 +664,6 @@ begin
|
|||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
FCurrExec := nil;
|
|
||||||
Memo1.Lines.Add('Error deserializing AST from JSON:');
|
Memo1.Lines.Add('Error deserializing AST from JSON:');
|
||||||
Memo1.Lines.Add(E.Message);
|
Memo1.Lines.Add(E.Message);
|
||||||
Memo1.Lines.Add('--- Original JSON ---');
|
Memo1.Lines.Add('--- Original JSON ---');
|
||||||
@@ -675,7 +683,7 @@ var
|
|||||||
begin
|
begin
|
||||||
Memo1.Lines.Clear;
|
Memo1.Lines.Clear;
|
||||||
|
|
||||||
if not Assigned(FCurrExec) then
|
if not Assigned(FCurrExec.Func) then
|
||||||
begin
|
begin
|
||||||
Memo1.Lines.Add('No AST available to serialize. Please generate one first.');
|
Memo1.Lines.Add('No AST available to serialize. Please generate one first.');
|
||||||
exit;
|
exit;
|
||||||
@@ -703,7 +711,7 @@ begin
|
|||||||
Memo1.Lines.Clear;
|
Memo1.Lines.Clear;
|
||||||
Memo1.Lines.Add('--- AST Dump ---');
|
Memo1.Lines.Add('--- AST Dump ---');
|
||||||
|
|
||||||
if not Assigned(FCurrExec) then
|
if not Assigned(FCurrExec.Func) then
|
||||||
begin
|
begin
|
||||||
Memo1.Lines.Add('No *compiled* AST has been generated yet. Click a test button first.');
|
Memo1.Lines.Add('No *compiled* AST has been generated yet. Click a test button first.');
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
unit Myc.Ast.Analysis.Purity;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
Myc.Data.Value,
|
||||||
|
Myc.Ast.Nodes,
|
||||||
|
Myc.Ast.Visitor,
|
||||||
|
Myc.Ast.Scope;
|
||||||
|
|
||||||
|
type
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes an AST to determine if it is referentially transparent and free of side effects.
|
||||||
|
/// </summary>
|
||||||
|
TPurityAnalyzer = class(TAstVisitor<Boolean>)
|
||||||
|
protected
|
||||||
|
// Default behavior: Visit children. If all children return True, then True.
|
||||||
|
// However, we must explicitly define what is allowed.
|
||||||
|
function Accept(const Node: IAstNode): Boolean; override;
|
||||||
|
|
||||||
|
// --- Safe Leaves / Constructs ---
|
||||||
|
function VisitConstant(const Node: IConstantNode): Boolean; override;
|
||||||
|
function VisitKeyword(const Node: IKeywordNode): Boolean; override;
|
||||||
|
function VisitIfExpression(const Node: IIfExpressionNode): Boolean; override;
|
||||||
|
function VisitTernaryExpression(const Node: ITernaryExpressionNode): Boolean; override;
|
||||||
|
function VisitBlockExpression(const Node: IBlockExpressionNode): Boolean; override;
|
||||||
|
function VisitRecordLiteral(const Node: IRecordLiteralNode): Boolean; override;
|
||||||
|
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): Boolean; override;
|
||||||
|
function VisitSeriesLength(const Node: ISeriesLengthNode): Boolean; override;
|
||||||
|
|
||||||
|
// --- Critical Checks ---
|
||||||
|
function VisitIdentifier(const Node: IIdentifierNode): Boolean; override;
|
||||||
|
function VisitFunctionCall(const Node: IFunctionCallNode): Boolean; override;
|
||||||
|
function VisitRecurNode(const Node: IRecurNode): Boolean; override;
|
||||||
|
|
||||||
|
// --- Forbidden Constructs (Side Effects / Unsafe) ---
|
||||||
|
function VisitAssignment(const Node: IAssignmentNode): Boolean; override;
|
||||||
|
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): Boolean; override;
|
||||||
|
|
||||||
|
// Allocation is considered pure in this context (it creates a new value, doesn't mutate existing world)
|
||||||
|
function VisitCreateSeries(const Node: ICreateSeriesNode): Boolean; override;
|
||||||
|
|
||||||
|
function VisitIndexer(const Node: IIndexerNode): Boolean; override;
|
||||||
|
function VisitMemberAccess(const Node: IMemberAccessNode): Boolean; override;
|
||||||
|
|
||||||
|
// Ignored / Irrelevant for Runtime Purity (Compile-time constructs or Definitions)
|
||||||
|
function VisitLambdaExpression(const Node: ILambdaExpressionNode): Boolean; override;
|
||||||
|
function VisitMacroDefinition(const Node: IMacroDefinitionNode): Boolean; override;
|
||||||
|
function VisitQuasiquote(const Node: IQuasiquoteNode): Boolean; override;
|
||||||
|
function VisitUnquote(const Node: IUnquoteNode): Boolean; override;
|
||||||
|
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): Boolean; override;
|
||||||
|
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): Boolean; override;
|
||||||
|
function VisitNop(const Node: INopNode): Boolean; override;
|
||||||
|
|
||||||
|
public
|
||||||
|
class function IsPure(const RootNode: IAstNode): Boolean;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TPurityAnalyzer }
|
||||||
|
|
||||||
|
class function TPurityAnalyzer.IsPure(const RootNode: IAstNode): Boolean;
|
||||||
|
begin
|
||||||
|
var analyzer := TPurityAnalyzer.Create;
|
||||||
|
try
|
||||||
|
Result := analyzer.Accept(RootNode);
|
||||||
|
finally
|
||||||
|
analyzer.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.Accept(const Node: IAstNode): Boolean;
|
||||||
|
begin
|
||||||
|
// If a node is nil (e.g. optional else-branch), it's "nothing", which is pure.
|
||||||
|
if not Assigned(Node) then
|
||||||
|
exit(True);
|
||||||
|
|
||||||
|
// Dispatch to specific Visit method via generic base
|
||||||
|
Result := Node.Accept(Self).AsGeneric<Boolean>;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Safe Leaves ---
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitConstant(const Node: IConstantNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitKeyword(const Node: IKeywordNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitNop(const Node: INopNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Identifier: Only local variables are safe ---
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitIdentifier(const Node: IIdentifierNode): Boolean;
|
||||||
|
begin
|
||||||
|
// We only allow access to local variables (ScopeDepth = 0).
|
||||||
|
// Accessing Parent/Upvalues (ScopeDepth > 0) makes the function state-dependent (closure state),
|
||||||
|
// effectively impure regarding referential transparency across different closure instances,
|
||||||
|
// unless we could prove the upvalue is constant (which we don't track yet).
|
||||||
|
|
||||||
|
// Note: Parameters are also ScopeDepth=0 in the Binder logic.
|
||||||
|
Result := (Node.Address.Kind = akLocalOrParent) and (Node.Address.ScopeDepth = 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Function Call: The Core Logic ---
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitFunctionCall(const Node: IFunctionCallNode): Boolean;
|
||||||
|
begin
|
||||||
|
// 1. The target function MUST be marked as Pure (from RTL or previous inference).
|
||||||
|
if not Node.IsTargetPure then
|
||||||
|
exit(False);
|
||||||
|
|
||||||
|
// 2. All arguments must be pure expressions.
|
||||||
|
for var arg in Node.Arguments do
|
||||||
|
if not Accept(arg) then
|
||||||
|
exit(False);
|
||||||
|
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Recursion ---
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitRecurNode(const Node: IRecurNode): Boolean;
|
||||||
|
begin
|
||||||
|
// 'recur' is just control flow. It is pure if its arguments are pure.
|
||||||
|
for var arg in Node.Arguments do
|
||||||
|
if not Accept(arg) then
|
||||||
|
exit(False);
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Structures: Recursive Checks ---
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitIfExpression(const Node: IIfExpressionNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := Accept(Node.Condition) and Accept(Node.ThenBranch) and Accept(Node.ElseBranch);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitTernaryExpression(const Node: ITernaryExpressionNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := Accept(Node.Condition) and Accept(Node.ThenBranch) and Accept(Node.ElseBranch);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitBlockExpression(const Node: IBlockExpressionNode): Boolean;
|
||||||
|
begin
|
||||||
|
for var expr in Node.Expressions do
|
||||||
|
if not Accept(expr) then
|
||||||
|
exit(False);
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): Boolean;
|
||||||
|
begin
|
||||||
|
// 'def x = ...' is locally pure if the initializer is pure.
|
||||||
|
// It mutates the local scope (stack), but that is contained within the function execution.
|
||||||
|
Result := Accept(Node.Initializer);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitRecordLiteral(const Node: IRecordLiteralNode): Boolean;
|
||||||
|
begin
|
||||||
|
for var field in Node.Fields do
|
||||||
|
if not Accept(field.Value) then
|
||||||
|
exit(False);
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitIndexer(const Node: IIndexerNode): Boolean;
|
||||||
|
begin
|
||||||
|
// Reading from a structure is pure if the indices/base are pure.
|
||||||
|
Result := Accept(Node.Base) and Accept(Node.Index);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitMemberAccess(const Node: IMemberAccessNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := Accept(Node.Base);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitSeriesLength(const Node: ISeriesLengthNode): Boolean;
|
||||||
|
begin
|
||||||
|
// Querying length is pure.
|
||||||
|
// The series identifier check happens in VisitIdentifier.
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Forbidden (Impure) ---
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitAssignment(const Node: IAssignmentNode): Boolean;
|
||||||
|
begin
|
||||||
|
// Mutation of variables is defined as impure.
|
||||||
|
Result := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): Boolean;
|
||||||
|
begin
|
||||||
|
// Mutation of a series (side effect).
|
||||||
|
Result := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitCreateSeries(const Node: ICreateSeriesNode): Boolean;
|
||||||
|
begin
|
||||||
|
// Creating a NEW object is considered pure in this context,
|
||||||
|
// as it does not mutate existing global state.
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Irrelevant / Nested (Definitions are pure, execution logic checked separately) ---
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitLambdaExpression(const Node: ILambdaExpressionNode): Boolean;
|
||||||
|
begin
|
||||||
|
// Defining a function is a pure operation.
|
||||||
|
// Whether the function itself is pure when executed is determined when *that* function is compiled.
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitMacroDefinition(const Node: IMacroDefinitionNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := True; // Compile-time construct
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitQuasiquote(const Node: IQuasiquoteNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := True; // Structural construction
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitUnquote(const Node: IUnquoteNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := Accept(Node.Expression);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): Boolean;
|
||||||
|
begin
|
||||||
|
Result := Accept(Node.Expression);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPurityAnalyzer.VisitMacroExpansionNode(const Node: IMacroExpansionNode): Boolean;
|
||||||
|
begin
|
||||||
|
// We analyze the already expanded body.
|
||||||
|
Result := Accept(Node.ExpandedBody);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -368,7 +368,7 @@ begin
|
|||||||
// If FLambdaCounter increased by more than just 1 (myself), children were visited.
|
// If FLambdaCounter increased by more than just 1 (myself), children were visited.
|
||||||
hasNested := FLambdaCounter > (startCount + 1);
|
hasNested := FLambdaCounter > (startCount + 1);
|
||||||
|
|
||||||
Result := TAst.LambdaExpr(newParams, newBody, finalLayout, nil, upvaluesList, hasNested);
|
Result := TAst.LambdaExpr(newParams, newBody, finalLayout, nil, upvaluesList, hasNested, Node.IsPure);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstBinder.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
function TAstBinder.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ begin
|
|||||||
|
|
||||||
newBody := Accept(Node.Body);
|
newBody := Accept(Node.Body);
|
||||||
|
|
||||||
Result := TAst.LambdaExpr(newParams, newBody, nil, nil, nil, False, TTypes.Unknown);
|
Result := TAst.LambdaExpr(newParams, newBody, nil, nil, nil, False, Node.IsPure, TTypes.Unknown);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type
|
|||||||
// This transformer runs *after* TypeChecker.
|
// This transformer runs *after* TypeChecker.
|
||||||
// It specializes all statically resolvable function calls (RTL and user-defined)
|
// It specializes all statically resolvable function calls (RTL and user-defined)
|
||||||
// by replacing them with nodes that have a direct StaticTarget.
|
// by replacing them with nodes that have a direct StaticTarget.
|
||||||
|
// It propagates Purity information but does NOT perform Constant Folding yet.
|
||||||
TStaticSpecializer = class(TAstTransformer, IAstSpecializer)
|
TStaticSpecializer = class(TAstTransformer, IAstSpecializer)
|
||||||
private
|
private
|
||||||
FEnvironment: IEnvironment;
|
FEnvironment: IEnvironment;
|
||||||
@@ -35,7 +36,6 @@ type
|
|||||||
constructor Create(const AEnvironment: IEnvironment);
|
constructor Create(const AEnvironment: IEnvironment);
|
||||||
function Execute(const RootNode: IAstNode): IAstNode;
|
function Execute(const RootNode: IAstNode): IAstNode;
|
||||||
|
|
||||||
// Descriptor removed from signature as it is not needed for specialization
|
|
||||||
class function Specialize(const AEnvironment: IEnvironment; const RootNode: IAstNode): IAstNode; static;
|
class function Specialize(const AEnvironment: IEnvironment; const RootNode: IAstNode): IAstNode; static;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -128,7 +128,16 @@ begin
|
|||||||
if FEnvironment.MonomorphCache.TryGetValue(key, specializedMethod) then
|
if FEnvironment.MonomorphCache.TryGetValue(key, specializedMethod) then
|
||||||
begin
|
begin
|
||||||
// 4a. Cache Hit (Environment)
|
// 4a. Cache Hit (Environment)
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, specializedMethod.ReturnType, Node.IsTailCall, specializedMethod.Target);
|
// Propagate IsPure flag from cache to AST node
|
||||||
|
Result :=
|
||||||
|
TAst.FunctionCall(
|
||||||
|
newCallee,
|
||||||
|
newArgs,
|
||||||
|
specializedMethod.ReturnType,
|
||||||
|
Node.IsTailCall,
|
||||||
|
specializedMethod.Target,
|
||||||
|
specializedMethod.IsPure
|
||||||
|
);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -139,7 +148,16 @@ begin
|
|||||||
// 5a. Cache Hit (RTL)
|
// 5a. Cache Hit (RTL)
|
||||||
FEnvironment.MonomorphCache.Add(key, specializedMethod);
|
FEnvironment.MonomorphCache.Add(key, specializedMethod);
|
||||||
|
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, specializedMethod.ReturnType, Node.IsTailCall, specializedMethod.Target);
|
// Propagate IsPure flag from RTL definition to AST node
|
||||||
|
Result :=
|
||||||
|
TAst.FunctionCall(
|
||||||
|
newCallee,
|
||||||
|
newArgs,
|
||||||
|
specializedMethod.ReturnType,
|
||||||
|
Node.IsTailCall,
|
||||||
|
specializedMethod.Target,
|
||||||
|
specializedMethod.IsPure
|
||||||
|
);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -148,7 +166,7 @@ begin
|
|||||||
|
|
||||||
if (funcDef <> nil) then
|
if (funcDef <> nil) then
|
||||||
begin
|
begin
|
||||||
// Cannot specialize closures safely without more complex analysis
|
// Cannot specialize closures safely without more complex analysis if they have state
|
||||||
if funcDef.Kind = akLambdaExpression then
|
if funcDef.Kind = akLambdaExpression then
|
||||||
begin
|
begin
|
||||||
var lambdaDef := funcDef.AsLambdaExpression;
|
var lambdaDef := funcDef.AsLambdaExpression;
|
||||||
@@ -160,17 +178,18 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// 6a. Compile func with KNOWN TYPES
|
// 6a. Compile func with KNOWN TYPES
|
||||||
// This recursively triggers Bind -> Check -> Specialize for the callee body!
|
// This recursively triggers Bind -> Check -> Specialize -> Purity Inference for the callee body!
|
||||||
var compiled := FEnvironment.Compile(funcDef, argTypes);
|
var compiled := FEnvironment.Compile(funcDef, argTypes);
|
||||||
|
|
||||||
// 6b. Store in cache
|
// 6b. Store in cache
|
||||||
// Get the return type from the *full function type* returned by Compile
|
// Get the return type from the *full function type* returned by Compile
|
||||||
var returnType := compiled.StaticType.Signatures[0].ReturnType;
|
var returnType := compiled.StaticType.Signatures[0].ReturnType;
|
||||||
specializedMethod := TSpecializedMethod.Create(compiled.Func, returnType);
|
specializedMethod := TSpecializedMethod.Create(compiled.Func, returnType, compiled.IsPure);
|
||||||
FEnvironment.MonomorphCache.Add(key, specializedMethod);
|
FEnvironment.MonomorphCache.Add(key, specializedMethod);
|
||||||
|
|
||||||
// 6c. Return the new node
|
// 6c. Return the new node
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, returnType, Node.IsTailCall, compiled.Func);
|
// Propagate the inferred IsPure flag to the AST node
|
||||||
|
Result := TAst.FunctionCall(newCallee, newArgs, returnType, Node.IsTailCall, compiled.Func, compiled.IsPure);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -180,7 +180,18 @@ begin
|
|||||||
begin
|
begin
|
||||||
// Rebuild using factory.
|
// Rebuild using factory.
|
||||||
// IMPORTANT: Pass Layout AND Descriptor (TCO runs after TypeChecker).
|
// IMPORTANT: Pass Layout AND Descriptor (TCO runs after TypeChecker).
|
||||||
Result := TAst.LambdaExpr(newParams, newBody, Node.Layout, Node.Descriptor, Node.Upvalues, Node.HasNestedLambdas, Node.StaticType);
|
// (* FIX: Pass Node.IsPure to preserve it *)
|
||||||
|
Result :=
|
||||||
|
TAst.LambdaExpr(
|
||||||
|
newParams,
|
||||||
|
newBody,
|
||||||
|
Node.Layout,
|
||||||
|
Node.Descriptor,
|
||||||
|
Node.Upvalues,
|
||||||
|
Node.HasNestedLambdas,
|
||||||
|
Node.IsPure,
|
||||||
|
Node.StaticType
|
||||||
|
);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -242,7 +253,8 @@ begin
|
|||||||
newArgs,
|
newArgs,
|
||||||
Node.StaticType,
|
Node.StaticType,
|
||||||
isTailCall,
|
isTailCall,
|
||||||
Node.StaticTarget // Preserve the static target from Specializer
|
Node.StaticTarget, // Preserve the static target from Specializer
|
||||||
|
Node.IsTargetPure // (* FIX: Preserve the purity flag from Specializer! *)
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -400,7 +400,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// 9. Create new node with the Descriptor attached!
|
// 9. Create new node with the Descriptor attached!
|
||||||
Result := TAst.LambdaExpr(newParams, newBody, Node.Layout, finalDescriptor, Node.Upvalues, Node.HasNestedLambdas, methodType);
|
Result :=
|
||||||
|
TAst.LambdaExpr(newParams, newBody, Node.Layout, finalDescriptor, Node.Upvalues, Node.HasNestedLambdas, Node.IsPure, methodType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
function TTypeChecker.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
||||||
|
|||||||
@@ -201,7 +201,12 @@ var
|
|||||||
slot: Integer;
|
slot: Integer;
|
||||||
typ: IStaticType;
|
typ: IStaticType;
|
||||||
begin
|
begin
|
||||||
LogFmt('LambdaExpression (HasNested: %s)', [Node.HasNestedLambdas.ToString(TUseBoolStrs.True)], Node);
|
// (* UPDATED: Added IsPure output *)
|
||||||
|
LogFmt(
|
||||||
|
'LambdaExpression (HasNested: %s, IsPure: %s)',
|
||||||
|
[Node.HasNestedLambdas.ToString(TUseBoolStrs.True), BoolToStr(Node.IsPure, True)],
|
||||||
|
Node
|
||||||
|
);
|
||||||
Indent;
|
Indent;
|
||||||
|
|
||||||
// 1. Layout & Symbols
|
// 1. Layout & Symbols
|
||||||
@@ -277,7 +282,12 @@ begin
|
|||||||
else
|
else
|
||||||
staticStatus := 'nil';
|
staticStatus := 'nil';
|
||||||
|
|
||||||
LogFmt('FunctionCall (IsTailCall: %s, StaticTarget: %s%s)', [Node.IsTailCall.ToString(TUseBoolStrs.True), staticStatus, sigStr], Node);
|
// (* UPDATED: Added IsTargetPure output *)
|
||||||
|
LogFmt(
|
||||||
|
'FunctionCall (IsTailCall: %s, StaticTarget: %s%s, IsTargetPure: %s)',
|
||||||
|
[Node.IsTailCall.ToString(TUseBoolStrs.True), staticStatus, sigStr, BoolToStr(Node.IsTargetPure, True)],
|
||||||
|
Node
|
||||||
|
);
|
||||||
|
|
||||||
Indent;
|
Indent;
|
||||||
Log('Callee:');
|
Log('Callee:');
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ type
|
|||||||
public
|
public
|
||||||
Func: TDataValue.TFunc;
|
Func: TDataValue.TFunc;
|
||||||
StaticType: IStaticType;
|
StaticType: IStaticType;
|
||||||
constructor Create(const AFunc: TDataValue.TFunc; const AStaticType: IStaticType);
|
IsPure: Boolean;
|
||||||
|
constructor Create(const AFunc: TDataValue.TFunc; const AStaticType: IStaticType; AIsPure: Boolean);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
IExecutionStrategy = interface
|
IExecutionStrategy = interface
|
||||||
@@ -143,7 +144,8 @@ uses
|
|||||||
Myc.Ast.Compiler.Binder,
|
Myc.Ast.Compiler.Binder,
|
||||||
Myc.Ast.Compiler.TypeChecker,
|
Myc.Ast.Compiler.TypeChecker,
|
||||||
Myc.Ast.Compiler.Specializer,
|
Myc.Ast.Compiler.Specializer,
|
||||||
Myc.Ast.Compiler.TCO;
|
Myc.Ast.Compiler.TCO,
|
||||||
|
Myc.Ast.Analysis.Purity;
|
||||||
|
|
||||||
{ TMonoCacheKey }
|
{ TMonoCacheKey }
|
||||||
|
|
||||||
@@ -202,10 +204,11 @@ end;
|
|||||||
|
|
||||||
{ TCompiledFunction }
|
{ TCompiledFunction }
|
||||||
|
|
||||||
constructor TCompiledFunction.Create(const AFunc: TDataValue.TFunc; const AStaticType: IStaticType);
|
constructor TCompiledFunction.Create(const AFunc: TDataValue.TFunc; const AStaticType: IStaticType; AIsPure: Boolean);
|
||||||
begin
|
begin
|
||||||
Func := AFunc;
|
Func := AFunc;
|
||||||
StaticType := AStaticType;
|
StaticType := AStaticType;
|
||||||
|
IsPure := AIsPure;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -344,6 +347,8 @@ end;
|
|||||||
procedure TAstEnvironment.Define(const Name: String; const AScript: IAstNode);
|
procedure TAstEnvironment.Define(const Name: String; const AScript: IAstNode);
|
||||||
begin
|
begin
|
||||||
var compiled := Compile(AScript);
|
var compiled := Compile(AScript);
|
||||||
|
// Note: Define now potentially handles Pure flags internally via the Node metadata,
|
||||||
|
// but the runtime value is just the TDataValue.
|
||||||
RootScope.Define(Name, compiled.Func([]), compiled.StaticType);
|
RootScope.Define(Name, compiled.Func([]), compiled.StaticType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -526,6 +531,7 @@ var
|
|||||||
typedNode: IAstNode;
|
typedNode: IAstNode;
|
||||||
specialized: IAstNode;
|
specialized: IAstNode;
|
||||||
tcoOptimized: IAstNode;
|
tcoOptimized: IAstNode;
|
||||||
|
isPure: Boolean;
|
||||||
begin
|
begin
|
||||||
// 0. Wrap in Lambda to create an isolatable compilation unit (scope)
|
// 0. Wrap in Lambda to create an isolatable compilation unit (scope)
|
||||||
var prg := TAst.LambdaExpr(Params, Node);
|
var prg := TAst.LambdaExpr(Params, Node);
|
||||||
@@ -549,25 +555,27 @@ begin
|
|||||||
// 4. Optimize (TCO)
|
// 4. Optimize (TCO)
|
||||||
tcoOptimized := TAstTCO.Optimize(specialized);
|
tcoOptimized := TAstTCO.Optimize(specialized);
|
||||||
|
|
||||||
// 5. Create Evaluator
|
// 5. Purity Inference
|
||||||
// We create a scope based on the descriptor to run the lambda body.
|
// Check the body of the optimized lambda for purity.
|
||||||
// The lambda itself (as a node) is evaluated to produce a closure.
|
// Note: tcoOptimized is a LambdaExpressionNode. Its body is what we check.
|
||||||
// Since we wrapped the user code in a LambdaExpr, evaluating 'tcoOptimized'
|
isPure := TPurityAnalyzer.IsPure(tcoOptimized.AsLambdaExpression.Body);
|
||||||
// returns the closure (TFunc).
|
|
||||||
|
// 6. Create Evaluator
|
||||||
var visitor := FExecutionStrategy.CreateVisitor(descriptor.CreateScope(FRootScope));
|
var visitor := FExecutionStrategy.CreateVisitor(descriptor.CreateScope(FRootScope));
|
||||||
|
|
||||||
// Execute the AST to get the TFunc (Closure)
|
// Execute the AST to get the TFunc (Closure)
|
||||||
var closure := tcoOptimized.Accept(visitor);
|
var closure := tcoOptimized.Accept(visitor).AsMethod();
|
||||||
|
|
||||||
// Wrap TCO handling for the final result
|
// Wrap TCO handling for the final result
|
||||||
finalFunc :=
|
finalFunc :=
|
||||||
function(const Args: TArray<TDataValue>): TDataValue
|
function(const Args: TArray<TDataValue>): TDataValue
|
||||||
begin
|
begin
|
||||||
Result := closure.AsMethod()(Args);
|
Result := closure(Args);
|
||||||
TEvaluatorVisitor.HandleTCO(Result);
|
TEvaluatorVisitor.HandleTCO(Result);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TCompiledFunction.Create(finalFunc, funcType);
|
// Pass IsPure flag to the compiled function record
|
||||||
|
Result := TCompiledFunction.Create(finalFunc, funcType, isPure);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TEnvironment.Compile(const Node: IFunctionDefinition; const ArgTypes: TArray<IStaticType>): TCompiledFunction;
|
function TEnvironment.Compile(const Node: IFunctionDefinition; const ArgTypes: TArray<IStaticType>): TCompiledFunction;
|
||||||
@@ -578,6 +586,7 @@ var
|
|||||||
finalFunc: TDataValue.TFunc;
|
finalFunc: TDataValue.TFunc;
|
||||||
|
|
||||||
typedNode, specialized, tcoOptimized: IAstNode;
|
typedNode, specialized, tcoOptimized: IAstNode;
|
||||||
|
isPure: Boolean;
|
||||||
begin
|
begin
|
||||||
// Same steps as above, but Node is already a definition (Lambda)
|
// Same steps as above, but Node is already a definition (Lambda)
|
||||||
var expanded := ExpandMacros(Node);
|
var expanded := ExpandMacros(Node);
|
||||||
@@ -590,6 +599,9 @@ begin
|
|||||||
specialized := Specialize(typedNode);
|
specialized := Specialize(typedNode);
|
||||||
tcoOptimized := TAstTCO.Optimize(specialized);
|
tcoOptimized := TAstTCO.Optimize(specialized);
|
||||||
|
|
||||||
|
// Purity Inference
|
||||||
|
isPure := TPurityAnalyzer.IsPure(tcoOptimized.AsLambdaExpression.Body);
|
||||||
|
|
||||||
var visitor := FExecutionStrategy.CreateVisitor(descriptor.CreateScope(FRootScope));
|
var visitor := FExecutionStrategy.CreateVisitor(descriptor.CreateScope(FRootScope));
|
||||||
var closure := tcoOptimized.Accept(visitor);
|
var closure := tcoOptimized.Accept(visitor);
|
||||||
|
|
||||||
@@ -600,7 +612,7 @@ begin
|
|||||||
TEvaluatorVisitor.HandleTCO(Result);
|
TEvaluatorVisitor.HandleTCO(Result);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TCompiledFunction.Create(finalFunc, funcType);
|
Result := TCompiledFunction.Create(finalFunc, funcType, isPure);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TEnvironment.ExpandMacros(const Node: IAstNode): IAstNode;
|
function TEnvironment.ExpandMacros(const Node: IAstNode): IAstNode;
|
||||||
|
|||||||
@@ -158,9 +158,11 @@ type
|
|||||||
{$region 'private'}
|
{$region 'private'}
|
||||||
function GetBody: IAstNode;
|
function GetBody: IAstNode;
|
||||||
function GetParameters: TArray<IIdentifierNode>;
|
function GetParameters: TArray<IIdentifierNode>;
|
||||||
|
function GetIsPure: Boolean; // (* ADDED *)
|
||||||
{$endregion}
|
{$endregion}
|
||||||
property Body: IAstNode read GetBody;
|
property Body: IAstNode read GetBody;
|
||||||
property Parameters: TArray<IIdentifierNode> read GetParameters;
|
property Parameters: TArray<IIdentifierNode> read GetParameters;
|
||||||
|
property IsPure: Boolean read GetIsPure; // (* ADDED *)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
INopNode = interface(IAstTypedNode)
|
INopNode = interface(IAstTypedNode)
|
||||||
@@ -228,6 +230,7 @@ type
|
|||||||
|
|
||||||
function GetUpvalues: TArray<TResolvedAddress>;
|
function GetUpvalues: TArray<TResolvedAddress>;
|
||||||
function GetHasNestedLambdas: Boolean;
|
function GetHasNestedLambdas: Boolean;
|
||||||
|
// GetIsPure is inherited from IFunctionDefinition
|
||||||
{$endregion}
|
{$endregion}
|
||||||
|
|
||||||
property Parameters: TArray<IIdentifierNode> read GetParameters;
|
property Parameters: TArray<IIdentifierNode> read GetParameters;
|
||||||
@@ -238,6 +241,7 @@ type
|
|||||||
|
|
||||||
property Upvalues: TArray<TResolvedAddress> read GetUpvalues;
|
property Upvalues: TArray<TResolvedAddress> read GetUpvalues;
|
||||||
property HasNestedLambdas: Boolean read GetHasNestedLambdas;
|
property HasNestedLambdas: Boolean read GetHasNestedLambdas;
|
||||||
|
// IsPure is inherited
|
||||||
end;
|
end;
|
||||||
|
|
||||||
IFunctionCallNode = interface(IAstTypedNode)
|
IFunctionCallNode = interface(IAstTypedNode)
|
||||||
@@ -246,11 +250,13 @@ type
|
|||||||
function GetArguments: TArray<IAstNode>;
|
function GetArguments: TArray<IAstNode>;
|
||||||
function GetIsTailCall: Boolean;
|
function GetIsTailCall: Boolean;
|
||||||
function GetStaticTarget: TDataValue.TFunc;
|
function GetStaticTarget: TDataValue.TFunc;
|
||||||
|
function GetIsTargetPure: Boolean; // (* ADDED *)
|
||||||
{$endregion}
|
{$endregion}
|
||||||
property Callee: IAstNode read GetCallee;
|
property Callee: IAstNode read GetCallee;
|
||||||
property Arguments: TArray<IAstNode> read GetArguments;
|
property Arguments: TArray<IAstNode> read GetArguments;
|
||||||
property IsTailCall: Boolean read GetIsTailCall;
|
property IsTailCall: Boolean read GetIsTailCall;
|
||||||
property StaticTarget: TDataValue.TFunc read GetStaticTarget;
|
property StaticTarget: TDataValue.TFunc read GetStaticTarget;
|
||||||
|
property IsTargetPure: Boolean read GetIsTargetPure; // (* ADDED *)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// A node representing a macro call.
|
// A node representing a macro call.
|
||||||
|
|||||||
+16
-15
@@ -47,11 +47,11 @@ type
|
|||||||
public
|
public
|
||||||
Target: TDataValue.TFunc;
|
Target: TDataValue.TFunc;
|
||||||
ReturnType: IStaticType;
|
ReturnType: IStaticType;
|
||||||
constructor Create(const ATarget: TDataValue.TFunc; const AReturnType: IStaticType);
|
IsPure: Boolean; // (* ADDED *)
|
||||||
|
constructor Create(const ATarget: TDataValue.TFunc; const AReturnType: IStaticType; AIsPure: Boolean);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// The cache holding TDataValue.TFunc wrappers for static native functions.
|
// The cache holding TDataValue.TFunc wrappers for static native functions.
|
||||||
// Verwendet den generischen TSpecializedMethod-Record aus Myc.Ast.Types
|
|
||||||
TStaticBootstrapCache = TDictionary<TStaticSignatureKey, TSpecializedMethod>;
|
TStaticBootstrapCache = TDictionary<TStaticSignatureKey, TSpecializedMethod>;
|
||||||
|
|
||||||
TRtlFunctionInfo = class
|
TRtlFunctionInfo = class
|
||||||
@@ -153,7 +153,7 @@ constructor TRtlExportAttribute.Create(const AName: string);
|
|||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
Self.Name := AName;
|
Self.Name := AName;
|
||||||
Self.IsPure := False; // Default to impure
|
Self.IsPure := False; // Default to impure (safe)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TRtlExportAttribute.Create(const AName: string; AIsPure: Boolean);
|
constructor TRtlExportAttribute.Create(const AName: string; AIsPure: Boolean);
|
||||||
@@ -230,6 +230,15 @@ begin
|
|||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TSpecializedMethod }
|
||||||
|
|
||||||
|
constructor TSpecializedMethod.Create(const ATarget: TDataValue.TFunc; const AReturnType: IStaticType; AIsPure: Boolean);
|
||||||
|
begin
|
||||||
|
Target := ATarget;
|
||||||
|
ReturnType := AReturnType;
|
||||||
|
IsPure := AIsPure;
|
||||||
|
end;
|
||||||
|
|
||||||
{ TRtlRegistry }
|
{ TRtlRegistry }
|
||||||
|
|
||||||
class constructor TRtlRegistry.Create;
|
class constructor TRtlRegistry.Create;
|
||||||
@@ -252,7 +261,7 @@ begin
|
|||||||
FStaticBootstrap := TStaticBootstrapCache.Create(TStaticSignatureKeyComparer.Create);
|
FStaticBootstrap := TStaticBootstrapCache.Create(TStaticSignatureKeyComparer.Create);
|
||||||
FStaticFuncMap := TRtlFunctionMap.Create;
|
FStaticFuncMap := TRtlFunctionMap.Create;
|
||||||
|
|
||||||
// 2. Perform RTTI Scan (formerly Pass 1 of RegisterAll)
|
// 2. Perform RTTI Scan
|
||||||
ctx := TRttiContext.Create;
|
ctx := TRttiContext.Create;
|
||||||
try
|
try
|
||||||
rtlType := ctx.GetType(TypeInfo(TRtlFunctions));
|
rtlType := ctx.GetType(TypeInfo(TRtlFunctions));
|
||||||
@@ -292,7 +301,7 @@ begin
|
|||||||
|
|
||||||
if isDynamic then
|
if isDynamic then
|
||||||
begin
|
begin
|
||||||
if Assigned(funcInfo.DynamicWrapper) and Assigned(funcInfo.DynamicWrapper) then
|
if Assigned(funcInfo.DynamicWrapper) then
|
||||||
raise EInvalidOpException.CreateFmt('Duplicate dynamic fallback defined for %s', [rtlName]);
|
raise EInvalidOpException.CreateFmt('Duplicate dynamic fallback defined for %s', [rtlName]);
|
||||||
|
|
||||||
var wrapperFactory :=
|
var wrapperFactory :=
|
||||||
@@ -323,8 +332,8 @@ begin
|
|||||||
var wrapper := CreateStaticWrapper(method, retType, argTypes, rttiParams);
|
var wrapper := CreateStaticWrapper(method, retType, argTypes, rttiParams);
|
||||||
if Assigned(wrapper) then
|
if Assigned(wrapper) then
|
||||||
begin
|
begin
|
||||||
methodRecord := TSpecializedMethod.Create(wrapper, retType);
|
// (* UPDATED: Capture IsPure from attribute *)
|
||||||
// Use renamed static helper
|
methodRecord := TSpecializedMethod.Create(wrapper, retType, exportAttr.IsPure);
|
||||||
RegisterStaticSpecialization(rtlName, argTypes, methodRecord);
|
RegisterStaticSpecialization(rtlName, argTypes, methodRecord);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -366,8 +375,6 @@ var
|
|||||||
key: TStaticSignatureKey;
|
key: TStaticSignatureKey;
|
||||||
begin
|
begin
|
||||||
key := TStaticSignatureKey.Create(AName, AArgTypes);
|
key := TStaticSignatureKey.Create(AName, AArgTypes);
|
||||||
// This allows multiple RTTI scans (e.g. in tests) without crashing,
|
|
||||||
// though only the class constructor should call this now.
|
|
||||||
FStaticBootstrap.AddOrSetValue(key, AMethod);
|
FStaticBootstrap.AddOrSetValue(key, AMethod);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -724,12 +731,6 @@ begin
|
|||||||
TRtlRegistry.RegisterAll(AScope);
|
TRtlRegistry.RegisterAll(AScope);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TSpecializedMethod.Create(const ATarget: TDataValue.TFunc; const AReturnType: IStaticType);
|
|
||||||
begin
|
|
||||||
Target := ATarget;
|
|
||||||
ReturnType := AReturnType;
|
|
||||||
end;
|
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
// Register this library's functions with the central AST factory.
|
// Register this library's functions with the central AST factory.
|
||||||
TAst.RegisterLibrary(RegisterRtlFunctions);
|
TAst.RegisterLibrary(RegisterRtlFunctions);
|
||||||
|
|||||||
@@ -486,7 +486,17 @@ begin
|
|||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Use TAst factory and copy properties via interface getters
|
// Use TAst factory and copy properties via interface getters
|
||||||
Result := TAst.LambdaExpr(newParams, newBody, Node.Layout, Node.Descriptor, Node.Upvalues, Node.HasNestedLambdas, Node.StaticType);
|
Result :=
|
||||||
|
TAst.LambdaExpr(
|
||||||
|
newParams,
|
||||||
|
newBody,
|
||||||
|
Node.Layout,
|
||||||
|
Node.Descriptor,
|
||||||
|
Node.Upvalues,
|
||||||
|
Node.HasNestedLambdas,
|
||||||
|
Node.IsPure,
|
||||||
|
Node.StaticType
|
||||||
|
);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
+39
-15
@@ -56,14 +56,15 @@ type
|
|||||||
const AStaticType: IStaticType = nil
|
const AStaticType: IStaticType = nil
|
||||||
): ITernaryExpressionNode; static;
|
): ITernaryExpressionNode; static;
|
||||||
|
|
||||||
// Factory Updated: Includes Layout (mandatory) and Descriptor (optional)
|
// (* UPDATED Factory: Added AIsPure *)
|
||||||
class function LambdaExpr(
|
class function LambdaExpr(
|
||||||
const AParameters: TArray<IIdentifierNode>;
|
const AParameters: TArray<IIdentifierNode>;
|
||||||
const ABody: IAstNode;
|
const ABody: IAstNode;
|
||||||
const ALayout: IScopeLayout = nil; // Nil allowed only for raw AST construction (before Binder)
|
const ALayout: IScopeLayout = nil;
|
||||||
const ADescriptor: IScopeDescriptor = nil; // Nil allowed before TypeChecker
|
const ADescriptor: IScopeDescriptor = nil;
|
||||||
const AUpvalues: TArray<TResolvedAddress> = nil;
|
const AUpvalues: TArray<TResolvedAddress> = nil;
|
||||||
const AHasNestedLambdas: Boolean = False;
|
const AHasNestedLambdas: Boolean = False;
|
||||||
|
const AIsPure: Boolean = False; // (* ADDED *)
|
||||||
const AStaticType: IStaticType = nil
|
const AStaticType: IStaticType = nil
|
||||||
): ILambdaExpressionNode; static;
|
): ILambdaExpressionNode; static;
|
||||||
|
|
||||||
@@ -76,12 +77,14 @@ type
|
|||||||
class function Unquote(const AExpression: IAstNode): IUnquoteNode; static;
|
class function Unquote(const AExpression: IAstNode): IUnquoteNode; static;
|
||||||
class function UnquoteSplicing(const AExpression: IQuasiquoteNode): IUnquoteSplicingNode; static;
|
class function UnquoteSplicing(const AExpression: IQuasiquoteNode): IUnquoteSplicingNode; static;
|
||||||
|
|
||||||
|
// (* UPDATED Factory: Added AIsTargetPure *)
|
||||||
class function FunctionCall(
|
class function FunctionCall(
|
||||||
const ACallee: IAstNode;
|
const ACallee: IAstNode;
|
||||||
const AArguments: TArray<IAstNode>;
|
const AArguments: TArray<IAstNode>;
|
||||||
const AStaticType: IStaticType = nil;
|
const AStaticType: IStaticType = nil;
|
||||||
const AIsTailCall: Boolean = False;
|
const AIsTailCall: Boolean = False;
|
||||||
const AStaticTarget: TDataValue.TFunc = nil
|
const AStaticTarget: TDataValue.TFunc = nil;
|
||||||
|
const AIsTargetPure: Boolean = False // (* ADDED *)
|
||||||
): IFunctionCallNode; static;
|
): IFunctionCallNode; static;
|
||||||
|
|
||||||
class function MacroExpansionNode(
|
class function MacroExpansionNode(
|
||||||
@@ -194,6 +197,7 @@ type
|
|||||||
FDescriptor: IScopeDescriptor;
|
FDescriptor: IScopeDescriptor;
|
||||||
FUpvalues: TArray<TResolvedAddress>;
|
FUpvalues: TArray<TResolvedAddress>;
|
||||||
FHasNestedLambdas: Boolean;
|
FHasNestedLambdas: Boolean;
|
||||||
|
FIsPure: Boolean; // (* ADDED *)
|
||||||
|
|
||||||
function GetParameters: TArray<IIdentifierNode>;
|
function GetParameters: TArray<IIdentifierNode>;
|
||||||
function GetBody: IAstNode;
|
function GetBody: IAstNode;
|
||||||
@@ -201,6 +205,7 @@ type
|
|||||||
function GetDescriptor: IScopeDescriptor;
|
function GetDescriptor: IScopeDescriptor;
|
||||||
function GetUpvalues: TArray<TResolvedAddress>;
|
function GetUpvalues: TArray<TResolvedAddress>;
|
||||||
function GetHasNestedLambdas: Boolean;
|
function GetHasNestedLambdas: Boolean;
|
||||||
|
function GetIsPure: Boolean; // (* ADDED *)
|
||||||
function GetKind: TAstNodeKind; override;
|
function GetKind: TAstNodeKind; override;
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
@@ -210,7 +215,8 @@ type
|
|||||||
const ALayout: IScopeLayout;
|
const ALayout: IScopeLayout;
|
||||||
const ADescriptor: IScopeDescriptor;
|
const ADescriptor: IScopeDescriptor;
|
||||||
const AUpvalues: TArray<TResolvedAddress>;
|
const AUpvalues: TArray<TResolvedAddress>;
|
||||||
const AHasNestedLambdas: Boolean
|
const AHasNestedLambdas: Boolean;
|
||||||
|
const AIsPure: Boolean // (* ADDED *)
|
||||||
);
|
);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
||||||
@@ -223,10 +229,12 @@ type
|
|||||||
FArguments: TArray<IAstNode>;
|
FArguments: TArray<IAstNode>;
|
||||||
FIsTailCall: Boolean;
|
FIsTailCall: Boolean;
|
||||||
FStaticTarget: TDataValue.TFunc;
|
FStaticTarget: TDataValue.TFunc;
|
||||||
|
FIsTargetPure: Boolean; // (* ADDED *)
|
||||||
function GetCallee: IAstNode;
|
function GetCallee: IAstNode;
|
||||||
function GetArguments: TArray<IAstNode>;
|
function GetArguments: TArray<IAstNode>;
|
||||||
function GetIsTailCall: Boolean;
|
function GetIsTailCall: Boolean;
|
||||||
function GetStaticTarget: TDataValue.TFunc;
|
function GetStaticTarget: TDataValue.TFunc;
|
||||||
|
function GetIsTargetPure: Boolean; // (* ADDED *)
|
||||||
function GetKind: TAstNodeKind; override;
|
function GetKind: TAstNodeKind; override;
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
@@ -234,12 +242,14 @@ type
|
|||||||
const AArguments: TArray<IAstNode>;
|
const AArguments: TArray<IAstNode>;
|
||||||
const AStaticType: IStaticType;
|
const AStaticType: IStaticType;
|
||||||
const AIsTailCall: Boolean;
|
const AIsTailCall: Boolean;
|
||||||
const AStaticTarget: TDataValue.TFunc
|
const AStaticTarget: TDataValue.TFunc;
|
||||||
|
const AIsTargetPure: Boolean // (* ADDED *)
|
||||||
);
|
);
|
||||||
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
||||||
function AsFunctionCall: IFunctionCallNode; override;
|
function AsFunctionCall: IFunctionCallNode; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// ... (Other node definitions unchanged) ...
|
||||||
TVariableDeclarationNode = class(TAstTypedNode, IVariableDeclarationNode)
|
TVariableDeclarationNode = class(TAstTypedNode, IVariableDeclarationNode)
|
||||||
private
|
private
|
||||||
FIdentifier: IIdentifierNode;
|
FIdentifier: IIdentifierNode;
|
||||||
@@ -260,8 +270,6 @@ type
|
|||||||
function AsVariableDeclaration: IVariableDeclarationNode; override;
|
function AsVariableDeclaration: IVariableDeclarationNode; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- Other internal node classes ---
|
|
||||||
|
|
||||||
TConstantNode = class(TAstTypedNode, IConstantNode)
|
TConstantNode = class(TAstTypedNode, IConstantNode)
|
||||||
private
|
private
|
||||||
FValue: TDataValue;
|
FValue: TDataValue;
|
||||||
@@ -644,7 +652,6 @@ end;
|
|||||||
class function TAst.Constant(const AValue: TDataValue; const AStaticType: IStaticType = nil): IConstantNode;
|
class function TAst.Constant(const AValue: TDataValue; const AStaticType: IStaticType = nil): IConstantNode;
|
||||||
begin
|
begin
|
||||||
var constType := AStaticType;
|
var constType := AStaticType;
|
||||||
|
|
||||||
if constType = nil then
|
if constType = nil then
|
||||||
begin
|
begin
|
||||||
case AValue.Kind of
|
case AValue.Kind of
|
||||||
@@ -655,7 +662,6 @@ begin
|
|||||||
constType := TTypes.Unknown;
|
constType := TTypes.Unknown;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TConstantNode.Create(AValue, constType);
|
Result := TConstantNode.Create(AValue, constType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -684,7 +690,8 @@ class function TAst.FunctionCall(
|
|||||||
const AArguments: TArray<IAstNode>;
|
const AArguments: TArray<IAstNode>;
|
||||||
const AStaticType: IStaticType = nil;
|
const AStaticType: IStaticType = nil;
|
||||||
const AIsTailCall: Boolean = False;
|
const AIsTailCall: Boolean = False;
|
||||||
const AStaticTarget: TDataValue.TFunc = nil
|
const AStaticTarget: TDataValue.TFunc = nil;
|
||||||
|
const AIsTargetPure: Boolean = False // (* ADDED *)
|
||||||
): IFunctionCallNode;
|
): IFunctionCallNode;
|
||||||
begin
|
begin
|
||||||
Result :=
|
Result :=
|
||||||
@@ -694,7 +701,8 @@ begin
|
|||||||
if AStaticType <> nil then AStaticType
|
if AStaticType <> nil then AStaticType
|
||||||
else TTypes.Unknown,
|
else TTypes.Unknown,
|
||||||
AIsTailCall,
|
AIsTailCall,
|
||||||
AStaticTarget
|
AStaticTarget,
|
||||||
|
AIsTargetPure
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -759,6 +767,7 @@ class function TAst.LambdaExpr(
|
|||||||
const ADescriptor: IScopeDescriptor = nil;
|
const ADescriptor: IScopeDescriptor = nil;
|
||||||
const AUpvalues: TArray<TResolvedAddress> = nil;
|
const AUpvalues: TArray<TResolvedAddress> = nil;
|
||||||
const AHasNestedLambdas: Boolean = False;
|
const AHasNestedLambdas: Boolean = False;
|
||||||
|
const AIsPure: Boolean = False; // (* ADDED *)
|
||||||
const AStaticType: IStaticType = nil
|
const AStaticType: IStaticType = nil
|
||||||
): ILambdaExpressionNode;
|
): ILambdaExpressionNode;
|
||||||
begin
|
begin
|
||||||
@@ -771,7 +780,8 @@ begin
|
|||||||
ALayout,
|
ALayout,
|
||||||
ADescriptor,
|
ADescriptor,
|
||||||
AUpvalues,
|
AUpvalues,
|
||||||
AHasNestedLambdas
|
AHasNestedLambdas,
|
||||||
|
AIsPure
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1247,7 +1257,8 @@ constructor TLambdaExpressionNode.Create(
|
|||||||
const ALayout: IScopeLayout;
|
const ALayout: IScopeLayout;
|
||||||
const ADescriptor: IScopeDescriptor;
|
const ADescriptor: IScopeDescriptor;
|
||||||
const AUpvalues: TArray<TResolvedAddress>;
|
const AUpvalues: TArray<TResolvedAddress>;
|
||||||
const AHasNestedLambdas: Boolean
|
const AHasNestedLambdas: Boolean;
|
||||||
|
const AIsPure: Boolean // (* ADDED *)
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create(AStaticType);
|
inherited Create(AStaticType);
|
||||||
@@ -1257,6 +1268,7 @@ begin
|
|||||||
FDescriptor := ADescriptor;
|
FDescriptor := ADescriptor;
|
||||||
FUpvalues := AUpvalues;
|
FUpvalues := AUpvalues;
|
||||||
FHasNestedLambdas := AHasNestedLambdas;
|
FHasNestedLambdas := AHasNestedLambdas;
|
||||||
|
FIsPure := AIsPure;
|
||||||
|
|
||||||
// Consistency Check
|
// Consistency Check
|
||||||
if Assigned(FDescriptor) and (FDescriptor.Layout <> FLayout) then
|
if Assigned(FDescriptor) and (FDescriptor.Layout <> FLayout) then
|
||||||
@@ -1308,6 +1320,11 @@ begin
|
|||||||
Result := FHasNestedLambdas;
|
Result := FHasNestedLambdas;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TLambdaExpressionNode.GetIsPure: Boolean; // (* ADDED *)
|
||||||
|
begin
|
||||||
|
Result := FIsPure;
|
||||||
|
end;
|
||||||
|
|
||||||
function TLambdaExpressionNode.GetKind: TAstNodeKind;
|
function TLambdaExpressionNode.GetKind: TAstNodeKind;
|
||||||
begin
|
begin
|
||||||
Result := akLambdaExpression;
|
Result := akLambdaExpression;
|
||||||
@@ -1444,7 +1461,8 @@ constructor TFunctionCallNode.Create(
|
|||||||
const AArguments: TArray<IAstNode>;
|
const AArguments: TArray<IAstNode>;
|
||||||
const AStaticType: IStaticType;
|
const AStaticType: IStaticType;
|
||||||
const AIsTailCall: Boolean;
|
const AIsTailCall: Boolean;
|
||||||
const AStaticTarget: TDataValue.TFunc
|
const AStaticTarget: TDataValue.TFunc;
|
||||||
|
const AIsTargetPure: Boolean // (* ADDED *)
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create(AStaticType);
|
inherited Create(AStaticType);
|
||||||
@@ -1452,6 +1470,7 @@ begin
|
|||||||
FArguments := AArguments;
|
FArguments := AArguments;
|
||||||
FIsTailCall := AIsTailCall;
|
FIsTailCall := AIsTailCall;
|
||||||
FStaticTarget := AStaticTarget;
|
FStaticTarget := AStaticTarget;
|
||||||
|
FIsTargetPure := AIsTargetPure;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TFunctionCallNode.Accept(const Visitor: IAstVisitor): TDataValue;
|
function TFunctionCallNode.Accept(const Visitor: IAstVisitor): TDataValue;
|
||||||
@@ -1489,6 +1508,11 @@ begin
|
|||||||
Result := FStaticTarget;
|
Result := FStaticTarget;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TFunctionCallNode.GetIsTargetPure: Boolean; // (* ADDED *)
|
||||||
|
begin
|
||||||
|
Result := FIsTargetPure;
|
||||||
|
end;
|
||||||
|
|
||||||
{ TMacroExpansionNode }
|
{ TMacroExpansionNode }
|
||||||
|
|
||||||
constructor TMacroExpansionNode.Create(const ACallNode: IFunctionCallNode; const AExpandedBody: IAstNode);
|
constructor TMacroExpansionNode.Create(const ACallNode: IFunctionCallNode; const AExpandedBody: IAstNode);
|
||||||
|
|||||||
Reference in New Issue
Block a user