/*****************************************************************************/
/*                                                                           */
/*                                 pragma.c                                  */
/*                                                                           */
/*                  Pragma handling for the cc65 C compiler                  */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 1998-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.                                                          */
/*                                                                           */
/*****************************************************************************/



#include <stdlib.h>
#include <string.h>

/* common */
#include "addrsize.h"
#include "chartype.h"
#include "segnames.h"
#include "tgttrans.h"
#include "xmalloc.h"

/* cc65 */
#include "codegen.h"
#include "error.h"
#include "global.h"
#include "litpool.h"
#include "scanner.h"
#include "scanstrbuf.h"
#include "symtab.h"
#include "pragma.h"
#include "wrappedcall.h"



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



/* Tokens for the #pragmas */
typedef enum {
    PRAGMA_ILLEGAL = -1,
    PRAGMA_ALIGN,
    PRAGMA_ALLOW_EAGER_INLINE,
    PRAGMA_BSS_NAME,
    PRAGMA_CHARMAP,
    PRAGMA_CHECK_STACK,
    PRAGMA_CODE_NAME,
    PRAGMA_CODESIZE,
    PRAGMA_DATA_NAME,
    PRAGMA_INLINE_STDFUNCS,
    PRAGMA_LOCAL_STRINGS,
    PRAGMA_MESSAGE,
    PRAGMA_OPTIMIZE,
    PRAGMA_REGISTER_VARS,
    PRAGMA_REGVARADDR,
    PRAGMA_RODATA_NAME,
    PRAGMA_SIGNED_CHARS,
    PRAGMA_STATIC_LOCALS,
    PRAGMA_WARN,
    PRAGMA_WRAPPED_CALL,
    PRAGMA_WRITABLE_STRINGS,
    PRAGMA_ZPSYM,
} pragma_t;

/* Pragma table */
/* CAUTION: table must be sorted for bsearch */
static const struct Pragma {
    const char* Key;            /* Keyword */
    pragma_t    Tok;            /* Token */
} Pragmas[] = {
/* BEGIN SORTED.SH */
    { "align",                  PRAGMA_ALIGN              },
    { "allow-eager-inline",     PRAGMA_ALLOW_EAGER_INLINE },
    { "allow_eager_inline",     PRAGMA_ALLOW_EAGER_INLINE },
    { "bss-name",               PRAGMA_BSS_NAME           },
    { "bss_name",               PRAGMA_BSS_NAME           },
    { "charmap",                PRAGMA_CHARMAP            },
    { "check-stack",            PRAGMA_CHECK_STACK        },
    { "check_stack",            PRAGMA_CHECK_STACK        },
    { "code-name",              PRAGMA_CODE_NAME          },
    { "code_name",              PRAGMA_CODE_NAME          },
    { "codesize",               PRAGMA_CODESIZE           },
    { "data-name",              PRAGMA_DATA_NAME          },
    { "data_name",              PRAGMA_DATA_NAME          },
    { "inline-stdfuncs",        PRAGMA_INLINE_STDFUNCS    },
    { "inline_stdfuncs",        PRAGMA_INLINE_STDFUNCS    },
    { "local-strings",          PRAGMA_LOCAL_STRINGS      },
    { "local_strings",          PRAGMA_LOCAL_STRINGS      },
    { "message",                PRAGMA_MESSAGE            },
    { "optimize",               PRAGMA_OPTIMIZE           },
    { "register-vars",          PRAGMA_REGISTER_VARS      },
    { "register_vars",          PRAGMA_REGISTER_VARS      },
    { "regvaraddr",             PRAGMA_REGVARADDR         },
    { "rodata-name",            PRAGMA_RODATA_NAME        },
    { "rodata_name",            PRAGMA_RODATA_NAME        },
    { "signed-chars",           PRAGMA_SIGNED_CHARS       },
    { "signed_chars",           PRAGMA_SIGNED_CHARS       },
    { "static-locals",          PRAGMA_STATIC_LOCALS      },
    { "static_locals",          PRAGMA_STATIC_LOCALS      },
    { "warn",                   PRAGMA_WARN               },
    { "wrapped-call",           PRAGMA_WRAPPED_CALL       },
    { "wrapped_call",           PRAGMA_WRAPPED_CALL       },
    { "writable-strings",       PRAGMA_WRITABLE_STRINGS   },
    { "writable_strings",       PRAGMA_WRITABLE_STRINGS   },
    { "zpsym",                  PRAGMA_ZPSYM              },
/* END SORTED.SH */
};
#define PRAGMA_COUNT    (sizeof (Pragmas) / sizeof (Pragmas[0]))

