/*****************************************************************************/
/*                                                                           */
/*                                 ppexpr.h                                  */
/*                                                                           */
/*                      Expressions for C preprocessor                       */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 2022  The cc65 Authors                                                */
/*                                                                           */
/*                                                                           */
/* This software is provided 'as-is', without any expressed or implied       */
/* warranty.  In no event will the authors be held liable for any damages    */
/* arising from the use of this software.                                    */
/*                                                                           */
/* Permission is granted to anyone to use this software for any purpose,     */
/* including commercial applications, and to alter it and redistribute it    */
/* freely, subject to the following restrictions:                            */
/*                                                                           */
/* 1. The origin of this software must not be misrepresented; you must not   */
/*    claim that you wrote the original software. If you use this software   */
/*    in a product, an acknowledgment in the product documentation would be  */
/*    appreciated but is not required.                                       */
/* 2. Altered source versions must be plainly marked as such, and must not   */
/*    be misrepresented as being the original software.                      */
/* 3. This notice may not be removed or altered from any source              */
/*    distribution.                                                          */
/*                                                                           */
/*****************************************************************************/



/* cc65 */
#include "error.h"
#include "scanner.h"
#include "ppexpr.h"



/*****************************************************************************/
/*                                   Data                                    */
/*****************************************************************************/



/* PP expression parser status */
static int PPEvaluationEnabled = 0;
static int PPEvaluationFailed  = 0;



/*****************************************************************************/
/*                                 Forwards                                  */
/*****************************************************************************/



static void PPhie0 (PPExpr* Expr);
static void PPhie1 (PPExpr* Expr);



/*****************************************************************************/
/*                             Helper functions                              */
/*****************************************************************************/



static token_t PPFindTok (token_t Tok, const token_t* Table)
/* Find a token in a generator table */
{
    while (*Table != TOK_INVALID) {
        if (*Table == Tok) {
            return Tok;
        }
        ++Table;
    }
    return TOK_INVALID;
}



static void PPExprInit (PPExpr* Expr)
/* Initialize the expression */
{
    Expr->IVal  = 0;
    Expr->Flags = PPEXPR_NONE;
}



static void PPErrorSkipLine (void)
/* Set the expression parser error flag, skip the remain tokens till the end
** of the line, clear the current and the next tokens.
*/
{
    PPEvaluationFailed = 1;
    SkipTokens (0, 0);
    CurTok.Tok  = TOK_CEOF;
    NextTok.Tok = TOK_CEOF;
}



/*****************************************************************************/
/*                                   Code                                    */
/*****************************************************************************/



static void PPhiePrimary (PPExpr* Expr)
/* This is the lowest level of the PP expression parser */
{
    switch (CurTok.Tok) {
        case TOK_ICONST:
        case TOK_CCONST:
        case TOK_WCCONST:
            /* Character and integer constants */
            Expr->IVal = CurTok.IVal;
            /* According to the C standard, all signed types act as intmax_t
            ** and all unsigned types act as uintmax_t.
            */
            if (IsSignUnsigned (CurTok.Type)) {
                Expr->Flags |= PPEXPR_UNSIGNED;
            }
            NextToken ();
            break;

        case TOK_FCONST:
            /* Floating point constant */
            PPError ("Floating constant in preprocessor expression");
            Expr->IVal = 0;
            NextToken ();
            break;

        case TOK_LPAREN:
            /* Parse parenthesized subexpression by calling the whole parser
            ** recursively.
            */
            NextToken ();
            PPhie0 (Expr);
            ConsumeRParen ();
            break;

        case TOK_IDENT:
            /* Assume that this identifier is an undefined macro and replace
            ** it by a constant value of zero.
            */
            NextToken ();
            Expr->Flags |= PPEXPR_UNDEFINED;
            Expr->IVal = 0;
            break;

        case TOK_CEOF:
            /* Error recovery */
            break;

        default:
            /* Illegal expression in PP mode */
            PPError ("Preprocessor expression expected");
            PPErrorSkipLine ();
            break;
    }
}



