unit Myc.Ast.Analysis.Purity;

interface

uses
    System.SysUtils,
    Myc.Data.Value,
    Myc.Ast.Nodes,
    Myc.Ast.Visitor,
    Myc.Ast.Scope,
    Myc.Ast;

type
    // Analyzes an AST to determine if it is referentially transparent and free of side effects.
    TPurityAnalyzer = class(TAstVisitor<Boolean>)
    strict private
        // --- Safe Leaves / Constructs ---
        function VisitConstant(const Node: IAstNode): Boolean;
        function VisitKeyword(const Node: IAstNode): Boolean;
        function VisitIfExpression(const Node: IAstNode): Boolean;
        function VisitCondExpression(const Node: IAstNode): Boolean;
        function VisitBlockExpression(const Node: IAstNode): Boolean;
        function VisitRecordLiteral(const Node: IAstNode): Boolean;
        function VisitVariableDeclaration(const Node: IAstNode): Boolean;
        function VisitSeriesLength(const Node: IAstNode): Boolean;

        // --- List Visitors (Aggregation Logic: All must be pure) ---
        function VisitParameterList(const Node: IAstNode): Boolean;
        function VisitArgumentList(const Node: IAstNode): Boolean;
        function VisitExpressionList(const Node: IAstNode): Boolean;
        function VisitRecordFieldList(const Node: IAstNode): Boolean;
        function VisitRecordField(const Node: IAstNode): Boolean;

        // --- Critical Checks ---
        function VisitIdentifier(const Node: IAstNode): Boolean;
        function VisitFunctionCall(const Node: IAstNode): Boolean;
        function VisitRecurNode(const Node: IAstNode): Boolean;

        // --- Forbidden Constructs (Side Effects / Unsafe) ---
        function VisitAssignment(const Node: IAstNode): Boolean;
        function VisitAddSeriesItem(const Node: IAstNode): Boolean;

        // Allocation is considered pure in this context
        function VisitCreateSeries(const Node: IAstNode): Boolean;

        function VisitIndexer(const Node: IAstNode): Boolean;
        function VisitMemberAccess(const Node: IAstNode): Boolean;

        // Ignored / Irrelevant for Runtime Purity
        function VisitLambdaExpression(const Node: IAstNode): Boolean;
        function VisitMacroDefinition(const Node: IAstNode): Boolean;
        function VisitQuasiquote(const Node: IAstNode): Boolean;
        function VisitUnquote(const Node: IAstNode): Boolean;
        function VisitUnquoteSplicing(const Node: IAstNode): Boolean;
        function VisitMacroExpansionNode(const Node: IAstNode): Boolean;
        function VisitNop(const Node: IAstNode): Boolean;

        // Pipe Support (Structural Check)
        function VisitPipeInput(const Node: IAstNode): Boolean;
        function VisitPipeSelectorList(const Node: IAstNode): Boolean;
        function VisitPipeInputList(const Node: IAstNode): Boolean;
        function VisitPipe(const Node: IAstNode): Boolean;

        // Helper to check if a node is pure (handling nil gracefully)
        function IsNodePure(const Node: IAstNode): Boolean;

    protected
        procedure SetupHandlers; 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
        // Use the visitor's central dispatch method
        Result := analyzer.Visit(RootNode);
    finally
        analyzer.Free;
    end;
end;

