VirtualBox

source: vbox/trunk/src/bldprogs/scmrw-kmk.cpp

Last change on this file was 99039, checked in by vboxsync, 14 months ago

scm/kmk: Indent multiline variable/function expansions in variable assignments better. bugref:10348

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 82.9 KB
Line 
1/* $Id: scmrw-kmk.cpp 99039 2023-03-18 21:03:37Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager, Makefile.kmk/kup.
4 */
5
6/*
7 * Copyright (C) 2010-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <iprt/assert.h>
33#include <iprt/ctype.h>
34#include <iprt/dir.h>
35#include <iprt/env.h>
36#include <iprt/file.h>
37#include <iprt/err.h>
38#include <iprt/getopt.h>
39#include <iprt/initterm.h>
40#include <iprt/mem.h>
41#include <iprt/message.h>
42#include <iprt/param.h>
43#include <iprt/path.h>
44#include <iprt/process.h>
45#include <iprt/stream.h>
46#include <iprt/string.h>
47
48#include "scm.h"
49
50
51/*********************************************************************************************************************************
52* Structures and Typedefs *
53*********************************************************************************************************************************/
54typedef enum KMKASSIGNTYPE
55{
56 kKmkAssignType_Recursive,
57 kKmkAssignType_Conditional,
58 kKmkAssignType_Appending,
59 kKmkAssignType_Prepending,
60 kKmkAssignType_Simple,
61 kKmkAssignType_Immediate
62} KMKASSIGNTYPE;
63
64/** Context for scmKmkWordLength. */
65typedef enum
66{
67 /** Target file or assignment.
68 * Separators: space, '=', ':' */
69 kKmkWordCtx_TargetFileOrAssignment,
70 /** Target file.
71 * Separators: space, ':' */
72 kKmkWordCtx_TargetFile,
73 /** Dependency file or (target variable) assignment.
74 * Separators: space, '=', ':', '|' */
75 kKmkWordCtx_DepFileOrAssignment,
76 /** Dependency file.
77 * Separators: space, '|' */
78 kKmkWordCtx_DepFile,
79 /** Last context which may do double expansion. */
80 kKmkWordCtx_LastDoubleExpansion = kKmkWordCtx_DepFile
81} KMKWORDCTX;
82
83typedef struct KMKWORDSTATE
84{
85 uint16_t uDepth;
86 char chOpen;
87} KMKWORDSTATE;
88
89typedef enum KMKTOKEN
90{
91 kKmkToken_Word = 0,
92 kKmkToken_Comment,
93
94 /* Conditionals: */
95 kKmkToken_ifeq,
96 kKmkToken_ifneq,
97 kKmkToken_if1of,
98 kKmkToken_ifn1of,
99 kKmkToken_ifdef,
100 kKmkToken_ifndef,
101 kKmkToken_if,
102 kKmkToken_else,
103 kKmkToken_endif,
104
105 /* Includes: */
106 kKmkToken_include,
107 kKmkToken_sinclude,
108 kKmkToken_dash_include,
109 kKmkToken_includedep,
110 kKmkToken_includedep_queue,
111 kKmkToken_includedep_flush,
112
113 /* Others: */
114 kKmkToken_define,
115 kKmkToken_endef,
116 kKmkToken_export,
117 kKmkToken_unexport,
118 kKmkToken_local,
119 kKmkToken_override,
120 kKmkToken_undefine
121} KMKTOKEN;
122
123typedef struct KMKPARSER
124{
125 struct
126 {
127 KMKTOKEN enmToken;
128 bool fIgnoreNesting;
129 size_t iLine;
130 } aDepth[64];
131 unsigned iDepth;
132 unsigned iActualDepth;
133 bool fInRecipe;
134
135 /** The EOL type of the current line. */
136 SCMEOL enmEol;
137 /** The length of the current line. */
138 size_t cchLine;
139 /** Pointer to the start of the current line. */
140 char const *pchLine;
141
142 /** @name Only used for rule/assignment parsing.
143 * @{ */
144 /** Number of continuation lines at current rule/assignment. */
145 uint32_t cLines;
146 /** Characters in continuation lines at current rule/assignment. */
147 size_t cchTotalLine;
148 /** @} */
149
150 /** The SCM rewriter state. */
151 PSCMRWSTATE pState;
152 /** The input stream. */
153 PSCMSTREAM pIn;
154 /** The output stream. */
155 PSCMSTREAM pOut;
156 /** The settings. */
157 PCSCMSETTINGSBASE pSettings;
158 /** Scratch buffer. */
159 char szBuf[4096];
160} KMKPARSER;
161
162
163/*********************************************************************************************************************************
164* Global Variables *
165*********************************************************************************************************************************/
166static 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";
167
168
169static KMKTOKEN scmKmkIdentifyToken(const char *pchWord, size_t cchWord)
170{
171 static struct { const char *psz; uint32_t cch; KMKTOKEN enmToken; } s_aTokens[] =
172 {
173 { RT_STR_TUPLE("if"), kKmkToken_if },
174 { RT_STR_TUPLE("ifeq"), kKmkToken_ifeq },
175 { RT_STR_TUPLE("ifneq"), kKmkToken_ifneq },
176 { RT_STR_TUPLE("if1of"), kKmkToken_if1of },
177 { RT_STR_TUPLE("ifn1of"), kKmkToken_ifn1of },
178 { RT_STR_TUPLE("ifdef"), kKmkToken_ifdef },
179 { RT_STR_TUPLE("ifndef"), kKmkToken_ifndef },
180 { RT_STR_TUPLE("else"), kKmkToken_else },
181 { RT_STR_TUPLE("endif"), kKmkToken_endif },
182 { RT_STR_TUPLE("include"), kKmkToken_include },
183 { RT_STR_TUPLE("sinclude"), kKmkToken_sinclude },
184 { RT_STR_TUPLE("-include"), kKmkToken_dash_include },
185 { RT_STR_TUPLE("includedep"), kKmkToken_includedep },
186 { RT_STR_TUPLE("includedep-queue"), kKmkToken_includedep_queue },
187 { RT_STR_TUPLE("includedep-flush"), kKmkToken_includedep_flush },
188 { RT_STR_TUPLE("define"), kKmkToken_define },
189 { RT_STR_TUPLE("endef"), kKmkToken_endef },
190 { RT_STR_TUPLE("export"), kKmkToken_export },
191 { RT_STR_TUPLE("unexport"), kKmkToken_unexport },
192 { RT_STR_TUPLE("local"), kKmkToken_local },
193 { RT_STR_TUPLE("override"), kKmkToken_override },
194 { RT_STR_TUPLE("undefine"), kKmkToken_undefine },
195 };
196 char chFirst = *pchWord;
197 if ( chFirst == 'i'
198 || chFirst == 'e'
199 || chFirst == 'd'
200 || chFirst == 's'
201 || chFirst == '-'
202 || chFirst == 'u'
203 || chFirst == 'l'
204 || chFirst == 'o')
205 {
206 for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
207 if ( s_aTokens[i].cch == cchWord
208 && *s_aTokens[i].psz == chFirst
209 && memcmp(s_aTokens[i].psz, pchWord, cchWord) == 0)
210 return s_aTokens[i].enmToken;
211 }
212#ifdef VBOX_STRICT
213 else
214 for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
215 Assert(chFirst != *s_aTokens[i].psz);
216#endif
217
218 if (chFirst == '#')
219 return kKmkToken_Comment;
220 return kKmkToken_Word;
221}
222
223
224/**
225 * Modifies the fInRecipe state variable, logging changes in verbose mode.
226 */
227static void scmKmkSetInRecipe(KMKPARSER *pParser, bool fInRecipe)
228{
229 if (pParser->fInRecipe != fInRecipe)
230 ScmVerbose(pParser->pState, 4, "%u: debug: %s\n",
231 ScmStreamTellLine(pParser->pIn), fInRecipe ? "in-recipe" : "not-in-recipe");
232 pParser->fInRecipe = fInRecipe;
233}
234
235
236/**
237 * Gives up on the current line, copying it as it and requesting manual repair.
238 */
239static bool scmKmkGiveUp(KMKPARSER *pParser, const char *pszFormat, ...)
240{
241 va_list va;
242 va_start(va, pszFormat);
243 ScmFixManually(pParser->pState, "%u: %N\n", ScmStreamTellLine(pParser->pIn), pszFormat, &va);
244 va_end(va);
245
246 ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
247 return false;
248}
249
250
251static bool scmKmkIsLineWithContinuationSlow(const char *pchLine, size_t cchLine)
252{
253 size_t cchSlashes = 1;
254 cchLine--;
255 while (cchSlashes < cchLine && pchLine[cchLine - cchSlashes - 1] == '\\')
256 cchSlashes++;
257 return RT_BOOL(cchSlashes & 1);
258}
259
260
261DECLINLINE(bool) scmKmkIsLineWithContinuation(const char *pchLine, size_t cchLine)
262{
263 if (cchLine == 0 || pchLine[cchLine - 1] != '\\')
264 return false;
265 return scmKmkIsLineWithContinuationSlow(pchLine, cchLine);
266}
267
268
269/**
270 * Finds the length of a line where line continuation is in play.
271 *
272 * @returns Length from start of current line to the final unescaped EOL.
273 * @param pParser The KMK parser state.
274 * @param pcLine Where to return the number of lines. Optional.
275 * @param pcchMaxLeadWord Where to return the max lead word length on
276 * subsequent lines. Used to help balance multi-line
277 * 'if' statements (imperfect). Optional.
278 */
279static size_t scmKmkLineContinuationPeek(KMKPARSER *pParser, uint32_t *pcLines, size_t *pcchMaxLeadWord)
280{
281 size_t const offSaved = ScmStreamTell(pParser->pIn);
282 uint32_t cLines = 1;
283 size_t cchMaxLeadWord = 0;
284 const char *pchLine = pParser->pchLine;
285 size_t cchLine = pParser->cchLine;
286 SCMEOL enmEol;
287 for (;;)
288 {
289 /* Return if no line continuation (or end of stream): */
290 if ( cchLine == 0
291 || !scmKmkIsLineWithContinuation(pchLine, cchLine)
292 || ScmStreamIsEndOfStream(pParser->pIn))
293 {
294 ScmStreamSeekAbsolute(pParser->pIn, offSaved);
295 if (pcLines)
296 *pcLines = cLines;
297 if (pcchMaxLeadWord)
298 *pcchMaxLeadWord = cchMaxLeadWord;
299 return (size_t)(pchLine - pParser->pchLine) + cchLine;
300 }
301
302 /* Get the next line: */
303 pchLine = ScmStreamGetLine(pParser->pIn, &cchLine, &enmEol);
304 cLines++;
305
306 /* Check the length of the first word if requested: */
307 if (pcchMaxLeadWord)
308 {
309 size_t offLine = 0;
310 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
311 offLine++;
312
313 size_t const offStartWord = offLine;
314 while (offLine < cchLine && !RT_C_IS_BLANK(pchLine[offLine]))
315 offLine++;
316
317 if (offLine - offStartWord > cchMaxLeadWord)
318 cchMaxLeadWord = offLine - offStartWord;
319 }
320 }
321}
322
323
324/**
325 * Checks if the given line contains a comment with the @a pszMarker word in it.
326 *
327 * This can be used to disable warnings.
328 *
329 * @returns true if this is the case, false if not
330 * @param pchLine The line.
331 * @param cchLine The line length.
332 * @param offLine The current line position, 0 if uncertain.
333 * @param pszMarker The marker to check for.
334 * @param cchMarker The length of the marker string.
335 */
336static bool scmKmkHasCommentMarker(const char *pchLine, size_t cchLine, size_t offLine, const char *pszMarker, size_t cchMarker)
337{
338 const char *pchCur = (const char *)memchr(&pchLine[offLine], '#', cchLine - RT_MIN(offLine, cchLine));
339 if (pchCur)
340 {
341 pchCur++;
342 size_t cchLeft = (size_t)(&pchLine[cchLine] - pchCur);
343 while (cchLeft >= cchMarker)
344 {
345 const char *pchHit = (char *)memchr(pchCur, *pszMarker, cchLeft - cchMarker + 1);
346 if (!pchHit)
347 break;
348 if (memcmp(pchHit, pszMarker, cchMarker) == 0)
349 return true;
350 pchCur = pchHit + 1;
351 cchLeft = (size_t)(&pchLine[cchLine] - pchCur);
352 }
353 }
354 return false;
355}
356
357
358/**
359 * Pushes a if or define on the nesting stack.
360 */
361static bool scmKmkPushNesting(KMKPARSER *pParser, KMKTOKEN enmToken)
362{
363 uint32_t iDepth = pParser->iDepth;
364 if (iDepth + 1 >= RT_ELEMENTS(pParser->aDepth))
365 {
366 ScmError(pParser->pState, VERR_ASN1_TOO_DEEPLY_NESTED /*?*/,
367 "%u: Too deep if/define nesting!\n", ScmStreamTellLine(pParser->pIn));
368 return false;
369 }
370
371 pParser->aDepth[iDepth].enmToken = enmToken;
372 pParser->aDepth[iDepth].iLine = ScmStreamTellLine(pParser->pIn);
373 pParser->aDepth[iDepth].fIgnoreNesting = false;
374 pParser->iDepth = iDepth + 1;
375 pParser->iActualDepth += 1;
376 ScmVerbose(pParser->pState, 5, "%u: debug: nesting %u (token %u)\n", pParser->aDepth[iDepth].iLine, iDepth + 1, enmToken);
377 return true;
378}
379
380
381/**
382 * Checks if we're inside a define or not.
383 */
384static bool scmKmkIsInsideDefine(KMKPARSER const *pParser)
385{
386 unsigned iDepth = pParser->iDepth;
387 while (iDepth-- > 0)
388 if (pParser->aDepth[iDepth].enmToken == kKmkToken_define)
389 return true;
390 return false;
391}
392
393
394/**
395 * Skips a string stopping at @a chStop1 or @a chStop2, taking $() and ${} into
396 * account.
397 */
398static size_t scmKmkSkipExpString(const char *pchLine, size_t cchLine, size_t off, char chStop1, char chStop2 = '\0')
399{
400 unsigned iExpDepth = 0;
401 char ch;
402 while ( off < cchLine
403 && (ch = pchLine[off])
404 && ( (ch != chStop1 && ch != chStop2)
405 || iExpDepth > 0))
406 {
407 off++;
408 if (ch == '$')
409 {
410 ch = pchLine[off];
411 if (ch == '(' || ch == '{')
412 {
413 iExpDepth++;
414 off++;
415 }
416 }
417 else if ((ch == ')' || ch == '}') && iExpDepth > 0)
418 iExpDepth--;
419 }
420 return off;
421}
422
423
424/**
425 * Finds the length of the word (file) @a offStart.
426 *
427 * This only takes one line into account, so variable expansions (function
428 * calls) spanning multiple lines will be handled as one word per line with help
429 * from @a pState. This allows the caller to properly continutation intend the
430 * additional lines.
431 *
432 * @returns Length of word starting at @a offStart. Zero if there is whitespace
433 * at given offset or it's beyond the end of the line (both cases will
434 * assert).
435 * @param pchLine The line.
436 * @param cchLine The line length.
437 * @param offStart Offset to the start of the word.
438 * @param pState Where multiline variable expansion is tracked.
439 */
440static size_t scmKmkWordLength(const char *pchLine, size_t cchLine, size_t offStart, KMKWORDCTX enmCtx, KMKWORDSTATE *pState)
441{
442 AssertReturn(offStart < cchLine && !RT_C_IS_BLANK(pchLine[offStart]), 0);
443
444 /*
445 * Drop any line continuation slash from the line length, so we don't count
446 * it into the word length. Also, any spaces preceeding it (for multiline
447 * variable function expansion). ASSUMES no trailing slash escaping.
448 */
449 if (cchLine > 0 && pchLine[cchLine - 1] == '\\')
450 do
451 cchLine--;
452 while (cchLine > offStart && RT_C_IS_SPACE(pchLine[cchLine - 1]));
453
454 /*
455 * If we were inside a variable function expansion, continue till we reach the end.
456 * This kind of duplicates the code below.
457 */
458 size_t off = offStart;
459 if (pState->uDepth > 0)
460 {
461 Assert(pState->chOpen == '(' || pState->chOpen == '{');
462 char const chOpen = pState->chOpen;
463 char const chClose = chOpen == '(' ? ')' : '}';
464 unsigned uDepth = pState->uDepth;
465 for (;;)
466 {
467 char ch;
468 if (off < cchLine)
469 ch = pchLine[off++];
470 else /* Reached the end while still inside the expansion. */
471 {
472 pState->chOpen = chOpen;
473 pState->uDepth = (uint16_t)uDepth;
474 return cchLine - offStart;
475 }
476 if (ch == chOpen)
477 uDepth++;
478 else if (ch == chClose && --uDepth == 0)
479 break;
480 }
481 pState->uDepth = 0;
482 pState->chOpen = 0;
483 }
484
485 /*
486 * Process till we find blank or end of the line.
487 */
488 while (off < cchLine)
489 {
490 char ch = pchLine[off];
491 if (RT_C_IS_BLANK(ch))
492 break;
493
494 if (ch == '$')
495 {
496 /*
497 * Skip variable expansion. ASSUMING double expansion being enabled
498 * for rules, we respond to both $() and $$() here, $$$$()
499 */
500 size_t cDollars = 0;
501 do
502 {
503 off++;
504 if (off >= cchLine)
505 return cchLine - offStart;
506 cDollars++;
507 ch = pchLine[off];
508 } while (ch == '$');
509 if ((cDollars & 1) || (cDollars == 2 && enmCtx <= kKmkWordCtx_LastDoubleExpansion))
510 {
511 char const chOpen = ch;
512 if (ch == '(' || ch == '{')
513 {
514 char const chClose = chOpen == '(' ? ')' : '}';
515 unsigned uDepth = 1;
516 off++;
517 for (;;)
518 {
519 if (off < cchLine)
520 ch = pchLine[off++];
521 else /* Reached the end while inside the expansion. */
522 {
523 pState->chOpen = chOpen;
524 pState->uDepth = (uint16_t)uDepth;
525 return cchLine - offStart;
526 }
527 if (ch == chOpen)
528 uDepth++;
529 else if (ch == chClose && --uDepth == 0)
530 break;
531 }
532 }
533 else if (cDollars & 1)
534 off++; /* $X */
535 }
536 continue;
537 }
538 else if (ch == ':')
539 {
540 /*
541 * Check for plain driver letter, omitting the archive member variant.
542 */
543 if (off - offStart != 1 || !RT_C_IS_ALPHA(pchLine[off - 1]))
544 {
545 if (off == offStart)
546 {
547 /* We need to check for single and double colon rules as well as
548 simple and immediate assignments here. */
549 off++;
550 if (pchLine[off] == ':')
551 {
552 off++;
553 if (pchLine[off] == '=')
554 {
555 if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
556 return 3; /* ::= - immediate assignment. */
557 off++;
558 }
559 else if (enmCtx != kKmkWordCtx_DepFile)
560 return 2; /* :: - double colon rule */
561 }
562 else if (pchLine[off] == '=')
563 {
564 if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
565 return 2; /* := - simple assignment. */
566 off++;
567 }
568 else if (enmCtx != kKmkWordCtx_DepFile)
569 return 1; /* : - regular rule. */
570 continue;
571 }
572 /* ':' is a separator except in DepFile context. */
573 else if (enmCtx != kKmkWordCtx_DepFile)
574 return off - offStart;
575 }
576 }
577 else if (ch == '=')
578 {
579 /*
580 * Assignment. We check for the previous character too so we'll catch
581 * append, prepend and conditional assignments. Simple and immediate
582 * assignments are handled above.
583 */
584 if ( enmCtx == kKmkWordCtx_TargetFileOrAssignment
585 || enmCtx == kKmkWordCtx_DepFileOrAssignment)
586 {
587 if (off > offStart)
588 {
589 ch = pchLine[off - 1];
590 if (ch == '?' || ch == '+' || ch == '>')
591 off = off - 1 == offStart
592 ? off + 2 /* return '+=', '?=', '<=' */
593 : off - 1; /* up to '+=', '?=', '<=' */
594 else
595 Assert(ch != ':'); /* handled above */
596 }
597 else
598 off++; /* '=' */
599 return off - offStart;
600 }
601 }
602 else if (ch == '|')
603 {
604 /*
605 * This is rather straight forward.
606 */
607 if (enmCtx == kKmkWordCtx_DepFileOrAssignment || enmCtx == kKmkWordCtx_DepFile)
608 {
609 if (off == offStart)
610 return 1;
611 return off - offStart;
612 }
613 }
614 off++;
615 }
616 return off - offStart;
617}
618
619
620static bool scmKmkTailComment(KMKPARSER *pParser, const char *pchLine, size_t cchLine, size_t offSrc, char **ppszDst)
621{
622 /* Wind back offSrc to the first blank space (not all callers can do this). */
623 Assert(offSrc <= cchLine);
624 while (offSrc > 0 && RT_C_IS_SPACE(pchLine[offSrc - 1]))
625 offSrc--;
626 size_t const offSrcStart = offSrc;
627
628 /* Skip blanks. */
629 while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
630 offSrc++;
631 if (offSrc >= cchLine)
632 return true;
633
634 /* Is it a comment? */
635 char *pszDst = *ppszDst;
636 if (pchLine[offSrc] == '#')
637 {
638 /* Try preserve the start column number. */
639/** @todo tabs */
640 size_t const offDst = pszDst - pParser->szBuf;
641 if (offDst < offSrc)
642 {
643 memset(pszDst, ' ', offSrc - offDst);
644 pszDst += offSrc - offDst;
645 }
646 else if (offSrc != offSrcStart)
647 *pszDst++ = ' ';
648
649 *ppszDst = pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchLine - offSrc);
650 return false; /*dummy*/
651 }
652
653 /* Complain and copy out the text unmodified. */
654 ScmError(pParser->pState, VERR_PARSE_ERROR, "%u:%u: Expected comment, found: %.*s",
655 ScmStreamTellLine(pParser->pIn), offSrc, cchLine - offSrc, &pchLine[offSrc]);
656 *ppszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchLine - offSrcStart);
657 return false; /*dummy*/
658}
659
660
661/**
662 * Deals with: ifeq, ifneq, if1of and ifn1of
663 *
664 * @returns dummy (false) to facility return + call.
665 */
666static bool scmKmkHandleIfParentheses(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
667{
668 const char * const pchLine = pParser->pchLine;
669 size_t const cchLine = pParser->cchLine;
670 uint32_t const cchIndent = pParser->iActualDepth
671 - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
672
673 /*
674 * Push it onto the stack. All these nestings are relevant.
675 */
676 if (!fElse)
677 {
678 if (!scmKmkPushNesting(pParser, enmToken))
679 return false;
680 }
681 else
682 {
683 pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
684 pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn);
685 }
686
687 /*
688 * We do not allow line continuation for these.
689 */
690 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
691 return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
692
693 /*
694 * We stage the modified line in the buffer, so check that the line isn't
695 * too long (it seriously should be).
696 */
697 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
698 return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", cchToken, &pchLine[offToken], cchLine);
699 char *pszDst = pParser->szBuf;
700
701 /*
702 * Emit indent and initial token.
703 */
704 memset(pszDst, ' ', cchIndent);
705 pszDst += cchIndent;
706
707 if (fElse)
708 pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
709
710 memcpy(pszDst, &pchLine[offToken], cchToken);
711 pszDst += cchToken;
712
713 size_t offSrc = offToken + cchToken;
714
715 /*
716 * There shall be exactly one space between the token and the opening parenthesis.
717 */
718 if (pchLine[offSrc] == ' ' && pchLine[offSrc + 1] == '(')
719 offSrc += 2;
720 else
721 {
722 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
723 offSrc++;
724 if (pchLine[offSrc] != '(')
725 return scmKmkGiveUp(pParser, "Expected '(' to follow '%.*s'", cchToken, &pchLine[offToken]);
726 offSrc++;
727 }
728 *pszDst++ = ' ';
729 *pszDst++ = '(';
730
731 /*
732 * Skip spaces after the opening parenthesis.
733 */
734 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
735 offSrc++;
736
737 /*
738 * Work up to the ',' separator. It shall likewise not be preceeded by any spaces.
739 * Need to take $(func 1,2,3) calls into account here, so we trac () and {} while
740 * skipping ahead.
741 */
742 if (pchLine[offSrc] != ',')
743 {
744 size_t const offSrcStart = offSrc;
745 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ',');
746 if (pchLine[offSrc] != ',')
747 return scmKmkGiveUp(pParser, "Expected ',' somewhere after '%.*s('", cchToken, &pchLine[offToken]);
748
749 size_t cchCopy = offSrc - offSrcStart;
750 while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
751 cchCopy--;
752
753 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
754 }
755 /* 'if1of(, stuff)' does not make sense in committed code: */
756 else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
757 return scmKmkGiveUp(pParser, "Left set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
758 offSrc++;
759 *pszDst++ = ',';
760
761 /*
762 * For if1of and ifn1of we require a space after the comma, whereas ifeq and
763 * ifneq shall not have any blanks. This is to help tell them apart.
764 */
765 if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
766 {
767 *pszDst++ = ' ';
768 if (pchLine[offSrc] == ' ')
769 offSrc++;
770 }
771 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
772 offSrc++;
773
774 if (pchLine[offSrc] != ')')
775 {
776 size_t const offSrcStart = offSrc;
777 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ')');
778 if (pchLine[offSrc] != ')')
779 return scmKmkGiveUp(pParser, "No closing parenthesis for '%.*s'?", cchToken, &pchLine[offToken]);
780
781 size_t cchCopy = offSrc - offSrcStart;
782 while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
783 cchCopy--;
784
785 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
786 }
787 /* 'if1of(stuff, )' does not make sense in committed code: */
788 else if ( (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
789 && !scmKmkHasCommentMarker(pchLine, cchLine, offSrc, RT_STR_TUPLE("scm:ignore-empty-if1of-set")))
790 return scmKmkGiveUp(pParser, "Right set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
791 offSrc++;
792 *pszDst++ = ')';
793
794 /*
795 * Handle comment.
796 */
797 if (offSrc < cchLine)
798 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
799
800 /*
801 * Done.
802 */
803 *pszDst = '\0';
804 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
805 return false; /* dummy */
806}
807
808
809/**
810 * Deals with: if, ifdef and ifndef
811 *
812 * @returns dummy (false) to facility return + call.
813 */
814static bool scmKmkHandleIfSpace(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
815{
816 const char *pchLine = pParser->pchLine;
817 size_t cchLine = pParser->cchLine;
818 uint32_t const cchIndent = pParser->iActualDepth
819 - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
820
821 /*
822 * Push it onto the stack.
823 *
824 * For ifndef we ignore the outmost ifndef in non-Makefile.kmk files, if
825 * the define matches the typical pattern for a file blocker.
826 */
827 bool fIgnoredNesting = false;
828 if (!fElse)
829 {
830 if (!scmKmkPushNesting(pParser, enmToken))
831 return false;
832 if (enmToken == kKmkToken_ifndef)
833 {
834 /** @todo */
835 }
836 }
837 else
838 {
839 pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
840 pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn);
841 }
842
843 /*
844 * We do not allow line continuation for these.
845 */
846 uint32_t cLines = 1;
847 size_t cchMaxLeadWord = 0;
848 size_t cchTotalLine = cchLine;
849 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
850 {
851 if (enmToken != kKmkToken_if)
852 return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
853 cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord);
854 }
855
856 /*
857 * We stage the modified line in the buffer, so check that the line isn't
858 * too long (plain if can be long, but not ifndef/ifdef).
859 */
860 if (cchTotalLine + pParser->iActualDepth + 32 > sizeof(pParser->szBuf))
861 return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars",
862 cchToken, &pchLine[offToken], cchTotalLine);
863 char *pszDst = pParser->szBuf;
864
865 /*
866 * Emit indent and initial token.
867 */
868 memset(pszDst, ' ', cchIndent);
869 pszDst += cchIndent;
870
871 if (fElse)
872 pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
873
874 memcpy(pszDst, &pchLine[offToken], cchToken);
875 pszDst += cchToken;
876
877 size_t offSrc = offToken + cchToken;
878
879 /*
880 * ifndef/ifdef shall have exactly one space. For 'if' we allow up to 4, but
881 * we'll deal with that further down.
882 */
883 size_t cchSpaces = 0;
884 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
885 {
886 cchSpaces++;
887 offSrc++;
888 }
889 if (cchSpaces == 0)
890 return scmKmkGiveUp(pParser, "Nothing following '%.*s' or bogus line continuation?", cchToken, &pchLine[offToken]);
891 *pszDst++ = ' ';
892
893 /*
894 * For ifdef and ifndef there now comes a single word.
895 */
896 if (enmToken != kKmkToken_if)
897 {
898 size_t const offSrcStart = offSrc;
899 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ' ', '\t'); /** @todo probably not entirely correct */
900 if (offSrc == offSrcStart)
901 return scmKmkGiveUp(pParser, "No word following '%.*s'?", cchToken, &pchLine[offToken]);
902
903 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], offSrc - offSrcStart);
904 }
905 /*
906 * While for 'if' things are more complicated, especially if it spans more
907 * than one line.
908 */
909 else if (cLines <= 1)
910 {
911 /* Single line expression: Just assume the expression goes up to the
912 EOL or comment hash. Strip and copy as-is for now. */
913 const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
914 size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
915 while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
916 cchExpr--;
917
918 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
919 offSrc += cchExpr;
920 }
921 else
922 {
923 /* Multi line expression: We normalize leading whitespace using
924 cchMaxLeadWord for now. Expression on line 2+ are indented by two
925 extra characters, because we'd otherwise be puttin the operator on
926 the same level as the 'if', which would be confusing. Thus:
927
928 if expr1
929 + expr2
930 endif
931
932 if expr1
933 || expr2
934 endif
935
936 if expr3
937 vtg expr4
938 endif
939
940 We do '#' / EOL handling for the final line the same way as above.
941
942 Later we should add the ability to rework the expression properly,
943 making sure new lines starts with operators and such. */
944 /** @todo Implement simples expression parser and indenter, possibly also
945 * removing unnecessary parentheses. Can be shared with C/C++. */
946 if (cchMaxLeadWord > 3)
947 return scmKmkGiveUp(pParser,
948 "Bogus multi-line 'if' expression! Extra lines must start with operator (cchMaxLeadWord=%u).",
949 cchMaxLeadWord);
950 memset(pszDst, ' ', cchMaxLeadWord);
951 pszDst += cchMaxLeadWord;
952
953 size_t cchSrcContIndent = offToken + 2;
954 for (uint32_t iSubLine = 0; iSubLine < cLines - 1; iSubLine++)
955 {
956 /* Trim the line. */
957 size_t offSrcEnd = cchLine;
958 Assert(pchLine[offSrcEnd - 1] == '\\');
959 offSrcEnd--;
960
961 if (pchLine[offSrcEnd - 1] == '\\')
962 return scmKmkGiveUp(pParser, "Escaped '\\' before line continuation in 'if' expression is not allowed!");
963
964 while (offSrcEnd > offSrc && RT_C_IS_BLANK(pchLine[offSrcEnd - 1]))
965 offSrcEnd--;
966
967 /* Comments with line continuation is not allowed in commited makefiles. */
968 if (offSrc < offSrcEnd && memchr(&pchLine[offSrc], '#', cchLine - offSrc) != NULL)
969 return scmKmkGiveUp(pParser, "Comment in multi-line 'if' expression is not allowed to start before the final line!");
970
971 /* Output it. */
972 if (offSrc < offSrcEnd)
973 {
974 if (iSubLine > 0 && offSrc > cchSrcContIndent)
975 {
976 memset(pszDst, ' ', offSrc - cchSrcContIndent);
977 pszDst += offSrc - cchSrcContIndent;
978 }
979 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], offSrcEnd - offSrc);
980 *pszDst++ = ' ';
981 }
982 else if (iSubLine == 0)
983 return scmKmkGiveUp(pParser, "Expected expression after 'if', not line continuation!");
984 *pszDst++ = '\\';
985 *pszDst = '\0';
986 size_t cchDst = (size_t)(pszDst - pParser->szBuf);
987 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
988
989 /*
990 * Fetch the next line and start processing it.
991 */
992 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
993 if (!pchLine)
994 {
995 ScmError(pParser->pState, VERR_INTERNAL_ERROR_3, "ScmStreamGetLine unexpectedly returned NULL!");
996 return false;
997 }
998 cchLine = pParser->cchLine;
999
1000 /* Skip leading whitespace and adjust the source continuation indent: */
1001 offSrc = 0;
1002 while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
1003 offSrc++;
1004 /** @todo tabs */
1005
1006 if (iSubLine == 0)
1007 cchSrcContIndent = offSrc;
1008
1009 /* Initial indent: */
1010 pszDst = pParser->szBuf;
1011 memset(pszDst, ' ', cchIndent + 2);
1012 pszDst += cchIndent + 2;
1013 }
1014
1015 /* Output the expression on the final line. */
1016 const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
1017 size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
1018 while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
1019 cchExpr--;
1020
1021 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
1022 offSrc += cchExpr;
1023 }
1024
1025
1026 /*
1027 * Handle comment.
1028 *
1029 * Here we check for the "scm:ignore-nesting" directive that makes us not
1030 * add indentation for this directive. We do this on the destination buffer
1031 * as that can be zero terminated and is therefore usable with strstr.
1032 */
1033 if (offSrc >= cchLine)
1034 *pszDst = '\0';
1035 else
1036 {
1037 char * const pszDstSrc = pszDst;
1038 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
1039 *pszDst = '\0';
1040
1041 /* Check for special comment making us ignore the nesting. We do this
1042 on the destination buffer since it's zero terminated allowing normal
1043 strstr use. */
1044 if (!fIgnoredNesting && strstr(pszDstSrc, "scm:ignore-nesting") != NULL)
1045 {
1046 pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting = true;
1047 pParser->iActualDepth--;
1048 ScmVerbose(pParser->pState, 5, "%u: debug: ignoring nesting - actual depth: %u\n",
1049 pParser->aDepth[pParser->iDepth - 1].iLine, pParser->iActualDepth);
1050 }
1051 }
1052
1053 /*
1054 * Done.
1055 */
1056 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1057 return false; /* dummy */
1058}
1059
1060
1061/**
1062 * Deals with: else
1063 *
1064 * @returns dummy (false) to facility return + call.
1065 */
1066static bool scmKmkHandleElse(KMKPARSER *pParser, size_t offToken)
1067{
1068 const char * const pchLine = pParser->pchLine;
1069 size_t const cchLine = pParser->cchLine;
1070
1071 if (pParser->iDepth < 1)
1072 return scmKmkGiveUp(pParser, "Lone 'else'");
1073 uint32_t const cchIndent = pParser->iActualDepth
1074 - (pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
1075
1076 /*
1077 * Look past the else and check if there any ifxxx token following it.
1078 */
1079 size_t offSrc = offToken + 4;
1080 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
1081 offSrc++;
1082 if (offSrc < cchLine)
1083 {
1084 size_t cchWord = 0;
1085 while (offSrc + cchWord < cchLine && RT_C_IS_ALNUM(pchLine[offSrc + cchWord]))
1086 cchWord++;
1087 if (cchWord)
1088 {
1089 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offSrc], cchWord);
1090 switch (enmToken)
1091 {
1092 case kKmkToken_ifeq:
1093 case kKmkToken_ifneq:
1094 case kKmkToken_if1of:
1095 case kKmkToken_ifn1of:
1096 return scmKmkHandleIfParentheses(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
1097
1098 case kKmkToken_ifdef:
1099 case kKmkToken_ifndef:
1100 case kKmkToken_if:
1101 return scmKmkHandleIfSpace(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
1102
1103 default:
1104 break;
1105 }
1106 }
1107 }
1108
1109 /*
1110 * We do not allow line continuation for these.
1111 */
1112 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
1113 return scmKmkGiveUp(pParser, "Line continuation not allowed with 'else' directive.");
1114
1115 /*
1116 * We stage the modified line in the buffer, so check that the line isn't
1117 * too long (it seriously should be).
1118 */
1119 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
1120 return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
1121 char *pszDst = pParser->szBuf;
1122
1123 /*
1124 * Emit indent and initial token.
1125 */
1126 memset(pszDst, ' ', cchIndent);
1127 pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("else"));
1128
1129 offSrc = offToken + 4;
1130
1131 /*
1132 * Handle comment.
1133 */
1134 if (offSrc < cchLine)
1135 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
1136
1137 /*
1138 * Done.
1139 */
1140 *pszDst = '\0';
1141 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1142 return false; /* dummy */
1143}
1144
1145
1146/**
1147 * Deals with: endif
1148 *
1149 * @returns dummy (false) to facility return + call.
1150 */
1151static bool scmKmkHandleEndif(KMKPARSER *pParser, size_t offToken)
1152{
1153 const char * const pchLine = pParser->pchLine;
1154 size_t const cchLine = pParser->cchLine;
1155
1156 /*
1157 * Pop a nesting.
1158 */
1159 if (pParser->iDepth < 1)
1160 return scmKmkGiveUp(pParser, "Lone 'endif'");
1161 uint32_t iDepth = pParser->iDepth - 1;
1162 pParser->iDepth = iDepth;
1163 if (!pParser->aDepth[iDepth].fIgnoreNesting)
1164 {
1165 AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++);
1166 pParser->iActualDepth -= 1;
1167 }
1168 ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endif)\n",
1169 ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth);
1170 uint32_t const cchIndent = pParser->iActualDepth;
1171
1172 /*
1173 * We do not allow line continuation for these.
1174 */
1175 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
1176 return scmKmkGiveUp(pParser, "Line continuation not allowed with 'endif' directive.");
1177
1178 /*
1179 * We stage the modified line in the buffer, so check that the line isn't
1180 * too long (it seriously should be).
1181 */
1182 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
1183 return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
1184 char *pszDst = pParser->szBuf;
1185
1186 /*
1187 * Emit indent and initial token.
1188 */
1189 memset(pszDst, ' ', cchIndent);
1190 pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("endif"));
1191
1192 size_t offSrc = offToken + 5;
1193
1194 /*
1195 * Handle comment.
1196 */
1197 if (offSrc < cchLine)
1198 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
1199
1200 /*
1201 * Done.
1202 */
1203 *pszDst = '\0';
1204 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1205 return false; /* dummy */
1206}
1207
1208
1209/**
1210 * Passing thru any line continuation lines following the current one.
1211 */
1212static bool scmKmkPassThruLineContinuationLines(KMKPARSER *pParser)
1213{
1214 while (scmKmkIsLineWithContinuation(pParser->pchLine, pParser->cchLine))
1215 {
1216 pParser->pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1217 if (!pParser->pchLine)
1218 break;
1219 ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
1220 }
1221 return false; /* dummy */
1222}
1223
1224
1225/**
1226 * For dealing with a directive w/o special formatting rules (yet).
1227 *
1228 * @returns dummy (false) to facility return + call.
1229 */
1230static bool scmKmkHandleSimple(KMKPARSER *pParser, size_t offToken, bool fIndentIt = true)
1231{
1232 const char *pchLine = pParser->pchLine;
1233 size_t cchLine = pParser->cchLine;
1234 uint32_t const cchIndent = fIndentIt ? pParser->iActualDepth : 0;
1235
1236 /*
1237 * Just reindent the statement.
1238 */
1239 ScmStreamWrite(pParser->pOut, g_szSpaces, cchIndent);
1240 ScmStreamWrite(pParser->pOut, &pchLine[offToken], cchLine - offToken);
1241 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1242
1243 /*
1244 * Check for line continuation and output concatenated lines.
1245 */
1246 scmKmkPassThruLineContinuationLines(pParser);
1247 return false; /* dummy */
1248}
1249
1250
1251static bool scmKmkHandleDefine(KMKPARSER *pParser, size_t offToken)
1252{
1253 scmKmkHandleSimple(pParser, offToken);
1254
1255 /* Hack Alert! Start out parsing the define in recipe mode.
1256
1257 Technically, we shouldn't evaluate the content of a define till it's
1258 used. However, we ASSUME they are either makefile code snippets or
1259 recipe templates. */
1260 scmKmkPushNesting(pParser, kKmkToken_define);
1261 scmKmkSetInRecipe(pParser, true);
1262 return false;
1263}
1264
1265
1266static bool scmKmkHandleEndef(KMKPARSER *pParser, size_t offToken)
1267{
1268 /* Leaving a define resets the recipt mode. */
1269 scmKmkSetInRecipe(pParser, false);
1270
1271 /*
1272 * Pop a nesting.
1273 */
1274 if (pParser->iDepth < 1)
1275 return scmKmkGiveUp(pParser, "Lone 'endef'");
1276 uint32_t iDepth = pParser->iDepth - 1;
1277 if (pParser->aDepth[iDepth].enmToken != kKmkToken_define)
1278 return scmKmkGiveUp(pParser, "Unpexected 'endef', expected 'endif' for line %u", pParser->aDepth[iDepth].iLine);
1279 pParser->iDepth = iDepth;
1280 if (!pParser->aDepth[iDepth].fIgnoreNesting)
1281 {
1282 AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++);
1283 pParser->iActualDepth -= 1;
1284 }
1285 ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endef)\n",
1286 ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth);
1287
1288 return scmKmkHandleSimple(pParser, offToken);
1289}
1290
1291
1292/**
1293 * Checks for escaped trailing slashes on a line, giving up and asking the
1294 * developer to fix those manually.
1295 *
1296 * @returns true if we gave up. false if no escaped slashed and we didn't.
1297 */
1298static bool scmKmkGiveUpIfTrailingEscapedSlashed(KMKPARSER *pParser, const char *pchLine, size_t cchLine)
1299{
1300 if (cchLine > 2 && pchLine[cchLine - 2] == '\\' && pchLine[cchLine - 1] == '\\')
1301 {
1302 scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1303 return true;
1304 }
1305 return false;
1306}
1307
1308
1309/**
1310 * Scans the given line segment for variable expansion and updates the state
1311 * accordingly.
1312 *
1313 * @returns New cExpandNesting value.
1314 * @param cExpandNesting Current variable expansion nesting
1315 * level.
1316 * @param achExpandingNestingClose String with the closing character for
1317 * each expansion level. 256 chars.
1318 * @param pchLine The string to scan.
1319 * @param cchLine Length to scan.
1320 */
1321static unsigned scmKmkScanStringForExpansions(unsigned cExpandNesting, char achExpandNestingClose[256],
1322 const char *pchLine, size_t cchLine)
1323{
1324 while (cchLine-- > 0)
1325 {
1326 char ch = *pchLine++;
1327 switch (ch)
1328 {
1329 case '$':
1330 {
1331 size_t cDollars = 1;
1332 while (cchLine > 0 && (ch = *pchLine) == '$')
1333 cDollars++, pchLine++, cchLine--;
1334 if ((cDollars & 1) && cchLine > 0 && (ch == '{' || ch == '('))
1335 {
1336 if (cExpandNesting < 256)
1337 achExpandNestingClose[cExpandNesting] = ch == '(' ? ')' : '}';
1338 cExpandNesting++;
1339 pchLine++;
1340 cchLine--;
1341 }
1342 break;
1343 }
1344
1345 case ')':
1346 case '}':
1347 if (cExpandNesting > 0 && (cExpandNesting > 256 || ch == achExpandNestingClose[cExpandNesting - 1]))
1348 cExpandNesting--;
1349 break;
1350 }
1351 }
1352 return cExpandNesting;
1353}
1354
1355
1356/**
1357 * @returns dummy (false) to facility return + call.
1358 */
1359static bool scmKmkHandleAssignment2(KMKPARSER *pParser, size_t offVarStart, size_t offVarEnd, KMKASSIGNTYPE enmType,
1360 size_t offAssignOp, unsigned fFlags)
1361{
1362 unsigned const cchIndent = pParser->iActualDepth;
1363 const char *pchLine = pParser->pchLine;
1364 size_t cchLine = pParser->cchLine;
1365 uint32_t const cLines = pParser->cLines;
1366 uint32_t iSubLine = 0;
1367
1368 RT_NOREF(fFlags);
1369 Assert(offVarStart < cchLine);
1370 Assert(offVarEnd <= cchLine);
1371 Assert(offVarStart < offVarEnd);
1372 Assert(!RT_C_IS_SPACE(pchLine[offVarStart]));
1373 Assert(!RT_C_IS_SPACE(pchLine[offVarEnd - 1]));
1374
1375 /* Assignments takes us out of recipe mode. */
1376 ScmVerbose(pParser->pState, 6, "%u: debug: assignment\n", ScmStreamTellLine(pParser->pIn));
1377 scmKmkSetInRecipe(pParser, false);
1378
1379 /* This is too much hazzle to deal with. */
1380 if (cLines > 1 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1381 return false;
1382 if (cchLine + 64 > sizeof(pParser->szBuf))
1383 return scmKmkGiveUp(pParser, "Line too long!");
1384
1385 /*
1386 * Indent and output the variable name.
1387 */
1388 char *pszDst = pParser->szBuf;
1389 memset(pszDst, ' ', cchIndent);
1390 pszDst += cchIndent;
1391 pszDst = (char *)mempcpy(pszDst, &pchLine[offVarStart], offVarEnd - offVarStart);
1392
1393 /*
1394 * Try preserve the assignment operator position, but make sure we've got a
1395 * space in front of it.
1396 */
1397 if (offAssignOp < cchLine)
1398 {
1399 size_t offDst = (size_t)(pszDst - pParser->szBuf);
1400 size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings);
1401 if (offDst < offEffAssignOp)
1402 {
1403 size_t cchSpacesToWrite = offEffAssignOp - offDst;
1404 memset(pszDst, ' ', cchSpacesToWrite);
1405 pszDst += cchSpacesToWrite;
1406 }
1407 else
1408 *pszDst++ = ' ';
1409 }
1410 else
1411 {
1412 /* Pull up the assignment operator to the variable line. */
1413 *pszDst++ = ' ';
1414
1415 /* Eat up lines till we hit the operator. */
1416 while (offAssignOp < cchLine)
1417 {
1418 const char * const pchPrevLine = pchLine;
1419 Assert(iSubLine + 1 < cLines);
1420 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1421 AssertReturn(pchLine, false /*dummy*/);
1422 cchLine = pParser->cchLine;
1423 iSubLine++;
1424 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1425 return false;
1426
1427 /* Adjust offAssignOp: */
1428 offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
1429 Assert(offAssignOp < ~(size_t)0 / 2);
1430 }
1431
1432 if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf))
1433 return scmKmkGiveUp(pParser, "Line too long!");
1434 }
1435
1436 /*
1437 * Emit the operator.
1438 */
1439 size_t offLine = offAssignOp;
1440 switch (enmType)
1441 {
1442 default:
1443 AssertReleaseFailed();
1444 RT_FALL_THRU();
1445 case kKmkAssignType_Recursive:
1446 *pszDst++ = '=';
1447 Assert(pchLine[offLine] == '=');
1448 offLine++;
1449 break;
1450 case kKmkAssignType_Conditional:
1451 *pszDst++ = '?';
1452 *pszDst++ = '=';
1453 Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '=');
1454 offLine += 2;
1455 break;
1456 case kKmkAssignType_Appending:
1457 *pszDst++ = '+';
1458 *pszDst++ = '=';
1459 Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '=');
1460 offLine += 2;
1461 break;
1462 case kKmkAssignType_Prepending:
1463 *pszDst++ = '<';
1464 *pszDst++ = '=';
1465 Assert(pchLine[offLine] == '<'); Assert(pchLine[offLine + 1] == '=');
1466 offLine += 2;
1467 break;
1468 case kKmkAssignType_Immediate:
1469 *pszDst++ = ':';
1470 Assert(pchLine[offLine] == ':');
1471 offLine++;
1472 RT_FALL_THRU();
1473 case kKmkAssignType_Simple:
1474 *pszDst++ = ':';
1475 *pszDst++ = '=';
1476 Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '=');
1477 offLine += 2;
1478 break;
1479 }
1480
1481 /*
1482 * Skip space till we hit the value or comment.
1483 */
1484 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1485 offLine++;
1486
1487/** @todo this block can probably be merged into the final loop below. */
1488 unsigned cPendingEols = 0;
1489 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1490 {
1491 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1492 AssertReturn(pchLine, false /*dummy*/);
1493 cchLine = pParser->cchLine;
1494 iSubLine++;
1495 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
1496 {
1497 *pszDst++ = ' ';
1498 *pszDst++ = '\\';
1499 *pszDst = '\0';
1500 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1501 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1502 }
1503 cPendingEols = 1;
1504
1505 /* Skip indent/whitespace. */
1506 offLine = 0;
1507 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1508 offLine++;
1509 }
1510
1511 /*
1512 * Okay, we've gotten to the value / comment part.
1513 */
1514 char achExpandNestingClose[256];
1515 unsigned cExpandNesting = 0;
1516 for (;;)
1517 {
1518 /*
1519 * The end? Flush what we've got.
1520 */
1521 if (offLine == cchLine)
1522 {
1523 Assert(iSubLine + 1 == cLines);
1524 *pszDst = '\0';
1525 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1526 if (cPendingEols > 0)
1527 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1528 return false; /* dummy */
1529 }
1530
1531 /*
1532 * Output any non-comment stuff, stripping off newlines.
1533 */
1534 const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
1535 if (pchHash != &pchLine[offLine])
1536 {
1537 /* Add space or flush pending EOLs. */
1538 if (!cPendingEols)
1539 *pszDst++ = ' ';
1540 else
1541 {
1542 unsigned iEol = 0;
1543 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
1544 do
1545 {
1546 if (iEol++ == 0) /* skip this for the 2nd empty line. */
1547 *pszDst++ = ' ';
1548 *pszDst++ = '\\';
1549 *pszDst = '\0';
1550 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1551
1552 pszDst = pParser->szBuf;
1553 memset(pszDst, ' ', cchIndent);
1554 pszDst += cchIndent;
1555
1556 size_t cTabIndent = cExpandNesting + 1;
1557 while (cTabIndent-- > 0)
1558 *pszDst++ = '\t';
1559
1560 cPendingEols--;
1561 } while (cPendingEols > 0);
1562 }
1563
1564 /* Strip backwards. */
1565 size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
1566 size_t offValueEnd = offValueEnd2;
1567 while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
1568 offValueEnd--;
1569 Assert(offValueEnd > offLine);
1570
1571 /* Append the value part we found. */
1572 pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
1573 cExpandNesting = scmKmkScanStringForExpansions(cExpandNesting, achExpandNestingClose,
1574 &pchLine[offLine], offValueEnd - offLine);
1575 offLine = offValueEnd2;
1576 }
1577
1578 /*
1579 * If we found a comment hash, emit it and whatever follows just as-is w/o
1580 * any particular reformatting. Comments within a variable definition are
1581 * usually to disable portitions of a property like _DEFS or _SOURCES.
1582 */
1583 if (pchHash != NULL)
1584 {
1585 if (cPendingEols == 0)
1586 scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst);
1587 size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
1588 *pszDst = '\0';
1589 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
1590
1591 if (cPendingEols > 1)
1592 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1593
1594 if (cPendingEols > 0)
1595 ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
1596 scmKmkPassThruLineContinuationLines(pParser);
1597 return false; /* dummy */
1598 }
1599
1600 /*
1601 * Fetch another line, if we've got one.
1602 */
1603 if (iSubLine + 1 >= cLines)
1604 Assert(offLine == cchLine);
1605 else
1606 {
1607 Assert(offLine + 1 == cchLine);
1608 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1609 {
1610 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1611 AssertReturn(pchLine, false /*dummy*/);
1612 cchLine = pParser->cchLine;
1613 iSubLine++;
1614 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
1615 {
1616 *pszDst++ = ' ';
1617 *pszDst++ = '\\';
1618 *pszDst = '\0';
1619 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1620 if (cPendingEols > 1)
1621 ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
1622 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1623 }
1624 cPendingEols++;
1625
1626 /* Deal with indent/whitespace. */
1627 offLine = 0;
1628 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1629 offLine++;
1630 }
1631 }
1632 }
1633}
1634
1635
1636/**
1637 * A rule.
1638 *
1639 * This is a bit involved. Sigh.
1640 *
1641 * @returns dummy (false) to facility return + call.
1642 */
1643static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon)
1644{
1645 SCMSTREAM *pOut = pParser->pOut;
1646 unsigned const cchIndent = pParser->iActualDepth;
1647 const char *pchLine = pParser->pchLine;
1648 size_t cchLine = pParser->cchLine;
1649 Assert(offFirstWord < cchLine);
1650 uint32_t const cLines = pParser->cLines;
1651 uint32_t iSubLine = 0;
1652
1653 /* Following this, we'll be in recipe-mode. */
1654 ScmVerbose(pParser->pState, 4, "%u: debug: start rule\n", ScmStreamTellLine(pParser->pIn));
1655 scmKmkSetInRecipe(pParser, true);
1656
1657 /* This is too much hazzle to deal with. */
1658 if (cLines > 0 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1659 return false;
1660
1661 /* Too special case. */
1662 if (offColon <= offFirstWord)
1663 return scmKmkGiveUp(pParser, "Missing target file before colon!");
1664
1665 /*
1666 * Indent it.
1667 */
1668 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1669 size_t offLine = offFirstWord;
1670
1671 /*
1672 * Process word by word past the colon, taking new lines into account.
1673 */
1674 KMKWORDSTATE WordState = { 0, 0 };
1675 KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment;
1676 unsigned cPendingEols = 0;
1677 for (;;)
1678 {
1679 /*
1680 * Output the next word.
1681 */
1682 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
1683 Assert(offLine + cchWord <= offColon);
1684 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
1685 offLine += cchWord;
1686
1687 /* Skip whitespace (if any). */
1688 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1689 offLine++;
1690
1691 /* Have we reached the colon already? */
1692 if (offLine >= offColon)
1693 {
1694 Assert(pchLine[offLine] == ':');
1695 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
1696 offLine += fDoubleColon ? 2 : 1;
1697
1698 ScmStreamPutCh(pOut, ':');
1699 if (fDoubleColon)
1700 ScmStreamPutCh(pOut, ':');
1701 break;
1702 }
1703
1704 /* Deal with new line and emit indentation. */
1705 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1706 {
1707 /* Get the next input line. */
1708 for (;;)
1709 {
1710 const char * const pchPrevLine = pchLine;
1711 Assert(iSubLine + 1 < cLines);
1712 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1713 AssertReturn(pchLine, false /*dummy*/);
1714 cchLine = pParser->cchLine;
1715 iSubLine++;
1716 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1717 return false;
1718
1719 /* Adjust offColon: */
1720 offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
1721 Assert(offColon < ~(size_t)0 / 2);
1722
1723 /* Skip leading spaces. */
1724 offLine = 0;
1725 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1726 offLine++;
1727
1728 /* Just drop empty lines. */
1729 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1730 continue;
1731
1732 /* Complete the current line and emit indent, unless we reached the colon: */
1733 if (offLine >= offColon)
1734 {
1735 Assert(pchLine[offLine] == ':');
1736 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
1737 offLine += fDoubleColon ? 2 : 1;
1738
1739 ScmStreamPutCh(pOut, ':');
1740 if (fDoubleColon)
1741 ScmStreamPutCh(pOut, ':');
1742
1743 cPendingEols = 1;
1744 }
1745 else
1746 {
1747 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
1748 ScmStreamPutEol(pOut, pParser->enmEol);
1749 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1750 if (WordState.uDepth > 0)
1751 ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1));
1752 }
1753 break;
1754 }
1755 if (offLine >= offColon)
1756 break;
1757 }
1758 else
1759 ScmStreamPutCh(pOut, ' ');
1760 enmCtx = kKmkWordCtx_TargetFile;
1761 }
1762
1763 /*
1764 * We're immediately past the colon now, so eat whitespace and newlines and
1765 * whatever till we get to a solid word or the end of the line.
1766 */
1767 /* Skip spaces - there should be exactly one. */
1768 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1769 offLine++;
1770
1771 /* Deal with new lines: */
1772 while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1773 {
1774 cPendingEols = 1;
1775
1776 Assert(iSubLine + 1 < cLines);
1777 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1778 AssertReturn(pchLine, false /*dummy*/);
1779 cchLine = pParser->cchLine;
1780 iSubLine++;
1781 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1782 return false;
1783
1784 /* Skip leading spaces. */
1785 offLine = 0;
1786 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1787 offLine++;
1788
1789 /* Just drop empty lines. */
1790 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1791 continue;
1792 }
1793
1794 /*
1795 * Special case: No dependencies.
1796 */
1797 if (offLine == cchLine && iSubLine + 1 >= cLines)
1798 {
1799 ScmStreamPutEol(pOut, pParser->enmEol);
1800 return false /*dummy*/;
1801 }
1802
1803 /*
1804 * Work the dependencies word for word. Indent in spaces + two tabs.
1805 * (Pattern rules will also end up here, but we'll just ignore that for now.)
1806 */
1807 enmCtx = kKmkWordCtx_DepFileOrAssignment;
1808 for (;;)
1809 {
1810 /* Indent the next word. */
1811 if (cPendingEols == 0)
1812 ScmStreamPutCh(pOut, ' ');
1813 else
1814 {
1815 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
1816 ScmStreamPutEol(pOut, pParser->enmEol);
1817 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1818 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
1819 if (cPendingEols > 1)
1820 {
1821 ScmStreamWrite(pOut, RT_STR_TUPLE("\\"));
1822 ScmStreamPutEol(pOut, pParser->enmEol);
1823 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1824 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
1825 }
1826 cPendingEols = 0;
1827 }
1828 if (WordState.uDepth > 0)
1829 ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1));
1830
1831 /* Get the next word and output it. */
1832 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
1833 Assert(offLine + cchWord <= cchLine);
1834
1835 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
1836 offLine += cchWord;
1837
1838 /* Skip whitespace (if any). */
1839 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1840 offLine++;
1841
1842 /* Deal with new line and emit indentation. */
1843 if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1844 {
1845 /* Get the next input line. */
1846 for (;;)
1847 {
1848 Assert(iSubLine + 1 < cLines);
1849 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1850 AssertReturn(pchLine, false /*dummy*/);
1851 cchLine = pParser->cchLine;
1852 iSubLine++;
1853 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1854 return false;
1855
1856 /* Skip leading spaces. */
1857 offLine = 0;
1858 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1859 offLine++;
1860
1861 /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
1862 cPendingEols++;
1863 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1864 continue;
1865 break;
1866 }
1867 }
1868
1869 if (offLine >= cchLine)
1870 {
1871 /* End of input. */
1872/** @todo deal with comments */
1873 Assert(iSubLine + 1 == cLines);
1874 ScmStreamPutEol(pOut, pParser->enmEol);
1875 return false; /* dummmy */
1876 }
1877 enmCtx = kKmkWordCtx_DepFile;
1878 }
1879}
1880
1881
1882/**
1883 * Checks if the (extended) line is a variable assignment.
1884 *
1885 * We scan past line continuation stuff here as the assignment operator could be
1886 * on the next line, even if that's very unlikely it is recommened by the coding
1887 * guide lines if the line needs to be split. Fortunately, though, the caller
1888 * already removes empty empty leading lines, so we only have to consider the
1889 * line continuation issue if no '=' was found on the first line.
1890 *
1891 * @returns Modified or not.
1892 * @param pParser The parser.
1893 * @param cLines Number of lines to consider.
1894 * @param cchTotalLine Total length of all the lines to consider.
1895 * @param offWord Where the first word of the line starts.
1896 * @param pfIsAssignment Where to return whether this is an assignment or
1897 * not.
1898 */
1899static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord)
1900{
1901 const char *pchLine = pParser->pchLine;
1902 size_t const cchTotalLine = pParser->cchTotalLine;
1903
1904 /*
1905 * Scan words till we find ':' or '='.
1906 */
1907 uint32_t iWord = 0;
1908 size_t offCurWord = offWord;
1909 size_t offEndPrev = 0;
1910 size_t offLine = offWord;
1911 while (offLine < cchTotalLine)
1912 {
1913 char ch = pchLine[offLine++];
1914 if (ch == '$')
1915 {
1916 /*
1917 * Skip variable expansion.
1918 */
1919 char const chOpen = pchLine[offLine++];
1920 if (chOpen == '(' || chOpen == '{')
1921 {
1922 char const chClose = chOpen == '(' ? ')' : '}';
1923 unsigned cDepth = 1;
1924 while (offLine < cchTotalLine)
1925 {
1926 ch = pchLine[offLine++];
1927 if (ch == chOpen)
1928 cDepth++;
1929 else if (ch == chClose)
1930 if (!--cDepth)
1931 break;
1932 }
1933 }
1934 /* else: $x or $$, so just skip the next character. */
1935 }
1936 else if (RT_C_IS_SPACE(ch))
1937 {
1938 /*
1939 * End of word. Skip whitespace till the next word starts.
1940 */
1941 offEndPrev = offLine - 1;
1942 Assert(offLine != offWord);
1943 while (offLine < cchTotalLine)
1944 {
1945 ch = pchLine[offLine];
1946 if (RT_C_IS_SPACE(ch))
1947 offLine++;
1948 else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n'))
1949 offLine += 2;
1950 else
1951 break;
1952 }
1953 offCurWord = offLine;
1954 iWord++;
1955
1956 /*
1957 * To simplify the assignment operator checks, we just check the
1958 * start of the 2nd word when we're here.
1959 */
1960 if (iWord == 1 && offLine < cchTotalLine)
1961 {
1962 ch = pchLine[offLine];
1963 if (ch == '=')
1964 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
1965 if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
1966 {
1967 if (ch == ':')
1968 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0);
1969 if (ch == '+')
1970 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0);
1971 if (ch == '<')
1972 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0);
1973 if (ch == '?')
1974 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
1975 }
1976 else if ( ch == ':'
1977 && pchLine[offLine + 1] == ':'
1978 && pchLine[offLine + 2] == '=')
1979 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
1980
1981 /* Check for rule while we're here. */
1982 if (ch == ':')
1983 return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
1984 }
1985 }
1986 /*
1987 * If '=' is found in the first word it's an assignment.
1988 */
1989 else if (ch == '=')
1990 {
1991 if (iWord == 0)
1992 {
1993 KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
1994 ch = pchLine[offLine - 2];
1995 if (ch == '+')
1996 enmType = kKmkAssignType_Appending;
1997 else if (ch == '?')
1998 enmType = kKmkAssignType_Conditional;
1999 else if (ch == '<')
2000 enmType = kKmkAssignType_Prepending;
2001 else
2002 {
2003 Assert(ch != ':');
2004 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
2005 }
2006 return scmKmkHandleAssignment2(pParser, offWord, offLine - 2, enmType, offLine - 2, 0);
2007 }
2008 }
2009 /*
2010 * When ':' is found it can mean a drive letter, a rule or in the
2011 * first word a simple or immediate assignment.
2012 */
2013 else if (ch == ':')
2014 {
2015 /* Check for drive letters (we ignore the archive form): */
2016 if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
2017 { /* ignore */ }
2018 else
2019 {
2020 /* Simple or immediate assignment? */
2021 ch = pchLine[offLine];
2022 if (iWord == 0)
2023 {
2024 if (ch == '=')
2025 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
2026 if (ch == ':' && pchLine[offLine + 1] == '=')
2027 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
2028 }
2029
2030 /* Okay, it's a rule then. */
2031 return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
2032 }
2033 }
2034 }
2035
2036 /*
2037 * Check if this is a $(error ) or similar function call line.
2038 *
2039 * If we're inside a 'define' we treat $$ as $ as it's probably a case of
2040 * double expansion (e.g. def_vmm_lib_dtrace_preprocess in VMM/Makefile.kmk).
2041 */
2042 if (pchLine[offWord] == '$')
2043 {
2044 size_t const cDollars = pchLine[offWord + 1] != '$' || !scmKmkIsInsideDefine(pParser) ? 1 : 2;
2045 if ( pchLine[offWord + cDollars] == '('
2046 || pchLine[offWord + cDollars] == '{')
2047 {
2048 size_t const cchLine = pParser->cchLine;
2049 size_t offEnd = offWord + cDollars + 1;
2050 char ch = '\0';
2051 while (offEnd < cchLine && (RT_C_IS_LOWER(ch = pchLine[offEnd]) || RT_C_IS_DIGIT(ch) || ch == '-'))
2052 offEnd++;
2053 if (offEnd >= cchLine || RT_C_IS_SPACE(ch) || (offEnd == cchLine - 1 && ch == '\\'))
2054 {
2055 static const RTSTRTUPLE s_aAllowedFunctions[] =
2056 {
2057 { RT_STR_TUPLE("info") },
2058 { RT_STR_TUPLE("error") },
2059 { RT_STR_TUPLE("warning") },
2060 { RT_STR_TUPLE("set-umask") },
2061 { RT_STR_TUPLE("foreach") },
2062 { RT_STR_TUPLE("call") },
2063 { RT_STR_TUPLE("eval") },
2064 { RT_STR_TUPLE("evalctx") },
2065 { RT_STR_TUPLE("evalval") },
2066 { RT_STR_TUPLE("evalvalctx") },
2067 { RT_STR_TUPLE("evalcall") },
2068 { RT_STR_TUPLE("evalcall2") },
2069 { RT_STR_TUPLE("eval-opt-var") },
2070 { RT_STR_TUPLE("kb-src-one") },
2071 };
2072 size_t cchFunc = offEnd - offWord - cDollars - 1;
2073 for (size_t i = 0; i < RT_ELEMENTS(s_aAllowedFunctions); i++)
2074 if ( cchFunc == s_aAllowedFunctions[i].cch
2075 && memcmp(&pchLine[offWord + cDollars + 1], s_aAllowedFunctions[i].psz, cchFunc) == 0)
2076 return scmKmkHandleSimple(pParser, offWord);
2077 }
2078 }
2079 }
2080
2081 /*
2082 * If we didn't find anything, output it as-as.
2083 * We use scmKmkHandleSimple in a special way to do this.
2084 */
2085 if (!RTStrStartsWith(pchLine, "$(TOOL_")) /* ValKit/Config.kmk */
2086 ScmVerbose(pParser->pState, 1, "%u: debug: Unable to make sense of this line!\n", ScmStreamTellLine(pParser->pIn));
2087 return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
2088}
2089
2090
2091static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord,
2092 bool fMustBeAssignment)
2093{
2094 /* Assignments takes us out of recipe mode. */
2095 scmKmkSetInRecipe(pParser, false);
2096
2097 RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment);
2098 return scmKmkHandleSimple(pParser, offToken);
2099}
2100
2101
2102/**
2103 * Handles a line with a recipe command.
2104 */
2105static void scmKmkHandleRecipeCommand(KMKPARSER *pParser, const char *pchLine, size_t cchLine)
2106{
2107 /*
2108 * Make sure there is only a single tab and no spaces following it.
2109 * This helps tell prerequisites from the commands in the recipe.
2110 *
2111 * Iff the line starts with a '#' it is probably a Makefile comment line,
2112 * but it will be executed by the shell (kmk_ash) and waste time. So, we
2113 * expand the initial tab into spaces when seeing that.
2114 */
2115 Assert(*pchLine == '\t');
2116 size_t offLine = 1;
2117 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2118 offLine++;
2119
2120 if (offLine < cchLine && pchLine[offLine] == '#')
2121 ScmStreamWrite(pParser->pOut, g_szSpaces, pParser->pSettings->cchTab);
2122 else
2123 ScmStreamPutCh(pParser->pOut, '\t');
2124
2125 ScmStreamWrite(pParser->pOut, &pchLine[offLine], cchLine - offLine);
2126 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
2127
2128 /*
2129 * Any continuation lines are currently just passed thru as-is.
2130 * We could insist on these also starting with tabs, but later.
2131 */
2132 scmKmkPassThruLineContinuationLines(pParser);
2133}
2134
2135
2136/**
2137 * Rewrite a kBuild makefile.
2138 *
2139 * @returns kScmMaybeModified or kScmUnmodified.
2140 * @param pIn The input stream.
2141 * @param pOut The output stream.
2142 * @param pSettings The settings.
2143 *
2144 * @todo
2145 *
2146 * Ideas for Makefile.kmk and Config.kmk:
2147 * - sort if1of/ifn1of sets.
2148 * - line continuation slashes should only be preceded by one space.
2149 */
2150SCMREWRITERRES rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2151{
2152 if (!pSettings->fStandarizeKmk)
2153 return kScmUnmodified;
2154
2155 /*
2156 * Parser state.
2157 */
2158 KMKPARSER Parser;
2159 Parser.iDepth = 0;
2160 Parser.iActualDepth = 0;
2161 Parser.fInRecipe = false;
2162 Parser.pState = pState;
2163 Parser.pIn = pIn;
2164 Parser.pOut = pOut;
2165 Parser.pSettings = pSettings;
2166
2167 /*
2168 * Iterate the file.
2169 */
2170 const char *pchLine;
2171 while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL)
2172 {
2173 size_t cchLine = Parser.cchLine;
2174
2175 /*
2176 * If we're in the command part of a recipe, anything starting with a
2177 * tab is considered another command for the recipe.
2178 */
2179 if (Parser.fInRecipe && *pchLine == '\t')
2180 {
2181 scmKmkHandleRecipeCommand(&Parser, pchLine, cchLine);
2182 continue;
2183 }
2184
2185 /*
2186 * Skip leading whitespace and check for directives (simplified).
2187 *
2188 * This is simplified in the sense that GNU make first checks for variable
2189 * assignments, so that directive can be used as variable names. We don't
2190 * want that, so we do the variable assignment check later.
2191 */
2192 size_t offLine = 0;
2193 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2194 offLine++;
2195
2196 /* Find end of word (if any) - only looking for keywords here: */
2197 size_t cchWord = 0;
2198 while ( offLine + cchWord < cchLine
2199 && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord])
2200 || pchLine[offLine + cchWord] == '-'))
2201 cchWord++;
2202 if (cchWord > 0)
2203 {
2204 /* If the line is just a line continuation slash, simply remove it
2205 (this also makes the parsing a lot easier). */
2206 if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
2207 continue;
2208
2209 /* Unlike the GNU make parser, we won't recognize 'if' or any other
2210 directives as variable names, so we can */
2211 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
2212 switch (enmToken)
2213 {
2214 case kKmkToken_ifeq:
2215 case kKmkToken_ifneq:
2216 case kKmkToken_if1of:
2217 case kKmkToken_ifn1of:
2218 scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
2219 continue;
2220
2221 case kKmkToken_ifdef:
2222 case kKmkToken_ifndef:
2223 case kKmkToken_if:
2224 scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
2225 continue;
2226
2227 case kKmkToken_else:
2228 scmKmkHandleElse(&Parser, offLine);
2229 continue;
2230
2231 case kKmkToken_endif:
2232 scmKmkHandleEndif(&Parser, offLine);
2233 continue;
2234
2235 /* Includes: */
2236 case kKmkToken_include:
2237 case kKmkToken_sinclude:
2238 case kKmkToken_dash_include:
2239 case kKmkToken_includedep:
2240 case kKmkToken_includedep_queue:
2241 case kKmkToken_includedep_flush:
2242 scmKmkHandleSimple(&Parser, offLine);
2243 continue;
2244
2245 /* Others: */
2246 case kKmkToken_define:
2247 scmKmkHandleDefine(&Parser, offLine);
2248 continue;
2249 case kKmkToken_endef:
2250 scmKmkHandleEndef(&Parser, offLine);
2251 continue;
2252
2253 case kKmkToken_override:
2254 case kKmkToken_local:
2255 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
2256 continue;
2257
2258 case kKmkToken_export:
2259 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
2260 continue;
2261
2262 case kKmkToken_unexport:
2263 case kKmkToken_undefine:
2264 scmKmkHandleSimple(&Parser, offLine);
2265 continue;
2266
2267 case kKmkToken_Comment:
2268 AssertFailed(); /* not possible */
2269 break;
2270
2271 /*
2272 * Check if it's perhaps an variable assignment or start of a rule.
2273 * We'll do this in a very simple fashion.
2274 */
2275 case kKmkToken_Word:
2276 {
2277 Parser.cLines = 1;
2278 Parser.cchTotalLine = cchLine;
2279 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2280 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
2281 scmKmkHandleAssignmentOrRule(&Parser, offLine);
2282 continue;
2283 }
2284 }
2285 }
2286 /*
2287 * Not keyword, check for assignment, rule or comment:
2288 */
2289 else if (offLine < cchLine)
2290 {
2291 if (pchLine[offLine] != '#')
2292 {
2293 Parser.cLines = 1;
2294 Parser.cchTotalLine = cchLine;
2295 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2296 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
2297 scmKmkHandleAssignmentOrRule(&Parser, offLine);
2298 continue;
2299 }
2300
2301 /*
2302 * Indent comment lines, unless the comment is too far too the right.
2303 */
2304 size_t const offEffLine = ScmCalcSpacesForSrcSpan(pchLine, 0, offLine, pSettings);
2305 if (offEffLine <= Parser.iActualDepth + 7)
2306 {
2307 ScmStreamWrite(pOut, g_szSpaces, Parser.iActualDepth);
2308 ScmStreamWrite(pOut, &pchLine[offLine], cchLine - offLine);
2309 ScmStreamPutEol(pOut, Parser.enmEol);
2310
2311 /* If line continuation is used, it's typically to disable
2312 a property variable, so we just pass it thru as-is */
2313 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
2314 {
2315 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
2316 if (!pchLine)
2317 break;
2318 cchLine = Parser.cchLine;
2319 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2320 }
2321 continue;
2322 }
2323 }
2324
2325 /*
2326 * Pass it thru as-is with line continuation.
2327 */
2328 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
2329 {
2330 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2331 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
2332 if (!pchLine)
2333 break;
2334 cchLine = Parser.cchLine;
2335 }
2336 if (pchLine)
2337 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2338 }
2339
2340 return kScmMaybeModified; /* Make the caller check */
2341}
2342
2343
2344/**
2345 * Makefile.kup are empty files, enforce this.
2346 *
2347 * @returns true if modifications were made, false if not.
2348 * @param pIn The input stream.
2349 * @param pOut The output stream.
2350 * @param pSettings The settings.
2351 */
2352SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2353{
2354 RT_NOREF2(pOut, pSettings);
2355
2356 /* These files should be zero bytes. */
2357 if (pIn->cb == 0)
2358 return kScmUnmodified;
2359 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2360 return kScmModified;
2361}
2362
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use