/**
 * Defines initializers of variables, e.g. the array literal in `int[3] x = [0, 1, 2]`.
 *
 * Copyright:   Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved
 * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/init.d, _init.d)
 * Documentation:  https://dlang.org/phobos/dmd_init.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/init.d
 */

module dmd.init;

import core.stdc.stdio;

import dmd.arraytypes;
import dmd.astenums;
import dmd.ast_node;
import dmd.expression;
import dmd.identifier;
import dmd.location;
import dmd.mtype;
import dmd.rootobject;
import dmd.visitor;

enum NeedInterpret : int
{
    INITnointerpret,
    INITinterpret,
}

alias INITnointerpret = NeedInterpret.INITnointerpret;
alias INITinterpret = NeedInterpret.INITinterpret;

/***********************************************************
 */
extern (C++) class Initializer : ASTNode
{
    Loc loc;
    InitKind kind;

    override DYNCAST dyncast() const
    {
        return DYNCAST.initializer;
    }


    extern (D) this(const ref Loc loc, InitKind kind) @safe
    {
        this.loc = loc;
        this.kind = kind;
    }

    final inout(ErrorInitializer) isErrorInitializer() inout @nogc nothrow pure
    {
        // Use void* cast to skip dynamic casting call
        return kind == InitKind.error ? cast(inout ErrorInitializer)cast(void*)this : null;
    }

    final inout(VoidInitializer) isVoidInitializer() inout @nogc nothrow pure
    {
        return kind == InitKind.void_ ? cast(inout VoidInitializer)cast(void*)this : null;
    }

    final inout(DefaultInitializer) isDefaultInitializer() inout @nogc nothrow pure
    {
        return kind == InitKind.default_ ? cast(inout DefaultInitializer)cast(void*)this : null;
    }

    final inout(StructInitializer) isStructInitializer() inout @nogc nothrow pure
    {
        return kind == InitKind.struct_ ? cast(inout StructInitializer)cast(void*)this : null;
    }

    final inout(ArrayInitializer) isArrayInitializer() inout @nogc nothrow pure
    {
        return kind == InitKind.array ? cast(inout ArrayInitializer)cast(void*)this : null;
    }

    final inout(ExpInitializer) isExpInitializer() inout @nogc nothrow pure
    {
        return kind == InitKind.exp ? cast(inout ExpInitializer)cast(void*)this : null;
    }

    final inout(CInitializer) isCInitializer() inout @nogc nothrow pure
    {
        return kind == InitKind.C_ ? cast(inout CInitializer)cast(void*)this : null;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class VoidInitializer : Initializer
{
    Type type;      // type that this will initialize to

    extern (D) this(const ref Loc loc) @safe
    {
        super(loc, InitKind.void_);
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * The C23 default initializer `{ }`
 */
extern (C++) final class DefaultInitializer : Initializer
{
    Type type;      // type that this will initialize to

    extern (D) this(const ref Loc loc) @safe
    {
        super(loc, InitKind.default_);
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class ErrorInitializer : Initializer
{
    extern (D) this() @safe
    {
        super(Loc.initial, InitKind.error);
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class StructInitializer : Initializer
{
    Identifiers field;      // of Identifier *'s
    Initializers value;     // parallel array of Initializer *'s

    extern (D) this(const ref Loc loc)
    {
        super(loc, InitKind.struct_);
    }

    extern (D) void addInit(Identifier field, Initializer value)
    {
        //printf("StructInitializer::addInit(field = %p, value = %p)\n", field, value);
        this.field.push(field);
        this.value.push(value);
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class ArrayInitializer : Initializer
{
    Expressions index;      // indices
    Initializers value;     // of Initializer *'s
    uint dim;               // length of array being initialized
    Type type;              // type that array will be used to initialize
    bool sem;               // true if semantic() is run
    bool isCarray;          // C array semantics

    extern (D) this(const ref Loc loc)
    {
        super(loc, InitKind.array);
    }

    extern (D) void addInit(Expression index, Initializer value)
    {
        this.index.push(index);
        this.value.push(value);
        dim = 0;
        type = null;
    }

    bool isAssociativeArray() const pure
    {
        foreach (idx; index)
        {
            if (idx)
                return true;
        }
        return false;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class ExpInitializer : Initializer
{
    bool expandTuples;
    Expression exp;

    extern (D) this(const ref Loc loc, Expression exp) @safe
    {
        super(loc, InitKind.exp);
        this.exp = exp;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/*********************************************
 * Holds the `designator` for C initializers
 */
struct Designator
{
    Expression exp;     /// [ constant-expression ]
    Identifier ident;   /// . identifier

    this(Expression exp) @safe { this.exp = exp; }
    this(Identifier ident) @safe  { this.ident = ident; }
}

/*********************************************
 * Holds the `designation (opt) initializer` for C initializers
 */
struct DesigInit
{
    Designators* designatorList; /// designation (opt)
    Initializer initializer;     /// initializer
}

/********************************
 * C11 6.7.9 Initialization
 * Represents the C initializer-list
 */
extern (C++) final class CInitializer : Initializer
{
    DesigInits initializerList; /// initializer-list
    Type type;              /// type that array will be used to initialize
    bool sem;               /// true if semantic() is run

    extern (D) this(const ref Loc loc)
    {
        super(loc, InitKind.C_);
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/****************************************
 * Copy the AST for Initializer.
 * Params:
 *      inx = Initializer AST to copy
 * Returns:
 *      the copy
 */
Initializer syntaxCopy(Initializer inx)
{
    static Initializer visitVoid(VoidInitializer vi)
    {
        return new VoidInitializer(vi.loc);
    }

    static Initializer visitDefault(DefaultInitializer vi)
    {
        return new DefaultInitializer(vi.loc);
    }

    static Initializer visitError(ErrorInitializer vi)
    {
        return vi;
    }

    static Initializer visitExp(ExpInitializer vi)
    {
        return new ExpInitializer(vi.loc, vi.exp.syntaxCopy());
    }

    static Initializer visitStruct(StructInitializer vi)
    {
        auto si = new StructInitializer(vi.loc);
        assert(vi.field.length == vi.value.length);
        si.field.setDim(vi.field.length);
        si.value.setDim(vi.value.length);
        foreach (const i; 0 .. vi.field.length)
        {
            si.field[i] = vi.field[i];
            si.value[i] = vi.value[i].syntaxCopy();
        }
        return si;
    }

    static Initializer visitArray(ArrayInitializer vi)
    {
        auto ai = new ArrayInitializer(vi.loc);
        assert(vi.index.length == vi.value.length);
        ai.index.setDim(vi.index.length);
        ai.value.setDim(vi.value.length);
        foreach (const i; 0 .. vi.value.length)
        {
            ai.index[i] = vi.index[i] ? vi.index[i].syntaxCopy() : null;
            ai.value[i] = vi.value[i].syntaxCopy();
        }
        return ai;
    }

    static Initializer visitC(CInitializer vi)
    {
        auto ci = new CInitializer(vi.loc);
        ci.initializerList.setDim(vi.initializerList.length);
        foreach (const i; 0 .. vi.initializerList.length)
        {
            DesigInit* cdi = &ci.initializerList[i];
            DesigInit* vdi = &ci.initializerList[i];
            cdi.initializer = vdi.initializer.syntaxCopy();
            if (vdi.designatorList)
            {
                cdi.designatorList = new Designators();
                cdi.designatorList.setDim(vdi.designatorList.length);
                foreach (const j; 0 .. vdi.designatorList.length)
                {
                    Designator* cdid = &(*cdi.designatorList)[j];
                    Designator* vdid = &(*vdi.designatorList)[j];
                    cdid.exp = vdid.exp ? vdid.exp.syntaxCopy() : null;
                    cdid.ident = vdid.ident;
                }
            }
        }
        return ci;
    }

    mixin VisitInitializer!Initializer visit;
    return visit.VisitInitializer(inx);
}

/***********************************************************
 * Visit each Initializer in init. Call a function visit%s(init) for
 * each node, where %s is the op of the node. Otherwise call visitDefault(init)
 * for that node. If the visit function returns R.init, continue
 * visiting each node, otherwise return the value of R.
 * Params:
 *      Result = return type
 *      init = Initializer tree to traverse
 * Returns:
 *      Result.init for continue, value of type Result for early exit
 */

mixin template VisitInitializer(Result)
{
    Result VisitInitializer(Initializer init)
    {
        final switch (init.kind)
        {
            case InitKind.void_:    mixin(visitCase("Void"));    break;
            case InitKind.default_: mixin(visitCase("Default")); break;
            case InitKind.error:    mixin(visitCase("Error"));   break;
            case InitKind.struct_:  mixin(visitCase("Struct"));  break;
            case InitKind.array:    mixin(visitCase("Array"));   break;
            case InitKind.exp:      mixin(visitCase("Exp"));     break;
            case InitKind.C_:       mixin(visitCase("C"));       break;
        }
        static if (is(Result == void)) { } else
            return Result.init;
    }
}

/****************************************
 * CTFE-only helper function for VisitInitializer.
 * Params:
 *      handler = string for the name of the visit handler
 * Returns: boilerplate code for a case
 */
string visitCase(string handler) pure @safe
{
    if (__ctfe)
    {
        return
            "
            auto ix = init.is"~handler~"Initializer();
            static if (is(Result == void))
                visit"~handler~"(ix);
            else
            {
                Result r = visit"~handler~"(ix);
                if (r !is Result.init)
                    return r;
            }
            ";
    }
    assert(0);
}
