diff options
Diffstat (limited to 'src/bldprogs/VBoxCPP.cpp')
-rw-r--r-- | src/bldprogs/VBoxCPP.cpp | 5542 |
1 files changed, 5542 insertions, 0 deletions
diff --git a/src/bldprogs/VBoxCPP.cpp b/src/bldprogs/VBoxCPP.cpp new file mode 100644 index 00000000..eb5a7d44 --- /dev/null +++ b/src/bldprogs/VBoxCPP.cpp @@ -0,0 +1,5542 @@ +/* $Id: VBoxCPP.cpp $ */ +/** @file + * VBox Build Tool - A mini C Preprocessor. + * + * Purposes to which this preprocessor will be put: + * - Preprocessig vm.h into dtrace/lib/vm.d so we can access the VM + * structure (as well as substructures) from DTrace without having + * to handcraft it all. + * - Removing \#ifdefs relating to a new feature that has become + * stable and no longer needs \#ifdef'ing. + * - Pretty printing preprocessor directives. This will be used by + * SCM. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/VBoxTpG.h> + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scmstream.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The bitmap type. */ +#define VBCPP_BITMAP_TYPE uint64_t +/** The bitmap size as a multiple of VBCPP_BITMAP_TYPE. */ +#define VBCPP_BITMAP_SIZE (128 / 64) +/** Checks if a bit is set. */ +#define VBCPP_BITMAP_IS_SET(a_bm, a_ch) ASMBitTest(a_bm, (a_ch) & 0x7f) +/** Sets a bit. */ +#define VBCPP_BITMAP_SET(a_bm, a_ch) ASMBitSet(a_bm, (a_ch) & 0x7f) +/** Empties the bitmap. */ +#define VBCPP_BITMAP_EMPTY(a_bm) do { (a_bm)[0] = 0; (a_bm)[1] = 0; } while (0) +/** Joins to bitmaps by OR'ing their values.. */ +#define VBCPP_BITMAP_OR(a_bm1, a_bm2) do { (a_bm1)[0] |= (a_bm2)[0]; (a_bm1)[1] |= (a_bm2)[1]; } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the C preprocessor instance data. */ +typedef struct VBCPP *PVBCPP; + + +/** + * Variable string buffer (very simple version of SCMSTREAM). + */ +typedef struct VBCPPSTRBUF +{ + /** The preprocessor instance (for error reporting). */ + struct VBCPP *pThis; + /** The length of the string in the buffer. */ + size_t cchBuf; + /** The string storage. */ + char *pszBuf; + /** Allocated buffer space. */ + size_t cbBufAllocated; +} VBCPPSTRBUF; +/** Pointer to a variable string buffer. */ +typedef VBCPPSTRBUF *PVBCPPSTRBUF; + + +/** + * The preprocessor mode. + */ +typedef enum VBCPPMODE +{ + kVBCppMode_Invalid = 0, + kVBCppMode_Standard, + kVBCppMode_Selective, + kVBCppMode_SelectiveD, + kVBCppMode_End +} VBCPPMODE; + + +/** + * A macro (aka define). + */ +typedef struct VBCPPMACRO +{ + /** The string space core. */ + RTSTRSPACECORE Core; +#if 0 + /** For linking macros that have the fExpanding flag set. */ + struct VBCPPMACRO *pUpExpanding; +#endif + /** Whether it's a function. */ + bool fFunction; + /** Variable argument count. */ + bool fVarArg; + /** Set if originating on the command line. */ + bool fCmdLine; + /** Set if this macro is currently being expanded and should not be + * recursively applied. */ + bool fExpanding; + /** The number of known arguments. */ + uint32_t cArgs; + /** Pointer to a list of argument names. */ + const char **papszArgs; + /** Lead character bitmap for the argument names. */ + VBCPP_BITMAP_TYPE bmArgs[VBCPP_BITMAP_SIZE]; + /** The value length. */ + size_t cchValue; + /** The define value. (This is followed by the name and arguments.) */ + char szValue[1]; +} VBCPPMACRO; +/** Pointer to a macro. */ +typedef VBCPPMACRO *PVBCPPMACRO; + + +/** + * Macro expansion data. + */ +typedef struct VBCPPMACROEXP +{ + /** The expansion buffer. */ + VBCPPSTRBUF StrBuf; +#if 0 + /** List of expanding macros (Stack). */ + PVBCPPMACRO pMacroStack; +#endif + /** The input stream (in case we want to look for parameter lists). */ + PSCMSTREAM pStrmInput; + /** Array of argument values. Used when expanding function style macros. */ + char **papszArgs; + /** The number of argument values current in papszArgs. */ + uint32_t cArgs; + /** The number of argument values papszArgs can currently hold */ + uint32_t cArgsAlloced; +} VBCPPMACROEXP; +/** Pointer to macro expansion data. */ +typedef VBCPPMACROEXP *PVBCPPMACROEXP; + + +/** + * The vbcppMacroExpandReScan mode of operation. + */ +typedef enum VBCPPMACRORESCANMODE +{ + /** Invalid mode. */ + kMacroReScanMode_Invalid = 0, + /** Normal expansion mode. */ + kMacroReScanMode_Normal, + /** Replaces known macros and heeds the 'defined' operator. */ + kMacroReScanMode_Expression, + /** End of valid modes. */ + kMacroReScanMode_End +} VBCPPMACRORESCANMODE; + + +/** + * Expression node type. + */ +typedef enum VBCPPEXPRKIND +{ + kVBCppExprKind_Invalid = 0, + kVBCppExprKind_Unary, + kVBCppExprKind_Binary, + kVBCppExprKind_Ternary, + kVBCppExprKind_SignedValue, + kVBCppExprKind_UnsignedValue, + kVBCppExprKind_End +} VBCPPEXPRKIND; + + +/** Macro used for the precedence field. */ +#define VBCPPOP_PRECEDENCE(a_iPrecedence) ((a_iPrecedence) << 8) +/** Mask for getting the precedence field value. */ +#define VBCPPOP_PRECEDENCE_MASK 0xff00 +/** Operator associativity - Left to right. */ +#define VBCPPOP_L2R (1 << 16) +/** Operator associativity - Right to left. */ +#define VBCPPOP_R2L (2 << 16) + +/** + * Unary operators. + */ +typedef enum VBCPPUNARYOP +{ + kVBCppUnaryOp_Invalid = 0, + kVBCppUnaryOp_Pluss = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 5, + kVBCppUnaryOp_Minus = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 6, + kVBCppUnaryOp_LogicalNot = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 7, + kVBCppUnaryOp_BitwiseNot = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 8, + kVBCppUnaryOp_Parenthesis = VBCPPOP_R2L | VBCPPOP_PRECEDENCE(15) | 9, + kVBCppUnaryOp_End +} VBCPPUNARYOP; + +/** + * Binary operators. + */ +typedef enum VBCPPBINARYOP +{ + kVBCppBinary_Invalid = 0, + kVBCppBinary_Multiplication = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 2, + kVBCppBinary_Division = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 4, + kVBCppBinary_Modulo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 5, + kVBCppBinary_Addition = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 6) | 6, + kVBCppBinary_Subtraction = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 6) | 7, + kVBCppBinary_LeftShift = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 7) | 8, + kVBCppBinary_RightShift = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 7) | 9, + kVBCppBinary_LessThan = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 10, + kVBCppBinary_LessThanOrEqual = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 11, + kVBCppBinary_GreaterThan = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 12, + kVBCppBinary_GreaterThanOrEqual = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 13, + kVBCppBinary_EqualTo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 9) | 14, + kVBCppBinary_NotEqualTo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 9) | 15, + kVBCppBinary_BitwiseAnd = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(10) | 16, + kVBCppBinary_BitwiseXor = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(11) | 17, + kVBCppBinary_BitwiseOr = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(12) | 18, + kVBCppBinary_LogicalAnd = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(13) | 19, + kVBCppBinary_LogicalOr = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(14) | 20, + kVBCppBinary_End +} VBCPPBINARYOP; + +/** The precedence of the ternary operator (expr ? true : false). */ +#define VBCPPTERNAROP_PRECEDENCE VBCPPOP_PRECEDENCE(16) + + +/** Pointer to an expression parsing node. */ +typedef struct VBCPPEXPR *PVBCPPEXPR; +/** + * Expression parsing node. + */ +typedef struct VBCPPEXPR +{ + /** Parent expression. */ + PVBCPPEXPR pParent; + /** Whether the expression is complete or not. */ + bool fComplete; + /** The kind of expression. */ + VBCPPEXPRKIND enmKind; + /** Kind specific content. */ + union + { + /** kVBCppExprKind_Unary */ + struct + { + VBCPPUNARYOP enmOperator; + PVBCPPEXPR pArg; + } Unary; + + /** kVBCppExprKind_Binary */ + struct + { + VBCPPBINARYOP enmOperator; + PVBCPPEXPR pLeft; + PVBCPPEXPR pRight; + } Binary; + + /** kVBCppExprKind_Ternary */ + struct + { + PVBCPPEXPR pExpr; + PVBCPPEXPR pTrue; + PVBCPPEXPR pFalse; + } Ternary; + + /** kVBCppExprKind_SignedValue */ + struct + { + int64_t s64; + } SignedValue; + + /** kVBCppExprKind_UnsignedValue */ + struct + { + uint64_t u64; + } UnsignedValue; + } u; +} VBCPPEXPR; + + +/** + * Operator return statuses. + */ +typedef enum VBCPPEXPRRET +{ + kExprRet_Error = -1, + kExprRet_Ok = 0, + kExprRet_UnaryOperator, + kExprRet_Value, + kExprRet_EndOfExpr, + kExprRet_End +} VBCPPEXPRRET; + +/** + * Expression parser context. + */ +typedef struct VBCPPEXPRPARSER +{ + /** The current expression posistion. */ + const char *pszCur; + /** The root node. */ + PVBCPPEXPR pRoot; + /** The current expression node. */ + PVBCPPEXPR pCur; + /** Where to insert the next expression. */ + PVBCPPEXPR *ppCur; + /** The expression. */ + const char *pszExpr; + /** The number of undefined macros we've encountered while parsing. */ + size_t cUndefined; + /** Pointer to the C preprocessor instance. */ + PVBCPP pThis; +} VBCPPEXPRPARSER; +/** Pointer to an expression parser context. */ +typedef VBCPPEXPRPARSER *PVBCPPEXPRPARSER; + + +/** + * Evaluation result. + */ +typedef enum VBCPPEVAL +{ + kVBCppEval_Invalid = 0, + kVBCppEval_True, + kVBCppEval_False, + kVBCppEval_Undecided, + kVBCppEval_End +} VBCPPEVAL; + + +/** + * The condition kind. + */ +typedef enum VBCPPCONDKIND +{ + kVBCppCondKind_Invalid = 0, + /** \#if expr */ + kVBCppCondKind_If, + /** \#ifdef define */ + kVBCppCondKind_IfDef, + /** \#ifndef define */ + kVBCppCondKind_IfNDef, + /** \#elif expr */ + kVBCppCondKind_ElIf, + /** The end of valid values. */ + kVBCppCondKind_End +} VBCPPCONDKIND; + + +/** + * Conditional stack entry. + */ +typedef struct VBCPPCOND +{ + /** The next conditional on the stack. */ + struct VBCPPCOND *pUp; + /** The kind of conditional. This changes on encountering \#elif. */ + VBCPPCONDKIND enmKind; + /** Evaluation result. */ + VBCPPEVAL enmResult; + /** The evaluation result of the whole stack. */ + VBCPPEVAL enmStackResult; + + /** Whether we've seen the last else. */ + bool fSeenElse; + /** Set if we have an else if which has already been decided. */ + bool fElIfDecided; + /** The nesting level of this condition. */ + uint16_t iLevel; + /** The nesting level of this condition wrt the ones we keep. */ + uint16_t iKeepLevel; + + /** The condition string. (Points within the stream buffer.) */ + const char *pchCond; + /** The condition length. */ + size_t cchCond; +} VBCPPCOND; +/** Pointer to a conditional stack entry. */ +typedef VBCPPCOND *PVBCPPCOND; + + +/** + * Input buffer stack entry. + */ +typedef struct VBCPPINPUT +{ + /** Pointer to the next input on the stack. */ + struct VBCPPINPUT *pUp; + /** The input stream. */ + SCMSTREAM StrmInput; + /** Pointer into szName to the part which was specified. */ + const char *pszSpecified; + /** The input file name with include path. */ + char szName[1]; +} VBCPPINPUT; +/** Pointer to a input buffer stack entry */ +typedef VBCPPINPUT *PVBCPPINPUT; + + +/** + * The action to take with \#include. + */ +typedef enum VBCPPINCLUDEACTION +{ + kVBCppIncludeAction_Invalid = 0, + kVBCppIncludeAction_Include, + kVBCppIncludeAction_PassThru, + kVBCppIncludeAction_Drop, + kVBCppIncludeAction_End +} VBCPPINCLUDEACTION; + + +/** + * C Preprocessor instance data. + */ +typedef struct VBCPP +{ + /** @name Options + * @{ */ + /** The preprocessing mode. */ + VBCPPMODE enmMode; + /** Whether to keep comments. */ + bool fKeepComments; + /** Whether to respect source defines. */ + bool fRespectSourceDefines; + /** Whether to let source defines overrides the ones on the command + * line. */ + bool fAllowRedefiningCmdLineDefines; + /** Whether to pass thru defines. */ + bool fPassThruDefines; + /** Whether to allow undecided conditionals. */ + bool fUndecidedConditionals; + /** Whether to pass thru D pragmas. */ + bool fPassThruPragmaD; + /** Whether to pass thru STD pragmas. */ + bool fPassThruPragmaSTD; + /** Whether to pass thru other pragmas. */ + bool fPassThruPragmaOther; + /** Whether to remove dropped lines from the output. */ + bool fRemoveDroppedLines; + /** Whether to preforme line splicing. + * @todo implement line splicing */ + bool fLineSplicing; + /** What to do about include files. */ + VBCPPINCLUDEACTION enmIncludeAction; + + /** The number of include directories. */ + uint32_t cIncludes; + /** Array of directories to search for include files. */ + char **papszIncludes; + + /** The name of the input file. */ + const char *pszInput; + /** The name of the output file. NULL if stdout. */ + const char *pszOutput; + /** @} */ + + /** The define string space. */ + RTSTRSPACE StrSpace; + /** The string space holding explicitly undefined macros for selective + * preprocessing runs. */ + RTSTRSPACE UndefStrSpace; + /** Indicates whether a C-word might need expansion. + * The bitmap is indexed by C-word lead character. Bits that are set + * indicates that the lead character is used in a \#define that we know and + * should expand. */ + VBCPP_BITMAP_TYPE bmDefined[VBCPP_BITMAP_SIZE]; + + /** The current depth of the conditional stack. */ + uint32_t cCondStackDepth; + /** Conditional stack. */ + PVBCPPCOND pCondStack; + /** The current condition evaluates to kVBCppEval_False, don't output. */ + bool fIf0Mode; + /** Just dropped a line and should maybe drop the current line. */ + bool fJustDroppedLine; + + /** Whether the current line could be a preprocessor line. + * This is set when EOL is encountered and cleared again when a + * non-comment-or-space character is encountered. See vbcppPreprocess. */ + bool fMaybePreprocessorLine; + + /** The input stack depth */ + uint32_t cInputStackDepth; + /** The input buffer stack. */ + PVBCPPINPUT pInputStack; + + /** The output stream. */ + SCMSTREAM StrmOutput; + + /** The status of the whole job, as far as we know. */ + RTEXITCODE rcExit; + /** Whether StrmOutput is valid (for vbcppTerm). */ + bool fStrmOutputValid; +} VBCPP; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static PVBCPPMACRO vbcppMacroLookup(PVBCPP pThis, const char *pszDefine, size_t cchDefine); +static RTEXITCODE vbcppMacroExpandIt(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offMacro, PVBCPPMACRO pMacro, size_t offParameters); +static RTEXITCODE vbcppMacroExpandReScan(PVBCPP pThis, PVBCPPMACROEXP pExp, VBCPPMACRORESCANMODE enmMode, size_t *pcReplacements); +static void vbcppMacroExpandCleanup(PVBCPPMACROEXP pExp); + + + +/* + * + * + * Message Handling. + * Message Handling. + * Message Handling. + * Message Handling. + * Message Handling. + * + * + */ + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszMsg The message. + * @param va Message arguments. + */ +static RTEXITCODE vbcppErrorV(PVBCPP pThis, const char *pszMsg, va_list va) +{ + NOREF(pThis); + if (pThis->pInputStack) + { + PSCMSTREAM pStrm = &pThis->pInputStack->StrmInput; + + size_t const off = ScmStreamTell(pStrm); + size_t const iLine = ScmStreamTellLine(pStrm); + ScmStreamSeekByLine(pStrm, iLine); + size_t const offLine = ScmStreamTell(pStrm); + + RTPrintf("%s:%d:%zd: error: %N.\n", pThis->pInputStack->szName, iLine + 1, off - offLine + 1, pszMsg, va); + + size_t cchLine; + SCMEOL enmEof; + const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof); + if (pszLine) + RTPrintf(" %.*s\n" + " %*s^\n", + cchLine, pszLine, off - offLine, ""); + + ScmStreamSeekAbsolute(pStrm, off); + } + else + RTMsgErrorV(pszMsg, va); + return pThis->rcExit = RTEXITCODE_FAILURE; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszMsg The message. + * @param ... Message arguments. + */ +static RTEXITCODE vbcppError(PVBCPP pThis, const char *pszMsg, ...) +{ + va_list va; + va_start(va, pszMsg); + RTEXITCODE rcExit = vbcppErrorV(pThis, pszMsg, va); + va_end(va); + return rcExit; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszPos Pointer to the offending character. + * @param pszMsg The message. + * @param ... Message arguments. + */ +static RTEXITCODE vbcppErrorPos(PVBCPP pThis, const char *pszPos, const char *pszMsg, ...) +{ + NOREF(pszPos); NOREF(pThis); + va_list va; + va_start(va, pszMsg); + RTMsgErrorV(pszMsg, va); + va_end(va); + return pThis->rcExit = RTEXITCODE_FAILURE; +} + + + + + + + +/* + * + * + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * + * + */ + + +/** + * Initializes a string buffer. + * + * @param pStrBuf The buffer structure to initialize. + * @param pThis The C preprocessor instance. + */ +static void vbcppStrBufInit(PVBCPPSTRBUF pStrBuf, PVBCPP pThis) +{ + pStrBuf->pThis = pThis; + pStrBuf->cchBuf = 0; + pStrBuf->cbBufAllocated = 0; + pStrBuf->pszBuf = NULL; +} + + +/** + * Deletes a string buffer. + * + * @param pStrBuf Pointer to the string buffer. + */ +static void vbcppStrBufDelete(PVBCPPSTRBUF pStrBuf) +{ + RTMemFree(pStrBuf->pszBuf); + pStrBuf->pszBuf = NULL; +} + + +/** + * Ensures that sufficient bufferspace is available, growing the buffer if + * necessary. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param cbMin The minimum buffer size. + */ +static RTEXITCODE vbcppStrBufGrow(PVBCPPSTRBUF pStrBuf, size_t cbMin) +{ + if (pStrBuf->cbBufAllocated >= cbMin) + return RTEXITCODE_SUCCESS; + + size_t cbNew = pStrBuf->cbBufAllocated * 2; + if (cbNew < cbMin) + cbNew = RT_ALIGN_Z(cbMin, _1K); + void *pv = RTMemRealloc(pStrBuf->pszBuf, cbNew); + if (!pv) + return vbcppError(pStrBuf->pThis, "out of memory (%zu bytes)", cbNew); + + pStrBuf->pszBuf = (char *)pv; + pStrBuf->cbBufAllocated = cbNew; + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a substring. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param pchSrc Pointer to the first character in the substring. + * @param cchSrc The length of the substring. + */ +static RTEXITCODE vbcppStrBufAppendN(PVBCPPSTRBUF pStrBuf, const char *pchSrc, size_t cchSrc) +{ + size_t cchBuf = pStrBuf->cchBuf; + if (cchBuf + cchSrc + 1 > pStrBuf->cbBufAllocated) + { + RTEXITCODE rcExit = vbcppStrBufGrow(pStrBuf, cchBuf + cchSrc + 1); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + memcpy(&pStrBuf->pszBuf[cchBuf], pchSrc, cchSrc); + cchBuf += cchSrc; + pStrBuf->pszBuf[cchBuf] = '\0'; + pStrBuf->cchBuf = cchBuf; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a character. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param ch The charater to append. + */ +static RTEXITCODE vbcppStrBufAppendCh(PVBCPPSTRBUF pStrBuf, char ch) +{ + size_t cchBuf = pStrBuf->cchBuf; + if (cchBuf + 2 > pStrBuf->cbBufAllocated) + { + RTEXITCODE rcExit = vbcppStrBufGrow(pStrBuf, cchBuf + 2); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + pStrBuf->pszBuf[cchBuf++] = ch; + pStrBuf->pszBuf[cchBuf] = '\0'; + pStrBuf->cchBuf = cchBuf; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a string to the buffer. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param psz The string to append. + */ +static RTEXITCODE vbcppStrBufAppend(PVBCPPSTRBUF pStrBuf, const char *psz) +{ + return vbcppStrBufAppendN(pStrBuf, psz, strlen(psz)); +} + + +/** + * Gets the last char in the buffer. + * + * @returns Last character, 0 if empty. + * @param pStrBuf Pointer to the string buffer. + */ +static char vbcppStrBufLastCh(PVBCPPSTRBUF pStrBuf) +{ + if (!pStrBuf->cchBuf) + return '\0'; + return pStrBuf->pszBuf[pStrBuf->cchBuf - 1]; +} + + + + + + + +/* + * + * + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * + * + */ + + +/** + * Checks if the given character is a valid C identifier lead character. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCIdentifierLeadChar(char ch) +{ + return RT_C_IS_ALPHA(ch) + || ch == '_'; +} + + +/** + * Checks if the given character is a valid C identifier character. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCIdentifierChar(char ch) +{ + return RT_C_IS_ALNUM(ch) + || ch == '_'; +} + + + +/** + * + * @returns @c true if valid, @c false if not. Error message already displayed + * on failure. + * @param pThis The C preprocessor instance. + * @param pchIdentifier The start of the identifier to validate. + * @param cchIdentifier The length of the identifier. RTSTR_MAX if not + * known. + */ +static bool vbcppValidateCIdentifier(PVBCPP pThis, const char *pchIdentifier, size_t cchIdentifier) +{ + if (cchIdentifier == RTSTR_MAX) + cchIdentifier = strlen(pchIdentifier); + + if (cchIdentifier == 0) + { + vbcppErrorPos(pThis, pchIdentifier, "Zero length identifier"); + return false; + } + + if (!vbcppIsCIdentifierLeadChar(*pchIdentifier)) + { + vbcppErrorPos(pThis, pchIdentifier, "Bad lead chararacter in identifier: '%.*s'", cchIdentifier, pchIdentifier); + return false; + } + + for (size_t off = 1; off < cchIdentifier; off++) + { + if (!vbcppIsCIdentifierChar(pchIdentifier[off])) + { + vbcppErrorPos(pThis, pchIdentifier + off, "Illegal chararacter in identifier: '%.*s' (#%zu)", cchIdentifier, pchIdentifier, off + 1); + return false; + } + } + + return true; +} + +#if 0 + +/** + * Checks if the given character is valid C punctuation. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCPunctuationLeadChar(char ch) +{ + switch (ch) + { + case '!': + case '#': + case '%': + case '&': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '[': + case ']': + case '^': + case '{': + case '|': + case '}': + case '~': + return true; + default: + return false; + } +} + + +/** + * Checks if the given string start with valid C punctuation. + * + * @returns 0 if not, otherwise the length of the punctuation. + * @param pch The which start we should evaluate. + * @param cchMax The maximum string length. + */ +static size_t vbcppIsCPunctuationLeadChar(const char *psz, size_t cchMax) +{ + if (!cchMax) + return 0; + + switch (psz[0]) + { + case '!': + case '*': + case '/': + case '=': + case '^': + if (cchMax >= 2 && psz[1] == '=') + return 2; + return 1; + + case '#': + if (cchMax >= 2 && psz[1] == '#') + return 2; + return 1; + + case '%': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '>')) + return 2; + if (cchMax >= 2 && psz[1] == ':') + { + if (cchMax >= 4 && psz[2] == '%' && psz[3] == ':') + return 4; + return 2; + } + return 1; + + case '&': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '&')) + return 2; + return 1; + + case '(': + case ')': + case ',': + case '?': + case '[': + case ']': + case '{': + case '}': + return 1; + + case '+': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '+')) + return 2; + return 1; + + case '-': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '-' || psz[1] == '>')) + return 2; + return 1; + + case ':': + if (cchMax >= 2 && psz[1] == '>') + return 2; + return 1; + + case ';': + return 1; + + case '<': + if (cchMax >= 2 && psz[1] == '<') + { + if (cchMax >= 3 && psz[2] == '=') + return 3; + return 2; + } + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == ':' || psz[1] == '%')) + return 2; + return 1; + + case '.': + if (cchMax >= 3 && psz[1] == '.' && psz[2] == '.') + return 3; + return 1; + + case '>': + if (cchMax >= 2 && psz[1] == '>') + { + if (cchMax >= 3 && psz[2] == '=') + return 3; + return 2; + } + if (cchMax >= 2 && psz[1] == '=') + return 2; + return 1; + + case '|': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '|')) + return 2; + return 1; + + case '~': + return 1; + + default: + return 0; + } +} + +#endif + + + + + +/* + * + * + * Output + * Output + * Output + * Output + * Output + * + * + */ + + +/** + * Outputs a character. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param ch The character to output. + */ +static RTEXITCODE vbcppOutputCh(PVBCPP pThis, char ch) +{ + int rc = ScmStreamPutCh(&pThis->StrmOutput, ch); + if (RT_SUCCESS(rc)) + return RTEXITCODE_SUCCESS; + return vbcppError(pThis, "Output error: %Rrc", rc); +} + + +/** + * Outputs a string. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pch The string. + * @param cch The number of characters to write. + */ +static RTEXITCODE vbcppOutputWrite(PVBCPP pThis, const char *pch, size_t cch) +{ + int rc = ScmStreamWrite(&pThis->StrmOutput, pch, cch); + if (RT_SUCCESS(rc)) + return RTEXITCODE_SUCCESS; + return vbcppError(pThis, "Output error: %Rrc", rc); +} + + +static RTEXITCODE vbcppOutputComment(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, size_t cchOutputted, + size_t cchMinIndent) +{ + RT_NOREF_PV(cchMinIndent); /** @todo cchMinIndent */ + + size_t offCur = ScmStreamTell(pStrmInput); + if (offStart < offCur) + { + int rc = ScmStreamSeekAbsolute(pStrmInput, offStart); + AssertRCReturn(rc, vbcppError(pThis, "Input seek error: %Rrc", rc)); + + /* + * Use the same indent, if possible. + */ + size_t cchIndent = offStart - ScmStreamTellOffsetOfLine(pStrmInput, ScmStreamTellLine(pStrmInput)); + if (cchOutputted < cchIndent) + rc = ScmStreamPrintf(&pThis->StrmOutput, "%*s", cchIndent - cchOutputted, ""); + else + rc = ScmStreamPutCh(&pThis->StrmOutput, ' '); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "Output error: %Rrc", rc); + + /* + * Copy the bytes. + */ + while (ScmStreamTell(pStrmInput) < offCur) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + return vbcppError(pThis, "Input error: %Rrc", rc); + rc = ScmStreamPutCh(&pThis->StrmOutput, ch); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "Output error: %Rrc", rc); + } + } + + return RTEXITCODE_SUCCESS; +} + + + + + +/* + * + * + * Input + * Input + * Input + * Input + * Input + * + * + */ + + +#if 0 /* unused */ +/** + * Skips white spaces, including escaped new-lines. + * + * @param pStrmInput The input stream. + */ +static void vbcppProcessSkipWhiteAndEscapedEol(PSCMSTREAM pStrmInput) +{ + unsigned chPrev = ~(unsigned)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + chPrev = ch; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + else if (RT_C_IS_SPACE(ch)) + { + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); + Assert(ch == chPrev); + } + else + break; + } +} +#endif + + +/** + * Skips white spaces, escaped new-lines and multi line comments. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessSkipWhiteEscapedEolAndComments(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + unsigned chPrev = ~(unsigned)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch)) + { + /* Multi-line Comment? */ + if (ch != '/') + break; /* most definitely, not. */ + + size_t offSaved = ScmStreamTell(pStrmInput); + ScmStreamGetCh(pStrmInput); + if (ScmStreamPeekCh(pStrmInput) != '*') + { + ScmStreamSeekAbsolute(pStrmInput, offSaved); + break; /* no */ + } + + /* Skip to the end of the comment. */ + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '*') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '/') + break; + if (ch == ~(unsigned)0) + break; + } + } + if (ch == ~(unsigned)0) + return vbcppError(pThis, "unterminated multi-line comment"); + chPrev = '/'; + } + /* New line (also matched by RT_C_IS_SPACE). */ + else if (ch == '\r' || ch == '\n') + { + /* Stop if not escaped. */ + if (chPrev != '\\') + break; + chPrev = ch; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + /* Real space char. */ + else + { + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); + Assert(ch == chPrev); + } + } + return RTEXITCODE_SUCCESS; +} + + +/** + * Skips white spaces, escaped new-lines, and multi line comments, then checking + * that we're at the end of a line. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + unsigned ch = ScmStreamPeekCh(pStrmInput); + if ( ch != ~(unsigned)0 + && ch != '\r' + && ch != '\n') + rcExit = vbcppError(pThis, "Did not expected anything more on this line"); + } + return rcExit; +} + + +/** + * Skips white spaces. + * + * @returns The current location upon return. + * @param pStrmInput The input stream. + */ +static size_t vbcppProcessSkipWhite(PSCMSTREAM pStrmInput) +{ + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch) || ch == '\r' || ch == '\n') + break; + unsigned chCheck = ScmStreamGetCh(pStrmInput); + AssertBreak(chCheck == ch); + } + return ScmStreamTell(pStrmInput); +} + + +/** + * Looks for a left parenthesis in the input stream. + * + * Used during macro expansion. Will ignore comments, newlines and other + * whitespace. + * + * @retval true if found. The stream position at opening parenthesis. + * @retval false if not found. The stream position is unchanged. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static bool vbcppInputLookForLeftParenthesis(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + size_t offSaved = ScmStreamTell(pStrmInput); + /*RTEXITCODE rcExit =*/ vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + return true; + + int rc = ScmStreamSeekAbsolute(pStrmInput, offSaved); + AssertFatalRC(rc); + return false; +} + + +/** + * Skips input until the real end of the current directive line has been + * reached. + * + * This includes multiline comments starting on the same line + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param poffComment Where to note down the position of the final + * comment. Optional. + */ +static RTEXITCODE vbcppInputSkipToEndOfDirectiveLine(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t *poffComment) +{ + if (poffComment) + *poffComment = ~(size_t)0; + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + bool fInComment = false; + unsigned chPrev = 0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev == '\\') + { + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + continue; + } + if (!fInComment) + break; + /* The expression continues after multi-line comments. Cool. :-) */ + } + else if (!fInComment) + { + if (chPrev == '/' && ch == '*' ) + { + fInComment = true; + if (poffComment) + *poffComment = ScmStreamTell(pStrmInput) - 1; + } + else if (chPrev == '/' && ch == '/') + { + if (poffComment) + *poffComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + break; /* done */ + } + } + else if (ch == '/' && chPrev == '*') + fInComment = false; + + /* advance */ + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); Assert(ch == chPrev); + } + return rcExit; +} + + +/** + * Processes a multi-line comment. + * + * Must either string the comment or keep it. If the latter, we must refrain + * from replacing C-words in it. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessMultiLineComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + /* The open comment sequence. */ + ScmStreamGetCh(pStrmInput); /* '*' */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, "/*", 2); + + /* The comment.*/ + unsigned ch; + while ( rcExit == RTEXITCODE_SUCCESS + && (ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0 ) + { + if (ch == '*') + { + /* Closing sequence? */ + unsigned ch2 = ScmStreamPeekCh(pStrmInput); + if (ch2 == '/') + { + ScmStreamGetCh(pStrmInput); + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, "*/", 2); + break; + } + } + + if (ch == '\r' || ch == '\n') + { + if ( ( pThis->fKeepComments + && !pThis->fIf0Mode) + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput)) + rcExit = vbcppOutputCh(pThis, ch); + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + } + else if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, ch); + + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + return rcExit; +} + + +/** + * Processes a single line comment. + * + * Must either string the comment or keep it. If the latter, we must refrain + * from replacing C-words in it. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessOneLineComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + SCMEOL enmEol; + size_t cchLine; + const char *pszLine = ScmStreamGetLine(pStrmInput, &cchLine, &enmEol); Assert(pszLine); + pszLine--; cchLine++; /* unfetching the first slash. */ + for (;;) + { + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, pszLine, cchLine + enmEol); + else if ( !pThis->fIf0Mode + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput) ) + rcExit = vbcppOutputWrite(pThis, pszLine + cchLine, enmEol); + if (rcExit != RTEXITCODE_SUCCESS) + break; + if ( cchLine == 0 + || pszLine[cchLine - 1] != '\\') + break; + + pszLine = ScmStreamGetLine(pStrmInput, &cchLine, &enmEol); + if (!pszLine) + break; + } + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + return rcExit; +} + + +/** + * Processes a double quoted string. + * + * Must not replace any C-words in strings. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessStringLitteral(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, '"'); + if (rcExit == RTEXITCODE_SUCCESS) + { + bool fEscaped = false; + for (;;) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + { + rcExit = vbcppError(pThis, "Unterminated double quoted string"); + break; + } + + rcExit = vbcppOutputCh(pThis, ch); + if (rcExit != RTEXITCODE_SUCCESS) + break; + + if (ch == '"' && !fEscaped) + break; + fEscaped = !fEscaped && ch == '\\'; + } + } + return rcExit; +} + + +/** + * Processes a single quoted constant. + * + * Must not replace any C-words in character constants. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessCharacterConstant(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, '\''); + if (rcExit == RTEXITCODE_SUCCESS) + { + bool fEscaped = false; + for (;;) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + { + rcExit = vbcppError(pThis, "Unterminated singled quoted string"); + break; + } + + rcExit = vbcppOutputCh(pThis, ch); + if (rcExit != RTEXITCODE_SUCCESS) + break; + + if (ch == '\'' && !fEscaped) + break; + fEscaped = !fEscaped && ch == '\\'; + } + } + return rcExit; +} + + +/** + * Processes a integer or floating point number constant. + * + * Must not replace the type suffix. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param chFirst The first character. + */ +static RTEXITCODE vbcppProcessNumber(PVBCPP pThis, PSCMSTREAM pStrmInput, char chFirst) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, chFirst); + + unsigned ch; + while ( rcExit == RTEXITCODE_SUCCESS + && (ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if ( !vbcppIsCIdentifierChar(ch) + && ch != '.') + break; + + unsigned ch2 = ScmStreamGetCh(pStrmInput); + AssertBreakStmt(ch2 == ch, rcExit = vbcppError(pThis, "internal error")); + rcExit = vbcppOutputCh(pThis, ch); + } + + return rcExit; +} + + +/** + * Processes a identifier, possibly replacing it with a definition. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessIdentifier(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit; + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWordM1(pStrmInput, &cchDefine); + AssertReturn(pchDefine, vbcppError(pThis, "Internal error in ScmStreamCGetWordM1")); + + /* + * Does this look like a define we know? + */ + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, pchDefine, cchDefine); + if ( pMacro + && ( !pMacro->fFunction + || vbcppInputLookForLeftParenthesis(pThis, pStrmInput)) ) + { + /* + * Expand it. + */ + VBCPPMACROEXP ExpCtx; +#if 0 + ExpCtx.pMacroStack = NULL; +#endif + ExpCtx.pStrmInput = pStrmInput; + ExpCtx.papszArgs = NULL; + ExpCtx.cArgs = 0; + ExpCtx.cArgsAlloced = 0; + vbcppStrBufInit(&ExpCtx.StrBuf, pThis); + rcExit = vbcppStrBufAppendN(&ExpCtx.StrBuf, pchDefine, cchDefine); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandIt(pThis, &ExpCtx, 0 /* offset */, pMacro, cchDefine); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandReScan(pThis, &ExpCtx, kMacroReScanMode_Normal, NULL); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Insert it into the output stream. Make sure there is a + * whitespace following it. + */ + int rc = ScmStreamWrite(&pThis->StrmOutput, ExpCtx.StrBuf.pszBuf, ExpCtx.StrBuf.cchBuf); + if (RT_SUCCESS(rc)) + { + unsigned chAfter = ScmStreamPeekCh(pStrmInput); + if (chAfter != ~(unsigned)0 && !RT_C_IS_SPACE(chAfter)) + rcExit = vbcppOutputCh(pThis, ' '); + } + else + rcExit = vbcppError(pThis, "Output error: %Rrc", rc); + } + vbcppMacroExpandCleanup(&ExpCtx); + } + else + { + /* + * Not a macro or a function-macro name match but no invocation, just + * output the text unchanged. + */ + int rc = ScmStreamWrite(&pThis->StrmOutput, pchDefine, cchDefine); + if (RT_SUCCESS(rc)) + rcExit = RTEXITCODE_SUCCESS; + else + rcExit = vbcppError(pThis, "Output error: %Rrc", rc); + } + return rcExit; +} + + + + + + + +/* + * + * + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * + * + */ + + +/** + * Checks if a define exists. + * + * @returns true or false. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + */ +static bool vbcppMacroExists(PVBCPP pThis, const char *pszDefine, size_t cchDefine) +{ + return cchDefine > 0 + && VBCPP_BITMAP_IS_SET(pThis->bmDefined, *pszDefine) + && RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine) != NULL; +} + + +/** + * Looks up a define. + * + * @returns Pointer to the define if found, NULL if not. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + */ +static PVBCPPMACRO vbcppMacroLookup(PVBCPP pThis, const char *pszDefine, size_t cchDefine) +{ + if (!cchDefine) + return NULL; + if (!VBCPP_BITMAP_IS_SET(pThis->bmDefined, *pszDefine)) + return NULL; + return (PVBCPPMACRO)RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine); +} + + +static uint32_t vbcppMacroLookupArg(PVBCPPMACRO pMacro, const char *pchName, size_t cchName) +{ + Assert(cchName > 0); + + char const ch = *pchName; + for (uint32_t i = 0; i < pMacro->cArgs; i++) + if ( pMacro->papszArgs[i][0] == ch + && !strncmp(pMacro->papszArgs[i], pchName, cchName) + && pMacro->papszArgs[i][cchName] == '\0') + return i; + + if ( pMacro->fVarArg + && cchName == sizeof("__VA_ARGS__") - 1 + && !strncmp(pchName, "__VA_ARGS__", sizeof("__VA_ARGS__") - 1) ) + return pMacro->cArgs; + + return UINT32_MAX; +} + + +static RTEXITCODE vbcppMacroExpandReplace(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t off, size_t cchToReplace, + const char *pchReplacement, size_t cchReplacement) +{ + RT_NOREF_PV(pThis); + + /* + * Figure how much space we actually need. + * (Hope this whitespace stuff is correct...) + */ + bool const fLeadingSpace = off > 0 + && !RT_C_IS_SPACE(pExp->StrBuf.pszBuf[off - 1]); + bool const fTrailingSpace = off + cchToReplace < pExp->StrBuf.cchBuf + && !RT_C_IS_SPACE(pExp->StrBuf.pszBuf[off + cchToReplace]); + size_t const cchActualReplacement = fLeadingSpace + cchReplacement + fTrailingSpace; + + /* + * Adjust the buffer size and contents. + */ + if (cchActualReplacement > cchToReplace) + { + size_t const offMore = cchActualReplacement - cchToReplace; + + /* Ensure enough buffer space. */ + size_t cbMinBuf = offMore + pExp->StrBuf.cchBuf + 1; + RTEXITCODE rcExit = vbcppStrBufGrow(&pExp->StrBuf, cbMinBuf); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* Push the chars following the replacement area down to make room. */ + memmove(&pExp->StrBuf.pszBuf[off + cchToReplace + offMore], + &pExp->StrBuf.pszBuf[off + cchToReplace], + pExp->StrBuf.cchBuf - off - cchToReplace + 1); + pExp->StrBuf.cchBuf += offMore; + + } + else if (cchActualReplacement < cchToReplace) + { + size_t const offLess = cchToReplace - cchActualReplacement; + + /* Pull the chars following the replacement area up. */ + memmove(&pExp->StrBuf.pszBuf[off + cchToReplace - offLess], + &pExp->StrBuf.pszBuf[off + cchToReplace], + pExp->StrBuf.cchBuf - off - cchToReplace + 1); + pExp->StrBuf.cchBuf -= offLess; + } + + /* + * Insert the replacement string. + */ + char *pszCur = &pExp->StrBuf.pszBuf[off]; + if (fLeadingSpace) + *pszCur++ = ' '; + memcpy(pszCur, pchReplacement, cchReplacement); + if (fTrailingSpace) + *pszCur++ = ' '; + + Assert(strlen(pExp->StrBuf.pszBuf) == pExp->StrBuf.cchBuf); + + return RTEXITCODE_SUCCESS; +} + + +static unsigned vbcppMacroExpandPeekCh(PVBCPPMACROEXP pExp, size_t *poff) +{ + size_t off = *poff; + if (off >= pExp->StrBuf.cchBuf) + return pExp->pStrmInput ? ScmStreamPeekCh(pExp->pStrmInput) : ~(unsigned)0; + return pExp->StrBuf.pszBuf[off]; +} + + +static unsigned vbcppMacroExpandGetCh(PVBCPPMACROEXP pExp, size_t *poff) +{ + size_t off = *poff; + if (off >= pExp->StrBuf.cchBuf) + return pExp->pStrmInput ? ScmStreamGetCh(pExp->pStrmInput) : ~(unsigned)0; + *poff = off + 1; + return pExp->StrBuf.pszBuf[off]; +} + + +static RTEXITCODE vbcppMacroExpandSkipEolEx(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff, unsigned chFirst) +{ + if (chFirst == '\r') + { + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + if (ch2 == '\n') + { + ch2 = ScmStreamGetCh(pExp->pStrmInput); + AssertReturn(ch2 == '\n', vbcppError(pThis, "internal error")); + } + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandSkipEol(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '\r' || ch == '\n', vbcppError(pThis, "internal error")); + return vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); +} + + +static RTEXITCODE vbcppMacroExpandSkipCommentLine(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '/', vbcppError(pThis, "Internal error - expected '/' got '%c'", ch)); + + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + RTEXITCODE rcExit = vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (chPrev != '\\') + break; + } + + chPrev = ch; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandSkipComment(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '*', vbcppError(pThis, "Internal error - expected '*' got '%c'", ch)); + + unsigned chPrev2 = 0; + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { + if (ch == '/' && chPrev == '*') + break; + + if (ch == '\r' || ch == '\n') + { + RTEXITCODE rcExit = vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (chPrev == '\\') + { + chPrev = chPrev2; /* for line splicing */ + continue; + } + } + + chPrev2 = chPrev; + chPrev = ch; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandGrowArgArray(PVBCPP pThis, PVBCPPMACROEXP pExp, uint32_t cMinArgs) +{ + if (cMinArgs > pExp->cArgsAlloced) + { + void *pv = RTMemRealloc(pExp->papszArgs, cMinArgs * sizeof(char *)); + if (!pv) + return vbcppError(pThis, "out of memory"); + pExp->papszArgs = (char **)pv; + pExp->cArgsAlloced = cMinArgs; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandAddEmptyParameter(PVBCPP pThis, PVBCPPMACROEXP pExp) +{ + RTEXITCODE rcExit = vbcppMacroExpandGrowArgArray(pThis, pExp, pExp->cArgs + 1); + if (rcExit == RTEXITCODE_SUCCESS) + { + char *pszArg = (char *)RTMemAllocZ(1); + if (pszArg) + pExp->papszArgs[pExp->cArgs++] = pszArg; + else + rcExit = vbcppError(pThis, "out of memory"); + } + return rcExit; +} + + +static RTEXITCODE vbcppMacroExpandGatherParameters(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff, uint32_t cArgsHint) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + /* + * Free previous argument values. + */ + while (pExp->cArgs > 0) + { + RTMemFree(pExp->papszArgs[--pExp->cArgs]); + pExp->papszArgs[pExp->cArgs] = NULL; + } + + /* + * The current character should be an opening parenthsis. + */ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + if (ch != '(') + return vbcppError(pThis, "Internal error - expected '(', found '%c' (#x)", ch, ch); + + /* + * Parse the argument list. + */ + char chQuote = 0; + size_t cbArgAlloc = 0; + size_t cchArg = 0; + char *pszArg = NULL; + size_t cParentheses = 1; + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { +/** @todo check for '#directives'! */ + if (ch == ')' && !chQuote) + { + Assert(cParentheses >= 1); + cParentheses--; + + /* The end? */ + if (!cParentheses) + { + if (cchArg) + while (cchArg > 0 && RT_C_IS_SPACE(pszArg[cchArg - 1])) + pszArg[--cchArg] = '\0'; + else if (pExp->cArgs || cArgsHint > 0) + rcExit = vbcppMacroExpandAddEmptyParameter(pThis, pExp); + break; + } + } + else if (ch == '(' && !chQuote) + cParentheses++; + else if (ch == ',' && cParentheses == 1 && !chQuote) + { + /* End of one argument, start of the next. */ + if (cchArg) + while (cchArg > 0 && RT_C_IS_SPACE(pszArg[cchArg - 1])) + pszArg[--cchArg] = '\0'; + else + { + rcExit = vbcppMacroExpandAddEmptyParameter(pThis, pExp); + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + + cbArgAlloc = 0; + cchArg = 0; + pszArg = NULL; + continue; + } + else if (ch == '/' && !chQuote) + { + /* Comment? */ + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + /** @todo This ain't right wrt line splicing. */ + if (ch2 == '/' || ch == '*') + { + if (ch2 == '/') + rcExit = vbcppMacroExpandSkipCommentLine(pThis, pExp, poff); + else + rcExit = vbcppMacroExpandSkipComment(pThis, pExp, poff); + if (rcExit != RTEXITCODE_SUCCESS) + break; + continue; + } + } + else if (ch == '"') + { + if (!chQuote) + chQuote = '"'; + else if (chPrev != '\\') + chQuote = 0; + } + else if (ch == '\'') + { + if (!chQuote) + chQuote = '\''; + else if (chPrev != '\\') + chQuote = 0; + } + else if (ch == '\\') + { + /* Splice lines? */ + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + if (ch2 == '\r' || ch2 == '\n') + { + rcExit = vbcppMacroExpandSkipEol(pThis, pExp, poff); + if (rcExit != RTEXITCODE_SUCCESS) + break; + continue; + } + } + else if (cchArg == 0 && RT_C_IS_SPACE(ch)) + continue; /* ignore spaces leading up to an argument value */ + + /* Append the character to the argument value, adding the argument + to the output array if it's first character in it. */ + if (cchArg + 1 >= cbArgAlloc) + { + /* Add argument to the vector. */ + if (!cchArg) + { + rcExit = vbcppMacroExpandGrowArgArray(pThis, pExp, RT_MAX(pExp->cArgs + 1, cArgsHint)); + if (rcExit != RTEXITCODE_SUCCESS) + break; + pExp->papszArgs[pExp->cArgs++] = pszArg; + } + + /* Resize the argument value buffer. */ + cbArgAlloc = cbArgAlloc ? cbArgAlloc * 2 : 16; + pszArg = (char *)RTMemRealloc(pszArg, cbArgAlloc); + if (!pszArg) + { + rcExit = vbcppError(pThis, "out of memory"); + break; + } + pExp->papszArgs[pExp->cArgs - 1] = pszArg; + } + + pszArg[cchArg++] = ch; + pszArg[cchArg] = '\0'; + } + + /* + * Check that we're leaving on good terms. + */ + if (rcExit == RTEXITCODE_SUCCESS) + { + if (cParentheses) + rcExit = vbcppError(pThis, "Missing ')'"); + } + + return rcExit; +} + + +/** + * Expands the arguments referenced in the macro value. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param pMacro The macro. Must be a function macro. + * @param pStrBuf String buffer containing the result. The caller + * should initialize and destroy this! + */ +static RTEXITCODE vbcppMacroExpandValueWithArguments(PVBCPP pThis, PVBCPPMACROEXP pExp, PVBCPPMACRO pMacro, + PVBCPPSTRBUF pStrBuf) +{ + Assert(pMacro->fFunction); + + /* + * Empty? + */ + if ( !pMacro->cchValue + || (pMacro->cchValue == 1 && pMacro->szValue[0] == '#')) + return RTEXITCODE_SUCCESS; + + /* + * Parse the value. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + const char *pszSrc = pMacro->szValue; + const char *pszSrcSeq; + char ch; + while ((ch = *pszSrc++) != '\0') + { + Assert(ch != '\r'); Assert(ch != '\n'); /* probably not true atm. */ + if (ch == '#') + { + if (*pszSrc == '#') + { + /* Concatenate operator. */ + rcExit = vbcppError(pThis, "The '##' operatore is not yet implemented"); + } + else + { + /* Stringify macro argument. */ + rcExit = vbcppError(pThis, "The '#' operatore is not yet implemented"); + } + return rcExit; + } + else if (ch == '"') + { + /* String litteral. */ + pszSrcSeq = pszSrc - 1; + while ((ch = *pszSrc++) != '"') + { + if (ch == '\\') + ch = *pszSrc++; + if (ch == '\0') + { + rcExit = vbcppError(pThis, "String litteral is missing closing quote (\")."); + break; + } + } + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (ch == '\'') + { + /* Character constant. */ + pszSrcSeq = pszSrc - 1; + while ((ch = *pszSrc++) != '\'') + { + if (ch == '\\') + ch = *pszSrc++; + if (ch == '\0') + { + rcExit = vbcppError(pThis, "Character constant is missing closing quote (')."); + break; + } + } + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (RT_C_IS_DIGIT(ch)) + { + /* Process numerical constants correctly (i.e. don't mess with the suffix). */ + pszSrcSeq = pszSrc - 1; + while ( (ch = *pszSrc) != '\0' + && ( vbcppIsCIdentifierChar(ch) + || ch == '.') ) + pszSrc++; + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (RT_C_IS_SPACE(ch)) + { + /* join spaces */ + if (RT_C_IS_SPACE(vbcppStrBufLastCh(pStrBuf))) + continue; + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + else if (vbcppIsCIdentifierLeadChar(ch)) + { + /* Something we should replace? */ + pszSrcSeq = pszSrc - 1; + while ( (ch = *pszSrc) != '\0' + && vbcppIsCIdentifierChar(ch)) + pszSrc++; + size_t cchDefine = pszSrc - pszSrcSeq; + uint32_t iArg; + if ( VBCPP_BITMAP_IS_SET(pMacro->bmArgs, *pszSrcSeq) + && (iArg = vbcppMacroLookupArg(pMacro, pszSrcSeq, cchDefine)) != UINT32_MAX) + { + /** @todo check out spaces here! */ + if (iArg < pMacro->cArgs) + { + Assert(iArg < pExp->cArgs); + rcExit = vbcppStrBufAppend(pStrBuf, pExp->papszArgs[iArg]); + if (*pExp->papszArgs[iArg] != '\0' && rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ' '); + } + else + { + /* __VA_ARGS__ */ + if (iArg < pExp->cArgs) + { + for (;;) + { + rcExit = vbcppStrBufAppend(pStrBuf, pExp->papszArgs[iArg]); + if (rcExit != RTEXITCODE_SUCCESS) + break; + iArg++; + if (iArg >= pExp->cArgs) + break; + rcExit = vbcppStrBufAppendCh(pStrBuf, ','); + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + } + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ' '); + } + } + /* Not an argument needing replacing. */ + else + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, cchDefine); + } + else + { + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + } + + return rcExit; +} + + + +/** + * Expands the given macro. + * + * Caller already checked if a function macro should be expanded, i.e. whether + * there is a parameter list. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param offMacro Offset into the expansion buffer of the macro + * invocation. + * @param pMacro The macro. + * @param offParameters The start of the parameter list if applicable. + * Ignored if not function macro. If the + * parameter list starts at the current stream + * position shall be at the end of the expansion + * buffer. + */ +static RTEXITCODE vbcppMacroExpandIt(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offMacro, PVBCPPMACRO pMacro, + size_t offParameters) +{ + RTEXITCODE rcExit; + Assert(offMacro + pMacro->Core.cchString <= pExp->StrBuf.cchBuf); + Assert(!pMacro->fExpanding); + + /* + * Function macros are kind of difficult... + */ + if (pMacro->fFunction) + { + rcExit = vbcppMacroExpandGatherParameters(pThis, pExp, &offParameters, pMacro->cArgs + pMacro->fVarArg); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (pExp->cArgs > pMacro->cArgs && !pMacro->fVarArg) + rcExit = vbcppError(pThis, "Too many arguments to macro '%s' - found %u, expected %u", + pMacro->Core.pszString, pExp->cArgs, pMacro->cArgs); + else if (pExp->cArgs < pMacro->cArgs) + rcExit = vbcppError(pThis, "Too few arguments to macro '%s' - found %u, expected %u", + pMacro->Core.pszString, pExp->cArgs, pMacro->cArgs); + } + if (rcExit == RTEXITCODE_SUCCESS) + { + VBCPPSTRBUF ValueBuf; + vbcppStrBufInit(&ValueBuf, pThis); + rcExit = vbcppMacroExpandValueWithArguments(pThis, pExp, pMacro, &ValueBuf); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandReplace(pThis, pExp, offMacro, offParameters - offMacro, + ValueBuf.pszBuf, ValueBuf.cchBuf); + vbcppStrBufDelete(&ValueBuf); + } + } + /* + * Object-like macros are easy. :-) + */ + else + rcExit = vbcppMacroExpandReplace(pThis, pExp, offMacro, pMacro->Core.cchString, pMacro->szValue, pMacro->cchValue); + if (rcExit == RTEXITCODE_SUCCESS) + { +#if 0 /* wrong */ + /* + * Push the macro onto the stack. + */ + pMacro->fExpanding = true; + pMacro->pUpExpanding = pExp->pMacroStack; + pExp->pMacroStack = pMacro; +#endif + } + + return rcExit; +} + + +/** + * Looks for a left parenthesis in the macro expansion buffer and the input + * stream. + * + * @retval true if found. The stream position at opening parenthesis. + * @retval false if not found. The stream position is unchanged. + * + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param poff The current offset in the expansion context. + * Will be updated on success. + * + * @sa vbcppInputLookForLeftParenthesis + */ +static bool vbcppMacroExpandLookForLeftParenthesis(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + /* + * Search the buffer first. (No comments there.) + */ + size_t off = *poff; + while (off < pExp->StrBuf.cchBuf) + { + char ch = pExp->StrBuf.pszBuf[off]; + if (!RT_C_IS_SPACE(ch)) + { + if (ch == '(') + { + *poff = off; + return true; + } + return false; + } + off++; + } + + /* + * Reached the end of the buffer, continue searching in the stream. + */ + PSCMSTREAM pStrmInput = pExp->pStrmInput; + size_t offSaved = ScmStreamTell(pStrmInput); + /*RTEXITCODE rcExit = */ vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + { + *poff = pExp->StrBuf.cchBuf; + return true; + } + + int rc = ScmStreamSeekAbsolute(pStrmInput, offSaved); + AssertFatalRC(rc); + return false; +} + + +/** + * Implements the 'defined' unary operator for \#if and \#elif expressions. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param offStart The expansion buffer offset where the 'defined' + * occurs. + * @param poff Where to store the offset at which the re-scan + * shall resume upon return. + */ +static RTEXITCODE vbcppMacroExpandDefinedOperator(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offStart, size_t *poff) +{ + Assert(!pExp->pStrmInput); /* offset usage below. */ + + /* + * Skip white space. + */ + unsigned ch; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!RT_C_IS_SPACE(ch)) + break; + bool const fWithParenthesis = ch == '('; + if (fWithParenthesis) + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!RT_C_IS_SPACE(ch)) + break; + + /* + * Macro identifier. + */ + if (!vbcppIsCIdentifierLeadChar(ch)) + return vbcppError(pThis, "Expected macro name after 'defined' operator"); + + size_t const offDefine = *poff - 1; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!vbcppIsCIdentifierChar(ch)) + break; + size_t const cchDefine = *poff - offDefine - 1; + + /* + * Check for closing parenthesis. + */ + if (fWithParenthesis) + { + while (RT_C_IS_SPACE(ch)) + ch = vbcppMacroExpandGetCh(pExp, poff); + if (ch != ')') + return vbcppError(pThis, "Expected closing parenthesis after macro name"); + } + + /* + * Do the job. + */ + const char *pszResult = vbcppMacroExists(pThis, &pExp->StrBuf.pszBuf[offDefine], cchDefine) + ? "1" : "0"; + RTEXITCODE rcExit = vbcppMacroExpandReplace(pThis, pExp, offStart, *poff - offStart, pszResult, 1); + *poff = offStart + 1; + return rcExit; +} + + +/** + * Re-scan the expanded macro. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param enmMode The re-scan mode. + * @param pcReplacements Where to return the number of replacements + * performed. Optional. + */ +static RTEXITCODE vbcppMacroExpandReScan(PVBCPP pThis, PVBCPPMACROEXP pExp, VBCPPMACRORESCANMODE enmMode, size_t *pcReplacements) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + size_t cReplacements = 0; + size_t off = 0; + unsigned ch; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandGetCh(pExp, &off)) != ~(unsigned)0) + { + /* + * String litteral or character constant. + */ + if (ch == '\'' || ch == '"') + { + unsigned const chEndQuote = ch; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandGetCh(pExp, &off)) != ~(unsigned)0) + { + if (ch == '\\') + { + ch = vbcppMacroExpandGetCh(pExp, &off); + if (ch == ~(unsigned)0) + break; + } + else if (ch == chEndQuote) + break; + } + if (ch == ~(unsigned)0) + return vbcppError(pThis, "Missing end quote (%c)", chEndQuote); + } + /* + * Number constant. + */ + else if ( RT_C_IS_DIGIT(ch) + || ( ch == '.' + && off + 1 < pExp->StrBuf.cchBuf + && RT_C_IS_DIGIT(vbcppMacroExpandPeekCh(pExp, &off)) + ) + ) + { + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandPeekCh(pExp, &off)) != ~(unsigned)0 + && vbcppIsCIdentifierChar(ch) ) + vbcppMacroExpandGetCh(pExp, &off); + } + /* + * Something that can be replaced? + */ + else if (vbcppIsCIdentifierLeadChar(ch)) + { + size_t offDefine = off - 1; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandPeekCh(pExp, &off)) != ~(unsigned)0 + && vbcppIsCIdentifierChar(ch) ) + vbcppMacroExpandGetCh(pExp, &off); + size_t cchDefine = off - offDefine; + + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, &pExp->StrBuf.pszBuf[offDefine], cchDefine); + if ( pMacro + && ( !pMacro->fFunction + || vbcppMacroExpandLookForLeftParenthesis(pThis, pExp, &off)) ) + { + cReplacements++; + rcExit = vbcppMacroExpandIt(pThis, pExp, offDefine, pMacro, off); + off = offDefine; + } + else + { + if ( !pMacro + && enmMode == kMacroReScanMode_Expression + && cchDefine == sizeof("defined") - 1 + && !strncmp(&pExp->StrBuf.pszBuf[offDefine], "defined", cchDefine)) + { + cReplacements++; + rcExit = vbcppMacroExpandDefinedOperator(pThis, pExp, offDefine, &off); + } + else + off = offDefine + cchDefine; + } + } + else + { + Assert(RT_C_IS_SPACE(ch) || RT_C_IS_PUNCT(ch)); + Assert(ch != '\r' && ch != '\n'); + } + } + + if (pcReplacements) + *pcReplacements = cReplacements; + return rcExit; +} + + +/** + * Cleans up the expansion context. + * + * This involves clearing VBCPPMACRO::fExpanding and VBCPPMACRO::pUpExpanding, + * and freeing the memory resources associated with the expansion context. + * + * @param pExp The expansion context. + */ +static void vbcppMacroExpandCleanup(PVBCPPMACROEXP pExp) +{ +#if 0 + while (pExp->pMacroStack) + { + PVBCPPMACRO pMacro = pExp->pMacroStack; + pExp->pMacroStack = pMacro->pUpExpanding; + + pMacro->fExpanding = false; + pMacro->pUpExpanding = NULL; + } +#endif + + while (pExp->cArgs > 0) + { + RTMemFree(pExp->papszArgs[--pExp->cArgs]); + pExp->papszArgs[pExp->cArgs] = NULL; + } + + RTMemFree(pExp->papszArgs); + pExp->papszArgs = NULL; + + vbcppStrBufDelete(&pExp->StrBuf); +} + + + +/** + * Frees a define. + * + * @returns VINF_SUCCESS (used when called by RTStrSpaceDestroy) + * @param pStr Pointer to the VBCPPMACRO::Core member. + * @param pvUser Unused. + */ +static DECLCALLBACK(int) vbcppMacroFree(PRTSTRSPACECORE pStr, void *pvUser) +{ + RTMemFree(pStr); + NOREF(pvUser); + return VINF_SUCCESS; +} + + +/** + * Removes a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name, no argument list or anything. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + * @param fExplicitUndef Explicit undefinition, that is, in a selective + * preprocessing run it will evaluate to undefined. + */ +static RTEXITCODE vbcppMacroUndef(PVBCPP pThis, const char *pszDefine, size_t cchDefine, bool fExplicitUndef) +{ + PRTSTRSPACECORE pHit = RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine); + if (pHit) + { + RTStrSpaceRemove(&pThis->StrSpace, pHit->pszString); + vbcppMacroFree(pHit, NULL); + } + + if (fExplicitUndef) + { + if (cchDefine == RTSTR_MAX) + cchDefine = strlen(pszDefine); + + PRTSTRSPACECORE pStr = (PRTSTRSPACECORE)RTMemAlloc(sizeof(*pStr) + cchDefine + 1); + if (!pStr) + return vbcppError(pThis, "out of memory"); + char *pszDst = (char *)(pStr + 1); + pStr->pszString = pszDst; + memcpy(pszDst, pszDefine, cchDefine); + pszDst[cchDefine] = '\0'; + if (!RTStrSpaceInsert(&pThis->UndefStrSpace, pStr)) + RTMemFree(pStr); + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Inserts a define (rejecting and freeing it in some case). + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pMacro The define to insert. + */ +static RTEXITCODE vbcppMacroInsert(PVBCPP pThis, PVBCPPMACRO pMacro) +{ + /* + * Reject illegal macro names. + */ + if (!strcmp(pMacro->Core.pszString, "defined")) + { + RTEXITCODE rcExit = vbcppError(pThis, "Cannot use '%s' as a macro name", pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + return rcExit; + } + + /* + * Ignore in source-file defines when doing selective preprocessing. + */ + if ( !pThis->fRespectSourceDefines + && !pMacro->fCmdLine) + { + /* Ignore*/ + vbcppMacroFree(&pMacro->Core, NULL); + return RTEXITCODE_SUCCESS; + } + + /* + * Insert it and update the lead character hint bitmap. + */ + if (RTStrSpaceInsert(&pThis->StrSpace, &pMacro->Core)) + VBCPP_BITMAP_SET(pThis->bmDefined, *pMacro->Core.pszString); + else + { + /* + * Duplicate. When doing selective D preprocessing, let the command + * line take precendece. + */ + PVBCPPMACRO pOld = (PVBCPPMACRO)RTStrSpaceGet(&pThis->StrSpace, pMacro->Core.pszString); Assert(pOld); + if ( pThis->fAllowRedefiningCmdLineDefines + || pMacro->fCmdLine == pOld->fCmdLine) + { + if (pMacro->fCmdLine) + RTMsgWarning("Redefining '%s'", pMacro->Core.pszString); + + RTStrSpaceRemove(&pThis->StrSpace, pOld->Core.pszString); + vbcppMacroFree(&pOld->Core, NULL); + + bool fRc = RTStrSpaceInsert(&pThis->StrSpace, &pMacro->Core); + Assert(fRc); NOREF(fRc); + } + else + { + RTMsgWarning("Ignoring redefinition of '%s'", pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + } + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Adds a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name, no parameter list. + * @param cchDefine The length of the name. + * @param pszParams The parameter list. + * @param cchParams The length of the parameter list. + * @param pszValue The value. + * @param cchDefine The length of the value. + * @param fCmdLine Set if originating on the command line. + */ +static RTEXITCODE vbcppMacroAddFn(PVBCPP pThis, const char *pszDefine, size_t cchDefine, + const char *pszParams, size_t cchParams, + const char *pszValue, size_t cchValue, + bool fCmdLine) + +{ + Assert(RTStrNLen(pszDefine, cchDefine) == cchDefine); + Assert(RTStrNLen(pszParams, cchParams) == cchParams); + Assert(RTStrNLen(pszValue, cchValue) == cchValue); + + /* + * Determin the number of arguments and how much space their names + * requires. Performing syntax validation while parsing. + */ + uint32_t cchArgNames = 0; + uint32_t cArgs = 0; + for (size_t off = 0; off < cchParams; off++) + { + /* Skip blanks and maybe one comma. */ + bool fIgnoreComma = cArgs != 0; + while (off < cchParams) + { + if (!RT_C_IS_SPACE(pszParams[off])) + { + if (pszParams[off] != ',' || !fIgnoreComma) + { + if (vbcppIsCIdentifierLeadChar(pszParams[off])) + break; + /** @todo variadic macros. */ + return vbcppErrorPos(pThis, &pszParams[off], "Unexpected character"); + } + fIgnoreComma = false; + } + off++; + } + if (off >= cchParams) + break; + + /* Found and argument. First character is already validated. */ + cArgs++; + cchArgNames += 2; + off++; + while ( off < cchParams + && vbcppIsCIdentifierChar(pszParams[off])) + off++, cchArgNames++; + } + + /* + * Allocate a structure. + */ + size_t cbDef = RT_UOFFSETOF_DYN(VBCPPMACRO, szValue[cchValue + 1 + cchDefine + 1 + cchArgNames]) + + sizeof(const char *) * cArgs; + cbDef = RT_ALIGN_Z(cbDef, sizeof(const char *)); + PVBCPPMACRO pMacro = (PVBCPPMACRO)RTMemAlloc(cbDef); + if (!pMacro) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory"); + + char *pszDst = &pMacro->szValue[cchValue + 1]; + pMacro->Core.pszString = pszDst; + memcpy(pszDst, pszDefine, cchDefine); + pszDst += cchDefine; + *pszDst++ = '\0'; + pMacro->fFunction = true; + pMacro->fVarArg = false; + pMacro->fCmdLine = fCmdLine; + pMacro->fExpanding = false; + pMacro->cArgs = cArgs; + pMacro->papszArgs = (const char **)((uintptr_t)pMacro + cbDef - sizeof(const char *) * cArgs); + VBCPP_BITMAP_EMPTY(pMacro->bmArgs); + pMacro->cchValue = cchValue; + memcpy(pMacro->szValue, pszValue, cchValue); + pMacro->szValue[cchValue] = '\0'; + + /* + * Set up the arguments. + */ + uint32_t iArg = 0; + for (size_t off = 0; off < cchParams; off++) + { + /* Skip blanks and maybe one comma. */ + bool fIgnoreComma = cArgs != 0; + while (off < cchParams) + { + if (!RT_C_IS_SPACE(pszParams[off])) + { + if (pszParams[off] != ',' || !fIgnoreComma) + break; + fIgnoreComma = false; + } + off++; + } + if (off >= cchParams) + break; + + /* Found and argument. First character is already validated. */ + VBCPP_BITMAP_SET(pMacro->bmArgs, pszParams[off]); + pMacro->papszArgs[iArg] = pszDst; + do + { + *pszDst++ = pszParams[off++]; + } while ( off < cchParams + && vbcppIsCIdentifierChar(pszParams[off])); + *pszDst++ = '\0'; + iArg++; + } + Assert((uintptr_t)pszDst <= (uintptr_t)pMacro->papszArgs); + + return vbcppMacroInsert(pThis, pMacro); +} + + +/** + * Adds a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + * @param pszValue The value. + * @param cchDefine The length of the value. RTSTR_MAX is ok. + * @param fCmdLine Set if originating on the command line. + */ +static RTEXITCODE vbcppMacroAdd(PVBCPP pThis, const char *pszDefine, size_t cchDefine, + const char *pszValue, size_t cchValue, bool fCmdLine) +{ + /* + * We need the lengths. Trim the input. + */ + if (cchDefine == RTSTR_MAX) + cchDefine = strlen(pszDefine); + while (cchDefine > 0 && RT_C_IS_SPACE(*pszDefine)) + pszDefine++, cchDefine--; + while (cchDefine > 0 && RT_C_IS_SPACE(pszDefine[cchDefine - 1])) + cchDefine--; + if (!cchDefine) + return vbcppErrorPos(pThis, pszDefine, "The define has no name"); + + if (cchValue == RTSTR_MAX) + cchValue = strlen(pszValue); + while (cchValue > 0 && RT_C_IS_SPACE(*pszValue)) + pszValue++, cchValue--; + while (cchValue > 0 && RT_C_IS_SPACE(pszValue[cchValue - 1])) + cchValue--; + + /* + * Arguments make the job a bit more annoying. Handle that elsewhere + */ + const char *pszParams = (const char *)memchr(pszDefine, '(', cchDefine); + if (pszParams) + { + size_t cchParams = pszDefine + cchDefine - pszParams; + cchDefine -= cchParams; + if (!vbcppValidateCIdentifier(pThis, pszDefine, cchDefine)) + return RTEXITCODE_FAILURE; + if (pszParams[cchParams - 1] != ')') + return vbcppErrorPos(pThis, pszParams + cchParams - 1, "Missing closing parenthesis"); + pszParams++; + cchParams -= 2; + return vbcppMacroAddFn(pThis, pszDefine, cchDefine, pszParams, cchParams, pszValue, cchValue, fCmdLine); + } + + /* + * Simple define, no arguments. + */ + if (!vbcppValidateCIdentifier(pThis, pszDefine, cchDefine)) + return RTEXITCODE_FAILURE; + + PVBCPPMACRO pMacro = (PVBCPPMACRO)RTMemAlloc(RT_UOFFSETOF_DYN(VBCPPMACRO, szValue[cchValue + 1 + cchDefine + 1])); + if (!pMacro) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory"); + + pMacro->Core.pszString = &pMacro->szValue[cchValue + 1]; + memcpy((char *)pMacro->Core.pszString, pszDefine, cchDefine); + ((char *)pMacro->Core.pszString)[cchDefine] = '\0'; + pMacro->fFunction = false; + pMacro->fVarArg = false; + pMacro->fCmdLine = fCmdLine; + pMacro->fExpanding = false; + pMacro->cArgs = 0; + pMacro->papszArgs = NULL; + VBCPP_BITMAP_EMPTY(pMacro->bmArgs); + pMacro->cchValue = cchValue; + memcpy(pMacro->szValue, pszValue, cchValue); + pMacro->szValue[cchValue] = '\0'; + + return vbcppMacroInsert(pThis, pMacro); +} + + +/** + * Tries to convert a define into an inline D constant. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pMacro The macro. + */ +static RTEXITCODE vbcppMacroTryConvertToInlineD(PVBCPP pThis, PVBCPPMACRO pMacro) +{ + AssertReturn(pMacro, vbcppError(pThis, "Internal error")); + if (pMacro->fFunction) + return RTEXITCODE_SUCCESS; + + /* + * Do some simple macro resolving. (Mostly to make x86.h work.) + */ + const char *pszDefine = pMacro->Core.pszString; + const char *pszValue = pMacro->szValue; + size_t cchValue = pMacro->cchValue; + + unsigned i = 0; + PVBCPPMACRO pMacro2; + while ( i < 10 + && cchValue > 0 + && vbcppIsCIdentifierLeadChar(*pszValue) + && (pMacro2 = vbcppMacroLookup(pThis, pszValue, cchValue)) != NULL + && !pMacro2->fFunction ) + { + pszValue = pMacro2->szValue; + cchValue = pMacro2->cchValue; + i++; + } + + if (!pMacro->cchValue) + return RTEXITCODE_SUCCESS; + + + /* + * A lone value? + */ + ssize_t cch = 0; + uint64_t u64; + char *pszNext; + int rc = RTStrToUInt64Ex(pszValue, &pszNext, 0, &u64); + if (RT_SUCCESS(rc)) + { + if ( rc == VWRN_TRAILING_SPACES + || rc == VWRN_NEGATIVE_UNSIGNED + || rc == VWRN_NUMBER_TOO_BIG) + return RTEXITCODE_SUCCESS; + const char *pszType; + if (rc == VWRN_TRAILING_CHARS) + { + if (!strcmp(pszNext, "u") || !strcmp(pszNext, "U")) + pszType = "uint32_t"; + else if (!strcmp(pszNext, "ul") || !strcmp(pszNext, "UL")) + pszType = "uintptr_t"; + else if (!strcmp(pszNext, "ull") || !strcmp(pszNext, "ULL")) + pszType = "uint64_t"; + else + pszType = NULL; + } + else if (u64 <= UINT8_MAX) + pszType = "uint8_t"; + else if (u64 <= UINT16_MAX) + pszType = "uint16_t"; + else if (u64 <= UINT32_MAX) + pszType = "uint32_t"; + else + pszType = "uint64_t"; + if (!pszType) + return RTEXITCODE_SUCCESS; + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline %s %s = %.*s;\n", + pszType, pszDefine, pszNext - pszValue, pszValue); + } + /* + * A value wrapped in a constant macro? + */ + else if ( (pszNext = (char *)strchr(pszValue, '(')) != NULL + && pszValue[cchValue - 1] == ')' ) + { + size_t cchPrefix = pszNext - pszValue; + size_t cchInnerValue = cchValue - cchPrefix - 2; + const char *pchInnerValue = &pszValue[cchPrefix + 1]; + while (cchInnerValue > 0 && RT_C_IS_SPACE(*pchInnerValue)) + cchInnerValue--, pchInnerValue++; + while (cchInnerValue > 0 && RT_C_IS_SPACE(pchInnerValue[cchInnerValue - 1])) + cchInnerValue--; + if (!cchInnerValue || !RT_C_IS_XDIGIT(*pchInnerValue)) + return RTEXITCODE_SUCCESS; + + rc = RTStrToUInt64Ex(pchInnerValue, &pszNext, 0, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_TRAILING_SPACES + || rc == VWRN_NEGATIVE_UNSIGNED + || rc == VWRN_NUMBER_TOO_BIG) + return RTEXITCODE_SUCCESS; + + const char *pszType; +#define MY_MATCH_STR(a_sz) (sizeof(a_sz) - 1 == cchPrefix && !strncmp(pszValue, a_sz, sizeof(a_sz) - 1)) + if (MY_MATCH_STR("UINT8_C")) + pszType = "uint8_t"; + else if (MY_MATCH_STR("UINT16_C")) + pszType = "uint16_t"; + else if (MY_MATCH_STR("UINT32_C")) + pszType = "uint32_t"; + else if (MY_MATCH_STR("UINT64_C")) + pszType = "uint64_t"; + else + pszType = NULL; + if (pszType) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline %s %s = %.*s;\n", + pszType, pszDefine, cchInnerValue, pchInnerValue); + else if (MY_MATCH_STR("RT_BIT") || MY_MATCH_STR("RT_BIT_32")) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline uint32_t %s = 1U << %llu;\n", + pszDefine, u64); + else if (MY_MATCH_STR("RT_BIT_64")) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline uint64_t %s = 1ULL << %llu;\n", + pszDefine, u64); + else + return RTEXITCODE_SUCCESS; +#undef MY_MATCH_STR + } + /* Dunno what this is... */ + else + return RTEXITCODE_SUCCESS; + + /* + * Check for output error and clear the output suppression indicator. + */ + if (cch < 0) + return vbcppError(pThis, "Output error"); + + pThis->fJustDroppedLine = false; + return RTEXITCODE_SUCCESS; +} + + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveDefine(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + /* If it's a function style define, parse out the parameter list. */ + size_t cchParams = 0; + const char *pchParams = NULL; + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + { + ScmStreamGetCh(pStrmInput); + pchParams = ScmStreamGetCur(pStrmInput); + + unsigned chPrev = ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + { + rcExit = vbcppError(pThis, "Missing ')'"); + break; + } + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + if (ch == ')') + { + cchParams = ScmStreamGetCur(pStrmInput) - pchParams; + ScmStreamGetCh(pStrmInput); + break; + } + ScmStreamGetCh(pStrmInput); + } + } + /* The simple kind. */ + else if (!RT_C_IS_SPACE(ch) && ch != ~(unsigned)0) + rcExit = vbcppError(pThis, "Expected whitespace after macro name"); + + /* Parse out the value. */ + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t offValue = ScmStreamTell(pStrmInput); + const char *pchValue = ScmStreamGetCur(pStrmInput); + unsigned chPrev = ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + chPrev = ScmStreamGetCh(pStrmInput); + } + size_t cchValue = ScmStreamGetCur(pStrmInput) - pchValue; + + /* + * Execute. + */ + if (pchParams) + rcExit = vbcppMacroAddFn(pThis, pchDefine, cchDefine, pchParams, cchParams, pchValue, cchValue, false); + else + rcExit = vbcppMacroAdd(pThis, pchDefine, cchDefine, pchValue, cchValue, false); + + /* + * Pass thru? + */ + if ( rcExit == RTEXITCODE_SUCCESS + && pThis->fPassThruDefines) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch; + if (pchParams) + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sdefine %.*s(%.*s)", + cchIndent, "", cchDefine, pchDefine, cchParams, pchParams); + else + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sdefine %.*s", + cchIndent, "", cchDefine, pchDefine); + if (cch > 0) + vbcppOutputComment(pThis, pStrmInput, offValue, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + else if ( rcExit == RTEXITCODE_SUCCESS + && pThis->enmMode == kVBCppMode_SelectiveD) + rcExit = vbcppMacroTryConvertToInlineD(pThis, vbcppMacroLookup(pThis, pchDefine, cchDefine)); + else + pThis->fJustDroppedLine = true; + } + } + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveUndef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + size_t offMaybeComment = vbcppProcessSkipWhite(pStrmInput); + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Take action. + */ + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, pchDefine, cchDefine); + if ( pMacro + && pThis->fRespectSourceDefines + && ( !pMacro->fCmdLine + || pThis->fAllowRedefiningCmdLineDefines ) ) + { + RTStrSpaceRemove(&pThis->StrSpace, pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + } + + /* + * Pass thru. + */ + if ( rcExit == RTEXITCODE_SUCCESS + && pThis->fPassThruDefines) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sundef %.*s", + cchIndent, "", cchDefine, pchDefine); + if (cch > 0) + vbcppOutputComment(pThis, pStrmInput, offMaybeComment, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifndef"); + } + return rcExit; + +} + + + + + +/* + * + * + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * + * + */ + + +/** + * Combines current stack result with the one being pushed. + * + * @returns Combined result. + * @param enmEvalPush The result of the condition being pushed. + * @param enmEvalStack The current stack result. + */ +static VBCPPEVAL vbcppCondCombine(VBCPPEVAL enmEvalPush, VBCPPEVAL enmEvalStack) +{ + if (enmEvalStack == kVBCppEval_False) + return kVBCppEval_False; + return enmEvalPush; +} + + +/** + * Pushes an conditional onto the stack. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The current input stream. + * @param offStart Not currently used, using @a pchCondition and + * @a cchCondition instead. + * @param enmKind The kind of conditional. + * @param enmResult The result of the evaluation. + * @param pchCondition The raw condition. + * @param cchCondition The length of @a pchCondition. + */ +static RTEXITCODE vbcppCondPush(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, + VBCPPCONDKIND enmKind, VBCPPEVAL enmResult, + const char *pchCondition, size_t cchCondition) +{ + RT_NOREF_PV(offStart); RT_NOREF_PV(pStrmInput); + + + if (pThis->cCondStackDepth >= _64K) + return vbcppError(pThis, "Too many nested #if/#ifdef/#ifndef statements"); + + /* + * Allocate a new entry and push it. + */ + PVBCPPCOND pCond = (PVBCPPCOND)RTMemAlloc(sizeof(*pCond)); + if (!pCond) + return vbcppError(pThis, "out of memory"); + + PVBCPPCOND pUp = pThis->pCondStack; + pCond->enmKind = enmKind; + pCond->enmResult = enmResult; + pCond->enmStackResult = pUp ? vbcppCondCombine(enmResult, pUp->enmStackResult) : enmResult; + pCond->fSeenElse = false; + pCond->fElIfDecided = enmResult == kVBCppEval_True; + pCond->iLevel = pThis->cCondStackDepth; + pCond->iKeepLevel = (pUp ? pUp->iKeepLevel : 0) + enmResult == kVBCppEval_Undecided; + pCond->pchCond = pchCondition; + pCond->cchCond = cchCondition; + + pCond->pUp = pThis->pCondStack; + pThis->pCondStack = pCond; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && enmResult == kVBCppEval_Undecided) + { + /** @todo this is stripping comments of \#ifdef and \#ifndef atm. */ + const char *pszDirective; + switch (enmKind) + { + case kVBCppCondKind_If: pszDirective = "if"; break; + case kVBCppCondKind_IfDef: pszDirective = "ifdef"; break; + case kVBCppCondKind_IfNDef: pszDirective = "ifndef"; break; + case kVBCppCondKind_ElIf: pszDirective = "elif"; break; + default: AssertFailedReturn(RTEXITCODE_FAILURE); + } + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*s%s %.*s", + pCond->iKeepLevel - 1, "", pszDirective, cchCondition, pchCondition); + if (cch < 0) + return vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Recursively destroys the expression tree. + * + * @param pExpr The root of the expression tree to destroy. + */ +static void vbcppExprDestoryTree(PVBCPPEXPR pExpr) +{ + if (!pExpr) + return; + + switch (pExpr->enmKind) + { + case kVBCppExprKind_Unary: + vbcppExprDestoryTree(pExpr->u.Unary.pArg); + break; + case kVBCppExprKind_Binary: + vbcppExprDestoryTree(pExpr->u.Binary.pLeft); + vbcppExprDestoryTree(pExpr->u.Binary.pRight); + break; + case kVBCppExprKind_Ternary: + vbcppExprDestoryTree(pExpr->u.Ternary.pExpr); + vbcppExprDestoryTree(pExpr->u.Ternary.pExpr); + vbcppExprDestoryTree(pExpr->u.Ternary.pFalse); + break; + case kVBCppExprKind_SignedValue: + case kVBCppExprKind_UnsignedValue: + break; + default: + AssertFailed(); + return; + } + RTMemFree(pExpr); +} + + +/** + * Report error during expression parsing. + * + * @returns kExprRet_Error + * @param pParser The parser instance. + * @param pszMsg The error message. + * @param ... Format arguments. + */ +static VBCPPEXPRRET vbcppExprParseError(PVBCPPEXPRPARSER pParser, const char *pszMsg, ...) +{ + va_list va; + va_start(va, pszMsg); + vbcppErrorV(pParser->pThis, pszMsg, va); + va_end(va); + return kExprRet_Error; +} + + +/** + * Skip white space. + * + * @param pParser The parser instance. + */ +static void vbcppExprParseSkipWhiteSpace(PVBCPPEXPRPARSER pParser) +{ + while (RT_C_IS_SPACE(*pParser->pszCur)) + pParser->pszCur++; +} + + +/** + * Allocate a new + * + * @returns Pointer to the node. NULL+msg on failure. + * @param pParser The parser instance. + */ +static PVBCPPEXPR vbcppExprParseAllocNode(PVBCPPEXPRPARSER pParser) +{ + PVBCPPEXPR pExpr = (PVBCPPEXPR)RTMemAllocZ(sizeof(*pExpr)); + if (!pExpr) + vbcppExprParseError(pParser, "out of memory (expression node)"); + return pExpr; +} + + +/** + * Looks for right parentheses and/or end of expression. + * + * @returns Expression status. + * @retval kExprRet_Ok + * @retval kExprRet_Error with msg. + * @retval kExprRet_EndOfExpr + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseMaybeRParenOrEoe(PVBCPPEXPRPARSER pParser) +{ + Assert(!pParser->ppCur); + for (;;) + { + vbcppExprParseSkipWhiteSpace(pParser); + char ch = *pParser->pszCur; + if (ch == '\0') + return kExprRet_EndOfExpr; + if (ch != ')') + break; + pParser->pszCur++; + + PVBCPPEXPR pCur = pParser->pCur; + while ( pCur + && ( pCur->enmKind != kVBCppExprKind_Unary + || pCur->u.Unary.enmOperator != kVBCppUnaryOp_Parenthesis)) + { + switch (pCur->enmKind) + { + case kVBCppExprKind_SignedValue: + case kVBCppExprKind_UnsignedValue: + Assert(pCur->fComplete); + break; + case kVBCppExprKind_Unary: + AssertReturn(pCur->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + break; + case kVBCppExprKind_Binary: + AssertReturn(pCur->u.Binary.pLeft, vbcppExprParseError(pParser, "internal error")); + AssertReturn(pCur->u.Binary.pRight, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + break; + case kVBCppExprKind_Ternary: +#if 1 /** @todo Check out the ternary operator implementation. */ + return vbcppExprParseError(pParser, "The ternary operator is not implemented"); +#else + Assert(pCur->u.Ternary.pExpr); + if (!pCur->u.Ternary.pTrue) + return vbcppExprParseError(pParser, "?!?!?"); + if (!pCur->u.Ternary.pFalse) + return vbcppExprParseError(pParser, "?!?!?!?"); + pCur->fComplete = true; +#endif + break; + default: + return vbcppExprParseError(pParser, "Internal error (enmKind=%d)", pCur->enmKind); + } + pCur = pCur->pParent; + } + if (!pCur) + return vbcppExprParseError(pParser, "Right parenthesis without a left one"); + pCur->fComplete = true; + + while ( pCur->enmKind == kVBCppExprKind_Unary + && pCur->u.Unary.enmOperator != kVBCppUnaryOp_Parenthesis + && pCur->pParent) + { + AssertReturn(pCur->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + pCur = pCur->pParent; + } + } + + return kExprRet_Ok; +} + + +/** + * Parses an binary operator. + * + * @returns Expression status. + * @retval kExprRet_Ok + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseBinaryOperator(PVBCPPEXPRPARSER pParser) +{ + /* + * Binary or ternary operator should follow now. + */ + VBCPPBINARYOP enmOp; + char ch = *pParser->pszCur; + switch (ch) + { + case '*': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by product operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Multiplication; + break; + case '/': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by quotient operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Division; + break; + case '%': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by remainder operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Modulo; + break; + case '+': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by sum operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Addition; + break; + case '-': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by difference operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Subtraction; + break; + case '<': + enmOp = kVBCppBinary_LessThan; + if (pParser->pszCur[1] == '=') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LessThanOrEqual; + } + else if (pParser->pszCur[1] == '<') + { + pParser->pszCur++; + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise left shift operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_LeftShift; + } + break; + case '>': + enmOp = kVBCppBinary_GreaterThan; + if (pParser->pszCur[1] == '=') + { + pParser->pszCur++; + enmOp = kVBCppBinary_GreaterThanOrEqual; + } + else if (pParser->pszCur[1] == '<') + { + pParser->pszCur++; + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise right shift operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_LeftShift; + } + break; + case '=': + if (pParser->pszCur[1] != '=') + return vbcppExprParseError(pParser, "The assignment operator is not valid in a preprocessor expression"); + pParser->pszCur++; + enmOp = kVBCppBinary_EqualTo; + break; + + case '!': + if (pParser->pszCur[1] != '=') + return vbcppExprParseError(pParser, "Expected binary operator, found the unary operator logical NOT"); + pParser->pszCur++; + enmOp = kVBCppBinary_NotEqualTo; + break; + + case '&': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise AND operator is not valid in a preprocessor expression"); + if (pParser->pszCur[1] == '&') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LogicalAnd; + } + else + enmOp = kVBCppBinary_BitwiseAnd; + break; + case '^': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise XOR operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_BitwiseXor; + break; + case '|': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise AND operator is not valid in a preprocessor expression"); + if (pParser->pszCur[1] == '|') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LogicalOr; + } + else + enmOp = kVBCppBinary_BitwiseOr; + break; + case '~': + return vbcppExprParseError(pParser, "Expected binary operator, found the unary operator bitwise NOT"); + + case ':': + case '?': + return vbcppExprParseError(pParser, "The ternary operator is not yet implemented"); + + default: + return vbcppExprParseError(pParser, "Expected binary operator, found '%.20s'", pParser->pszCur); + } + pParser->pszCur++; + + /* + * Create a binary operator node. + */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_Binary; + pExpr->u.Binary.enmOperator = enmOp; + pExpr->u.Binary.pLeft = NULL; + pExpr->u.Binary.pRight = NULL; + + /* + * Back up the tree until we find our spot. + */ + PVBCPPEXPR *ppPlace = NULL; + PVBCPPEXPR pChild = NULL; + PVBCPPEXPR pParent = pParser->pCur; + while (pParent) + { + if (pParent->enmKind == kVBCppExprKind_Unary) + { + if (pParent->u.Unary.enmOperator == kVBCppUnaryOp_Parenthesis) + { + ppPlace = &pParent->u.Unary.pArg; + break; + } + AssertReturn(pParent->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pParent->fComplete = true; + } + else if (pParent->enmKind == kVBCppExprKind_Binary) + { + AssertReturn(pParent->u.Binary.pLeft, vbcppExprParseError(pParser, "internal error")); + AssertReturn(pParent->u.Binary.pRight, vbcppExprParseError(pParser, "internal error")); + if ((pParent->u.Binary.enmOperator & VBCPPOP_PRECEDENCE_MASK) >= (enmOp & VBCPPOP_PRECEDENCE_MASK)) + { + AssertReturn(pChild, vbcppExprParseError(pParser, "internal error")); + + if (pParent->u.Binary.pRight == pChild) + ppPlace = &pParent->u.Binary.pRight; + else + ppPlace = &pParent->u.Binary.pLeft; + AssertReturn(*ppPlace == pChild, vbcppExprParseError(pParser, "internal error")); + break; + } + pParent->fComplete = true; + } + else if (pParent->enmKind == kVBCppExprKind_Ternary) + { + return vbcppExprParseError(pParser, "The ternary operator is not implemented"); + } + else + AssertReturn( pParent->enmKind == kVBCppExprKind_SignedValue + || pParent->enmKind == kVBCppExprKind_UnsignedValue, + vbcppExprParseError(pParser, "internal error")); + + /* Up on level */ + pChild = pParent; + pParent = pParent->pParent; + } + + /* + * Do the rotation. + */ + Assert(pChild); + Assert(pChild->pParent == pParent); + pChild->pParent = pExpr; + + pExpr->u.Binary.pLeft = pChild; + pExpr->pParent = pParent; + + if (!pParent) + pParser->pRoot = pExpr; + else + *ppPlace = pExpr; + + pParser->ppCur = &pExpr->u.Binary.pRight; + pParser->pCur = pExpr; + + return kExprRet_Ok; +} + + +/** + * Deals with right paretheses or/and end of expression, looks for binary + * operators. + * + * @returns Expression status. + * @retval kExprRet_Ok if binary operator was found processed. + * @retval kExprRet_Error with msg. + * @retval kExprRet_EndOfExpr + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseBinaryOrEoeOrRparen(PVBCPPEXPRPARSER pParser) +{ + VBCPPEXPRRET enmRet = vbcppExprParseMaybeRParenOrEoe(pParser); + if (enmRet != kExprRet_Ok) + return enmRet; + return vbcppExprParseBinaryOperator(pParser); +} + + +/** + * Parses an identifier in the expression, replacing it by 0. + * + * All known identifiers has already been replaced by their macro values, so + * what's left are unknown macros. These are replaced by 0. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseIdentifier(PVBCPPEXPRPARSER pParser) +{ +/** @todo don't increment if it's an actively undefined macro. Need to revise + * the expression related code wrt selective preprocessing. */ + pParser->cUndefined++; + + /* Find the end. */ + const char *pszMacro = pParser->pszCur; + const char *pszNext = pszMacro + 1; + while (vbcppIsCIdentifierChar(*pszNext)) + pszNext++; + size_t cchMacro = pszNext - pszMacro; + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_UnsignedValue; + pExpr->u.UnsignedValue.u64 = 0; + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + /* Skip spaces and check for parenthesis. */ + pParser->pszCur = pszNext; + vbcppExprParseSkipWhiteSpace(pParser); + if (*pParser->pszCur == '(') + return vbcppExprParseError(pParser, "Unknown unary operator '%.*s'", cchMacro, pszMacro); + + return kExprRet_Value; +} + + +/** + * Parses an numeric constant in the expression. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseNumber(PVBCPPEXPRPARSER pParser) +{ + bool fSigned; + char *pszNext; + uint64_t u64; + char ch = *pParser->pszCur++; + char ch2 = *pParser->pszCur; + if ( ch == '0' + && (ch == 'x' || ch == 'X')) + { + ch2 = *++pParser->pszCur; + if (!RT_C_IS_XDIGIT(ch2)) + return vbcppExprParseError(pParser, "Expected hex digit following '0x'"); + int rc = RTStrToUInt64Ex(pParser->pszCur, &pszNext, 16, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid hex value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = false; + } + else if (ch == '0') + { + int rc = RTStrToUInt64Ex(pParser->pszCur - 1, &pszNext, 8, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid octal value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = u64 > (uint64_t)INT64_MAX ? false : true; + } + else + { + int rc = RTStrToUInt64Ex(pParser->pszCur - 1, &pszNext, 10, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid decimal value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = u64 > (uint64_t)INT64_MAX ? false : true; + } + + /* suffix. */ + if (vbcppIsCIdentifierLeadChar(*pszNext)) + { + size_t cchSuffix = 1; + while (vbcppIsCIdentifierLeadChar(pszNext[cchSuffix])) + cchSuffix++; + + if (cchSuffix == '1' && (*pszNext == 'u' || *pszNext == 'U')) + fSigned = false; + else if ( cchSuffix == '1' + && (*pszNext == 'l' || *pszNext == 'L')) + fSigned = true; + else if ( cchSuffix == '2' + && (!strncmp(pszNext, "ul", 2) || !strncmp(pszNext, "UL", 2))) + fSigned = false; + else if ( cchSuffix == '2' + && (!strncmp(pszNext, "ll", 2) || !strncmp(pszNext, "LL", 2))) + fSigned = true; + else if ( cchSuffix == '3' + && (!strncmp(pszNext, "ull", 3) || !strncmp(pszNext, "ULL", 3))) + fSigned = false; + else + return vbcppExprParseError(pParser, "Invalid number suffix '%.*s'", cchSuffix, pszNext); + + pszNext += cchSuffix; + } + pParser->pszCur = pszNext; + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + if (fSigned) + { + pExpr->enmKind = kVBCppExprKind_SignedValue; + pExpr->u.SignedValue.s64 = (int64_t)u64; + } + else + { + pExpr->enmKind = kVBCppExprKind_UnsignedValue; + pExpr->u.UnsignedValue.u64 = u64; + } + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + return kExprRet_Value; +} + + +/** + * Parses an character constant in the expression. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseCharacterConstant(PVBCPPEXPRPARSER pParser) +{ + Assert(*pParser->pszCur == '\''); + pParser->pszCur++; + char ch2 = *pParser->pszCur++; + if (ch2 == '\'') + return vbcppExprParseError(pParser, "Empty character constant"); + int64_t s64; + if (ch2 == '\\') + { + ch2 = *pParser->pszCur++; + switch (ch2) + { + case '0': s64 = 0x00; break; + case 'n': s64 = 0x0d; break; + case 'r': s64 = 0x0a; break; + case 't': s64 = 0x09; break; + default: + return vbcppExprParseError(pParser, "Escape character '%c' is not implemented", ch2); + } + } + else + s64 = ch2; + if (*pParser->pszCur != '\'') + return vbcppExprParseError(pParser, "Character constant contains more than one character"); + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_SignedValue; + pExpr->u.SignedValue.s64 = s64; + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + return kExprRet_Value; +} + + +/** + * Parses a unary operator or a value. + * + * @returns Expression status. + * @retval kExprRet_Value if value was found and processed. + * @retval kExprRet_UnaryOperator if an unary operator was found and processed. + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseUnaryOrValue(PVBCPPEXPRPARSER pParser) +{ + vbcppExprParseSkipWhiteSpace(pParser); + char ch = *pParser->pszCur; + if (ch == '\0') + return vbcppExprParseError(pParser, "Premature end of expression"); + + /* + * Value? + */ + if (ch == '\'') + return vbcppExprParseCharacterConstant(pParser); + if (RT_C_IS_DIGIT(ch)) + return vbcppExprParseNumber(pParser); + if (ch == '"') + return vbcppExprParseError(pParser, "String litteral"); + if (vbcppIsCIdentifierLeadChar(ch)) + return vbcppExprParseIdentifier(pParser); + + /* + * Operator? + */ + VBCPPUNARYOP enmOperator; + if (ch == '+') + { + enmOperator = kVBCppUnaryOp_Pluss; + if (pParser->pszCur[1] == '+') + return vbcppExprParseError(pParser, "The prefix increment operator is not valid in a preprocessor expression"); + } + else if (ch == '-') + { + enmOperator = kVBCppUnaryOp_Minus; + if (pParser->pszCur[1] == '-') + return vbcppExprParseError(pParser, "The prefix decrement operator is not valid in a preprocessor expression"); + } + else if (ch == '!') + enmOperator = kVBCppUnaryOp_LogicalNot; + else if (ch == '~') + enmOperator = kVBCppUnaryOp_BitwiseNot; + else if (ch == '(') + enmOperator = kVBCppUnaryOp_Parenthesis; + else + return vbcppExprParseError(pParser, "Unknown token '%.*s'", 32, pParser->pszCur - 1); + pParser->pszCur++; + + /* Create an operator node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = false; + pExpr->enmKind = kVBCppExprKind_Unary; + pExpr->u.Unary.enmOperator = enmOperator; + pExpr->u.Unary.pArg = NULL; + + /* Link it into the tree. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = &pExpr->u.Unary.pArg; + + return kExprRet_UnaryOperator; +} + + +/** + * Parses an expanded preprocessor expression. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pszExpr The expression to parse. + * @param cchExpr The length of the expression in case we need it. + * @param ppExprTree Where to return the parse tree. + * @param pcUndefined Where to return the number of unknown undefined + * macros. Optional. + */ +static RTEXITCODE vbcppExprParse(PVBCPP pThis, char *pszExpr, size_t cchExpr, PVBCPPEXPR *ppExprTree, size_t *pcUndefined) +{ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + NOREF(cchExpr); + + /* + * Initialize the parser context structure. + */ + VBCPPEXPRPARSER Parser; + Parser.pszCur = pszExpr; + Parser.pRoot = NULL; + Parser.pCur = NULL; + Parser.ppCur = &Parser.pRoot; + Parser.pszExpr = pszExpr; + Parser.cUndefined = 0; + Parser.pThis = pThis; + + /* + * Do the parsing. + */ + VBCPPEXPRRET enmRet; + for (;;) + { + /* + * Eat unary operators until we hit a value. + */ + do + enmRet = vbcppExprParseUnaryOrValue(&Parser); + while (enmRet == kExprRet_UnaryOperator); + if (enmRet == kExprRet_Error) + break; + AssertBreakStmt(enmRet == kExprRet_Value, enmRet = vbcppExprParseError(&Parser, "Expected value (enmRet=%d)", enmRet)); + + /* + * Non-unary operator, right parenthesis or end of expression is up next. + */ + enmRet = vbcppExprParseBinaryOrEoeOrRparen(&Parser); + if (enmRet == kExprRet_Error) + break; + if (enmRet == kExprRet_EndOfExpr) + { + /** @todo check if there are any open parentheses. */ + rcExit = RTEXITCODE_SUCCESS; + break; + } + AssertBreakStmt(enmRet == kExprRet_Ok, enmRet = vbcppExprParseError(&Parser, "Expected value (enmRet=%d)", enmRet)); + } + + if (rcExit != RTEXITCODE_SUCCESS) + { + vbcppExprDestoryTree(Parser.pRoot); + return rcExit; + } + + if (pcUndefined) + *pcUndefined = Parser.cUndefined; + *ppExprTree = Parser.pRoot; + return rcExit; +} + + +/** + * Checks if an expression value value is evaluates to @c true or @c false. + * + * @returns @c true or @c false. + * @param pExpr The value expression. + */ +static bool vbcppExprIsExprTrue(PVBCPPEXPR pExpr) +{ + Assert(pExpr->enmKind == kVBCppExprKind_SignedValue || pExpr->enmKind == kVBCppExprKind_UnsignedValue); + + return pExpr->enmKind == kVBCppExprKind_SignedValue + ? pExpr->u.SignedValue.s64 != 0 + : pExpr->u.UnsignedValue.u64 != 0; +} + + +/** + * Evalutes a parse (sub-)tree. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pRoot The root of the parse (sub-)tree. + * @param pResult Where to store the result value. + */ +static RTEXITCODE vbcppExprEvaluteTree(PVBCPP pThis, PVBCPPEXPR pRoot, PVBCPPEXPR pResult) +{ + RTEXITCODE rcExit; + switch (pRoot->enmKind) + { + case kVBCppExprKind_SignedValue: + pResult->enmKind = kVBCppExprKind_SignedValue; + pResult->u.SignedValue.s64 = pRoot->u.SignedValue.s64; + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_UnsignedValue: + pResult->enmKind = kVBCppExprKind_UnsignedValue; + pResult->u.UnsignedValue.u64 = pRoot->u.UnsignedValue.u64; + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_Unary: + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Unary.pArg, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* Apply the unary operator to the value */ + switch (pRoot->u.Unary.enmOperator) + { + case kVBCppUnaryOp_Minus: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = -pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = (uint64_t)-(int64_t)pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_LogicalNot: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = !pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = !pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_BitwiseNot: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = ~pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = ~pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_Pluss: + case kVBCppUnaryOp_Parenthesis: + /* do nothing. */ + break; + + default: + return vbcppError(pThis, "Internal error: u.Unary.enmOperator=%d", pRoot->u.Unary.enmOperator); + } + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_Binary: + { + /* Always evalute the left side. */ + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Binary.pLeft, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* If logical AND or OR we can sometimes skip evaluting the right side. */ + if ( pRoot->u.Binary.enmOperator == kVBCppBinary_LogicalAnd + && !vbcppExprIsExprTrue(pResult)) + return RTEXITCODE_SUCCESS; + + if ( pRoot->u.Binary.enmOperator == kVBCppBinary_LogicalOr + && vbcppExprIsExprTrue(pResult)) + return RTEXITCODE_SUCCESS; + + /* Evalute the right side. */ + VBCPPEXPR Result2; + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Binary.pRight, &Result2); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* If one of them is unsigned, promote the other to unsigned as well. */ + if ( pResult->enmKind == kVBCppExprKind_UnsignedValue + && Result2.enmKind == kVBCppExprKind_SignedValue) + { + Result2.enmKind = kVBCppExprKind_UnsignedValue; + Result2.u.UnsignedValue.u64 = Result2.u.SignedValue.s64; + } + else if ( pResult->enmKind == kVBCppExprKind_SignedValue + && Result2.enmKind == kVBCppExprKind_UnsignedValue) + { + pResult->enmKind = kVBCppExprKind_UnsignedValue; + pResult->u.UnsignedValue.u64 = pResult->u.SignedValue.s64; + } + + /* Perform the operation. */ + if (pResult->enmKind == kVBCppExprKind_UnsignedValue) + { + switch (pRoot->u.Binary.enmOperator) + { + case kVBCppBinary_Multiplication: + pResult->u.UnsignedValue.u64 *= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Division: + if (!Result2.u.UnsignedValue.u64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.UnsignedValue.u64 /= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Modulo: + if (!Result2.u.UnsignedValue.u64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.UnsignedValue.u64 %= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Addition: + pResult->u.UnsignedValue.u64 += Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Subtraction: + pResult->u.UnsignedValue.u64 -= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LeftShift: + pResult->u.UnsignedValue.u64 <<= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_RightShift: + pResult->u.UnsignedValue.u64 >>= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LessThan: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 < Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LessThanOrEqual: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 <= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_GreaterThan: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 > Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_GreaterThanOrEqual: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 >= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_EqualTo: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 == Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_NotEqualTo: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 != Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseAnd: + pResult->u.UnsignedValue.u64 &= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseXor: + pResult->u.UnsignedValue.u64 ^= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseOr: + pResult->u.UnsignedValue.u64 |= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LogicalAnd: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 && Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LogicalOr: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 || Result2.u.UnsignedValue.u64; + break; + default: + return vbcppError(pThis, "Internal error: u.Binary.enmOperator=%d", pRoot->u.Binary.enmOperator); + } + } + else + { + switch (pRoot->u.Binary.enmOperator) + { + case kVBCppBinary_Multiplication: + pResult->u.SignedValue.s64 *= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Division: + if (!Result2.u.SignedValue.s64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.SignedValue.s64 /= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Modulo: + if (!Result2.u.SignedValue.s64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.SignedValue.s64 %= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Addition: + pResult->u.SignedValue.s64 += Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Subtraction: + pResult->u.SignedValue.s64 -= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LeftShift: + pResult->u.SignedValue.s64 <<= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_RightShift: + pResult->u.SignedValue.s64 >>= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LessThan: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 < Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LessThanOrEqual: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 <= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_GreaterThan: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 > Result2.u.SignedValue.s64; + break; + case kVBCppBinary_GreaterThanOrEqual: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 >= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_EqualTo: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 == Result2.u.SignedValue.s64; + break; + case kVBCppBinary_NotEqualTo: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 != Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseAnd: + pResult->u.SignedValue.s64 &= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseXor: + pResult->u.SignedValue.s64 ^= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseOr: + pResult->u.SignedValue.s64 |= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LogicalAnd: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 && Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LogicalOr: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 || Result2.u.SignedValue.s64; + break; + default: + return vbcppError(pThis, "Internal error: u.Binary.enmOperator=%d", pRoot->u.Binary.enmOperator); + } + } + return rcExit; + } + + case kVBCppExprKind_Ternary: + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pExpr, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (vbcppExprIsExprTrue(pResult)) + return vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pTrue, pResult); + return vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pFalse, pResult); + + default: + return vbcppError(pThis, "Internal error: enmKind=%d", pRoot->enmKind); + } +} + + +/** + * Evalutes the expression. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pszExpr The expression. + * @param cchExpr The length of the expression. + * @param penmResult Where to store the result. + */ +static RTEXITCODE vbcppExprEval(PVBCPP pThis, char *pszExpr, size_t cchExpr, size_t cReplacements, VBCPPEVAL *penmResult) +{ + Assert(strlen(pszExpr) == cchExpr); + RT_NOREF_PV(cReplacements); + + size_t cUndefined; + PVBCPPEXPR pExprTree; + RTEXITCODE rcExit = vbcppExprParse(pThis, pszExpr, cchExpr, &pExprTree, &cUndefined); + if (rcExit == RTEXITCODE_SUCCESS) + { + if ( !cUndefined + || pThis->enmMode == kVBCppMode_SelectiveD + || pThis->enmMode == kVBCppMode_Standard) + { + VBCPPEXPR Result; + rcExit = vbcppExprEvaluteTree(pThis, pExprTree, &Result); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (vbcppExprIsExprTrue(&Result)) + *penmResult = kVBCppEval_True; + else + *penmResult = kVBCppEval_False; + } + } + else + *penmResult = kVBCppEval_Undecided; + } + return rcExit; +} + + +static RTEXITCODE vbcppExtractSkipCommentLine(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RT_NOREF_PV(pThis); + + unsigned chPrev = ScmStreamGetCh(pStrmInput); Assert(chPrev == '/'); + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + chPrev = ch; + } + else + { + chPrev = ScmStreamGetCh(pStrmInput); + Assert(chPrev == ch); + } + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppExtractSkipComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + unsigned ch = ScmStreamGetCh(pStrmInput); Assert(ch == '*'); + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '*') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '/') + return RTEXITCODE_SUCCESS; + } + } + return vbcppError(pThis, "Expected '*/'"); +} + + +static RTEXITCODE vbcppExtractQuotedString(PVBCPP pThis, PSCMSTREAM pStrmInput, PVBCPPSTRBUF pStrBuf, + char chOpen, char chClose) +{ + unsigned ch = ScmStreamGetCh(pStrmInput); + Assert(ch == (unsigned)chOpen); + + RTEXITCODE rcExit = vbcppStrBufAppendCh(pStrBuf, chOpen); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + for (;;) + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '\\') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + break; + rcExit = vbcppStrBufAppendCh(pStrBuf, '\\'); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + else if (ch != ~(unsigned)0) + { + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (ch == (unsigned)chClose) + return RTEXITCODE_SUCCESS; + } + else + break; + } + + return vbcppError(pThis, "File ended with an open character constant"); +} + + +/** + * Extracts a line from the stream, stripping it for comments and maybe + * optimzing some of the whitespace. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param pStrBuf Where to store the extracted line. Caller must + * initialize this prior to the call an delete it + * after use (even on failure). + * @param poffComment Where to note down the position of the final + * comment. Optional. + */ +static RTEXITCODE vbcppExtractDirectiveLine(PVBCPP pThis, PSCMSTREAM pStrmInput, PVBCPPSTRBUF pStrBuf, size_t *poffComment) +{ + size_t offComment = ~(size_t)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + RTEXITCODE rcExit; + if (ch == '/') + { + /* Comment? */ + unsigned ch2 = ScmStreamGetCh(pStrmInput); Assert(ch == ch2); NOREF(ch2); + ch = ScmStreamPeekCh(pStrmInput); + if (ch == '*') + { + offComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppExtractSkipComment(pThis, pStrmInput); + } + else if (ch == '/') + { + offComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppExtractSkipCommentLine(pThis, pStrmInput); + } + else + rcExit = vbcppStrBufAppendCh(pStrBuf, '/'); + } + else if (ch == '\'') + { + offComment = ~(size_t)0; + rcExit = vbcppExtractQuotedString(pThis, pStrmInput, pStrBuf, '\'', '\''); + } + else if (ch == '"') + { + offComment = ~(size_t)0; + rcExit = vbcppExtractQuotedString(pThis, pStrmInput, pStrBuf, '"', '"'); + } + else if (ch == '\r' || ch == '\n') + break; /* done */ + else if ( RT_C_IS_SPACE(ch) + && ( RT_C_IS_SPACE(vbcppStrBufLastCh(pStrBuf)) + || vbcppStrBufLastCh(pStrBuf) == '\0') ) + { + unsigned ch2 = ScmStreamGetCh(pStrmInput); + Assert(ch == ch2); NOREF(ch2); + rcExit = RTEXITCODE_SUCCESS; + } + else + { + unsigned ch2 = ScmStreamGetCh(pStrmInput); Assert(ch == ch2); + + /* Escaped newline? */ + if ( ch == '\\' + && ( (ch2 = ScmStreamPeekCh(pStrmInput)) == '\r' + || ch2 == '\n')) + { + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + rcExit = RTEXITCODE_SUCCESS; + } + else + { + offComment = ~(size_t)0; + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + } + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + if (poffComment) + *poffComment = offComment; + return RTEXITCODE_SUCCESS; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + * @param enmKind The kind of directive we're processing. + */ +static RTEXITCODE vbcppDirectiveIfOrElif(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, + VBCPPCONDKIND enmKind) +{ + /* + * Check for missing #if if #elif. + */ + if ( enmKind == kVBCppCondKind_ElIf + && !pThis->pCondStack ) + return vbcppError(pThis, "#elif without #if"); + + /* + * Extract the expression string. + */ + const char *pchCondition = ScmStreamGetCur(pStrmInput); + size_t offComment; + VBCPPMACROEXP ExpCtx; +#if 0 + ExpCtx.pMacroStack = NULL; +#endif + ExpCtx.pStrmInput = NULL; + ExpCtx.papszArgs = NULL; + ExpCtx.cArgs = 0; + ExpCtx.cArgsAlloced = 0; + vbcppStrBufInit(&ExpCtx.StrBuf, pThis); + RTEXITCODE rcExit = vbcppExtractDirectiveLine(pThis, pStrmInput, &ExpCtx.StrBuf, &offComment); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t const cchCondition = ScmStreamGetCur(pStrmInput) - pchCondition; + + /* + * Expand known macros in it. + */ + size_t cReplacements; + rcExit = vbcppMacroExpandReScan(pThis, &ExpCtx, kMacroReScanMode_Expression, &cReplacements); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Strip it and check that it's not empty. + */ + char *pszExpr = ExpCtx.StrBuf.pszBuf; + size_t cchExpr = ExpCtx.StrBuf.cchBuf; + while (cchExpr > 0 && RT_C_IS_SPACE(*pszExpr)) + pszExpr++, cchExpr--; + + while (cchExpr > 0 && RT_C_IS_SPACE(pszExpr[cchExpr - 1])) + { + pszExpr[--cchExpr] = '\0'; + ExpCtx.StrBuf.cchBuf--; + } + if (cchExpr) + { + /* + * Now, evalute the expression. + */ + VBCPPEVAL enmResult; + rcExit = vbcppExprEval(pThis, pszExpr, cchExpr, cReplacements, &enmResult); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Take action. + */ + if (enmKind != kVBCppCondKind_ElIf) + rcExit = vbcppCondPush(pThis, pStrmInput, offComment, enmKind, enmResult, + pchCondition, cchCondition); + else + { + PVBCPPCOND pCond = pThis->pCondStack; + if ( pCond->enmResult != kVBCppEval_Undecided + && ( !pCond->pUp + || pCond->pUp->enmStackResult == kVBCppEval_True)) + { + Assert(enmResult == kVBCppEval_True || enmResult == kVBCppEval_False); + if ( pCond->enmResult == kVBCppEval_False + && enmResult == kVBCppEval_True + && !pCond->fElIfDecided) + { + pCond->enmStackResult = kVBCppEval_True; + pCond->fElIfDecided = true; + } + else + pCond->enmStackResult = kVBCppEval_False; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + } + pCond->enmKind = kVBCppCondKind_ElIf; + pCond->enmResult = enmResult; + pCond->pchCond = pchCondition; + pCond->cchCond = cchCondition; + + /* + * Do #elif pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*selif", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 2); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + } + } + else + rcExit = vbcppError(pThis, "Empty #if expression"); + } + } + vbcppMacroExpandCleanup(&ExpCtx); + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveIfDef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Evaluate it. + */ + VBCPPEVAL enmEval; + if (vbcppMacroExists(pThis, pchDefine, cchDefine)) + enmEval = kVBCppEval_True; + else if ( !pThis->fUndecidedConditionals + || RTStrSpaceGetN(&pThis->UndefStrSpace, pchDefine, cchDefine) != NULL) + enmEval = kVBCppEval_False; + else + enmEval = kVBCppEval_Undecided; + rcExit = vbcppCondPush(pThis, pStrmInput, offStart, kVBCppCondKind_IfDef, enmEval, + pchDefine, cchDefine); + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifdef"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveIfNDef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Evaluate it. + */ + VBCPPEVAL enmEval; + if (vbcppMacroExists(pThis, pchDefine, cchDefine)) + enmEval = kVBCppEval_False; + else if ( !pThis->fUndecidedConditionals + || RTStrSpaceGetN(&pThis->UndefStrSpace, pchDefine, cchDefine) != NULL) + enmEval = kVBCppEval_True; + else + enmEval = kVBCppEval_Undecided; + rcExit = vbcppCondPush(pThis, pStrmInput, offStart, kVBCppCondKind_IfNDef, enmEval, + pchDefine, cchDefine); + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifndef"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveElse(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Nothing to parse, just comment positions to find and note down. + */ + offStart = vbcppProcessSkipWhite(pStrmInput); + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute. + */ + PVBCPPCOND pCond = pThis->pCondStack; + if (pCond) + { + if (!pCond->fSeenElse) + { + pCond->fSeenElse = true; + if ( pCond->enmResult != kVBCppEval_Undecided + && ( !pCond->pUp + || pCond->pUp->enmStackResult == kVBCppEval_True)) + { + if ( pCond->enmResult == kVBCppEval_True + || pCond->fElIfDecided) + + pCond->enmStackResult = kVBCppEval_False; + else + pCond->enmStackResult = kVBCppEval_True; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + } + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*selse", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 2); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + else + rcExit = vbcppError(pThis, "Double #else or/and missing #endif"); + } + else + rcExit = vbcppError(pThis, "#else without #if"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveEndif(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Nothing to parse, just comment positions to find and note down. + */ + offStart = vbcppProcessSkipWhite(pStrmInput); + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute. + */ + PVBCPPCOND pCond = pThis->pCondStack; + if (pCond) + { + pThis->pCondStack = pCond->pUp; + pThis->fIf0Mode = pCond->pUp && pCond->pUp->enmStackResult == kVBCppEval_False; + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sendif", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 1); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + else + rcExit = vbcppError(pThis, "#endif without #if"); + } + return rcExit; +} + + + + + +/* + * + * + * Misc Directives + * Misc Directives + * Misc Directives + * Misc Directives + * + * + */ + + +/** + * Adds an include directory. + * + * @returns Program exit code, with error message on failure. + * @param pThis The C preprocessor instance. + * @param pszDir The directory to add. + */ +static RTEXITCODE vbcppAddInclude(PVBCPP pThis, const char *pszDir) +{ + uint32_t cIncludes = pThis->cIncludes; + if (cIncludes >= _64K) + return vbcppError(pThis, "Too many include directories"); + + void *pv = RTMemRealloc(pThis->papszIncludes, (cIncludes + 1) * sizeof(char **)); + if (!pv) + return vbcppError(pThis, "No memory for include directories"); + pThis->papszIncludes = (char **)pv; + + int rc = RTStrDupEx(&pThis->papszIncludes[cIncludes], pszDir); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "No string memory for include directories"); + + pThis->cIncludes = cIncludes + 1; + return RTEXITCODE_SUCCESS; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveInclude(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchFileSpec = 0; + const char *pchFileSpec = NULL; + size_t cchFilename = 0; + const char *pchFilename = NULL; + + unsigned ch = ScmStreamPeekCh(pStrmInput); + unsigned chType = ch; + if (ch == '"' || ch == '<') + { + ScmStreamGetCh(pStrmInput); + pchFileSpec = pchFilename = ScmStreamGetCur(pStrmInput); + unsigned chEnd = chType == '<' ? '>' : '"'; + while ( (ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0 + && ch != chEnd) + { + if (ch == '\r' || ch == '\n') + { + rcExit = vbcppError(pThis, "Multi-line include file specfications are not supported"); + break; + } + } + + if (rcExit == RTEXITCODE_SUCCESS) + { + if (ch != ~(unsigned)0) + cchFileSpec = cchFilename = ScmStreamGetCur(pStrmInput) - pchFilename - 1; + else + rcExit = vbcppError(pThis, "Expected '%c'", chType); + } + } + else if (vbcppIsCIdentifierLeadChar(ch)) + { + //pchFileSpec = ScmStreamCGetWord(pStrmInput, &cchFileSpec); + rcExit = vbcppError(pThis, "Including via a define is not implemented yet"); + } + else + rcExit = vbcppError(pThis, "Malformed include directive"); + + /* + * Take down the location of the next non-white space, in case we need + * to pass thru the directive further down. Then skip to the end of the + * line. + */ + size_t const offIncEnd = vbcppProcessSkipWhite(pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute it. + */ + if (pThis->enmIncludeAction == kVBCppIncludeAction_Include) + { + /** @todo Search for the include file and push it onto the input stack. + * Not difficult, just unnecessary rigth now. */ + rcExit = vbcppError(pThis, "Includes are fully implemented"); + } + else if (pThis->enmIncludeAction == kVBCppIncludeAction_PassThru) + { + /* Pretty print the passthru. */ + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch; + if (chType == '<') + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude <%.*s>", + cchIndent, "", cchFileSpec, pchFileSpec); + else if (chType == '"') + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude \"%.*s\"", + cchIndent, "", cchFileSpec, pchFileSpec); + else + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude %.*s", + cchIndent, "", cchFileSpec, pchFileSpec); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offIncEnd, cch, 1); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + + } + else + { + Assert(pThis->enmIncludeAction == kVBCppIncludeAction_Drop); + pThis->fJustDroppedLine = true; + } + } + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectivePragma(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse out the first word. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchPragma; + const char *pchPragma = ScmStreamCGetWord(pStrmInput, &cchPragma); + if (pchPragma) + { + size_t const off2nd = vbcppProcessSkipWhite(pStrmInput); + size_t offComment; + rcExit = vbcppInputSkipToEndOfDirectiveLine(pThis, pStrmInput, &offComment); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * What to do about this + */ + bool fPassThru = false; + if ( cchPragma == 1 + && *pchPragma == 'D') + fPassThru = pThis->fPassThruPragmaD; + else if ( cchPragma == 3 + && !strncmp(pchPragma, "STD", 3)) + fPassThru = pThis->fPassThruPragmaSTD; + else + fPassThru = pThis->fPassThruPragmaOther; + if (fPassThru) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*spragma %.*s", + cchIndent, "", cchPragma, pchPragma); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, off2nd, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + else + pThis->fJustDroppedLine = true; + } + } + else + rcExit = vbcppError(pThis, "Malformed #pragma"); + } + + return rcExit; +} + + +/** + * Processes an error directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveError(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Hit an #error"); +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveLineNo(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Not implemented: %s", __FUNCTION__); +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppDirectiveLineNoShort(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Not implemented: %s", __FUNCTION__); +} + + +/** + * Handles a preprocessor directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessDirective(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + /* + * Get the directive and do a string switch on it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + size_t cchDirective; + const char *pchDirective = ScmStreamCGetWord(pStrmInput, &cchDirective); + if (pchDirective) + { + size_t const offStart = ScmStreamTell(pStrmInput); +#define IS_DIRECTIVE(a_sz) ( sizeof(a_sz) - 1 == cchDirective && strncmp(pchDirective, a_sz, sizeof(a_sz) - 1) == 0) + if (IS_DIRECTIVE("if")) + rcExit = vbcppDirectiveIfOrElif(pThis, pStrmInput, offStart, kVBCppCondKind_If); + else if (IS_DIRECTIVE("elif")) + rcExit = vbcppDirectiveIfOrElif(pThis, pStrmInput, offStart, kVBCppCondKind_ElIf); + else if (IS_DIRECTIVE("ifdef")) + rcExit = vbcppDirectiveIfDef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("ifndef")) + rcExit = vbcppDirectiveIfNDef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("else")) + rcExit = vbcppDirectiveElse(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("endif")) + rcExit = vbcppDirectiveEndif(pThis, pStrmInput, offStart); + else if (!pThis->fIf0Mode) + { + if (IS_DIRECTIVE("include")) + rcExit = vbcppDirectiveInclude(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("define")) + rcExit = vbcppDirectiveDefine(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("undef")) + rcExit = vbcppDirectiveUndef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("pragma")) + rcExit = vbcppDirectivePragma(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("error")) + rcExit = vbcppDirectiveError(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("line")) + rcExit = vbcppDirectiveLineNo(pThis, pStrmInput, offStart); + else + rcExit = vbcppError(pThis, "Unknown preprocessor directive '#%.*s'", cchDirective, pchDirective); + } +#undef IS_DIRECTIVE + } + else if (!pThis->fIf0Mode) + { + /* Could it be a # <num> "file" directive? */ + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (RT_C_IS_DIGIT(ch)) + rcExit = vbcppDirectiveLineNoShort(pThis, pStrmInput); + else + rcExit = vbcppError(pThis, "Malformed preprocessor directive"); + } + return rcExit; +} + + +/* + * + * + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * + * + */ + + +/** + * Does the actually preprocessing of the input file. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppPreprocess(PVBCPP pThis) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + /* + * Parse. + */ + while (pThis->pInputStack) + { + pThis->fMaybePreprocessorLine = true; + + PSCMSTREAM pStrmInput = &pThis->pInputStack->StrmInput; + unsigned ch; + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '/') + { + ch = ScmStreamPeekCh(pStrmInput); + if (ch == '*') + rcExit = vbcppProcessMultiLineComment(pThis, pStrmInput); + else if (ch == '/') + rcExit = vbcppProcessOneLineComment(pThis, pStrmInput); + else + { + pThis->fMaybePreprocessorLine = false; + if (!pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, '/'); + } + } + else if (ch == '#' && pThis->fMaybePreprocessorLine) + { + rcExit = vbcppProcessDirective(pThis, pStrmInput); + pStrmInput = &pThis->pInputStack->StrmInput; + } + else if (ch == '\r' || ch == '\n') + { + if ( ( !pThis->fIf0Mode + && !pThis->fJustDroppedLine) + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput)) + rcExit = vbcppOutputCh(pThis, ch); + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + } + else if (RT_C_IS_SPACE(ch)) + { + if (!pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, ch); + } + else + { + pThis->fMaybePreprocessorLine = false; + if (!pThis->fIf0Mode) + { + if (ch == '"') + rcExit = vbcppProcessStringLitteral(pThis, pStrmInput); + else if (ch == '\'') + rcExit = vbcppProcessCharacterConstant(pThis, pStrmInput); + else if (vbcppIsCIdentifierLeadChar(ch)) + rcExit = vbcppProcessIdentifier(pThis, pStrmInput); + else if (RT_C_IS_DIGIT(ch)) + rcExit = vbcppProcessNumber(pThis, pStrmInput, ch); + else + rcExit = vbcppOutputCh(pThis, ch); + } + } + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + + /* + * Check for errors. + */ + if (rcExit != RTEXITCODE_SUCCESS) + break; + + /* + * Pop the input stack. + */ + PVBCPPINPUT pPopped = pThis->pInputStack; + pThis->pInputStack = pPopped->pUp; + RTMemFree(pPopped); + } + + return rcExit; +} + + +/** + * Opens the input and output streams. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppOpenStreams(PVBCPP pThis) +{ + if (!pThis->pszInput) + return vbcppError(pThis, "Preprocessing the standard input stream is currently not supported"); + + size_t cchName = strlen(pThis->pszInput); + PVBCPPINPUT pInput = (PVBCPPINPUT)RTMemAlloc(RT_UOFFSETOF_DYN(VBCPPINPUT, szName[cchName + 1])); + if (!pInput) + return vbcppError(pThis, "out of memory"); + pInput->pUp = pThis->pInputStack; + pInput->pszSpecified = pInput->szName; + memcpy(pInput->szName, pThis->pszInput, cchName + 1); + pThis->pInputStack = pInput; + int rc = ScmStreamInitForReading(&pInput->StrmInput, pThis->pszInput); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "ScmStreamInitForReading returned %Rrc when opening input file (%s)", + rc, pThis->pszInput); + + rc = ScmStreamInitForWriting(&pThis->StrmOutput, &pInput->StrmInput); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "ScmStreamInitForWriting returned %Rrc", rc); + + pThis->fStrmOutputValid = true; + return RTEXITCODE_SUCCESS; +} + + +/** + * Changes the preprocessing mode. + * + * @param pThis The C preprocessor instance. + * @param enmMode The new mode. + */ +static void vbcppSetMode(PVBCPP pThis, VBCPPMODE enmMode) +{ + switch (enmMode) + { + case kVBCppMode_Standard: + pThis->fKeepComments = false; + pThis->fRespectSourceDefines = true; + pThis->fAllowRedefiningCmdLineDefines = true; + pThis->fPassThruDefines = false; + pThis->fUndecidedConditionals = false; + pThis->fPassThruPragmaD = false; + pThis->fPassThruPragmaSTD = true; + pThis->fPassThruPragmaOther = true; + pThis->fRemoveDroppedLines = false; + pThis->fLineSplicing = true; + pThis->enmIncludeAction = kVBCppIncludeAction_Include; + break; + + case kVBCppMode_Selective: + pThis->fKeepComments = true; + pThis->fRespectSourceDefines = false; + pThis->fAllowRedefiningCmdLineDefines = false; + pThis->fPassThruDefines = true; + pThis->fUndecidedConditionals = true; + pThis->fPassThruPragmaD = true; + pThis->fPassThruPragmaSTD = true; + pThis->fPassThruPragmaOther = true; + pThis->fRemoveDroppedLines = true; + pThis->fLineSplicing = false; + pThis->enmIncludeAction = kVBCppIncludeAction_PassThru; + break; + + case kVBCppMode_SelectiveD: + pThis->fKeepComments = true; + pThis->fRespectSourceDefines = true; + pThis->fAllowRedefiningCmdLineDefines = false; + pThis->fPassThruDefines = false; + pThis->fUndecidedConditionals = false; + pThis->fPassThruPragmaD = true; + pThis->fPassThruPragmaSTD = false; + pThis->fPassThruPragmaOther = false; + pThis->fRemoveDroppedLines = true; + pThis->fLineSplicing = false; + pThis->enmIncludeAction = kVBCppIncludeAction_Drop; + break; + + default: + AssertFailedReturnVoid(); + } + pThis->enmMode = enmMode; +} + + +/** + * Parses the command line options. + * + * @returns Program exit code. Exit on non-success or if *pfExit is set. + * @param pThis The C preprocessor instance. + * @param argc The argument count. + * @param argv The argument vector. + * @param pfExit Pointer to the exit indicator. + */ +static RTEXITCODE vbcppParseOptions(PVBCPP pThis, int argc, char **argv, bool *pfExit) +{ + RTEXITCODE rcExit; + + *pfExit = false; + + /* + * Option config. + */ + static RTGETOPTDEF const s_aOpts[] = + { + { "--define", 'D', RTGETOPT_REQ_STRING }, + { "--include-dir", 'I', RTGETOPT_REQ_STRING }, + { "--undefine", 'U', RTGETOPT_REQ_STRING }, + { "--keep-comments", 'C', RTGETOPT_REQ_NOTHING }, + { "--strip-comments", 'c', RTGETOPT_REQ_NOTHING }, + { "--D-strip", 'd', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetOptState; + int rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertReleaseRCReturn(rc, RTEXITCODE_FAILURE); + + /* + * Process the options. + */ + while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'c': + pThis->fKeepComments = false; + break; + + case 'C': + pThis->fKeepComments = false; + break; + + case 'd': + vbcppSetMode(pThis, kVBCppMode_SelectiveD); + break; + + case 'D': + { + const char *pszEqual = strchr(ValueUnion.psz, '='); + if (pszEqual) + rcExit = vbcppMacroAdd(pThis, ValueUnion.psz, pszEqual - ValueUnion.psz, pszEqual + 1, RTSTR_MAX, true); + else + rcExit = vbcppMacroAdd(pThis, ValueUnion.psz, RTSTR_MAX, "1", 1, true); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + } + + case 'I': + rcExit = vbcppAddInclude(pThis, ValueUnion.psz); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + + case 'U': + rcExit = vbcppMacroUndef(pThis, ValueUnion.psz, RTSTR_MAX, true); + break; + + case 'h': + RTPrintf("No help yet, sorry\n"); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 'V': + { + /* The following is assuming that svn does it's job here. */ + static const char s_szRev[] = "$Revision: 155244 $"; + const char *psz = RTStrStripL(strchr(s_szRev, ' ')); + RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz); + *pfExit = true; + return RTEXITCODE_SUCCESS; + } + + case VINF_GETOPT_NOT_OPTION: + if (!pThis->pszInput) + pThis->pszInput = ValueUnion.psz; + else if (!pThis->pszOutput) + pThis->pszOutput = ValueUnion.psz; + else + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "too many file arguments"); + break; + + + /* + * Errors and bugs. + */ + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Terminates the preprocessor. + * + * This may return failure if an error was delayed. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppTerm(PVBCPP pThis) +{ + /* + * Flush the output first. + */ + if (pThis->fStrmOutputValid) + { + if (pThis->pszOutput) + { + int rc = ScmStreamWriteToFile(&pThis->StrmOutput, "%s", pThis->pszOutput); + if (RT_FAILURE(rc)) + vbcppError(pThis, "ScmStreamWriteToFile failed with %Rrc when writing '%s'", rc, pThis->pszOutput); + } + else + { + int rc = ScmStreamWriteToStdOut(&pThis->StrmOutput); + if (RT_FAILURE(rc)) + vbcppError(pThis, "ScmStreamWriteToStdOut failed with %Rrc", rc); + } + } + + /* + * Cleanup. + */ + while (pThis->pInputStack) + { + ScmStreamDelete(&pThis->pInputStack->StrmInput); + void *pvFree = pThis->pInputStack; + pThis->pInputStack = pThis->pInputStack->pUp; + RTMemFree(pvFree); + } + + ScmStreamDelete(&pThis->StrmOutput); + + RTStrSpaceDestroy(&pThis->StrSpace, vbcppMacroFree, NULL); + pThis->StrSpace = NULL; + + uint32_t i = pThis->cIncludes; + while (i-- > 0) + RTStrFree(pThis->papszIncludes[i]); + RTMemFree(pThis->papszIncludes); + pThis->papszIncludes = NULL; + + return pThis->rcExit; +} + + +/** + * Initializes the C preprocessor instance data. + * + * @param pThis The C preprocessor instance data. + */ +static void vbcppInit(PVBCPP pThis) +{ + vbcppSetMode(pThis, kVBCppMode_Selective); + pThis->cIncludes = 0; + pThis->papszIncludes = NULL; + pThis->pszInput = NULL; + pThis->pszOutput = NULL; + pThis->StrSpace = NULL; + pThis->UndefStrSpace = NULL; + pThis->cCondStackDepth = 0; + pThis->pCondStack = NULL; + pThis->fIf0Mode = false; + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + VBCPP_BITMAP_EMPTY(pThis->bmDefined); + pThis->cCondStackDepth = 0; + pThis->pInputStack = NULL; + RT_ZERO(pThis->StrmOutput); + pThis->rcExit = RTEXITCODE_SUCCESS; + pThis->fStrmOutputValid = false; +} + + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Do the job. The code says it all. + */ + VBCPP This; + vbcppInit(&This); + bool fExit; + RTEXITCODE rcExit = vbcppParseOptions(&This, argc, argv, &fExit); + if (!fExit && rcExit == RTEXITCODE_SUCCESS) + { + rcExit = vbcppOpenStreams(&This); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppPreprocess(&This); + } + + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppTerm(&This); + else + vbcppTerm(&This); + return rcExit; +} + |