function TPurityAnalyzer.IsNodePure(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 registry
    Result := Visit(Node);
end;

procedure TPurityAnalyzer.SetupHandlers;
begin
    // Core
    Register(akConstant, VisitConstant);
    Register(akIdentifier, VisitIdentifier);
    Register(akKeyword, VisitKeyword);

    // Lists
    Register(akParameterList, VisitParameterList);
    Register(akArgumentList, VisitArgumentList);
    Register(akExpressionList, VisitExpressionList);
    Register(akRecordFieldList, VisitRecordFieldList);
    Register(akRecordField, VisitRecordField);

    // Structural
    Register(akIfExpression, VisitIfExpression);
    Register(akCondExpression, VisitCondExpression);
    Register(akLambdaExpression, VisitLambdaExpression);
    Register(akFunctionCall, VisitFunctionCall);
    Register(akMacroExpansion, VisitMacroExpansionNode);
    Register(akBlockExpression, VisitBlockExpression);
    Register(akVariableDeclaration, VisitVariableDeclaration);
    Register(akAssignment, VisitAssignment);
    Register(akMacroDefinition, VisitMacroDefinition);
    Register(akQuasiquote, VisitQuasiquote);
    Register(akUnquote, VisitUnquote);
    Register(akUnquoteSplicing, VisitUnquoteSplicing);
    Register(akIndexer, VisitIndexer);
    Register(akMemberAccess, VisitMemberAccess);
    Register(akRecordLiteral, VisitRecordLiteral);
    Register(akCreateSeries, VisitCreateSeries);
    Register(akAddSeriesItem, VisitAddSeriesItem);
    Register(akSeriesLength, VisitSeriesLength);
    Register(akRecur, VisitRecurNode);
    Register(akNop, VisitNop);

    // Pipes
    Register(akPipeInput, VisitPipeInput);
    Register(akPipeSelectorList, VisitPipeSelectorList);
    Register(akPipeInputList, VisitPipeInputList);
    Register(akPipe, VisitPipe);
end;

// --- List Visitors ---

function TPurityAnalyzer.VisitParameterList(const Node: IAstNode): Boolean;
begin
    // Declarations are pure
    Result := True;
end;

function TPurityAnalyzer.VisitArgumentList(const Node: IAstNode): Boolean;
begin
    for var item in Node.AsArgumentList do
        if not IsNodePure(item) then
            exit(False);
    Result := True;
end;

function TPurityAnalyzer.VisitExpressionList(const Node: IAstNode): Boolean;
begin
    for var item in Node.AsExpressionList do
        if not IsNodePure(item) then
            exit(False);
    Result := True;
end;

function TPurityAnalyzer.VisitRecordFieldList(const Node: IAstNode): Boolean;
begin
    for var item in Node.AsRecordFieldList do
        if not IsNodePure(item) then
            exit(False);
    Result := True;
end;

function TPurityAnalyzer.VisitRecordField(const Node: IAstNode): Boolean;
begin
    // Key is usually pure (Keyword), check Value
    var F := Node.AsRecordField;
    Result := IsNodePure(F.Key) and IsNodePure(F.Value);
end;

// --- Safe Leaves ---

function TPurityAnalyzer.VisitConstant(const Node: IAstNode): Boolean;
begin
    Result := True;
end;

function TPurityAnalyzer.VisitKeyword(const Node: IAstNode): Boolean;
begin
    Result := True;
end;

function TPurityAnalyzer.VisitNop(const Node: IAstNode): Boolean;
begin
    Result := True;
end;

// --- Identifier: Only local variables are safe ---

function TPurityAnalyzer.VisitIdentifier(const Node: IAstNode): 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.
    var I := Node.AsIdentifier;
    Result := (I.Address.Kind = akLocalOrParent) and (I.Address.ScopeDepth = 0);
end;

// --- Function Call: The Core Logic ---

function TPurityAnalyzer.VisitFunctionCall(const Node: IAstNode): Boolean;
begin
    var C := Node.AsFunctionCall;
    // 1. The target function MUST be marked as Pure (from RTL or previous inference).
    if not C.IsTargetPure then
        exit(False);

    // 2. All arguments must be pure expressions.
    Result := IsNodePure(C.Arguments);
end;

// --- Recursion ---

function TPurityAnalyzer.VisitRecurNode(const Node: IAstNode): Boolean;
begin
    // 'recur' is just control flow. It is pure if its arguments are pure.
    Result := IsNodePure(Node.AsRecur.Arguments);
end;

// --- Structures: Recursive Checks ---

function TPurityAnalyzer.VisitIfExpression(const Node: IAstNode): Boolean;
begin
    var E := Node.AsIfExpression;
    Result := IsNodePure(E.Condition) and IsNodePure(E.ThenBranch) and IsNodePure(E.ElseBranch);
end;

function TPurityAnalyzer.VisitCondExpression(const Node: IAstNode): Boolean;
begin
    var E := Node.AsCondExpression;
    // All conditions and all branches must be pure
    for var pair in E.Pairs do
    begin
        if not (IsNodePure(pair.Condition) and IsNodePure(pair.Branch)) then
            Exit(False);
    end;
    // And the Else branch
    Result := IsNodePure(E.ElseBranch);
end;

function TPurityAnalyzer.VisitBlockExpression(const Node: IAstNode): Boolean;
begin
    // Delegate to ExpressionList
    Result := IsNodePure(Node.AsBlockExpression.Expressions);
end;

function TPurityAnalyzer.VisitVariableDeclaration(const Node: IAstNode): 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 := IsNodePure(Node.AsVariableDeclaration.Initializer);
end;

function TPurityAnalyzer.VisitRecordLiteral(const Node: IAstNode): Boolean;
begin
    // Delegate to RecordFieldList
    Result := IsNodePure(Node.AsRecordLiteral.Fields);
end;

function TPurityAnalyzer.VisitIndexer(const Node: IAstNode): Boolean;
begin
    // Reading from a structure is pure if the indices/base are pure.
    var I := Node.AsIndexer;
    Result := IsNodePure(I.Base) and IsNodePure(I.Index);
end;

function TPurityAnalyzer.VisitMemberAccess(const Node: IAstNode): Boolean;
begin
    Result := IsNodePure(Node.AsMemberAccess.Base);
end;

function TPurityAnalyzer.VisitSeriesLength(const Node: IAstNode): Boolean;
begin
    // Querying length is pure.
    // The series identifier check happens in VisitIdentifier.
    Result := True;
end;

// --- Forbidden (Impure) ---

function TPurityAnalyzer.VisitAssignment(const Node: IAstNode): Boolean;
begin
    // Mutation of variables is defined as impure.
    Result := False;
end;

function TPurityAnalyzer.VisitAddSeriesItem(const Node: IAstNode): Boolean;
begin
    // Mutation of a series (side effect).
    Result := False;
end;

function TPurityAnalyzer.VisitCreateSeries(const Node: IAstNode): 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: IAstNode): 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: IAstNode): Boolean;
begin
    Result := True; // Compile-time construct
