/**
 * \file libyasm/valparam.h
 * \brief YASM value/parameter interface.
 *
 * \rcs
 * $Id: valparam.h 2101 2008-05-23 06:46:51Z peter $
 * \endrcs
 *
 * \license
 *  Copyright (C) 2001-2007  Peter Johnson
 *
 * 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 AUTHOR AND OTHER 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 AUTHOR OR OTHER 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.
 * \endlicense
 */
#ifndef YASM_VALPARAM_H
#define YASM_VALPARAM_H

#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif

/** Value/parameter pair.  \internal */
struct yasm_valparam {
    /*@reldef@*/ STAILQ_ENTRY(yasm_valparam) link;  /**< Next pair in list */
    /*@owned@*/ /*@null@*/ char *val;           /**< Value */

    /** Parameter type. */
    enum yasm_param_type {
        YASM_PARAM_ID,                          /**< Identifier */
        YASM_PARAM_STRING,                      /**< String */
        YASM_PARAM_EXPR                         /**< Expression */
    } type;                                     /**< Parameter type */

    /** Parameter value. */
    union yasm_param {
        /*@owned@*/ char *id;                   /**< Identifier */
        /*@owned@*/ char *str;                  /**< String */
        /*@owned@*/ yasm_expr *e;               /**< Expression */
    } param;                                    /**< Parameter */

    /** Prefix character that indicates a raw identifier.  When
     * yasm_vp_string() is called on a #YASM_PARAM_ID, all characters are
     * returned.  When yasm_vp_id() is called on a #YASM_PARAM_ID, if the
     * identifier begins with this character, this character is stripped
     * from the returned value.
     */
    char id_prefix;
};

/** Linked list of value/parameter pairs.  \internal */
/*@reldef@*/ STAILQ_HEAD(yasm_valparamhead, yasm_valparam);

/** Directive list entry structure. */
struct yasm_directive {
    /** Directive name.  GAS directives should include the ".", NASM
     * directives should just be the raw name (not including the []).
     * NULL entry required to terminate list of directives.
     */
    /*@null@*/ const char *name;

    const char *parser;                     /**< Parser keyword */

    /** Handler callback function for the directive.
     * \param object            object 
     * \param valparams         value/parameters
     * \param objext_valparams  object format-specific value/parameters
     * \param line              virtual line (from yasm_linemap)
     */
    void (*handler) (yasm_object *object, yasm_valparamhead *valparams,
                     yasm_valparamhead *objext_valparams, unsigned long line);

    /** Flags for pre-handler parameter checking. */
    enum yasm_directive_flags {
        YASM_DIR_ANY = 0,           /**< Any valparams accepted */
        YASM_DIR_ARG_REQUIRED = 1,  /**< Require at least 1 valparam */
        YASM_DIR_ID_REQUIRED = 2    /**< First valparam must be ID */
    } flags;
};

/** Call a directive.  Performs any valparam checks asked for by the
 * directive prior to call.  Note that for a variety of reasons, a directive
 * can generate an error.
 * \param directive             directive
 * \param object                object 
 * \param valparams             value/parameters
 * \param objext_valparams      object format-specific value/parameters
 * \param line                  virtual line (from yasm_linemap)
 */
YASM_LIB_DECL
void yasm_call_directive(const yasm_directive *directive, yasm_object *object,
                         yasm_valparamhead *valparams,
                         yasm_valparamhead *objext_valparams,
                         unsigned long line);

/** Create a new valparam with identifier parameter.
 * \param v             value
 * \param p             parameter
 * \param id_prefix     identifier prefix for raw identifiers
 * \return Newly allocated valparam.
 */
YASM_LIB_DECL
yasm_valparam *yasm_vp_create_id(/*@keep@*/ char *v, /*@keep@*/ char *p,
                                 int id_prefix);

/** Create a new valparam with string parameter.
 * \param v     value
 * \param p     parameter
 * \return Newly allocated valparam.
 */
YASM_LIB_DECL
yasm_valparam *yasm_vp_create_string(/*@keep@*/ char *v, /*@keep@*/ char *p);

/** Create a new valparam with expression parameter.
 * \param v     value
 * \param p     parameter
 * \return Newly allocated valparam.
 */
YASM_LIB_DECL
yasm_valparam *yasm_vp_create_expr(/*@keep@*/ char *v,
                                   /*@keep@*/ yasm_expr *p);

/** Get a valparam parameter as an expr.  If the parameter is an identifier,
 * it's treated as a symbol (yasm_symtab_use() is called to convert it).
 * \param vp            valparam
 * \param symtab        symbol table
 * \param line          virtual line
 * \return Expression, or NULL if vp is NULL or the parameter cannot be
 *         converted to an expression.
 */
YASM_LIB_DECL
/*@null@*/ /*@only@*/ yasm_expr *yasm_vp_expr
    (const yasm_valparam *vp, yasm_symtab *symtab, unsigned long line);

/** Get a valparam parameter as a string.  If the parameter is an identifier,
 * it's treated as a string.
 * \param vp            valparam
 * \return String, or NULL if vp is NULL or the parameter cannot be realized
 *         as a string.
 */
