VirtualBox

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

Last change on this file since 98416 was 98407, checked in by vboxsync, 22 months ago

scm: More on the kmk makefile cleanup. bugref:10348

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