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.Specializer in '..\Src\AST\Myc.Ast.Compiler.Specializer.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}
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Compiler.Specializer.pas"/>
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Environment.pas"/>
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Analysis.Purity.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
@@ -212,6 +213,12 @@
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</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">
|
||||
<Platform Name="Win64">
|
||||
<RemoteName>ASTPlayground.exe</RemoteName>
|
||||
|
||||
@@ -197,7 +197,7 @@ object Form1: TForm1
|
||||
'Expanded'
|
||||
'Bound'
|
||||
'Specialized')
|
||||
ItemIndex = 0
|
||||
ItemIndex = 3
|
||||
Position.X = 672.000000000000000000
|
||||
Position.Y = 576.000000000000000000
|
||||
Size.Width = 104.000000000000000000
|
||||
|
||||
@@ -110,7 +110,7 @@ type
|
||||
procedure RTLListViewChange(Sender: TObject);
|
||||
private
|
||||
FCurrUnboundAst: IAstNode;
|
||||
FCurrExec: TDataValue.TFunc;
|
||||
FCurrExec: TCompiledFunction;
|
||||
FEnvironment: TAstEnvironment;
|
||||
FWorkspace: TAuraWorkspace;
|
||||
FScriptUpdate: Boolean;
|
||||
@@ -262,7 +262,7 @@ end;
|
||||
function TForm1.ExecuteAst(const ANode: IAstNode): TDataValue;
|
||||
begin
|
||||
FCurrUnboundAst := ANode;
|
||||
FCurrExec := nil; // Clear previous
|
||||
FCurrExec := Default(TCompiledFunction);
|
||||
|
||||
// 1. Set strategy based on UI
|
||||
if DebugBox.IsChecked then
|
||||
@@ -273,12 +273,21 @@ begin
|
||||
try
|
||||
// Wrap in Lambda to compile
|
||||
var funcDef := TAst.LambdaExpr([], ANode);
|
||||
FCurrExec := FEnvironment.Compile(funcDef).Func;
|
||||
Result := FCurrExec([]);
|
||||
FCurrExec := FEnvironment.Compile(funcDef);
|
||||
|
||||
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
|
||||
on E: Exception do
|
||||
begin
|
||||
FCurrExec.Func := nil;
|
||||
Memo1.Lines.Add('--- ERROR ---');
|
||||
Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
|
||||
Result := TDataValue.Void;
|
||||
@@ -645,7 +654,7 @@ begin
|
||||
FCurrUnboundAst := converter.Deserialize(jsonObj);
|
||||
|
||||
// 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('You can now visualize it (Middle Mouse Click) or pretty-print it.');
|
||||
@@ -655,7 +664,6 @@ begin
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
FCurrExec := nil;
|
||||
Memo1.Lines.Add('Error deserializing AST from JSON:');
|
||||
Memo1.Lines.Add(E.Message);
|
||||
Memo1.Lines.Add('--- Original JSON ---');
|
||||
@@ -675,7 +683,7 @@ var
|
||||
begin
|
||||
Memo1.Lines.Clear;
|
||||
|
||||
if not Assigned(FCurrExec) then
|
||||
if not Assigned(FCurrExec.Func) then
|
||||
begin
|
||||
Memo1.Lines.Add('No AST available to serialize. Please generate one first.');
|
||||
exit;
|
||||
@@ -703,7 +711,7 @@ begin
|
||||
Memo1.Lines.Clear;
|
||||
Memo1.Lines.Add('--- AST Dump ---');
|
||||
|
||||
if not Assigned(FCurrExec) then
|
||||
if not Assigned(FCurrExec.Func) then
|
||||
begin
|
||||
Memo1.Lines.Add('No *compiled* AST has been generated yet. Click a test button first.');
|
||||
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.
|
||||
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;
|
||||
|
||||
function TAstBinder.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
||||
|
||||
@@ -219,7 +219,7 @@ begin
|
||||
|
||||
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;
|
||||
|
||||
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
||||
|
||||
@@ -22,6 +22,7 @@ type
|
||||
// This transformer runs *after* TypeChecker.
|
||||
// It specializes all statically resolvable function calls (RTL and user-defined)
|
||||
// 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)
|
||||
private
|
||||
FEnvironment: IEnvironment;
|
||||
@@ -35,7 +36,6 @@ type
|
||||
constructor Create(const AEnvironment: IEnvironment);
|
||||
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;
|
||||
end;
|
||||
|
||||
@@ -128,7 +128,16 @@ begin
|
||||
if FEnvironment.MonomorphCache.TryGetValue(key, specializedMethod) then
|
||||
begin
|
||||
// 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;
|
||||
end;
|
||||
|
||||
@@ -139,7 +148,16 @@ begin
|
||||
// 5a. Cache Hit (RTL)
|
||||
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;
|
||||
end;
|
||||
|
||||
@@ -148,7 +166,7 @@ begin
|
||||
|
||||
if (funcDef <> nil) then
|
||||
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
|
||||
begin
|
||||
var lambdaDef := funcDef.AsLambdaExpression;
|
||||
@@ -160,17 +178,18 @@ begin
|
||||
end;
|
||||
|
||||
// 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);
|
||||
|
||||
// 6b. Store in cache
|
||||
// Get the return type from the *full function type* returned by Compile
|
||||
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);
|
||||
|
||||
// 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;
|
||||
end;
|
||||
|
||||
|
||||
@@ -180,7 +180,18 @@ begin
|
||||
begin
|
||||
// Rebuild using factory.
|
||||
// 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;
|
||||
|
||||
@@ -242,7 +253,8 @@ begin
|
||||
newArgs,
|
||||
Node.StaticType,
|
||||
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;
|
||||
|
||||
|
||||
@@ -400,7 +400,8 @@ begin
|
||||
end;
|
||||
|
||||
// 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;
|
||||
|
||||
function TTypeChecker.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
||||
|
||||
@@ -201,7 +201,12 @@ var
|
||||
slot: Integer;
|
||||
typ: IStaticType;
|
||||
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;
|
||||
|
||||
// 1. Layout & Symbols
|
||||
@@ -277,7 +282,12 @@ begin
|
||||
else
|
||||
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;
|
||||
Log('Callee:');
|
||||
|
||||
@@ -41,7 +41,8 @@ type
|
||||
public
|
||||
Func: TDataValue.TFunc;
|
||||
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;
|
||||
|
||||
IExecutionStrategy = interface
|
||||
@@ -143,7 +144,8 @@ uses
|
||||
Myc.Ast.Compiler.Binder,
|
||||
Myc.Ast.Compiler.TypeChecker,
|
||||
Myc.Ast.Compiler.Specializer,
|
||||
Myc.Ast.Compiler.TCO;
|
||||
Myc.Ast.Compiler.TCO,
|
||||
Myc.Ast.Analysis.Purity;
|
||||
|
||||
{ TMonoCacheKey }
|
||||
|
||||
@@ -202,10 +204,11 @@ end;
|
||||
|
||||
{ TCompiledFunction }
|
||||
|
||||
constructor TCompiledFunction.Create(const AFunc: TDataValue.TFunc; const AStaticType: IStaticType);
|
||||
constructor TCompiledFunction.Create(const AFunc: TDataValue.TFunc; const AStaticType: IStaticType; AIsPure: Boolean);
|
||||
begin
|
||||
Func := AFunc;
|
||||
StaticType := AStaticType;
|
||||
IsPure := AIsPure;
|
||||
end;
|
||||
|
||||
type
|
||||
@@ -344,6 +347,8 @@ end;
|
||||
procedure TAstEnvironment.Define(const Name: String; const AScript: IAstNode);
|
||||
begin
|
||||
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);
|
||||
end;
|
||||
|
||||
@@ -526,6 +531,7 @@ var
|
||||
typedNode: IAstNode;
|
||||
specialized: IAstNode;
|
||||
tcoOptimized: IAstNode;
|
||||
isPure: Boolean;
|
||||
begin
|
||||
// 0. Wrap in Lambda to create an isolatable compilation unit (scope)
|
||||
var prg := TAst.LambdaExpr(Params, Node);
|
||||
@@ -549,25 +555,27 @@ begin
|
||||
// 4. Optimize (TCO)
|
||||
tcoOptimized := TAstTCO.Optimize(specialized);
|
||||
|
||||
// 5. Create Evaluator
|
||||
// We create a scope based on the descriptor to run the lambda body.
|
||||
// The lambda itself (as a node) is evaluated to produce a closure.
|
||||
// Since we wrapped the user code in a LambdaExpr, evaluating 'tcoOptimized'
|
||||
// returns the closure (TFunc).
|
||||
// 5. Purity Inference
|
||||
// Check the body of the optimized lambda for purity.
|
||||
// Note: tcoOptimized is a LambdaExpressionNode. Its body is what we check.
|
||||
isPure := TPurityAnalyzer.IsPure(tcoOptimized.AsLambdaExpression.Body);
|
||||
|
||||
// 6. Create Evaluator
|
||||
var visitor := FExecutionStrategy.CreateVisitor(descriptor.CreateScope(FRootScope));
|
||||
|
||||
// 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
|
||||
finalFunc :=
|
||||
function(const Args: TArray<TDataValue>): TDataValue
|
||||
begin
|
||||
Result := closure.AsMethod()(Args);
|
||||
Result := closure(Args);
|
||||
TEvaluatorVisitor.HandleTCO(Result);
|
||||
end;
|
||||
|
||||
Result := TCompiledFunction.Create(finalFunc, funcType);
|
||||
// Pass IsPure flag to the compiled function record
|
||||
Result := TCompiledFunction.Create(finalFunc, funcType, isPure);
|
||||
end;
|
||||
|
||||
function TEnvironment.Compile(const Node: IFunctionDefinition; const ArgTypes: TArray<IStaticType>): TCompiledFunction;
|
||||
@@ -578,6 +586,7 @@ var
|
||||
finalFunc: TDataValue.TFunc;
|
||||
|
||||
typedNode, specialized, tcoOptimized: IAstNode;
|
||||
isPure: Boolean;
|
||||
begin
|
||||
// Same steps as above, but Node is already a definition (Lambda)
|
||||
var expanded := ExpandMacros(Node);
|
||||
@@ -590,6 +599,9 @@ begin
|
||||
specialized := Specialize(typedNode);
|
||||
tcoOptimized := TAstTCO.Optimize(specialized);
|
||||
|
||||
// Purity Inference
|
||||
isPure := TPurityAnalyzer.IsPure(tcoOptimized.AsLambdaExpression.Body);
|
||||
|
||||
var visitor := FExecutionStrategy.CreateVisitor(descriptor.CreateScope(FRootScope));
|
||||
var closure := tcoOptimized.Accept(visitor);
|
||||
|
||||
@@ -600,7 +612,7 @@ begin
|
||||
TEvaluatorVisitor.HandleTCO(Result);
|
||||
end;
|
||||
|
||||
Result := TCompiledFunction.Create(finalFunc, funcType);
|
||||
Result := TCompiledFunction.Create(finalFunc, funcType, isPure);
|
||||
end;
|
||||
|
||||
function TEnvironment.ExpandMacros(const Node: IAstNode): IAstNode;
|
||||
|
||||
@@ -158,9 +158,11 @@ type
|
||||
{$region 'private'}
|
||||
function GetBody: IAstNode;
|
||||
function GetParameters: TArray<IIdentifierNode>;
|
||||
function GetIsPure: Boolean; // (* ADDED *)
|
||||
{$endregion}
|
||||
property Body: IAstNode read GetBody;
|
||||
property Parameters: TArray<IIdentifierNode> read GetParameters;
|
||||
property IsPure: Boolean read GetIsPure; // (* ADDED *)
|
||||
end;
|
||||
|
||||
INopNode = interface(IAstTypedNode)
|
||||
@@ -228,6 +230,7 @@ type
|
||||
|
||||
function GetUpvalues: TArray<TResolvedAddress>;
|
||||
function GetHasNestedLambdas: Boolean;
|
||||
// GetIsPure is inherited from IFunctionDefinition
|
||||
{$endregion}
|
||||
|
||||
property Parameters: TArray<IIdentifierNode> read GetParameters;
|
||||
@@ -238,6 +241,7 @@ type
|
||||
|
||||
property Upvalues: TArray<TResolvedAddress> read GetUpvalues;
|
||||
property HasNestedLambdas: Boolean read GetHasNestedLambdas;
|
||||
// IsPure is inherited
|
||||
end;
|
||||
|
||||
IFunctionCallNode = interface(IAstTypedNode)
|
||||
@@ -246,11 +250,13 @@ type
|
||||
function GetArguments: TArray<IAstNode>;
|
||||
function GetIsTailCall: Boolean;
|
||||
function GetStaticTarget: TDataValue.TFunc;
|
||||
function GetIsTargetPure: Boolean; // (* ADDED *)
|
||||
{$endregion}
|
||||
property Callee: IAstNode read GetCallee;
|
||||
property Arguments: TArray<IAstNode> read GetArguments;
|
||||
property IsTailCall: Boolean read GetIsTailCall;
|
||||
property StaticTarget: TDataValue.TFunc read GetStaticTarget;
|
||||
property IsTargetPure: Boolean read GetIsTargetPure; // (* ADDED *)
|
||||
end;
|
||||
|
||||
// A node representing a macro call.
|
||||
|
||||
+16
-15
@@ -47,11 +47,11 @@ type
|
||||
public
|
||||
Target: TDataValue.TFunc;
|
||||
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;
|
||||
|
||||
// The cache holding TDataValue.TFunc wrappers for static native functions.
|
||||
// Verwendet den generischen TSpecializedMethod-Record aus Myc.Ast.Types
|
||||
TStaticBootstrapCache = TDictionary<TStaticSignatureKey, TSpecializedMethod>;
|
||||
|
||||
TRtlFunctionInfo = class
|
||||
@@ -153,7 +153,7 @@ constructor TRtlExportAttribute.Create(const AName: string);
|
||||
begin
|
||||
inherited Create;
|
||||
Self.Name := AName;
|
||||
Self.IsPure := False; // Default to impure
|
||||
Self.IsPure := False; // Default to impure (safe)
|
||||
end;
|
||||
|
||||
constructor TRtlExportAttribute.Create(const AName: string; AIsPure: Boolean);
|
||||
@@ -230,6 +230,15 @@ begin
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
{ TSpecializedMethod }
|
||||
|
||||
constructor TSpecializedMethod.Create(const ATarget: TDataValue.TFunc; const AReturnType: IStaticType; AIsPure: Boolean);
|
||||
begin
|
||||
Target := ATarget;
|
||||
ReturnType := AReturnType;
|
||||
IsPure := AIsPure;
|
||||
end;
|
||||
|
||||
{ TRtlRegistry }
|
||||
|
||||
class constructor TRtlRegistry.Create;
|
||||
@@ -252,7 +261,7 @@ begin
|
||||
FStaticBootstrap := TStaticBootstrapCache.Create(TStaticSignatureKeyComparer.Create);
|
||||
FStaticFuncMap := TRtlFunctionMap.Create;
|
||||
|
||||
// 2. Perform RTTI Scan (formerly Pass 1 of RegisterAll)
|
||||
// 2. Perform RTTI Scan
|
||||
ctx := TRttiContext.Create;
|
||||
try
|
||||
rtlType := ctx.GetType(TypeInfo(TRtlFunctions));
|
||||
@@ -292,7 +301,7 @@ begin
|
||||
|
||||
if isDynamic then
|
||||
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]);
|
||||
|
||||
var wrapperFactory :=
|
||||
@@ -323,8 +332,8 @@ begin
|
||||
var wrapper := CreateStaticWrapper(method, retType, argTypes, rttiParams);
|
||||
if Assigned(wrapper) then
|
||||
begin
|
||||
methodRecord := TSpecializedMethod.Create(wrapper, retType);
|
||||
// Use renamed static helper
|
||||
// (* UPDATED: Capture IsPure from attribute *)
|
||||
methodRecord := TSpecializedMethod.Create(wrapper, retType, exportAttr.IsPure);
|
||||
RegisterStaticSpecialization(rtlName, argTypes, methodRecord);
|
||||
end
|
||||
else
|
||||
@@ -366,8 +375,6 @@ var
|
||||
key: TStaticSignatureKey;
|
||||
begin
|
||||
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);
|
||||
end;
|
||||
|
||||
@@ -724,12 +731,6 @@ begin
|
||||
TRtlRegistry.RegisterAll(AScope);
|
||||
end;
|
||||
|
||||
constructor TSpecializedMethod.Create(const ATarget: TDataValue.TFunc; const AReturnType: IStaticType);
|
||||
begin
|
||||
Target := ATarget;
|
||||
ReturnType := AReturnType;
|
||||
end;
|
||||
|
||||
initialization
|
||||
// Register this library's functions with the central AST factory.
|
||||
TAst.RegisterLibrary(RegisterRtlFunctions);
|
||||
|
||||
@@ -486,7 +486,17 @@ begin
|
||||
else
|
||||
begin
|
||||
// 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;
|
||||
|
||||
|
||||
+39
-15
@@ -56,14 +56,15 @@ type
|
||||
const AStaticType: IStaticType = nil
|
||||
): ITernaryExpressionNode; static;
|
||||
|
||||
// Factory Updated: Includes Layout (mandatory) and Descriptor (optional)
|
||||
// (* UPDATED Factory: Added AIsPure *)
|
||||
class function LambdaExpr(
|
||||
const AParameters: TArray<IIdentifierNode>;
|
||||
const ABody: IAstNode;
|
||||
const ALayout: IScopeLayout = nil; // Nil allowed only for raw AST construction (before Binder)
|
||||
const ADescriptor: IScopeDescriptor = nil; // Nil allowed before TypeChecker
|
||||
const ALayout: IScopeLayout = nil;
|
||||
const ADescriptor: IScopeDescriptor = nil;
|
||||
const AUpvalues: TArray<TResolvedAddress> = nil;
|
||||
const AHasNestedLambdas: Boolean = False;
|
||||
const AIsPure: Boolean = False; // (* ADDED *)
|
||||
const AStaticType: IStaticType = nil
|
||||
): ILambdaExpressionNode; static;
|
||||
|
||||
@@ -76,12 +77,14 @@ type
|
||||
class function Unquote(const AExpression: IAstNode): IUnquoteNode; static;
|
||||
class function UnquoteSplicing(const AExpression: IQuasiquoteNode): IUnquoteSplicingNode; static;
|
||||
|
||||
// (* UPDATED Factory: Added AIsTargetPure *)
|
||||
class function FunctionCall(
|
||||
const ACallee: IAstNode;
|
||||
const AArguments: TArray<IAstNode>;
|
||||
const AStaticType: IStaticType = nil;
|
||||
const AIsTailCall: Boolean = False;
|
||||
const AStaticTarget: TDataValue.TFunc = nil
|
||||
const AStaticTarget: TDataValue.TFunc = nil;
|
||||
const AIsTargetPure: Boolean = False // (* ADDED *)
|
||||
): IFunctionCallNode; static;
|
||||
|
||||
class function MacroExpansionNode(
|
||||
@@ -194,6 +197,7 @@ type
|
||||
FDescriptor: IScopeDescriptor;
|
||||
FUpvalues: TArray<TResolvedAddress>;
|
||||
FHasNestedLambdas: Boolean;
|
||||
FIsPure: Boolean; // (* ADDED *)
|
||||
|
||||
function GetParameters: TArray<IIdentifierNode>;
|
||||
function GetBody: IAstNode;
|
||||
@@ -201,6 +205,7 @@ type
|
||||
function GetDescriptor: IScopeDescriptor;
|
||||
function GetUpvalues: TArray<TResolvedAddress>;
|
||||
function GetHasNestedLambdas: Boolean;
|
||||
function GetIsPure: Boolean; // (* ADDED *)
|
||||
function GetKind: TAstNodeKind; override;
|
||||
public
|
||||
constructor Create(
|
||||
@@ -210,7 +215,8 @@ type
|
||||
const ALayout: IScopeLayout;
|
||||
const ADescriptor: IScopeDescriptor;
|
||||
const AUpvalues: TArray<TResolvedAddress>;
|
||||
const AHasNestedLambdas: Boolean
|
||||
const AHasNestedLambdas: Boolean;
|
||||
const AIsPure: Boolean // (* ADDED *)
|
||||
);
|
||||
destructor Destroy; override;
|
||||
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
||||
@@ -223,10 +229,12 @@ type
|
||||
FArguments: TArray<IAstNode>;
|
||||
FIsTailCall: Boolean;
|
||||
FStaticTarget: TDataValue.TFunc;
|
||||
FIsTargetPure: Boolean; // (* ADDED *)
|
||||
function GetCallee: IAstNode;
|
||||
function GetArguments: TArray<IAstNode>;
|
||||
function GetIsTailCall: Boolean;
|
||||
function GetStaticTarget: TDataValue.TFunc;
|
||||
function GetIsTargetPure: Boolean; // (* ADDED *)
|
||||
function GetKind: TAstNodeKind; override;
|
||||
public
|
||||
constructor Create(
|
||||
@@ -234,12 +242,14 @@ type
|
||||
const AArguments: TArray<IAstNode>;
|
||||
const AStaticType: IStaticType;
|
||||
const AIsTailCall: Boolean;
|
||||
const AStaticTarget: TDataValue.TFunc
|
||||
const AStaticTarget: TDataValue.TFunc;
|
||||
const AIsTargetPure: Boolean // (* ADDED *)
|
||||
);
|
||||
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
||||
function AsFunctionCall: IFunctionCallNode; override;
|
||||
end;
|
||||
|
||||
// ... (Other node definitions unchanged) ...
|
||||
TVariableDeclarationNode = class(TAstTypedNode, IVariableDeclarationNode)
|
||||
private
|
||||
FIdentifier: IIdentifierNode;
|
||||
@@ -260,8 +270,6 @@ type
|
||||
function AsVariableDeclaration: IVariableDeclarationNode; override;
|
||||
end;
|
||||
|
||||
// --- Other internal node classes ---
|
||||
|
||||
TConstantNode = class(TAstTypedNode, IConstantNode)
|
||||
private
|
||||
FValue: TDataValue;
|
||||
@@ -644,7 +652,6 @@ end;
|
||||
class function TAst.Constant(const AValue: TDataValue; const AStaticType: IStaticType = nil): IConstantNode;
|
||||
begin
|
||||
var constType := AStaticType;
|
||||
|
||||
if constType = nil then
|
||||
begin
|
||||
case AValue.Kind of
|
||||
@@ -655,7 +662,6 @@ begin
|
||||
constType := TTypes.Unknown;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := TConstantNode.Create(AValue, constType);
|
||||
end;
|
||||
|
||||
@@ -684,7 +690,8 @@ class function TAst.FunctionCall(
|
||||
const AArguments: TArray<IAstNode>;
|
||||
const AStaticType: IStaticType = nil;
|
||||
const AIsTailCall: Boolean = False;
|
||||
const AStaticTarget: TDataValue.TFunc = nil
|
||||
const AStaticTarget: TDataValue.TFunc = nil;
|
||||
const AIsTargetPure: Boolean = False // (* ADDED *)
|
||||
): IFunctionCallNode;
|
||||
begin
|
||||
Result :=
|
||||
@@ -694,7 +701,8 @@ begin
|
||||
if AStaticType <> nil then AStaticType
|
||||
else TTypes.Unknown,
|
||||
AIsTailCall,
|
||||
AStaticTarget
|
||||
AStaticTarget,
|
||||
AIsTargetPure
|
||||
);
|
||||
end;
|
||||
|
||||
@@ -759,6 +767,7 @@ class function TAst.LambdaExpr(
|
||||
const ADescriptor: IScopeDescriptor = nil;
|
||||
const AUpvalues: TArray<TResolvedAddress> = nil;
|
||||
const AHasNestedLambdas: Boolean = False;
|
||||
const AIsPure: Boolean = False; // (* ADDED *)
|
||||
const AStaticType: IStaticType = nil
|
||||
): ILambdaExpressionNode;
|
||||
begin
|
||||
@@ -771,7 +780,8 @@ begin
|
||||
ALayout,
|
||||
ADescriptor,
|
||||
AUpvalues,
|
||||
AHasNestedLambdas
|
||||
AHasNestedLambdas,
|
||||
AIsPure
|
||||
);
|
||||
end;
|
||||
|
||||
@@ -1247,7 +1257,8 @@ constructor TLambdaExpressionNode.Create(
|
||||
const ALayout: IScopeLayout;
|
||||
const ADescriptor: IScopeDescriptor;
|
||||
const AUpvalues: TArray<TResolvedAddress>;
|
||||
const AHasNestedLambdas: Boolean
|
||||
const AHasNestedLambdas: Boolean;
|
||||
const AIsPure: Boolean // (* ADDED *)
|
||||
);
|
||||
begin
|
||||
inherited Create(AStaticType);
|
||||
@@ -1257,6 +1268,7 @@ begin
|
||||
FDescriptor := ADescriptor;
|
||||
FUpvalues := AUpvalues;
|
||||
FHasNestedLambdas := AHasNestedLambdas;
|
||||
FIsPure := AIsPure;
|
||||
|
||||
// Consistency Check
|
||||
if Assigned(FDescriptor) and (FDescriptor.Layout <> FLayout) then
|
||||
@@ -1308,6 +1320,11 @@ begin
|
||||
Result := FHasNestedLambdas;
|
||||
end;
|
||||
|
||||
function TLambdaExpressionNode.GetIsPure: Boolean; // (* ADDED *)
|
||||
begin
|
||||
Result := FIsPure;
|
||||
end;
|
||||
|
||||
function TLambdaExpressionNode.GetKind: TAstNodeKind;
|
||||
begin
|
||||
Result := akLambdaExpression;
|
||||
@@ -1444,7 +1461,8 @@ constructor TFunctionCallNode.Create(
|
||||
const AArguments: TArray<IAstNode>;
|
||||
const AStaticType: IStaticType;
|
||||
const AIsTailCall: Boolean;
|
||||
const AStaticTarget: TDataValue.TFunc
|
||||
const AStaticTarget: TDataValue.TFunc;
|
||||
const AIsTargetPure: Boolean // (* ADDED *)
|
||||
);
|
||||
begin
|
||||
inherited Create(AStaticType);
|
||||
@@ -1452,6 +1470,7 @@ begin
|
||||
FArguments := AArguments;
|
||||
FIsTailCall := AIsTailCall;
|
||||
FStaticTarget := AStaticTarget;
|
||||
FIsTargetPure := AIsTargetPure;
|
||||
end;
|
||||
|
||||
function TFunctionCallNode.Accept(const Visitor: IAstVisitor): TDataValue;
|
||||
@@ -1489,6 +1508,11 @@ begin
|
||||
Result := FStaticTarget;
|
||||
end;
|
||||
|
||||
function TFunctionCallNode.GetIsTargetPure: Boolean; // (* ADDED *)
|
||||
begin
|
||||
Result := FIsTargetPure;
|
||||
end;
|
||||
|
||||
{ TMacroExpansionNode }
|
||||
|
||||
constructor TMacroExpansionNode.Create(const ACallNode: IFunctionCallNode; const AExpandedBody: IAstNode);
|
||||
|
||||
Reference in New Issue
Block a user