YASM_LIB_DECL
/*@null@*/ /*@dependent@*/ const char *yasm_vp_string(const yasm_valparam *vp);

/** Get a valparam parameter as an identifier.
 * \param vp            valparam
 * \return Identifier (string), or NULL if vp is NULL or the parameter is not
 *         an identifier.
 */
YASM_LIB_DECL
/*@null@*/ /*@dependent@*/ const char *yasm_vp_id(const yasm_valparam *vp);

/** Create a new linked list of valparams.
 * \return Newly allocated valparam list.
 */
YASM_LIB_DECL
yasm_valparamhead *yasm_vps_create(void);

/** Destroy a list of valparams (created with yasm_vps_create).
 * \param headp         list of valparams
 */
YASM_LIB_DECL
void yasm_vps_destroy(yasm_valparamhead *headp);

/** Initialize linked list of valparams.
 * \param headp linked list
 */
void yasm_vps_initialize(/*@out@*/ yasm_valparamhead *headp);
#ifndef YASM_DOXYGEN
#define yasm_vps_initialize(headp)      STAILQ_INIT(headp)
#endif

/** Destroy (free allocated memory for) linked list of valparams (created with
 * yasm_vps_initialize).
 * \warning Deletes val/params.
 * \param headp linked list
 */
YASM_LIB_DECL
void yasm_vps_delete(yasm_valparamhead *headp);

/** Append valparam to tail of linked list.
 * \param headp linked list
 * \param vp    valparam
 */
void yasm_vps_append(yasm_valparamhead *headp, /*@keep@*/ yasm_valparam *vp);
#ifndef YASM_DOXYGEN
#define yasm_vps_append(headp, vp)      do {        \
        if (vp)                                     \
            STAILQ_INSERT_TAIL(headp, vp, link);    \
    } while(0)
#endif

/** Get first valparam in linked list.
 * \param headp linked list
 * \return First valparam in linked list.
 */
/*@null@*/ /*@dependent@*/ yasm_valparam *yasm_vps_first
    (yasm_valparamhead *headp);
#ifndef YASM_DOXYGEN
#define yasm_vps_first(headp)       STAILQ_FIRST(headp)
#endif

/** Get next valparam in linked list.
 * \param cur   previous valparam in linked list
 * \return Next valparam in linked list.
 */
/*@null@*/ /*@dependent@*/ yasm_valparam *yasm_vps_next(yasm_valparam *cur);
#ifndef YASM_DOXYGEN
#define yasm_vps_next(cur)          STAILQ_NEXT(cur, link)
#endif

/** Iterate through linked list of valparams.
 * \internal
 * \param iter      iterator variable
 * \param headp     linked list
 */
#ifndef YASM_DOXYGEN
#define yasm_vps_foreach(iter, headp)   STAILQ_FOREACH(iter, headp, link)
#endif

/** Print linked list of valparams.  For debugging purposes.
 * \param f     file
 * \param headp linked list
 */
YASM_LIB_DECL
void yasm_vps_print(/*@null@*/ const yasm_valparamhead *headp, FILE *f);

/** Directive valparam parse helper structure. */
typedef struct yasm_dir_help {
    /** Value portion of val=param (if needsparam=1), or standalone identifier
     * (if needsparam=0).
     */
    const char *name;

    /** 1 if value requires parameter, 0 if it must not have a parameter. */
    int needsparam;

    /** Helper callback function if name and parameter existence match.
     * \param obj       obj passed into yasm_dir_helper()
     * \param vp        value/parameter
     * \param line      line passed into yasm_dir_helper()
     * \param data      data passed into yasm_dir_helper() plus
                        #yasm_dir_help.off offset
     * \param arg       #yasm_dir_help.arg argument
     * \return -1 on error, 0 otherwise.
     */
    int (*helper) (void *obj, yasm_valparam *vp, unsigned long line,
                   void *data, uintptr_t arg);

    /** Offset added to data pointer passed into yasm_dir_helper() before
     * data pointer is given to #yasm_dir_help.helper().  This is so that
     * a structure can be passed into yasm_dir_helper() and this can be an
     * offsetof() to point the helper function to a specific structure
     * member.
     */
    size_t off;

    /** Argument to pass in as the arg parameter to #yasm_dir_help.helper().
     */
    uintptr_t arg;
} yasm_dir_help;

/** Help parse a list of directive value/parameters.  Takes an array of
 * #yasm_dir_help structures and tries to match val=param (or just val)
 * against the passed value/parameters.  When no match is found in the
 * array of help structures, calls helper_valparam.
 * \param obj       object to be passed to yasm_dir_help.helper() or
 *                  helper_valparam() callback
 * \param vp_first  first value/parameter to examine
 * \param line      virtual line number; passed down to helper callback
 * \param help      array of #yasm_dir_help structures
 * \param nhelp     number of array elements
 * \param data      base data pointer; if a match is found,
 *                  the respective #yasm_dir_help.off is added to this
 *                  prior to it being passed to the helper callback
 * \param helper_valparam   catch-all callback; should return -1 on error,
 *                          0 if not matched, 1 if matched.
 * \return -1 on error, 1 if any arguments matched (including via
 *         catch-all callback), 0 if no match.
 */