/* Result of ParsePushPop */
typedef enum {
    PP_NONE,
    PP_POP,
    PP_PUSH,
    PP_ERROR,
} PushPopResult;

/* Effective scope of the pragma.
** This talks about how far the pragma has effects on whenever it shows up,
** even in the middle of an expression, statement or something.
*/
typedef enum {
    PES_NONE,
    PES_IMM,                    /* No way back */
    PES_EXPR,                   /* Current expression/declarator */
    PES_STMT,                   /* Current statement/declaration */
    PES_SCOPE,                  /* Current scope */
    PES_FUNC,                   /* Current function */
    PES_FILE,                   /* Current file */
    PES_ALL,                    /* All */
} pragma_scope_t;



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



static void PragmaErrorSkip (void)
/* Called in case of an error, skips tokens until the closing paren or a
** semicolon is reached.
*/
{
    static const token_t TokenList[] = { TOK_RPAREN, TOK_SEMI };
    SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
}



static int CmpKey (const void* Key, const void* Elem)
/* Compare function for bsearch */
{
    return strcmp ((const char*) Key, ((const struct Pragma*) Elem)->Key);
}



static pragma_t FindPragma (const StrBuf* Key)
/* Find a pragma and return the token. Return PRAGMA_ILLEGAL if the keyword is
** not a valid pragma.
*/
{
    struct Pragma* P;
    P = bsearch (SB_GetConstBuf (Key), Pragmas, PRAGMA_COUNT, sizeof (Pragmas[0]), CmpKey);
    return P? P->Tok : PRAGMA_ILLEGAL;
}



static int GetComma (StrBuf* B)
/* Expects and skips a comma in B. Prints an error and returns zero if no
** comma is found. Return a value <> 0 otherwise.
*/
{
    SB_SkipWhite (B);
    if (SB_Get (B) != ',') {
        Error ("Comma expected");
        return 0;
    }
    SB_SkipWhite (B);
    return 1;
}



static int GetString (StrBuf* B, StrBuf* S)
/* Expects and skips a string in B. Prints an error and returns zero if no
** string is found. Returns a value <> 0 otherwise.
*/
{
    if (!SB_GetString (B, S)) {
        Error ("String literal expected");
        return 0;
    }
    return 1;
}



static int GetNumber (StrBuf* B, long* Val)
/* Expects and skips a number in B. Prints an eror and returns zero if no
** number is found. Returns a value <> 0 otherwise.
*/
{
    if (!SB_GetNumber (B, Val)) {
        Error ("Constant integer expected");
        return 0;
    }
    return 1;
}



static IntStack* GetWarning (StrBuf* B)
/* Get a warning name from the string buffer. Returns a pointer to the intstack
** that holds the state of the warning, and NULL in case of errors. The
** function will output error messages in case of problems.
*/
{
    IntStack* S = 0;
    StrBuf W = AUTO_STRBUF_INITIALIZER;

    /* The warning name is a symbol but the '-' char is allowed within */
    if (SB_GetSym (B, &W, "-")) {

        /* Map the warning name to an IntStack that contains its state */
        S = FindWarning (SB_GetConstBuf (&W));

        /* Handle errors */
        if (S == 0) {
            Error ("Pragma expects a warning name as first argument");
        }
    }

    /* Deallocate the string */
    SB_Done (&W);

    /* Done */
    return S;
}



static int HasStr (StrBuf* B, const char* E)
/* Checks if E follows in B. If so, skips it and returns true */
{
    unsigned Len = strlen (E);
    if (SB_GetLen (B) - SB_GetIndex (B) >= Len) {
        if (strncmp (SB_GetConstBuf (B) + SB_GetIndex (B), E, Len) == 0) {
            /* Found */
            SB_SkipMultiple (B, Len);
            return 1;
        }
    }
    return 0;
}



