/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright 1996-2025 The NASM Authors - All Rights Reserved */

/*
 * Parse and handle assembler directives
 */

#include "compiler.h"

#include "nctype.h"

#include "nasm.h"
#include "nasmlib.h"
#include "ilog2.h"
#include "error.h"
#include "floats.h"
#include "stdscan.h"
#include "preproc.h"
#include "eval.h"
#include "assemble.h"
#include "outform.h"
#include "listing.h"
#include "labels.h"
#include "iflag.h"
#include "quote.h"

struct cpunames {
    const char *name;
    unsigned int level;
    /* Eventually a table of features */
};

static void iflag_set_cpu(iflag_t *a, unsigned int lvl)
{
    a->field[0] = 0;     /* Not applicable to the CPU type */
    iflag_set_all_features(a);    /* All feature masking bits set for now */
    if (lvl >= IF_ANY) {
        /* This is a hack for now */
        iflag_set(a, IF_LATEVEX);
    }
    a->field[IF_CPU_FIELD] &= ~IF_CPU_LEVEL_MASK;
    iflag_set(a, lvl);
}

void set_cpu(const char *value)
{
    const char *p;
    char modifier;
    const struct cpunames *cpuflag;
    static const struct cpunames cpunames[] = {
        { "default", IF_DEFAULT }, /* Must be first */
        { "8086", IF_8086 },
        { "186",  IF_186  },
        { "286",  IF_286  },
        { "386",  IF_386  },
        { "486",  IF_486  },
        { "586",  IF_PENT },
        { "pentium", IF_PENT },
        { "pentiummmx", IF_PENT },
        { "686",  IF_P6 },
        { "p6",   IF_P6 },
        { "ppro", IF_P6 },
        { "pentiumpro", IF_P6 },
        { "p2", IF_P6 },        /* +MMX */
        { "pentiumii", IF_P6 },
        { "p3", IF_KATMAI },
        { "katmai", IF_KATMAI },
        { "p4", IF_WILLAMETTE },
        { "willamette", IF_WILLAMETTE },
        { "prescott", IF_PRESCOTT },
        { "x64", IF_X86_64 },
        { "x86-64", IF_X86_64 },
        { "ia64", IF_IA64 },
        { "ia-64", IF_IA64 },
        { "itanium", IF_IA64 },
        { "itanic", IF_IA64 },
        { "merced", IF_IA64 },
        { "nehalem", IF_NEHALEM },
        { "westmere", IF_WESTMERE },
        { "sandybridge", IF_SANDYBRIDGE },
        { "ivybridge", IF_FUTURE },
        { "any", IF_ANY },
        { "all", IF_ANY },
        { "latevex", IF_LATEVEX },
        { "apx", IF_APX },
        { "evex", IF_EVEX },
        { "vex", IF_VEX },
        { NULL, 0 }
    };

    if (!value) {
        iflag_set_cpu(&cpu, cpunames[0].level);
        return;
    }

    p = value;
    modifier = '+';
    while (*p) {
        int len = strcspn(p, " ,");

        while (len && (*p == '+' || *p == '-' || *p == '*')) {
            modifier = *p++;
            len--;
            if (!len && modifier == '*')
                cpu = cmd_cpu;
        }

        if (len) {
            bool invert_flag = false;

            if (len >= 3 && !nasm_memicmp(p, "no", 2)) {
                invert_flag = true;
                p += 2;
                len -= 2;
            }

            for (cpuflag = cpunames; cpuflag->name; cpuflag++)
                if (!nasm_strnicmp(p, cpuflag->name, len))
                    break;

            if (!cpuflag->name) {
                nasm_nonfatal("unknown CPU type or flag '%.*s'", len, p);
                return;
            }

            if (cpuflag->level >= IF_CPU_FIRST && cpuflag->level <= IF_ANY) {
                iflag_set_cpu(&cpu, cpuflag->level);
            } else {
                switch (modifier) {
                case '-':
                    invert_flag = !invert_flag;
                    break;
                case '*':
                    invert_flag ^= iflag_test(&cmd_cpu, cpuflag->level);
                    break;
                default:
                    break;
                }

                iflag_set(&cpu, cpuflag->level);
                if (invert_flag)
                    iflag_clear(&cpu, cpuflag->level);
            }
        }
        p += len;
        if (!*p)
            break;
        p++;                /* Skip separator */
    }
}

