VirtualBox

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

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

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