/*
 * Copyright (C) Jan 2019 Mellanox Technologies Ltd. All rights reserved.
 * Copyright (c) 2013-2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "expr.h"

char* Expr::str;
char* Expr::initial_arg;
Expr::status Expr::state;

/*
 * Number of elements in array
 */
#define numbel(x) (sizeof(x) / sizeof((x)[0]))

/*
 * Unary prefixes which present radix
 */
#define RADIX16 "0x"
#define RADIX2 "0b"

/*
 * Unary operations
 */
#define ULOGNOT 71   /* !    */
#define UMINUS 72    /* -    */
#define UNOT 73      /* ~    */
#define UPLUS 74     /* +    */
#define U2POW 75     /* :    */
#define U2POW_ALT 76 /* POW2 */
#define ULOG2 77     /* '    */
#define ULOG2_ALT 78 /* LOG2 */
#define SWAP32 79    /* SWAP32 */
#define SWAP16 80    /* SWAP16 */

/*
 * Binary operations
 */
#define AND 1      /* &&     */
#define BIT_AND 2  /* &      */
#define BIT_OR 3   /* |      */
#define BIT_XOR 4  /* ^      */
#define DIVID 5    /* /      */
#define EQ 6       /* ==     */
#define GREAT 7    /* >      */
#define GREAT_EQ 8 /* >=     */
#define LESS 9     /* <      */
#define LESS_EQ 10 /* <=     */
#define MINUS 11   /* -      */
#define MODUL 12   /* %      */
#define MULT 13    /* *      */
#define NOTEQ 14   /* !=     */
#define OR 15      /* ||     */
#define PLUS 16    /* +      */
#define SHIFT_L 17 /* <<     */
#define SHIFT_R 18 /* >>     */
#define XOR 19     /* log.xor*/

/*
 * Other token types
 */
#define VALUE 103 /* Name or constant */
#define EXPR_OK 0 /* Expression getted successfully */

/*
 * Miscellaneous constant
 */
#ifdef IGNORE
#undef IGNORE
#endif
#define IGNORE " \t\n\r" /* Ignore in parsing */
#define HIGH_PRI 1
#define LOW_PRI 9

/*
 * Unary operations. Priority not applicable,
 * all unary operations have same priority.
 */
static Expr::table unar[] = {{U2POW, 0, ":"},       {U2POW_ALT, 0, "POW2"}, {ULOG2, 0, "'"}, {ULOG2_ALT, 0, "LOG2"},
                             {ULOGNOT, 0, "!"},     {UMINUS, 0, "-"},       {UNOT, 0, "~"},  {UPLUS, 0, "+"},
                             {SWAP32, 0, "SWAP32"}, {SWAP16, 0, "SWAP16"}};
static int Lunar = numbel(unar);

/*
 * Binary operations.
 */
static Expr::table binar[] = {
  {MODUL, 1, "%"},           {MULT, 1, "*"},     {DIVID, 1, "/"},         {PLUS, 2, "+"},
  {MINUS, 2, "-"},           {SHIFT_L, 3, "<<"}, {SHIFT_L, 3, "SHIFT_L"}, {SHIFT_R, 3, ">>"},
  {SHIFT_R, 3, "SHIFT_R"},   {GREAT, 4, ">"},    {GREAT, 4, "GREAT"},     {GREAT_EQ, 4, ">="},
  {GREAT_EQ, 4, "GREAT_EQ"}, {LESS, 4, "<"},     {LESS, 4, "LESS"},       {LESS_EQ, 4, "<="},
  {LESS_EQ, 4, "LESS_EQ"},   {EQ, 5, "=="},      {EQ, 5, "EQ"},           {NOTEQ, 5, "!="},
  {NOTEQ, 5, "NOTEQ"},       {BIT_AND, 6, "&"},  {BIT_AND, 6, "BIT_AND"}, {BIT_OR, 7, "|"},
  {BIT_OR, 7, "BIT_OR"},     {BIT_XOR, 7, "^"},  {BIT_XOR, 7, "BIT_XOR"}, {AND, 8, "&&"},
  {AND, 8, "AND"},           {OR, 9, "||"},      {OR, 9, "OR"},           {XOR, 9, "XOR"}};
