// See the file "COPYING" in the main distribution directory for copyright.

#pragma once

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "zeek/EventHandler.h"
#include "zeek/IntrusivePtr.h"
#include "zeek/StmtBase.h"
#include "zeek/Timer.h"
#include "zeek/TraverseTypes.h"
#include "zeek/Type.h"
#include "zeek/Val.h"
#include "zeek/ZeekArgs.h"
#include "zeek/ZeekList.h"

namespace zeek {
template<class T>
class IntrusivePtr;

namespace detail {

class Frame;
class Scope;
class FunctionIngredients;
class WhenInfo;
using IDPtr = IntrusivePtr<ID>;
using ScopePtr = IntrusivePtr<Scope>;
using ScriptFuncPtr = IntrusivePtr<ScriptFunc>;
using FunctionIngredientsPtr = std::shared_ptr<FunctionIngredients>;

enum ExprTag : int8_t {
    EXPR_ANY = -1,
    EXPR_NAME,
    EXPR_CONST,
    EXPR_CLONE,
    EXPR_INCR,
    EXPR_DECR,
    EXPR_NOT,
    EXPR_COMPLEMENT,
    EXPR_POSITIVE,
    EXPR_NEGATE,
    EXPR_ADD,
    EXPR_SUB,
    EXPR_AGGR_ADD,
    EXPR_AGGR_DEL,
    EXPR_ADD_TO,
    EXPR_REMOVE_FROM,
    EXPR_TIMES,
    EXPR_DIVIDE,
    EXPR_MASK,
    EXPR_MOD,
    EXPR_AND,
    EXPR_OR,
    EXPR_XOR,
    EXPR_LSHIFT,
    EXPR_RSHIFT,
    EXPR_AND_AND,
    EXPR_OR_OR,
    EXPR_LT,
    EXPR_LE,
    EXPR_EQ,
    EXPR_NE,
    EXPR_GE,
    EXPR_GT,
    EXPR_COND,
    EXPR_REF,
    EXPR_ASSIGN,
    EXPR_INDEX,
    EXPR_FIELD,
    EXPR_HAS_FIELD,
    EXPR_RECORD_CONSTRUCTOR,
    EXPR_TABLE_CONSTRUCTOR,
    EXPR_SET_CONSTRUCTOR,
    EXPR_VECTOR_CONSTRUCTOR,
    EXPR_FIELD_ASSIGN,
    EXPR_IN,
    EXPR_LIST,
    EXPR_CALL,
    EXPR_LAMBDA,
    EXPR_EVENT,
    EXPR_SCHEDULE,
    EXPR_ARITH_COERCE,
    EXPR_RECORD_COERCE,
    EXPR_TABLE_COERCE,
    EXPR_VECTOR_COERCE,
    EXPR_TO_ANY_COERCE,
    EXPR_FROM_ANY_COERCE,
    EXPR_SIZE,
    EXPR_CAST,
    EXPR_IS,
    EXPR_INDEX_SLICE_ASSIGN,

    // The following types of expressions are only created for ASTs
    // transformed to reduced form; they aren't germane for ASTs produced
    // by parsing .zeek script files. See script_opt/Expr.h for the
    // corresponding definitions.
    EXPR_INLINE,
    EXPR_APPEND_TO,
    EXPR_INDEX_ASSIGN,
    EXPR_FIELD_LHS_ASSIGN,
    EXPR_REC_ASSIGN_FIELDS,
    EXPR_REC_ADD_FIELDS,
    EXPR_REC_CONSTRUCT_WITH_REC,
    EXPR_FROM_ANY_VEC_COERCE,
    EXPR_ANY_INDEX,
    EXPR_SCRIPT_OPT_BUILTIN,

    EXPR_NOP,

#define NUM_EXPRS (int(EXPR_NOP) + 1)
};

extern const char* expr_name(ExprTag t);

class AddToExpr;
class AssignExpr;
class CallExpr;
class ConstExpr;
class EventExpr;
class FieldAssignExpr;
class FieldExpr;
class ForExpr;
class HasFieldExpr;
class IndexExpr;
class IsExpr;
class LambdaExpr;
class ListExpr;
class NameExpr;
class RefExpr;

class Expr;
using CallExprPtr = IntrusivePtr<CallExpr>;
using ConstExprPtr = IntrusivePtr<ConstExpr>;
using EventExprPtr = IntrusivePtr<EventExpr>;
using ExprPtr = IntrusivePtr<Expr>;
using NameExprPtr = IntrusivePtr<NameExpr>;
using RefExprPtr = IntrusivePtr<RefExpr>;
using LambdaExprPtr = IntrusivePtr<LambdaExpr>;

class Stmt;
using StmtPtr = IntrusivePtr<Stmt>;

class ExprOptInfo;

class Expr : public Obj {
public:
    const TypePtr& GetType() const { return type; }

    template<class T>
    IntrusivePtr<T> GetType() const {
        return cast_intrusive<T>(type);
    }

    ExprTag Tag() const { return tag; }

    Expr* Ref() {
        zeek::Ref(this);
        return this;
    }
    ExprPtr ThisPtr() { return {NewRef{}, this}; }

    // Evaluates the expression and returns a corresponding Val*,
    // or nil if the expression's value isn't fixed.
    virtual ValPtr Eval(Frame* f) const = 0;

    // Assign to the given value, if appropriate.
    virtual void Assign(Frame* f, ValPtr v);

    // Returns the type corresponding to this expression interpreted
    // as an initialization.  Returns nil if the initialization is illegal.
    virtual TypePtr InitType() const;

    // Returns true if this expression, interpreted as an initialization,
    // constitutes a record element, false otherwise.  If the TypeDecl*
    // is non-nil and the expression is a record element, fills in the
    // TypeDecl with a description of the element.
    virtual bool IsRecordElement(TypeDecl* td) const;

    // True if the expression has no side effects, false otherwise.
    virtual bool IsPure() const { return true; }

    // True if the expression is a constant, false otherwise.
    bool IsConst() const { return tag == EXPR_CONST; }