static int get_bits(const char *value)
{
    int i = atoi(value);

    switch (i) {
    case 16:
        break;                  /* Always safe */
    case 32:
        if (!iflag_cpu_level_ok(&cpu, IF_386)) {
            nasm_nonfatal("cannot specify 32-bit segment on processor below a 386");
            i = 16;
        }
        break;
    case 64:
        if (!iflag_cpu_level_ok(&cpu, IF_X86_64)) {
            nasm_nonfatal("cannot specify 64-bit segment on processor below an x86-64");
            i = 16;
        }
        break;
    default:
        nasm_nonfatal("`%s' is not a valid segment size; must be 16, 32 or 64",
                      value);
        i = 16;
        break;
    }
    return i;
}

static enum directive parse_directive_line(char **directive, char **value)
{
    char *p, *q, *eol, *buf;
    char c;

    buf = nasm_skip_spaces(*directive);

    /*
     * It should be enclosed in [ ].
     *
     * Strip off the comments.  We should really strip the comments in
     * generic code, not here.  While we're at it, it would be better
     * to pass the backend a series of tokens instead of a raw string,
     * and actually process quoted strings for it, like of like argv
     * is handled in C.
     */
    if (*buf != '[')
        return D_none;

    q = buf;
    while ((c = *q) != ']') {
        switch (c) {
        case '\0':
        case ';':
            return D_corrupt;   /* No ] in directive */
        case '\'':
        case '\"':
        case '`':
            q = nasm_skip_string(q);
            if (!*q++)
                return D_corrupt;
            break;
        default:
            q++;
            break;
        }
    }

    /*
     * Found the ] at the end of the directive. Make sure there isn't
     * anything else at the end of the line, except a possible
     * comment.
     */
    eol = nasm_skip_spaces(q+1);
    if (*eol != '\0' && *eol != ';') {
        nasm_warn(WARN_DIRECTIVE_GARBAGE_EOL,
                  "garbage found on line after directive");
    }

    /* no brace, no trailing spaces */
    *q = '\0';
    nasm_zap_spaces_rev(--q);

    /* directive */
    p = nasm_skip_spaces(++buf);
    q = nasm_skip_word(p);
    if (!q)
        return D_corrupt; /* sigh... no value there */
    *q = '\0';
    *directive = p;

    /* and value finally */
    p = nasm_skip_spaces(++q);
    *value = p;

    return directive_find(*directive);
}

/*
 * Check to see if a string matches a valid directive name (sans [],
 * whitespace must be already trimmed.)
 */
bool directive_valid(const char *directive)
{
    enum directive d;

    d = directive_find(directive);

    if (d <= D_corrupt)
        return false;
    else if (d < D_ofmt)
        return true;            /* Global directive or pseudo-op */
    else if (d < D_pragma_tokens)
        return ofmt->directive(d, NULL) == DIRR_OK;
    else
        return false;
}

/*
 * Process a line from the assembler and try to handle it if it
 * is a directive.  Return true if the line was handled (including
 * if it was an error), false otherwise.
 */
