/**
 * Does semantic analysis for pragmas.
 *
 * Specification: $(LINK2 https://dlang.org/spec/pragma.html, Pragmas)
 *
 * Copyright:   Copyright (C) 1999-2023 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/pragmasem.d, _pragmasem.d)
 * Documentation:  https://dlang.org/phobos/dmd_pragmasem.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/pragmasem.d
 */

module dmd.pragmasem;

import core.stdc.stdio;

import dmd.astenums;
import dmd.arraytypes;
import dmd.attrib;
import dmd.dinterpret;
import dmd.dscope;
import dmd.dsymbol;
import dmd.errors;
import dmd.expression;
import dmd.expressionsem;
import dmd.globals;
import dmd.location;
import dmd.id;
import dmd.statement;

/**
 * Run semantic on `pragma` declaration.
 *
 * Params:
 *  pd = pragma declaration or statement to evaluate
 *  sc = enclosing scope
 */
void pragmaDeclSemantic(PragmaDeclaration pd, Scope* sc)
{
    import dmd.aggregate;
    import dmd.common.outbuffer;
    import dmd.dmangle;
    import dmd.dmodule;
    import dmd.dsymbolsem;
    import dmd.identifier;
    import dmd.root.rmem;
    import dmd.root.utf;
    import dmd.target;
    import dmd.utils;

    StringExp verifyMangleString(ref Expression e)
    {
        auto se = semanticString(sc, e, "mangled name");
        if (!se)
            return null;
        e = se;
        if (!se.len)
        {
            .error(pd.loc, "%s `%s` - zero-length string not allowed for mangled name", pd.kind, pd.toPrettyChars);
            return null;
        }
        if (se.sz != 1)
        {
            .error(pd.loc, "%s `%s` - mangled name characters can only be of type `char`", pd.kind, pd.toPrettyChars);
            return null;
        }
        version (all)
        {
            import dmd.common.charactertables;

            /* Note: D language specification should not have any assumption about backend
             * implementation. Ideally pragma(mangle) can accept a string of any content.
             *
             * Therefore, this validation is compiler implementation specific.
             */
            auto slice = se.peekString();
            for (size_t i = 0; i < se.len;)
            {
                dchar c = slice[i];
                if (c < 0x80)
                {
                    if (c.isValidMangling)
                    {
                        ++i;
                        continue;
                    }
                    else
                    {
                        .error(pd.loc, "%s `%s` char 0x%02x not allowed in mangled name", pd.kind, pd.toPrettyChars, c);
                        break;
                    }
                }
                if (const msg = utf_decodeChar(slice, i, c))
                {
                    .error(pd.loc, "%s `%s` %.*s", pd.kind, pd.toPrettyChars, cast(int)msg.length, msg.ptr);
                    break;
                }
                if (!isAnyIdentifierCharacter(c))
                {
                    .error(pd.loc, "%s `%s` char `0x%04x` not allowed in mangled name", pd.kind, pd.toPrettyChars, c);
                    break;
                }
            }
        }
        return se;
    }
    void declarations()
    {
        if (!pd.decl)
            return;

        Scope* sc2 = pd.newScope(sc);
        scope(exit)
            if (sc2 != sc)
                sc2.pop();

        foreach (s; (*pd.decl)[])
        {
            if (pd.ident == Id.printf || pd.ident == Id.scanf)
            {
                s.setPragmaPrintf(pd.ident == Id.printf);
                s.dsymbolSemantic(sc2);
                continue;
            }

            s.dsymbolSemantic(sc2);
            if (pd.ident != Id.mangle)
                continue;
            assert(pd.args);
            if (auto ad = s.isAggregateDeclaration())
            {
                Expression e = (*pd.args)[0];
                sc2 = sc2.startCTFE();
                e = e.expressionSemantic(sc);
                e = resolveProperties(sc2, e);
                sc2 = sc2.endCTFE();
                AggregateDeclaration agg;
                if (auto tc = e.type.isTypeClass())
                    agg = tc.sym;
                else if (auto ts = e.type.isTypeStruct())
                    agg = ts.sym;
                ad.pMangleOverride = new MangleOverride;
                void setString(ref Expression e)
                {
                    if (auto se = verifyMangleString(e))
                    {
                        const name = (cast(const(char)[])se.peekData()).xarraydup;
                        ad.pMangleOverride.id = Identifier.idPool(name);
                        e = se;
                    }
                    else
                        error(e.loc, "must be a string");
                }
                if (agg)
                {
                    ad.pMangleOverride.agg = agg;
                    if (pd.args.length == 2)
                    {
                        setString((*pd.args)[1]);
                    }
                    else
                        ad.pMangleOverride.id = agg.ident;
                }
                else
                    setString((*pd.args)[0]);
            }
            else if (auto td = s.isTemplateDeclaration())
            {
                .error(pd.loc, "%s `%s` cannot apply to a template declaration", pd.kind, pd.toPrettyChars);
                errorSupplemental(pd.loc, "use `template Class(Args...){ pragma(mangle, \"other_name\") class Class {} }`");
            }
            else if (auto se = verifyMangleString((*pd.args)[0]))
            {
                const name = (cast(const(char)[])se.peekData()).xarraydup;
                uint cnt = setMangleOverride(s, name);
                if (cnt > 1)
                    .error(pd.loc, "%s `%s` can only apply to a single declaration", pd.kind, pd.toPrettyChars);
            }
        }
    }

    void noDeclarations()
    {
        if (pd.decl)
        {
            .error(pd.loc, "%s `%s` is missing a terminating `;`", pd.kind, pd.toPrettyChars);
            declarations();
            // do them anyway, to avoid segfaults.
        }
    }

    // Should be merged with PragmaStatement
    //printf("\tpragmaDeclSemantic '%s'\n", pd.toChars());
    if (target.supportsLinkerDirective())
    {
        if (pd.ident == Id.linkerDirective)
        {
            if (!pd.args || pd.args.length != 1)
                .error(pd.loc, "%s `%s` one string argument expected for pragma(linkerDirective)", pd.kind, pd.toPrettyChars);
            else
            {
                auto se = semanticString(sc, (*pd.args)[0], "linker directive");
                if (!se)
                    return noDeclarations();
                (*pd.args)[0] = se;
                if (global.params.v.verbose)
                    message("linkopt   %.*s", cast(int)se.len, se.peekString().ptr);
            }
            return noDeclarations();
        }
    }
    if (pd.ident == Id.msg)
    {
        if (!pd.args)
            return noDeclarations();

        if (!pragmaMsgSemantic(pd.loc, sc, pd.args))
            return;

        return noDeclarations();
    }
    else if (pd.ident == Id.lib)
    {
        if (!pd.args || pd.args.length != 1)
            .error(pd.loc, "%s `%s` string expected for library name", pd.kind, pd.toPrettyChars);
        else
        {
            auto se = semanticString(sc, (*pd.args)[0], "library name");
            if (!se)
                return noDeclarations();
            (*pd.args)[0] = se;

            auto name = se.peekString().xarraydup;
            if (global.params.v.verbose)
                message("library   %s", name.ptr);
            if (global.params.moduleDeps.buffer && !global.params.moduleDeps.name)
            {
                OutBuffer* ob = global.params.moduleDeps.buffer;
                Module imod = sc._module;
                ob.writestring("depsLib ");
                ob.writestring(imod.toPrettyChars());
                ob.writestring(" (");
                escapePath(ob, imod.srcfile.toChars());
                ob.writestring(") : ");
                ob.writestring(name);
                ob.writenl();
            }
            mem.xfree(name.ptr);
        }
        return noDeclarations();
    }
    else if (pd.ident == Id.startaddress)
    {
        pragmaStartAddressSemantic(pd.loc, sc, pd.args);
        return noDeclarations();
    }
    else if (pd.ident == Id.Pinline)
    {
        // this pragma now gets evaluated on demand in function semantic

        return declarations();
    }
    else if (pd.ident == Id.mangle)
    {
        if (!pd.args)
            pd.args = new Expressions();
        if (pd.args.length == 0 || pd.args.length > 2)
        {
            .error(pd.loc, pd.args.length == 0 ? "%s `%s` - string expected for mangled name"
                                      : "%s `%s` expected 1 or 2 arguments", pd.kind, pd.toPrettyChars);
            pd.args.setDim(1);
            (*pd.args)[0] = ErrorExp.get(); // error recovery
        }
        return declarations();
    }
    else if (pd.ident == Id.crt_constructor || pd.ident == Id.crt_destructor)
    {
        if (pd.args && pd.args.length != 0)
            .error(pd.loc, "%s `%s` takes no argument", pd.kind, pd.toPrettyChars);
        else
        {
            immutable isCtor = pd.ident == Id.crt_constructor;

            static uint recurse(Dsymbol s, bool isCtor)
            {
                if (auto ad = s.isAttribDeclaration())
                {
                    uint nestedCount;
                    auto decls = ad.include(null);
                    if (decls)
                    {
                        for (size_t i = 0; i < decls.length; ++i)
                            nestedCount += recurse((*decls)[i], isCtor);
                    }
                    return nestedCount;
                }
                else if (auto f = s.isFuncDeclaration())
                {
                    if (isCtor)
                        f.isCrtCtor = true;
                    else
                        f.isCrtDtor = true;

                    return 1;
                }
                else
                    return 0;
                assert(0);
            }

            if (recurse(pd, isCtor) > 1)
                .error(pd.loc, "%s `%s` can only apply to a single declaration", pd.kind, pd.toPrettyChars);
        }
        return declarations();
    }
    else if (pd.ident == Id.printf || pd.ident == Id.scanf)
    {
        if (pd.args && pd.args.length != 0)
            .error(pd.loc, "%s `%s` takes no argument", pd.kind, pd.toPrettyChars);
        return declarations();
    }
    else if (!global.params.ignoreUnsupportedPragmas)
    {
        error(pd.loc, "unrecognized `pragma(%s)`", pd.ident.toChars());
        return declarations();
    }

    if (!global.params.v.verbose)
        return declarations();

    /* Print unrecognized pragmas
     */
    OutBuffer buf;
    buf.writestring(pd.ident.toString());
    if (pd.args)
    {
        const errors_save = global.startGagging();
        for (size_t i = 0; i < pd.args.length; i++)
        {
            Expression e = (*pd.args)[i];
            sc = sc.startCTFE();
            e = e.expressionSemantic(sc);
            e = resolveProperties(sc, e);
            sc = sc.endCTFE();
            e = e.ctfeInterpret();
            if (i == 0)
                buf.writestring(" (");
            else
                buf.writeByte(',');
            buf.writestring(e.toChars());
        }
        if (pd.args.length)
            buf.writeByte(')');
        global.endGagging(errors_save);
    }
    message("pragma    %s", buf.peekChars());
    return declarations();
}