end;

function TPurityAnalyzer.VisitQuasiquote(const Node: IAstNode): Boolean;
begin
    Result := True; // Structural construction
end;

function TPurityAnalyzer.VisitUnquote(const Node: IAstNode): Boolean;
begin
    Result := IsNodePure(Node.AsUnquote.Expression);
end;

function TPurityAnalyzer.VisitUnquoteSplicing(const Node: IAstNode): Boolean;
begin
    Result := IsNodePure(Node.AsUnquoteSplicing.Expression);
end;

function TPurityAnalyzer.VisitMacroExpansionNode(const Node: IAstNode): Boolean;
begin
    // We analyze the already expanded body.
    Result := IsNodePure(Node.AsMacroExpansion.ExpandedBody);
end;

// --- Pipe Support ---

function TPurityAnalyzer.VisitPipeInput(const Node: IAstNode): Boolean;
begin
    var P := Node.AsPipeInput;
    // Input node itself is declarative.
    // But we check its parts just in case.
    Result := IsNodePure(P.StreamSource) and IsNodePure(P.Selectors);
end;

function TPurityAnalyzer.VisitPipeSelectorList(const Node: IAstNode): Boolean;
begin
    for var item in Node.AsPipeSelectorList do
        if not IsNodePure(item) then
            exit(False);
    Result := True;
end;

function TPurityAnalyzer.VisitPipeInputList(const Node: IAstNode): Boolean;
begin
    for var item in Node.AsPipeInputList do
        if not IsNodePure(item) then
            exit(False);
    Result := True;
end;

function TPurityAnalyzer.VisitPipe(const Node: IAstNode): Boolean;
begin
    var P := Node.AsPipe;
    // A pipe is pure if its input definitions are pure AND its transformation lambda is pure.
    Result := IsNodePure(P.Inputs) and IsNodePure(P.Transformation);
end;

end.
