diff options
Diffstat (limited to 'src/bldprogs/scmrw-kmk.cpp')
-rw-r--r-- | src/bldprogs/scmrw-kmk.cpp | 2273 |
1 files changed, 2273 insertions, 0 deletions
diff --git a/src/bldprogs/scmrw-kmk.cpp b/src/bldprogs/scmrw-kmk.cpp new file mode 100644 index 00000000..7907150b --- /dev/null +++ b/src/bldprogs/scmrw-kmk.cpp @@ -0,0 +1,2273 @@ +/* $Id: scmrw-kmk.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager, Makefile.kmk/kup. + */ + +/* + * Copyright (C) 2010-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 <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scm.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef enum KMKASSIGNTYPE +{ + kKmkAssignType_Recursive, + kKmkAssignType_Conditional, + kKmkAssignType_Appending, + kKmkAssignType_Prepending, + kKmkAssignType_Simple, + kKmkAssignType_Immediate +} KMKASSIGNTYPE; + +/** Context for scmKmkWordLength. */ +typedef enum +{ + /** Target file or assignment. + * Separators: space, '=', ':' */ + kKmkWordCtx_TargetFileOrAssignment, + /** Target file. + * Separators: space, ':' */ + kKmkWordCtx_TargetFile, + /** Dependency file or (target variable) assignment. + * Separators: space, '=', ':', '|' */ + kKmkWordCtx_DepFileOrAssignment, + /** Dependency file. + * Separators: space, '|' */ + kKmkWordCtx_DepFile, + /** Last context which may do double expansion. */ + kKmkWordCtx_LastDoubleExpansion = kKmkWordCtx_DepFile +} KMKWORDCTX; + +typedef struct KMKWORDSTATE +{ + uint16_t uDepth; + char chOpen; +} KMKWORDSTATE; + +typedef enum KMKTOKEN +{ + kKmkToken_Word = 0, + kKmkToken_Comment, + + /* Conditionals: */ + kKmkToken_ifeq, + kKmkToken_ifneq, + kKmkToken_if1of, + kKmkToken_ifn1of, + kKmkToken_ifdef, + kKmkToken_ifndef, + kKmkToken_if, + kKmkToken_else, + kKmkToken_endif, + + /* Includes: */ + kKmkToken_include, + kKmkToken_sinclude, + kKmkToken_dash_include, + kKmkToken_includedep, + kKmkToken_includedep_queue, + kKmkToken_includedep_flush, + + /* Others: */ + kKmkToken_define, + kKmkToken_endef, + kKmkToken_export, + kKmkToken_unexport, + kKmkToken_local, + kKmkToken_override, + kKmkToken_undefine +} KMKTOKEN; + +typedef struct KMKPARSER +{ + struct + { + KMKTOKEN enmToken; + bool fIgnoreNesting; + size_t iLine; + } aDepth[64]; + unsigned iDepth; + unsigned iActualDepth; + bool fInRecipe; + + /** The EOL type of the current line. */ + SCMEOL enmEol; + /** The length of the current line. */ + size_t cchLine; + /** Pointer to the start of the current line. */ + char const *pchLine; + + /** @name Only used for rule/assignment parsing. + * @{ */ + /** Number of continuation lines at current rule/assignment. */ + uint32_t cLines; + /** Characters in continuation lines at current rule/assignment. */ + size_t cchTotalLine; + /** @} */ + + /** The SCM rewriter state. */ + PSCMRWSTATE pState; + /** The input stream. */ + PSCMSTREAM pIn; + /** The output stream. */ + PSCMSTREAM pOut; + /** The settings. */ + PCSCMSETTINGSBASE pSettings; + /** Scratch buffer. */ + char szBuf[4096]; +} KMKPARSER; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static char const g_szTabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + + +static KMKTOKEN scmKmkIdentifyToken(const char *pchWord, size_t cchWord) +{ + static struct { const char *psz; uint32_t cch; KMKTOKEN enmToken; } s_aTokens[] = + { + { RT_STR_TUPLE("if"), kKmkToken_if }, + { RT_STR_TUPLE("ifeq"), kKmkToken_ifeq }, + { RT_STR_TUPLE("ifneq"), kKmkToken_ifneq }, + { RT_STR_TUPLE("if1of"), kKmkToken_if1of }, + { RT_STR_TUPLE("ifn1of"), kKmkToken_ifn1of }, + { RT_STR_TUPLE("ifdef"), kKmkToken_ifdef }, + { RT_STR_TUPLE("ifndef"), kKmkToken_ifndef }, + { RT_STR_TUPLE("else"), kKmkToken_else }, + { RT_STR_TUPLE("endif"), kKmkToken_endif }, + { RT_STR_TUPLE("include"), kKmkToken_include }, + { RT_STR_TUPLE("sinclude"), kKmkToken_sinclude }, + { RT_STR_TUPLE("-include"), kKmkToken_dash_include }, + { RT_STR_TUPLE("includedep"), kKmkToken_includedep }, + { RT_STR_TUPLE("includedep-queue"), kKmkToken_includedep_queue }, + { RT_STR_TUPLE("includedep-flush"), kKmkToken_includedep_flush }, + { RT_STR_TUPLE("define"), kKmkToken_define }, + { RT_STR_TUPLE("endef"), kKmkToken_endef }, + { RT_STR_TUPLE("export"), kKmkToken_export }, + { RT_STR_TUPLE("unexport"), kKmkToken_unexport }, + { RT_STR_TUPLE("local"), kKmkToken_local }, + { RT_STR_TUPLE("override"), kKmkToken_override }, + { RT_STR_TUPLE("undefine"), kKmkToken_undefine }, + }; + char chFirst = *pchWord; + if ( chFirst == 'i' + || chFirst == 'e' + || chFirst == 'd' + || chFirst == 's' + || chFirst == '-' + || chFirst == 'u' + || chFirst == 'l' + || chFirst == 'o') + { + for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++) + if ( s_aTokens[i].cch == cchWord + && *s_aTokens[i].psz == chFirst + && memcmp(s_aTokens[i].psz, pchWord, cchWord) == 0) + return s_aTokens[i].enmToken; + } +#ifdef VBOX_STRICT + else + for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++) + Assert(chFirst != *s_aTokens[i].psz); +#endif + + if (chFirst == '#') + return kKmkToken_Comment; + return kKmkToken_Word; +} + + +/** + * Modifies the fInRecipe state variable, logging changes in verbose mode. + */ +static void scmKmkSetInRecipe(KMKPARSER *pParser, bool fInRecipe) +{ + if (pParser->fInRecipe != fInRecipe) + ScmVerbose(pParser->pState, 4, "%u: debug: %s\n", + ScmStreamTellLine(pParser->pIn), fInRecipe ? "in-recipe" : "not-in-recipe"); + pParser->fInRecipe = fInRecipe; +} + + +/** + * Gives up on the current line, copying it as it and requesting manual repair. + */ +static bool scmKmkGiveUp(KMKPARSER *pParser, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + ScmFixManually(pParser->pState, "%u: %N\n", ScmStreamTellLine(pParser->pIn), pszFormat, &va); + va_end(va); + + ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol); + return false; +} + + +static bool scmKmkIsLineWithContinuationSlow(const char *pchLine, size_t cchLine) +{ + size_t cchSlashes = 1; + cchLine--; + while (cchSlashes < cchLine && pchLine[cchLine - cchSlashes - 1] == '\\') + cchSlashes++; + return RT_BOOL(cchSlashes & 1); +} + + +DECLINLINE(bool) scmKmkIsLineWithContinuation(const char *pchLine, size_t cchLine) +{ + if (cchLine == 0 || pchLine[cchLine - 1] != '\\') + return false; + return scmKmkIsLineWithContinuationSlow(pchLine, cchLine); +} + + +/** + * Finds the length of a line where line continuation is in play. + * + * @returns Length from start of current line to the final unescaped EOL. + * @param pParser The KMK parser state. + * @param pcLine Where to return the number of lines. Optional. + * @param pcchMaxLeadWord Where to return the max lead word length on + * subsequent lines. Used to help balance multi-line + * 'if' statements (imperfect). Optional. + */ +static size_t scmKmkLineContinuationPeek(KMKPARSER *pParser, uint32_t *pcLines, size_t *pcchMaxLeadWord) +{ + size_t const offSaved = ScmStreamTell(pParser->pIn); + uint32_t cLines = 1; + size_t cchMaxLeadWord = 0; + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + SCMEOL enmEol; + for (;;) + { + /* Return if no line continuation (or end of stream): */ + if ( cchLine == 0 + || !scmKmkIsLineWithContinuation(pchLine, cchLine) + || ScmStreamIsEndOfStream(pParser->pIn)) + { + ScmStreamSeekAbsolute(pParser->pIn, offSaved); + if (pcLines) + *pcLines = cLines; + if (pcchMaxLeadWord) + *pcchMaxLeadWord = cchMaxLeadWord; + return (size_t)(pchLine - pParser->pchLine) + cchLine; + } + + /* Get the next line: */ + pchLine = ScmStreamGetLine(pParser->pIn, &cchLine, &enmEol); + cLines++; + + /* Check the length of the first word if requested: */ + if (pcchMaxLeadWord) + { + size_t offLine = 0; + while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + + size_t const offStartWord = offLine; + while (offLine < cchLine && !RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + + if (offLine - offStartWord > cchMaxLeadWord) + cchMaxLeadWord = offLine - offStartWord; + } + } +} + + +/** + * Checks if the given line contains a comment with the @a pszMarker word in it. + * + * This can be used to disable warnings. + * + * @returns true if this is the case, false if not + * @param pchLine The line. + * @param cchLine The line length. + * @param offLine The current line position, 0 if uncertain. + * @param pszMarker The marker to check for. + * @param cchMarker The length of the marker string. + */ +static bool scmKmkHasCommentMarker(const char *pchLine, size_t cchLine, size_t offLine, const char *pszMarker, size_t cchMarker) +{ + const char *pchCur = (const char *)memchr(&pchLine[offLine], '#', cchLine - RT_MIN(offLine, cchLine)); + if (pchCur) + { + pchCur++; + size_t cchLeft = (size_t)(&pchLine[cchLine] - pchCur); + while (cchLeft >= cchMarker) + { + const char *pchHit = (char *)memchr(pchCur, *pszMarker, cchLeft - cchMarker + 1); + if (!pchHit) + break; + if (memcmp(pchHit, pszMarker, cchMarker) == 0) + return true; + pchCur = pchHit + 1; + cchLeft = (size_t)(&pchLine[cchLine] - pchCur); + } + } + return false; +} + + +/** + * Pushes a if or define on the nesting stack. + */ +static bool scmKmkPushNesting(KMKPARSER *pParser, KMKTOKEN enmToken) +{ + uint32_t iDepth = pParser->iDepth; + if (iDepth + 1 >= RT_ELEMENTS(pParser->aDepth)) + { + ScmError(pParser->pState, VERR_ASN1_TOO_DEEPLY_NESTED /*?*/, + "%u: Too deep if/define nesting!\n", ScmStreamTellLine(pParser->pIn)); + return false; + } + + pParser->aDepth[iDepth].enmToken = enmToken; + pParser->aDepth[iDepth].iLine = ScmStreamTellLine(pParser->pIn); + pParser->aDepth[iDepth].fIgnoreNesting = false; + pParser->iDepth = iDepth + 1; + pParser->iActualDepth += 1; + ScmVerbose(pParser->pState, 5, "%u: debug: nesting %u (token %u)\n", pParser->aDepth[iDepth].iLine, iDepth + 1, enmToken); + return true; +} + + +/** + * Checks if we're inside a define or not. + */ +static bool scmKmkIsInsideDefine(KMKPARSER const *pParser) +{ + unsigned iDepth = pParser->iDepth; + while (iDepth-- > 0) + if (pParser->aDepth[iDepth].enmToken == kKmkToken_define) + return true; + return false; +} + + +/** + * Skips a string stopping at @a chStop1 or @a chStop2, taking $() and ${} into + * account. + */ +static size_t scmKmkSkipExpString(const char *pchLine, size_t cchLine, size_t off, char chStop1, char chStop2 = '\0') +{ + unsigned iExpDepth = 0; + char ch; + while ( off < cchLine + && (ch = pchLine[off]) + && ( (ch != chStop1 && ch != chStop2) + || iExpDepth > 0)) + { + off++; + if (ch == '$') + { + ch = pchLine[off]; + if (ch == '(' || ch == '{') + { + iExpDepth++; + off++; + } + } + else if ((ch == ')' || ch == '}') && iExpDepth > 0) + iExpDepth--; + } + return off; +} + + +/** + * Finds the length of the word (file) @a offStart. + * + * This only takes one line into account, so variable expansions (function + * calls) spanning multiple lines will be handled as one word per line with help + * from @a pState. This allows the caller to properly continutation intend the + * additional lines. + * + * @returns Length of word starting at @a offStart. Zero if there is whitespace + * at given offset or it's beyond the end of the line (both cases will + * assert). + * @param pchLine The line. + * @param cchLine The line length. + * @param offStart Offset to the start of the word. + * @param pState Where multiline variable expansion is tracked. + */ +static size_t scmKmkWordLength(const char *pchLine, size_t cchLine, size_t offStart, KMKWORDCTX enmCtx, KMKWORDSTATE *pState) +{ + AssertReturn(offStart < cchLine && !RT_C_IS_BLANK(pchLine[offStart]), 0); + + /* + * Drop any line continuation slash from the line length, so we don't count + * it into the word length. Also, any spaces preceeding it (for multiline + * variable function expansion). ASSUMES no trailing slash escaping. + */ + if (cchLine > 0 && pchLine[cchLine - 1] == '\\') + do + cchLine--; + while (cchLine > offStart && RT_C_IS_SPACE(pchLine[cchLine - 1])); + + /* + * If we were inside a variable function expansion, continue till we reach the end. + * This kind of duplicates the code below. + */ + size_t off = offStart; + if (pState->uDepth > 0) + { + Assert(pState->chOpen == '(' || pState->chOpen == '{'); + char const chOpen = pState->chOpen; + char const chClose = chOpen == '(' ? ')' : '}'; + unsigned uDepth = pState->uDepth; + for (;;) + { + char ch; + if (off < cchLine) + ch = pchLine[off++]; + else /* Reached the end while still inside the expansion. */ + { + pState->chOpen = chOpen; + pState->uDepth = (uint16_t)uDepth; + return cchLine - offStart; + } + if (ch == chOpen) + uDepth++; + else if (ch == chClose && --uDepth == 0) + break; + } + pState->uDepth = 0; + pState->chOpen = 0; + } + + /* + * Process till we find blank or end of the line. + */ + while (off < cchLine) + { + char ch = pchLine[off]; + if (RT_C_IS_BLANK(ch)) + break; + + if (ch == '$') + { + /* + * Skip variable expansion. ASSUMING double expansion being enabled + * for rules, we respond to both $() and $$() here, $$$$() + */ + size_t cDollars = 0; + do + { + off++; + if (off >= cchLine) + return cchLine - offStart; + cDollars++; + ch = pchLine[off]; + } while (ch == '$'); + if ((cDollars & 1) || (cDollars == 2 && enmCtx <= kKmkWordCtx_LastDoubleExpansion)) + { + char const chOpen = ch; + if (ch == '(' || ch == '{') + { + char const chClose = chOpen == '(' ? ')' : '}'; + unsigned uDepth = 1; + off++; + for (;;) + { + if (off < cchLine) + ch = pchLine[off++]; + else /* Reached the end while inside the expansion. */ + { + pState->chOpen = chOpen; + pState->uDepth = (uint16_t)uDepth; + return cchLine - offStart; + } + if (ch == chOpen) + uDepth++; + else if (ch == chClose && --uDepth == 0) + break; + } + } + else if (cDollars & 1) + off++; /* $X */ + } + continue; + } + else if (ch == ':') + { + /* + * Check for plain driver letter, omitting the archive member variant. + */ + if (off - offStart != 1 || !RT_C_IS_ALPHA(pchLine[off - 1])) + { + if (off == offStart) + { + /* We need to check for single and double colon rules as well as + simple and immediate assignments here. */ + off++; + if (pchLine[off] == ':') + { + off++; + if (pchLine[off] == '=') + { + if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment) + return 3; /* ::= - immediate assignment. */ + off++; + } + else if (enmCtx != kKmkWordCtx_DepFile) + return 2; /* :: - double colon rule */ + } + else if (pchLine[off] == '=') + { + if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment) + return 2; /* := - simple assignment. */ + off++; + } + else if (enmCtx != kKmkWordCtx_DepFile) + return 1; /* : - regular rule. */ + continue; + } + /* ':' is a separator except in DepFile context. */ + else if (enmCtx != kKmkWordCtx_DepFile) + return off - offStart; + } + } + else if (ch == '=') + { + /* + * Assignment. We check for the previous character too so we'll catch + * append, prepend and conditional assignments. Simple and immediate + * assignments are handled above. + */ + if ( enmCtx == kKmkWordCtx_TargetFileOrAssignment + || enmCtx == kKmkWordCtx_DepFileOrAssignment) + { + if (off > offStart) + { + ch = pchLine[off - 1]; + if (ch == '?' || ch == '+' || ch == '>') + off = off - 1 == offStart + ? off + 2 /* return '+=', '?=', '<=' */ + : off - 1; /* up to '+=', '?=', '<=' */ + else + Assert(ch != ':'); /* handled above */ + } + else + off++; /* '=' */ + return off - offStart; + } + } + else if (ch == '|') + { + /* + * This is rather straight forward. + */ + if (enmCtx == kKmkWordCtx_DepFileOrAssignment || enmCtx == kKmkWordCtx_DepFile) + { + if (off == offStart) + return 1; + return off - offStart; + } + } + off++; + } + return off - offStart; +} + + +static bool scmKmkTailComment(KMKPARSER *pParser, const char *pchLine, size_t cchLine, size_t offSrc, char **ppszDst) +{ + /* Wind back offSrc to the first blank space (not all callers can do this). */ + Assert(offSrc <= cchLine); + while (offSrc > 0 && RT_C_IS_SPACE(pchLine[offSrc - 1])) + offSrc--; + size_t const offSrcStart = offSrc; + + /* Skip blanks. */ + while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc])) + offSrc++; + if (offSrc >= cchLine) + return true; + + /* Is it a comment? */ + char *pszDst = *ppszDst; + if (pchLine[offSrc] == '#') + { + /* Try preserve the start column number. */ +/** @todo tabs */ + size_t const offDst = pszDst - pParser->szBuf; + if (offDst < offSrc) + { + memset(pszDst, ' ', offSrc - offDst); + pszDst += offSrc - offDst; + } + else if (offSrc != offSrcStart) + *pszDst++ = ' '; + + *ppszDst = pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchLine - offSrc); + return false; /*dummy*/ + } + + /* Complain and copy out the text unmodified. */ + ScmError(pParser->pState, VERR_PARSE_ERROR, "%u:%u: Expected comment, found: %.*s", + ScmStreamTellLine(pParser->pIn), offSrc, cchLine - offSrc, &pchLine[offSrc]); + *ppszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchLine - offSrcStart); + return false; /*dummy*/ +} + + +/** + * Deals with: ifeq, ifneq, if1of and ifn1of + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleIfParentheses(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse) +{ + const char * const pchLine = pParser->pchLine; + size_t const cchLine = pParser->cchLine; + uint32_t const cchIndent = pParser->iActualDepth + - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting); + + /* + * Push it onto the stack. All these nestings are relevant. + */ + if (!fElse) + { + if (!scmKmkPushNesting(pParser, enmToken)) + return false; + } + else + { + pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken; + pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn); + } + + /* + * We do not allow line continuation for these. + */ + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]); + + /* + * We stage the modified line in the buffer, so check that the line isn't + * too long (it seriously should be). + */ + if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", cchToken, &pchLine[offToken], cchLine); + char *pszDst = pParser->szBuf; + + /* + * Emit indent and initial token. + */ + memset(pszDst, ' ', cchIndent); + pszDst += cchIndent; + + if (fElse) + pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else ")); + + memcpy(pszDst, &pchLine[offToken], cchToken); + pszDst += cchToken; + + size_t offSrc = offToken + cchToken; + + /* + * There shall be exactly one space between the token and the opening parenthesis. + */ + if (pchLine[offSrc] == ' ' && pchLine[offSrc + 1] == '(') + offSrc += 2; + else + { + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + offSrc++; + if (pchLine[offSrc] != '(') + return scmKmkGiveUp(pParser, "Expected '(' to follow '%.*s'", cchToken, &pchLine[offToken]); + offSrc++; + } + *pszDst++ = ' '; + *pszDst++ = '('; + + /* + * Skip spaces after the opening parenthesis. + */ + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + offSrc++; + + /* + * Work up to the ',' separator. It shall likewise not be preceeded by any spaces. + * Need to take $(func 1,2,3) calls into account here, so we trac () and {} while + * skipping ahead. + */ + if (pchLine[offSrc] != ',') + { + size_t const offSrcStart = offSrc; + offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ','); + if (pchLine[offSrc] != ',') + return scmKmkGiveUp(pParser, "Expected ',' somewhere after '%.*s('", cchToken, &pchLine[offToken]); + + size_t cchCopy = offSrc - offSrcStart; + while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1])) + cchCopy--; + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy); + } + /* 'if1of(, stuff)' does not make sense in committed code: */ + else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of) + return scmKmkGiveUp(pParser, "Left set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]); + offSrc++; + *pszDst++ = ','; + + /* + * For if1of and ifn1of we require a space after the comma, whereas ifeq and + * ifneq shall not have any blanks. This is to help tell them apart. + */ + if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of) + { + *pszDst++ = ' '; + if (pchLine[offSrc] == ' ') + offSrc++; + } + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + offSrc++; + + if (pchLine[offSrc] != ')') + { + size_t const offSrcStart = offSrc; + offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ')'); + if (pchLine[offSrc] != ')') + return scmKmkGiveUp(pParser, "No closing parenthesis for '%.*s'?", cchToken, &pchLine[offToken]); + + size_t cchCopy = offSrc - offSrcStart; + while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1])) + cchCopy--; + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy); + } + /* 'if1of(stuff, )' does not make sense in committed code: */ + else if ( (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of) + && !scmKmkHasCommentMarker(pchLine, cchLine, offSrc, RT_STR_TUPLE("scm:ignore-empty-if1of-set"))) + return scmKmkGiveUp(pParser, "Right set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]); + offSrc++; + *pszDst++ = ')'; + + /* + * Handle comment. + */ + if (offSrc < cchLine) + scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst); + + /* + * Done. + */ + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return false; /* dummy */ +} + + +/** + * Deals with: if, ifdef and ifndef + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleIfSpace(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse) +{ + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + uint32_t const cchIndent = pParser->iActualDepth + - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting); + + /* + * Push it onto the stack. + * + * For ifndef we ignore the outmost ifndef in non-Makefile.kmk files, if + * the define matches the typical pattern for a file blocker. + */ + bool fIgnoredNesting = false; + if (!fElse) + { + if (!scmKmkPushNesting(pParser, enmToken)) + return false; + if (enmToken == kKmkToken_ifndef) + { + /** @todo */ + } + } + else + { + pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken; + pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn); + } + + /* + * We do not allow line continuation for these. + */ + uint32_t cLines = 1; + size_t cchMaxLeadWord = 0; + size_t cchTotalLine = cchLine; + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + { + if (enmToken != kKmkToken_if) + return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]); + cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord); + } + + /* + * We stage the modified line in the buffer, so check that the line isn't + * too long (plain if can be long, but not ifndef/ifdef). + */ + if (cchTotalLine + pParser->iActualDepth + 32 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", + cchToken, &pchLine[offToken], cchTotalLine); + char *pszDst = pParser->szBuf; + + /* + * Emit indent and initial token. + */ + memset(pszDst, ' ', cchIndent); + pszDst += cchIndent; + + if (fElse) + pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else ")); + + memcpy(pszDst, &pchLine[offToken], cchToken); + pszDst += cchToken; + + size_t offSrc = offToken + cchToken; + + /* + * ifndef/ifdef shall have exactly one space. For 'if' we allow up to 4, but + * we'll deal with that further down. + */ + size_t cchSpaces = 0; + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + { + cchSpaces++; + offSrc++; + } + if (cchSpaces == 0) + return scmKmkGiveUp(pParser, "Nothing following '%.*s' or bogus line continuation?", cchToken, &pchLine[offToken]); + *pszDst++ = ' '; + + /* + * For ifdef and ifndef there now comes a single word. + */ + if (enmToken != kKmkToken_if) + { + size_t const offSrcStart = offSrc; + offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ' ', '\t'); /** @todo probably not entirely correct */ + if (offSrc == offSrcStart) + return scmKmkGiveUp(pParser, "No word following '%.*s'?", cchToken, &pchLine[offToken]); + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], offSrc - offSrcStart); + } + /* + * While for 'if' things are more complicated, especially if it spans more + * than one line. + */ + else if (cLines <= 1) + { + /* Single line expression: Just assume the expression goes up to the + EOL or comment hash. Strip and copy as-is for now. */ + const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc); + size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc; + while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1])) + cchExpr--; + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr); + offSrc += cchExpr; + } + else + { + /* Multi line expression: We normalize leading whitespace using + cchMaxLeadWord for now. Expression on line 2+ are indented by two + extra characters, because we'd otherwise be puttin the operator on + the same level as the 'if', which would be confusing. Thus: + + if expr1 + + expr2 + endif + + if expr1 + || expr2 + endif + + if expr3 + vtg expr4 + endif + + We do '#' / EOL handling for the final line the same way as above. + + Later we should add the ability to rework the expression properly, + making sure new lines starts with operators and such. */ + /** @todo Implement simples expression parser and indenter, possibly also + * removing unnecessary parentheses. Can be shared with C/C++. */ + if (cchMaxLeadWord > 3) + return scmKmkGiveUp(pParser, + "Bogus multi-line 'if' expression! Extra lines must start with operator (cchMaxLeadWord=%u).", + cchMaxLeadWord); + memset(pszDst, ' ', cchMaxLeadWord); + pszDst += cchMaxLeadWord; + + size_t cchSrcContIndent = offToken + 2; + for (uint32_t iSubLine = 0; iSubLine < cLines - 1; iSubLine++) + { + /* Trim the line. */ + size_t offSrcEnd = cchLine; + Assert(pchLine[offSrcEnd - 1] == '\\'); + offSrcEnd--; + + if (pchLine[offSrcEnd - 1] == '\\') + return scmKmkGiveUp(pParser, "Escaped '\\' before line continuation in 'if' expression is not allowed!"); + + while (offSrcEnd > offSrc && RT_C_IS_BLANK(pchLine[offSrcEnd - 1])) + offSrcEnd--; + + /* Comments with line continuation is not allowed in commited makefiles. */ + if (offSrc < offSrcEnd && memchr(&pchLine[offSrc], '#', cchLine - offSrc) != NULL) + return scmKmkGiveUp(pParser, "Comment in multi-line 'if' expression is not allowed to start before the final line!"); + + /* Output it. */ + if (offSrc < offSrcEnd) + { + if (iSubLine > 0 && offSrc > cchSrcContIndent) + { + memset(pszDst, ' ', offSrc - cchSrcContIndent); + pszDst += offSrc - cchSrcContIndent; + } + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], offSrcEnd - offSrc); + *pszDst++ = ' '; + } + else if (iSubLine == 0) + return scmKmkGiveUp(pParser, "Expected expression after 'if', not line continuation!"); + *pszDst++ = '\\'; + *pszDst = '\0'; + size_t cchDst = (size_t)(pszDst - pParser->szBuf); + ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol); + + /* + * Fetch the next line and start processing it. + */ + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + if (!pchLine) + { + ScmError(pParser->pState, VERR_INTERNAL_ERROR_3, "ScmStreamGetLine unexpectedly returned NULL!"); + return false; + } + cchLine = pParser->cchLine; + + /* Skip leading whitespace and adjust the source continuation indent: */ + offSrc = 0; + while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc])) + offSrc++; + /** @todo tabs */ + + if (iSubLine == 0) + cchSrcContIndent = offSrc; + + /* Initial indent: */ + pszDst = pParser->szBuf; + memset(pszDst, ' ', cchIndent + 2); + pszDst += cchIndent + 2; + } + + /* Output the expression on the final line. */ + const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc); + size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc; + while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1])) + cchExpr--; + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr); + offSrc += cchExpr; + } + + + /* + * Handle comment. + * + * Here we check for the "scm:ignore-nesting" directive that makes us not + * add indentation for this directive. We do this on the destination buffer + * as that can be zero terminated and is therefore usable with strstr. + */ + if (offSrc >= cchLine) + *pszDst = '\0'; + else + { + char * const pszDstSrc = pszDst; + scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst); + *pszDst = '\0'; + + /* Check for special comment making us ignore the nesting. We do this + on the destination buffer since it's zero terminated allowing normal + strstr use. */ + if (!fIgnoredNesting && strstr(pszDstSrc, "scm:ignore-nesting") != NULL) + { + pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting = true; + pParser->iActualDepth--; + ScmVerbose(pParser->pState, 5, "%u: debug: ignoring nesting - actual depth: %u\n", + pParser->aDepth[pParser->iDepth - 1].iLine, pParser->iActualDepth); + } + } + + /* + * Done. + */ + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return false; /* dummy */ +} + + +/** + * Deals with: else + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleElse(KMKPARSER *pParser, size_t offToken) +{ + const char * const pchLine = pParser->pchLine; + size_t const cchLine = pParser->cchLine; + + if (pParser->iDepth < 1) + return scmKmkGiveUp(pParser, "Lone 'else'"); + uint32_t const cchIndent = pParser->iActualDepth + - (pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting); + + /* + * Look past the else and check if there any ifxxx token following it. + */ + size_t offSrc = offToken + 4; + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + offSrc++; + if (offSrc < cchLine) + { + size_t cchWord = 0; + while (offSrc + cchWord < cchLine && RT_C_IS_ALNUM(pchLine[offSrc + cchWord])) + cchWord++; + if (cchWord) + { + KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offSrc], cchWord); + switch (enmToken) + { + case kKmkToken_ifeq: + case kKmkToken_ifneq: + case kKmkToken_if1of: + case kKmkToken_ifn1of: + return scmKmkHandleIfParentheses(pParser, offSrc, enmToken, cchWord, true /*fElse*/); + + case kKmkToken_ifdef: + case kKmkToken_ifndef: + case kKmkToken_if: + return scmKmkHandleIfSpace(pParser, offSrc, enmToken, cchWord, true /*fElse*/); + + default: + break; + } + } + } + + /* + * We do not allow line continuation for these. + */ + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + return scmKmkGiveUp(pParser, "Line continuation not allowed with 'else' directive."); + + /* + * We stage the modified line in the buffer, so check that the line isn't + * too long (it seriously should be). + */ + if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine); + char *pszDst = pParser->szBuf; + + /* + * Emit indent and initial token. + */ + memset(pszDst, ' ', cchIndent); + pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("else")); + + offSrc = offToken + 4; + + /* + * Handle comment. + */ + if (offSrc < cchLine) + scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst); + + /* + * Done. + */ + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return false; /* dummy */ +} + + +/** + * Deals with: endif + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleEndif(KMKPARSER *pParser, size_t offToken) +{ + const char * const pchLine = pParser->pchLine; + size_t const cchLine = pParser->cchLine; + + /* + * Pop a nesting. + */ + if (pParser->iDepth < 1) + return scmKmkGiveUp(pParser, "Lone 'endif'"); + uint32_t iDepth = pParser->iDepth - 1; + pParser->iDepth = iDepth; + if (!pParser->aDepth[iDepth].fIgnoreNesting) + { + AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++); + pParser->iActualDepth -= 1; + } + ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endif)\n", + ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth); + uint32_t const cchIndent = pParser->iActualDepth; + + /* + * We do not allow line continuation for these. + */ + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + return scmKmkGiveUp(pParser, "Line continuation not allowed with 'endif' directive."); + + /* + * We stage the modified line in the buffer, so check that the line isn't + * too long (it seriously should be). + */ + if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine); + char *pszDst = pParser->szBuf; + + /* + * Emit indent and initial token. + */ + memset(pszDst, ' ', cchIndent); + pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("endif")); + + size_t offSrc = offToken + 5; + + /* + * Handle comment. + */ + if (offSrc < cchLine) + scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst); + + /* + * Done. + */ + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return false; /* dummy */ +} + + +/** + * Passing thru any line continuation lines following the current one. + */ +static bool scmKmkPassThruLineContinuationLines(KMKPARSER *pParser) +{ + while (scmKmkIsLineWithContinuation(pParser->pchLine, pParser->cchLine)) + { + pParser->pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + if (!pParser->pchLine) + break; + ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol); + } + return false; /* dummy */ +} + + +/** + * For dealing with a directive w/o special formatting rules (yet). + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleSimple(KMKPARSER *pParser, size_t offToken, bool fIndentIt = true) +{ + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + uint32_t const cchIndent = fIndentIt ? pParser->iActualDepth : 0; + + /* + * Just reindent the statement. + */ + ScmStreamWrite(pParser->pOut, g_szSpaces, cchIndent); + ScmStreamWrite(pParser->pOut, &pchLine[offToken], cchLine - offToken); + ScmStreamPutEol(pParser->pOut, pParser->enmEol); + + /* + * Check for line continuation and output concatenated lines. + */ + scmKmkPassThruLineContinuationLines(pParser); + return false; /* dummy */ +} + + +static bool scmKmkHandleDefine(KMKPARSER *pParser, size_t offToken) +{ + scmKmkHandleSimple(pParser, offToken); + + /* Hack Alert! Start out parsing the define in recipe mode. + + Technically, we shouldn't evaluate the content of a define till it's + used. However, we ASSUME they are either makefile code snippets or + recipe templates. */ + scmKmkPushNesting(pParser, kKmkToken_define); + scmKmkSetInRecipe(pParser, true); + return false; +} + + +static bool scmKmkHandleEndef(KMKPARSER *pParser, size_t offToken) +{ + /* Leaving a define resets the recipt mode. */ + scmKmkSetInRecipe(pParser, false); + + /* + * Pop a nesting. + */ + if (pParser->iDepth < 1) + return scmKmkGiveUp(pParser, "Lone 'endef'"); + uint32_t iDepth = pParser->iDepth - 1; + if (pParser->aDepth[iDepth].enmToken != kKmkToken_define) + return scmKmkGiveUp(pParser, "Unpexected 'endef', expected 'endif' for line %u", pParser->aDepth[iDepth].iLine); + pParser->iDepth = iDepth; + if (!pParser->aDepth[iDepth].fIgnoreNesting) + { + AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++); + pParser->iActualDepth -= 1; + } + ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endef)\n", + ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth); + + return scmKmkHandleSimple(pParser, offToken); +} + + +/** + * Checks for escaped trailing slashes on a line, giving up and asking the + * developer to fix those manually. + * + * @returns true if we gave up. false if no escaped slashed and we didn't. + */ +static bool scmKmkGiveUpIfTrailingEscapedSlashed(KMKPARSER *pParser, const char *pchLine, size_t cchLine) +{ + if (cchLine > 2 && pchLine[cchLine - 2] == '\\' && pchLine[cchLine - 1] == '\\') + { + scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!"); + return true; + } + return false; +} + +/** + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleAssignment2(KMKPARSER *pParser, size_t offVarStart, size_t offVarEnd, KMKASSIGNTYPE enmType, + size_t offAssignOp, unsigned fFlags) +{ + unsigned const cchIndent = pParser->iActualDepth; + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + uint32_t const cLines = pParser->cLines; + uint32_t iSubLine = 0; + + RT_NOREF(fFlags); + Assert(offVarStart < cchLine); + Assert(offVarEnd <= cchLine); + Assert(offVarStart < offVarEnd); + Assert(!RT_C_IS_SPACE(pchLine[offVarStart])); + Assert(!RT_C_IS_SPACE(pchLine[offVarEnd - 1])); + + /* Assignments takes us out of recipe mode. */ + ScmVerbose(pParser->pState, 6, "%u: debug: assignment\n", ScmStreamTellLine(pParser->pIn)); + scmKmkSetInRecipe(pParser, false); + + /* This is too much hazzle to deal with. */ + if (cLines > 1 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + if (cchLine + 64 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long!"); + + /* + * Indent and output the variable name. + */ + char *pszDst = pParser->szBuf; + memset(pszDst, ' ', cchIndent); + pszDst += cchIndent; + pszDst = (char *)mempcpy(pszDst, &pchLine[offVarStart], offVarEnd - offVarStart); + + /* + * Try preserve the assignment operator position, but make sure we've got a + * space in front of it. + */ + if (offAssignOp < cchLine) + { + size_t offDst = (size_t)(pszDst - pParser->szBuf); + size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings); + if (offDst < offEffAssignOp) + { + size_t cchSpacesToWrite = offEffAssignOp - offDst; + memset(pszDst, ' ', cchSpacesToWrite); + pszDst += cchSpacesToWrite; + } + else + *pszDst++ = ' '; + } + else + { + /* Pull up the assignment operator to the variable line. */ + *pszDst++ = ' '; + + /* Eat up lines till we hit the operator. */ + while (offAssignOp < cchLine) + { + const char * const pchPrevLine = pchLine; + Assert(iSubLine + 1 < cLines); + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Adjust offAssignOp: */ + offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine; + Assert(offAssignOp < ~(size_t)0 / 2); + } + + if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long!"); + } + + /* + * Emit the operator. + */ + size_t offLine = offAssignOp; + switch (enmType) + { + default: + AssertReleaseFailed(); + RT_FALL_THRU(); + case kKmkAssignType_Recursive: + *pszDst++ = '='; + Assert(pchLine[offLine] == '='); + offLine++; + break; + case kKmkAssignType_Conditional: + *pszDst++ = '?'; + *pszDst++ = '='; + Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '='); + offLine += 2; + break; + case kKmkAssignType_Appending: + *pszDst++ = '+'; + *pszDst++ = '='; + Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '='); + offLine += 2; + break; + case kKmkAssignType_Prepending: + *pszDst++ = '<'; + *pszDst++ = '='; + Assert(pchLine[offLine] == '<'); Assert(pchLine[offLine + 1] == '='); + offLine += 2; + break; + case kKmkAssignType_Immediate: + *pszDst++ = ':'; + Assert(pchLine[offLine] == ':'); + offLine++; + RT_FALL_THRU(); + case kKmkAssignType_Simple: + *pszDst++ = ':'; + *pszDst++ = '='; + Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '='); + offLine += 2; + break; + } + + /* + * Skip space till we hit the value or comment. + */ + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + +/** @todo this block can probably be merged into the final loop below. */ + unsigned cPendingEols = 0; + while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\') + { + *pszDst++ = ' '; + *pszDst++ = '\\'; + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!"); + } + cPendingEols = 1; + + /* Skip indent/whitespace. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + } + + /* + * Okay, we've gotten to the value / comment part. + */ + for (;;) + { + /* + * The end? Flush what we've got. + */ + if (offLine == cchLine) + { + Assert(iSubLine + 1 == cLines); + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + if (cPendingEols > 0) + ScmStreamPutEol(pParser->pOut, pParser->enmEol); + return false; /* dummy */ + } + + /* + * Output any non-comment stuff, stripping off newlines. + */ + const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine); + if (pchHash != &pchLine[offLine]) + { + /* Add space or flush pending EOLs. */ + if (!cPendingEols) + *pszDst++ = ' '; + else + { + unsigned iEol = 0; + cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */ + do + { + if (iEol++ == 0) /* skip this for the 2nd empty line. */ + *pszDst++ = ' '; + *pszDst++ = '\\'; + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + + pszDst = pParser->szBuf; + memset(pszDst, ' ', cchIndent); + pszDst += cchIndent; + *pszDst++ = '\t'; + cPendingEols--; + } while (cPendingEols > 0); + } + + /* Strip backwards. */ + size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines); + size_t offValueEnd = offValueEnd2; + while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1])) + offValueEnd--; + Assert(offValueEnd > offLine); + + /* Append the value part we found. */ + pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine); + offLine = offValueEnd2; + } + + /* + * If we found a comment hash, emit it and whatever follows just as-is w/o + * any particular reformatting. Comments within a variable definition are + * usually to disable portitions of a property like _DEFS or _SOURCES. + */ + if (pchHash != NULL) + { + if (cPendingEols == 0) + scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst); + size_t const cchDst = (size_t)(pszDst - pParser->szBuf); + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol); + + if (cPendingEols > 1) + ScmStreamPutEol(pParser->pOut, pParser->enmEol); + + if (cPendingEols > 0) + ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol); + scmKmkPassThruLineContinuationLines(pParser); + return false; /* dummy */ + } + + /* + * Fetch another line, if we've got one. + */ + if (iSubLine + 1 >= cLines) + Assert(offLine == cchLine); + else + { + Assert(offLine + 1 == cchLine); + while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\') + { + *pszDst++ = ' '; + *pszDst++ = '\\'; + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + if (cPendingEols > 1) + ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!"); + return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!"); + } + cPendingEols++; + + /* Deal with indent/whitespace. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + } + } + } +} + + +/** + * A rule. + * + * This is a bit involved. Sigh. + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon) +{ + SCMSTREAM *pOut = pParser->pOut; + unsigned const cchIndent = pParser->iActualDepth; + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + Assert(offFirstWord < cchLine); + uint32_t const cLines = pParser->cLines; + uint32_t iSubLine = 0; + + /* Following this, we'll be in recipe-mode. */ + ScmVerbose(pParser->pState, 4, "%u: debug: start rule\n", ScmStreamTellLine(pParser->pIn)); + scmKmkSetInRecipe(pParser, true); + + /* This is too much hazzle to deal with. */ + if (cLines > 0 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Too special case. */ + if (offColon <= offFirstWord) + return scmKmkGiveUp(pParser, "Missing target file before colon!"); + + /* + * Indent it. + */ + ScmStreamWrite(pOut, g_szSpaces, cchIndent); + size_t offLine = offFirstWord; + + /* + * Process word by word past the colon, taking new lines into account. + */ + KMKWORDSTATE WordState = { 0, 0 }; + KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment; + unsigned cPendingEols = 0; + for (;;) + { + /* + * Output the next word. + */ + size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState); + Assert(offLine + cchWord <= offColon); + ScmStreamWrite(pOut, &pchLine[offLine], cchWord); + offLine += cchWord; + + /* Skip whitespace (if any). */ + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Have we reached the colon already? */ + if (offLine >= offColon) + { + Assert(pchLine[offLine] == ':'); + Assert(!fDoubleColon || pchLine[offLine + 1] == ':'); + offLine += fDoubleColon ? 2 : 1; + + ScmStreamPutCh(pOut, ':'); + if (fDoubleColon) + ScmStreamPutCh(pOut, ':'); + break; + } + + /* Deal with new line and emit indentation. */ + if (offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + /* Get the next input line. */ + for (;;) + { + const char * const pchPrevLine = pchLine; + Assert(iSubLine + 1 < cLines); + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Adjust offColon: */ + offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine; + Assert(offColon < ~(size_t)0 / 2); + + /* Skip leading spaces. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Just drop empty lines. */ + if (offLine + 1 == cchLine && pchLine[offLine] == '\\') + continue; + + /* Complete the current line and emit indent, unless we reached the colon: */ + if (offLine >= offColon) + { + Assert(pchLine[offLine] == ':'); + Assert(!fDoubleColon || pchLine[offLine + 1] == ':'); + offLine += fDoubleColon ? 2 : 1; + + ScmStreamPutCh(pOut, ':'); + if (fDoubleColon) + ScmStreamPutCh(pOut, ':'); + + cPendingEols = 1; + } + else + { + ScmStreamWrite(pOut, RT_STR_TUPLE(" \\")); + ScmStreamPutEol(pOut, pParser->enmEol); + ScmStreamWrite(pOut, g_szSpaces, cchIndent); + if (WordState.uDepth > 0) + ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1)); + } + break; + } + if (offLine >= offColon) + break; + } + else + ScmStreamPutCh(pOut, ' '); + enmCtx = kKmkWordCtx_TargetFile; + } + + /* + * We're immediately past the colon now, so eat whitespace and newlines and + * whatever till we get to a solid word or the end of the line. + */ + /* Skip spaces - there should be exactly one. */ + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Deal with new lines: */ + while (offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + cPendingEols = 1; + + Assert(iSubLine + 1 < cLines); + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Skip leading spaces. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Just drop empty lines. */ + if (offLine + 1 == cchLine && pchLine[offLine] == '\\') + continue; + } + + /* + * Special case: No dependencies. + */ + if (offLine == cchLine && iSubLine + 1 >= cLines) + { + ScmStreamPutEol(pOut, pParser->enmEol); + return false /*dummy*/; + } + + /* + * Work the dependencies word for word. Indent in spaces + two tabs. + * (Pattern rules will also end up here, but we'll just ignore that for now.) + */ + enmCtx = kKmkWordCtx_DepFileOrAssignment; + for (;;) + { + /* Indent the next word. */ + if (cPendingEols == 0) + ScmStreamPutCh(pOut, ' '); + else + { + ScmStreamWrite(pOut, RT_STR_TUPLE(" \\")); + ScmStreamPutEol(pOut, pParser->enmEol); + ScmStreamWrite(pOut, g_szSpaces, cchIndent); + ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t")); + if (cPendingEols > 1) + { + ScmStreamWrite(pOut, RT_STR_TUPLE("\\")); + ScmStreamPutEol(pOut, pParser->enmEol); + ScmStreamWrite(pOut, g_szSpaces, cchIndent); + ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t")); + } + cPendingEols = 0; + } + if (WordState.uDepth > 0) + ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1)); + + /* Get the next word and output it. */ + size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState); + Assert(offLine + cchWord <= cchLine); + + ScmStreamWrite(pOut, &pchLine[offLine], cchWord); + offLine += cchWord; + + /* Skip whitespace (if any). */ + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Deal with new line and emit indentation. */ + if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + /* Get the next input line. */ + for (;;) + { + Assert(iSubLine + 1 < cLines); + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Skip leading spaces. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */ + cPendingEols++; + if (offLine + 1 == cchLine && pchLine[offLine] == '\\') + continue; + break; + } + } + + if (offLine >= cchLine) + { + /* End of input. */ +/** @todo deal with comments */ + Assert(iSubLine + 1 == cLines); + ScmStreamPutEol(pOut, pParser->enmEol); + return false; /* dummmy */ + } + enmCtx = kKmkWordCtx_DepFile; + } +} + + +/** + * Checks if the (extended) line is a variable assignment. + * + * We scan past line continuation stuff here as the assignment operator could be + * on the next line, even if that's very unlikely it is recommened by the coding + * guide lines if the line needs to be split. Fortunately, though, the caller + * already removes empty empty leading lines, so we only have to consider the + * line continuation issue if no '=' was found on the first line. + * + * @returns Modified or not. + * @param pParser The parser. + * @param cLines Number of lines to consider. + * @param cchTotalLine Total length of all the lines to consider. + * @param offWord Where the first word of the line starts. + * @param pfIsAssignment Where to return whether this is an assignment or + * not. + */ +static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord) +{ + const char *pchLine = pParser->pchLine; + size_t const cchTotalLine = pParser->cchTotalLine; + + /* + * Scan words till we find ':' or '='. + */ + uint32_t iWord = 0; + size_t offCurWord = offWord; + size_t offEndPrev = 0; + size_t offLine = offWord; + while (offLine < cchTotalLine) + { + char ch = pchLine[offLine++]; + if (ch == '$') + { + /* + * Skip variable expansion. + */ + char const chOpen = pchLine[offLine++]; + if (chOpen == '(' || chOpen == '{') + { + char const chClose = chOpen == '(' ? ')' : '}'; + unsigned cDepth = 1; + while (offLine < cchTotalLine) + { + ch = pchLine[offLine++]; + if (ch == chOpen) + cDepth++; + else if (ch == chClose) + if (!--cDepth) + break; + } + } + /* else: $x or $$, so just skip the next character. */ + } + else if (RT_C_IS_SPACE(ch)) + { + /* + * End of word. Skip whitespace till the next word starts. + */ + offEndPrev = offLine - 1; + Assert(offLine != offWord); + while (offLine < cchTotalLine) + { + ch = pchLine[offLine]; + if (RT_C_IS_SPACE(ch)) + offLine++; + else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n')) + offLine += 2; + else + break; + } + offCurWord = offLine; + iWord++; + + /* + * To simplify the assignment operator checks, we just check the + * start of the 2nd word when we're here. + */ + if (iWord == 1 && offLine < cchTotalLine) + { + ch = pchLine[offLine]; + if (ch == '=') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0); + if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=') + { + if (ch == ':') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0); + if (ch == '+') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0); + if (ch == '<') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0); + if (ch == '?') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0); + } + else if ( ch == ':' + && pchLine[offLine + 1] == ':' + && pchLine[offLine + 2] == '=') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0); + + /* Check for rule while we're here. */ + if (ch == ':') + return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine); + } + } + /* + * If '=' is found in the first word it's an assignment. + */ + else if (ch == '=') + { + if (iWord == 0) + { + KMKASSIGNTYPE enmType = kKmkAssignType_Recursive; + ch = pchLine[offLine - 2]; + if (ch == '+') + enmType = kKmkAssignType_Appending; + else if (ch == '?') + enmType = kKmkAssignType_Conditional; + else if (ch == '<') + enmType = kKmkAssignType_Prepending; + else + { + Assert(ch != ':'); + return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0); + } + return scmKmkHandleAssignment2(pParser, offWord, offLine - 2, enmType, offLine - 2, 0); + } + } + /* + * When ':' is found it can mean a drive letter, a rule or in the + * first word a simple or immediate assignment. + */ + else if (ch == ':') + { + /* Check for drive letters (we ignore the archive form): */ + if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2])) + { /* ignore */ } + else + { + /* Simple or immediate assignment? */ + ch = pchLine[offLine]; + if (iWord == 0) + { + if (ch == '=') + return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0); + if (ch == ':' && pchLine[offLine + 1] == '=') + return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0); + } + + /* Okay, it's a rule then. */ + return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1); + } + } + } + + /* + * Check if this is a $(error ) or similar function call line. + * + * If we're inside a 'define' we treat $$ as $ as it's probably a case of + * double expansion (e.g. def_vmm_lib_dtrace_preprocess in VMM/Makefile.kmk). + */ + if (pchLine[offWord] == '$') + { + size_t const cDollars = pchLine[offWord + 1] != '$' || !scmKmkIsInsideDefine(pParser) ? 1 : 2; + if ( pchLine[offWord + cDollars] == '(' + || pchLine[offWord + cDollars] == '{') + { + size_t const cchLine = pParser->cchLine; + size_t offEnd = offWord + cDollars + 1; + char ch = '\0'; + while (offEnd < cchLine && (RT_C_IS_LOWER(ch = pchLine[offEnd]) || RT_C_IS_DIGIT(ch) || ch == '-')) + offEnd++; + if (offEnd >= cchLine || RT_C_IS_SPACE(ch) || (offEnd == cchLine - 1 && ch == '\\')) + { + static const RTSTRTUPLE s_aAllowedFunctions[] = + { + { RT_STR_TUPLE("info") }, + { RT_STR_TUPLE("error") }, + { RT_STR_TUPLE("warning") }, + { RT_STR_TUPLE("set-umask") }, + { RT_STR_TUPLE("foreach") }, + { RT_STR_TUPLE("call") }, + { RT_STR_TUPLE("eval") }, + { RT_STR_TUPLE("evalctx") }, + { RT_STR_TUPLE("evalval") }, + { RT_STR_TUPLE("evalvalctx") }, + { RT_STR_TUPLE("evalcall") }, + { RT_STR_TUPLE("evalcall2") }, + { RT_STR_TUPLE("eval-opt-var") }, + { RT_STR_TUPLE("kb-src-one") }, + }; + size_t cchFunc = offEnd - offWord - cDollars - 1; + for (size_t i = 0; i < RT_ELEMENTS(s_aAllowedFunctions); i++) + if ( cchFunc == s_aAllowedFunctions[i].cch + && memcmp(&pchLine[offWord + cDollars + 1], s_aAllowedFunctions[i].psz, cchFunc) == 0) + return scmKmkHandleSimple(pParser, offWord); + } + } + } + + /* + * If we didn't find anything, output it as-as. + * We use scmKmkHandleSimple in a special way to do this. + */ + if (!RTStrStartsWith(pchLine, "$(TOOL_")) /* ValKit/Config.kmk */ + ScmVerbose(pParser->pState, 1, "%u: debug: Unable to make sense of this line!\n", ScmStreamTellLine(pParser->pIn)); + return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/); +} + + +static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord, + bool fMustBeAssignment) +{ + /* Assignments takes us out of recipe mode. */ + scmKmkSetInRecipe(pParser, false); + + RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment); + return scmKmkHandleSimple(pParser, offToken); +} + + +/** + * Rewrite a kBuild makefile. + * + * @returns kScmMaybeModified or kScmUnmodified. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + * + * @todo + * + * Ideas for Makefile.kmk and Config.kmk: + * - sort if1of/ifn1of sets. + * - line continuation slashes should only be preceded by one space. + */ +SCMREWRITERRES rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if (!pSettings->fStandarizeKmk) + return kScmUnmodified; + + /* + * Parser state. + */ + KMKPARSER Parser; + Parser.iDepth = 0; + Parser.iActualDepth = 0; + Parser.fInRecipe = false; + Parser.pState = pState; + Parser.pIn = pIn; + Parser.pOut = pOut; + Parser.pSettings = pSettings; + + /* + * Iterate the file. + */ + const char *pchLine; + while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL) + { + size_t cchLine = Parser.cchLine; + + /* + * If we're in the command part of a recipe, anything starting with a + * tab is considered another command for the recipe. + */ + if (Parser.fInRecipe && *pchLine == '\t') + { + /* Do we do anything here? */ + } + else + { + /* + * Skip leading whitespace and check for directives (simplified). + * + * This is simplified in the sense that GNU make first checks for variable + * assignments, so that directive can be used as variable names. We don't + * want that, so we do the variable assignment check later. + */ + size_t offLine = 0; + while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + + /* Find end of word (if any) - only looking for keywords here: */ + size_t cchWord = 0; + while ( offLine + cchWord < cchLine + && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord]) + || pchLine[offLine + cchWord] == '-')) + cchWord++; + if (cchWord > 0) + { + /* If the line is just a line continuation slash, simply remove it + (this also makes the parsing a lot easier). */ + if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\') + continue; + + /* Unlike the GNU make parser, we won't recognize 'if' or any other + directives as variable names, so we can */ + KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord); + switch (enmToken) + { + case kKmkToken_ifeq: + case kKmkToken_ifneq: + case kKmkToken_if1of: + case kKmkToken_ifn1of: + scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/); + continue; + + case kKmkToken_ifdef: + case kKmkToken_ifndef: + case kKmkToken_if: + scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/); + continue; + + case kKmkToken_else: + scmKmkHandleElse(&Parser, offLine); + continue; + + case kKmkToken_endif: + scmKmkHandleEndif(&Parser, offLine); + continue; + + /* Includes: */ + case kKmkToken_include: + case kKmkToken_sinclude: + case kKmkToken_dash_include: + case kKmkToken_includedep: + case kKmkToken_includedep_queue: + case kKmkToken_includedep_flush: + scmKmkHandleSimple(&Parser, offLine); + continue; + + /* Others: */ + case kKmkToken_define: + scmKmkHandleDefine(&Parser, offLine); + continue; + case kKmkToken_endef: + scmKmkHandleEndef(&Parser, offLine); + continue; + + case kKmkToken_override: + case kKmkToken_local: + scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/); + continue; + + case kKmkToken_export: + scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/); + continue; + + case kKmkToken_unexport: + case kKmkToken_undefine: + scmKmkHandleSimple(&Parser, offLine); + continue; + + case kKmkToken_Comment: + AssertFailed(); /* not possible */ + break; + + /* + * Check if it's perhaps an variable assignment or start of a rule. + * We'll do this in a very simple fashion. + */ + case kKmkToken_Word: + { + Parser.cLines = 1; + Parser.cchTotalLine = cchLine; + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL); + scmKmkHandleAssignmentOrRule(&Parser, offLine); + continue; + } + } + } + /* + * Not keyword, check for assignment, rule or comment: + */ + else if (offLine < cchLine) + { + if (pchLine[offLine] != '#') + { + Parser.cLines = 1; + Parser.cchTotalLine = cchLine; + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL); + scmKmkHandleAssignmentOrRule(&Parser, offLine); + continue; + } + + /* + * Indent comment lines, unless the comment is too far too the right. + */ + size_t const offEffLine = ScmCalcSpacesForSrcSpan(pchLine, 0, offLine, pSettings); + if (offEffLine <= Parser.iActualDepth + 7) + { + ScmStreamWrite(pOut, g_szSpaces, Parser.iActualDepth); + ScmStreamWrite(pOut, &pchLine[offLine], cchLine - offLine); + ScmStreamPutEol(pOut, Parser.enmEol); + + /* If line continuation is used, it's typically to disable + a property variable, so we just pass it thru as-is */ + while (scmKmkIsLineWithContinuation(pchLine, cchLine)) + { + Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol); + if (!pchLine) + break; + cchLine = Parser.cchLine; + ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol); + } + continue; + } + } + } + + /* + * Pass it thru as-is with line continuation. + */ + while (scmKmkIsLineWithContinuation(pchLine, cchLine)) + { + ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol); + Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol); + if (!pchLine) + break; + cchLine = Parser.cchLine; + } + if (pchLine) + ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol); + } + + return kScmMaybeModified; /* Make the caller check */ +} + + +/** + * Makefile.kup are empty files, enforce this. + * + * @returns true if modifications were made, false if not. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pOut, pSettings); + + /* These files should be zero bytes. */ + if (pIn->cb == 0) + return kScmUnmodified; + ScmVerbose(pState, 2, " * Truncated file to zero bytes\n"); + return kScmModified; +} + |