/**
 * Run semantic on `pragma` statement.
 *
 * Params:
 *  ps = pragma statement to evaluate
 *  sc = enclosing scope
 *
 * Returns : true if `pragma` is valid, or false if an error was found
 */
bool pragmaStmtSemantic(PragmaStatement ps, Scope* sc)
{
    import dmd.statementsem;

    /* https://dlang.org/spec/statement.html#pragma-statement
     */
    // Should be merged with PragmaDeclaration

    //printf("pragmaStmtSemantic() %s\n", ps.toChars());
    //printf("body = %p\n", ps._body);
    if (ps.ident == Id.msg)
    {
        if (!pragmaMsgSemantic(ps.loc, sc, ps.args))
            return false;
    }
    else if (ps.ident == Id.lib)
    {
        version (all)
        {
            /* Should this be allowed?
             */
            error(ps.loc, "`pragma(lib)` not allowed as statement");
            return false;
        }
        else
        {
            if (!ps.args || ps.args.length != 1)
            {
                error(ps.loc, "`string` expected for library name");
                return false;
            }
            else
            {
                auto se = semanticString(sc, (*ps.args)[0], "library name");
                if (!se)
                    return false;

                if (global.params.v.verbose)
                {
                    message("library   %.*s", cast(int)se.len, se.string);
                }
            }
        }
    }
    else if (ps.ident == Id.linkerDirective)
    {
        /* Should this be allowed?
         */
        error(ps.loc, "`pragma(linkerDirective)` not allowed as statement");
        return false;
    }
    else if (ps.ident == Id.startaddress)
    {
        if (!pragmaStartAddressSemantic(ps.loc, sc, ps.args))
            return false;
    }
    else if (ps.ident == Id.Pinline)
    {
        if (auto fd = sc.func)
        {
            fd.inlining = evalPragmaInline(ps.loc, sc, ps.args);
        }
        else
        {
            error(ps.loc, "`pragma(inline)` is not inside a function");
            return false;
        }
    }
    else if (ps.ident == Id.mangle)
    {
        auto es = ps._body ? ps._body.isExpStatement() : null;
        auto de = es ? es.exp.isDeclarationExp() : null;
        if (!de)
        {
            error(ps.loc, "`pragma(mangle)` must be attached to a declaration");
            return false;
        }
        const se = ps.args && (*ps.args).length == 1 ? semanticString(sc, (*ps.args)[0], "pragma mangle argument") : null;
        if (!se)
        {
            error(ps.loc, "`pragma(mangle)` takes a single argument that must be a string literal");
            return false;
        }
        const cnt = setMangleOverride(de.declaration, cast(const(char)[])se.peekData());
        if (cnt != 1)
            assert(0);
    }
    else if (!global.params.ignoreUnsupportedPragmas)
    {
        error(ps.loc, "unrecognized `pragma(%s)`", ps.ident.toChars());
        return false;
    }

    if (ps._body)
    {
        if (ps.ident == Id.msg || ps.ident == Id.startaddress)
        {
            error(ps.loc, "`pragma(%s)` is missing a terminating `;`", ps.ident.toChars());
            return false;
        }
        ps._body = ps._body.statementSemantic(sc);
    }
    return true;
}