static PushPopResult ParsePushPop (StrBuf* B)
/* Check for and parse the "push" and "pop" keywords. In case of "push", a
** following comma is expected and skipped.
*/
{
    StrBuf Ident      = AUTO_STRBUF_INITIALIZER;
    PushPopResult Res = PP_NONE;

    /* Remember the current string index, so we can go back in case of errors */
    unsigned Index = SB_GetIndex (B);

    /* Try to read an identifier */
    if (SB_GetSym (B, &Ident, 0)) {

        /* Check if we have a first argument named "pop" */
        if (SB_CompareStr (&Ident, "pop") == 0) {

            Res = PP_POP;

        /* Check if we have a first argument named "push" */
        } else if (SB_CompareStr (&Ident, "push") == 0) {

            Res = PP_PUSH;

            /* Skip the following comma */
            if (!GetComma (B)) {
                /* Error already flagged by GetComma */
                Res = PP_ERROR;
            }

        } else {

            /* Unknown keyword, roll back */
            SB_SetIndex (B, Index);
        }
    }

    /* Free the string buffer and return the result */
    SB_Done (&Ident);
    return Res;
}



static void PopInt (IntStack* S)
/* Pops an integer from an IntStack. Prints an error if the stack is empty */
{
    if (IS_GetCount (S) < 2) {
        Error ("Cannot pop, stack is empty");
    } else {
        IS_Drop (S);
    }
}



static void PushInt (IntStack* S, long Val)
/* Pushes an integer onto an IntStack. Prints an error if the stack is full */
{
    if (IS_IsFull (S)) {
        Error ("Cannot push: stack overflow");
    } else {
        IS_Push (S, Val);
    }
}



static int IsBoolKeyword (StrBuf* Ident)
/* Check if the identifier in Ident is a keyword for a boolean value. Currently
** accepted are true/false/on/off.
*/
{
    if (SB_CompareStr (Ident, "true") == 0) {
        return 1;
    }
    if (SB_CompareStr (Ident, "on") == 0) {
        return 1;
    }
    if (SB_CompareStr (Ident, "false") == 0) {
        return 0;
    }
    if (SB_CompareStr (Ident, "off") == 0) {
        return 0;
    }

    /* Error */
    Error ("Pragma argument must be one of 'on', 'off', 'true' or 'false'");
    return 0;
}



static void ApplyPragma (int PushPop, IntStack* Stack, long Val)
/* Apply a pragma immediately */
{
    if (PushPop > 0) {
        /* Push the new value */
        PushInt (Stack, Val);
    } else if (PushPop < 0) {
        /* Pop the old value */
        PopInt (Stack);
    } else {
        /* Set the new value */
        IS_Set (Stack, Val);
    }
}



static void ApplySegNamePragma (pragma_t Token, int PushPop, const char* Name, unsigned char AddrSize)
/* Process a segname pragma */
{
    segment_t Seg = SEG_CODE;

    switch (Token) {
        case PRAGMA_CODE_NAME:
            Seg = SEG_CODE;
            break;

        case PRAGMA_RODATA_NAME:
            Seg = SEG_RODATA;
            break;

        case PRAGMA_DATA_NAME:
            Seg = SEG_DATA;
            break;

        case PRAGMA_BSS_NAME:
            Seg = SEG_BSS;
            break;

        default:
            Internal ("Unknown segment name pragma: %02X", Token);
            break;
    }

    /* Set the new name */
    if (PushPop > 0) {
        PushSegName (Seg, Name);
    } else if (PushPop < 0) {
        PopSegName (Seg);
    } else {
        SetSegName (Seg, Name);
    }

    /* Set the optional address size for the segment if valid */
    if (PushPop >= 0 && AddrSize != ADDR_SIZE_INVALID) {
        SetSegAddrSize (Name, AddrSize);
    }

    g_segname (Seg);
}



/*****************************************************************************/
/*                         Pragma handling functions                         */
/*****************************************************************************/



static void StringPragma (pragma_scope_t Scope, StrBuf* B, void (*Func) (const char*))
/* Handle a pragma that expects a string parameter */
{
    StrBuf S = AUTO_STRBUF_INITIALIZER;

    /* Only PES_IMM is supported */
    CHECK (Scope == PES_IMM);

    /* We expect a string here */
    if (GetString (B, &S)) {
        /* Call the given function with the string argument */
        Func (SB_GetConstBuf (&S));
    }

    /* Call the string buf destructor */
    SB_Done (&S);
}