static int Lbinar = numbel(binar);

/********************************************************
 * routine:     expr
 *
 * description:
 *     Main routine for expression evaluation. Do some interface
 *     functions and call binary operation evaluation with lowest
 *     priority.
 *
 * arguments:
 *     pstr            pointer to string which contains expression to
 *                     evaluation. After successfully evaluation expr
 *                     moves this pointer to end of expression (first
 *                     unparsed character).
 *     result          pointer to unsigned long to put result of expression.
 *     dr              default radix for numbers.
 *     ErrorRep        pointer to callbeck routine for error reporting.
 *                     arguments - like printf.
 *
 * return code:
 *     >0              length of evaluated expression.
 *     <0              error code, one of following:
 *        ERR_RPAR_EXP   - Right parentheses expected.
 *        ERR_VALUE_EXP  - Value expected.
 *        ERR_BIN_EXP    - Binary operation expected. It is a normal situation
 *                         on experssion end.
 *        ERR_DIV_ZERO   - Divide zero attempt.
 *        ERR_BAD_NUMBER - Bad constant syntax.
 *        ERR_BAD_NAME   - Name not found in symbol table.
 *
 ********************************************************/
int Expr::expr(char** pstr, u_int64_t* result)
{
    int rc = 0;
    int len = 0;

    str = *pstr;
    initial_arg = *pstr;
    state = was_bin;

    /* Operation(s) found, do really evaluation. */
    rc = GetBinaryOp(result, LOW_PRI);
    if (rc == EXPR_OK || rc == ERR_BIN_EXP)
    {
        len = (int)(str - *pstr);
        *pstr = str;
        return len;
    }
    else
    {
        return rc;
    }
}

/********************************************************
 * routine:     GetBinaryOp
 *
 * description:
 *     Executes binary operations with current priority level.
 *     If priority great then constant HIGH_PRI, calls itself
 *     recursive with decreased priority to get  operands for
 *     expression, otherwise calls GetUnaryOp to get operands.
 *
 * arguments:
 *     val             Pointer to unsigned long where result of
 *                     operation will be placed.
 *     priority        Execute operation only with this priority
 *
 * return code:
 *     EXPR_OK         Binary operation was calculated successfully
 *     ERR_DIV_ZERO    Zero divide attempt
 *     otherwise       Error code from GetUnaryOp (see)
 *
 * side effects:
 *     Attention, deep recursion...
 *
 ********************************************************/
int Expr::GetBinaryOp(u_int64_t* val, int priority)
{
    char* str_old;
    int rc = 0;
    int i = 0;
    u_int64_t left = 0;
    u_int64_t right = 0;
    token curr;
    status stat_old;

    rc = priority > HIGH_PRI ? GetBinaryOp(&left, priority - 1) : GetUnaryOp(&left);
    if (rc != EXPR_OK)
    {
        return rc;
    }

    for (;;)
    {
        str_old = str;
        stat_old = state;
        GetToken(&curr);
        for (i = 0; i < Lbinar; i++)
        {
            if (binar[i].type == curr.type && binar[i].pri == priority)
            {
                rc = priority > HIGH_PRI ? GetBinaryOp(&right, priority - 1) : GetUnaryOp(&right);
                if (rc != EXPR_OK)
                {
                    str = str_old;
                    state = stat_old;
                    return rc;
                }

                switch (curr.type)
                {
                    case MODUL:
                        if (right == 0)
                        {
                            ErrorReport("Zero modulo attempt.\n");
                            return ERR_DIV_ZERO;
                        }
                        left = left % right;
                        break;

                    case MULT:
                        left = left * right;
                        break;

                    case DIVID:
                        if (right == 0)
                        {
                            ErrorReport("Zero divide attempt.\n");
                            return ERR_DIV_ZERO;
                        }
                        left = left / right;
                        break;

                    case PLUS:
                        left = left + right;
                        break;

                    case MINUS:
                        left = left - right;
                        break;

                    case SHIFT_L:
                        left = (left << (int)right);
                        break;

                    case SHIFT_R:
                        left = (left >> (int)right);
                        break;

                    case GREAT:
                        left = (left > right);
                        break;

                    case GREAT_EQ:
                        left = (left >= right);
                        break;

                    case LESS:
                        left = (left < right);
                        break;

                    case LESS_EQ:
                        left = (left <= right);
                        break;

                    case EQ:
                        left = (left == right);
                        break;

                    case NOTEQ:
                        left = (left != right);
                        break;

                    case BIT_AND:
                        left = left & right;
                        break;

                    case BIT_OR:
                        left = left | right;
                        break;

                    case BIT_XOR:
                        left = left ^ right;
                        break;

                    case AND:
                        left = (left && right);
                        break;

                    case OR:
                        left = (left || right);
                        break;

                    case XOR:
                        left = (left && !right) || (!left && right);
                        break;
                }
                break;
            }
        }
        if (i >= Lbinar)
        {
            UngetToken(curr);
            *val = left;
            return EXPR_OK;
        }
    }
}