/***************************************
 * Interpret a `pragma(inline, x)`
 *
 * Params:
 *   loc = location for error messages
 *   sc = scope for evaluation of argument
 *   args = pragma arguments
 * Returns: corresponding `PINLINE` state
 */
package PINLINE evalPragmaInline(Loc loc, Scope* sc, Expressions* args)
{
    if (!args || args.length == 0)
        return PINLINE.default_;

    if (args && args.length > 1)
    {
        .error(loc, "one boolean expression expected for `pragma(inline)`, not %llu", cast(ulong) args.length);
        args.setDim(1);
        (*args)[0] = ErrorExp.get();
    }

    Expression e = (*args)[0];
    if (!e.type)
    {
        sc = sc.startCTFE();
        e = e.expressionSemantic(sc);
        e = resolveProperties(sc, e);
        sc = sc.endCTFE();
        e = e.ctfeInterpret();
        e = e.toBoolean(sc);
        if (e.isErrorExp())
            .error(loc, "pragma(`inline`, `true` or `false`) expected, not `%s`", (*args)[0].toChars());
        (*args)[0] = e;
    }

    const opt = e.toBool();
    if (opt.isEmpty())
        return PINLINE.default_;
    else if (opt.get())
        return PINLINE.always;
    else
        return PINLINE.never;
}

