X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fcc65%2Fpragma.c;h=608ef7e6d03db81ab588ea64c6453f8c07faa9b4;hb=05f72963695f843cca3fd3dac1be0175b470b179;hp=ba3815d965980be603936767a470520c2504db33;hpb=c1b6680a92b6e9d3d3e87d8dcd6ae6f11ead8cf5;p=cc65 diff --git a/src/cc65/pragma.c b/src/cc65/pragma.c index ba3815d96..608ef7e6d 100644 --- a/src/cc65/pragma.c +++ b/src/cc65/pragma.c @@ -6,10 +6,10 @@ /* */ /* */ /* */ -/* (C) 1998-2001 Ullrich von Bassewitz */ -/* Wacholderweg 14 */ -/* D-70597 Stuttgart */ -/* EMail: uz@cc65.org */ +/* (C) 1998-2009, Ullrich von Bassewitz */ +/* Roemerstrasse 52 */ +/* D-70794 Filderstadt */ +/* EMail: uz@cc65.org */ /* */ /* */ /* This software is provided 'as-is', without any expressed or implied */ @@ -36,6 +36,11 @@ #include #include +/* common */ +#include "chartype.h" +#include "segnames.h" +#include "tgttrans.h" + /* cc65 */ #include "codegen.h" #include "error.h" @@ -43,7 +48,7 @@ #include "global.h" #include "litpool.h" #include "scanner.h" -#include "segments.h" +#include "scanstrbuf.h" #include "symtab.h" #include "pragma.h" @@ -57,45 +62,92 @@ /* Tokens for the #pragmas */ typedef enum { - PR_BSSSEG, - PR_CHECKSTACK, - PR_CODESEG, - PR_DATASEG, - PR_REGVARADDR, - PR_RODATASEG, - PR_SIGNEDCHARS, - PR_STATICLOCALS, - PR_ZPSYM, - PR_ILLEGAL + PRAGMA_ILLEGAL = -1, + PRAGMA_BSS_NAME, + PRAGMA_BSSSEG, /* obsolete */ + PRAGMA_CHARMAP, + PRAGMA_CHECK_STACK, + PRAGMA_CHECKSTACK, /* obsolete */ + PRAGMA_CODE_NAME, + PRAGMA_CODESEG, /* obsolete */ + PRAGMA_CODESIZE, + PRAGMA_DATA_NAME, + PRAGMA_DATASEG, /* obsolete */ + PRAGMA_LOCAL_STRINGS, + PRAGMA_OPTIMIZE, + PRAGMA_REGVARADDR, + PRAGMA_REGISTER_VARS, + PRAGMA_REGVARS, /* obsolete */ + PRAGMA_RODATA_NAME, + PRAGMA_RODATASEG, /* obsolete */ + PRAGMA_SIGNED_CHARS, + PRAGMA_SIGNEDCHARS, /* obsolete */ + PRAGMA_STATIC_LOCALS, + PRAGMA_STATICLOCALS, /* obsolete */ + PRAGMA_WARN, + PRAGMA_WRITABLE_STRINGS, + PRAGMA_ZPSYM, + PRAGMA_COUNT } pragma_t; /* Pragma table */ static const struct Pragma { const char* Key; /* Keyword */ - pragma_t Tok; /* Token */ -} Pragmas[] = { - { "bssseg", PR_BSSSEG }, - { "checkstack", PR_CHECKSTACK }, - { "codeseg", PR_CODESEG }, - { "dataseg", PR_DATASEG }, - { "regvaraddr", PR_REGVARADDR }, - { "rodataseg", PR_RODATASEG }, - { "signedchars", PR_SIGNEDCHARS }, - { "staticlocals", PR_STATICLOCALS }, - { "zpsym", PR_ZPSYM }, + pragma_t Tok; /* Token */ +} Pragmas[PRAGMA_COUNT] = { + { "bss-name", PRAGMA_BSS_NAME }, + { "bssseg", PRAGMA_BSSSEG }, /* obsolete */ + { "charmap", PRAGMA_CHARMAP }, + { "check-stack", PRAGMA_CHECK_STACK }, + { "checkstack", PRAGMA_CHECKSTACK }, /* obsolete */ + { "code-name", PRAGMA_CODE_NAME }, + { "codeseg", PRAGMA_CODESEG }, /* obsolete */ + { "codesize", PRAGMA_CODESIZE }, + { "data-name", PRAGMA_DATA_NAME }, + { "dataseg", PRAGMA_DATASEG }, /* obsolete */ + { "local-strings", PRAGMA_LOCAL_STRINGS }, + { "optimize", PRAGMA_OPTIMIZE }, + { "register-vars", PRAGMA_REGISTER_VARS }, + { "regvaraddr", PRAGMA_REGVARADDR }, + { "regvars", PRAGMA_REGVARS }, /* obsolete */ + { "rodata-name", PRAGMA_RODATA_NAME }, + { "rodataseg", PRAGMA_RODATASEG }, /* obsolete */ + { "signed-chars", PRAGMA_SIGNED_CHARS }, + { "signedchars", PRAGMA_SIGNEDCHARS }, /* obsolete */ + { "static-locals", PRAGMA_STATIC_LOCALS }, + { "staticlocals", PRAGMA_STATICLOCALS }, /* obsolete */ + { "warn", PRAGMA_WARN }, + { "writable-strings", PRAGMA_WRITABLE_STRINGS }, + { "zpsym", PRAGMA_ZPSYM }, }; -/* Number of pragmas */ -#define PRAGMA_COUNT (sizeof(Pragmas) / sizeof(Pragmas[0])) +/* Result of ParsePushPop */ +typedef enum { + PP_NONE, + PP_POP, + PP_PUSH, + PP_ERROR, +} PushPopResult; /*****************************************************************************/ -/* Code */ +/* 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 */ { @@ -104,157 +156,688 @@ static int CmpKey (const void* Key, const void* Elem) -static pragma_t FindPragma (const char* Key) -/* Find a pragma and return the token. Return PR_ILLEGAL if the keyword is +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 (Key, Pragmas, PRAGMA_COUNT, sizeof (Pragmas[0]), CmpKey); - return P? P->Tok : PR_ILLEGAL; + P = bsearch (SB_GetConstBuf (Key), Pragmas, PRAGMA_COUNT, sizeof (Pragmas[0]), CmpKey); + return P? P->Tok : PRAGMA_ILLEGAL; } -static void StringPragma (void (*Func) (const char*)) -/* Handle a pragma that expects a string parameter */ +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 (curtok != TOK_SCONST) { + 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; + + + /* 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)) { + Res = PP_ERROR; + } + + } else { + + /* Unknown keyword */ + Error ("Invalid pragma arguments"); + Res = PP_ERROR; + } + } + + /* 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 { - /* Get the string */ - const char* Name = GetLiteral (curval); + IS_Drop (S); + } +} + - /* Call the given function with the string argument */ - Func (Name); - /* Reset the string pointer, removing the string from the pool */ - ResetLiteralPoolOffs (curval); +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); } +} - /* Skip the string (or error) token */ - NextToken (); + + +static int BoolKeyword (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 SegNamePragma (segment_t Seg) +/*****************************************************************************/ +/* Pragma handling functions */ +/*****************************************************************************/ + + + +static void StringPragma (StrBuf* B, void (*Func) (const char*)) +/* Handle a pragma that expects a string parameter */ +{ + StrBuf S = AUTO_STRBUF_INITIALIZER; + + /* 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 (StrBuf* B, segment_t Seg) /* Handle a pragma that expects a segment name parameter */ { - if (curtok != TOK_SCONST) { - Error ("String literal expected"); + StrBuf S = AUTO_STRBUF_INITIALIZER; + const char* Name; + + /* Check for the "push" or "pop" keywords */ + int Push = 0; + switch (ParsePushPop (B)) { + + case PP_NONE: + break; + + case PP_PUSH: + Push = 1; + break; + + case PP_POP: + /* Pop the old value and output it */ + PopSegName (Seg); + g_segname (Seg); + + /* 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 string */ + Name = SB_GetConstBuf (&S); + + /* Check if the name is valid */ + if (ValidSegName (Name)) { + + /* Set the new name */ + if (Push) { + PushSegName (Seg, Name); + } else { + SetSegName (Seg, Name); + } + g_segname (Seg); + } else { - /* Get the segment name */ - const char* Name = GetLiteral (curval); - /* Check if the name is valid */ - if (ValidSegName (Name)) { + /* Segment name is invalid */ + Error ("Illegal segment name: `%s'", Name); + + } - /* Set the new name */ - g_segname (Seg, Name); +ExitPoint: + /* Call the string buf destructor */ + SB_Done (&S); +} - } else { - /* Segment name is invalid */ - Error ("Illegal segment name: `%s'", Name); - } +static void CharMapPragma (StrBuf* B) +/* Change the character map */ +{ + long Index, C; - /* Reset the string pointer, removing the string from the pool */ - ResetLiteralPoolOffs (curval); + /* Read the character index */ + if (!GetNumber (B, &Index)) { + return; + } + if (Index < 1 || Index > 255) { + if (Index == 0) { + /* For groepaz */ + Error ("Remapping 0 is not allowed"); + } else { + Error ("Character index out of range"); + } + return; } - /* Skip the string (or error) token */ - NextToken (); + /* Comma follows */ + if (!GetComma (B)) { + return; + } + + /* Read the character code */ + if (!GetNumber (B, &C)) { + return; + } + if (C < 1 || C > 255) { + if (C == 0) { + /* For groepaz */ + Error ("Remapping 0 is not allowed"); + } else { + Error ("Character code out of range"); + } + return; + } + + /* Remap the character */ + TgtTranslateSet ((unsigned) Index, (unsigned char) C); } -static void FlagPragma (unsigned char* Flag) +static void WarnPragma (StrBuf* B) +/* Enable/disable warnings */ +{ + long Val; + int Push; + + /* A warning name must follow */ + IntStack* S =GetWarning (B); + 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 (StrBuf* B, IntStack* Stack) /* Handle a pragma that expects a boolean paramater */ { - /* Read a constant expression */ - struct expent val; - constexpr (&val); + StrBuf Ident = AUTO_STRBUF_INITIALIZER; + long Val; + int Push; + + + /* 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) { + PopInt (Stack); + /* No other arguments allowed */ + return; + } + + /* Check if we have a first argument named "push" */ + if (IsIdent && SB_CompareStr (&Ident, "push") == 0) { + Push = 1; + if (!GetComma (B)) { + goto ExitPoint; + } + IsIdent = SB_GetSym (B, &Ident, 0); + } else { + Push = 0; + } - /* Store the value into the flag parameter */ - *Flag = (val.e_const != 0); + /* Boolean argument follows */ + if (IsIdent) { + Val = BoolKeyword (&Ident); + } else if (!GetNumber (B, &Val)) { + goto ExitPoint; + } + + /* Set/push the new value */ + if (Push) { + PushInt (Stack, Val); + } else { + IS_Set (Stack, Val); + } + +ExitPoint: + /* Free the identifier */ + SB_Done (&Ident); } -void DoPragma (void) -/* Handle pragmas */ +static void IntPragma (StrBuf* B, IntStack* Stack, long Low, long High) +/* Handle a pragma that expects an int paramater */ +{ + long Val; + int Push; + + /* 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 (Stack); + 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; + } + + /* Set/push the new value */ + if (Push) { + PushInt (Stack, Val); + } else { + IS_Set (Stack, Val); + } +} + + + +static void ParsePragma (void) +/* Parse the contents of the _Pragma statement */ { pragma_t Pragma; + StrBuf Ident = AUTO_STRBUF_INITIALIZER; - /* Skip the token itself */ + /* Create a string buffer from the string literal */ + StrBuf B = AUTO_STRBUF_INITIALIZER; + SB_Append (&B, GetLiteralStrBuf (CurTok.SVal)); + + /* Skip the string token */ NextToken (); - /* Identifier must follow */ - if (curtok != TOK_IDENT) { - Error ("Identifier expected"); - return; + /* Get the pragma name from the string */ + SB_SkipWhite (&B); + if (!SB_GetSym (&B, &Ident, "-")) { + Error ("Invalid pragma"); + goto ExitPoint; } - /* Search for the name, then skip the identifier */ - Pragma = FindPragma (CurTok.Ident); - NextToken (); + /* Search for the name */ + Pragma = FindPragma (&Ident); /* Do we know this pragma? */ - if (Pragma == PR_ILLEGAL) { + if (Pragma == PRAGMA_ILLEGAL) { /* According to the ANSI standard, we're not allowed to generate errors - * for unknown pragmas, however, we're allowed to warn - and we will - * do so. Otherwise one typo may give you hours of bug hunting... + * for unknown pragmas, but warn about them if enabled (the default). */ - Warning ("Unknown #pragma `%s'", CurTok.Ident); - return; + if (IS_Get (&WarnUnknownPragma)) { + Warning ("Unknown pragma `%s'", SB_GetConstBuf (&Ident)); + } + goto ExitPoint; } /* Check for an open paren */ - ConsumeLParen (); + 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 PR_BSSSEG: - SegNamePragma (SEG_BSS); - break; + case PRAGMA_BSSSEG: + Warning ("#pragma bssseg is obsolete, please use #pragma bss-name instead"); + /* FALLTHROUGH */ + case PRAGMA_BSS_NAME: + SegNamePragma (&B, SEG_BSS); + break; + + case PRAGMA_CHARMAP: + CharMapPragma (&B); + break; + + case PRAGMA_CHECKSTACK: + Warning ("#pragma checkstack is obsolete, please use #pragma check-stack instead"); + /* FALLTHROUGH */ + case PRAGMA_CHECK_STACK: + FlagPragma (&B, &CheckStack); + break; + + case PRAGMA_CODESEG: + Warning ("#pragma codeseg is obsolete, please use #pragma code-name instead"); + /* FALLTHROUGH */ + case PRAGMA_CODE_NAME: + SegNamePragma (&B, SEG_CODE); + break; + + case PRAGMA_CODESIZE: + IntPragma (&B, &CodeSizeFactor, 10, 1000); + break; + + case PRAGMA_DATASEG: + Warning ("#pragma dataseg is obsolete, please use #pragma data-name instead"); + /* FALLTHROUGH */ + case PRAGMA_DATA_NAME: + SegNamePragma (&B, SEG_DATA); + break; + + case PRAGMA_LOCAL_STRINGS: + FlagPragma (&B, &LocalStrings); + break; + + case PRAGMA_OPTIMIZE: + FlagPragma (&B, &Optimize); + break; + + case PRAGMA_REGVARADDR: + FlagPragma (&B, &AllowRegVarAddr); + break; + + case PRAGMA_REGVARS: + Warning ("#pragma regvars is obsolete, please use #pragma register-vars instead"); + /* FALLTHROUGH */ + case PRAGMA_REGISTER_VARS: + FlagPragma (&B, &EnableRegVars); + break; + + case PRAGMA_RODATASEG: + Warning ("#pragma rodataseg is obsolete, please use #pragma rodata-name instead"); + /* FALLTHROUGH */ + case PRAGMA_RODATA_NAME: + SegNamePragma (&B, SEG_RODATA); + break; + + case PRAGMA_SIGNEDCHARS: + Warning ("#pragma signedchars is obsolete, please use #pragma signed-chars instead"); + /* FALLTHROUGH */ + case PRAGMA_SIGNED_CHARS: + FlagPragma (&B, &SignedChars); + break; + + case PRAGMA_STATICLOCALS: + Warning ("#pragma staticlocals is obsolete, please use #pragma static-locals instead"); + /* FALLTHROUGH */ + case PRAGMA_STATIC_LOCALS: + FlagPragma (&B, &StaticLocals); + break; + + case PRAGMA_WARN: + WarnPragma (&B); + break; + + case PRAGMA_WRITABLE_STRINGS: + FlagPragma (&B, &WritableStrings); + break; + + case PRAGMA_ZPSYM: + StringPragma (&B, MakeZPSym); + break; + + default: + Internal ("Invalid pragma"); + } - case PR_CHECKSTACK: - FlagPragma (&CheckStack); - break; + /* Closing paren expected */ + SB_SkipWhite (&B); + if (SB_Get (&B) != ')') { + Error ("')' expected"); + goto ExitPoint; + } + SB_SkipWhite (&B); - case PR_CODESEG: - SegNamePragma (SEG_CODE); - break; + /* Allow an optional semicolon to be compatible with the old syntax */ + if (SB_Peek (&B) == ';') { + SB_Skip (&B); + SB_SkipWhite (&B); + } - case PR_DATASEG: - SegNamePragma (SEG_DATA); - break; + /* Make sure nothing follows */ + if (SB_Peek (&B) != '\0') { + Error ("Unexpected input following pragma directive"); + } - case PR_REGVARADDR: - FlagPragma (&AllowRegVarAddr); - break; +ExitPoint: + /* Release the string buffers */ + SB_Done (&B); + SB_Done (&Ident); +} - case PR_RODATASEG: - SegNamePragma (SEG_RODATA); - break; - case PR_SIGNEDCHARS: - FlagPragma (&SignedChars); - break; - case PR_STATICLOCALS: - FlagPragma (&StaticLocals); - break; +void DoPragma (void) +/* Handle pragmas. These come always in form of the new C99 _Pragma() operator. */ +{ + /* Skip the token itself */ + NextToken (); + + /* We expect an opening paren */ + if (!ConsumeLParen ()) { + return; + } - case PR_ZPSYM: - StringPragma (MakeZPSym); - break; + /* String literal */ + if (CurTok.Tok != TOK_SCONST) { - default: - Internal ("Invalid pragma"); + /* 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 statement */ + ParsePragma (); } /* Closing paren needed */