/*****************************************************************************/
/*                                                                           */
/*                                 struct.c                                  */
/*                                                                           */
/*                          .STRUCT/.UNION commands                          */
/*                                                                           */
/*                                                                           */
/* (C) 2003-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.                                                          */
/*                                                                           */
/*****************************************************************************/



/* common */
#include "addrsize.h"
#include "scopedefs.h"

/* ca65 */
#include "condasm.h"
#include "error.h"
#include "expr.h"
#include "macro.h"
#include "nexttok.h"
#include "scanner.h"
#include "sizeof.h"
#include "symbol.h"
#include "symtab.h"
#include "struct.h"



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



enum {
    STRUCT,
    UNION
};



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



static long Member (long AllocSize)
/* Read one struct member and return its size */
{
    long Multiplier;

    /* A multiplier may follow */
    if (CurTok.Tok != TOK_SEP) {
        Multiplier = ConstExpression ();
        if (Multiplier <= 0) {
            ErrorSkip ("Range error");
            Multiplier = 1;
        }
        AllocSize *= Multiplier;
    }

    /* Check the size for a reasonable value */
    if (AllocSize >= 0x1000000) {
        ErrorSkip ("Range error");
        AllocSize = 1;
    }

    /* Return the size */
    return AllocSize;
}



static long DoStructInternal (long Offs, unsigned Type)
/* Handle the .STRUCT command */
{
    long Size = 0;

    /* Outside of other structs, we need a name. Inside another struct or
    ** union, the struct may be anonymous; in which case, no new lexical level
    ** is started.
    */
    int Anon = (CurTok.Tok != TOK_IDENT);

    if (!Anon) {
        /* Enter a new scope, then skip the name */
        SymEnterLevel (&CurTok.SVal, SCOPE_STRUCT, ADDR_SIZE_ABS, 0);
        NextTok ();
        /* Start at zero offset in the new scope */
        Offs = 0;
    }

    /* Test for end of line */
    ConsumeSep ();

    /* Read until end of struct */
    while (CurTok.Tok != TOK_ENDSTRUCT &&
           CurTok.Tok != TOK_ENDUNION  &&
           CurTok.Tok != TOK_EOF) {
        long      MemberSize;
        SymTable* Struct;
        SymEntry* Sym;

        /* Allow empty and comment lines */
        if (CurTok.Tok == TOK_SEP) {
            NextTok ();
            continue;
        }

        /* The format is "[identifier ].storage-allocator[ multiplier]" */
        Sym = 0;
        if (CurTok.Tok == TOK_IDENT) {
            /* Beware: An identifier may be a macro also;
            ** in which case, we must start over.
            */
            Macro* M = FindMacro (&CurTok.SVal);

            if (M) {
                MacExpandStart (M);
                continue;
            }

            /* We have an identifier, generate a symbol */
            Sym = SymFind (CurrentScope, &CurTok.SVal, SYM_ALLOC_NEW);

            /* Assign the symbol the offset of the current member */
            SymDef (Sym, GenLiteralExpr (Offs), ADDR_SIZE_DEFAULT, SF_NONE);

            /* Skip the member name */
            NextTok ();
        }

        /* Read the storage allocator */
        MemberSize = 0;                 /* In case of errors or .ORG, use zero */
        switch (CurTok.Tok) {
            case TOK_BYTE:
                NextTok ();
                MemberSize = Member (1);
                break;

            case TOK_DBYT:
            case TOK_WORD:
            case TOK_ADDR:
                NextTok ();
                MemberSize = Member (2);
                break;

            case TOK_FARADDR:
                NextTok ();
                MemberSize = Member (3);
                break;

            case TOK_DWORD:
                NextTok ();
                MemberSize = Member (4);
                break;

            case TOK_RES:
                NextTok ();
                if (CurTok.Tok == TOK_SEP) {
                    ErrorSkip ("Size is missing");
                } else {
                    MemberSize = Member (1);
                }
                break;

            case TOK_ORG:
                NextTok ();
                if (CurTok.Tok == TOK_SEP) {
                    ErrorSkip ("Address is missing");
                } else {
                    Offs = ConstExpression ();

                    /* Check the address for a reasonable value */
                    if (Offs >= 0x1000000) {
                        ErrorSkip ("Range error");
                        Offs = 0;
                    }
                }
                break;

            case TOK_TAG:
                NextTok ();
                Struct = ParseScopedSymTable ();
                if (Struct == 0) {
                    ErrorSkip ("Unknown struct/union");
                } else if (GetSymTabType (Struct) != SCOPE_STRUCT) {
                    ErrorSkip ("Not a struct/union");
                } else {
                    SymEntry* SizeSym = GetSizeOfScope (Struct);
                    if (!SymIsDef (SizeSym) || !SymIsConst (SizeSym, &MemberSize)) {
                        ErrorSkip ("Size of struct/union is unknown");
                    }
                }
                MemberSize = Member (MemberSize);
                break;

            case TOK_STRUCT:
                NextTok ();
                MemberSize = DoStructInternal (Offs, STRUCT);
                break;

            case TOK_UNION:
                NextTok ();
                MemberSize = DoStructInternal (Offs, UNION);
                break;

            default:
                if (!CheckConditionals ()) {
                    /* Not a conditional directive */
                    ErrorSkip ("Invalid storage allocator in struct/union");
                }
        }

        /* Assign the size to the member if it has a name */
        if (Sym) {
            DefSizeOfSymbol (Sym, MemberSize);
        }

        /* Next member */
        if (Type == STRUCT) {
            /* Struct */
            Offs += MemberSize;
            Size += MemberSize;
        } else {
            /* Union */
            if (MemberSize > Size) {
                Size = MemberSize;
            }
        }

        /* Expect end of line */
        ConsumeSep ();
    }

    /* If this is not an anon. struct, enter a special symbol named ".size"
    ** into the symbol table, of the struct, that holds the size of the
    ** struct. Since the symbol starts with a dot, it cannot be accessed
    ** by user code.
    ** Leave the struct scope level.
    */
    if (!Anon) {
        /* Add a symbol */
        SymEntry* SizeSym = GetSizeOfScope (CurrentScope);
        SymDef (SizeSym, GenLiteralExpr (Size), ADDR_SIZE_DEFAULT, SF_NONE);

        /* Close the struct scope */
        SymLeaveLevel ();
    }

    /* End of struct/union definition */
    if (Type == STRUCT) {
        Consume (TOK_ENDSTRUCT, "'.ENDSTRUCT' expected");
    } else {
        Consume (TOK_ENDUNION, "'.ENDUNION' expected");
    }

    /* Return the size of the struct */
    return Size;
}



long GetStructSize (SymTable* Struct)
/* Get the size of a struct or union */
{
    SymEntry* SizeSym = FindSizeOfScope (Struct);
    if (SizeSym == 0) {
        Error ("Size of struct/union is unknown");
        return 0;
    } else {
        return GetSymVal (SizeSym);
    }
}



void DoStruct (void)
/* Handle the .STRUCT command */
{
    DoStructInternal (0, STRUCT);
}



void DoUnion (void)
/* Handle the .UNION command */
{
    DoStructInternal (0, UNION);
}
