/* ----------------------------------------------------------------------- *
 *
 *   Copyright 1996-2018 The NASM Authors - All Rights Reserved
 *   See the file AUTHORS included with the NASM distribution for
 *   the specific copyright holders.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following
 *   conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *
 *     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *     INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *     MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 *     CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *     NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 *     OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 *     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ----------------------------------------------------------------------- */

/*
 * Parse and handle assembler directives
 */

#include "compiler.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#include "nasm.h"
#include "nasmlib.h"
#include "ilog2.h"
#include "error.h"
#include "float.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"

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

static iflag_t get_cpu(const char *value)
{
    iflag_t r;
    const struct cpunames *cpu;
    static const struct cpunames cpunames[] = {
        { "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 },
        { "any", IF_PLEVEL },
        { "default", IF_PLEVEL },
        { "all", IF_PLEVEL },
        { NULL, IF_PLEVEL }     /* Error and final default entry */
    };

    iflag_clear_all(&r);

    for (cpu = cpunames; cpu->name; cpu++) {
        if (!nasm_stricmp(value, cpu->name))
            break;
    }

    if (!cpu->name) {
        nasm_error(pass0 < 2 ? ERR_NONFATAL : ERR_FATAL,
                   "unknown 'cpu' type '%s'", value);
    }

    iflag_set_cpu(&r, cpu->level);
    return r;
}

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_error(ERR_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_error(ERR_NONFATAL,
                       "cannot specify 64-bit segment on processor below an x86-64");
            i = 16;
        }
        break;
    default:
        nasm_error(pass0 < 2 ? ERR_NONFATAL : ERR_FATAL,
                   "`%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, *buf;

    buf = nasm_skip_spaces(*directive);

    /*
     * It should be enclosed in [ ].
     * XXX: we don't check there is nothing else on the remainder of the
     * line, except a possible comment.
     */
    if (*buf != '[')
        return D_none;
    q = strchr(buf, ']');
    if (!q)
        return D_corrupt;

    /*
     * Strip off the comments.  XXX: this doesn't account for quoted
     * strings inside a directive.  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.
     */
    p = strchr(buf, ';');
    if (p) {
        if (p < q) /* ouch! somewhere inside */
            return D_corrupt;
        *p = '\0';
    }

    /* 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);
}

/*
 * 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;
    int pass2 = passn > 1 ? 2 : 1;
    enum label_type type;

    d = parse_directive_line(&directive, &value);

    switch (d) {
    case D_none:
        return D_none;      /* Not a directive */

    case D_corrupt:
	nasm_error(ERR_NONFATAL, "invalid directive line");
	break;

    default:			/* It's a backend-specific directive */
        switch (ofmt->directive(d, value, pass2)) {
        case DIRR_UNKNOWN:
            goto unknown;
        case DIRR_OK:
        case DIRR_ERROR:
            break;
        case DIRR_BADPARAM:
            bad_param = true;
            break;
        default:
            panic();
        }
        break;

    case D_unknown:
    unknown:
        nasm_error(pass0 < 2 ? ERR_NONFATAL : ERR_PANIC,
                   "unrecognised directive [%s]", directive);
        break;

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

        if (seg == NO_SEG) {
            nasm_error(pass0 < 2 ? ERR_NONFATAL : ERR_PANIC,
                       "segment name `%s' not recognized", value);
        } else {
            globalbits = sb;
            switch_segment(seg);
        }
        break;
    }

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

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

		if (!is_power2(e->value)) {
                    nasm_error(ERR_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_error(ERR_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] */
        globalbits = 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_COMMON:
        type = LBL_COMMON;
        goto symdef;

    symdef:
    {
        bool validid = true;
        int64_t size = 0;
        char *sizestr;
        bool rn_error;

        if (*value == '$')
            value++;        /* skip initial $ if present */

        q = value;
        if (!isidstart(*q)) {
            validid = false;
        } else {
            q++;
            while (*q && *q != ':' && !nasm_isspace(*q)) {
                if (!isidchar(*q))
                    validid = false;
                q++;
            }
        }
        if (!validid) {
            nasm_error(ERR_NONFATAL,
                       "identifier expected after %s, got `%s'",
                       directive, value);
            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_error(ERR_NONFATAL,
                           "%s size specified in common declaration",
                           sizestr ? "invalid" : "no");
        } else if (sizestr) {
            nasm_error(ERR_NONFATAL, "invalid syntax in %s declaration",
                       directive);
        }

        if (!declare_label(value, type, special))
            break;
        
        if (type == LBL_COMMON || type == LBL_EXTERN)
            define_label(value, 0, size, false);

    	break;
    }

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

        stdscan_reset();
        stdscan_set(value);
        tokval.t_type = TOKEN_INVALID;
        e = evaluate(stdscan, NULL, &tokval, NULL, pass2, NULL);
        if (e) {
            if (!is_reloc(e))
                nasm_error(pass0 ==
                           1 ? ERR_NONFATAL : ERR_PANIC,
                           "cannot use non-relocatable expression as "
                           "ABSOLUTE address");
            else {
                absolute.segment = reloc_seg(e);
                absolute.offset = reloc_value(e);
            }
        } else if (passn == 1)
            absolute.offset = 0x100;     /* don't go near zero in case of / */
        else
            nasm_panic(0, "invalid ABSOLUTE address "
                       "in pass two");
        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 (!isidstart(*p)) {
            badid = true;
        } else {
            while (*p && !nasm_isspace(*p)) {
                if (q >= debugid + sizeof debugid - 1) {
                    overlong = true;
                    break;
                }
                if (!isidchar(*p))
                    badid = true;
                *q++ = *p++;
            }
            *q = 0;
        }
        if (badid) {
            nasm_error(passn == 1 ? ERR_NONFATAL : ERR_PANIC,
                       "identifier expected after DEBUG");
            break;
        }
        if (overlong) {
            nasm_error(passn == 1 ? ERR_NONFATAL : ERR_PANIC,
                       "DEBUG identifier too long");
            break;
        }
        p = nasm_skip_spaces(p);
        if (pass0 == 2)
            dfmt->debug_directive(debugid, p);
        break;
    }

    case D_WARNING:         /* [WARNING {+|-|*}warn-name] */
        if (!set_warning_status(value)) {
            nasm_error(ERR_WARNING|WARN_UNK_WARNING,
                       "unknown warning option: %s", value);
        }
        break;

    case D_CPU:         /* [CPU] */
        cpu = get_cpu(value);
        break;

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

    case D_DEFAULT:         /* [DEFAULT] */
        stdscan_reset();
        stdscan_set(value);
        tokval.t_type = TOKEN_INVALID;
        if (stdscan(NULL, &tokval) != TOKEN_INVALID) {
            switch (tokval.t_integer) {
            case S_REL:
                globalrel = 1;
                break;
            case S_ABS:
                globalrel = 0;
                break;
            case P_BND:
                globalbnd = 1;
                break;
            case P_NOBND:
                globalbnd = 0;
                break;
            default:
                bad_param = true;
                break;
            }
        } else {
            bad_param = true;
        }
        break;

    case D_FLOAT:
        if (float_option(value)) {
            nasm_error(pass0 < 2 ? ERR_NONFATAL : ERR_PANIC,
                       "unknown 'float' directive: %s", value);
        }
        break;

    case D_PRAGMA:
        process_pragma(value);
        break;
    }


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

    return d != D_none;
}
