VirtualBox

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

Last change on this file since 98394 was 98393, checked in by vboxsync, 2 years 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.0 KB
Line 
1/* $Id: scmrw-kmk.cpp 98393 2023-02-01 14:35:20Z 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 return false; /* dummy */
1442 }
1443
1444 /*
1445 * Output any non-comment stuff, stripping off newlines.
1446 */
1447 const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
1448 if (pchHash != &pchLine[offLine])
1449 {
1450 /* Add space or flush pending EOLs. */
1451 if (!cPendingEols)
1452 *pszDst++ = ' ';
1453 else
1454 {
1455 unsigned iEol = 0;
1456 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
1457 do
1458 {
1459 if (iEol++ == 0) /* skip this for the 2nd empty line. */
1460 *pszDst++ = ' ';
1461 *pszDst++ = '\\';
1462 *pszDst = '\0';
1463 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1464
1465 pszDst = pParser->szBuf;
1466 memset(pszDst, ' ', cchIndent);
1467 pszDst += cchIndent;
1468 *pszDst++ = '\t';
1469 cPendingEols--;
1470 } while (cPendingEols > 0);
1471 }
1472
1473 /* Strip backwards. */
1474 size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
1475 size_t offValueEnd = offValueEnd2;
1476 while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
1477 offValueEnd--;
1478 Assert(offValueEnd > offLine);
1479
1480 /* Append the value part we found. */
1481 pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
1482 offLine = offValueEnd2;
1483 }
1484
1485 /*
1486 * If we found a comment hash, emit it and whatever follows just as-is w/o
1487 * any particular reformatting. Comments within a variable definition are
1488 * usually to disable portitions of a property like _DEFS or _SOURCES.
1489 */
1490 if (pchHash != NULL)
1491 {
1492 if (cPendingEols == 0)
1493 scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst);
1494 size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
1495 *pszDst = '\0';
1496 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
1497
1498 if (cPendingEols > 1)
1499 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
1500
1501 if (cPendingEols > 0)
1502 ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
1503 scmKmkPassThruLineContinuationLines(pParser);
1504 return false; /* dummy */
1505 }
1506
1507 /*
1508 * Fetch another line, if we've got one.
1509 */
1510 if (iSubLine + 1 >= cLines)
1511 Assert(offLine == cchLine);
1512 else
1513 {
1514 Assert(offLine + 1 == cchLine);
1515 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1516 {
1517 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1518 AssertReturn(pchLine, false /*dummy*/);
1519 cchLine = pParser->cchLine;
1520 iSubLine++;
1521 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
1522 {
1523 *pszDst++ = ' ';
1524 *pszDst++ = '\\';
1525 *pszDst = '\0';
1526 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
1527 if (cPendingEols > 1)
1528 ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
1529 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
1530 }
1531 cPendingEols++;
1532
1533 /* Deal with indent/whitespace. */
1534 offLine = 0;
1535 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1536 offLine++;
1537 }
1538 }
1539 }
1540}
1541
1542
1543/**
1544 * A rule.
1545 *
1546 * This is a bit involved. Sigh.
1547 *
1548 * @returns dummy (false) to facility return + call.
1549 */
1550static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon)
1551{
1552 SCMSTREAM *pOut = pParser->pOut;
1553 unsigned const cchIndent = pParser->iActualDepth;
1554 const char *pchLine = pParser->pchLine;
1555 size_t cchLine = pParser->cchLine;
1556 Assert(offFirstWord < cchLine);
1557 uint32_t const cLines = pParser->cLines;
1558 uint32_t iSubLine = 0;
1559
1560 /* Following this, we'll be in recipe-mode. */
1561 ScmVerbose(pParser->pState, 4, "%u: debug: start rule\n", ScmStreamTellLine(pParser->pIn));
1562 scmKmkSetInRecipe(pParser, true);
1563
1564 /* This is too much hazzle to deal with. */
1565 if (cLines > 0 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1566 return false;
1567
1568 /* Too special case. */
1569 if (offColon <= offFirstWord)
1570 return scmKmkGiveUp(pParser, "Missing target file before colon!");
1571
1572 /*
1573 * Indent it.
1574 */
1575 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1576 size_t offLine = offFirstWord;
1577
1578 /*
1579 * Process word by word past the colon, taking new lines into account.
1580 */
1581 KMKWORDSTATE WordState = { 0, 0 };
1582 KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment;
1583 bool fPendingEol = false;
1584 for (;;)
1585 {
1586 /*
1587 * Output the next word.
1588 */
1589 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
1590 Assert(offLine + cchWord <= offColon);
1591 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
1592 offLine += cchWord;
1593
1594 /* Skip whitespace (if any). */
1595 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1596 offLine++;
1597
1598 /* Have we reached the colon already? */
1599 if (offLine >= offColon)
1600 {
1601 Assert(pchLine[offLine] == ':');
1602 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
1603 offLine += fDoubleColon ? 2 : 1;
1604
1605 ScmStreamPutCh(pOut, ':');
1606 if (fDoubleColon)
1607 ScmStreamPutCh(pOut, ':');
1608 break;
1609 }
1610
1611 /* Deal with new line and emit indentation. */
1612 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1613 {
1614 /* Get the next input line. */
1615 for (;;)
1616 {
1617 const char * const pchPrevLine = pchLine;
1618 Assert(iSubLine + 1 < cLines);
1619 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1620 AssertReturn(pchLine, false /*dummy*/);
1621 cchLine = pParser->cchLine;
1622 iSubLine++;
1623 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1624 return false;
1625
1626 /* Adjust offColon: */
1627 offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
1628 Assert(offColon < ~(size_t)0 / 2);
1629
1630 /* Skip leading spaces. */
1631 offLine = 0;
1632 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1633 offLine++;
1634
1635 /* Just drop empty lines. */
1636 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1637 continue;
1638
1639 /* Complete the current line and emit indent, unless we reached the colon: */
1640 if (offLine >= offColon)
1641 {
1642 Assert(pchLine[offLine] == ':');
1643 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
1644 offLine += fDoubleColon ? 2 : 1;
1645
1646 ScmStreamPutCh(pOut, ':');
1647 if (fDoubleColon)
1648 ScmStreamPutCh(pOut, ':');
1649
1650 fPendingEol = true;
1651 }
1652 else
1653 {
1654 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
1655 ScmStreamPutEol(pOut, pParser->enmEol);
1656 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1657 }
1658 break;
1659 }
1660 if (offLine >= offColon)
1661 break;
1662 }
1663 else
1664 ScmStreamPutCh(pOut, ' ');
1665 enmCtx = kKmkWordCtx_TargetFile;
1666 }
1667
1668 /*
1669 * We're immediately past the colon now, so eat whitespace and newlines and
1670 * whatever till we get to a solid word or the end of the line.
1671 */
1672 /* Skip spaces - there should be exactly one. */
1673 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1674 offLine++;
1675
1676 /* Deal with new lines: */
1677 while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1678 {
1679 fPendingEol = true;
1680
1681 Assert(iSubLine + 1 < cLines);
1682 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1683 AssertReturn(pchLine, false /*dummy*/);
1684 cchLine = pParser->cchLine;
1685 iSubLine++;
1686 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1687 return false;
1688
1689 /* Skip leading spaces. */
1690 offLine = 0;
1691 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1692 offLine++;
1693
1694 /* Just drop empty lines. */
1695 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1696 continue;
1697 }
1698
1699 /*
1700 * Special case: No dependencies.
1701 */
1702 if (offLine == cchLine && iSubLine + 1 >= cLines)
1703 {
1704 ScmStreamPutEol(pOut, pParser->enmEol);
1705 return false /*dummy*/;
1706 }
1707
1708 /*
1709 * Work the dependencies word for word. Indent in spaces + two tabs.
1710 * (Pattern rules will also end up here, but we'll just ignore that for now.)
1711 */
1712 enmCtx = kKmkWordCtx_DepFileOrAssignment;
1713 for (;;)
1714 {
1715 /* Indent the next word. */
1716 if (!fPendingEol)
1717 ScmStreamPutCh(pOut, ' ');
1718 else
1719 {
1720 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
1721 ScmStreamPutEol(pOut, pParser->enmEol);
1722 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
1723 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
1724 fPendingEol = false;
1725 }
1726
1727 /* Get the next word and output it. */
1728 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
1729 Assert(offLine + cchWord <= cchLine);
1730
1731 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
1732 offLine += cchWord;
1733
1734 /* Skip whitespace (if any). */
1735 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1736 offLine++;
1737
1738 /* Deal with new line and emit indentation. */
1739 if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
1740 {
1741 /* Get the next input line. */
1742 unsigned cEmptyLines = 0;
1743 for (;;)
1744 {
1745 Assert(iSubLine + 1 < cLines);
1746 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
1747 AssertReturn(pchLine, false /*dummy*/);
1748 cchLine = pParser->cchLine;
1749 iSubLine++;
1750 if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
1751 return false;
1752
1753 /* Skip leading spaces. */
1754 offLine = 0;
1755 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
1756 offLine++;
1757
1758 /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
1759 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
1760 {
1761 cEmptyLines++;
1762 continue;
1763 }
1764
1765 fPendingEol = true;
1766 break;
1767 }
1768 }
1769
1770 if (offLine >= cchLine)
1771 {
1772 /* End of input. */
1773/** @todo deal with comments */
1774 Assert(iSubLine + 1 == cLines);
1775 ScmStreamPutEol(pOut, pParser->enmEol);
1776 return false; /* dummmy */
1777 }
1778 enmCtx = kKmkWordCtx_DepFile;
1779 }
1780}
1781
1782
1783/**
1784 * Checks if the (extended) line is a variable assignment.
1785 *
1786 * We scan past line continuation stuff here as the assignment operator could be
1787 * on the next line, even if that's very unlikely it is recommened by the coding
1788 * guide lines if the line needs to be split. Fortunately, though, the caller
1789 * already removes empty empty leading lines, so we only have to consider the
1790 * line continuation issue if no '=' was found on the first line.
1791 *
1792 * @returns Modified or not.
1793 * @param pParser The parser.
1794 * @param cLines Number of lines to consider.
1795 * @param cchTotalLine Total length of all the lines to consider.
1796 * @param offWord Where the first word of the line starts.
1797 * @param pfIsAssignment Where to return whether this is an assignment or
1798 * not.
1799 */
1800static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord)
1801{
1802 const char *pchLine = pParser->pchLine;
1803 size_t const cchTotalLine = pParser->cchTotalLine;
1804
1805 /*
1806 * Scan words till we find ':' or '='.
1807 */
1808 uint32_t iWord = 0;
1809 size_t offCurWord = offWord;
1810 size_t offEndPrev = 0;
1811 size_t offLine = offWord;
1812 while (offLine < cchTotalLine)
1813 {
1814 char ch = pchLine[offLine++];
1815 if (ch == '$')
1816 {
1817 /*
1818 * Skip variable expansion.
1819 */
1820 char const chOpen = pchLine[offLine++];
1821 if (chOpen == '(' || chOpen == '{')
1822 {
1823 char const chClose = chOpen == '(' ? ')' : '}';
1824 unsigned cDepth = 1;
1825 while (offLine < cchTotalLine)
1826 {
1827 ch = pchLine[offLine++];
1828 if (ch == chOpen)
1829 cDepth++;
1830 else if (ch == chClose)
1831 if (!--cDepth)
1832 break;
1833 }
1834 }
1835 /* else: $x or $$, so just skip the next character. */
1836 }
1837 else if (RT_C_IS_SPACE(ch))
1838 {
1839 /*
1840 * End of word. Skip whitespace till the next word starts.
1841 */
1842 offEndPrev = offLine - 1;
1843 Assert(offLine != offWord);
1844 while (offLine < cchTotalLine)
1845 {
1846 ch = pchLine[offLine];
1847 if (RT_C_IS_SPACE(ch))
1848 offLine++;
1849 else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n'))
1850 offLine += 2;
1851 else
1852 break;
1853 }
1854 offCurWord = offLine;
1855 iWord++;
1856
1857 /*
1858 * To simplify the assignment operator checks, we just check the
1859 * start of the 2nd word when we're here.
1860 */
1861 if (iWord == 1 && offLine < cchTotalLine)
1862 {
1863 ch = pchLine[offLine];
1864 if (ch == '=')
1865 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
1866 if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
1867 {
1868 if (ch == ':')
1869 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0);
1870 if (ch == '+')
1871 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0);
1872 if (ch == '<')
1873 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0);
1874 if (ch == '?')
1875 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
1876 }
1877 else if ( ch == ':'
1878 && pchLine[offLine + 1] == ':'
1879 && pchLine[offLine + 2] == '=')
1880 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
1881
1882 /* Check for rule while we're here. */
1883 if (ch == ':')
1884 return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
1885 }
1886 }
1887 /*
1888 * If '=' is found in the first word it's an assignment.
1889 */
1890 else if (ch == '=')
1891 {
1892 if (iWord == 0)
1893 {
1894 KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
1895 ch = pchLine[offLine - 2];
1896 if (ch == '+')
1897 enmType = kKmkAssignType_Appending;
1898 else if (ch == '?')
1899 enmType = kKmkAssignType_Conditional;
1900 else if (ch == '<')
1901 enmType = kKmkAssignType_Prepending;
1902 else
1903 {
1904 Assert(ch != ':');
1905 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
1906 }
1907 return scmKmkHandleAssignment2(pParser, offWord, offLine - 2, enmType, offLine - 2, 0);
1908 }
1909 }
1910 /*
1911 * When ':' is found it can mean a drive letter, a rule or in the
1912 * first word a simple or immediate assignment.
1913 */
1914 else if (ch == ':')
1915 {
1916 /* Check for drive letters (we ignore the archive form): */
1917 if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
1918 { /* ignore */ }
1919 else
1920 {
1921 /* Simple or immediate assignment? */
1922 ch = pchLine[offLine];
1923 if (iWord == 0)
1924 {
1925 if (ch == '=')
1926 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
1927 if (ch == ':' && pchLine[offLine + 1] == '=')
1928 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
1929 }
1930
1931 /* Okay, it's a rule then. */
1932 return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
1933 }
1934 }
1935 }
1936
1937 /*
1938 * Check if this is a $(error ) or similar function call line.
1939 */
1940 if ( pchLine[offWord] == '$'
1941 && pchLine[offWord + 1] == '(')
1942 {
1943 size_t const cchLine = pParser->cchLine;
1944 size_t offEnd = offWord + 2;
1945 char ch = '\0';
1946 while (offEnd < cchLine && (RT_C_IS_LOWER(ch = pchLine[offEnd]) || RT_C_IS_DIGIT(ch) || ch == '-'))
1947 offEnd++;
1948 if (offEnd >= cchLine || RT_C_IS_SPACE(ch) || (offEnd == cchLine - 1 && ch == '\\'))
1949 {
1950 static const RTSTRTUPLE s_aAllowedFunctions[] =
1951 {
1952 { RT_STR_TUPLE("info") },
1953 { RT_STR_TUPLE("error") },
1954 { RT_STR_TUPLE("warning") },
1955 { RT_STR_TUPLE("set-umask") },
1956 { RT_STR_TUPLE("foreach") },
1957 { RT_STR_TUPLE("call") },
1958 { RT_STR_TUPLE("eval") },
1959 { RT_STR_TUPLE("evalctx") },
1960 { RT_STR_TUPLE("evalval") },
1961 { RT_STR_TUPLE("evalvalctx") },
1962 { RT_STR_TUPLE("evalcall") },
1963 { RT_STR_TUPLE("evalcall2") },
1964 { RT_STR_TUPLE("eval-opt-var") },
1965 };
1966 size_t cchFunc = offEnd - offWord - 2;
1967 for (size_t i = 0; i < RT_ELEMENTS(s_aAllowedFunctions); i++)
1968 if ( cchFunc == s_aAllowedFunctions[i].cch
1969 && memcmp(&pchLine[offWord + 2], s_aAllowedFunctions[i].psz, cchFunc) == 0)
1970 return scmKmkHandleSimple(pParser, offWord);
1971 }
1972 }
1973
1974 /*
1975 * If we didn't find anything, output it as-as.
1976 * We use scmKmkHandleSimple in a special way to do this.
1977 */
1978 ScmVerbose(pParser->pState, 1, "%u: debug: Unable to make sense of this line!\n", ScmStreamTellLine(pParser->pIn));
1979 return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
1980}
1981
1982
1983static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord,
1984 bool fMustBeAssignment)
1985{
1986 /* Assignments takes us out of recipe mode. */
1987 scmKmkSetInRecipe(pParser, false);
1988
1989 RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment);
1990 return scmKmkHandleSimple(pParser, offToken);
1991}
1992
1993
1994/**
1995 * Rewrite a kBuild makefile.
1996 *
1997 * @returns kScmMaybeModified or kScmUnmodified.
1998 * @param pIn The input stream.
1999 * @param pOut The output stream.
2000 * @param pSettings The settings.
2001 *
2002 * @todo
2003 *
2004 * Ideas for Makefile.kmk and Config.kmk:
2005 * - sort if1of/ifn1of sets.
2006 * - line continuation slashes should only be preceded by one space.
2007 */
2008SCMREWRITERRES rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2009{
2010 if (!pSettings->fStandarizeKmk)
2011 return kScmUnmodified;
2012
2013 /*
2014 * Parser state.
2015 */
2016 KMKPARSER Parser;
2017 Parser.iDepth = 0;
2018 Parser.iActualDepth = 0;
2019 Parser.fInRecipe = false;
2020 Parser.pState = pState;
2021 Parser.pIn = pIn;
2022 Parser.pOut = pOut;
2023 Parser.pSettings = pSettings;
2024
2025 /*
2026 * Iterate the file.
2027 */
2028 const char *pchLine;
2029 while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL)
2030 {
2031 size_t cchLine = Parser.cchLine;
2032
2033 /*
2034 * If we're in the command part of a recipe, anything starting with a
2035 * tab is considered another command for the recipe.
2036 */
2037 if (Parser.fInRecipe && *pchLine == '\t')
2038 {
2039 /* Do we do anything here? */
2040 }
2041 else
2042 {
2043 /*
2044 * Skip leading whitespace and check for directives (simplified).
2045 *
2046 * This is simplified in the sense that GNU make first checks for variable
2047 * assignments, so that directive can be used as variable names. We don't
2048 * want that, so we do the variable assignment check later.
2049 */
2050 size_t offLine = 0;
2051 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2052 offLine++;
2053
2054 /* Find end of word (if any) - only looking for keywords here: */
2055 size_t cchWord = 0;
2056 while ( offLine + cchWord < cchLine
2057 && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord])
2058 || pchLine[offLine + cchWord] == '-'))
2059 cchWord++;
2060 if (cchWord > 0)
2061 {
2062 /* If the line is just a line continuation slash, simply remove it
2063 (this also makes the parsing a lot easier). */
2064 if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
2065 continue;
2066
2067 /* Unlike the GNU make parser, we won't recognize 'if' or any other
2068 directives as variable names, so we can */
2069 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
2070 switch (enmToken)
2071 {
2072 case kKmkToken_ifeq:
2073 case kKmkToken_ifneq:
2074 case kKmkToken_if1of:
2075 case kKmkToken_ifn1of:
2076 scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
2077 continue;
2078
2079 case kKmkToken_ifdef:
2080 case kKmkToken_ifndef:
2081 case kKmkToken_if:
2082 scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
2083 continue;
2084
2085 case kKmkToken_else:
2086 scmKmkHandleElse(&Parser, offLine);
2087 continue;
2088
2089 case kKmkToken_endif:
2090 scmKmkHandleEndif(&Parser, offLine);
2091 continue;
2092
2093 /* Includes: */
2094 case kKmkToken_include:
2095 case kKmkToken_sinclude:
2096 case kKmkToken_dash_include:
2097 case kKmkToken_includedep:
2098 case kKmkToken_includedep_queue:
2099 case kKmkToken_includedep_flush:
2100 scmKmkHandleSimple(&Parser, offLine);
2101 continue;
2102
2103 /* Others: */
2104 case kKmkToken_define:
2105 scmKmkHandleDefine(&Parser, offLine);
2106 continue;
2107 case kKmkToken_endef:
2108 scmKmkHandleEndef(&Parser, offLine);
2109 continue;
2110
2111 case kKmkToken_override:
2112 case kKmkToken_local:
2113 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
2114 continue;
2115
2116 case kKmkToken_export:
2117 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
2118 continue;
2119
2120 case kKmkToken_unexport:
2121 case kKmkToken_undefine:
2122 scmKmkHandleSimple(&Parser, offLine);
2123 break;
2124
2125 case kKmkToken_Comment:
2126 AssertFailed(); /* not possible */
2127 break;
2128
2129 /*
2130 * Check if it's perhaps an variable assignment or start of a rule.
2131 * We'll do this in a very simple fashion.
2132 */
2133 case kKmkToken_Word:
2134 {
2135 Parser.cLines = 1;
2136 Parser.cchTotalLine = cchLine;
2137 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2138 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
2139 scmKmkHandleAssignmentOrRule(&Parser, offLine);
2140 continue;
2141 }
2142 }
2143 }
2144 /*
2145 * Not keyword, check for assignment, rule or comment:
2146 */
2147 else if (offLine < cchLine)
2148 {
2149 if (pchLine[offLine] != '#')
2150 {
2151 Parser.cLines = 1;
2152 Parser.cchTotalLine = cchLine;
2153 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2154 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
2155 scmKmkHandleAssignmentOrRule(&Parser, offLine);
2156 continue;
2157 }
2158
2159 /*
2160 * Indent comment lines, unless the comment is too far into the line and such.
2161 */
2162 size_t const offEffLine = ScmCalcSpacesForSrcSpan(pchLine, 0, offLine, pSettings);
2163 if (offEffLine <= Parser.iActualDepth + 7)
2164 {
2165 ScmStreamWrite(pOut, g_szSpaces, Parser.iActualDepth);
2166 ScmStreamWrite(pOut, &pchLine[offLine], cchLine - offLine);
2167 ScmStreamPutEol(pOut, Parser.enmEol);
2168 continue;
2169 }
2170 }
2171 }
2172
2173 /*
2174 * Pass it thru as-is with line continuation.
2175 */
2176 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
2177 {
2178 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2179 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
2180 if (!pchLine)
2181 break;
2182 cchLine = Parser.cchLine;
2183 }
2184 if (pchLine)
2185 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
2186 }
2187
2188 return kScmMaybeModified; /* Make the caller check */
2189}
2190
2191
2192/**
2193 * Makefile.kup are empty files, enforce this.
2194 *
2195 * @returns true if modifications were made, false if not.
2196 * @param pIn The input stream.
2197 * @param pOut The output stream.
2198 * @param pSettings The settings.
2199 */
2200SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2201{
2202 RT_NOREF2(pOut, pSettings);
2203
2204 /* These files should be zero bytes. */
2205 if (pIn->cb == 0)
2206 return kScmUnmodified;
2207 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2208 return kScmModified;
2209}
2210
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