/********************************************************
 * routine:     GetUnaryOp
 *
 * description:
 *     Executes unary operations.
 *
 * arguments:
 *     val             Pointer to unsigned long where result of
 *                     operation will be placed.
 *
 * return code:
 *     EXPR_OK         Binary operation was calculated successfully
 *     otherwise       Error code from GetToken (see)
 *
 ********************************************************/
int Expr::GetUnaryOp(u_int64_t* val)
{
    int rc, unary_op;
    u_int64_t tmp = 0;
    u_int64_t tmp1 = 0;
    token curr;
    u_int64_t tmpVal = *val;

    GetToken(&curr);
    switch (unary_op = curr.type)
    {
        case U2POW:
        case U2POW_ALT:
        case ULOG2:
        case ULOG2_ALT:
        case ULOGNOT:
        case UMINUS:
        case UNOT:
        case UPLUS:
        case SWAP32:
        case SWAP16:
            /*
             * Was unary operation - retrieve more token.
             */
            rc = GetUnaryOp(val);
            if (rc != EXPR_OK)
            {
                return rc;
            }

            /* OK, execute required unary operation. */
            switch (unary_op)
            {
                case U2POW_ALT:
                case U2POW:
                    *val = (u_int64_t)1 << (int)(*val);
                    break;

                case ULOG2_ALT:
                case ULOG2:
                    if (*val != 0)
                    {
                        for (tmp = 1, tmp1 = 0; tmp < *val; tmp = tmp << 1)
                            ++tmp1;
                        *val = tmp1;
                    }
                    break;

                case ULOGNOT:
                    *val = !(*val);
                    break;

                case UMINUS:
                    *val = (u_int64_t)(-((int64_t)(*val)));
                    break;

                case UNOT:
                    *val = ~(*val);
                    break;

                case UPLUS:
                    break;

                case SWAP32:
                    *val = ((tmpVal & 0x000000FFUL) << 24) | ((tmpVal & 0x0000FF00UL) << 8) |
                           ((tmpVal & 0x00FF0000UL) >> 8) | ((tmpVal & 0xFF000000UL) >> 24);
                    break;

                case SWAP16:
                    *val = ((tmpVal & 0xFF000000U) >> 8) | ((tmpVal & 0x00FF0000U) << 8) |
                           ((tmpVal & 0x0000FF00U) >> 8) | ((tmpVal & 0x000000FFU) << 8);
                    break;
            }
            break;

        case VALUE:
            /*
             * Unary operation is omitted. Assume no unary operations.
             */
            *val = curr.value;
            break;

        default:
            /*
             * Here we expect only unary operation or value.
             * Something other - error. Bring error code witch
             * present by curr.type to caller.
             */
            UngetToken(curr);
            return curr.type;
    }

    return EXPR_OK;
}

/********************************************************
 * routine:     GetToken
 *
 * description:
 *     Gets next token from static pointer str.
 *
 * arguments:
 *     pt              pointer to token. GetToken fills in all
 *                     field by this pointer.
 *
 * return code:
 *     None
 *
 * side effects:
 *     1. May insrease pointer str (if token getted successfully).
 *     2. May change static variable state (if token getted
 *        successfully and state was changed).
 *
 ********************************************************/
