unit Myc.Ast.Analysis.Purity; interface uses System.SysUtils, Myc.Data.Value, Myc.Ast.Nodes, Myc.Ast.Visitor, Myc.Ast.Scope; type /// /// Analyzes an AST to determine if it is referentially transparent and free of side effects. /// TPurityAnalyzer = class(TAstVisitor) 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 VisitCondExpression(const Node: ICondExpressionNode): Boolean; override; // Replaces Ternary 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; // --- List Visitors (New) --- function VisitParameterList(const Node: IParameterList): Boolean; override; function VisitArgumentList(const Node: IArgumentList): Boolean; override; function VisitExpressionList(const Node: IExpressionList): Boolean; override; function VisitRecordFieldList(const Node: IRecordFieldList): Boolean; override; function VisitRecordField(const Node: IRecordFieldNode): 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; end; // --- List Visitors --- function TPurityAnalyzer.VisitParameterList(const Node: IParameterList): Boolean; begin // Declarations are pure Result := True; end; function TPurityAnalyzer.VisitArgumentList(const Node: IArgumentList): Boolean; begin for var item in Node do if not Accept(item) then exit(False); Result := True; end; function TPurityAnalyzer.VisitExpressionList(const Node: IExpressionList): Boolean; begin for var item in Node do if not Accept(item) then exit(False); Result := True; end; function TPurityAnalyzer.VisitRecordFieldList(const Node: IRecordFieldList): Boolean; begin for var item in Node do if not Accept(item) then exit(False); Result := True; end; function TPurityAnalyzer.VisitRecordField(const Node: IRecordFieldNode): Boolean; begin // Key is usually pure (Keyword), check Value Result := Accept(Node.Key) and Accept(Node.Value); 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. // Delegate to ArgumentList visitor Result := Accept(Node.Arguments); end; // --- Recursion --- function TPurityAnalyzer.VisitRecurNode(const Node: IRecurNode): Boolean; begin // 'recur' is just control flow. It is pure if its arguments are pure. Result := Accept(Node.Arguments); 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.VisitCondExpression(const Node: ICondExpressionNode): Boolean; var pair: TCondPair; begin // All conditions and all branches must be pure for pair in Node.Pairs do begin if not (Accept(pair.Condition) and Accept(pair.Branch)) then Exit(False); end; // And the Else branch Result := Accept(Node.ElseBranch); end; function TPurityAnalyzer.VisitBlockExpression(const Node: IBlockExpressionNode): Boolean; begin // Delegate to ExpressionList Result := Accept(Node.Expressions); 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 // Delegate to RecordFieldList Result := Accept(Node.Fields); 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.