static void SegNamePragma (pragma_scope_t Scope, pragma_t Token, StrBuf* B)
/* Handle a pragma that expects a segment name parameter */
{
    const char* Name;
    unsigned char AddrSize = ADDR_SIZE_INVALID;
    StrBuf S = AUTO_STRBUF_INITIALIZER;
    StrBuf A = AUTO_STRBUF_INITIALIZER;
    int PushPop = 0;

    /* Unused at the moment */
    (void)Scope;

    /* Check for the "push" or "pop" keywords */
    switch (ParsePushPop (B)) {

        case PP_NONE:
            break;

        case PP_PUSH:
            PushPop = 1;
            break;

        case PP_POP:
            /* Pop the old value and output it */
            ApplySegNamePragma (Token, -1, 0, 0);

            /* Done */
            goto ExitPoint;

        case PP_ERROR:
            /* Bail out */
            goto ExitPoint;

        default:
            Internal ("Invalid result from ParsePushPop");

    }

    /* A string argument must follow */
    if (!GetString (B, &S)) {
        goto ExitPoint;
    }

    /* Get the name string of the segment */
    Name = SB_GetConstBuf (&S);

    /* Check if the name is valid */
    if (ValidSegName (Name)) {

        /* Skip the following comma */
        SB_SkipWhite (B);
        if (SB_Peek (B) == ',') {
            SB_Skip (B);
            SB_SkipWhite (B);

            /* A string argument must follow */
            if (!GetString (B, &A)) {
                goto ExitPoint;
            }

            /* Get the address size for the segment */
            AddrSize = AddrSizeFromStr (SB_GetConstBuf (&A));

            /* Check the address size for the segment */
            if (AddrSize == ADDR_SIZE_INVALID) {
                Warning ("Invalid address size for segment");
            }
        }

        /* Set the new name and optionally address size */
        ApplySegNamePragma (Token, PushPop, Name, AddrSize);

    } else {

        /* Segment name is invalid */
        Error ("Illegal segment name: '%s'", Name);

    }

ExitPoint:

    /* Call the string buf destructor */
    SB_Done (&S);
    SB_Done (&A);
}



static void WrappedCallPragma (pragma_scope_t Scope, StrBuf* B)
/* Handle the wrapped-call pragma */
{
    StrBuf      S = AUTO_STRBUF_INITIALIZER;
    const char *Name;
    long Val;
    SymEntry *Entry;

    /* Only PES_IMM is supported */
    CHECK (Scope == PES_IMM);

    /* Check for the "push" or "pop" keywords */
    switch (ParsePushPop (B)) {

        case PP_NONE:
            Error ("Push or pop required");
            break;

        case PP_PUSH:
            break;

        case PP_POP:
            PopWrappedCall();

            /* Done */
            goto ExitPoint;

        case PP_ERROR:
            /* Bail out */
            goto ExitPoint;

        default:
            Internal ("Invalid result from ParsePushPop");

    }

    /* A symbol argument must follow */
    if (!SB_GetSym (B, &S, NULL)) {
        goto ExitPoint;
    }

    /* Skip the following comma */
    if (!GetComma (B)) {
        /* Error already flagged by GetComma */
        Error ("Value or the word 'bank' required for wrapped-call identifier");
        goto ExitPoint;
    }

    /* Next must be either a numeric value, or "bank" */
    if (HasStr (B, "bank")) {
        Val = WRAPPED_CALL_USE_BANK;
    } else if (!GetNumber (B, &Val)) {
        Error ("Value required for wrapped-call identifier");
        goto ExitPoint;
    }

    if (!(Val == WRAPPED_CALL_USE_BANK) && (Val < 0 || Val > 255)) {
        Error ("Identifier must be between 0-255");
        goto ExitPoint;
    }

    /* Get the string */
    Name = SB_GetConstBuf (&S);
    Entry = FindSym(Name);

    /* Check if the name is valid */
    if (Entry && (Entry->Flags & SC_TYPEMASK) == SC_FUNC) {

        PushWrappedCall(Entry, (unsigned int) Val);
        Entry->Flags |= SC_REF;
        GetFuncDesc (Entry->Type)->Flags |= FD_CALL_WRAPPER;

    } else {

        /* Segment name is invalid */
        Error ("Wrapped-call target does not exist or is not a function");

    }

ExitPoint:
    /* Call the string buf destructor */
    SB_Done (&S);
}



