summaryrefslogtreecommitdiffstats
path: root/src/bldprogs/scmrw-kmk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bldprogs/scmrw-kmk.cpp')
-rw-r--r--src/bldprogs/scmrw-kmk.cpp2273
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;
+}
+