    // True if the expression is in error (to alleviate error propagation).
    bool IsError() const;

    // Mark expression as in error.
    void SetError();
    void SetError(const char* msg);

    // Returns the expression's constant value, or complains
    // if it's not a constant.
    inline Val* ExprVal() const;

    // True if the expression is a constant zero, false otherwise.
    bool IsZero() const;

    // True if the expression is a constant one, false otherwise.
    bool IsOne() const;

    // True if the expression supports the "add" or "delete" operations,
    // false otherwise.
    virtual bool CanAdd() const;
    virtual bool CanDel() const;

    // The types associated with those operations.
    virtual TypePtr AddType() const;
    virtual TypePtr DelType() const;

    virtual ValPtr Add(Frame* f);    // perform add operation
    virtual ValPtr Delete(Frame* f); // perform delete operation

    // Return the expression converted to L-value form.  If expr
    // cannot be used as an L-value, reports an error and returns
    // the current value of expr (this is the default method).
    virtual ExprPtr MakeLvalue();

    // Invert the sense of the operation.  Returns true if the expression
    // was invertible (currently only true for relational/equality
    // expressions), false otherwise.
    virtual bool InvertSense();

    // Marks the expression as one requiring (or at least appearing
    // with) parentheses.  Used for pretty-printing.
    void MarkParen() { paren = true; }
    bool IsParen() const { return paren; }

    // These are used by script optimization for AST analysis.
    // NOLINTBEGIN(bugprone-macro-parentheses)
#define ZEEK_EXPR_ACCESSOR_DECLS(ctype)                                                                                \
    const ctype* As##ctype() const;                                                                                    \
    ctype* As##ctype();                                                                                                \
    IntrusivePtr<ctype> As##ctype##Ptr();
    // NOLINTEND(bugprone-macro-parentheses)