static void PPhie11 (PPExpr* Expr)
/* Handle compound types (structs and arrays) etc which are invalid in PP */
{
    /* Evaluate the lhs */
    PPhiePrimary (Expr);

    /* Check for a rhs */
    while (CurTok.Tok == TOK_INC    || CurTok.Tok == TOK_DEC    ||
           CurTok.Tok == TOK_LBRACK || CurTok.Tok == TOK_LPAREN ||
           CurTok.Tok == TOK_DOT    || CurTok.Tok == TOK_PTR_REF) {

        switch (CurTok.Tok) {

            case TOK_LBRACK:
                PPError ("Token \".\" is not valid in preprocessor expressions");
                PPErrorSkipLine ();
                break;

            case TOK_LPAREN:
                /* Function call syntax is not recognized in preprocessor
                ** expressions.
                */
                PPError ("Missing binary operator before token \"(\"");
                PPErrorSkipLine ();
                break;

            case TOK_DOT:
                PPError ("Token \".\" is not valid in preprocessor expressions");
                PPErrorSkipLine ();
                break;

            case TOK_PTR_REF:
                PPError ("Token \"->\" is not valid in preprocessor expressions");
                PPErrorSkipLine ();
                break;

            case TOK_INC:
                PPError ("Token \"++\" is not valid in preprocessor expressions");
                PPErrorSkipLine ();
                break;

            case TOK_DEC:
                PPError ("Token \"--\" is not valid in preprocessor expressions");
                PPErrorSkipLine ();
                break;

            default:
                Internal ("Invalid token in PPhie11: %d", CurTok.Tok);

        }
    }

    /* Check for excessive expressions */
    if (!TokIsPunc (&CurTok)) {
        PPError ("Missing binary operator");
        PPErrorSkipLine ();
    }
}