YASM_LIB_DECL
int yasm_dir_helper(void *obj, yasm_valparam *vp_first, unsigned long line,
                    const yasm_dir_help *help, size_t nhelp, void *data,
                    int (*helper_valparam) (void *object,
                                            yasm_valparam *vp,
                                            unsigned long line,
                                            void *data));

/** Standard helper for yasm_dir_helper() that simply sets a flag when called.
 * It does not look at the vp; rather, it uses the value of the arg parameter,
 * and stores an unsigned long value to data.
 * \param obj   unused
 * \param vp    unused
 * \param line  unused
 * \param data  pointer to an unsigned long
 * \param arg   flag to set
 * \return 0
 */
YASM_LIB_DECL
int yasm_dir_helper_flag_set(void *obj, yasm_valparam *vp, unsigned long line,
                             void *data, uintptr_t arg);

/** Standard helper for yasm_dir_helper() that simply ORs a flag when called.
 * It does not look at the vp; rather, it uses the value of the arg parameter,
 * and ORs it with the unsigned long value in data.
 * \param obj   unused
 * \param vp    unused
 * \param line  unused
 * \param data  pointer to an unsigned long
 * \param arg   flag to OR
 * \return 0
 */
YASM_LIB_DECL
int yasm_dir_helper_flag_or(void *obj, yasm_valparam *vp, unsigned long line,
                            void *data, uintptr_t arg);

/** Standard helper for yasm_dir_helper() that simply ANDs a flag when called.
 * It does not look at the vp; rather, it uses the value of the arg parameter,
 * and ANDs its inverse (~) with the unsigned long value in data.
 * \param obj   unused
 * \param vp    unused
 * \param line  unused
 * \param data  pointer to an unsigned long
 * \param arg   flag to AND
 * \return 0
 */
YASM_LIB_DECL
int yasm_dir_helper_flag_and(void *obj, yasm_valparam *vp, unsigned long line,
                             void *data, uintptr_t arg);

/** Standard helper for yasm_dir_helper() that parses an expr parameter.
 * The #yasm_dir_help structure that uses this function should have
 * needsparam=1.  The obj parameter to yasm_dir_helper() when this helper
 * is used MUST point to a #yasm_object.  In addition, the data parameter
 * that is ultimately passed to this function (e.g. yasm_dir_helper() data
 * parameter plus #yasm_dir_help.off) must point to a #yasm_expr *
 * initialized to NULL.
 * \param obj   object; must be #yasm_object
 * \param vp    valparam
 * \param line  virtual line number
 * \param data  pointer to #yasm_expr *
 * \param arg   unused argument
 * \return -1 on error, 0 otherwise.
 */
YASM_LIB_DECL
int yasm_dir_helper_expr(void *obj, yasm_valparam *vp, unsigned long line,
                         void *data, uintptr_t arg);

/** Standard helper for yasm_dir_helper() that parses an intnum parameter.
 * The #yasm_dir_help structure that uses this function should have
 * needsparam=1.  The obj parameter to yasm_dir_helper() when this helper
 * is used MUST point to a #yasm_object.  In addition, the data parameter
 * that is ultimately passed to this function (e.g. yasm_dir_helper() data
 * parameter plus #yasm_dir_help.off) must point to a #yasm_intnum *
 * initialized to NULL.
 * \param obj   object; must be #yasm_object
 * \param vp    valparam
 * \param line  virtual line number
 * \param data  pointer to #yasm_intnum *
 * \param arg   unused argument
 * \return -1 on error, 0 otherwise.
 */
YASM_LIB_DECL
int yasm_dir_helper_intn(void *obj, yasm_valparam *vp, unsigned long line,
                         void *data, uintptr_t arg);

/** Standard helper for yasm_dir_helper() that parses an string (or
 * standalone identifier) parameter.
 * The #yasm_dir_help structure that uses this function should have
 * needsparam=1.  The data parameter that is ultimately passed to this
 * function (e.g. yasm_dir_helper() data parameter plus #yasm_dir_help.off)
 * must point to a char * initialized to NULL.
 * \param obj   unused
 * \param vp    valparam
 * \param line  unused
 * \param data  pointer to char *
 * \param arg   unused
 * \return -1 on error, 0 otherwise.
 */
YASM_LIB_DECL
int yasm_dir_helper_string(void *obj, yasm_valparam *vp, unsigned long line,
                           void *data, uintptr_t arg);

/** Standard catch-all callback fro yasm_dir_helper().  Generates standard
 * warning for all valparams.
 * \param obj   unused
 * \param vp    valparam
 * \param line  unused
 * \param data  unused
 * \return 0
 */
YASM_LIB_DECL
int yasm_dir_helper_valparam_warn(void *obj, yasm_valparam *vp,
                                  unsigned long line, void *data);
#endif
