VirtualBox

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

Last change on this file since 99030 was 99030, checked in by vboxsync, 18 months ago

scm: scm fix. bugref:10348

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 80.9 KB
Line 
1/* $Id: scmrw-kmk.cpp 99030 2023-03-17 20:53:11Z 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 * @returns dummy (false) to facility return + call.
1310 */
1311static bool scmKmkHandleAssignment2(KMKPARSER *pParser, size_t offVarStart, size_t offVarEnd, KMKASSIGNTYPE enmType,
1312 size_t offAssignOp, unsigned fFlags)
1313{
1314 unsigned const cchIndent = pParser->iActualDepth;
1315 const char *pchLine = pParser->pchLine;
1316 size_t cchLine = pParser->cchLine;
1317 uint32_t const cLines = pParser->cLines;
1318 uint32_t iSubLine = 0;
1319
1320 RT_NOREF(fFlags);
1321 Assert(offVarStart < cchLine);
1322 Assert(offVarEnd <= cchLine);
1323 Assert(offVarStart < offVarEnd);
1324 Assert(!RT_C_IS_SPACE(pchLine[offVarStart]));
1325 Assert(!RT_C_IS_SPACE(pchLine[offVarEnd - 1]));
1326
1327 /* Assignments takes us out of recipe mode. */
1328 ScmVerbose(pParser->pState, 6, "%u: debug: assignment\n", ScmStreamTellLine(pParser->pIn));
1329 scmKmkSetInRecipe(pParser, false);
1330
1331 /* This is too much hazzle to deal with. */
1332 if (cLines > 1 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1333 return false;
1334 if (cchLine + 64 > sizeof(pParser->szBuf))
1335 return scmKmkGiveUp(pParser, "Line too long!");
1336
1337 /*
1338 * Indent and output the variable name.
1339 */
1340 char *pszDst = pParser->szBuf;
1341 memset(pszDst, ' ', cchIndent);
1342 pszDst += cchIndent;
1343 pszDst = (char *)mempcpy(pszDst, &pchLine[offVarStart], offVarEnd - offVarStart);
1344
1345 /*
1346 * Try preserve the assignment operator position, but make sure we've got a
1347 * space in front of it.
1348 */
1349 if (offAssignOp < cchLine)
1350 {
1351 size_t offDst = (size_t)(pszDst - pParser->szBuf);
1352 size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings);
1353 if (offDst < offEffAssignOp)
1354 {
1355 size_t cchSpacesToWrite = offEffAssignOp - offDst;
1356 memset(pszDst, ' ', cchSpacesToWrite);
1357 pszDst += cchSpacesToWrite;
1358 }
1359 else
1360 *pszDst++ = ' ';
1361 }
1362 else
1363 {
1364 /* Pull up the assignment operator to the variable line. */
1365 *pszDst++ = ' ';
1366
1367 /* Eat up lines till we hit the operator. */
1368 while (offAssignOp < cchLine)
1369 {
1370 const char * const pchPrevLine = pchLine;
1371 Assert(iSubLine + 1 < cLines);
1372 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1373 AssertReturn(pchLine, false /*dummy*/);
1374 cchLine = pParser->cchLine;
1375 iSubLine++;
1376 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1377 return false;
1378
1379 /* Adjust offAssignOp: */
1380 offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
1381 Assert(offAssignOp < ~(size_t)0 / 2);
1382 }
1383
1384 if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf))
1385 return scmKmkGiveUp(pParser, "Line too long!");
1386 }
1387
1388 /*
1389 * Emit the operator.
1390 */
1391 size_t offLine = offAssignOp;
1392 switch (enmType)
1393 {
1394 default:
1395 AssertReleaseFailed();
1396 RT_FALL_THRU();
1397 case kKmkAssignType_Recursive:
1398 *pszDst++ = '=';
1399 Assert(pchLine[offLine] == '=');
1400 offLine++;
1401 break;
1402 case kKmkAssignType_Conditional:
1403 *pszDst++ = '?';
1404 *pszDst++ = '=';
1405 Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '=');
1406 offLine += 2;
1407 break;
1408 case kKmkAssignType_Appending:
1409 *pszDst++ = '+';
1410 *pszDst++ = '=';
1411 Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '=');
1412 offLine += 2;
1413 break;
1414 case kKmkAssignType_Prepending:
1415 *pszDst++ = '<';
1416 *pszDst++ = '=';
1417 Assert(pchLine[offLine] == '<'); Assert(pchLine[offLine + 1] == '=');
1418 offLine += 2;
1419 break;
1420 case kKmkAssignType_Immediate:
1421 *pszDst++ = ':';
1422 Assert(pchLine[offLine] == ':');
1423 offLine++;
1424 RT_FALL_THRU();
1425 case kKmkAssignType_Simple:
1426 *pszDst++ = ':';
1427 *pszDst++ = '=';
1428 Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '=');
1429 offLine += 2;
1430 break;
1431 }
1432
1433 /*
1434 * Skip space till we hit the value or comment.
1435 */
1436 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1437 offLine++;
1438
1439/** @todo this block can probably be merged into the final loop below. */
1440 unsigned cPendingEols = 0;
1441 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1442 {
1443 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1444 AssertReturn(pchLine, false /*dummy*/);
1445 cchLine = pParser->cchLine;
1446 iSubLine++;
1447 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
1448 {
1449 *pszDst++ = ' ';
1450 *pszDst++ = '\\';
1451 *pszDst = '\0';
1452 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1453 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1454 }
1455 cPendingEols = 1;
1456
1457 /* Skip indent/whitespace. */
1458 offLine = 0;
1459 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1460 offLine++;
1461 }
1462
1463 /*
1464 * Okay, we've gotten to the value / comment part.
1465 */
1466 for (;;)
1467 {
1468 /*
1469 * The end? Flush what we've got.
1470 */
1471 if (offLine == cchLine)
1472 {
1473 Assert(iSubLine + 1 == cLines);
1474 *pszDst = '\0';
1475 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1476 if (cPendingEols > 0)
1477 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1478 return false; /* dummy */
1479 }
1480
1481 /*
1482 * Output any non-comment stuff, stripping off newlines.
1483 */
1484 const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
1485 if (pchHash != &pchLine[offLine])
1486 {
1487 /* Add space or flush pending EOLs. */
1488 if (!cPendingEols)
1489 *pszDst++ = ' ';
1490 else
1491 {
1492 unsigned iEol = 0;
1493 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
1494 do
1495 {
1496 if (iEol++ == 0) /* skip this for the 2nd empty line. */
1497 *pszDst++ = ' ';
1498 *pszDst++ = '\\';
1499 *pszDst = '\0';
1500 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1501
1502 pszDst = pParser->szBuf;
1503 memset(pszDst, ' ', cchIndent);
1504 pszDst += cchIndent;
1505 *pszDst++ = '\t';
1506 cPendingEols--;
1507 } while (cPendingEols > 0);
1508 }
1509
1510 /* Strip backwards. */
1511 size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
1512 size_t offValueEnd = offValueEnd2;
1513 while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
1514 offValueEnd--;
1515 Assert(offValueEnd > offLine);
1516
1517 /* Append the value part we found. */
1518 pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
1519 offLine = offValueEnd2;
1520 }
1521
1522 /*
1523 * If we found a comment hash, emit it and whatever follows just as-is w/o
1524 * any particular reformatting. Comments within a variable definition are
1525 * usually to disable portitions of a property like _DEFS or _SOURCES.
1526 */
1527 if (pchHash != NULL)
1528 {
1529 if (cPendingEols == 0)
1530 scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst);
1531 size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
1532 *pszDst = '\0';
1533 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
1534
1535 if (cPendingEols > 1)
1536 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1537
1538 if (cPendingEols > 0)
1539 ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
1540 scmKmkPassThruLineContinuationLines(pParser);
1541 return false; /* dummy */
1542 }
1543
1544 /*
1545 * Fetch another line, if we've got one.
1546 */
1547 if (iSubLine + 1 >= cLines)
1548 Assert(offLine == cchLine);
1549 else
1550 {
1551 Assert(offLine + 1 == cchLine);
1552 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1553 {
1554 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1555 AssertReturn(pchLine, false /*dummy*/);
1556 cchLine = pParser->cchLine;
1557 iSubLine++;
1558 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
1559 {
1560 *pszDst++ = ' ';
1561 *pszDst++ = '\\';
1562 *pszDst = '\0';
1563 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1564 if (cPendingEols > 1)
1565 ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
1566 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1567 }
1568 cPendingEols++;
1569
1570 /* Deal with indent/whitespace. */
1571 offLine = 0;
1572 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1573 offLine++;
1574 }
1575 }
1576 }
1577}
1578
1579
1580/**
1581 * A rule.
1582 *
1583 * This is a bit involved. Sigh.
1584 *
1585 * @returns dummy (false) to facility return + call.
1586 */
1587static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon)
1588{
1589 SCMSTREAM *pOut = pParser->pOut;
1590 unsigned const cchIndent = pParser->iActualDepth;
1591 const char *pchLine = pParser->pchLine;
1592 size_t cchLine = pParser->cchLine;
1593 Assert(offFirstWord < cchLine);
1594 uint32_t const cLines = pParser->cLines;
1595 uint32_t iSubLine = 0;
1596
1597 /* Following this, we'll be in recipe-mode. */
1598 ScmVerbose(pParser->pState, 4, "%u: debug: start rule\n", ScmStreamTellLine(pParser->pIn));
1599 scmKmkSetInRecipe(pParser, true);
1600
1601 /* This is too much hazzle to deal with. */
1602 if (cLines > 0 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1603 return false;
1604
1605 /* Too special case. */
1606 if (offColon <= offFirstWord)
1607 return scmKmkGiveUp(pParser, "Missing target file before colon!");
1608
1609 /*
1610 * Indent it.
1611 */
1612 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1613 size_t offLine = offFirstWord;
1614
1615 /*
1616 * Process word by word past the colon, taking new lines into account.
1617 */
1618 KMKWORDSTATE WordState = { 0, 0 };
1619 KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment;
1620 unsigned cPendingEols = 0;
1621 for (;;)
1622 {
1623 /*
1624 * Output the next word.
1625 */
1626 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
1627 Assert(offLine + cchWord <= offColon);
1628 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
1629 offLine += cchWord;
1630
1631 /* Skip whitespace (if any). */
1632 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1633 offLine++;
1634
1635 /* Have we reached the colon already? */
1636 if (offLine >= offColon)
1637 {
1638 Assert(pchLine[offLine] == ':');
1639 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
1640 offLine += fDoubleColon ? 2 : 1;
1641
1642 ScmStreamPutCh(pOut, ':');
1643 if (fDoubleColon)
1644 ScmStreamPutCh(pOut, ':');
1645 break;
1646 }
1647
1648 /* Deal with new line and emit indentation. */
1649 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1650 {
1651 /* Get the next input line. */
1652 for (;;)
1653 {
1654 const char * const pchPrevLine = pchLine;
1655 Assert(iSubLine + 1 < cLines);
1656 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1657 AssertReturn(pchLine, false /*dummy*/);
1658 cchLine = pParser->cchLine;
1659 iSubLine++;
1660 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1661 return false;
1662
1663 /* Adjust offColon: */
1664 offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
1665 Assert(offColon < ~(size_t)0 / 2);
1666
1667 /* Skip leading spaces. */
1668 offLine = 0;
1669 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1670 offLine++;
1671
1672 /* Just drop empty lines. */
1673 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1674 continue;
1675
1676 /* Complete the current line and emit indent, unless we reached the colon: */
1677 if (offLine >= offColon)
1678 {
1679 Assert(pchLine[offLine] == ':');
1680 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
1681 offLine += fDoubleColon ? 2 : 1;
1682
1683 ScmStreamPutCh(pOut, ':');
1684 if (fDoubleColon)
1685 ScmStreamPutCh(pOut, ':');
1686
1687 cPendingEols = 1;
1688 }
1689 else
1690 {
1691 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
1692 ScmStreamPutEol(pOut, pParser->enmEol);
1693 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1694 if (WordState.uDepth > 0)
1695 ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1));
1696 }
1697 break;
1698 }
1699 if (offLine >= offColon)
1700 break;
1701 }
1702 else
1703 ScmStreamPutCh(pOut, ' ');
1704 enmCtx = kKmkWordCtx_TargetFile;
1705 }
1706
1707 /*
1708 * We're immediately past the colon now, so eat whitespace and newlines and
1709 * whatever till we get to a solid word or the end of the line.
1710 */
1711 /* Skip spaces - there should be exactly one. */
1712 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1713 offLine++;
1714
1715 /* Deal with new lines: */
1716 while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1717 {
1718 cPendingEols = 1;
1719
1720 Assert(iSubLine + 1 < cLines);
1721 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1722 AssertReturn(pchLine, false /*dummy*/);
1723 cchLine = pParser->cchLine;
1724 iSubLine++;
1725 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1726 return false;
1727
1728 /* Skip leading spaces. */
1729 offLine = 0;
1730 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1731 offLine++;
1732
1733 /* Just drop empty lines. */
1734 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1735 continue;
1736 }
1737
1738 /*
1739 * Special case: No dependencies.
1740 */
1741 if (offLine == cchLine && iSubLine + 1 >= cLines)
1742 {
1743 ScmStreamPutEol(pOut, pParser->enmEol);
1744 return false /*dummy*/;
1745 }
1746
1747 /*
1748 * Work the dependencies word for word. Indent in spaces + two tabs.
1749 * (Pattern rules will also end up here, but we'll just ignore that for now.)
1750 */
1751 enmCtx = kKmkWordCtx_DepFileOrAssignment;
1752 for (;;)
1753 {
1754 /* Indent the next word. */
1755 if (cPendingEols == 0)
1756 ScmStreamPutCh(pOut, ' ');
1757 else
1758 {
1759 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
1760 ScmStreamPutEol(pOut, pParser->enmEol);
1761 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1762 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
1763 if (cPendingEols > 1)
1764 {
1765 ScmStreamWrite(pOut, RT_STR_TUPLE("\\"));
1766 ScmStreamPutEol(pOut, pParser->enmEol);
1767 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1768 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
1769 }
1770 cPendingEols = 0;
1771 }
1772 if (WordState.uDepth > 0)
1773 ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1));
1774
1775 /* Get the next word and output it. */
1776 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
1777 Assert(offLine + cchWord <= cchLine);
1778
1779 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
1780 offLine += cchWord;
1781
1782 /* Skip whitespace (if any). */
1783 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1784 offLine++;
1785
1786 /* Deal with new line and emit indentation. */
1787 if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1788 {
1789 /* Get the next input line. */
1790 for (;;)
1791 {
1792 Assert(iSubLine + 1 < cLines);
1793 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1794 AssertReturn(pchLine, false /*dummy*/);
1795 cchLine = pParser->cchLine;
1796 iSubLine++;
1797 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1798 return false;
1799
1800 /* Skip leading spaces. */
1801 offLine = 0;
1802 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1803 offLine++;
1804
1805 /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
1806 cPendingEols++;
1807 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1808 continue;
1809 break;
1810 }
1811 }
1812
1813 if (offLine >= cchLine)
1814 {
1815 /* End of input. */
1816/** @todo deal with comments */
1817 Assert(iSubLine + 1 == cLines);
1818 ScmStreamPutEol(pOut, pParser->enmEol);
1819 return false; /* dummmy */
1820 }
1821 enmCtx = kKmkWordCtx_DepFile;
1822 }
1823}
1824
1825
1826/**
1827 * Checks if the (extended) line is a variable assignment.
1828 *
1829 * We scan past line continuation stuff here as the assignment operator could be
1830 * on the next line, even if that's very unlikely it is recommened by the coding
1831 * guide lines if the line needs to be split. Fortunately, though, the caller
1832 * already removes empty empty leading lines, so we only have to consider the
1833 * line continuation issue if no '=' was found on the first line.
1834 *
1835 * @returns Modified or not.
1836 * @param pParser The parser.
1837 * @param cLines Number of lines to consider.
1838 * @param cchTotalLine Total length of all the lines to consider.
1839 * @param offWord Where the first word of the line starts.
1840 * @param pfIsAssignment Where to return whether this is an assignment or
1841 * not.
1842 */
1843static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord)
1844{
1845 const char *pchLine = pParser->pchLine;
1846 size_t const cchTotalLine = pParser->cchTotalLine;
1847
1848 /*
1849 * Scan words till we find ':' or '='.
1850 */
1851 uint32_t iWord = 0;
1852 size_t offCurWord = offWord;
1853 size_t offEndPrev = 0;
1854 size_t offLine = offWord;
1855 while (offLine < cchTotalLine)
1856 {
1857 char ch = pchLine[offLine++];
1858 if (ch == '$')
1859 {
1860 /*
1861 * Skip variable expansion.
1862 */
1863 char const chOpen = pchLine[offLine++];
1864 if (chOpen == '(' || chOpen == '{')
1865 {
1866 char const chClose = chOpen == '(' ? ')' : '}';
1867 unsigned cDepth = 1;
1868 while (offLine < cchTotalLine)
1869 {
1870 ch = pchLine[offLine++];
1871 if (ch == chOpen)
1872 cDepth++;
1873 else if (ch == chClose)
1874 if (!--cDepth)
1875 break;
1876 }
1877 }
1878 /* else: $x or $$, so just skip the next character. */
1879 }
1880 else if (RT_C_IS_SPACE(ch))
1881 {
1882 /*
1883 * End of word. Skip whitespace till the next word starts.
1884 */
1885 offEndPrev = offLine - 1;
1886 Assert(offLine != offWord);
1887 while (offLine < cchTotalLine)
1888 {
1889 ch = pchLine[offLine];
1890 if (RT_C_IS_SPACE(ch))
1891 offLine++;
1892 else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n'))
1893 offLine += 2;
1894 else
1895 break;
1896 }
1897 offCurWord = offLine;
1898 iWord++;
1899
1900 /*
1901 * To simplify the assignment operator checks, we just check the
1902 * start of the 2nd word when we're here.
1903 */
1904 if (iWord == 1 && offLine < cchTotalLine)
1905 {
1906 ch = pchLine[offLine];
1907 if (ch == '=')
1908 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
1909 if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
1910 {
1911 if (ch == ':')
1912 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0);
1913 if (ch == '+')
1914 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0);
1915 if (ch == '<')
1916 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0);
1917 if (ch == '?')
1918 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
1919 }
1920 else if ( ch == ':'
1921 && pchLine[offLine + 1] == ':'
1922 && pchLine[offLine + 2] == '=')
1923 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
1924
1925 /* Check for rule while we're here. */
1926 if (ch == ':')
1927 return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
1928 }
1929 }
1930 /*
1931 * If '=' is found in the first word it's an assignment.
1932 */
1933 else if (ch == '=')
1934 {
1935 if (iWord == 0)
1936 {
1937 KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
1938 ch = pchLine[offLine - 2];
1939 if (ch == '+')
1940 enmType = kKmkAssignType_Appending;
1941 else if (ch == '?')
1942 enmType = kKmkAssignType_Conditional;
1943 else if (ch == '<')
1944 enmType = kKmkAssignType_Prepending;
1945 else
1946 {
1947 Assert(ch != ':');
1948 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
1949 }
1950 return scmKmkHandleAssignment2(pParser, offWord, offLine - 2, enmType, offLine - 2, 0);
1951 }
1952 }
1953 /*
1954 * When ':' is found it can mean a drive letter, a rule or in the
1955 * first word a simple or immediate assignment.
1956 */
1957 else if (ch == ':')
1958 {
1959 /* Check for drive letters (we ignore the archive form): */
1960 if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
1961 { /* ignore */ }
1962 else
1963 {
1964 /* Simple or immediate assignment? */
1965 ch = pchLine[offLine];
1966 if (iWord == 0)
1967 {
1968 if (ch == '=')
1969 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
1970 if (ch == ':' && pchLine[offLine + 1] == '=')
1971 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
1972 }
1973
1974 /* Okay, it's a rule then. */
1975 return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
1976 }
1977 }
1978 }
1979
1980 /*
1981 * Check if this is a $(error ) or similar function call line.
1982 *
1983 * If we're inside a 'define' we treat $$ as $ as it's probably a case of
1984 * double expansion (e.g. def_vmm_lib_dtrace_preprocess in VMM/Makefile.kmk).
1985 */
1986 if (pchLine[offWord] == '$')
1987 {
1988 size_t const cDollars = pchLine[offWord + 1] != '$' || !scmKmkIsInsideDefine(pParser) ? 1 : 2;
1989 if ( pchLine[offWord + cDollars] == '('
1990 || pchLine[offWord + cDollars] == '{')
1991 {
1992 size_t const cchLine = pParser->cchLine;
1993 size_t offEnd = offWord + cDollars + 1;
1994 char ch = '\0';
1995 while (offEnd < cchLine && (RT_C_IS_LOWER(ch = pchLine[offEnd]) || RT_C_IS_DIGIT(ch) || ch == '-'))
1996 offEnd++;
1997 if (offEnd >= cchLine || RT_C_IS_SPACE(ch) || (offEnd == cchLine - 1 && ch == '\\'))
1998 {
1999 static const RTSTRTUPLE s_aAllowedFunctions[] =
2000 {
2001 { RT_STR_TUPLE("info") },
2002 { RT_STR_TUPLE("error") },
2003 { RT_STR_TUPLE("warning") },
2004 { RT_STR_TUPLE("set-umask") },
2005 { RT_STR_TUPLE("foreach") },
2006 { RT_STR_TUPLE("call") },
2007 { RT_STR_TUPLE("eval") },
2008 { RT_STR_TUPLE("evalctx") },
2009 { RT_STR_TUPLE("evalval") },
2010 { RT_STR_TUPLE("evalvalctx") },
2011 { RT_STR_TUPLE("evalcall") },
2012 { RT_STR_TUPLE("evalcall2") },
2013 { RT_STR_TUPLE("eval-opt-var") },
2014 { RT_STR_TUPLE("kb-src-one") },
2015 };
2016 size_t cchFunc = offEnd - offWord - cDollars - 1;
2017 for (size_t i = 0; i < RT_ELEMENTS(s_aAllowedFunctions); i++)
2018 if ( cchFunc == s_aAllowedFunctions[i].cch
2019 && memcmp(&pchLine[offWord + cDollars + 1], s_aAllowedFunctions[i].psz, cchFunc) == 0)
2020 return scmKmkHandleSimple(pParser, offWord);
2021 }
2022 }
2023 }
2024
2025 /*
2026 * If we didn't find anything, output it as-as.
2027 * We use scmKmkHandleSimple in a special way to do this.
2028 */
2029 if (!RTStrStartsWith(pchLine, "$(TOOL_")) /* ValKit/Config.kmk */
2030 ScmVerbose(pParser->pState, 1, "%u: debug: Unable to make sense of this line!\n", ScmStreamTellLine(pParser->pIn));
2031 return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
2032}
2033
2034
2035static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord,
2036 bool fMustBeAssignment)
2037{
2038 /* Assignments takes us out of recipe mode. */
2039 scmKmkSetInRecipe(pParser, false);
2040
2041 RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment);
2042 return scmKmkHandleSimple(pParser, offToken);
2043}
2044
2045
2046/**
2047 * Handles a line with a recipe command.
2048 */
2049static void scmKmkHandleRecipeCommand(KMKPARSER *pParser, const char *pchLine, size_t cchLine)
2050{
2051 /*
2052 * Make sure there is only a single tab and no spaces following it.
2053 * This helps tell prerequisites from the commands in the recipe.
2054 *
2055 * Iff the line starts with a '#' it is probably a Makefile comment line,
2056 * but it will be executed by the shell (kmk_ash) and waste time. So, we
2057 * expand the initial tab into spaces when seeing that.
2058 */
2059 Assert(*pchLine == '\t');
2060 size_t offLine = 1;
2061 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2062 offLine++;
2063
2064 if (offLine < cchLine && pchLine[offLine] == '#')
2065 ScmStreamWrite(pParser->pOut, g_szSpaces, pParser->pSettings->cchTab);
2066 else
2067 ScmStreamPutCh(pParser->pOut, '\t');
2068
2069 ScmStreamWrite(pParser->pOut, &pchLine[offLine], cchLine - offLine);
2070 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
2071
2072 /*
2073 * Any continuation lines are currently just passed thru as-is.
2074 * We could insist on these also starting with tabs, but later.
2075 */
2076 scmKmkPassThruLineContinuationLines(pParser);
2077}
2078
2079
2080/**
2081 * Rewrite a kBuild makefile.
2082 *
2083 * @returns kScmMaybeModified or kScmUnmodified.
2084 * @param pIn The input stream.
2085 * @param pOut The output stream.
2086 * @param pSettings The settings.
2087 *
2088 * @todo
2089 *
2090 * Ideas for Makefile.kmk and Config.kmk:
2091 * - sort if1of/ifn1of sets.
2092 * - line continuation slashes should only be preceded by one space.
2093 */
2094SCMREWRITERRES rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2095{
2096 if (!pSettings->fStandarizeKmk)
2097 return kScmUnmodified;
2098
2099 /*
2100 * Parser state.
2101 */
2102 KMKPARSER Parser;
2103 Parser.iDepth = 0;
2104 Parser.iActualDepth = 0;
2105 Parser.fInRecipe = false;
2106 Parser.pState = pState;
2107 Parser.pIn = pIn;
2108 Parser.pOut = pOut;
2109 Parser.pSettings = pSettings;
2110
2111 /*
2112 * Iterate the file.
2113 */
2114 const char *pchLine;
2115 while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL)
2116 {
2117 size_t cchLine = Parser.cchLine;
2118
2119 /*
2120 * If we're in the command part of a recipe, anything starting with a
2121 * tab is considered another command for the recipe.
2122 */
2123 if (Parser.fInRecipe && *pchLine == '\t')
2124 {
2125 scmKmkHandleRecipeCommand(&Parser, pchLine, cchLine);
2126 continue;
2127 }
2128
2129 /*
2130 * Skip leading whitespace and check for directives (simplified).
2131 *
2132 * This is simplified in the sense that GNU make first checks for variable
2133 * assignments, so that directive can be used as variable names. We don't
2134 * want that, so we do the variable assignment check later.
2135 */
2136 size_t offLine = 0;
2137 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2138 offLine++;
2139
2140 /* Find end of word (if any) - only looking for keywords here: */
2141 size_t cchWord = 0;
2142 while ( offLine + cchWord < cchLine
2143 && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord])
2144 || pchLine[offLine + cchWord] == '-'))
2145 cchWord++;
2146 if (cchWord > 0)
2147 {
2148 /* If the line is just a line continuation slash, simply remove it
2149 (this also makes the parsing a lot easier). */
2150 if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
2151 continue;
2152
2153 /* Unlike the GNU make parser, we won't recognize 'if' or any other
2154 directives as variable names, so we can */
2155 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
2156 switch (enmToken)
2157 {
2158 case kKmkToken_ifeq:
2159 case kKmkToken_ifneq:
2160 case kKmkToken_if1of:
2161 case kKmkToken_ifn1of:
2162 scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
2163 continue;
2164
2165 case kKmkToken_ifdef:
2166 case kKmkToken_ifndef:
2167 case kKmkToken_if:
2168 scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
2169 continue;
2170
2171 case kKmkToken_else:
2172 scmKmkHandleElse(&Parser, offLine);
2173 continue;
2174
2175 case kKmkToken_endif:
2176 scmKmkHandleEndif(&Parser, offLine);
2177 continue;
2178
2179 /* Includes: */
2180 case kKmkToken_include:
2181 case kKmkToken_sinclude:
2182 case kKmkToken_dash_include:
2183 case kKmkToken_includedep:
2184 case kKmkToken_includedep_queue:
2185 case kKmkToken_includedep_flush:
2186 scmKmkHandleSimple(&Parser, offLine);
2187 continue;
2188
2189 /* Others: */
2190 case kKmkToken_define:
2191 scmKmkHandleDefine(&Parser, offLine);
2192 continue;
2193 case kKmkToken_endef:
2194 scmKmkHandleEndef(&Parser, offLine);
2195 continue;
2196
2197 case kKmkToken_override:
2198 case kKmkToken_local:
2199 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
2200 continue;
2201
2202 case kKmkToken_export:
2203 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
2204 continue;
2205
2206 case kKmkToken_unexport:
2207 case kKmkToken_undefine:
2208 scmKmkHandleSimple(&Parser, offLine);
2209 continue;
2210
2211 case kKmkToken_Comment:
2212 AssertFailed(); /* not possible */
2213 break;
2214
2215 /*
2216 * Check if it's perhaps an variable assignment or start of a rule.
2217 * We'll do this in a very simple fashion.
2218 */
2219 case kKmkToken_Word:
2220 {
2221 Parser.cLines = 1;
2222 Parser.cchTotalLine = cchLine;
2223 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2224 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
2225 scmKmkHandleAssignmentOrRule(&Parser, offLine);
2226 continue;
2227 }
2228 }
2229 }
2230 /*
2231 * Not keyword, check for assignment, rule or comment:
2232 */
2233 else if (offLine < cchLine)
2234 {
2235 if (pchLine[offLine] != '#')
2236 {
2237 Parser.cLines = 1;
2238 Parser.cchTotalLine = cchLine;
2239 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2240 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
2241 scmKmkHandleAssignmentOrRule(&Parser, offLine);
2242 continue;
2243 }
2244
2245 /*
2246 * Indent comment lines, unless the comment is too far too the right.
2247 */
2248 size_t const offEffLine = ScmCalcSpacesForSrcSpan(pchLine, 0, offLine, pSettings);
2249 if (offEffLine <= Parser.iActualDepth + 7)
2250 {
2251 ScmStreamWrite(pOut, g_szSpaces, Parser.iActualDepth);
2252 ScmStreamWrite(pOut, &pchLine[offLine], cchLine - offLine);
2253 ScmStreamPutEol(pOut, Parser.enmEol);
2254
2255 /* If line continuation is used, it's typically to disable
2256 a property variable, so we just pass it thru as-is */
2257 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
2258 {
2259 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
2260 if (!pchLine)
2261 break;
2262 cchLine = Parser.cchLine;
2263 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2264 }
2265 continue;
2266 }
2267 }
2268
2269 /*
2270 * Pass it thru as-is with line continuation.
2271 */
2272 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
2273 {
2274 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2275 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
2276 if (!pchLine)
2277 break;
2278 cchLine = Parser.cchLine;
2279 }
2280 if (pchLine)
2281 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2282 }
2283
2284 return kScmMaybeModified; /* Make the caller check */
2285}
2286
2287
2288/**
2289 * Makefile.kup are empty files, enforce this.
2290 *
2291 * @returns true if modifications were made, false if not.
2292 * @param pIn The input stream.
2293 * @param pOut The output stream.
2294 * @param pSettings The settings.
2295 */
2296SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2297{
2298 RT_NOREF2(pOut, pSettings);
2299
2300 /* These files should be zero bytes. */
2301 if (pIn->cb == 0)
2302 return kScmUnmodified;
2303 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2304 return kScmModified;
2305}
2306
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle
ContactPrivacy/Do Not Sell My InfoTerms of Use