void Expr::GetToken(token* pt)
{
    char *old_str, *p;
    int i, rc, oplen;

    /*
     * Skip all leading ignorable characters
     */
    do
    {
        old_str = str;
        for (p = (char*)IGNORE; *p; p++)
            if (*str == *p)
            {
                str++;
            }
    } while ((old_str != str) && *str);

    pt->value = 0;
    pt->beg = str;
    pt->sta = state;
    switch (state)
    {
        case was_bin:
            /*
             * Was binary operation (or begin of experssion).
             * Unary operation expected.
             * Try find unary operation with maximal length.
             */
            oplen = 0;
            for (i = 0; i < Lunar; i++)
                if (!strncmp(unar[i].st, str, strlen(unar[i].st)))
                {
                    if ((int)strlen(unar[i].st) > oplen)
                    {
                        pt->type = unar[i].type;
                        oplen = strlen(unar[i].st);
                    }
                }
            if (oplen)
            {
                /* Unary operation found. */
                str += oplen;
                state = was_bin;
                return;
            }

            /*
             * Unary operation not found.
             * Number or name expected.
             */
            if (!strncmp(str, RADIX16, strlen(RADIX16)) || !strncmp(str, RADIX2, strlen(RADIX2)))
            {
                /*
                 * Number expected.
                 */
                rc = GetNumb(&pt->value);
                if (rc == EXPR_OK)
                {
                    pt->type = VALUE;
                }
                else
                {
                    pt->type = rc;
                    return;
                }
            }
            else if (valid_name(*str) || valid_digit(*str, def_radix))
            {
                /*
                 * Name or number expected.
                 */
                rc = GetName(&pt->value);
                if (rc == EXPR_OK)
                {
                    pt->type = VALUE;
                }
                else
                {
                    pt->type = rc;
                    return;
                }
            }
            else if (*str == '(')
            {
                /*
                 * Parentheses expression expected.
                 */
                old_str = str++;
                rc = GetBinaryOp(&pt->value, LOW_PRI);
                if (rc == EXPR_OK)
                {
                    pt->type = VALUE;
                }
                else
                {
                    pt->type = rc;
                    return;
                }
                if (*str == ')')
                {
                    str++;
                }
                else
                {
                    /* error, right parentheses expected. */
                    pt->type = ERR_RPAR_EXP;
                    if (*str)
                    {
                        ErrorReport("Instead of\"" + std::string(str) + "\" right parentheses expected.\n");
                    }
                    else
                    {
                        ErrorReport("Unexpected end in expression \"" + std::string(initial_arg) +
                                    "\" - right parentheses expected.\n");
                    }
                    str = old_str;
                    return;
                }
            }
            else
            {
                /* error, number or name or unary operation expected. */
                pt->type = ERR_VALUE_EXP;
                if (*str)
                {
                    ErrorReport("Instead of \"" + std::string(str) + "\" value or unary operation expected.\n");
                }
                else
                {
                    ErrorReport("Unexpected end in expression \"" + std::string(initial_arg) +
                                "\" - value or unary operation expected.\n");
                }
                return;
            }
            state = was_opr;
            return;

        case was_opr:
            /*
             * Was operand. Binary operation expected
             * Try find binary operation with maximal length.
             */
            oplen = 0;
            for (i = 0; i < Lbinar; i++)
                if (!strncmp(binar[i].st, str, strlen(binar[i].st)))
                {
                    if ((int)strlen(binar[i].st) > oplen)
                    {
                        oplen = strlen(binar[i].st);
                        pt->type = binar[i].type;
                    }
                }
            if (oplen)
            {
                /* Binary operation found. */
                str += oplen;
                state = was_bin;
                return;
            }

            /* error, binary operation expected. */
            pt->type = ERR_BIN_EXP;
            return;
    }
}

/********************************************************
 * routine:     UngetToken
 *
 * description:
 *     Ungets previously getted token.
 *
 * arguments:
 *     t               token to unget
 *
 * return code:
 *     None
 *
 * side effects:
 *     1. Restores str as was before token.
 *     2. Restores state as was before token
 *
 ********************************************************/
void Expr::UngetToken(token t)
{
    str = t.beg;
    state = t.sta;
}