static void CharMapPragma (pragma_scope_t Scope, StrBuf* B)
/* Change the character map */
{
    long Index, C;

    /* Only PES_IMM is supported */
    CHECK (Scope == PES_IMM);

    /* Read the character index */
    if (!GetNumber (B, &Index)) {
        return;
    }
    if (Index < 0 || Index > 255) {
        Error ("Character index out of range");
        return;
    }

    /* Comma follows */
    if (!GetComma (B)) {
        return;
    }

    /* Read the character code */
    if (!GetNumber (B, &C)) {
        return;
    }
    if (C < 0 || C > 255) {
        Error ("Character code out of range");
        return;
    }

    /* Warn about remapping character code 0x00
    ** (except when remapping it back to itself).
    */
    if (Index + C != 0 && IS_Get (&WarnRemapZero)) {
        if (Index == 0) {
            Warning ("Remapping from 0 is dangerous with string functions");
        }
        else if (C == 0) {
            Warning ("Remapping to 0 can make string functions stop unexpectedly");
        }
    }

    /* Remap the character */
    TgtTranslateSet ((unsigned) Index, (unsigned char) C);
}



static void WarnPragma (pragma_scope_t Scope, StrBuf* B)
/* Enable/disable warnings */
{
    long   Val;
    int    Push;

    /* A warning name must follow */
    IntStack* S = GetWarning (B);

    /* Only PES_IMM is supported */
    CHECK (Scope == PES_IMM);

    if (S == 0) {
        return;
    }

    /* Comma follows */
    if (!GetComma (B)) {
        return;
    }

    /* Check for the "push" or "pop" keywords */
    switch (ParsePushPop (B)) {

        case PP_NONE:
            Push = 0;
            break;

        case PP_PUSH:
            Push = 1;
            break;

        case PP_POP:
            /* Pop the old value and bail out */
            PopInt (S);
            return;

        case PP_ERROR:
            /* Bail out */
            return;

        default:
            Internal ("Invalid result from ParsePushPop");
    }

    /* Boolean argument follows */
    if (HasStr (B, "true") || HasStr (B, "on")) {
        Val = 1;
    } else if (HasStr (B, "false") || HasStr (B, "off")) {
        Val = 0;
    } else if (!SB_GetNumber (B, &Val)) {
        Error ("Invalid pragma argument");
        return;
    }

    /* Set/push the new value */
    if (Push) {
        PushInt (S, Val);
    } else {
        IS_Set (S, Val);
    }
}



static void FlagPragma (pragma_scope_t Scope, pragma_t Token, StrBuf* B, IntStack* Stack)
/* Handle a pragma that expects a boolean parameter */
{
    StrBuf Ident = AUTO_STRBUF_INITIALIZER;
    long   Val;
    int    PushPop = 0;

    /* Unused at the moment */
    (void)Scope;
    (void)Token;

    /* Try to read an identifier */
    int IsIdent = SB_GetSym (B, &Ident, 0);

    /* Check if we have a first argument named "pop" */
    if (IsIdent && SB_CompareStr (&Ident, "pop") == 0) {
        /* Pop the old value and bail out */
        ApplyPragma (-1, Stack, 0);

        /* No other arguments allowed */
        return;
    }

    /* Check if we have a first argument named "push" */
    if (IsIdent && SB_CompareStr (&Ident, "push") == 0) {
        PushPop = 1;
        if (!GetComma (B)) {
            goto ExitPoint;
        }
        IsIdent = SB_GetSym (B, &Ident, 0);
    }

    /* Boolean argument follows */
    if (IsIdent) {
        Val = IsBoolKeyword (&Ident);
    } else if (!GetNumber (B, &Val)) {
        goto ExitPoint;
    }

    /* Add this pragma and apply it whenever appropriately */
    ApplyPragma (PushPop, Stack, Val);

ExitPoint:
    /* Free the identifier */
    SB_Done (&Ident);
}