void PPhie10 (PPExpr* Expr)
/* Handle prefixing unary operators */
{
    switch (CurTok.Tok) {

        case TOK_INC:
            PPError ("Token \"++\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_DEC:
            PPError ("Token \"--\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_PLUS:
            NextToken ();
            PPhie10 (Expr);
            Expr->IVal = +Expr->IVal;
            break;

        case TOK_MINUS:
            NextToken ();
            PPhie10 (Expr);
            Expr->IVal = -Expr->IVal;
            break;

        case TOK_COMP:
            NextToken ();
            PPhie10 (Expr);
            Expr->IVal = ~Expr->IVal;
            break;

        case TOK_BOOL_NOT:
            NextToken ();
            PPhie10 (Expr);
            Expr->IVal = !Expr->IVal;
            break;

        case TOK_CEOF:
            /* Error recovery */
            break;

        case TOK_STAR:
        case TOK_AND:
        case TOK_SIZEOF:
        default:
            /* Type cast, sizeof, *, &, are not recognized in preprocessor
            ** expressions. So everything is treated as as expression here.
            */
            PPhie11 (Expr);
            break;
    }
}



static void PPhie_internal (const token_t* Ops,   /* List of generators */
                            PPExpr* Expr,
                            void (*hienext) (PPExpr*))
/* Helper function */
{
    token_t Tok;

    hienext (Expr);

    while ((Tok = PPFindTok (CurTok.Tok, Ops)) != 0) {

        PPExpr Rhs;
        PPExprInit (&Rhs);

        /* Remember the operator token, then skip it */
        NextToken ();

        /* Get the right hand side */
        hienext (&Rhs);

        if (PPEvaluationEnabled && !PPEvaluationFailed) {

            /* Evaluate the result for operands */
            unsigned long Val1 = Expr->IVal;
            unsigned long Val2 = Rhs.IVal;

            /* If either side is unsigned, the result is unsigned */
            Expr->Flags |= Rhs.Flags & PPEXPR_UNSIGNED;

            switch (Tok) {
                case TOK_OR:
                    Expr->IVal = (Val1 | Val2);
                    break;
                case TOK_XOR:
                    Expr->IVal = (Val1 ^ Val2);
                    break;
                case TOK_AND:
                    Expr->IVal = (Val1 & Val2);
                    break;
                case TOK_PLUS:
                    Expr->IVal = (Val1 + Val2);
                    break;
                case TOK_MINUS:
                    Expr->IVal = (Val1 - Val2);
                    break;
                case TOK_MUL:
                    Expr->IVal = (Val1 * Val2);
                    break;
                case TOK_DIV:
                    if (Val2 == 0) {
                        PPError ("Division by zero");
                        Expr->IVal = 0;
                    } else {
                        /* Handle signed and unsigned operands differently */
                        if ((Expr->Flags & PPEXPR_UNSIGNED) == 0) {
                            Expr->IVal = ((long)Val1 / (long)Val2);
                        } else {
                            Expr->IVal = (Val1 / Val2);
                        }
                    }
                    break;
                case TOK_MOD:
                    if (Val2 == 0) {
                        PPError ("Modulo operation with zero");
                        Expr->IVal = 0;
                    } else {
                        /* Handle signed and unsigned operands differently */
                        if ((Expr->Flags & PPEXPR_UNSIGNED) == 0) {
                            Expr->IVal = ((long)Val1 % (long)Val2);
                        } else {
                            Expr->IVal = (Val1 % Val2);
                        }
                    }
                    break;
                default:
                    Internal ("PPhie_internal: got token 0x%X\n", Tok);
            }
        }
    }
}



static void PPhie_compare (const token_t* Ops,    /* List of generators */
                           PPExpr* Expr,
                           void (*hienext) (PPExpr*))
/* Helper function for the compare operators */
{
    token_t Tok;

    hienext (Expr);

    while ((Tok = PPFindTok (CurTok.Tok, Ops)) != 0) {

        PPExpr Rhs;

        PPExprInit (&Rhs);

        /* Skip the operator token */
        NextToken ();

        /* Get the right hand side */
        hienext (&Rhs);

        if (PPEvaluationEnabled && !PPEvaluationFailed) {

            /* If either side is unsigned, the comparison is unsigned */
            Expr->Flags |= Rhs.Flags & PPEXPR_UNSIGNED;

            /* Determine if this is a signed or unsigned compare */
            if ((Expr->Flags & PPEXPR_UNSIGNED) == 0) {

                /* Evaluate the result for signed operands */
                signed long Val1 = Expr->IVal;
                signed long Val2 = Rhs.IVal;
                switch (Tok) {
                    case TOK_EQ: Expr->IVal = (Val1 == Val2);   break;
                    case TOK_NE: Expr->IVal = (Val1 != Val2);   break;
                    case TOK_LT: Expr->IVal = (Val1 < Val2);    break;
                    case TOK_LE: Expr->IVal = (Val1 <= Val2);   break;
                    case TOK_GE: Expr->IVal = (Val1 >= Val2);   break;
                    case TOK_GT: Expr->IVal = (Val1 > Val2);    break;
                    default:     Internal ("PPhie_compare: got token 0x%X\n", Tok);
                }

            } else {

                /* Evaluate the result for unsigned operands */
                unsigned long Val1 = Expr->IVal;
                unsigned long Val2 = Rhs.IVal;
                switch (Tok) {
                    case TOK_EQ: Expr->IVal = (Val1 == Val2);   break;
                    case TOK_NE: Expr->IVal = (Val1 != Val2);   break;
                    case TOK_LT: Expr->IVal = (Val1 < Val2);    break;
                    case TOK_LE: Expr->IVal = (Val1 <= Val2);   break;
                    case TOK_GE: Expr->IVal = (Val1 >= Val2);   break;
                    case TOK_GT: Expr->IVal = (Val1 > Val2);    break;
                    default:     Internal ("PPhie_compare: got token 0x%X\n", Tok);
                }
            }
        }
    }

    /* The result is signed */
    Expr->Flags &= ~PPEXPR_UNSIGNED;
}



static void PPhie9 (PPExpr* Expr)
/* Handle "*", "/" and "%" operators */
{
    static const token_t PPhie9_ops[] = {
        TOK_STAR,
        TOK_DIV,
        TOK_MOD,
        TOK_INVALID
    };

    PPhie_internal (PPhie9_ops, Expr, PPhie10);
}



static void PPhie8 (PPExpr* Expr)
/* Handle "+" and "-" binary operators */
{
    static const token_t PPhie8_ops[] = {
        TOK_PLUS,
        TOK_MINUS,
        TOK_INVALID
    };

    PPhie_internal (PPhie8_ops, Expr, PPhie9);
}



static void PPhie7 (PPExpr* Expr)
/* Handle the "<<" and ">>" shift operators */
{
    /* Evaluate the lhs */
    PPhie8 (Expr);

    while (CurTok.Tok == TOK_SHL || CurTok.Tok == TOK_SHR) {

        token_t Op;             /* The operator token */
        PPExpr Rhs;
        PPExprInit (&Rhs);

        /* Remember the operator, then skip its token */
        Op = CurTok.Tok;
        NextToken ();

        /* Get the right hand side */
        PPhie8 (&Rhs);

        /* Evaluate */
        if (PPEvaluationEnabled && !PPEvaluationFailed) {
            /* For now we use 32-bit integer types for PP integer constants */
            if ((Rhs.Flags & PPEXPR_UNSIGNED) != 0) {
                if ((unsigned long)Rhs.IVal > LONG_BITS) {
                    Rhs.IVal = (long)LONG_BITS;
                }
            } else if (Rhs.IVal > (long)LONG_BITS) {
                Rhs.IVal = (long)LONG_BITS;
            } else if (Rhs.IVal < -(long)LONG_BITS) {
                Rhs.IVal = -(long)LONG_BITS;
            }

            /* Positive count for left-shift and negative for right-shift. So
            ** to shift by a count is equivalent to shift to the opposite
            ** direction by the negated count.
            */
            if (Op == TOK_SHR) {
                Rhs.IVal = -Rhs.IVal;
            }

            /* Evaluate the result */
            if ((Expr->Flags & PPEXPR_UNSIGNED) != 0) {
                if (Rhs.IVal >= (long)LONG_BITS) {
                    PPWarning ("Integer overflow in preprocessor expression");
                    Expr->IVal = 0;
                } else if (Rhs.IVal > 0) {
                    Expr->IVal <<= Rhs.IVal;
                } else if (Rhs.IVal <= -(long)LONG_BITS) {
                    Expr->IVal = 0;
                } else if (Rhs.IVal < 0) {
                    Expr->IVal = (unsigned long)Expr->IVal >> -Rhs.IVal;
                }
            } else {
                /* -1 for sign bit */
                if (Rhs.IVal >= (long)(LONG_BITS - 1)) {
                    PPWarning ("Integer overflow in preprocessor expression");
                    Expr->IVal = 0;
                } else if (Rhs.IVal > 0) {
                    Expr->IVal <<= Rhs.IVal;
                } else if (Rhs.IVal <= -(long)LONG_BITS) {
                    Expr->IVal = Expr->IVal >= 0 ? 0 : -1;
                } else if (Rhs.IVal < 0) {
                    Expr->IVal = (long)Expr->IVal >> -Rhs.IVal;
                }
            }
        }
    }
}



static void PPhie6 (PPExpr* Expr)
/* Handle greater-than type relational operators */
{
    static const token_t PPhie6_ops [] = {
        TOK_LT,
        TOK_LE,
        TOK_GE,
        TOK_GT,
        TOK_INVALID
    };

    PPhie_compare (PPhie6_ops, Expr, PPhie7);
}



static void PPhie5 (PPExpr* Expr)
/* Handle "==" and "!=" relational operators */
{
    static const token_t PPhie5_ops[] = {
        TOK_EQ,
        TOK_NE,
        TOK_INVALID
    };

    PPhie_compare (PPhie5_ops, Expr, PPhie6);
}



static void PPhie4 (PPExpr* Expr)
/* Handle the bitwise AND "&" operator */
{
    static const token_t PPhie4_ops[] = {
        TOK_AND,
        TOK_INVALID
    };

    PPhie_internal (PPhie4_ops, Expr, PPhie5);
}



static void PPhie3 (PPExpr* Expr)
/* Handle the bitwise exclusive OR "^" operator */
{
    static const token_t PPhie3_ops[] = {
        TOK_XOR,
        TOK_INVALID
    };

    PPhie_internal (PPhie3_ops, Expr, PPhie4);
}



static void PPhie2 (PPExpr* Expr)
/* Handle the bitwise OR "|" operator */
{
    static const token_t PPhie2_ops[] = {
        TOK_OR,
        TOK_INVALID
    };

    PPhie_internal (PPhie2_ops, Expr, PPhie3);
}



static void PPhieAnd (PPExpr* Expr)
/* Handle the logical AND "expr1 && expr2" operator */
{
    /* Get one operand */
    PPhie2 (Expr);

    if (CurTok.Tok == TOK_BOOL_AND) {

        int PPEvaluationEnabledPrev = PPEvaluationEnabled;
        PPExpr One;

        /* Do logical and */
        Expr->IVal = (Expr->IVal != 0);
        if (Expr->IVal == 0) {
            PPEvaluationEnabled = 0;
        }

        /* While there are more expressions */
        while (CurTok.Tok == TOK_BOOL_AND) {
            /* Skip the && */
            NextToken ();

            /* Get one operand */
            PPExprInit (&One);
            PPhie2 (&One);

            /* Evaluate */
            if (PPEvaluationEnabled) {
                if (One.IVal == 0) {
                    /* Skip evaluating remaining */
                    PPEvaluationEnabled = 0;
                    /* The value of the result will be false */
                    Expr->IVal = 0;
                }
            }
        }

        /* Restore evaluation as before */
        PPEvaluationEnabled = PPEvaluationEnabledPrev;
    }
}



static void PPhieOr (PPExpr* Expr)
/* Handle the logical OR "||" operator */
{
    /* Call the next level parser */
    PPhieAnd (Expr);

    if (CurTok.Tok == TOK_BOOL_OR) {

        int PPEvaluationEnabledPrev = PPEvaluationEnabled;
        PPExpr One;

        /* Do logical or */
        Expr->IVal = (Expr->IVal != 0);
        if (Expr->IVal != 0) {
            PPEvaluationEnabled = 0;
        }

        /* While there are more expressions */
        while (CurTok.Tok == TOK_BOOL_OR) {
            /* Skip the || */
            NextToken ();

            /* Get rhs subexpression */
            PPExprInit (&One);
            PPhieAnd (&One);

            /* Evaluate */
            if (PPEvaluationEnabled) {
                if (One.IVal != 0) {
                    /* Skip evaluating remaining */
                    PPEvaluationEnabled = 0;
                    /* The value of the result will be true */
                    Expr->IVal = 1;
                }
            }
        }

        /* Restore evaluation as before */
        PPEvaluationEnabled = PPEvaluationEnabledPrev;
    }
}



static void PPhieQuest (PPExpr* Expr)
/* Handle the ternary "expr1 ? expr2 : expr3 " operator */
{
    /* Call the lower level eval routine */
    PPhieOr (Expr);

    /* Check if it's a ternary expression */
    if (CurTok.Tok == TOK_QUEST) {
        int PPEvaluationEnabledPrev = PPEvaluationEnabled;
        PPExpr Expr2;       /* Expression 2 */
        PPExpr Expr3;       /* Expression 3 */

        /* Skip the question mark */
        NextToken ();

        /* Disable evaluation for Expr2 if the condition is false */
        if (Expr->IVal == 0) {
            PPEvaluationEnabled = 0;
        }

        /* Parse second expression */
        PPExprInit (&Expr2);
        PPhie0 (&Expr2);

        /* Skip the colon */
        ConsumeColon ();

        /* Disable evaluation for Expr3 if the condition is true */
        if (Expr->IVal != 0) {
            PPEvaluationEnabled = 0;
        }

        /* Parse third expression */
        PPExprInit (&Expr3);
        PPhieQuest (&Expr3);

        /* Set the result */
        Expr->IVal = Expr->IVal ? Expr2.IVal != 0 : Expr3.IVal != 0;

        /* Restore evaluation as before */
        PPEvaluationEnabled = PPEvaluationEnabledPrev;
    }
}



static void PPhie1 (PPExpr* Expr)
/* Handle first level of expression hierarchy */
{
    PPhieQuest (Expr);

    if (!PPEvaluationEnabled) {
        /* Skip evaluation */
        return;
    }

    switch (CurTok.Tok) {

        case TOK_ASSIGN:
            PPError ("Token \"=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_PLUS_ASSIGN:
            PPError ("Token \"+=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_MINUS_ASSIGN:
            PPError ("Token \"-=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_MUL_ASSIGN:
            PPError ("Token \"*=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_DIV_ASSIGN:
            PPError ("Token \"/=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_MOD_ASSIGN:
            PPError ("Token \"%%=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_SHL_ASSIGN:
            PPError ("Token \"<<=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_SHR_ASSIGN:
            PPError ("Token \">>=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_AND_ASSIGN:
            PPError ("Token \"&=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_OR_ASSIGN:
            PPError ("Token \"|=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        case TOK_XOR_ASSIGN:
            PPError ("Token \"^=\" is not valid in preprocessor expressions");
            PPErrorSkipLine ();
            break;

        default:
            break;
    }
}



static void PPhie0 (PPExpr* Expr)
/* Handle the comma "," operator */
{
    PPhie1 (Expr);

    while (CurTok.Tok == TOK_COMMA) {
        /* Skip the comma */
        NextToken ();
        /* Reset the expression */
        PPExprInit (Expr);
        /* Use the next operand as the value instead */
        PPhie1 (Expr);
    }
}



void ParsePPExprInLine (PPExpr* Expr)
/* Parse a line for PP expression */
{
    /* Initialize the parser status */
    PPEvaluationFailed = 0;
    PPEvaluationEnabled = 1;
    PPParserRunning = 1;

    /* Parse */
    PPExprInit (Expr);
    PPhie0 (Expr);

    /* If the evaluation fails, the result is always zero */
    if (PPEvaluationFailed) {
        Expr->IVal = 0;
        PPEvaluationFailed = 0;
    }

    /* Restore parser status */
    PPParserRunning = 0;
}
