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

#include "zeek/script_opt/CPP/Compile.h"

namespace zeek::detail {

using namespace std;

string CPPCompile::GenExprs(const Expr* e) {
    string gen;
    if ( e->Tag() == EXPR_LIST )
        gen = GenListExpr(e, GEN_VAL_PTR, true);
    else
        gen = GenExpr(e, GEN_VAL_PTR);

    return string("{ ") + gen + " }";
}

string CPPCompile::GenListExpr(const Expr* e, GenType gt, bool nested) {
    const auto& exprs = e->AsListExpr()->Exprs();
    string gen;

    int n = exprs.size();

    for ( auto i = 0; i < n; ++i ) {
        auto e_i = exprs[i];
        auto gen_i = GenExpr(e_i, gt);

        if ( nested && e_i->Tag() == EXPR_LIST )
            // These are table or set indices.
            gen_i = util::fmt("index_val__CPP({%s})", gen_i.c_str());

        gen += gen_i;

        if ( i < n - 1 )
            gen += ", ";
    }

    return gen;
}

string CPPCompile::GenExpr(const Expr* e, GenType gt, bool top_level) {
    string gen;

    switch ( e->Tag() ) {
        case EXPR_NAME: return GenNameExpr(e->AsNameExpr(), gt);
        case EXPR_CONST: return GenConstExpr(e->AsConstExpr(), gt);

        case EXPR_CLONE:
            gen = GenExpr(e->GetOp1(), GEN_VAL_PTR) + "->Clone()";
            return GenericValPtrToGT(gen, e->GetType(), gt);

        case EXPR_AGGR_ADD: return GenAggrAdd(e);
        case EXPR_AGGR_DEL: return GenAggrDel(e);

        case EXPR_INCR:
        case EXPR_DECR: return GenIncrExpr(e, gt, e->Tag() == EXPR_INCR, top_level);

        case EXPR_NOT: return GenUnary(e, gt, "!", "not");
        case EXPR_COMPLEMENT: return GenUnary(e, gt, "~", "comp");
        case EXPR_POSITIVE: return GenUnary(e, gt, "+", "pos");
        case EXPR_NEGATE: return GenUnary(e, gt, "-", "neg");

        case EXPR_ADD: return GenBinary(e, gt, "+", "add");
        case EXPR_SUB: return GenBinary(e, gt, "-", "sub");
        case EXPR_TIMES: return GenBinary(e, gt, "*", "mul");
        case EXPR_DIVIDE:
        case EXPR_MASK: // later code will split into addr masking
            return GenBinary(e, gt, "/", "div");
        case EXPR_MOD: return GenBinary(e, gt, "%", "mod");
        case EXPR_AND: return GenBinary(e, gt, "&", "and");
        case EXPR_OR: return GenBinary(e, gt, "|", "or");
        case EXPR_XOR: return GenBinary(e, gt, "^", "xor");
        case EXPR_LSHIFT: return GenBinary(e, gt, "<<", "lshift");
        case EXPR_RSHIFT: return GenBinary(e, gt, ">>", "rshift");
        case EXPR_AND_AND: return GenBinary(e, gt, "&&", "andand");
        case EXPR_OR_OR: return GenBinary(e, gt, "||", "oror");
        case EXPR_LT: return GenBinary(e, gt, "<", "lt");
        case EXPR_LE: return GenBinary(e, gt, "<=", "le");
        case EXPR_GE: return GenBinary(e, gt, ">=", "ge");
        case EXPR_GT: return GenBinary(e, gt, ">", "gt");

        case EXPR_EQ: return GenEQ(e, gt, "==", "eq");
        case EXPR_NE: return GenEQ(e, gt, "!=", "ne");

        case EXPR_COND: return GenCondExpr(e, gt);
        case EXPR_CALL: return GenCallExpr(e->AsCallExpr(), gt, top_level);
        case EXPR_LIST: return GenListExpr(e, gt, false);
        case EXPR_IN: return GenInExpr(e, gt);
        case EXPR_FIELD: return GenFieldExpr(e->AsFieldExpr(), gt);
        case EXPR_HAS_FIELD: return GenHasFieldExpr(e->AsHasFieldExpr(), gt);
        case EXPR_INDEX: return GenIndexExpr(e, gt);
        case EXPR_ASSIGN: return GenAssignExpr(e, gt, top_level);
        case EXPR_ADD_TO: return GenAddToExpr(e, gt, top_level);
        case EXPR_REMOVE_FROM: return GenRemoveFromExpr(e, gt, top_level);
        case EXPR_REF: return GenExpr(e->GetOp1(), gt);
        case EXPR_SIZE: return GenSizeExpr(e, gt);
        case EXPR_SCHEDULE: return GenScheduleExpr(e);
        case EXPR_LAMBDA: return GenLambdaExpr(e);
        case EXPR_IS: return GenIsExpr(e, gt);

        case EXPR_ARITH_COERCE: return GenArithCoerceExpr(e, gt);
        case EXPR_RECORD_COERCE: return GenRecordCoerceExpr(e);
        case EXPR_TABLE_COERCE: return GenTableCoerceExpr(e);
        case EXPR_VECTOR_COERCE: return GenVectorCoerceExpr(e);

        case EXPR_RECORD_CONSTRUCTOR: return GenRecordConstructorExpr(e);
        case EXPR_SET_CONSTRUCTOR: return GenSetConstructorExpr(e);
        case EXPR_TABLE_CONSTRUCTOR: return GenTableConstructorExpr(e);
        case EXPR_VECTOR_CONSTRUCTOR: return GenVectorConstructorExpr(e);

        case EXPR_EVENT:
            // These should not wind up being directly generated,
            // but instead deconstructed in the context of either
            // a "schedule" expression or an "event" statement.
            ASSERT(0);

        case EXPR_CAST:
            gen = string("cast_value_to_type__CPP(") + GenExpr(e->GetOp1(), GEN_VAL_PTR) + ", " +
                  GenTypeName(e->GetType()) + ")";
            return GenericValPtrToGT(gen, e->GetType(), gt);

        case EXPR_CAN_CONVERT: {
            auto cc = e->AsCanConvertExpr();
            auto cct = cc->ConversionType();

            gen = "(" + string("attempt_to_cast_value_to_type(") + GenExpr(e->GetOp1(), GEN_VAL_PTR) + ".get(), " +
                  GenTypeName(cct) + ".get()) != nullptr)";

            return NativeToGT(gen, e->GetType(), gt);
        }

        case EXPR_TO_ANY_COERCE: return GenExpr(e->GetOp1(), GEN_VAL_PTR);

        case EXPR_FROM_ANY_COERCE:
            gen = string("from_any__CPP(") + GenExpr(e->GetOp1(), GEN_VAL_PTR) + ", " + GenTypeName(e->GetType()) + ")";
            return GenericValPtrToGT(gen, e->GetType(), gt);

        case EXPR_FROM_ANY_VEC_COERCE:
            gen = string("from_any_vec__CPP(") + GenExpr(e->GetOp1(), GEN_VAL_PTR) + ", " +
                  GenTypeName(e->GetType()->Yield()) + ")";
            return GenericValPtrToGT(gen, e->GetType(), gt);

        case EXPR_FIELD_ASSIGN:
        case EXPR_INDEX_SLICE_ASSIGN:
        case EXPR_INLINE:
            // These are only generated for reduced ASTs, which
            // we shouldn't be compiling.
            ASSERT(0);

        default:
            // Intended to catch errors in overlooking the possible
            // expressions that might appear.
            return "EXPR";
    }
}

string CPPCompile::GenNameExpr(const NameExpr* ne, GenType gt) {
    const auto& t = ne->GetType();
    const auto& n = ne->IdPtr();
    bool is_global_var = global_vars.contains(n);

    if ( t->Tag() == TYPE_FUNC && ! is_global_var ) {
        auto func = n->Name();
        if ( globals.contains(func) && ! pfs->BiFGlobals().contains(n) )
            return GenericValPtrToGT(IDNameStr(n), t, gt);
    }

    if ( is_global_var ) {
        string gen;

        if ( n->IsType() )
            gen = string("make_intrusive<TypeVal>(") + globals[n->Name()] + "->GetType(), true)";

        else
            gen = globals[n->Name()] + "->GetVal()";

        return GenericValPtrToGT(gen, t, gt);
    }

    return NativeToGT(IDNameStr(n), t, gt);
}

string CPPCompile::GenConstExpr(const ConstExpr* c, GenType gt) {
    const auto& t = c->GetType();

    if ( ! IsNativeType(t) ) {
        auto v = c->ValuePtr();
        (void)RegisterConstant(v);
        return NativeToGT(const_vals[v.get()]->Name(), t, gt);
    }

    return NativeToGT(GenVal(c->ValuePtr()), t, gt);
}

string CPPCompile::GenAggrAdd(const Expr* e) {
    auto op = e->GetOp1();
    auto aggr = GenExpr(op->GetOp1(), GEN_DONT_CARE);
    auto indices = GenExpr(op->GetOp2(), GEN_VAL_PTR);

    return "add_element__CPP(" + aggr + ", index_val__CPP({" + indices + "}))";
}

string CPPCompile::GenAggrDel(const Expr* e) {
    auto op = e->GetOp1();

    if ( op->Tag() == EXPR_NAME ) {
        auto aggr_gen = GenExpr(op, GEN_VAL_PTR);

        if ( op->GetType()->Tag() == TYPE_TABLE )
            return aggr_gen + "->RemoveAll()";
        else
            return aggr_gen + "->Resize(0)";
    }

    auto aggr = op->GetOp1();
    auto aggr_gen = GenExpr(aggr, GEN_VAL_PTR);

    if ( op->Tag() == EXPR_INDEX ) {
        auto indices = GenExpr(op->GetOp2(), GEN_VAL_PTR);
        return "remove_element__CPP(" + aggr_gen + ", index_val__CPP({" + indices + "}))";
    }

    ASSERT(op->Tag() == EXPR_FIELD);
    auto field = GenField(aggr, op->AsFieldExpr()->Field());
    return aggr_gen + "->Remove(" + field + ")";
}

string CPPCompile::GenIncrExpr(const Expr* e, GenType gt, bool is_incr, bool top_level) {
    // For compound operands (table indexing, record fields),
    // Zeek's interpreter will actually evaluate the operand
    // twice, so easiest is to just transform this node
    // into the expanded equivalent.
    auto op = e->GetOp1();
    auto one = e->GetType()->InternalType() == TYPE_INTERNAL_INT ? val_mgr->Int(1) : val_mgr->Count(1);
    auto one_e = make_intrusive<ConstExpr>(one);

    ExprPtr rhs;
    if ( is_incr )
        rhs = make_intrusive<AddExpr>(op, one_e);
    else
        rhs = make_intrusive<SubExpr>(op, one_e);

    auto assign = make_intrusive<AssignExpr>(op, rhs, false, nullptr, nullptr, false);

    // Make sure any newly created types are known to
    // the profiler.
    (void)pfs->HashType(one_e->GetType());
    (void)pfs->HashType(rhs->GetType());
    (void)pfs->HashType(assign->GetType());

    auto gen = GenExpr(assign, GEN_DONT_CARE, top_level);

    if ( ! top_level )
        gen = "(" + gen + ", " + GenExpr(op, gt) + ")";

    return gen;
}

string CPPCompile::GenCondExpr(const Expr* e, GenType gt) {
    auto op1 = e->GetOp1();
    auto op2 = e->GetOp2();
    auto op3 = e->GetOp3();

    auto gen1 = GenExpr(op1, GEN_NATIVE);
    auto gen2 = GenExpr(op2, gt);
    auto gen3 = GenExpr(op3, gt);

    if ( op1->GetType()->Tag() == TYPE_VECTOR )
        return string("vector_select__CPP(") + gen1 + ", " + gen2 + ", " + gen3 + ")";

    return string("((") + gen1 + ") ? (" + gen2 + ") : (" + gen3 + "))";
}

string CPPCompile::GenCallExpr(const CallExpr* c, GenType gt, bool top_level) {
    const auto& t = c->GetType();
    auto f = c->Func();
    auto args_l = c->Args();
    bool is_async = c->IsInWhen();

    auto gen = GenExpr(f, GEN_DONT_CARE);

    if ( f->Tag() == EXPR_NAME ) {
        const auto& f_id = f->AsNameExpr()->IdPtr();
        const auto& params = f_id->GetType()->AsFuncType()->Params();
        auto id_name = f_id->Name();
        auto nargs = args_l->Exprs().length();

        bool is_compiled = compiled_simple_funcs.contains(id_name);
        bool was_compiled = hashed_funcs.contains(id_name);
        bool is_variadic = params->NumFields() == 1 && nargs != 1;

        if ( ! is_async && ! is_variadic && (is_compiled || was_compiled) ) { // Can call directly.
            string fname;

            if ( was_compiled )
                fname = hashed_funcs[id_name];
            else
                fname = compiled_simple_funcs[id_name];

            if ( nargs > 0 )
                gen = fname + "(" + GenArgs(params, args_l) + ", f__CPP)";
            else
                gen = fname + "(f__CPP)";

            return NativeToGT(gen, t, gt);
        }

        // If the function isn't a BiF, then it will have been
        // declared as a ValPtr (or a FuncValPtr, if a local),
        // and we need to convert it to a Func*.
        //
        // If it is a BiF *that's also a global variable*, then
        // we need to look up the BiF version of the global.
        if ( ! pfs->BiFGlobals().contains(f_id) )
            gen += +"->AsFunc()";

        else if ( accessed_globals.contains(f_id) )
            // The BiF version has an extra "_", per AddBiF(..., true).
            gen = globals[string(id_name) + "_"];
    }

    else
        // Indirect call.
        gen = string("(") + gen + ")->AsFunc()";

    string invoke_func;

    if ( is_async )
        invoke_func = "when_invoke__CPP";
    else if ( top_level || t->Tag() == TYPE_VOID ) {
        ASSERT(top_level);
        invoke_func = "invoke_void__CPP";
    }
    else
        invoke_func = "invoke__CPP";

    auto args_list = string(", {") + GenExpr(args_l, GEN_VAL_PTR) + "}";
    auto invoker = invoke_func + "(" + gen + args_list + ", f__CPP";

    if ( is_async )
        invoker += ", (void*) &" + body_name;

    invoker += ')';

    if ( top_level )
        // No need to use accessor.
        return invoker;

    if ( IsNativeType(t) && gt != GEN_VAL_PTR )
        return invoker + NativeAccessor(t);

    return GenericValPtrToGT(invoker, t, gt);
}

string CPPCompile::GenInExpr(const Expr* e, GenType gt) {
    auto op1 = e->GetOp1();
    auto op2 = e->GetOp2();

    auto t1 = op1->GetType();
    auto t2 = op2->GetType();

    auto tag1 = t1->Tag();
    auto tag2 = t2->Tag();

    string gen;

    if ( tag1 == TYPE_STRING && tag2 == TYPE_TABLE && t2->AsTableType()->IsPatternIndex() )
        gen = GenExpr(op2, GEN_DONT_CARE) + "->MatchPattern(" + GenExpr(op1, GEN_NATIVE) + ")";
    else if ( tag1 == TYPE_PATTERN )
        gen = string("(") + GenExpr(op1, GEN_DONT_CARE) + ")->MatchAnywhere(" + GenExpr(op2, GEN_DONT_CARE) +
              "->AsString())";

    else if ( tag2 == TYPE_STRING )
        gen = string("str_in__CPP(") + GenExpr(op1, GEN_DONT_CARE) + "->AsString(), " + GenExpr(op2, GEN_DONT_CARE) +
              "->AsString())";

    else if ( tag1 == TYPE_ADDR && tag2 == TYPE_SUBNET )
        gen = string("(") + GenExpr(op2, GEN_DONT_CARE) + ")->Contains(" + GenExpr(op1, GEN_VAL_PTR) + "->Get())";

    else if ( tag2 == TYPE_VECTOR )
        gen = GenExpr(op2, GEN_DONT_CARE) + "->Has(" + GenExpr(op1, GEN_NATIVE) + ")";

    else
        gen = string("(") + GenExpr(op2, GEN_DONT_CARE) + "->Find(index_val__CPP({" + GenExpr(op1, GEN_VAL_PTR) +
              "})) ? true : false)";

    return NativeToGT(gen, e->GetType(), gt);
}

string CPPCompile::GenFieldExpr(const FieldExpr* fe, GenType gt) {
    auto& t = fe->GetType();
    auto r = fe->GetOp1();
    auto f = fe->Field();
    auto f_s = GenField(r, f);

    string gen;

    if ( IsNativeType(t) ) {
        auto nt = TypeName(t);
        gen = string("field_access_") + nt + "__CPP(" + GenExpr(r, GEN_VAL_PTR) + ", " + f_s + ")";
        return NativeToGT(gen, t, gt);
    }

    switch ( t->Tag() ) {
        case TYPE_FILE:
        case TYPE_FUNC:
        case TYPE_VOID:
            gen = string("field_access__CPP(") + GenExpr(r, GEN_VAL_PTR) + ", " + f_s + ")";
            return GenericValPtrToGT(gen, t, gt);

        default: {
            auto nt = TypeName(t);
            return string("field_access_") + nt + "__CPP(" + GenExpr(r, GEN_VAL_PTR) + ", " + f_s + ")";
        }
    }
}

string CPPCompile::GenHasFieldExpr(const HasFieldExpr* hfe, GenType gt) {
    auto r = hfe->GetOp1();
    auto f = hfe->Field();
    auto f_s = GenField(r, f);

    auto gen = GenExpr(r, GEN_DONT_CARE) + "->HasField(" + f_s + ")";

    return NativeToGT(gen, hfe->GetType(), gt);
}

string CPPCompile::GenIndexExpr(const Expr* e, GenType gt) {
    auto aggr = e->GetOp1();
    const auto& aggr_t = aggr->GetType();
    bool inside_when = e->AsIndexExpr()->IsInsideWhen();

    string gen;
    string func;

    if ( aggr_t->Tag() == TYPE_TABLE ) {
        auto ind_expr = e->GetOp2()->AsListExpr()->Exprs()[0];
        auto is_pat_str_ind = false;

        if ( aggr_t->AsTableType()->IsPatternIndex() && ind_expr->GetType()->Tag() == TYPE_STRING )
            is_pat_str_ind = true;

        if ( inside_when ) {
            if ( is_pat_str_ind )
                func = "when_index_patstr__CPP";
            else
                func = "when_index_table__CPP";
        }
        else {
            if ( is_pat_str_ind )
                func = "index_patstr_table__CPP";
            else
                func = "index_table__CPP";
        }

        gen = func + "(" + GenExpr(aggr, GEN_NATIVE) + ", {" + GenExpr(e->GetOp2(), GEN_VAL_PTR) + "})";
    }

    else if ( aggr_t->Tag() == TYPE_VECTOR ) {
        const auto& op2 = e->GetOp2();
        const auto& t2 = op2->GetType();
        ASSERT(t2->Tag() == TYPE_LIST);

        if ( t2->Tag() == TYPE_LIST && t2->AsTypeList()->GetTypes().size() == 2 ) {
            auto& inds = op2->AsListExpr()->Exprs();
            auto first = inds[0];
            auto last = inds[1];
            func = inside_when ? "when_index_slice__CPP" : "index_slice";
            gen = func + "(" + GenExpr(aggr, GEN_VAL_PTR) + ".get(), " + GenExpr(first, GEN_NATIVE) + ", " +
                  GenExpr(last, GEN_NATIVE) + ")";
        }
        else {
            func = inside_when ? "when_index_vec__CPP" : "index_vec__CPP";
            gen = func + "(" + GenExpr(aggr, GEN_NATIVE) + ", " + GenExpr(e->GetOp2(), GEN_NATIVE) + ")";
        }
    }

    else if ( aggr_t->Tag() == TYPE_STRING )
        gen =
            string("index_string__CPP(") + GenExpr(aggr, GEN_NATIVE) + ", {" + GenExpr(e->GetOp2(), GEN_VAL_PTR) + "})";

    return GenericValPtrToGT(gen, e->GetType(), gt);
}

string CPPCompile::GenAssignExpr(const Expr* e, GenType gt, bool top_level) {
    auto op1 = e->GetOp1()->AsRefExprPtr()->GetOp1();
    auto op2 = e->GetOp2();

    const auto& t1 = op1->GetType();
    const auto& t2 = op2->GetType();

    auto rhs_native = GenExpr(op2, GEN_NATIVE);
    auto rhs_val_ptr = GenExpr(op2, GEN_VAL_PTR);

    auto lhs_is_any = t1->Tag() == TYPE_ANY;
    auto rhs_is_any = t2->Tag() == TYPE_ANY;

    if ( lhs_is_any && ! rhs_is_any )
        rhs_native = rhs_val_ptr;

    if ( rhs_is_any && ! lhs_is_any && t1->Tag() != TYPE_LIST )
        rhs_native = rhs_val_ptr = GenericValPtrToGT(rhs_val_ptr, t1, GEN_NATIVE);

    auto gen = GenAssign(op1, op2, rhs_native, rhs_val_ptr, gt, top_level);
    auto av = e->AsAssignExpr()->AssignVal();
    if ( av ) {
        auto av_e = make_intrusive<ConstExpr>(av);
        auto av_gen = GenExpr(av_e, gt, false);
        return string("(") + gen + ", " + av_gen + ")";
    }
    else
        return gen;
}

string CPPCompile::GenAddToExpr(const Expr* e, GenType gt, bool top_level) {
    const auto& t = e->GetType();
    auto lhs = e->GetOp1();
    auto rhs = e->GetOp2();

    std::string add_to_func;

    if ( t->Tag() == TYPE_VECTOR ) {
        auto& rt = rhs->GetType();

        ASSERT(e->Tag() == EXPR_ADD_TO);
        if ( static_cast<const AddToExpr*>(e)->IsVectorElemAppend() )
            add_to_func = "vector_append__CPP";
        else
            add_to_func = "vector_vec_append__CPP";
    }

    else if ( t->Tag() == TYPE_PATTERN )
        add_to_func = "re_append__CPP";

    else if ( t->Tag() == TYPE_TABLE )
        add_to_func = "table_append__CPP";

    if ( ! add_to_func.empty() ) {
        auto gen = add_to_func + "(" + GenExpr(lhs, GEN_VAL_PTR) + ", " + GenExpr(rhs, GEN_VAL_PTR) + ")";
        return GenericValPtrToGT(gen, t, gt);
    }

    // Second GetOp1 is because if we get this far, LHS will be a RefExpr.
    lhs = lhs->GetOp1();

    if ( t->Tag() == TYPE_STRING ) {
        auto rhs_native = GenBinaryString(e, GEN_NATIVE, "+=");
        auto rhs_val_ptr = GenBinaryString(e, GEN_VAL_PTR, "+=");

        return GenAssign(lhs, nullptr, rhs_native, rhs_val_ptr, gt, top_level);
    }

    if ( lhs->Tag() != EXPR_NAME || lhs->AsNameExpr()->Id()->IsGlobal() ) {
        // LHS is a compound, or a global (and thus doesn't
        // equate to a C++ variable); expand x += y to x = x + y
        rhs = make_intrusive<AddExpr>(lhs, rhs);
        auto assign = make_intrusive<AssignExpr>(lhs, rhs, false, nullptr, nullptr, false);

        // Make sure any newly created types are known to
        // the profiler.
        (void)pfs->HashType(rhs->GetType());
        (void)pfs->HashType(assign->GetType());

        return GenExpr(assign, gt, top_level);
    }

    return GenBinary(e, gt, "+=");
}

string CPPCompile::GenRemoveFromExpr(const Expr* e, GenType gt, bool top_level) {
    const auto& t = e->GetType();
    auto lhs = e->GetOp1();
    auto rhs = e->GetOp2();

    if ( t->Tag() == TYPE_TABLE && same_type(lhs->GetType(), rhs->GetType()) ) {
        auto gen =
            std::string("table_remove_from__CPP(") + GenExpr(lhs, GEN_VAL_PTR) + ", " + GenExpr(rhs, GEN_VAL_PTR) + ")";
        return GenericValPtrToGT(gen, t, gt);
    }

    // Second GetOp1 is because if we get this far, LHS will be a RefExpr.
    lhs = lhs->GetOp1();

    if ( lhs->Tag() != EXPR_NAME || lhs->AsNameExpr()->Id()->IsGlobal() ) {
        // LHS is a compound, or a global (and thus doesn't
        // equate to a C++ variable); expand x -= y to x = x - y
        rhs = make_intrusive<SubExpr>(lhs, rhs);
        auto assign = make_intrusive<AssignExpr>(lhs, rhs, false, nullptr, nullptr, false);

        // Make sure any newly created types are known to
        // the profiler.
        (void)pfs->HashType(rhs->GetType());
        (void)pfs->HashType(assign->GetType());

        return GenExpr(assign, gt, top_level);
    }

    return GenBinary(e, gt, "-=");
}

string CPPCompile::GenSizeExpr(const Expr* e, GenType gt) {
    const auto& t = e->GetType();
    const auto& t1 = e->GetOp1()->GetType();
    auto it = t1->InternalType();

    auto gen = GenExpr(e->GetOp1(), GEN_NATIVE);

    if ( t1->Tag() == TYPE_BOOL )
        gen = string("((") + gen + ") ? 1 : 0)";

    else if ( it == TYPE_INTERNAL_UNSIGNED )
        // no-op
        ;

    else if ( it == TYPE_INTERNAL_INT )
        gen = string("iabs__CPP(") + gen + ")";

    else if ( it == TYPE_INTERNAL_DOUBLE )
        gen = string("fabs__CPP(") + gen + ")";

    else if ( gt == GEN_NATIVE && (t1->Tag() == TYPE_TABLE || t1->Tag() == TYPE_VECTOR) )
        return gen + "->Size()";

    else
        return GenericValPtrToGT(gen + "->SizeVal()", t, gt);

    return NativeToGT(gen, t, gt);
}

string CPPCompile::GenScheduleExpr(const Expr* e) {
    auto s = static_cast<const ScheduleExpr*>(e);
    auto when = s->When();
    auto event = s->Event();
    string event_name(event->Handler()->Name());

    RegisterEvent(event_name);

    string when_s = GenExpr(when, GEN_NATIVE);
    if ( when->GetType()->Tag() == TYPE_INTERVAL )
        when_s += " + run_state::network_time";

    return string("schedule__CPP(") + when_s + ", " + globals[event_name] + "_ev, { " +
           GenExpr(event->Args(), GEN_VAL_PTR) + " })";
}

string CPPCompile::GenLambdaExpr(const Expr* e) {
    auto l = static_cast<const LambdaExpr*>(e);
    auto& body = l->Ingredients()->Body();
    return GenLambdaExpr(e, GenLambdaClone(l, false));
}

string CPPCompile::GenLambdaExpr(const Expr* e, string capture_args) {
    auto l = static_cast<const LambdaExpr*>(e);
    auto name = Canonicalize(l->Name()) + "_lb_cl";
    auto cl_args = string("\"") + name + "\"" + std::move(capture_args);
    auto body = string("make_intrusive<") + name + ">(" + cl_args + ")";
    auto func = string("make_intrusive<CPPLambdaFunc>(\"") + l->Name() + "\", cast_intrusive<FuncType>(" +
                GenTypeName(l->GetType()) + "), " + body + ")";
    return string("make_intrusive<FuncVal>(") + func + ")";
}

string CPPCompile::GenIsExpr(const Expr* e, GenType gt) {
    auto ie = static_cast<const IsExpr*>(e);
    auto gen = string("can_cast_any_to_type(") + GenExpr(ie->GetOp1(), GEN_VAL_PTR) + ".get(), " +
               GenTypeName(ie->TestType()) + ".get())";

    return NativeToGT(gen, ie->GetType(), gt);
}

string CPPCompile::GenArithCoerceExpr(const Expr* e, GenType gt) {
    const auto& t = e->GetType();
    auto op = e->GetOp1();

    if ( same_type(t, op->GetType()) )
        return GenExpr(op, gt);

    if ( t->Tag() == TYPE_VECTOR )
        return string("vector_coerce_to__CPP(") + GenExpr(op, GEN_NATIVE) + ", " + GenTypeName(t) + ")";

    string cast_name;

    switch ( t->InternalType() ) {
        case TYPE_INTERNAL_INT: cast_name = "zeek_int_t"; break;
        case TYPE_INTERNAL_UNSIGNED: cast_name = "zeek_uint_t"; break;
        case TYPE_INTERNAL_DOUBLE: cast_name = "double"; break;

        default: reporter->InternalError("bad type in arithmetic coercion");
    }

    return NativeToGT("static_cast<" + cast_name + ">(" + GenExpr(op, GEN_NATIVE) + ")", t, gt);
}

string CPPCompile::GenRecordCoerceExpr(const Expr* e) {
    auto rc = static_cast<const RecordCoerceExpr*>(e);
    auto op1 = rc->GetOp1();
    const auto& from_type = op1->GetType();
    const auto& to_type = rc->GetType();

    if ( same_type(from_type, to_type) )
        // Elide coercion.
        return GenExpr(op1, GEN_VAL_PTR);

    const auto& map = rc->Map();
    auto type_var = GenTypeName(to_type);

    return string("coerce_to_record(cast_intrusive<RecordType>(") + type_var + "), " + GenExpr(op1, GEN_VAL_PTR) +
           ".get(), " + GenIntVector(map) + ")";
}

string CPPCompile::GenTableCoerceExpr(const Expr* e) {
    auto tc = static_cast<const TableCoerceExpr*>(e);
    const auto& t = tc->GetType();
    auto op1 = tc->GetOp1();

    return string("table_coerce__CPP(") + GenExpr(op1, GEN_VAL_PTR) + ", " + GenTypeName(t) + ")";
}

string CPPCompile::GenVectorCoerceExpr(const Expr* e) {
    auto vc = static_cast<const VectorCoerceExpr*>(e);
    const auto& op = vc->GetOp1();
    const auto& t = vc->GetType<VectorType>();

    return string("vector_coerce__CPP(" + GenExpr(op, GEN_VAL_PTR) + ", " + GenTypeName(t) + ")");
}

string CPPCompile::GenRecordConstructorExpr(const Expr* e) {
    auto rc = static_cast<const RecordConstructorExpr*>(e);
    const auto& t = rc->GetType();
    const auto& exprs = rc->Op()->AsListExpr()->Exprs();
    auto n = exprs.length();

    string vals;

    for ( auto i = 0; i < n; ++i ) {
        const auto& expr = exprs[i];

        ASSERT(expr->Tag() == EXPR_FIELD_ASSIGN);

        vals += GenExpr(expr->GetOp1(), GEN_VAL_PTR);

        if ( i < n - 1 )
            vals += ", ";
    }

    vals = string("{") + vals + "}";

    const auto& map = rc->Map();

    if ( map ) {
        string map_vals;
        for ( auto m : *map ) {
            if ( ! map_vals.empty() )
                map_vals += ", ";

            map_vals += to_string(m);
        }

        map_vals = string("{") + map_vals + "}";

        return string("record_constructor_map__CPP(") + vals + ", " + map_vals + ", cast_intrusive<RecordType>(" +
               GenTypeName(t) + "))";
    }

    else
        return string("record_constructor__CPP(") + vals + ", cast_intrusive<RecordType>(" + GenTypeName(t) + "))";
}

string CPPCompile::GenSetConstructorExpr(const Expr* e) {
    auto sc = static_cast<const SetConstructorExpr*>(e);
    const auto& t = sc->GetType();
    const auto& attrs = sc->GetAttrs();

    string attr_tags;
    string attr_vals;
    BuildAttrs(attrs, attr_tags, attr_vals);

    return string("set_constructor__CPP(") + GenExprs(sc->GetOp1().get()) + ", " + "cast_intrusive<TableType>(" +
           GenTypeName(t) + "), " + attr_tags + ", " + attr_vals + ")";
}

string CPPCompile::GenTableConstructorExpr(const Expr* e) {
    auto tc = static_cast<const TableConstructorExpr*>(e);
    const auto& t = tc->GetType();
    const auto& attrs = tc->GetAttrs();

    string attr_tags;
    string attr_vals;
    BuildAttrs(attrs, attr_tags, attr_vals);

    string indices;
    string vals;

    const auto& exprs = tc->GetOp1()->AsListExpr()->Exprs();
    auto n = exprs.length();

    for ( auto i = 0; i < n; ++i ) {
        const auto& expr = exprs[i];

        ASSERT(expr->Tag() == EXPR_ASSIGN);

        auto index = expr->GetOp1();
        auto v = expr->GetOp2();

        if ( index->Tag() == EXPR_LIST )
            // Multiple indices.
            indices += "index_val__CPP({" + GenExpr(index, GEN_VAL_PTR) + "})";
        else
            indices += GenExpr(index, GEN_VAL_PTR);

        vals += GenExpr(v, GEN_VAL_PTR);

        if ( i < n - 1 ) {
            indices += ", ";
            vals += ", ";
        }
    }

    return string("table_constructor__CPP({") + indices + "}, {" + vals + "}, " + "cast_intrusive<TableType>(" +
           GenTypeName(t) + "), " + attr_tags + ", " + attr_vals + ")";
}

string CPPCompile::GenVectorConstructorExpr(const Expr* e) {
    auto vc = static_cast<const VectorConstructorExpr*>(e);
    const auto& t = vc->GetType();

    return string("vector_constructor__CPP({") + GenExpr(vc->GetOp1(), GEN_VAL_PTR) + "}, " +
           "cast_intrusive<VectorType>(" + GenTypeName(t) + "))";
}

string CPPCompile::GenVal(const ValPtr& v) {
    const auto& t = v->GetType();
    auto tag = t->Tag();
    auto it = t->InternalType();

    if ( tag == TYPE_BOOL )
        return v->IsZero() ? "false" : "true";

    if ( tag == TYPE_ENUM )
        return GenEnum(t, v);

    if ( tag == TYPE_PORT )
        return Fmt(static_cast<int>(v->AsCount()));

    if ( it == TYPE_INTERNAL_DOUBLE )
        return Fmt(v->AsDouble());

    ODesc d;
    d.SetQuotes(true);
    v->Describe(&d);
    return d.Description();
}

string CPPCompile::GenUnary(const Expr* e, GenType gt, const char* op, const char* vec_op) {
    if ( e->GetType()->Tag() == TYPE_VECTOR )
        return GenVectorOp(e, GenExpr(e->GetOp1(), GEN_NATIVE), vec_op);

    // Look for coercions that the interpreter does implicitly.
    auto op1 = e->GetOp1();
    if ( op1->GetType()->Tag() == TYPE_COUNT && (e->Tag() == EXPR_POSITIVE || e->Tag() == EXPR_NEGATE) )
        op1 = make_intrusive<ArithCoerceExpr>(op1, TYPE_INT);

    return NativeToGT(string(op) + "(" + GenExpr(op1, GEN_NATIVE) + ")", e->GetType(), gt);
}

string CPPCompile::GenBinary(const Expr* e, GenType gt, const char* op, const char* vec_op) {
    const auto& op1 = e->GetOp1();
    const auto& op2 = e->GetOp2();
    auto t = op1->GetType();

    if ( e->GetType()->Tag() == TYPE_VECTOR ) {
        auto gen1 = GenExpr(op1, GEN_NATIVE);
        auto gen2 = GenExpr(op2, GEN_NATIVE);

        if ( t->Tag() == TYPE_VECTOR && t->Yield()->Tag() == TYPE_STRING && op2->GetType()->Tag() == TYPE_VECTOR )
            return string("str_vec_op_") + vec_op + "__CPP(" + gen1 + ", " + gen2 + ")";

        return GenVectorOp(e, gen1, gen2, vec_op);
    }

    if ( t->IsSet() )
        return GenBinarySet(e, gt, op);

    // The following is only used for internal int/uint/double
    // operations.  For those, it holds the prefix we use to
    // distinguish different instances of inlined functions
    // employed to support an operation.
    string flavor;

    switch ( t->InternalType() ) {
        case TYPE_INTERNAL_INT: flavor = "i"; break;
        case TYPE_INTERNAL_UNSIGNED: flavor = "u"; break;
        case TYPE_INTERNAL_DOUBLE: flavor = "f"; break;

        case TYPE_INTERNAL_STRING: return GenBinaryString(e, gt, op);
        case TYPE_INTERNAL_ADDR: return GenBinaryAddr(e, gt, op);
        case TYPE_INTERNAL_SUBNET: return GenBinarySubNet(e, gt, op);

        default:
            if ( t->Tag() == TYPE_PATTERN )
                return GenBinaryPattern(e, gt, op);
            break;
    }

    auto g1 = GenExpr(e->GetOp1(), GEN_NATIVE);
    auto g2 = GenExpr(e->GetOp2(), GEN_NATIVE);

    string gen;

    if ( e->Tag() == EXPR_DIVIDE )
        gen = flavor + "div__CPP(" + g1 + ", " + g2 + ")";

    else if ( e->Tag() == EXPR_MOD )
        gen = flavor + "mod__CPP(" + g1 + ", " + g2 + ")";

    else
        gen = string("(") + g1 + ")" + op + "(" + g2 + ")";

    return NativeToGT(gen, e->GetType(), gt);
}

string CPPCompile::GenBinarySet(const Expr* e, GenType gt, const char* op) {
    auto v1 = GenExpr(e->GetOp1(), GEN_DONT_CARE) + "->AsTableVal()";
    auto v2 = GenExpr(e->GetOp2(), GEN_DONT_CARE) + "->AsTableVal()";

    string res;

    switch ( e->Tag() ) {
        case EXPR_AND: res = v1 + "->Intersection(*" + v2 + ")"; break;

        case EXPR_OR: res = v1 + "->Union(" + v2 + ")"; break;

        case EXPR_SUB: res = v1 + "->TakeOut(" + v2 + ")"; break;

        case EXPR_EQ: res = v1 + "->EqualTo(*" + v2 + ")"; break;

        case EXPR_NE: res = string("! ") + v1 + "->EqualTo(*" + v2 + ")"; break;

        case EXPR_LE: res = v1 + "->IsSubsetOf(*" + v2 + ")"; break;

        case EXPR_LT:
            res = string("(") + v1 + "->IsSubsetOf(*" + v2 + ") &&" + v1 + "->Size() < " + v2 + "->Size())";
            break;

        default: reporter->InternalError("bad type in CPPCompile::GenBinarySet");
    }

    return NativeToGT(res, e->GetType(), gt);
}

string CPPCompile::GenBinaryString(const Expr* e, GenType gt, const char* op) {
    auto v1 = GenExpr(e->GetOp1(), GEN_DONT_CARE) + "->AsString()";
    auto v2 = GenExpr(e->GetOp2(), GEN_DONT_CARE) + "->AsString()";

    string res;

    if ( e->Tag() == EXPR_ADD || e->Tag() == EXPR_ADD_TO )
        res = string("str_concat__CPP(") + v1 + ", " + v2 + ")";
    else
        res = string("(Bstr_cmp(") + v1 + ", " + v2 + ") " + op + " 0)";

    return NativeToGT(res, e->GetType(), gt);
}

string CPPCompile::GenBinaryPattern(const Expr* e, GenType gt, const char* op) {
    auto v1 = GenExpr(e->GetOp1(), GEN_DONT_CARE) + "->AsPattern()";
    auto v2 = GenExpr(e->GetOp2(), GEN_DONT_CARE) + "->AsPattern()";

    auto func = e->Tag() == EXPR_AND ? "RE_Matcher_conjunction" : "RE_Matcher_disjunction";

    return NativeToGT(string("make_intrusive<PatternVal>(") + func + "(" + v1 + ", " + v2 + "))", e->GetType(), gt);
}

string CPPCompile::GenBinaryAddr(const Expr* e, GenType gt, const char* op) {
    auto v1 = GenExpr(e->GetOp1(), GEN_DONT_CARE) + "->AsAddr()";

    if ( e->Tag() == EXPR_MASK ) {
        auto gen = string("addr_mask__CPP(") + v1 + ", " + GenExpr(e->GetOp2(), GEN_NATIVE) + ")";

        return NativeToGT(gen, e->GetType(), gt);
    }

    auto v2 = GenExpr(e->GetOp2(), GEN_DONT_CARE) + "->AsAddr()";

    return NativeToGT(v1 + op + v2, e->GetType(), gt);
}

string CPPCompile::GenBinarySubNet(const Expr* e, GenType gt, const char* op) {
    auto v1 = GenExpr(e->GetOp1(), GEN_DONT_CARE) + "->AsSubNet()";
    auto v2 = GenExpr(e->GetOp2(), GEN_DONT_CARE) + "->AsSubNet()";

    return NativeToGT(v1 + op + v2, e->GetType(), gt);
}

string CPPCompile::GenEQ(const Expr* e, GenType gt, const char* op, const char* vec_op) {
    auto op1 = e->GetOp1();
    auto op2 = e->GetOp2();

    if ( e->GetType()->Tag() == TYPE_VECTOR ) {
        auto gen1 = GenExpr(op1, GEN_NATIVE);
        auto gen2 = GenExpr(op2, GEN_NATIVE);
        return GenVectorOp(e, gen1, gen2, vec_op);
    }

    auto tag = op1->GetType()->Tag();
    string negated(e->Tag() == EXPR_EQ ? "" : "! ");

    if ( tag == TYPE_PATTERN ) {
        auto gen1 = GenExpr(op1, GEN_DONT_CARE);
        auto gen2 = GenExpr(op2, GEN_DONT_CARE);
        string gen;

        if ( op2->GetType()->Tag() == TYPE_PATTERN ) {
            gen1 += "->AsPattern()->PatternText()";
            gen2 += "->AsPattern()->PatternText()";

            gen = "(strcmp(" + gen1 + ", " + gen2 + ") == 0)";
        }

        else
            gen = gen1 + "->MatchExactly(" + gen2 + "->AsString())";

        return NativeToGT(negated + gen, e->GetType(), gt);
    }

    if ( tag == TYPE_FUNC ) {
        auto gen_f1 = GenExpr(op1, GEN_DONT_CARE);
        auto gen_f2 = GenExpr(op2, GEN_DONT_CARE);

        gen_f1 += "->AsFunc()";
        gen_f2 += "->AsFunc()";

        auto gen = string("(") + gen_f1 + "==" + gen_f2 + ")";

        return NativeToGT(negated + gen, e->GetType(), gt);
    }

    return GenBinary(e, gt, op, vec_op);
}

string CPPCompile::GenAssign(const ExprPtr& lhs, const ExprPtr& rhs, const string& rhs_native,
                             const string& rhs_val_ptr, GenType gt, bool top_level) {
    switch ( lhs->Tag() ) {
        case EXPR_NAME: return GenDirectAssign(lhs, rhs_native, rhs_val_ptr, gt, top_level);

        case EXPR_INDEX: return GenIndexAssign(lhs, rhs, rhs_val_ptr, gt, top_level);

        case EXPR_FIELD: return GenFieldAssign(lhs, rhs, rhs_native, rhs_val_ptr, gt, top_level);

        case EXPR_LIST: return GenListAssign(lhs, rhs);

        default: reporter->InternalError("bad assignment node in CPPCompile::GenExpr"); return "XXX";
    }
}

string CPPCompile::GenDirectAssign(const ExprPtr& lhs, const string& rhs_native, const string& rhs_val_ptr, GenType gt,
                                   bool top_level) {
    const auto& n = lhs->AsNameExpr()->IdPtr();

    if ( n->IsBlank() )
        return rhs_native;

    const auto& name = IDNameStr(n);

    string gen;

    if ( n->IsGlobal() ) {
        const auto& t = n->GetType();
        const auto& gn = globals[n->Name()];

        if ( t->Tag() == TYPE_FUNC && t->AsFuncType()->Flavor() == FUNC_FLAVOR_EVENT ) {
            gen = string("set_event__CPP(") + gn + ", " + rhs_val_ptr + ", " + gn + "_ev)";

            if ( ! top_level )
                gen = GenericValPtrToGT(gen, n->GetType(), gt);
        }

        else if ( top_level )
            gen = gn + "->SetVal(" + rhs_val_ptr + ")";

        else {
            gen = string("set_global__CPP(") + gn + ", " + rhs_val_ptr + ")";
            gen = GenericValPtrToGT(gen, n->GetType(), gt);
        }
    }
    else {
        gen = name + " = " + rhs_native;
        if ( ! top_level )
            gen = NativeToGT("(" + gen + ")", n->GetType(), gt);
    }

    return gen;
}

string CPPCompile::GenIndexAssign(const ExprPtr& lhs, const ExprPtr& rhs, const string& rhs_val_ptr, GenType gt,
                                  bool top_level) {
    auto gen = string("assign_to_index__CPP(");

    gen += GenExpr(lhs->GetOp1(), GEN_VAL_PTR) + ", " + "index_val__CPP({" + GenExpr(lhs->GetOp2(), GEN_VAL_PTR) +
           "}), " + rhs_val_ptr + ")";

    if ( ! top_level )
        gen = GenericValPtrToGT(gen, rhs->GetType(), gt);

    return gen;
}

string CPPCompile::GenFieldAssign(const ExprPtr& lhs, const ExprPtr& rhs, const string& rhs_native,
                                  const string& rhs_val_ptr, GenType gt, bool top_level) {
    auto rec = lhs->GetOp1();
    auto rec_gen = GenExpr(rec, GEN_VAL_PTR);
    auto field = GenField(rec, lhs->AsFieldExpr()->Field());

    if ( ! top_level ) {
        auto gen = string("assign_field__CPP(") + rec_gen + ", " + field + ", " + rhs_val_ptr + ")";
        return GenericValPtrToGT(gen, rhs->GetType(), gt);
    }

    auto rt = rhs ? rhs->GetType() : nullptr;
    if ( rt && (IsNativeType(rt) || rt->Tag() == TYPE_STRING) )
        return rec_gen + "->Assign(" + field + ", " + rhs_native + ")";
    else
        return rec_gen + "->Assign(" + field + ", " + rhs_val_ptr + ")";
}

string CPPCompile::GenListAssign(const ExprPtr& lhs, const ExprPtr& rhs) {
    if ( rhs->Tag() != EXPR_NAME )
        reporter->InternalError("compound RHS expression in multi-assignment");

    string gen;
    const auto& vars = lhs->AsListExpr()->Exprs();

    auto n = vars.length();
    for ( auto i = 0; i < n; ++i ) {
        const auto& var_i = vars[i];
        if ( var_i->Tag() != EXPR_NAME )
            reporter->InternalError("compound LHS expression in multi-assignment");
        const auto& t_i = var_i->GetType();
        auto var = var_i->AsNameExpr();

        auto rhs_i_base = GenExpr(rhs, GEN_DONT_CARE);
        rhs_i_base += "->AsListVal()->Idx(" + Fmt(i) + ")";

        auto rhs_i = GenericValPtrToGT(rhs_i_base, t_i, GEN_NATIVE);

        gen += IDNameStr(var->IdPtr()) + " = " + rhs_i;

        if ( i < n - 1 )
            gen += ", ";
    }

    return "(" + gen + ")";
}

string CPPCompile::GenVectorOp(const Expr* e, const string& op, const char* vec_op) {
    const auto& t = e->GetType();
    auto gen_t = GenTypeName(t);
    auto gen = string("vec_op_") + vec_op + "__CPP(" + op + ", " + gen_t + ")";

    if ( ! IsArithmetic(t->Yield()->Tag()) )
        gen = string("vector_coerce_to__CPP(") + gen + ", " + gen_t + ")";

    return gen;
}

string CPPCompile::GenVectorOp(const Expr* e, const string& op1, const string& op2, const char* vec_op) {
    auto& op1_t = e->GetOp1()->GetType();
    auto& op2_t = e->GetOp2()->GetType();

    if ( op1_t->Tag() != TYPE_VECTOR || op2_t->Tag() != TYPE_VECTOR ) {
        // This is a deprecated mixed-scalar-and-vector operation.
        // We don't support these.  Arrange for linking errors.
        reporter->Error("C++ generation does not support deprecated scalar-mixed-with-vector operations");
        return "vec_scalar_mixed_with_vector()";
    }

    auto invoke = string(vec_op) + "__CPP(" + op1 + ", " + op2 + ")";

    auto tag2 = op2_t->Yield()->Tag();

    if ( tag2 == TYPE_STRING )
        return string("str_vec_op_") + invoke;
    if ( tag2 == TYPE_PATTERN )
        return string("pat_vec_op_") + invoke;

    auto gen = string("vec_op_") + invoke;

    auto yt = e->GetType()->Yield()->Tag();
    if ( ! IsArithmetic(yt) && yt != TYPE_STRING )
        gen = string("vector_coerce_to__CPP(") + gen + ", " + GenTypeName(e->GetType()) + ")";

    return gen;
}

string CPPCompile::GenLambdaClone(const LambdaExpr* l, bool all_deep) {
    auto& ids = l->OuterIDs();
    const auto& captures = l->GetType<FuncType>()->GetCaptures();

    string cl_args;

    for ( const auto& id : ids ) {
        const auto& id_t = id->GetType();
        auto arg = CaptureName(id);

        if ( captures && ! IsNativeType(id_t) ) {
            for ( const auto& c : *captures )
                if ( id == c.Id() && (c.IsDeepCopy() || all_deep) )
                    arg = util::fmt("cast_intrusive<%s>(%s->Clone())", TypeName(id_t), arg.c_str());
        }

        cl_args += ", " + arg;
    }

    return cl_args;
}

string CPPCompile::GenIntVector(const vector<int>& vec) {
    string res("{ ");

    for ( auto i = 0u; i < vec.size(); ++i ) {
        res += Fmt(vec[i]);

        if ( i < vec.size() - 1 )
            res += ", ";
    }

    return res + " }";
}

string CPPCompile::GenField(const ExprPtr& rec, int field) {
    auto t = TypeRep(rec->GetType());
    auto rt = t->AsRecordType();

    int mapping_slot = GetFieldMapping(rt, field);

    if ( mapping_slot < 0 )
        return Fmt(field);

    return string("field_mapping[") + Fmt(mapping_slot) + "]";
}

string CPPCompile::GenEnum(const TypePtr& t, const ValPtr& ev) {
    auto et = TypeRep(t)->AsEnumType();
    auto v = ev->AsEnum();

    if ( ! et->HasRedefs() )
        // Can use direct access.
        return "zeek_int_t(" + std::to_string(v) + ")";

    // Need to dynamically map the access.
    int mapping_slot;

    auto evm = enum_val_mappings.find(et);
    if ( evm != enum_val_mappings.end() && evm->second.contains(v) )
        // We're already tracking this value.
        mapping_slot = evm->second[v];

    else {
        // New mapping.
        mapping_slot = num_ev_mappings++;

        string enum_name = et->Lookup(v);
        bool create_if_missing = standalone && obj_matches_opt_files(ev) == AnalyzeDecision::SHOULD;
        enum_names.emplace_back(EnumMappingInfo{TypeOffset(t), std::move(enum_name), create_if_missing});

        if ( evm != enum_val_mappings.end() ) {
            // We're already tracking this enum.
            evm->second[v] = mapping_slot;
        }
        else {
            // Need to start tracking this enum.
            unordered_map<int, int> et_mapping;
            et_mapping[v] = mapping_slot;
            enum_val_mappings[et] = et_mapping;
        }
    }

    return string("enum_mapping[") + Fmt(mapping_slot) + "]";
}

int CPPCompile::ReadyExpr(const ExprPtr& e) { return ReadyProfile(make_shared<ProfileFunc>(e.get())); }

int CPPCompile::ReadyProfile(const shared_ptr<ProfileFunc>& pf) {
    int max_cohort = 0;

    for ( const auto& c : pf->Constants() )
        max_cohort = max(max_cohort, RegisterConstant(c->ValuePtr())->FinalInitCohort() + 1);

    for ( const auto& t : pf->OrderedTypes() ) {
        TypePtr tp{NewRef{}, const_cast<Type*>(t)};
        max_cohort = max(max_cohort, RegisterType(tp)->FinalInitCohort() + 1);
    }

    for ( auto& [attrs, t] : pf->ConstructorAttrs() ) {
        AttributesPtr ap{NewRef{}, const_cast<Attributes*>(attrs)};
        max_cohort = max(max_cohort, RegisterAttributes(ap)->FinalInitCohort() + 1);
        max_cohort = max(max_cohort, RegisterType(t)->FinalInitCohort() + 1);
    }

    std::unordered_set<IDPtr> compiled_funcs_called;

    for ( const auto& g : pf->AllGlobals() ) {
        auto rg = readied_globals.find(g);
        if ( rg != readied_globals.end() ) {
            max_cohort = max(max_cohort, rg->second + 1);
            continue;
        }

        max_cohort = max(max_cohort, GenerateGlobalInit(g)->FinalInitCohort() + 1);

        if ( standalone && g->GetType()->Tag() == TYPE_FUNC && obj_matches_opt_files(g) == AnalyzeDecision::SHOULD ) {
            const auto& gv = g->GetVal();
            if ( ! gv )
                continue;

            auto f = gv->AsFunc();
            if ( ! f || f->GetBodies().empty() )
                continue;

            compiled_funcs_called.insert(g);
        }
    }

    for ( const auto& g : compiled_funcs_called ) {
        readied_globals[g] = 0; // prevent infinite loops on recursion
        auto f = g->GetVal()->AsFunc();
        int f_cohort = 0;

        for ( auto& body : f->GetBodies() ) {
            auto body_pf = body_profiles.find(body.stmts);
            ASSERT(body_pf != body_profiles.end());
            f_cohort = max(f_cohort, ReadyProfile(body_pf->second) + 1);
        }

        readied_globals[g] = f_cohort;
        max_cohort = max(max_cohort, f_cohort + 1);
    }

    return max_cohort;
}

int CPPCompile::GetFieldMapping(const RecordType* rt, int field) {
    if ( field < rt->NumOrigFields() )
        // Can use direct access.
        return -1;

    auto rfm = record_field_mappings.find(rt);
    if ( rfm != record_field_mappings.end() && rfm->second.contains(field) )
        // We're already tracking this field.
        return rfm->second[field];

    // Need to dynamically map the field.
    int mapping_slot = num_rf_mappings++;

    auto pt = processed_types.find(rt);
    ASSERT(pt != processed_types.end());
    auto rt_offset = pt->second->Offset();
    auto decl = rt->FieldDecl(field);
    ASSERT(decl);
    field_decls.emplace_back(rt_offset, decl);

    if ( rfm != record_field_mappings.end() )
        // We're already tracking this record.
        rfm->second[field] = mapping_slot;
    else {
        // Need to start tracking this record.
        unordered_map<int, int> rt_mapping;
        rt_mapping[field] = mapping_slot;
        record_field_mappings[rt] = rt_mapping;
    }

    return mapping_slot;
}

} // namespace zeek::detail