    ZEEK_EXPR_ACCESSOR_DECLS(AddToExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(AssignExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(CallExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(ConstExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(EventExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(FieldAssignExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(FieldExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(ForExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(HasFieldExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(IndexExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(IsExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(LambdaExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(ListExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(NameExpr)
    ZEEK_EXPR_ACCESSOR_DECLS(RefExpr)

    void Describe(ODesc* d) const final;

    virtual TraversalCode Traverse(TraversalCallback* cb) const = 0;

    // Returns a duplicate of the expression.
    virtual ExprPtr Duplicate() = 0;

    // Recursively traverses the AST to inline eligible function calls.
    virtual ExprPtr Inline(Inliner* inl) { return ThisPtr(); }

    // True if the expression can serve as an operand to a reduced
    // expression.
    bool IsSingleton(Reducer* r) const { return (tag == EXPR_NAME && IsReduced(r)) || tag == EXPR_CONST; }

    // True if the expression has no side effects, false otherwise.
    virtual bool HasNoSideEffects() const { return IsPure(); }

    // True if the expression is in fully reduced form: a singleton
    // or an assignment to an operator with singleton operands.
    virtual bool IsReduced(Reducer* c) const;

    // True if the expression's operands are singletons.
    virtual bool HasReducedOps(Reducer* c) const;

    // True if (a) the expression has at least one operand, and (b) all
    // of its operands are constant.
    bool HasConstantOps() const {
        return GetOp1() && GetOp1()->IsConst() &&
               (! GetOp2() || (GetOp2()->IsConst() && (! GetOp3() || GetOp3()->IsConst())));
    }

    // True if the expression is reduced to a form that can be
    // used in a conditional.
    bool IsReducedConditional(Reducer* c) const;

    // True if the expression is reduced to a form that can be
    // used in a field assignment.
    bool IsReducedFieldAssignment(Reducer* c) const;

    // True if this expression can be the RHS for a field assignment.
    bool IsFieldAssignable(const Expr* e) const;

    // True if the expression will transform to one of another AST node
    // (perhaps of the same type) upon reduction, for non-constant
    // operands.  "Transform" means something beyond assignment to a
    // temporary.  Necessary so that we know to fully reduce such
    // expressions if they're the RHS of an assignment.
    virtual bool WillTransform(Reducer* c) const { return false; }

    // The same, but for the expression when used in a conditional context.
    virtual bool WillTransformInConditional(Reducer* c) const { return false; }

    // Returns the current expression transformed into "new_me".
    ExprPtr TransformMe(ExprPtr new_me, Reducer* c, StmtPtr& red_stmt);

    // Returns a set of predecessor statements in red_stmt (which might
    // be nil if no reduction necessary), and the reduced version of
    // the expression, suitable for replacing previous uses.  The
    // second version always yields a singleton suitable for use
    // as an operand.  The first version does this too except
    // for assignment statements; thus, its form is not guarantee
    // suitable for use as an operand.
    virtual ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt);
    virtual ExprPtr ReduceToSingleton(Reducer* c, StmtPtr& red_stmt) { return Reduce(c, red_stmt); }

    // Reduces the expression to one whose operands are singletons.
    // Returns a predecessor statement (which might be a StmtList), if any.
    virtual StmtPtr ReduceToSingletons(Reducer* c);

    // Reduces the expression to one that can appear as a conditional.
    ExprPtr ReduceToConditional(Reducer* c, StmtPtr& red_stmt);

    // Transforms the expression into its form suitable for use in
    // a conditional. Only meaningful for expressions that return true
    // for WillTransformInConditional().
    virtual ExprPtr TransformToConditional(Reducer* c, StmtPtr& red_stmt);

    // Reduces the expression to one that can appear as a field
    // assignment.
    ExprPtr ReduceToFieldAssignment(Reducer* c, StmtPtr& red_stmt);

    // Helper function for factoring out complexities related to
    // index-based assignment.
    void AssignToIndex(ValPtr v1, ValPtr v2, ValPtr v3) const;

    // Returns a new expression corresponding to a temporary
    // that's been assigned to the given expression via red_stmt.
    ExprPtr AssignToTemporary(ExprPtr e, Reducer* c, StmtPtr& red_stmt);
    // Same but for this expression.
    ExprPtr AssignToTemporary(Reducer* c, StmtPtr& red_stmt) { return AssignToTemporary(ThisPtr(), c, red_stmt); }

    // If the expression always evaluates to the same value, returns
    // that value.  Otherwise, returns nullptr.
    virtual ValPtr FoldVal() const { return nullptr; }

    // Returns a Val or a constant Expr corresponding to zero.
    ValPtr MakeZero(TypeTag t) const;
    ConstExprPtr MakeZeroExpr(TypeTag t) const;

    // Returns the expression's operands, or nil if it doesn't
    // have the given operand.
    virtual ExprPtr GetOp1() const;
    virtual ExprPtr GetOp2() const;
    virtual ExprPtr GetOp3() const;

    // Sets the operands to new values.
    virtual void SetOp1(ExprPtr new_op);
    virtual void SetOp2(ExprPtr new_op);
    virtual void SetOp3(ExprPtr new_op);

    // Helper function to reduce boring code runs.
    StmtPtr MergeStmts(StmtPtr s1, StmtPtr s2, StmtPtr s3 = nullptr) const;

    // A convenience function for taking a newly-created Expr,
    // making it point to us as the successor, and returning it.
    //
    // Takes an Expr* rather than a ExprPtr to de-clutter the calling
    // code, which is always passing in "new XyzExpr(...)".  This
    // call, as a convenient side effect, transforms that bare pointer
    // into an ExprPtr.
    virtual ExprPtr SetSucc(Expr* succ) {
        succ->SetLocationInfo(GetLocationInfo());
        if ( IsParen() )
            succ->MarkParen();
        return {AdoptRef{}, succ};
    }

    // Access script optimization information associated with
    // this statement.
    ExprOptInfo* GetOptInfo() const { return opt_info; }

    // Returns the number of expressions created since the last reset.
    static int GetNumExprs() { return num_exprs; }

    // Clears the number of expressions created.
    static void ResetNumExprs() { num_exprs = 0; }

    ~Expr() override;

protected:
    Expr() = default;
    explicit Expr(ExprTag arg_tag);

    virtual void ExprDescribe(ODesc* d) const = 0;
    void AddTag(ODesc* d) const;

    // Puts the expression in canonical form.
    virtual void Canonicalize();

    void SetType(TypePtr t);

    // Reports the given error and sets the expression's type to
    // TYPE_ERROR.
    void ExprError(const char msg[]);

    // These two functions both call Reporter::RuntimeError or Reporter::ExprRuntimeError,
    // both of which are marked as [[noreturn]].
    [[noreturn]] void RuntimeError(const std::string& msg) const;
    [[noreturn]] void RuntimeErrorWithCallStack(const std::string& msg) const;

    ExprTag tag;
    bool paren;
    TypePtr type;

    // Information associated with the Expr for purposes of
    // script optimization.
    ExprOptInfo* opt_info;

    // Number of expressions created thus far.
    static int num_exprs;
};

class NameExpr final : public Expr {
public:
    explicit NameExpr(IDPtr id, bool const_init = false);

    bool CanDel() const override;
    ValPtr Delete(Frame* f) override;

    ID* Id() const { return id.get(); }
    const IDPtr& IdPtr() const;

    ValPtr Eval(Frame* f) const override;
    void Assign(Frame* f, ValPtr v) override;
    ExprPtr MakeLvalue() override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool HasNoSideEffects() const override { return true; }
    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override { return IsReduced(c); }
    bool WillTransform(Reducer* c) const override { return ! IsReduced(c); }
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    ValPtr FoldVal() const override;

protected:
    void ExprDescribe(ODesc* d) const override;

    // Returns true if our identifier is a global with a constant value
    // that can be propagated; used for optimization.
    bool FoldableGlobal() const;

    IDPtr id;
    bool in_const_init;
};

class ConstExpr final : public Expr {
public:
    explicit ConstExpr(ValPtr val);

    Val* Value() const { return val.get(); }
    ValPtr ValuePtr() const { return val; }

    ValPtr Eval(Frame* f) const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;

    ValPtr FoldVal() const override {
        if ( type->Tag() == TYPE_OPAQUE )
            // Aggressive constant propagation can lead to the appearance of
            // opaque "constants". Don't consider these as foldable because
            // they're problematic to generate independently.
            return nullptr;

        return val;
    }

protected:
    void ExprDescribe(ODesc* d) const override;
    ValPtr val;
};

class UnaryExpr : public Expr {
public:
    Expr* Op() const { return op.get(); }

    // UnaryExpr::Eval correctly handles vector types.  Any child
    // class that overrides Eval() should be modified to handle
    // vectors correctly as necessary.
    ValPtr Eval(Frame* f) const override;

    bool IsPure() const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Inline(Inliner* inl) override;

    bool HasNoSideEffects() const override;
    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

    ExprPtr GetOp1() const final { return op; }
    void SetOp1(ExprPtr _op) final { op = std::move(_op); }

protected:
    UnaryExpr(ExprTag arg_tag, ExprPtr arg_op);

    void ExprDescribe(ODesc* d) const override;

    // Returns the expression folded using the given constant.
    virtual ValPtr Fold(Val* v) const;

    ExprPtr op;
};

class BinaryExpr : public Expr {
public:
    Expr* Op1() const { return op1.get(); }
    Expr* Op2() const { return op2.get(); }

    bool IsPure() const override;

    // BinaryExpr::Eval correctly handles vector types.  Any child
    // class that overrides Eval() should be modified to handle
    // vectors correctly as necessary.
    ValPtr Eval(Frame* f) const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Inline(Inliner* inl) override;

    bool HasNoSideEffects() const override;
    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

    ExprPtr GetOp1() const final { return op1; }
    ExprPtr GetOp2() const final { return op2; }

    void SetOp1(ExprPtr _op) final { op1 = std::move(_op); }
    void SetOp2(ExprPtr _op) final { op2 = std::move(_op); }

protected:
    BinaryExpr(ExprTag arg_tag, ExprPtr arg_op1, ExprPtr arg_op2)
        : Expr(arg_tag), op1(std::move(arg_op1)), op2(std::move(arg_op2)) {
        if ( ! (op1 && op2) )
            return;
        if ( op1->IsError() || op2->IsError() )
            SetError();
    }

    // Returns the expression folded using the given constants.
    virtual ValPtr Fold(Val* v1, Val* v2) const;

    // Same for when the constants are strings.
    virtual ValPtr StringFold(Val* v1, Val* v2) const;

    // Same for when the constants are patterns.
    virtual ValPtr PatternFold(Val* v1, Val* v2) const;

    // Same for when the constants are sets.
    virtual ValPtr SetFold(Val* v1, Val* v2) const;

    // Same for when the constants are tables.
    virtual ValPtr TableFold(Val* v1, Val* v2) const;

    // Same for when the constants are addresses or subnets.
    virtual ValPtr AddrFold(Val* v1, Val* v2) const;
    virtual ValPtr SubNetFold(Val* v1, Val* v2) const;

    bool BothConst() const { return op1->IsConst() && op2->IsConst(); }

    // Exchange op1 and op2.
    void SwapOps();

    // Promote the operands to the given type tag, if necessary.
    void PromoteOps(TypeTag t);

    // Promote the expression to the given type tag (i.e., promote
    // operands and also set expression's type).
    void PromoteType(TypeTag t, bool is_vector);

    // Promote one of the operands to be "double" (if not already),
    // to make it suitable for combining with the other "interval"
    // operand, yielding an "interval" type.
    void PromoteForInterval(ExprPtr& op);

    void ExprDescribe(ODesc* d) const override;

    // For assignment operations (=, +=, -=) checks for a valid
    // expression-list on the RHS (op2), potentially transforming
    // op2 in the process.  Returns true if the list is present
    // and type-checks correctly, false otherwise.
    bool CheckForRHSList();

    ExprPtr op1;
    ExprPtr op2;
};

class CloneExpr final : public UnaryExpr {
public:
    explicit CloneExpr(ExprPtr op);
    ValPtr Eval(Frame* f) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    ValPtr Fold(Val* v) const override;
};

class IncrExpr final : public UnaryExpr {
public:
    IncrExpr(ExprTag tag, ExprPtr op);

    ValPtr Eval(Frame* f) const override;
    ValPtr DoSingleEval(Frame* f, Val* v) const;
    bool IsPure() const override { return false; }

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool HasNoSideEffects() const override;
    bool WillTransform(Reducer* c) const override { return true; }
    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override { return false; }
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    ExprPtr ReduceToSingleton(Reducer* c, StmtPtr& red_stmt) override;
};

class ComplementExpr final : public UnaryExpr {
public:
    explicit ComplementExpr(ExprPtr op);

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr Fold(Val* v) const override;
};

class NotExpr final : public UnaryExpr {
public:
    explicit NotExpr(ExprPtr op);

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr Fold(Val* v) const override;
};

class PosExpr final : public UnaryExpr {
public:
    explicit PosExpr(ExprPtr op);

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr Fold(Val* v) const override;
};

class NegExpr final : public UnaryExpr {
public:
    explicit NegExpr(ExprPtr op);

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr Fold(Val* v) const override;
};

class SizeExpr final : public UnaryExpr {
public:
    explicit SizeExpr(ExprPtr op);
    ValPtr Eval(Frame* f) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    ValPtr Fold(Val* v) const override;
};

class AddExpr final : public BinaryExpr {
public:
    AddExpr(ExprPtr op1, ExprPtr op2);
    void Canonicalize() override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ExprPtr BuildSub(const ExprPtr& op1, const ExprPtr& op2);
};

// A helper class that enables us to factor some common code.
class AggrAddDelExpr : public UnaryExpr {
public:
    explicit AggrAddDelExpr(ExprTag _tag, ExprPtr _e) : UnaryExpr(_tag, std::move(_e)) {}

    bool IsPure() const override { return false; }

    // Optimization-related:
    bool IsReduced(Reducer* c) const override { return HasReducedOps(c); }
    bool HasReducedOps(Reducer* c) const override { return op->HasReducedOps(c); }
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
};

class AggrAddExpr final : public AggrAddDelExpr {
public:
    explicit AggrAddExpr(ExprPtr e);

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    ValPtr Eval(Frame* f) const override;
};

class AggrDelExpr final : public AggrAddDelExpr {
public:
    explicit AggrDelExpr(ExprPtr e);

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    ValPtr Eval(Frame* f) const override;
};

class AddToExpr final : public BinaryExpr {
public:
    AddToExpr(ExprPtr op1, ExprPtr op2);
    ValPtr Eval(Frame* f) const override;

    bool IsVectorElemAppend() const { return is_vector_elem_append; }

    // Optimization-related:
    bool IsPure() const override { return false; }
    ExprPtr Duplicate() override;
    bool HasReducedOps(Reducer* c) const override { return false; }
    bool WillTransform(Reducer* c) const override { return true; }
    bool IsReduced(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    ExprPtr ReduceToSingleton(Reducer* c, StmtPtr& red_stmt) override;

private:
    // Whether this operation is appending a single element to a vector.
    bool is_vector_elem_append = false;
};

class RemoveFromExpr final : public BinaryExpr {
public:
    bool IsPure() const override { return false; }
    RemoveFromExpr(ExprPtr op1, ExprPtr op2);
    ValPtr Eval(Frame* f) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool HasReducedOps(Reducer* c) const override { return false; }
    bool WillTransform(Reducer* c) const override { return true; }
    bool IsReduced(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    ExprPtr ReduceToSingleton(Reducer* c, StmtPtr& red_stmt) override;
};

class SubExpr final : public BinaryExpr {
public:
    SubExpr(ExprPtr op1, ExprPtr op2);

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
};

class TimesExpr final : public BinaryExpr {
public:
    TimesExpr(ExprPtr op1, ExprPtr op2);
    void Canonicalize() override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
};

class DivideExpr final : public BinaryExpr {
public:
    DivideExpr(ExprPtr op1, ExprPtr op2);

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
};

class MaskExpr final : public BinaryExpr {
public:
    MaskExpr(ExprPtr op1, ExprPtr op2);

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    ValPtr AddrFold(Val* v1, Val* v2) const override;
};

class ModExpr final : public BinaryExpr {
public:
    ModExpr(ExprPtr op1, ExprPtr op2);

    // Optimization-related:
    ExprPtr Duplicate() override;
};

class BoolExpr final : public BinaryExpr {
public:
    BoolExpr(ExprTag tag, ExprPtr op1, ExprPtr op2);

    ValPtr Eval(Frame* f) const override;
    ValPtr DoSingleEval(Frame* f, ValPtr v1, Expr* op2) const;

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    bool WillTransformInConditional(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    ExprPtr TransformToConditional(Reducer* c, StmtPtr& red_stmt) override;

protected:
    bool IsTrue(const ExprPtr& e) const;
    bool IsFalse(const ExprPtr& e) const;
};

class BitExpr final : public BinaryExpr {
public:
    BitExpr(ExprTag tag, ExprPtr op1, ExprPtr op2);

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
};

// Intermediary class for comparison operators. Not directly instantiated.
class CmpExpr : public BinaryExpr {
protected:
    CmpExpr(ExprTag tag, ExprPtr op1, ExprPtr op2);

    void Canonicalize() override;

    bool WillTransform(Reducer* c) const override;
    bool WillTransformInConditional(Reducer* c) const override;
    bool IsReduced(Reducer* c) const override;
    ExprPtr TransformToConditional(Reducer* c, StmtPtr& red_stmt) override;

    bool IsHasElementsTest() const;
    ExprPtr BuildHasElementsTest() const;
};

class EqExpr final : public CmpExpr {
public:
    EqExpr(ExprTag tag, ExprPtr op1, ExprPtr op2);

    // Optimization-related:
    ExprPtr Duplicate() override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    bool InvertSense() override;

protected:
    ValPtr Fold(Val* v1, Val* v2) const override;
};

class RelExpr final : public CmpExpr {
public:
    RelExpr(ExprTag tag, ExprPtr op1, ExprPtr op2);

    // Optimization-related:
    ExprPtr Duplicate() override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    bool InvertSense() override;
};

class CondExpr final : public Expr {
public:
    CondExpr(ExprPtr op1, ExprPtr op2, ExprPtr op3);

    const Expr* Op1() const { return op1.get(); }
    const Expr* Op2() const { return op2.get(); }
    const Expr* Op3() const { return op3.get(); }

    ValPtr Eval(Frame* f) const override;
    bool IsPure() const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    ExprPtr Inline(Inliner* inl) override;

    bool WillTransform(Reducer* c) const override;
    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

    ExprPtr GetOp1() const final { return op1; }
    ExprPtr GetOp2() const final { return op2; }
    ExprPtr GetOp3() const final { return op3; }

    void SetOp1(ExprPtr _op) final { op1 = std::move(_op); }
    void SetOp2(ExprPtr _op) final { op2 = std::move(_op); }
    void SetOp3(ExprPtr _op) final { op3 = std::move(_op); }

protected:
    void ExprDescribe(ODesc* d) const override;

    bool IsMinOrMax(Reducer* c) const;
    ExprPtr TransformToMinOrMax() const;

    ExprPtr op1;
    ExprPtr op2;
    ExprPtr op3;
};

class RefExpr final : public UnaryExpr {
public:
    explicit RefExpr(ExprPtr op);

    void Assign(Frame* f, ValPtr v) override;
    ExprPtr MakeLvalue() override;

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool WillTransform(Reducer* c) const override;
    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

    // Reduce to simplified LHS form, i.e., a reference to only a name.
    StmtPtr ReduceToLHS(Reducer* c);
};

class AssignExpr : public BinaryExpr {
public:
    // If val is given, evaluating this expression will always yield the val
    // yet still perform the assignment.  Used for triggers.
    AssignExpr(ExprPtr op1, ExprPtr op2, bool is_init, ValPtr val = nullptr, const AttributesPtr& attrs = nullptr,
               bool type_check = true);

    ValPtr Eval(Frame* f) const override;
    TypePtr InitType() const override;
    bool IsRecordElement(TypeDecl* td) const override;
    bool IsPure() const override { return false; }

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool HasNoSideEffects() const override;
    bool WillTransform(Reducer* c) const override { return true; }
    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    ExprPtr ReduceToSingleton(Reducer* c, StmtPtr& red_stmt) override;

    // Whether this is an assignment to a temporary.
    bool IsTemp() const { return is_temp; }
    void SetIsTemp() { is_temp = true; }

    // The following is a hack that's used in "when" expressions to support
    // assignments to new locals, like "when ( (local l = foo()) && ...".
    // These methods return the value to use when evaluating such
    // assignments.  That would normally be the RHS of the assignment,
    // but to get when's to work in a convenient fashion, for them it's
    // instead boolean T.
    ValPtr AssignVal() { return val; }
    const ValPtr& AssignVal() const { return val; }

protected:
    bool TypeCheck(const AttributesPtr& attrs = nullptr);
    bool TypeCheckArithmetics(TypeTag bt1, TypeTag bt2);

    bool is_init;
    bool is_temp = false; // Optimization related

    ValPtr val; // optional
};

class IndexSliceAssignExpr final : public AssignExpr {
public:
    IndexSliceAssignExpr(ExprPtr op1, ExprPtr op2, bool is_init);
    ValPtr Eval(Frame* f) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
};

class IndexExpr : public BinaryExpr {
public:
    IndexExpr(ExprPtr op1, ListExprPtr op2, bool is_slice = false, bool is_inside_when = false);

    bool CanAdd() const override;
    bool CanDel() const override;

    ValPtr Add(Frame* f) override;
    ValPtr Delete(Frame* f) override;

    void Assign(Frame* f, ValPtr v) override;
    ExprPtr MakeLvalue() override;

    // Need to override Eval since it can take a vector arg but does
    // not necessarily return a vector.
    ValPtr Eval(Frame* f) const override;

    bool IsSlice() const { return is_slice; }
    bool IsInsideWhen() const { return is_inside_when; }

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool HasReducedOps(Reducer* c) const override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

protected:
    ValPtr Fold(Val* v1, Val* v2) const override;

    void ExprDescribe(ODesc* d) const override;

    bool is_slice;
    bool is_inside_when;
    bool is_pattern_table = false;
};

// The following execute the heart of IndexExpr functionality for
// vector slices and strings.

// Extracts a slice of a vector, where the span of the slice is specified
// by a list of (exactly) two values.  This is how the interpreter develops
// the components of a slice.
extern VectorValPtr index_slice(VectorVal* vect, const ListVal* lv);

// Lower-level access to the slice, where its span is expressed
// directly as integers.
extern VectorValPtr index_slice(VectorVal* vect, int first, int last);

// Returns a subset of a string, with the span specified by a list of
// (exactly) two values.
extern StringValPtr index_string(const String* s, const ListVal* lv);

// Returns a vector indexed by a boolean vector.
extern VectorValPtr vector_bool_select(VectorTypePtr vt, const VectorVal* v1, const VectorVal* v2);

// Returns a vector indexed by a numeric vector (which specifies the
// indices to select).
extern VectorValPtr vector_int_select(VectorTypePtr vt, const VectorVal* v1, const VectorVal* v2);

// The following is used for index expressions that occur inside "when"
// clauses.  It tracks all the results produced by evaluating indexing
// aggregates, so that if any of them are Modifiable(), the associated
// Trigger can register interest in changes to them.
//
// TODO: One Fine Day we should do the equivalent for accessing fields
// in records, too.
class IndexExprWhen final : public IndexExpr {
public:
    static inline std::vector<ValPtr> results = {};
    static inline int evaluating = 0;

    static void StartEval() { ++evaluating; }

    static void EndEval() { --evaluating; }

    static std::vector<ValPtr> TakeAllResults() {
        auto rval = std::move(results);
        results = {};
        return rval;
    }

    IndexExprWhen(ExprPtr op1, ListExprPtr op2, bool is_slice = false)
        : IndexExpr(std::move(op1), std::move(op2), is_slice, true) {}

    ValPtr Eval(Frame* f) const override {
        auto v = IndexExpr::Eval(f);

        if ( v && evaluating > 0 )
            results.emplace_back(v);

        return v;
    }

    // Optimization-related:
    ExprPtr Duplicate() override;
};

class FieldExpr final : public UnaryExpr {
public:
    FieldExpr(ExprPtr op, const char* field_name);
    ~FieldExpr() override;

    int Field() const { return field; }
    const char* FieldName() const { return field_name; }

    bool CanDel() const override;

    void Assign(Frame* f, ValPtr v) override;
    ValPtr Delete(Frame* f) override;

    ExprPtr MakeLvalue() override;

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    void Assign(ValPtr lhs, ValPtr rhs);
    ValPtr Fold(Val* v) const override;

    void ExprDescribe(ODesc* d) const override;

    const char* field_name;
    const TypeDecl* td = nullptr;
    int field = -1;
};

// "rec?$fieldname" is true if the value of $fieldname in rec is not nil.
// "rec?$$attrname" is true if the attribute attrname is not nil.
class HasFieldExpr final : public UnaryExpr {
public:
    HasFieldExpr(ExprPtr op, const char* field_name);
    ~HasFieldExpr() override;

    const char* FieldName() const { return field_name; }
    int Field() const { return field; }

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool IsReduced(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr Fold(Val* v) const override;

    void ExprDescribe(ODesc* d) const override;

    const char* field_name = nullptr;
    int field = -1;
};

class RecordConstructorExpr final : public Expr {
public:
    explicit RecordConstructorExpr(ListExprPtr constructor_list);

    // This form is used to construct records of a known (ultimate) type.
    // The flag allows skipping of checking for mandatory fields, for
    // script optimization that may elide them.
    explicit RecordConstructorExpr(RecordTypePtr known_rt, ListExprPtr constructor_list,
                                   bool check_mandatory_fields = true);

    ListExprPtr Op() const { return op; }
    const auto& Map() const { return map; }

    ValPtr Eval(Frame* f) const override;

    bool IsPure() const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    ExprPtr Inline(Inliner* inl) override;

    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

protected:
    void ExprDescribe(ODesc* d) const override;

    ListExprPtr op;
    std::optional<std::vector<int>> map;
};

class TableConstructorExpr final : public UnaryExpr {
public:
    TableConstructorExpr(ListExprPtr constructor_list, std::unique_ptr<std::vector<AttrPtr>> attrs,
                         TypePtr arg_type = nullptr, AttributesPtr arg_attrs = nullptr);

    void SetAttrs(AttributesPtr _attrs) { attrs = std::move(_attrs); }
    const AttributesPtr& GetAttrs() const { return attrs; }

    ValPtr Eval(Frame* f) const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

protected:
    void ExprDescribe(ODesc* d) const override;

    AttributesPtr attrs;
};

class SetConstructorExpr final : public UnaryExpr {
public:
    SetConstructorExpr(ListExprPtr constructor_list, std::unique_ptr<std::vector<AttrPtr>> attrs,
                       TypePtr arg_type = nullptr, AttributesPtr arg_attrs = nullptr);

    void SetAttrs(AttributesPtr _attrs) { attrs = std::move(_attrs); }
    const AttributesPtr& GetAttrs() const { return attrs; }

    ValPtr Eval(Frame* f) const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

protected:
    void ExprDescribe(ODesc* d) const override;

    AttributesPtr attrs;
};

class VectorConstructorExpr final : public UnaryExpr {
public:
    explicit VectorConstructorExpr(ListExprPtr constructor_list, TypePtr arg_type = nullptr);

    ValPtr Eval(Frame* f) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool HasReducedOps(Reducer* c) const override;

protected:
    void ExprDescribe(ODesc* d) const override;
};

class FieldAssignExpr final : public UnaryExpr {
public:
    FieldAssignExpr(const char* field_name, ExprPtr value);

    const char* FieldName() const { return field_name.c_str(); }

    // When these are first constructed, we don't know the type.
    // The following method coerces/promotes the assignment expression
    // as needed, once we do know the type.
    //
    // Returns true on success, false if the types were incompatible
    // (in which case an error is reported).
    bool PromoteTo(TypePtr t);

    bool IsRecordElement(TypeDecl* td) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    bool WillTransform(Reducer* c) const override { return true; }
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    void ExprDescribe(ODesc* d) const override;

    std::string field_name;
};

class ArithCoerceExpr final : public UnaryExpr {
public:
    ArithCoerceExpr(ExprPtr op, TypeTag t);

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr FoldSingleVal(ValPtr v, const TypePtr& t) const;
    ValPtr Fold(Val* v) const override;
};

class RecordCoerceExpr final : public UnaryExpr {
public:
    RecordCoerceExpr(ExprPtr op, RecordTypePtr r);

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool IsReduced(Reducer* c) const override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

    const std::vector<int>& Map() const { return map; }

protected:
    ValPtr Fold(Val* v) const override;

    // For each super-record slot, gives subrecord slot with which to
    // fill it.
    std::vector<int> map;
};

extern RecordValPtr coerce_to_record(RecordTypePtr rt, Val* v, const std::vector<int>& map);

class TableCoerceExpr final : public UnaryExpr {
public:
    TableCoerceExpr(ExprPtr op, TableTypePtr r, bool type_check = true);
    ~TableCoerceExpr() override = default;

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    ValPtr Fold(Val* v) const override;
};

class VectorCoerceExpr final : public UnaryExpr {
public:
    VectorCoerceExpr(ExprPtr op, VectorTypePtr v);
    ~VectorCoerceExpr() override = default;

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool IsReduced(Reducer* c) const override;
    bool WillTransform(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr Fold(Val* v) const override;
};

class ScheduleTimer final : public Timer {
public:
    ScheduleTimer(const EventHandlerPtr& event, zeek::Args args, double t);
    ~ScheduleTimer() override = default;

    void Dispatch(double t, bool is_expire) override;

protected:
    EventHandlerPtr event;
    zeek::Args args;
};

class ScheduleExpr final : public Expr {
public:
    ScheduleExpr(ExprPtr when, EventExprPtr event);

    bool IsPure() const override { return false; }

    ValPtr Eval(Frame* f) const override;

    Expr* When() const { return when.get(); }
    EventExpr* Event() const { return event.get(); }

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    ExprPtr Inline(Inliner* inl) override;

    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

    ExprPtr GetOp1() const final;
    ExprPtr GetOp2() const final;

    void SetOp1(ExprPtr _op) final;
    void SetOp2(ExprPtr _op) final;

protected:
    void ExprDescribe(ODesc* d) const override;

    ExprPtr when;
    EventExprPtr event;
};

class InExpr final : public BinaryExpr {
public:
    InExpr(ExprPtr op1, ExprPtr op2);

    // Optimization-related:
    ExprPtr Duplicate() override;

    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr Fold(Val* v1, Val* v2) const override;
};

class CallExpr final : public Expr {
public:
    CallExpr(ExprPtr func, ListExprPtr args, bool in_hook = false, bool in_when = false);

    Expr* Func() const { return func.get(); }
    ExprPtr FuncPtr() const { return func; }
    ListExpr* Args() const { return args.get(); }
    ListExprPtr ArgsPtr() const { return args; }

    bool IsPure() const override;
    bool IsInWhen() const { return in_when; }

    ValPtr Eval(Frame* f) const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    ExprPtr Inline(Inliner* inl) override;

    bool IsReduced(Reducer* c) const override;
    bool WillTransform(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

protected:
    void ExprDescribe(ODesc* d) const override;

    bool IsFoldableBiF() const;
    bool AllConstArgs() const;
    bool CheckForBuiltin() const;
    bool IsEmptyHook() const;
    ExprPtr TransformToBuiltin();

    ExprPtr func;
    ListExprPtr args;
    bool in_when;
};

/**
 * Class that represents an anonymous function expression in Zeek.
 * On evaluation, captures the frame that it is evaluated in. This becomes
 * the closure for the instance of the function that it creates.
 */
class LambdaExpr final : public Expr {
public:
    LambdaExpr(FunctionIngredientsPtr ingredients, IDPList outer_ids, std::string name = "",
               StmtPtr when_parent = nullptr);

    const std::string& Name() const { return my_name; }

    const IDPList& OuterIDs() const { return outer_ids; }

    // Lambda's potentially have their own private copy of captures,
    // to enable updates to the set during script optimization.
    using CaptureList = std::vector<FuncType::Capture>;
    const std::optional<CaptureList>& GetCaptures() const { return captures; }

    ValPtr Eval(Frame* f) const override;
    TraversalCode Traverse(TraversalCallback* cb) const override;

    ScopePtr GetScope() const;

    // Optimization-related:
    ExprPtr Duplicate() override;

    const ScriptFuncPtr& PrimaryFunc() const { return primary_func; }

    const FunctionIngredientsPtr& Ingredients() const { return ingredients; }

    void ReplaceBody(StmtPtr new_body);

    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

protected:
    // Constructor used for script optimization.
    LambdaExpr(LambdaExpr* orig);

    void ExprDescribe(ODesc* d) const override;

private:
    friend class WhenInfo;

    // "Private" captures are captures that correspond to "when"
    // condition locals.  These aren't true captures in that they
    // don't come from the outer frame when the lambda is constructed,
    // but they otherwise behave like captures in that they persist
    // across function invocations.
    void SetPrivateCaptures(const IDSet& pcaps) { private_captures = pcaps; }

    bool CheckCaptures(StmtPtr when_parent);
    void BuildName();

    void UpdateCaptures(Reducer* c);

    FunctionIngredientsPtr ingredients;
    ScriptFuncPtr primary_func;
    IDPtr lambda_id;
    IDPList outer_ids;
    std::optional<CaptureList> captures;
    IDSet private_captures;

    std::string my_name;
};

// This comes before EventExpr so that EventExpr::GetOp1 can return its
// arguments as convertible to ExprPtr.
class ListExpr : public Expr {
public:
    ListExpr();
    explicit ListExpr(ExprPtr e);
    ~ListExpr() override;

    void Append(ExprPtr e);

    const ExprPList& Exprs() const { return exprs; }
    ExprPList& Exprs() { return exprs; }

    // True if the entire list represents pure values.
    bool IsPure() const override;

    // True if the entire list represents constant values.
    bool HasConstantOps() const;

    ValPtr Eval(Frame* f) const override;

    TypePtr InitType() const override;
    ExprPtr MakeLvalue() override;
    void Assign(Frame* f, ValPtr v) override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    ExprPtr Inline(Inliner* inl) override;

    bool IsReduced(Reducer* c) const override;
    bool HasReducedOps(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

protected:
    void ExprDescribe(ODesc* d) const override;

    ExprPList exprs;
};

class EventExpr final : public Expr {
public:
    EventExpr(const char* name, ListExprPtr args);

    const char* Name() const { return name.c_str(); }
    ListExpr* Args() const { return args.get(); }
    EventHandlerPtr Handler() const { return handler; }

    ValPtr Eval(Frame* f) const override;

    TraversalCode Traverse(TraversalCallback* cb) const override;

    // Optimization-related:
    ExprPtr Duplicate() override;
    ExprPtr Inline(Inliner* inl) override;

    bool IsReduced(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
    StmtPtr ReduceToSingletons(Reducer* c) override;

    ExprPtr GetOp1() const final { return args; }
    void SetOp1(ExprPtr _op) final { args = {NewRef{}, _op->AsListExpr()}; }

protected:
    void ExprDescribe(ODesc* d) const override;

    std::string name;
    EventHandlerPtr handler;
    ListExprPtr args;
};

class RecordAssignExpr final : public ListExpr {
public:
    RecordAssignExpr(const ExprPtr& record, const ExprPtr& init_list, bool is_init);
};

class CastExpr final : public UnaryExpr {
public:
    CastExpr(ExprPtr op, TypePtr t);

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    ValPtr Fold(Val* v) const override;
    void ExprDescribe(ODesc* d) const override;
};

// Returns the value 'v' cast to type 't'.  On an error, returns nil
// and populates "error" with an error message.
extern ValPtr cast_value(ValPtr v, const TypePtr& t, std::string& error);

class IsExpr final : public UnaryExpr {
public:
    IsExpr(ExprPtr op, TypePtr t);

    const TypePtr& TestType() const { return t; }

    // Optimization-related:
    ExprPtr Duplicate() override;

protected:
    ValPtr Fold(Val* v) const override;
    void ExprDescribe(ODesc* d) const override;

private:
    TypePtr t;
};

// Expression to explicitly capture conversion to an "any" type, rather
// than it occurring implicitly during script interpretation.
class CoerceToAnyExpr : public UnaryExpr {
public:
    CoerceToAnyExpr(ExprPtr op);

    bool IsReduced(Reducer* c) const override;
    ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;

protected:
    ValPtr Fold(Val* v) const override;

    ExprPtr Duplicate() override;
};

// Same, but for conversion from an "any" type.
class CoerceFromAnyExpr : public UnaryExpr {
public:
    CoerceFromAnyExpr(ExprPtr op, TypePtr to_type);

protected:
    ValPtr Fold(Val* v) const override;

    ExprPtr Duplicate() override;
};

// Assigns v1[v2] = v3.  Returns an error message, or nullptr on success.
// Factored out so that compiled code can call it as well as the interpreter.
extern const char* assign_to_index(ValPtr v1, ValPtr v2, ValPtr v3, bool& iterators_invalidated);

inline Val* Expr::ExprVal() const {
    if ( ! IsConst() )
        BadTag("ExprVal::Val", expr_name(tag), expr_name(EXPR_CONST));
    return ((ConstExpr*)this)->Value();
}

// Decides whether to return an AssignExpr or a RecordAssignExpr.
extern ExprPtr get_assign_expr(ExprPtr op1, ExprPtr op2, bool is_init);

// Takes a RHS constructor list and returns a version with any embedded
// indices within it (used to concisely represent multiple set/table entries)
// expanded.
//
// Second argument gives the type that the list will expand to, if known.
extern ListExprPtr expand_op(ListExprPtr op, const TypePtr& t);

/**
 * Type-check the given expression(s) against the given type(s).  Complain
 * if the expression cannot match the given type, returning nullptr;
 * otherwise, returns an expression reflecting the promotion.
 *
 * The second, third, and fourth forms are for promoting a list of
 * expressions (which is updated in place) to either match a list of
 * types or a single type.
 *
 * Note, the type is not "const" because it can be ref'd.
 */
extern ExprPtr check_and_promote_expr(ExprPtr e, TypePtr t);

extern bool check_and_promote_exprs(ListExpr* elements, const TypeListPtr& types);
extern bool check_and_promote_args(ListExpr* args, const RecordType* types);
extern bool check_and_promote_exprs_to_type(ListExpr* elements, TypePtr type);

// Returns a ListExpr simplified down to a list a values, or nil
// if they couldn't all be reduced.
extern std::optional<std::vector<ValPtr>> eval_list(Frame* f, const ListExpr* l);

// Returns true if e1 is "greater" than e2 - here "greater" is just
// a heuristic, used with commutative operators to put them into
// a canonical form.
extern bool expr_greater(const Expr* e1, const Expr* e2);

// True if the given Expr* has a vector type
inline bool is_vector(Expr* e) { return e->GetType()->Tag() == TYPE_VECTOR; }
inline bool is_vector(const ExprPtr& e) { return is_vector(e.get()); }

// True if the given Expr* has a list type
inline bool is_list(Expr* e) { return e->GetType()->Tag() == TYPE_LIST; }

inline bool is_list(const ExprPtr& e) { return is_list(e.get()); }

} // namespace detail
} // namespace zeek