static void IntPragma (pragma_scope_t Scope, pragma_t Token, StrBuf* B, IntStack* Stack, long Low, long High)
/* Handle a pragma that expects an int parameter */
{
    long  Val;
    int   Push;

    /* Unused at the moment */
    (void)Scope;
    (void)Token;

    /* Check for the "push" or "pop" keywords */
    switch (ParsePushPop (B)) {

        case PP_NONE:
            Push = 0;
            break;

        case PP_PUSH:
            Push = 1;
            break;

        case PP_POP:
            /* Pop the old value and bail out */
            ApplyPragma (-1, Stack, 0);
            return;

        case PP_ERROR:
            /* Bail out */
            return;

        default:
            Internal ("Invalid result from ParsePushPop");

    }

    /* Integer argument follows */
    if (!GetNumber (B, &Val)) {
        return;
    }

    /* Check the argument */
    if (Val < Low || Val > High) {
        Error ("Pragma argument out of bounds (%ld-%ld)", Low, High);
        return;
    }

    /* Add this pragma and apply it whenever appropriately */
    ApplyPragma (Push, Stack, Val);
}



static void NoteMessagePragma (const char* Message)
/* Wrapper for printf-like Note() function protected from user-provided format
** specifiers.
*/
{
    Note ("%s", Message);
}



