VirtualBox

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

Last change on this file since 107044 was 106759, checked in by vboxsync, 5 weeks ago

scm: Hacked up minimal support for multiline if1of/ifn1of. [scm] jiraref:VBP-1253

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 84.2 KB
Line 
1/* $Id: scmrw-kmk.cpp 106759 2024-10-28 16:06:43Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager, Makefile.kmk/kup.
4 */
5
6/*
7 * Copyright (C) 2010-2024 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 *pchLine = pParser->pchLine;
669 size_t cchLine = pParser->cchLine;
670 uint32_t const cchIndent = pParser->iActualDepth
671 - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
672 const char * const pchToken = &pchLine[offToken];
673
674 /*
675 * Push it onto the stack. All these nestings are relevant.
676 */
677 if (!fElse)
678 {
679 if (!scmKmkPushNesting(pParser, enmToken))
680 return false;
681 }
682 else
683 {
684 pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
685 pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn);
686 }
687
688 /*
689 * We do not allow line continuation for these.
690 */
691 uint32_t cLines = 1;
692 size_t cchMaxLeadWord = 0;
693 size_t cchTotalLine = cchLine;
694 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
695 {
696 if (enmToken != kKmkToken_if1of && enmToken != kKmkToken_ifn1of)
697 return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, pchToken);
698 cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord);
699 }
700
701 /*
702 * We stage the modified line in the buffer, so check that the line isn't
703 * too long (it seriously should be).
704 */
705 if (cchTotalLine + cchIndent + 32 > sizeof(pParser->szBuf))
706 return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", cchToken, pchToken, cchTotalLine);
707 char *pszDst = pParser->szBuf;
708
709 /*
710 * Emit indent and initial token.
711 */
712 memset(pszDst, ' ', cchIndent);
713 pszDst += cchIndent;
714
715 if (fElse)
716 pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
717
718 memcpy(pszDst, pchToken, cchToken);
719 pszDst += cchToken;
720
721 size_t offSrc = offToken + cchToken;
722
723 /*
724 * There shall be exactly one space between the token and the opening parenthesis.
725 */
726 if (pchLine[offSrc] == ' ' && pchLine[offSrc + 1] == '(')
727 offSrc += 2;
728 else
729 {
730 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
731 offSrc++;
732 if (pchLine[offSrc] != '(')
733 return scmKmkGiveUp(pParser, "Expected '(' to follow '%.*s'", cchToken, pchToken);
734 offSrc++;
735 }
736 *pszDst++ = ' ';
737 *pszDst++ = '(';
738 size_t const offContIndent = pszDst - pParser->szBuf;
739
740 /*
741 * Skip spaces after the opening parenthesis.
742 *
743 * Note! We don't support/grok any line continuation stuff here.
744 */
745 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
746 offSrc++;
747
748 /*
749 * Work up to the ',' separator. It shall likewise not be preceeded by any spaces.
750 * Need to take $(func 1,2,3) calls into account here, so we trac () and {} while
751 * skipping ahead.
752 *
753 * Note! We don't support/grok any line continuation stuff here.
754 */
755 if (pchLine[offSrc] != ',')
756 {
757 size_t const offSrcStart = offSrc;
758 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ',');
759 if (pchLine[offSrc] != ',')
760 return scmKmkGiveUp(pParser, "Expected ',' somewhere after '%.*s('", cchToken, pchToken);
761
762 size_t cchCopy = offSrc - offSrcStart;
763 while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
764 cchCopy--;
765
766 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
767 }
768 /* 'if1of(, stuff)' does not make sense in committed code: */
769 else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
770 return scmKmkGiveUp(pParser, "Left set cannot be empty for '%.*s'", cchToken, pchToken);
771 offSrc++;
772 *pszDst++ = ',';
773
774 /*
775 * For if1of and ifn1of we require a space after the comma, whereas ifeq and
776 * ifneq shall not have any blanks. This is to help tell them apart.
777 */
778 if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
779 *pszDst++ = ' ';
780 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
781 offSrc++;
782
783 if (pchLine[offSrc] != ')')
784 {
785 do
786 {
787 if (pchLine[offSrc] == '\\' && offSrc + 1 == cchLine)
788 {
789 if (pszDst[-1] != ' ')
790 *pszDst++ = ' ';
791 *pszDst++ = '\\';
792 *pszDst = '\0';
793 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
794 pszDst = pParser->szBuf;
795
796 memset(pszDst, ' ', offContIndent);
797 pszDst += offContIndent;
798
799 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
800 cchLine = pParser->cchLine;
801 offSrc = 0;
802 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
803 offSrc++;
804 cLines -= 1;
805 }
806
807 size_t const offSrcStart = offSrc;
808 offSrc = scmKmkSkipExpString(pchLine, cLines <= 1 ? cchLine : cchLine - 1, offSrc, ')');
809 if (pchLine[offSrc] != ')' && cLines <= 1)
810 return scmKmkGiveUp(pParser, "No closing parenthesis for '%.*s'?", cchToken, pchToken);
811
812 size_t cchCopy = offSrc - offSrcStart;
813 while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
814 cchCopy--;
815
816 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
817 } while (offSrc + 1 == cchLine && pchLine[offSrc] == '\\');
818 }
819 /* 'if1of(stuff, )' does not make sense in committed code: */
820 else if ( (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
821 && !scmKmkHasCommentMarker(pchLine, cchLine, offSrc, RT_STR_TUPLE("scm:ignore-empty-if1of-set")))
822 return scmKmkGiveUp(pParser, "Right set cannot be empty for '%.*s'", cchToken, pchToken);
823 offSrc++;
824 *pszDst++ = ')';
825
826 /*
827 * Handle comment.
828 */
829 if (offSrc < cchLine)
830 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
831
832 /*
833 * Done.
834 */
835 *pszDst = '\0';
836 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
837 return false; /* dummy */
838}
839
840
841/**
842 * Deals with: if, ifdef and ifndef
843 *
844 * @returns dummy (false) to facility return + call.
845 */
846static bool scmKmkHandleIfSpace(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
847{
848 const char *pchLine = pParser->pchLine;
849 size_t cchLine = pParser->cchLine;
850 uint32_t const cchIndent = pParser->iActualDepth
851 - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
852
853 /*
854 * Push it onto the stack.
855 *
856 * For ifndef we ignore the outmost ifndef in non-Makefile.kmk files, if
857 * the define matches the typical pattern for a file blocker.
858 */
859 bool fIgnoredNesting = false;
860 if (!fElse)
861 {
862 if (!scmKmkPushNesting(pParser, enmToken))
863 return false;
864 if (enmToken == kKmkToken_ifndef)
865 {
866 /** @todo */
867 }
868 }
869 else
870 {
871 pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
872 pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn);
873 }
874
875 /*
876 * We do not allow line continuation for ifdef and ifndef, only if.
877 */
878 uint32_t cLines = 1;
879 size_t cchMaxLeadWord = 0;
880 size_t cchTotalLine = cchLine;
881 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
882 {
883 if (enmToken != kKmkToken_if)
884 return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
885 cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord);
886 }
887
888 /*
889 * We stage the modified line in the buffer, so check that the line isn't
890 * too long (plain if can be long, but not ifndef/ifdef).
891 */
892 if (cchTotalLine + pParser->iActualDepth + 32 > sizeof(pParser->szBuf))
893 return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars",
894 cchToken, &pchLine[offToken], cchTotalLine);
895 char *pszDst = pParser->szBuf;
896
897 /*
898 * Emit indent and initial token.
899 */
900 memset(pszDst, ' ', cchIndent);
901 pszDst += cchIndent;
902
903 if (fElse)
904 pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
905
906 memcpy(pszDst, &pchLine[offToken], cchToken);
907 pszDst += cchToken;
908
909 size_t offSrc = offToken + cchToken;
910
911 /*
912 * ifndef/ifdef shall have exactly one space. For 'if' we allow up to 4, but
913 * we'll deal with that further down.
914 */
915 size_t cchSpaces = 0;
916 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
917 {
918 cchSpaces++;
919 offSrc++;
920 }
921 if (cchSpaces == 0)
922 return scmKmkGiveUp(pParser, "Nothing following '%.*s' or bogus line continuation?", cchToken, &pchLine[offToken]);
923 *pszDst++ = ' ';
924
925 /*
926 * For ifdef and ifndef there now comes a single word.
927 */
928 if (enmToken != kKmkToken_if)
929 {
930 size_t const offSrcStart = offSrc;
931 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ' ', '\t'); /** @todo probably not entirely correct */
932 if (offSrc == offSrcStart)
933 return scmKmkGiveUp(pParser, "No word following '%.*s'?", cchToken, &pchLine[offToken]);
934
935 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], offSrc - offSrcStart);
936 }
937 /*
938 * While for 'if' things are more complicated, especially if it spans more
939 * than one line.
940 */
941 else if (cLines <= 1)
942 {
943 /* Single line expression: Just assume the expression goes up to the
944 EOL or comment hash. Strip and copy as-is for now. */
945 const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
946 size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
947 while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
948 cchExpr--;
949
950 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
951 offSrc += cchExpr;
952 }
953 else
954 {
955 /* Multi line expression: We normalize leading whitespace using
956 cchMaxLeadWord for now. Expression on line 2+ are indented by two
957 extra characters, because we'd otherwise be puttin the operator on
958 the same level as the 'if', which would be confusing. Thus:
959
960 if expr1
961 + expr2
962 endif
963
964 if expr1
965 || expr2
966 endif
967
968 if expr3
969 vtg expr4
970 endif
971
972 We do '#' / EOL handling for the final line the same way as above.
973
974 Later we should add the ability to rework the expression properly,
975 making sure new lines starts with operators and such. */
976 /** @todo Implement simples expression parser and indenter, possibly also
977 * removing unnecessary parentheses. Can be shared with C/C++. */
978 if (cchMaxLeadWord > 3)
979 return scmKmkGiveUp(pParser,
980 "Bogus multi-line 'if' expression! Extra lines must start with operator (cchMaxLeadWord=%u).",
981 cchMaxLeadWord);
982 memset(pszDst, ' ', cchMaxLeadWord);
983 pszDst += cchMaxLeadWord;
984
985 size_t cchSrcContIndent = offToken + 2;
986 for (uint32_t iSubLine = 0; iSubLine < cLines - 1; iSubLine++)
987 {
988 /* Trim the line. */
989 size_t offSrcEnd = cchLine;
990 Assert(pchLine[offSrcEnd - 1] == '\\');
991 offSrcEnd--;
992
993 if (pchLine[offSrcEnd - 1] == '\\')
994 return scmKmkGiveUp(pParser, "Escaped '\\' before line continuation in 'if' expression is not allowed!");
995
996 while (offSrcEnd > offSrc && RT_C_IS_BLANK(pchLine[offSrcEnd - 1]))
997 offSrcEnd--;
998
999 /* Comments with line continuation is not allowed in commited makefiles. */
1000 if (offSrc < offSrcEnd && memchr(&pchLine[offSrc], '#', cchLine - offSrc) != NULL)
1001 return scmKmkGiveUp(pParser, "Comment in multi-line 'if' expression is not allowed to start before the final line!");
1002
1003 /* Output it. */
1004 if (offSrc < offSrcEnd)
1005 {
1006 if (iSubLine > 0 && offSrc > cchSrcContIndent)
1007 {
1008 memset(pszDst, ' ', offSrc - cchSrcContIndent);
1009 pszDst += offSrc - cchSrcContIndent;
1010 }
1011 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], offSrcEnd - offSrc);
1012 *pszDst++ = ' ';
1013 }
1014 else if (iSubLine == 0)
1015 return scmKmkGiveUp(pParser, "Expected expression after 'if', not line continuation!");
1016 *pszDst++ = '\\';
1017 *pszDst = '\0';
1018 size_t cchDst = (size_t)(pszDst - pParser->szBuf);
1019 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
1020
1021 /*
1022 * Fetch the next line and start processing it.
1023 */
1024 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1025 if (!pchLine)
1026 {
1027 ScmError(pParser->pState, VERR_INTERNAL_ERROR_3, "ScmStreamGetLine unexpectedly returned NULL!");
1028 return false;
1029 }
1030 cchLine = pParser->cchLine;
1031
1032 /* Skip leading whitespace and adjust the source continuation indent: */
1033 offSrc = 0;
1034 while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
1035 offSrc++;
1036 /** @todo tabs */
1037
1038 if (iSubLine == 0)
1039 cchSrcContIndent = offSrc;
1040
1041 /* Initial indent: */
1042 pszDst = pParser->szBuf;
1043 memset(pszDst, ' ', cchIndent + 2);
1044 pszDst += cchIndent + 2;
1045 }
1046
1047 /* Output the expression on the final line. */
1048 const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
1049 size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
1050 while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
1051 cchExpr--;
1052
1053 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
1054 offSrc += cchExpr;
1055 }
1056
1057
1058 /*
1059 * Handle comment.
1060 *
1061 * Here we check for the "scm:ignore-nesting" directive that makes us not
1062 * add indentation for this directive. We do this on the destination buffer
1063 * as that can be zero terminated and is therefore usable with strstr.
1064 */
1065 if (offSrc >= cchLine)
1066 *pszDst = '\0';
1067 else
1068 {
1069 char * const pszDstSrc = pszDst;
1070 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
1071 *pszDst = '\0';
1072
1073 /* Check for special comment making us ignore the nesting. We do this
1074 on the destination buffer since it's zero terminated allowing normal
1075 strstr use. */
1076 if (!fIgnoredNesting && strstr(pszDstSrc, "scm:ignore-nesting") != NULL)
1077 {
1078 pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting = true;
1079 pParser->iActualDepth--;
1080 ScmVerbose(pParser->pState, 5, "%u: debug: ignoring nesting - actual depth: %u\n",
1081 pParser->aDepth[pParser->iDepth - 1].iLine, pParser->iActualDepth);
1082 }
1083 }
1084
1085 /*
1086 * Done.
1087 */
1088 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1089 return false; /* dummy */
1090}
1091
1092
1093/**
1094 * Deals with: else
1095 *
1096 * @returns dummy (false) to facility return + call.
1097 */
1098static bool scmKmkHandleElse(KMKPARSER *pParser, size_t offToken)
1099{
1100 const char * const pchLine = pParser->pchLine;
1101 size_t const cchLine = pParser->cchLine;
1102
1103 if (pParser->iDepth < 1)
1104 return scmKmkGiveUp(pParser, "Lone 'else'");
1105 uint32_t const cchIndent = pParser->iActualDepth
1106 - (pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
1107
1108 /*
1109 * Look past the else and check if there any ifxxx token following it.
1110 */
1111 size_t offSrc = offToken + 4;
1112 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
1113 offSrc++;
1114 if (offSrc < cchLine)
1115 {
1116 size_t cchWord = 0;
1117 while (offSrc + cchWord < cchLine && RT_C_IS_ALNUM(pchLine[offSrc + cchWord]))
1118 cchWord++;
1119 if (cchWord)
1120 {
1121 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offSrc], cchWord);
1122 switch (enmToken)
1123 {
1124 case kKmkToken_ifeq:
1125 case kKmkToken_ifneq:
1126 case kKmkToken_if1of:
1127 case kKmkToken_ifn1of:
1128 return scmKmkHandleIfParentheses(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
1129
1130 case kKmkToken_ifdef:
1131 case kKmkToken_ifndef:
1132 case kKmkToken_if:
1133 return scmKmkHandleIfSpace(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
1134
1135 default:
1136 break;
1137 }
1138 }
1139 }
1140
1141 /*
1142 * We do not allow line continuation for these.
1143 */
1144 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
1145 return scmKmkGiveUp(pParser, "Line continuation not allowed with 'else' directive.");
1146
1147 /*
1148 * We stage the modified line in the buffer, so check that the line isn't
1149 * too long (it seriously should be).
1150 */
1151 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
1152 return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
1153 char *pszDst = pParser->szBuf;
1154
1155 /*
1156 * Emit indent and initial token.
1157 */
1158 memset(pszDst, ' ', cchIndent);
1159 pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("else"));
1160
1161 offSrc = offToken + 4;
1162
1163 /*
1164 * Handle comment.
1165 */
1166 if (offSrc < cchLine)
1167 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
1168
1169 /*
1170 * Done.
1171 */
1172 *pszDst = '\0';
1173 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1174 return false; /* dummy */
1175}
1176
1177
1178/**
1179 * Deals with: endif
1180 *
1181 * @returns dummy (false) to facility return + call.
1182 */
1183static bool scmKmkHandleEndif(KMKPARSER *pParser, size_t offToken)
1184{
1185 const char * const pchLine = pParser->pchLine;
1186 size_t const cchLine = pParser->cchLine;
1187
1188 /*
1189 * Pop a nesting.
1190 */
1191 if (pParser->iDepth < 1)
1192 return scmKmkGiveUp(pParser, "Lone 'endif'");
1193 uint32_t iDepth = pParser->iDepth - 1;
1194 pParser->iDepth = iDepth;
1195 if (!pParser->aDepth[iDepth].fIgnoreNesting)
1196 {
1197 AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++);
1198 pParser->iActualDepth -= 1;
1199 }
1200 ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endif)\n",
1201 ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth);
1202 uint32_t const cchIndent = pParser->iActualDepth;
1203
1204 /*
1205 * We do not allow line continuation for these.
1206 */
1207 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
1208 return scmKmkGiveUp(pParser, "Line continuation not allowed with 'endif' directive.");
1209
1210 /*
1211 * We stage the modified line in the buffer, so check that the line isn't
1212 * too long (it seriously should be).
1213 */
1214 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
1215 return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
1216 char *pszDst = pParser->szBuf;
1217
1218 /*
1219 * Emit indent and initial token.
1220 */
1221 memset(pszDst, ' ', cchIndent);
1222 pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("endif"));
1223
1224 size_t offSrc = offToken + 5;
1225
1226 /*
1227 * Handle comment.
1228 */
1229 if (offSrc < cchLine)
1230 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
1231
1232 /*
1233 * Done.
1234 */
1235 *pszDst = '\0';
1236 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1237 return false; /* dummy */
1238}
1239
1240
1241/**
1242 * Passing thru any line continuation lines following the current one.
1243 */
1244static bool scmKmkPassThruLineContinuationLines(KMKPARSER *pParser)
1245{
1246 while (scmKmkIsLineWithContinuation(pParser->pchLine, pParser->cchLine))
1247 {
1248 pParser->pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1249 if (!pParser->pchLine)
1250 break;
1251 ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
1252 }
1253 return false; /* dummy */
1254}
1255
1256
1257/**
1258 * For dealing with a directive w/o special formatting rules (yet).
1259 *
1260 * @returns dummy (false) to facility return + call.
1261 */
1262static bool scmKmkHandleSimple(KMKPARSER *pParser, size_t offToken, bool fIndentIt = true)
1263{
1264 const char *pchLine = pParser->pchLine;
1265 size_t cchLine = pParser->cchLine;
1266 uint32_t const cchIndent = fIndentIt ? pParser->iActualDepth : 0;
1267
1268 /*
1269 * Just reindent the statement.
1270 */
1271 ScmStreamWrite(pParser->pOut, g_szSpaces, cchIndent);
1272 ScmStreamWrite(pParser->pOut, &pchLine[offToken], cchLine - offToken);
1273 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1274
1275 /*
1276 * Check for line continuation and output concatenated lines.
1277 */
1278 scmKmkPassThruLineContinuationLines(pParser);
1279 return false; /* dummy */
1280}
1281
1282
1283static bool scmKmkHandleDefine(KMKPARSER *pParser, size_t offToken)
1284{
1285 scmKmkHandleSimple(pParser, offToken);
1286
1287 /* Hack Alert! Start out parsing the define in recipe mode.
1288
1289 Technically, we shouldn't evaluate the content of a define till it's
1290 used. However, we ASSUME they are either makefile code snippets or
1291 recipe templates. */
1292 scmKmkPushNesting(pParser, kKmkToken_define);
1293 scmKmkSetInRecipe(pParser, true);
1294 return false;
1295}
1296
1297
1298static bool scmKmkHandleEndef(KMKPARSER *pParser, size_t offToken)
1299{
1300 /* Leaving a define resets the recipt mode. */
1301 scmKmkSetInRecipe(pParser, false);
1302
1303 /*
1304 * Pop a nesting.
1305 */
1306 if (pParser->iDepth < 1)
1307 return scmKmkGiveUp(pParser, "Lone 'endef'");
1308 uint32_t iDepth = pParser->iDepth - 1;
1309 if (pParser->aDepth[iDepth].enmToken != kKmkToken_define)
1310 return scmKmkGiveUp(pParser, "Unpexected 'endef', expected 'endif' for line %u", pParser->aDepth[iDepth].iLine);
1311 pParser->iDepth = iDepth;
1312 if (!pParser->aDepth[iDepth].fIgnoreNesting)
1313 {
1314 AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++);
1315 pParser->iActualDepth -= 1;
1316 }
1317 ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endef)\n",
1318 ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth);
1319
1320 return scmKmkHandleSimple(pParser, offToken);
1321}
1322
1323
1324/**
1325 * Checks for escaped trailing slashes on a line, giving up and asking the
1326 * developer to fix those manually.
1327 *
1328 * @returns true if we gave up. false if no escaped slashed and we didn't.
1329 */
1330static bool scmKmkGiveUpIfTrailingEscapedSlashed(KMKPARSER *pParser, const char *pchLine, size_t cchLine)
1331{
1332 if (cchLine > 2 && pchLine[cchLine - 2] == '\\' && pchLine[cchLine - 1] == '\\')
1333 {
1334 scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1335 return true;
1336 }
1337 return false;
1338}
1339
1340
1341/**
1342 * Scans the given line segment for variable expansion and updates the state
1343 * accordingly.
1344 *
1345 * @returns New cExpandNesting value.
1346 * @param cExpandNesting Current variable expansion nesting
1347 * level.
1348 * @param achExpandingNestingClose String with the closing character for
1349 * each expansion level. 256 chars.
1350 * @param pchLine The string to scan.
1351 * @param cchLine Length to scan.
1352 */
1353static unsigned scmKmkScanStringForExpansions(unsigned cExpandNesting, char achExpandNestingClose[256],
1354 const char *pchLine, size_t cchLine)
1355{
1356 while (cchLine-- > 0)
1357 {
1358 char ch = *pchLine++;
1359 switch (ch)
1360 {
1361 case '$':
1362 {
1363 size_t cDollars = 1;
1364 while (cchLine > 0 && (ch = *pchLine) == '$')
1365 cDollars++, pchLine++, cchLine--;
1366 if ((cDollars & 1) && cchLine > 0 && (ch == '{' || ch == '('))
1367 {
1368 if (cExpandNesting < 256)
1369 achExpandNestingClose[cExpandNesting] = ch == '(' ? ')' : '}';
1370 cExpandNesting++;
1371 pchLine++;
1372 cchLine--;
1373 }
1374 break;
1375 }
1376
1377 case ')':
1378 case '}':
1379 if (cExpandNesting > 0 && (cExpandNesting > 256 || ch == achExpandNestingClose[cExpandNesting - 1]))
1380 cExpandNesting--;
1381 break;
1382 }
1383 }
1384 return cExpandNesting;
1385}
1386
1387
1388/**
1389 * @returns dummy (false) to facility return + call.
1390 */
1391static bool scmKmkHandleAssignment2(KMKPARSER *pParser, size_t offVarStart, size_t offVarEnd, KMKASSIGNTYPE enmType,
1392 size_t offAssignOp, unsigned fFlags)
1393{
1394 unsigned const cchIndent = pParser->iActualDepth;
1395 const char *pchLine = pParser->pchLine;
1396 size_t cchLine = pParser->cchLine;
1397 uint32_t const cLines = pParser->cLines;
1398 uint32_t iSubLine = 0;
1399
1400 RT_NOREF(fFlags);
1401 Assert(offVarStart < cchLine);
1402 Assert(offVarEnd <= cchLine);
1403 Assert(offVarStart < offVarEnd);
1404 Assert(!RT_C_IS_SPACE(pchLine[offVarStart]));
1405 Assert(!RT_C_IS_SPACE(pchLine[offVarEnd - 1]));
1406
1407 /* Assignments takes us out of recipe mode. */
1408 ScmVerbose(pParser->pState, 6, "%u: debug: assignment\n", ScmStreamTellLine(pParser->pIn));
1409 scmKmkSetInRecipe(pParser, false);
1410
1411 /* This is too much hazzle to deal with. */
1412 if (cLines > 1 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1413 return false;
1414 if (cchLine + 64 > sizeof(pParser->szBuf))
1415 return scmKmkGiveUp(pParser, "Line too long!");
1416
1417 /*
1418 * Indent and output the variable name.
1419 */
1420 char *pszDst = pParser->szBuf;
1421 memset(pszDst, ' ', cchIndent);
1422 pszDst += cchIndent;
1423 pszDst = (char *)mempcpy(pszDst, &pchLine[offVarStart], offVarEnd - offVarStart);
1424
1425 /*
1426 * Try preserve the assignment operator position, but make sure we've got a
1427 * space in front of it.
1428 */
1429 if (offAssignOp < cchLine)
1430 {
1431 size_t offDst = (size_t)(pszDst - pParser->szBuf);
1432 size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings);
1433 if (offDst < offEffAssignOp)
1434 {
1435 size_t cchSpacesToWrite = offEffAssignOp - offDst;
1436 memset(pszDst, ' ', cchSpacesToWrite);
1437 pszDst += cchSpacesToWrite;
1438 }
1439 else
1440 *pszDst++ = ' ';
1441 }
1442 else
1443 {
1444 /* Pull up the assignment operator to the variable line. */
1445 *pszDst++ = ' ';
1446
1447 /* Eat up lines till we hit the operator. */
1448 while (offAssignOp < cchLine)
1449 {
1450 const char * const pchPrevLine = pchLine;
1451 Assert(iSubLine + 1 < cLines);
1452 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1453 AssertReturn(pchLine, false /*dummy*/);
1454 cchLine = pParser->cchLine;
1455 iSubLine++;
1456 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1457 return false;
1458
1459 /* Adjust offAssignOp: */
1460 offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
1461 Assert(offAssignOp < ~(size_t)0 / 2);
1462 }
1463
1464 if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf))
1465 return scmKmkGiveUp(pParser, "Line too long!");
1466 }
1467
1468 /*
1469 * Emit the operator.
1470 */
1471 size_t offLine = offAssignOp;
1472 switch (enmType)
1473 {
1474 default:
1475 AssertReleaseFailed();
1476 RT_FALL_THRU();
1477 case kKmkAssignType_Recursive:
1478 *pszDst++ = '=';
1479 Assert(pchLine[offLine] == '=');
1480 offLine++;
1481 break;
1482 case kKmkAssignType_Conditional:
1483 *pszDst++ = '?';
1484 *pszDst++ = '=';
1485 Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '=');
1486 offLine += 2;
1487 break;
1488 case kKmkAssignType_Appending:
1489 *pszDst++ = '+';
1490 *pszDst++ = '=';
1491 Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '=');
1492 offLine += 2;
1493 break;
1494 case kKmkAssignType_Prepending:
1495 *pszDst++ = '<';
1496 *pszDst++ = '=';
1497 Assert(pchLine[offLine] == '<'); Assert(pchLine[offLine + 1] == '=');
1498 offLine += 2;
1499 break;
1500 case kKmkAssignType_Immediate:
1501 *pszDst++ = ':';
1502 Assert(pchLine[offLine] == ':');
1503 offLine++;
1504 RT_FALL_THRU();
1505 case kKmkAssignType_Simple:
1506 *pszDst++ = ':';
1507 *pszDst++ = '=';
1508 Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '=');
1509 offLine += 2;
1510 break;
1511 }
1512
1513 /*
1514 * Skip space till we hit the value or comment.
1515 */
1516 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1517 offLine++;
1518
1519/** @todo this block can probably be merged into the final loop below. */
1520 unsigned cPendingEols = 0;
1521 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1522 {
1523 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1524 AssertReturn(pchLine, false /*dummy*/);
1525 cchLine = pParser->cchLine;
1526 iSubLine++;
1527 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
1528 {
1529 *pszDst++ = ' ';
1530 *pszDst++ = '\\';
1531 *pszDst = '\0';
1532 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1533 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1534 }
1535 cPendingEols = 1;
1536
1537 /* Skip indent/whitespace. */
1538 offLine = 0;
1539 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1540 offLine++;
1541 }
1542
1543 /*
1544 * Okay, we've gotten to the value / comment part.
1545 */
1546 char achExpandNestingClose[256];
1547 unsigned cExpandNesting = 0;
1548 for (;;)
1549 {
1550 /*
1551 * The end? Flush what we've got.
1552 */
1553 if (offLine == cchLine)
1554 {
1555 Assert(iSubLine + 1 == cLines);
1556 *pszDst = '\0';
1557 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1558 if (cPendingEols > 0)
1559 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1560 return false; /* dummy */
1561 }
1562
1563 /*
1564 * Output any non-comment stuff, stripping off newlines.
1565 */
1566 const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
1567 if (pchHash != &pchLine[offLine])
1568 {
1569 /* Add space or flush pending EOLs. */
1570 if (!cPendingEols)
1571 *pszDst++ = ' ';
1572 else
1573 {
1574 unsigned iEol = 0;
1575 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
1576 do
1577 {
1578 if (iEol++ == 0) /* skip this for the 2nd empty line. */
1579 *pszDst++ = ' ';
1580 *pszDst++ = '\\';
1581 *pszDst = '\0';
1582 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1583
1584 pszDst = pParser->szBuf;
1585 memset(pszDst, ' ', cchIndent);
1586 pszDst += cchIndent;
1587
1588 size_t cTabIndent = cExpandNesting + 1;
1589 while (cTabIndent-- > 0)
1590 *pszDst++ = '\t';
1591
1592 cPendingEols--;
1593 } while (cPendingEols > 0);
1594 }
1595
1596 /* Strip backwards. */
1597 size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
1598 size_t offValueEnd = offValueEnd2;
1599 while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
1600 offValueEnd--;
1601 Assert(offValueEnd > offLine);
1602
1603 /* Append the value part we found. */
1604 pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
1605 cExpandNesting = scmKmkScanStringForExpansions(cExpandNesting, achExpandNestingClose,
1606 &pchLine[offLine], offValueEnd - offLine);
1607 offLine = offValueEnd2;
1608 }
1609
1610 /*
1611 * If we found a comment hash, emit it and whatever follows just as-is w/o
1612 * any particular reformatting. Comments within a variable definition are
1613 * usually to disable portitions of a property like _DEFS or _SOURCES.
1614 */
1615 if (pchHash != NULL)
1616 {
1617 if (cPendingEols == 0)
1618 scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst);
1619 size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
1620 *pszDst = '\0';
1621 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
1622
1623 if (cPendingEols > 1)
1624 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1625
1626 if (cPendingEols > 0)
1627 ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
1628 scmKmkPassThruLineContinuationLines(pParser);
1629 return false; /* dummy */
1630 }
1631
1632 /*
1633 * Fetch another line, if we've got one.
1634 */
1635 if (iSubLine + 1 >= cLines)
1636 Assert(offLine == cchLine);
1637 else
1638 {
1639 Assert(offLine + 1 == cchLine);
1640 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1641 {
1642 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1643 AssertReturn(pchLine, false /*dummy*/);
1644 cchLine = pParser->cchLine;
1645 iSubLine++;
1646 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
1647 {
1648 *pszDst++ = ' ';
1649 *pszDst++ = '\\';
1650 *pszDst = '\0';
1651 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1652 if (cPendingEols > 1)
1653 ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
1654 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1655 }
1656 cPendingEols++;
1657
1658 /* Deal with indent/whitespace. */
1659 offLine = 0;
1660 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1661 offLine++;
1662 }
1663 }
1664 }
1665}
1666
1667
1668/**
1669 * A rule.
1670 *
1671 * This is a bit involved. Sigh.
1672 *
1673 * @returns dummy (false) to facility return + call.
1674 */
1675static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon)
1676{
1677 SCMSTREAM *pOut = pParser->pOut;
1678 unsigned const cchIndent = pParser->iActualDepth;
1679 const char *pchLine = pParser->pchLine;
1680 size_t cchLine = pParser->cchLine;
1681 Assert(offFirstWord < cchLine);
1682 uint32_t const cLines = pParser->cLines;
1683 uint32_t iSubLine = 0;
1684
1685 /* Following this, we'll be in recipe-mode. */
1686 ScmVerbose(pParser->pState, 4, "%u: debug: start rule\n", ScmStreamTellLine(pParser->pIn));
1687 scmKmkSetInRecipe(pParser, true);
1688
1689 /* This is too much hazzle to deal with. */
1690 if (cLines > 0 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1691 return false;
1692
1693 /* Too special case. */
1694 if (offColon <= offFirstWord)
1695 return scmKmkGiveUp(pParser, "Missing target file before colon!");
1696
1697 /*
1698 * Indent it.
1699 */
1700 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1701 size_t offLine = offFirstWord;
1702
1703 /*
1704 * Process word by word past the colon, taking new lines into account.
1705 */
1706 KMKWORDSTATE WordState = { 0, 0 };
1707 KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment;
1708 unsigned cPendingEols = 0;
1709 for (;;)
1710 {
1711 /*
1712 * Output the next word.
1713 */
1714 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
1715 Assert(offLine + cchWord <= offColon);
1716 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
1717 offLine += cchWord;
1718
1719 /* Skip whitespace (if any). */
1720 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1721 offLine++;
1722
1723 /* Have we reached the colon already? */
1724 if (offLine >= offColon)
1725 {
1726 Assert(pchLine[offLine] == ':');
1727 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
1728 offLine += fDoubleColon ? 2 : 1;
1729
1730 ScmStreamPutCh(pOut, ':');
1731 if (fDoubleColon)
1732 ScmStreamPutCh(pOut, ':');
1733 break;
1734 }
1735
1736 /* Deal with new line and emit indentation. */
1737 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1738 {
1739 /* Get the next input line. */
1740 for (;;)
1741 {
1742 const char * const pchPrevLine = pchLine;
1743 Assert(iSubLine + 1 < cLines);
1744 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1745 AssertReturn(pchLine, false /*dummy*/);
1746 cchLine = pParser->cchLine;
1747 iSubLine++;
1748 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1749 return false;
1750
1751 /* Adjust offColon: */
1752 offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
1753 Assert(offColon < ~(size_t)0 / 2);
1754
1755 /* Skip leading spaces. */
1756 offLine = 0;
1757 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1758 offLine++;
1759
1760 /* Just drop empty lines. */
1761 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1762 continue;
1763
1764 /* Complete the current line and emit indent, unless we reached the colon: */
1765 if (offLine >= offColon)
1766 {
1767 Assert(pchLine[offLine] == ':');
1768 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
1769 offLine += fDoubleColon ? 2 : 1;
1770
1771 ScmStreamPutCh(pOut, ':');
1772 if (fDoubleColon)
1773 ScmStreamPutCh(pOut, ':');
1774
1775 cPendingEols = 1;
1776 }
1777 else
1778 {
1779 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
1780 ScmStreamPutEol(pOut, pParser->enmEol);
1781 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1782 if (WordState.uDepth > 0)
1783 ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1));
1784 }
1785 break;
1786 }
1787 if (offLine >= offColon)
1788 break;
1789 }
1790 else
1791 ScmStreamPutCh(pOut, ' ');
1792 enmCtx = kKmkWordCtx_TargetFile;
1793 }
1794
1795 /*
1796 * We're immediately past the colon now, so eat whitespace and newlines and
1797 * whatever till we get to a solid word or the end of the line.
1798 */
1799 /* Skip spaces - there should be exactly one. */
1800 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1801 offLine++;
1802
1803 /* Deal with new lines: */
1804 while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1805 {
1806 cPendingEols = 1;
1807
1808 Assert(iSubLine + 1 < cLines);
1809 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1810 AssertReturn(pchLine, false /*dummy*/);
1811 cchLine = pParser->cchLine;
1812 iSubLine++;
1813 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1814 return false;
1815
1816 /* Skip leading spaces. */
1817 offLine = 0;
1818 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1819 offLine++;
1820
1821 /* Just drop empty lines. */
1822 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1823 continue;
1824 }
1825
1826 /*
1827 * Special case: No dependencies.
1828 */
1829 if (offLine == cchLine && iSubLine + 1 >= cLines)
1830 {
1831 ScmStreamPutEol(pOut, pParser->enmEol);
1832 return false /*dummy*/;
1833 }
1834
1835 /*
1836 * Work the dependencies word for word. Indent in spaces + two tabs.
1837 * (Pattern rules will also end up here, but we'll just ignore that for now.)
1838 */
1839 enmCtx = kKmkWordCtx_DepFileOrAssignment;
1840 for (;;)
1841 {
1842 /* Indent the next word. */
1843 if (cPendingEols == 0)
1844 ScmStreamPutCh(pOut, ' ');
1845 else
1846 {
1847 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
1848 ScmStreamPutEol(pOut, pParser->enmEol);
1849 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1850 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
1851 if (cPendingEols > 1)
1852 {
1853 ScmStreamWrite(pOut, RT_STR_TUPLE("\\"));
1854 ScmStreamPutEol(pOut, pParser->enmEol);
1855 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1856 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
1857 }
1858 cPendingEols = 0;
1859 }
1860 if (WordState.uDepth > 0)
1861 ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1));
1862
1863 /* Get the next word and output it. */
1864 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
1865 Assert(offLine + cchWord <= cchLine);
1866
1867 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
1868 offLine += cchWord;
1869
1870 /* Skip whitespace (if any). */
1871 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1872 offLine++;
1873
1874 /* Deal with new line and emit indentation. */
1875 if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1876 {
1877 /* Get the next input line. */
1878 for (;;)
1879 {
1880 Assert(iSubLine + 1 < cLines);
1881 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1882 AssertReturn(pchLine, false /*dummy*/);
1883 cchLine = pParser->cchLine;
1884 iSubLine++;
1885 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1886 return false;
1887
1888 /* Skip leading spaces. */
1889 offLine = 0;
1890 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1891 offLine++;
1892
1893 /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
1894 cPendingEols++;
1895 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1896 continue;
1897 break;
1898 }
1899 }
1900
1901 if (offLine >= cchLine)
1902 {
1903 /* End of input. */
1904/** @todo deal with comments */
1905 Assert(iSubLine + 1 == cLines);
1906 ScmStreamPutEol(pOut, pParser->enmEol);
1907 return false; /* dummmy */
1908 }
1909 enmCtx = kKmkWordCtx_DepFile;
1910 }
1911}
1912
1913
1914/**
1915 * Checks if the (extended) line is a variable assignment.
1916 *
1917 * We scan past line continuation stuff here as the assignment operator could be
1918 * on the next line, even if that's very unlikely it is recommened by the coding
1919 * guide lines if the line needs to be split. Fortunately, though, the caller
1920 * already removes empty empty leading lines, so we only have to consider the
1921 * line continuation issue if no '=' was found on the first line.
1922 *
1923 * @returns Modified or not.
1924 * @param pParser The parser.
1925 * @param cLines Number of lines to consider.
1926 * @param cchTotalLine Total length of all the lines to consider.
1927 * @param offWord Where the first word of the line starts.
1928 * @param pfIsAssignment Where to return whether this is an assignment or
1929 * not.
1930 */
1931static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord)
1932{
1933 const char *pchLine = pParser->pchLine;
1934 size_t const cchTotalLine = pParser->cchTotalLine;
1935
1936 /*
1937 * Scan words till we find ':' or '='.
1938 */
1939 uint32_t iWord = 0;
1940 size_t offCurWord = offWord;
1941 size_t offEndPrev = 0;
1942 size_t offLine = offWord;
1943 while (offLine < cchTotalLine)
1944 {
1945 char ch = pchLine[offLine++];
1946 if (ch == '$')
1947 {
1948 /*
1949 * Skip variable expansion.
1950 */
1951 char const chOpen = pchLine[offLine++];
1952 if (chOpen == '(' || chOpen == '{')
1953 {
1954 char const chClose = chOpen == '(' ? ')' : '}';
1955 unsigned cDepth = 1;
1956 while (offLine < cchTotalLine)
1957 {
1958 ch = pchLine[offLine++];
1959 if (ch == chOpen)
1960 cDepth++;
1961 else if (ch == chClose)
1962 if (!--cDepth)
1963 break;
1964 }
1965 }
1966 /* else: $x or $$, so just skip the next character. */
1967 }
1968 else if (RT_C_IS_SPACE(ch))
1969 {
1970 /*
1971 * End of word. Skip whitespace till the next word starts.
1972 */
1973 offEndPrev = offLine - 1;
1974 Assert(offLine != offWord);
1975 while (offLine < cchTotalLine)
1976 {
1977 ch = pchLine[offLine];
1978 if (RT_C_IS_SPACE(ch))
1979 offLine++;
1980 else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n'))
1981 offLine += 2;
1982 else
1983 break;
1984 }
1985 offCurWord = offLine;
1986 iWord++;
1987
1988 /*
1989 * To simplify the assignment operator checks, we just check the
1990 * start of the 2nd word when we're here.
1991 */
1992 if (iWord == 1 && offLine < cchTotalLine)
1993 {
1994 ch = pchLine[offLine];
1995 if (ch == '=')
1996 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
1997 if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
1998 {
1999 if (ch == ':')
2000 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0);
2001 if (ch == '+')
2002 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0);
2003 if (ch == '<')
2004 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0);
2005 if (ch == '?')
2006 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
2007 }
2008 else if ( ch == ':'
2009 && pchLine[offLine + 1] == ':'
2010 && pchLine[offLine + 2] == '=')
2011 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
2012
2013 /* Check for rule while we're here. */
2014 if (ch == ':')
2015 return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
2016 }
2017 }
2018 /*
2019 * If '=' is found in the first word it's an assignment.
2020 */
2021 else if (ch == '=')
2022 {
2023 if (iWord == 0)
2024 {
2025 KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
2026 ch = pchLine[offLine - 2];
2027 if (ch == '+')
2028 enmType = kKmkAssignType_Appending;
2029 else if (ch == '?')
2030 enmType = kKmkAssignType_Conditional;
2031 else if (ch == '<')
2032 enmType = kKmkAssignType_Prepending;
2033 else
2034 {
2035 Assert(ch != ':');
2036 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
2037 }
2038 return scmKmkHandleAssignment2(pParser, offWord, offLine - 2, enmType, offLine - 2, 0);
2039 }
2040 }
2041 /*
2042 * When ':' is found it can mean a drive letter, a rule or in the
2043 * first word a simple or immediate assignment.
2044 */
2045 else if (ch == ':')
2046 {
2047 /* Check for drive letters (we ignore the archive form): */
2048 if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
2049 { /* ignore */ }
2050 else
2051 {
2052 /* Simple or immediate assignment? */
2053 ch = pchLine[offLine];
2054 if (iWord == 0)
2055 {
2056 if (ch == '=')
2057 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
2058 if (ch == ':' && pchLine[offLine + 1] == '=')
2059 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
2060 }
2061
2062 /* Okay, it's a rule then. */
2063 return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
2064 }
2065 }
2066 }
2067
2068 /*
2069 * Check if this is a $(error ) or similar function call line.
2070 *
2071 * If we're inside a 'define' we treat $$ as $ as it's probably a case of
2072 * double expansion (e.g. def_vmm_lib_dtrace_preprocess in VMM/Makefile.kmk).
2073 */
2074 if (pchLine[offWord] == '$')
2075 {
2076 size_t const cDollars = pchLine[offWord + 1] != '$' || !scmKmkIsInsideDefine(pParser) ? 1 : 2;
2077 if ( pchLine[offWord + cDollars] == '('
2078 || pchLine[offWord + cDollars] == '{')
2079 {
2080 size_t const cchLine = pParser->cchLine;
2081 size_t offEnd = offWord + cDollars + 1;
2082 char ch = '\0';
2083 while (offEnd < cchLine && (RT_C_IS_LOWER(ch = pchLine[offEnd]) || RT_C_IS_DIGIT(ch) || ch == '-'))
2084 offEnd++;
2085 if (offEnd >= cchLine || RT_C_IS_SPACE(ch) || (offEnd == cchLine - 1 && ch == '\\'))
2086 {
2087 static const RTSTRTUPLE s_aAllowedFunctions[] =
2088 {
2089 { RT_STR_TUPLE("info") },
2090 { RT_STR_TUPLE("error") },
2091 { RT_STR_TUPLE("warning") },
2092 { RT_STR_TUPLE("set-umask") },
2093 { RT_STR_TUPLE("foreach") },
2094 { RT_STR_TUPLE("call") },
2095 { RT_STR_TUPLE("eval") },
2096 { RT_STR_TUPLE("evalctx") },
2097 { RT_STR_TUPLE("evalval") },
2098 { RT_STR_TUPLE("evalvalctx") },
2099 { RT_STR_TUPLE("evalcall") },
2100 { RT_STR_TUPLE("evalcall2") },
2101 { RT_STR_TUPLE("eval-opt-var") },
2102 { RT_STR_TUPLE("kb-src-one") },
2103 };
2104 size_t cchFunc = offEnd - offWord - cDollars - 1;
2105 for (size_t i = 0; i < RT_ELEMENTS(s_aAllowedFunctions); i++)
2106 if ( cchFunc == s_aAllowedFunctions[i].cch
2107 && memcmp(&pchLine[offWord + cDollars + 1], s_aAllowedFunctions[i].psz, cchFunc) == 0)
2108 return scmKmkHandleSimple(pParser, offWord);
2109 }
2110 }
2111 }
2112
2113 /*
2114 * If we didn't find anything, output it as-as.
2115 * We use scmKmkHandleSimple in a special way to do this.
2116 */
2117 if (!RTStrStartsWith(pchLine, "$(TOOL_")) /* ValKit/Config.kmk */
2118 ScmVerbose(pParser->pState, 1, "%u: debug: Unable to make sense of this line!\n", ScmStreamTellLine(pParser->pIn));
2119 return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
2120}
2121
2122
2123static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord,
2124 bool fMustBeAssignment)
2125{
2126 /* Assignments takes us out of recipe mode. */
2127 scmKmkSetInRecipe(pParser, false);
2128
2129 RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment);
2130 return scmKmkHandleSimple(pParser, offToken);
2131}
2132
2133
2134/**
2135 * Handles a line with a recipe command.
2136 */
2137static void scmKmkHandleRecipeCommand(KMKPARSER *pParser, const char *pchLine, size_t cchLine)
2138{
2139 /*
2140 * Make sure there is only a single tab and no spaces following it.
2141 * This helps tell prerequisites from the commands in the recipe.
2142 *
2143 * Iff the line starts with a '#' it is probably a Makefile comment line,
2144 * but it will be executed by the shell (kmk_ash) and waste time. So, we
2145 * expand the initial tab into spaces when seeing that.
2146 */
2147 Assert(*pchLine == '\t');
2148 size_t offLine = 1;
2149 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2150 offLine++;
2151
2152 if (offLine < cchLine && pchLine[offLine] == '#')
2153 ScmStreamWrite(pParser->pOut, g_szSpaces, pParser->pSettings->cchTab);
2154 else
2155 ScmStreamPutCh(pParser->pOut, '\t');
2156
2157 ScmStreamWrite(pParser->pOut, &pchLine[offLine], cchLine - offLine);
2158 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
2159
2160 /*
2161 * Any continuation lines are currently just passed thru as-is.
2162 * We could insist on these also starting with tabs, but later.
2163 */
2164 scmKmkPassThruLineContinuationLines(pParser);
2165}
2166
2167
2168/**
2169 * Rewrite a kBuild makefile.
2170 *
2171 * @returns kScmMaybeModified or kScmUnmodified.
2172 * @param pIn The input stream.
2173 * @param pOut The output stream.
2174 * @param pSettings The settings.
2175 *
2176 * @todo
2177 *
2178 * Ideas for Makefile.kmk and Config.kmk:
2179 * - sort if1of/ifn1of sets.
2180 * - line continuation slashes should only be preceded by one space.
2181 */
2182SCMREWRITERRES rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2183{
2184 if (!pSettings->fStandarizeKmk)
2185 return kScmUnmodified;
2186
2187 /*
2188 * Parser state.
2189 */
2190 KMKPARSER Parser;
2191 Parser.iDepth = 0;
2192 Parser.iActualDepth = 0;
2193 Parser.fInRecipe = false;
2194 Parser.pState = pState;
2195 Parser.pIn = pIn;
2196 Parser.pOut = pOut;
2197 Parser.pSettings = pSettings;
2198
2199 /*
2200 * Iterate the file.
2201 */
2202 const char *pchLine;
2203 while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL)
2204 {
2205 size_t cchLine = Parser.cchLine;
2206
2207 /*
2208 * If we're in the command part of a recipe, anything starting with a
2209 * tab is considered another command for the recipe.
2210 */
2211 if (Parser.fInRecipe && *pchLine == '\t')
2212 {
2213 scmKmkHandleRecipeCommand(&Parser, pchLine, cchLine);
2214 continue;
2215 }
2216
2217 /*
2218 * Skip leading whitespace and check for directives (simplified).
2219 *
2220 * This is simplified in the sense that GNU make first checks for variable
2221 * assignments, so that directive can be used as variable names. We don't
2222 * want that, so we do the variable assignment check later.
2223 */
2224 size_t offLine = 0;
2225 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2226 offLine++;
2227
2228 /* Find end of word (if any) - only looking for keywords here: */
2229 size_t cchWord = 0;
2230 while ( offLine + cchWord < cchLine
2231 && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord])
2232 || pchLine[offLine + cchWord] == '-'))
2233 cchWord++;
2234 if (cchWord > 0)
2235 {
2236 /* If the line is just a line continuation slash, simply remove it
2237 (this also makes the parsing a lot easier). */
2238 if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
2239 continue;
2240
2241 /* Unlike the GNU make parser, we won't recognize 'if' or any other
2242 directives as variable names, so we can */
2243 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
2244 switch (enmToken)
2245 {
2246 case kKmkToken_ifeq:
2247 case kKmkToken_ifneq:
2248 case kKmkToken_if1of:
2249 case kKmkToken_ifn1of:
2250 scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
2251 continue;
2252
2253 case kKmkToken_ifdef:
2254 case kKmkToken_ifndef:
2255 case kKmkToken_if:
2256 scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
2257 continue;
2258
2259 case kKmkToken_else:
2260 scmKmkHandleElse(&Parser, offLine);
2261 continue;
2262
2263 case kKmkToken_endif:
2264 scmKmkHandleEndif(&Parser, offLine);
2265 continue;
2266
2267 /* Includes: */
2268 case kKmkToken_include:
2269 case kKmkToken_sinclude:
2270 case kKmkToken_dash_include:
2271 case kKmkToken_includedep:
2272 case kKmkToken_includedep_queue:
2273 case kKmkToken_includedep_flush:
2274 scmKmkHandleSimple(&Parser, offLine);
2275 continue;
2276
2277 /* Others: */
2278 case kKmkToken_define:
2279 scmKmkHandleDefine(&Parser, offLine);
2280 continue;
2281 case kKmkToken_endef:
2282 scmKmkHandleEndef(&Parser, offLine);
2283 continue;
2284
2285 case kKmkToken_override:
2286 case kKmkToken_local:
2287 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
2288 continue;
2289
2290 case kKmkToken_export:
2291 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
2292 continue;
2293
2294 case kKmkToken_unexport:
2295 case kKmkToken_undefine:
2296 scmKmkHandleSimple(&Parser, offLine);
2297 continue;
2298
2299 case kKmkToken_Comment:
2300 AssertFailed(); /* not possible */
2301 break;
2302
2303 /*
2304 * Check if it's perhaps an variable assignment or start of a rule.
2305 * We'll do this in a very simple fashion.
2306 */
2307 case kKmkToken_Word:
2308 {
2309 Parser.cLines = 1;
2310 Parser.cchTotalLine = cchLine;
2311 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2312 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
2313 scmKmkHandleAssignmentOrRule(&Parser, offLine);
2314 continue;
2315 }
2316 }
2317 }
2318 /*
2319 * Not keyword, check for assignment, rule or comment:
2320 */
2321 else if (offLine < cchLine)
2322 {
2323 if (pchLine[offLine] != '#')
2324 {
2325 Parser.cLines = 1;
2326 Parser.cchTotalLine = cchLine;
2327 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2328 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
2329 scmKmkHandleAssignmentOrRule(&Parser, offLine);
2330 continue;
2331 }
2332
2333 /*
2334 * Indent comment lines, unless the comment is too far too the right.
2335 */
2336 size_t const offEffLine = ScmCalcSpacesForSrcSpan(pchLine, 0, offLine, pSettings);
2337 if (offEffLine <= Parser.iActualDepth + 7)
2338 {
2339 ScmStreamWrite(pOut, g_szSpaces, Parser.iActualDepth);
2340 ScmStreamWrite(pOut, &pchLine[offLine], cchLine - offLine);
2341 ScmStreamPutEol(pOut, Parser.enmEol);
2342
2343 /* If line continuation is used, it's typically to disable
2344 a property variable, so we just pass it thru as-is */
2345 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
2346 {
2347 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
2348 if (!pchLine)
2349 break;
2350 cchLine = Parser.cchLine;
2351 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2352 }
2353 continue;
2354 }
2355 }
2356
2357 /*
2358 * Pass it thru as-is with line continuation.
2359 */
2360 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
2361 {
2362 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2363 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
2364 if (!pchLine)
2365 break;
2366 cchLine = Parser.cchLine;
2367 }
2368 if (pchLine)
2369 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2370 }
2371
2372 return kScmMaybeModified; /* Make the caller check */
2373}
2374
2375
2376/**
2377 * Makefile.kup are empty files, enforce this.
2378 *
2379 * @returns true if modifications were made, false if not.
2380 * @param pIn The input stream.
2381 * @param pOut The output stream.
2382 * @param pSettings The settings.
2383 */
2384SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2385{
2386 RT_NOREF2(pOut, pSettings);
2387
2388 /* These files should be zero bytes. */
2389 if (pIn->cb == 0)
2390 return kScmUnmodified;
2391 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2392 return kScmModified;
2393}
2394
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette