/*****************************************************************************/
/*                                                                           */
/*                                 condasm.c                                 */
/*                                                                           */
/*                   Conditional assembly support for ca65                   */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 2000-2011, Ullrich von Bassewitz                                      */
/*                Roemerstrasse 52                                           */
/*                D-70794 Filderstadt                                        */
/* EMail:         uz@cc65.org                                                */
/*                                                                           */
/*                                                                           */
/* 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.                                                          */
/*                                                                           */
/*****************************************************************************/



/* ca65 */
#include "error.h"
#include "expr.h"
#include "instr.h"
#include "lineinfo.h"
#include "nexttok.h"
#include "symbol.h"
#include "symtab.h"
#include "condasm.h"



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



/* Maximum count of nested .ifs */
#define MAX_IFS         256

/* Set of bitmapped flags for the if descriptor */
enum {
    ifNone      = 0x0000,               /* No flag */
    ifCond      = 0x0001,               /* IF condition was true */
    ifParentCond= 0x0002,               /* IF condition of parent */
    ifElse      = 0x0004,               /* We had a .ELSE branch */
    ifNeedTerm  = 0x0008,               /* Need .ENDIF termination */
};

/* The overall .IF condition */
int IfCond      = 1;



/*****************************************************************************/
/*                               struct IfDesc                               */
/*****************************************************************************/



/* One .IF descriptor */
typedef struct IfDesc IfDesc;
struct IfDesc {
    unsigned    Flags;          /* Bitmapped flags, see above */
    Collection  LineInfos;      /* File position of the .IF */
    const char* Name;           /* Name of the directive */
};

/* The .IF stack */
static IfDesc IfStack [MAX_IFS];
static unsigned IfCount = 0;



static IfDesc* GetCurrentIf (void)
/* Return the current .IF descriptor */
{
    if (IfCount == 0) {
        return 0;
    } else {
        return &IfStack[IfCount-1];
    }
}



static int GetOverallIfCond (void)
/* Get the overall condition based on all conditions on the stack. */
{
    /* Since the last entry contains the overall condition of the parent, we
    ** must check it in combination of the current condition. If there is no
    ** last entry, the overall condition is true.
    */
    return (IfCount == 0) ||
           ((IfStack[IfCount-1].Flags & (ifCond | ifParentCond)) == (ifCond | ifParentCond));
}



static void CalcOverallIfCond (void)
/* Calculate the overall condition, based on all conditions on the stack. */
{
    IfCond = GetOverallIfCond ();
}



static void SetIfCond (IfDesc* ID, int C)
/* Set the .IF condition */
{
    if (C) {
        ID->Flags |= ifCond;
    } else {
        ID->Flags &= ~ifCond;
    }
}



static int ElseClause (IfDesc* ID, const char* Directive)
/* Enter an .ELSE clause. Return true if this was ok, zero on errors. */
{
    /* Check if we have an open .IF - otherwise .ELSE is not allowed */
    if (ID == 0) {
        Error ("Unexpected %s", Directive);
        return 0;
    }

    /* Check for a duplicate else, then remember that we had one */
    if (ID->Flags & ifElse) {
        /* We already had a .ELSE ! */
        Error ("Duplicate .ELSE");
        return 0;
    }
    ID->Flags |= ifElse;

    /* Condition is inverted now */
    ID->Flags ^= ifCond;
    return 1;
}



static IfDesc* AllocIf (const char* Directive, int NeedTerm)
/* Alloc a new element from the .IF stack */
{
    IfDesc* ID;

    /* Check for stack overflow */
    if (IfCount >= MAX_IFS) {
        Fatal ("Too many nested .IFs");
    }

    /* Get the next element */
    ID = &IfStack[IfCount];

    /* Initialize elements */
    ID->Flags = NeedTerm? ifNeedTerm : ifNone;
    if (GetOverallIfCond ()) {
        /* The parents .IF condition is true */
        ID->Flags |= ifParentCond;
    }
    ID->LineInfos = EmptyCollection;
    GetFullLineInfo (&ID->LineInfos);
    ID->Name = Directive;

    /* One more slot allocated */
    ++IfCount;

    /* Return the result */
    return ID;
}



static void FreeIf (void)
/* Free all .IF descriptors until we reach one with the NeedTerm bit set */
{
    int Done;
    do {
        IfDesc* ID = GetCurrentIf();
        if (ID == 0) {
            Error (" Unexpected .ENDIF");
            Done = 1;
        } else {
            Done = (ID->Flags & ifNeedTerm) != 0;
            ReleaseFullLineInfo (&ID->LineInfos);
            DoneCollection (&ID->LineInfos);
            --IfCount;
        }
    } while (!Done);
}



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



void DoConditionals (void)
/* Catch all for conditional directives */
{
    IfDesc* D;

    do {

        switch (CurTok.Tok) {

            case TOK_ELSE:
                D = GetCurrentIf ();

                /* Allow an .ELSE */
                if (ElseClause (D, ".ELSE")) {
                    /* Remember the data for the .ELSE */
                    if (D) {
                        ReleaseFullLineInfo (&D->LineInfos);
                        GetFullLineInfo (&D->LineInfos);
                        D->Name = ".ELSE";
                    }

                    /* Calculate the new overall condition */
                    CalcOverallIfCond ();

                    /* Skip .ELSE */
                    NextTok ();
                    ExpectSep ();
                } else {
                    /* Problem with .ELSE, ignore remainder of line */
                    SkipUntilSep ();
                }
                break;

            case TOK_ELSEIF:
                D = GetCurrentIf ();
                /* Handle as if there was an .ELSE first */
                if (ElseClause (D, ".ELSEIF")) {
                    /* Calculate the new overall if condition */
                    CalcOverallIfCond ();

                    /* Allocate and prepare a new descriptor */
                    D = AllocIf (".ELSEIF", 0);
                    NextTok ();

                    /* Ignore the new condition if we are inside a false .ELSE
                    ** branch. This way we won't get any errors about undefined
                    ** symbols or similar...
                    */
                    if (IfCond) {
                        SetIfCond (D, ConstExpression ());
                        ExpectSep ();
                    }

                    /* Get the new overall condition */
                    CalcOverallIfCond ();
                } else {
                    /* Problem with .ELSEIF, ignore remainder of line */
                    SkipUntilSep ();
                }
                break;

            case TOK_ENDIF:
                /* We're done with this .IF.. - remove the descriptor(s) */
                FreeIf ();

                /* Be sure not to read the next token until the .IF stack
                ** has been cleanup up, since we may be at end of file.
                */
                NextTok ();
                ExpectSep ();

                /* Get the new overall condition */
                CalcOverallIfCond ();
                break;

            case TOK_IF:
                D = AllocIf (".IF", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, ConstExpression ());
                    ExpectSep ();
                }
                CalcOverallIfCond ();
                break;

            case TOK_IFBLANK:
                D = AllocIf (".IFBLANK", 1);
                NextTok ();
                if (IfCond) {
                    if (TokIsSep (CurTok.Tok)) {
                        SetIfCond (D, 1);
                    } else {
                        SetIfCond (D, 0);
                        SkipUntilSep ();
                    }
                }
                CalcOverallIfCond ();
                break;

            case TOK_IFCONST:
                D = AllocIf (".IFCONST", 1);
                NextTok ();
                if (IfCond) {
                    ExprNode* Expr = Expression();
                    SetIfCond (D, IsConstExpr (Expr, 0));
                    FreeExpr (Expr);
                    ExpectSep ();
                }
                CalcOverallIfCond ();
                break;

            case TOK_IFDEF:
                D = AllocIf (".IFDEF", 1);
                NextTok ();
                if (IfCond) {
                    SymEntry* Sym = ParseAnySymName (SYM_FIND_EXISTING);
                    SetIfCond (D, Sym != 0 && SymIsDef (Sym));
                }
                CalcOverallIfCond ();
                break;

            case TOK_IFNBLANK:
                D = AllocIf (".IFNBLANK", 1);
                NextTok ();
                if (IfCond) {
                    if (TokIsSep (CurTok.Tok)) {
                        SetIfCond (D, 0);
                    } else {
                        SetIfCond (D, 1);
                        SkipUntilSep ();
                    }
                }
                CalcOverallIfCond ();
                break;

            case TOK_IFNCONST:
                D = AllocIf (".IFNCONST", 1);
                NextTok ();
                if (IfCond) {
                    ExprNode* Expr = Expression();
                    SetIfCond (D, !IsConstExpr (Expr, 0));
                    FreeExpr (Expr);
                    ExpectSep ();
                }
                CalcOverallIfCond ();
                break;

            case TOK_IFNDEF:
                D = AllocIf (".IFNDEF", 1);
                NextTok ();
                if (IfCond) {
                    SymEntry* Sym = ParseAnySymName (SYM_FIND_EXISTING);
                    SetIfCond (D, Sym == 0 || !SymIsDef (Sym));
                    ExpectSep ();
                }
                CalcOverallIfCond ();
                break;

            case TOK_IFNREF:
                D = AllocIf (".IFNREF", 1);
                NextTok ();
                if (IfCond) {
                    SymEntry* Sym = ParseAnySymName (SYM_FIND_EXISTING);
                    SetIfCond (D, Sym == 0 || !SymIsRef (Sym));
                    ExpectSep ();
                }
                CalcOverallIfCond ();
                break;

            case TOK_IFP02:
                D = AllocIf (".IFP02", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_6502);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFP02X:
                D = AllocIf (".IFP02X", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_6502X);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFP4510:
                D = AllocIf (".IFP4510", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_4510);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFP45GS02:
                D = AllocIf (".IFP45GS02", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_45GS02);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFP6280:
                D = AllocIf (".IFP6280", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_HUC6280);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFP816:
                D = AllocIf (".IFP816", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_65816);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFPC02:
                D = AllocIf (".IFPC02", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_65C02);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFPCE02:
                D = AllocIf (".IFPCE02", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_65CE02);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFPDTV:
                D = AllocIf (".IFPDTV", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_6502DTV);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFPM740:
                D = AllocIf (".IFPM740", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_M740);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFPSC02:
                D = AllocIf (".IFPSC02", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_65SC02);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFPSWEET16:
                D = AllocIf (".IFPSWEET16", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_SWEET16);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFPWC02:
                D = AllocIf (".IFPWC02", 1);
                NextTok ();
                if (IfCond) {
                    SetIfCond (D, GetCPU() == CPU_W65C02);
                }
                ExpectSep ();
                CalcOverallIfCond ();
                break;

            case TOK_IFREF:
                D = AllocIf (".IFREF", 1);
                NextTok ();
                if (IfCond) {
                    SymEntry* Sym = ParseAnySymName (SYM_FIND_EXISTING);
                    SetIfCond (D, Sym != 0 && SymIsRef (Sym));
                    ExpectSep ();
                }
                CalcOverallIfCond ();
                break;

            default:
                /* Skip tokens */
                NextTok ();

        }

    } while (IfCond == 0 && CurTok.Tok != TOK_EOF);
}



int CheckConditionals (void)
/* Check if the current token is one that starts a conditional directive, and
** call DoConditionals if so. Return true if a conditional directive was found,
** return false otherwise.
*/
{
    switch (CurTok.Tok) {
        case TOK_ELSE:
        case TOK_ELSEIF:
        case TOK_ENDIF:
        case TOK_IF:
        case TOK_IFBLANK:
        case TOK_IFCONST:
        case TOK_IFDEF:
        case TOK_IFNBLANK:
        case TOK_IFNCONST:
        case TOK_IFNDEF:
        case TOK_IFNREF:
        case TOK_IFP02:
        case TOK_IFP02X:
        case TOK_IFP4510:
        case TOK_IFP45GS02:
        case TOK_IFP6280:
        case TOK_IFP816:
        case TOK_IFPC02:
        case TOK_IFPCE02:
        case TOK_IFPDTV:
        case TOK_IFPM740:
        case TOK_IFPSC02:
        case TOK_IFPSWEET16:
        case TOK_IFPWC02:
        case TOK_IFREF:
            DoConditionals ();
            return 1;

        default:
            return 0;
    }
}



void CheckOpenIfs (void)
/* Called from the scanner before closing an input file. Will check for any
** open .ifs in this file.
*/
{
    const LineInfo* LI;

    while (1) {
        /* Get the current file number and check if the topmost entry on the
        ** .IF stack was inserted with this file number
        */
        IfDesc* D = GetCurrentIf ();
        if (D == 0) {
            /* There are no open .IFs */
            break;
        }

        LI = CollConstAt (&D->LineInfos, 0);
        if (GetSourcePos (LI)->Name != CurTok.Pos.Name) {
            /* The .if is from another file, bail out */
            break;
        }

        /* Start of .if is in the file we're about to leave */
        LIError (&D->LineInfos, "Conditional assembly branch was never closed");
        FreeIf ();
    }

    /* Calculate the new overall .IF condition */
    CalcOverallIfCond ();
}



unsigned GetIfStack (void)
/* Get the current .IF stack pointer */
{
    return IfCount;
}



void CleanupIfStack (unsigned SP)
/* Cleanup the .IF stack, remove anything above the given stack pointer */
{
    while (IfCount > SP) {
        FreeIf ();
    }

    /* Calculate the new overall .IF condition */
    CalcOverallIfCond ();
}