static void ParsePragmaString (void)
/* Parse the contents of _Pragma */
{
    pragma_t Pragma;
    StrBuf   Ident = AUTO_STRBUF_INITIALIZER;

    /* Create a string buffer from the string literal */
    StrBuf B = AUTO_STRBUF_INITIALIZER;


    SB_Append (&B, GetLiteralStrBuf (CurTok.SVal));

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

    /* Get the pragma name from the string */
    SB_SkipWhite (&B);
    if (!SB_GetSym (&B, &Ident, "-")) {
        Error ("Invalid pragma");
        goto ExitPoint;
    }

    /* Search for the name */
    Pragma = FindPragma (&Ident);

    /* Do we know this pragma? */
    if (Pragma == PRAGMA_ILLEGAL) {
        /* According to the ANSI standard, we're not allowed to generate errors
        ** for unknown pragmas, but warn about them if enabled (the default).
        */
        if (IS_Get (&WarnUnknownPragma)) {
            Warning ("Unknown pragma '%s'", SB_GetConstBuf (&Ident));
        }
        goto ExitPoint;
    }

    /* Check for an open paren */
    SB_SkipWhite (&B);
    if (SB_Get (&B) != '(') {
        Error ("'(' expected");
        goto ExitPoint;
    }

    /* Skip white space before the argument */
    SB_SkipWhite (&B);

    /* Switch for the different pragmas */
    switch (Pragma) {

        case PRAGMA_ALIGN:
            /* TODO: PES_EXPR (PES_DECL) */
            IntPragma (PES_STMT, Pragma, &B, &DataAlignment, 1, 4096);
            break;

        case PRAGMA_ALLOW_EAGER_INLINE:
            FlagPragma (PES_STMT, Pragma, &B, &EagerlyInlineFuncs);
            break;

        case PRAGMA_BSS_NAME:
            /* TODO: PES_STMT or even PES_EXPR (PES_DECL) maybe? */
            SegNamePragma (PES_FUNC, PRAGMA_BSS_NAME, &B);
            break;

        case PRAGMA_CHARMAP:
            CharMapPragma (PES_IMM, &B);
            break;

        case PRAGMA_CHECK_STACK:
            /* TODO: PES_SCOPE maybe? */
            FlagPragma (PES_FUNC, Pragma, &B, &CheckStack);
            break;

        case PRAGMA_CODE_NAME:
            /* PES_FUNC is the only sensible option so far */
            SegNamePragma (PES_FUNC, PRAGMA_CODE_NAME, &B);
            break;

        case PRAGMA_CODESIZE:
            /* PES_EXPR would be optimization nightmare */
            IntPragma (PES_STMT, Pragma, &B, &CodeSizeFactor, 10, 1000);
            break;

        case PRAGMA_DATA_NAME:
            /* TODO: PES_STMT or even PES_EXPR (PES_DECL) maybe? */
            SegNamePragma (PES_FUNC, PRAGMA_DATA_NAME, &B);
            break;

        case PRAGMA_INLINE_STDFUNCS:
            /* TODO: PES_EXPR maybe? */
            FlagPragma (PES_STMT, Pragma, &B, &InlineStdFuncs);
            break;

        case PRAGMA_LOCAL_STRINGS:
            /* TODO: PES_STMT or even PES_EXPR */
            FlagPragma (PES_FUNC, Pragma, &B, &LocalStrings);
            break;

        case PRAGMA_MESSAGE:
            /* PES_IMM is the only sensible option */
            StringPragma (PES_IMM, &B, NoteMessagePragma);
            break;

        case PRAGMA_OPTIMIZE:
            /* TODO: PES_STMT or even PES_EXPR maybe? */
            FlagPragma (PES_STMT, Pragma, &B, &Optimize);
            break;

        case PRAGMA_REGVARADDR:
            /* TODO: PES_STMT or even PES_EXPR maybe? */
            FlagPragma (PES_FUNC, Pragma, &B, &AllowRegVarAddr);
            break;

        case PRAGMA_REGISTER_VARS:
            /* TODO: PES_STMT or even PES_EXPR (PES_DECL) maybe? */
            FlagPragma (PES_FUNC, Pragma, &B, &EnableRegVars);
            break;

        case PRAGMA_RODATA_NAME:
            /* TODO: PES_STMT or even PES_EXPR maybe? */
            SegNamePragma (PES_FUNC, PRAGMA_RODATA_NAME, &B);
            break;

        case PRAGMA_SIGNED_CHARS:
            /* TODO: PES_STMT or even PES_EXPR maybe? */
            FlagPragma (PES_FUNC, Pragma, &B, &SignedChars);
            break;

        case PRAGMA_STATIC_LOCALS:
            /* TODO: PES_STMT or even PES_EXPR (PES_DECL) maybe? */
            FlagPragma (PES_FUNC, Pragma, &B, &StaticLocals);
            break;

        case PRAGMA_WRAPPED_CALL:
            /* PES_IMM is the only sensible option */
            WrappedCallPragma (PES_IMM, &B);
            break;

        case PRAGMA_WARN:
            /* PES_IMM is the only sensible option */
            WarnPragma (PES_IMM, &B);
            break;

        case PRAGMA_WRITABLE_STRINGS:
            /* TODO: PES_STMT or even PES_EXPR maybe? */
            FlagPragma (PES_FUNC, Pragma, &B, &WritableStrings);
            break;

        case PRAGMA_ZPSYM:
            /* PES_IMM is the only sensible option */
            StringPragma (PES_IMM, &B, MakeZPSym);
            break;

        default:
            Internal ("Invalid pragma");
    }

    /* Closing paren expected */
    SB_SkipWhite (&B);
    if (SB_Get (&B) != ')') {
        Error ("')' expected");
        goto ExitPoint;
    }
    SB_SkipWhite (&B);

    /* Allow an optional semicolon to be compatible with the old syntax */
    if (SB_Peek (&B) == ';') {
        SB_Skip (&B);
        SB_SkipWhite (&B);
    }

    /* Make sure nothing follows */
    if (SB_Peek (&B) != '\0') {
        Error ("Unexpected input following pragma directive");
    }

ExitPoint:
    /* Release the string buffers */
    SB_Done (&B);
    SB_Done (&Ident);
}



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



void ConsumePragma (void)
/* Parse a pragma. The pragma comes always in the form of the new C99 _Pragma()
** operator.
*/
{
    /* Skip the _Pragma token */
    NextToken ();

    /* Prevent from translating string literals in _Pragma */
    ++InPragmaParser;

    /* We expect an opening paren */
    if (!ConsumeLParen ()) {
        --InPragmaParser;
        return;
    }

    /* String literal */
    if (CurTok.Tok != TOK_SCONST) {
        /* Print a diagnostic */
        Error ("String literal expected");

        /* Try some smart error recovery: Skip tokens until we reach the
        ** enclosing paren, or a semicolon.
        */
        PragmaErrorSkip ();
    } else {
        /* Parse the pragma */
        ParsePragmaString ();
    }

    --InPragmaParser;

    /* Closing paren needed */
    ConsumeRParen ();
}