/**
 * Apply pragma mangle to FuncDeclarations and VarDeclarations
 * under `s`, poking through attribute declarations such as
 * `extern(C)` but not through aggregates or function bodies.
 *
 * Params:
 *    s = symbol to apply
 *    sym = overriding symbol name
 */
private uint setMangleOverride(Dsymbol s, const(char)[] sym)
{
    if (s.isFuncDeclaration() || s.isVarDeclaration())
    {
        s.isDeclaration().mangleOverride = sym;
        return 1;
    }

    if (auto ad = s.isAttribDeclaration())
    {
        uint nestedCount = 0;

        ad.include(null).foreachDsymbol( (s) { nestedCount += setMangleOverride(s, sym); } );

        return nestedCount;
    }
    return 0;
}

/***********************************************************
 * Evaluate and print a `pragma(msg, args)`
 *
 * Params:
 *    loc = location for error messages
 *    sc = scope for argument interpretation
 *    args = expressions to print
 * Returns:
 *    `true` on success
 */
private bool pragmaMsgSemantic(Loc loc, Scope* sc, Expressions* args)
{
    import dmd.tokens;

    if (!args)
        return true;
    foreach (arg; *args)
    {
        sc = sc.startCTFE();
        auto e = arg.expressionSemantic(sc);
        e = resolveProperties(sc, e);
        sc = sc.endCTFE();

        // pragma(msg) is allowed to contain types as well as expressions
        e = ctfeInterpretForPragmaMsg(e);
        if (e.op == EXP.error)
        {
            errorSupplemental(loc, "while evaluating `pragma(msg, %s)`", arg.toChars());
            return false;
        }
        if (auto se = e.toStringExp())
        {
            const slice = se.toUTF8(sc).peekString();
            fprintf(stderr, "%.*s", cast(int)slice.length, slice.ptr);
        }
        else
            fprintf(stderr, "%s", e.toChars());
    }
    fprintf(stderr, "\n");
    return true;
}

/**
 * Apply pragma printf/scanf to FuncDeclarations under `s`,
 * poking through attribute declarations such as `extern(C)`
 * but not through aggregates or function bodies.
 *
 * Params:
 *    s = symbol to apply
 *    printf = `true` for printf, `false` for scanf
 */
private void setPragmaPrintf(Dsymbol s, bool printf)
{
    if (auto fd = s.isFuncDeclaration())
    {
        fd.printf = printf;
        fd.scanf = !printf;
    }

    if (auto ad = s.isAttribDeclaration())
    {
        ad.include(null).foreachDsymbol( (s) { setPragmaPrintf(s, printf); } );
    }
}

/***********************************************************
 * Evaluate `pragma(startAddress, func)` and store the resolved symbol in `args`
 *
 * Params:
 *    loc = location for error messages
 *    sc = scope for argument interpretation
 *    args = pragma arguments
 * Returns:
 *    `true` on success
 */
private bool pragmaStartAddressSemantic(Loc loc, Scope* sc, Expressions* args)
{
    import dmd.dtemplate;

    if (!args || args.length != 1)
    {
        .error(loc, "function name expected for start address");
        return false;
    }
    else
    {
        /* https://issues.dlang.org/show_bug.cgi?id=11980
         * resolveProperties and ctfeInterpret call are not necessary.
         */
        Expression e = (*args)[0];
        sc = sc.startCTFE();
        e = e.expressionSemantic(sc);
        // e = resolveProperties(sc, e);
        sc = sc.endCTFE();

        // e = e.ctfeInterpret();
        (*args)[0] = e;
        Dsymbol sa = getDsymbol(e);
        if (!sa || !sa.isFuncDeclaration())
        {
            .error(loc, "function name expected for start address, not `%s`", e.toChars());
            return false;
        }
    }
    return true;
}