bool process_directives(char *directive)
{
    enum directive d;
    char *value, *p, *q, *special;
    struct tokenval tokval;
    bool bad_param = false;
    enum label_type type;

    d = parse_directive_line(&directive, &value);

    switch (d) {
    case D_none:
        return false;

    case D_corrupt:
	nasm_nonfatal("invalid directive line");
	break;

    default:
        if (d > D_ofmt && d < D_pragma_tokens) {
            /* It's a backend-specific directive */
            switch (ofmt->directive(d, value)) {
            case DIRR_UNKNOWN:
                goto unknown;
            case DIRR_OK:
            case DIRR_ERROR:
                break;
            case DIRR_BADPARAM:
                bad_param = true;
                break;
            default:
                panic();
            }
        } else if (d < D_pseudo_ops) {
            nasm_nonfatal("internal error: unimplemented directive [%s]",
                          directive);
            break;
        } else {
            goto unknown;
        }
        break;

    case D_unknown:
    unknown:
        nasm_nonfatal("unrecognized directive [%s]", directive);
        break;

    case D_SEGMENT:         /* [SEGMENT n] */
    case D_SECTION:
    {
	int sb = globl.bits;
        int32_t seg = ofmt->section(value, &sb);

        if (seg == NO_SEG) {
            nasm_nonfatal("segment name `%s' not recognized", value);
        } else {
            globl.bits = sb;
            switch_segment(seg);
        }
        break;
    }

    case D_SECTALIGN:       /* [SECTALIGN n] */
    {
	expr *e;

        if (*value) {
            stdscan_reset(value);
            tokval.t_type = TOKEN_INVALID;
            e = evaluate(stdscan, NULL, &tokval, NULL, true, NULL);
            if (e) {
                uint64_t align = e->value;

		if (!is_power2(e->value)) {
                    nasm_nonfatal("segment alignment `%s' is not power of two",
				  value);
		} else if (align > UINT64_C(0x7fffffff)) {
                    /*
                     * FIXME: Please make some sane message here
                     * ofmt should have some 'check' method which
                     * would report segment alignment bounds.
                     */
		    nasm_nonfatal("absurdly large segment alignment `%s' (2^%d)",
				  value, ilog2_64(align));
                }

                /* callee should be able to handle all details */
                if (location.segment != NO_SEG)
                    ofmt->sectalign(location.segment, align);
            }
        }
        break;
    }

    case D_BITS:            /* [BITS bits] */
        globl.bits = get_bits(value);
        break;

    case D_GLOBAL:          /* [GLOBAL|STATIC|EXTERN|COMMON symbol:special] */
        type = LBL_GLOBAL;
        goto symdef;
    case D_STATIC:
        type = LBL_STATIC;
        goto symdef;
    case D_EXTERN:
        type = LBL_EXTERN;
        goto symdef;
    case D_REQUIRED:
        type = LBL_REQUIRED;
        goto symdef;
    case D_COMMON:
        type = LBL_COMMON;
        goto symdef;

    symdef:
    {
        bool validid;
        int64_t size = 0;
        char *fullvalue = value; /* Including leading $ if present */
        char *sizestr;
        bool rn_error;

        if (*value == '$') {
            value++;        /* skip escaping $ if present */
            validid = nasm_isidchar(*value);
            if (globl.dollarhex)
                validid &= !nasm_isnumstart(value[1]);
        } else {
            validid = nasm_isidstart(*value);
        }

        q = value;
        if (validid) {
            q++;
            while (*q && *q != ':' && !nasm_isspace(*q)) {
                if (!nasm_isidchar(*q))
                    validid = false;
                q++;
            }
        }
        if (!validid) {
            nasm_nonfatal("identifier expected after %s, got `%s'",
			  directive, fullvalue);
            break;
        }

        if (nasm_isspace(*q)) {
            *q++ = '\0';
            sizestr = q = nasm_skip_spaces(q);
            q = strchr(q, ':');
        } else {
            sizestr = NULL;
        }

        if (q && *q == ':') {
            *q++ = '\0';
            special = q;
        } else {
            special = NULL;
        }

        if (type == LBL_COMMON) {
            if (sizestr)
                size = readnum(sizestr, &rn_error);
            if (!sizestr || rn_error)
                nasm_nonfatal("%s size specified in common declaration",
			      sizestr ? "invalid" : "no");
        } else if (sizestr) {
            nasm_nonfatal("invalid syntax in %s declaration", directive);
        }

        if (!declare_label(value, type, special))
            break;

        if (type == LBL_COMMON || type == LBL_EXTERN || type == LBL_REQUIRED)
            define_label(value, 0, size, false);

	break;
    }

    case D_ABSOLUTE:        /* [ABSOLUTE address] */
    {
	expr *e;

        stdscan_reset(value);
        tokval.t_type = TOKEN_INVALID;
        e = evaluate(stdscan, NULL, &tokval, NULL, true, NULL);
        if (e) {
            if (!is_reloc(e)) {
                nasm_nonfatal("cannot use non-relocatable expression as "
                              "ABSOLUTE address");
            } else {
                absolute.segment = reloc_seg(e);
                absolute.offset = reloc_value(e);
            }
        } else if (pass_first()) {
            absolute.offset = 0x100;     /* don't go near zero in case of / */
        } else {
            nasm_nonfatal("invalid ABSOLUTE address");
        }
        in_absolute = true;
        location.segment = NO_SEG;
        location.offset = absolute.offset;
        break;
    }

    case D_DEBUG:           /* [DEBUG] */
    {
        bool badid, overlong;
	char debugid[128];

        p = value;
        q = debugid;
        badid = overlong = false;
        if (*p == '$') {
            /* Skip $ used to escape an identifier */
            p++;
            badid = !nasm_isidchar(*p);
            if (globl.dollarhex)
                badid |= nasm_isnumchar(*p);
        } else {
            badid = !nasm_isidstart(*p);
        }

        if (!badid) {
            while (*p && !nasm_isspace(*p)) {
                if (q >= debugid + sizeof debugid - 1) {
                    overlong = true;
                    break;
                }
                if (!nasm_isidchar(*p))
                    badid = true;
                *q++ = *p++;
            }
            *q = 0;
        }
        if (badid) {
            nasm_nonfatal("identifier expected after DEBUG");
            break;
        }
        if (overlong) {
            nasm_nonfatal("DEBUG identifier too long");
            break;
        }
        p = nasm_skip_spaces(p);
        if (pass_final())
            dfmt->debug_directive(debugid, p);
        break;
    }

    case D_WARNING:         /* [WARNING {push|pop|{+|-|*}warn-name}] */
        value = nasm_skip_spaces(value);
        if ((*value | 0x20) == 'p') {
            if (!nasm_stricmp(value, "push"))
                push_warnings();
            else if (!nasm_stricmp(value, "pop"))
                pop_warnings();
        } else {
            set_warning_status(value);
        }
        break;

    case D_CPU:         /* [CPU] */
        set_cpu(value);
        break;

    case D_LIST:        /* [LIST {+|-}] */
        value = nasm_skip_spaces(value);
        if (*value == '+') {
            user_nolist = false;
        } else {
            if (*value == '-') {
                user_nolist = !list_option('F');
            } else {
                bad_param = true;
            }
        }
        break;

    case D_DEFAULT:         /* [DEFAULT] */
    {
        enum ea_flags relabs_applies = EAF_NOTFSGS;
        bool eat_colon = false;

        stdscan_reset(value);
        tokval.t_type = TOKEN_INVALID;
        while (!bad_param) {
            enum token_type type = stdscan(NULL, &tokval);
            if (type <= 0)
                break;

            switch (tokval.t_type) {
            case TOKEN_REG:
            case TOKEN_SPECIAL:
            case TOKEN_PREFIX:
                switch (tokval.t_integer) {
                case R_FS:
                    relabs_applies = EAF_FS;
                    eat_colon = true;
                    goto next_token;
                case R_GS:
                    relabs_applies = EAF_GS;
                    eat_colon = true;
                    goto next_token;
                case S_REL:
                    globl.rel    |= relabs_applies;
                    globl.reldef |= relabs_applies;
                    break;
                case S_ABS:
                    globl.rel    &= ~relabs_applies;
                    globl.reldef |= relabs_applies;
                    break;
                case P_BND:
                    globl.bnd = 1;
                    break;
                case P_NOBND:
                    globl.bnd = 0;
                    break;
                default:
                    bad_param = true;
                    break;
                }
                break;

            case ',':
                break;

            case ':':
                if (eat_colon) {
                    eat_colon = false;
                    goto next_token;
                }
            /* else fall through */
            default:
                bad_param = true;
                break;
            }

            eat_colon = false;
            relabs_applies = EAF_NOTFSGS;
        next_token:
            ;
        }
        break;
    }

    case D_FLOAT:
        if (float_option(value)) {
            nasm_nonfatal("unknown 'float' directive: %s", value);
        }
        break;

    case D_DOLLARHEX:
        get_boolean_option(value, &globl.dollarhex);
        break;

    case D_PRAGMA:
        process_pragma(value);
        break;

    case D_PREFIX:
    case D_GPREFIX:
    case D_SUFFIX:
    case D_GSUFFIX:
    case D_POSTFIX:
    case D_GPOSTFIX:
    case D_LPREFIX:
    case D_LSUFFIX:
    case D_LPOSTFIX:
        set_label_mangle(d, value);
        break;
    }

    /* A common error message */
    if (bad_param) {
        nasm_nonfatal("invalid parameter to [%s] directive", directive);
    }

    return true;
}