/********************************************************
 * routine:     GetNumb
 *
 * description:
 *     Gets constant from str. First character of constant may
 *     be special value to set radix (% for 2, & for 10 and $ for 16).
 *
 * arguments:
 *     val              pointer to unsigned long to put constatnt value.
 *
 * return code:
 *     EXPR_OK        - Constant OK
 *     ERR_BAD_NUMBER - Bad constant syntax
 *
 * side effects:
 *     Moves pointer str to end of constant.
 *
 ********************************************************/
int Expr::GetNumb(u_int64_t* val)
{
    int radix = def_radix;

    if (!strncmp(str, RADIX16, strlen(RADIX16)))
    {
        radix = 16;
        str += strlen(RADIX16);
    }
    else if (!strncmp(str, RADIX2, strlen(RADIX2)))
    {
        radix = 2;
        str += strlen(RADIX2);
    }

    if (!valid_digit(*str, radix))
    {
        ErrorReport("\"" + std::string(str) + "\" -- bad constant syntax.\n");
        return ERR_BAD_NUMBER;
    }

    *val = 0L;
    for (; valid_digit(*str, radix); str++)
        *val =
          *val * radix + (('a' <= *str && *str <= 'f') ? *str - 'a' + 10 :
                                                         (('A' <= *str && *str <= 'F') ? *str - 'A' + 10 : *str - '0'));

    return EXPR_OK;
}

/********************************************************
 * routine:     GetName
 *
 * description:
 *     Gets name from str and tries to find it in symbol
 *     table. If name looks as constant in "def_radix",
 *     interprets it as constant rather than name.
 *
 * arguments:
 *     val          - pointer to unsigned long to put value
 *
 * return code:
 *     EXPR_OK      - Name successfully found in symbol table and
 *                    substituted by its value.
 *     ERR_BAD_NAME - Name not found in symbol table.
 *
 * side effects:
 *     Moves pointer str to end of name.
 *
 ********************************************************/
#define MAXNAM 100
int Expr::GetName(u_int64_t* val)
{
    static char name[MAXNAM];
    char *p, *old_str;

    old_str = str; /* Save start name position. */

    /* Get name. */
    for (p = name; valid_name(*str);)
        *p++ = *str++;
    *p++ = '\0';

    /* Try interpret as constant first. */
    for (p = name; *p; p++)
        if (!valid_digit(*p, def_radix))
        {
            break;
        }
    if (!*p)
    {
        /*
         * All characters of this name are valid digit in radix
         * "def_radix". So, interpret this name as constant.
         */
        str = old_str;       /* unget name to common buffer */
        return GetNumb(val); /* and get it as number        */
    }

    /* Retrieve name from symbol table. */
    /* -------------------------------- */
    if (ResolveName(name, val) == 0)
    {
        return EXPR_OK;
    }
    else
    {
        ErrorReport("Symbolic name \"" + std::string(name) + "\" not resolved.\n");
        return ERR_BAD_NAME;
    }
}

/********************************************************
 * routine:     valid_name
 *
 * description:
 *     Function checks if character may be part of symbolic name.
 *
 * arguments:
 *     ch           - character to check
 *
 * return code:
 *     0 - character is not valid
 *     1 - character is valid
 *
 ********************************************************/
int Expr::valid_name(char ch)
{
    return (ch == '_' || ch == '.') ? 1 : isalnum(ch);
}

/********************************************************
 * routine:     valid_digit
 *
 * description:
 *     Check validity of digit ch (in ASCII, ex. '1') according to radix 'radix'.
 *
 * arguments:
 *     ch           - character to check
 *     radix        - radix
 *
 * return code:
 *     0 - character is not valid
 *     1 - character is valid
 *
 ********************************************************/
int Expr::valid_digit(char ch, int radix)
{
    switch (radix)
    {
        case 2:
            return (ch == '0' || ch == '1') ? 1 : 0;

        case 10:
            return isdigit(ch);

        case 16:
            return isxdigit(ch);

        default:
            return 1;
    }
}

/********************************************************
 * routine:     ErrorReport
 *
 * description:
 *     Error reporting
 *
 * arguments:
 *     like printf
 *
 ********************************************************/
void Expr::ErrorReport(const std::string& msg)
{
    Error(msg);
}
