VirtualBox

source: vbox/trunk/src/bldprogs/scmrw.cpp@ 98364

Last change on this file since 98364 was 98364, 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: 212.8 KB
Line 
1/* $Id: scmrw.cpp 98364 2023-01-31 14:57:25Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
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*********************************************************************************************************************************/
54/** License types. */
55typedef enum SCMLICENSETYPE
56{
57 kScmLicenseType_Invalid = 0,
58 kScmLicenseType_OseGpl,
59 kScmLicenseType_OseDualGplCddl,
60 kScmLicenseType_OseCddl,
61 kScmLicenseType_VBoxLgpl,
62 kScmLicenseType_Mit,
63 kScmLicenseType_Confidential
64} SCMLICENSETYPE;
65
66/** A license. */
67typedef struct SCMLICENSETEXT
68{
69 /** The license type. */
70 SCMLICENSETYPE enmType;
71 /** The license option. */
72 SCMLICENSE enmOpt;
73 /** The license text. */
74 const char *psz;
75 /** The license text length. */
76 size_t cch;
77} SCMLICENSETEXT;
78/** Pointer to a license. */
79typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;
80
81/**
82 * Copyright + license rewriter state.
83 */
84typedef struct SCMCOPYRIGHTINFO
85{
86 /** State. */
87 PSCMRWSTATE pState; /**< input */
88 /** The comment style (neede for C/C++). */
89 SCMCOMMENTSTYLE enmCommentStyle; /**< input */
90
91 /** Number of comments we've parsed. */
92 uint32_t cComments;
93
94 /** Copy of the contributed-by line if present. */
95 char *pszContributedBy;
96
97 /** @name Common info
98 * @{ */
99 uint32_t iLineComment;
100 uint32_t cLinesComment; /**< This excludes any external license lines. */
101 /** @} */
102
103 /** @name Copyright info
104 * @{ */
105 uint32_t iLineCopyright;
106 uint32_t uFirstYear;
107 uint32_t uLastYear;
108 bool fWellFormedCopyright;
109 bool fUpToDateCopyright;
110 /** @} */
111
112 /** @name License info
113 * @{ */
114 bool fOpenSource; /**< input */
115 PCSCMLICENSETEXT pExpectedLicense; /**< input */
116 PCSCMLICENSETEXT paLicenses; /**< input */
117 SCMLICENSE enmLicenceOpt; /**< input */
118 uint32_t iLineLicense;
119 uint32_t cLinesLicense;
120 PCSCMLICENSETEXT pCurrentLicense;
121 bool fIsCorrectLicense;
122 bool fWellFormedLicense;
123 bool fExternalLicense;
124 /** @} */
125
126 /** @name LGPL licence notice and disclaimer info
127 * @{ */
128 /** Wheter to check for LGPL license notices and disclaimers. */
129 bool fCheckforLgpl;
130 /** The approximate line we found the (first) LGPL licence notice on. */
131 uint32_t iLineLgplNotice;
132 /** The line number after the LGPL notice comment. */
133 uint32_t iLineAfterLgplComment;
134 /** The LGPL disclaimer line. */
135 uint32_t iLineLgplDisclaimer;
136 /** @} */
137
138} SCMCOPYRIGHTINFO;
139typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO;
140
141
142/*********************************************************************************************************************************
143* Global Variables *
144*********************************************************************************************************************************/
145/** --license-ose-gpl */
146static const char g_szVBoxOseGpl[] =
147 "This file is part of VirtualBox base platform packages, as\n"
148 "available from https://www.virtualbox.org.\n"
149 "\n"
150 "This program is free software; you can redistribute it and/or\n"
151 "modify it under the terms of the GNU General Public License\n"
152 "as published by the Free Software Foundation, in version 3 of the\n"
153 "License.\n"
154 "\n"
155 "This program is distributed in the hope that it will be useful, but\n"
156 "WITHOUT ANY WARRANTY; without even the implied warranty of\n"
157 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
158 "General Public License for more details.\n"
159 "\n"
160 "You should have received a copy of the GNU General Public License\n"
161 "along with this program; if not, see <https://www.gnu.org/licenses>.\n"
162 "\n"
163 "SPDX-License-Identifier: GPL-3.0-only\n";
164
165static const char g_szVBoxOseOldGpl2[] =
166 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
167 "available from http://www.virtualbox.org. This file is free software;\n"
168 "you can redistribute it and/or modify it under the terms of the GNU\n"
169 "General Public License (GPL) as published by the Free Software\n"
170 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
171 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
172 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n";
173
174/** --license-ose-dual */
175static const char g_szVBoxOseDualGplCddl[] =
176 "This file is part of VirtualBox base platform packages, as\n"
177 "available from https://www.virtualbox.org.\n"
178 "\n"
179 "This program is free software; you can redistribute it and/or\n"
180 "modify it under the terms of the GNU General Public License\n"
181 "as published by the Free Software Foundation, in version 3 of the\n"
182 "License.\n"
183 "\n"
184 "This program is distributed in the hope that it will be useful, but\n"
185 "WITHOUT ANY WARRANTY; without even the implied warranty of\n"
186 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
187 "General Public License for more details.\n"
188 "\n"
189 "You should have received a copy of the GNU General Public License\n"
190 "along with this program; if not, see <https://www.gnu.org/licenses>.\n"
191 "\n"
192 "The contents of this file may alternatively be used under the terms\n"
193 "of the Common Development and Distribution License Version 1.0\n"
194 "(CDDL), a copy of it is provided in the \"COPYING.CDDL\" file included\n"
195 "in the VirtualBox distribution, in which case the provisions of the\n"
196 "CDDL are applicable instead of those of the GPL.\n"
197 "\n"
198 "You may elect to license modified versions of this file under the\n"
199 "terms and conditions of either the GPL or the CDDL or both.\n"
200 "\n"
201 "SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0\n";
202
203static const char g_szVBoxOseOldDualGpl2Cddl[] =
204 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
205 "available from http://www.virtualbox.org. This file is free software;\n"
206 "you can redistribute it and/or modify it under the terms of the GNU\n"
207 "General Public License (GPL) as published by the Free Software\n"
208 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
209 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
210 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n"
211 "\n"
212 "The contents of this file may alternatively be used under the terms\n"
213 "of the Common Development and Distribution License Version 1.0\n"
214 "(CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n"
215 "VirtualBox OSE distribution, in which case the provisions of the\n"
216 "CDDL are applicable instead of those of the GPL.\n"
217 "\n"
218 "You may elect to license modified versions of this file under the\n"
219 "terms and conditions of either the GPL or the CDDL or both.\n";
220
221/** --license-ose-cddl */
222static const char g_szVBoxOseCddl[] =
223 "This file is part of VirtualBox base platform packages, as\n"
224 "available from http://www.virtualbox.org.\n"
225 "\n"
226 "The contents of this file are subject to the terms of the Common\n"
227 "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
228 "comes in the \"COPYING.CDDL\" file of the VirtualBox distribution.\n"
229 "\n"
230 "SPDX-License-Identifier: CDDL-1.0\n";
231
232static const char g_szVBoxOseOldCddl[] =
233 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
234 "available from http://www.virtualbox.org. This file is free software;\n"
235 "you can redistribute it and/or modify it under the terms of the Common\n"
236 "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
237 "comes in the \"COPYING.CDDL\" file of the VirtualBox OSE distribution.\n"
238 "VirtualBox OSE is distributed in the hope that it will be useful, but\n"
239 "WITHOUT ANY WARRANTY of any kind.\n";
240
241/** --license-lgpl */
242static const char g_szVBoxLgpl[] =
243 "This file is part of a free software library; you can redistribute\n"
244 "it and/or modify it under the terms of the GNU Lesser General\n"
245 "Public License version 2.1 as published by the Free Software\n"
246 "Foundation and shipped in the \"COPYING.LIB\" file with this library.\n"
247 "The library is distributed in the hope that it will be useful,\n"
248 "but WITHOUT ANY WARRANTY of any kind.\n"
249 "\n"
250 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if\n"
251 "any license choice other than GPL or LGPL is available it will\n"
252 "apply instead, Oracle elects to use only the Lesser General Public\n"
253 "License version 2.1 (LGPLv2) at this time for any software where\n"
254 "a choice of LGPL license versions is made available with the\n"
255 "language indicating that LGPLv2 or any later version may be used,\n"
256 "or where a choice of which version of the LGPL is applied is\n"
257 "otherwise unspecified.\n"
258 "\n"
259 "SPDX-License-Identifier: LGPL-2.1-only\n";
260
261/** --license-mit
262 * @note This isn't detectable as VirtualBox or Oracle specific.
263 */
264static const char g_szMit[] =
265 "Permission is hereby granted, free of charge, to any person\n"
266 "obtaining a copy of this software and associated documentation\n"
267 "files (the \"Software\"), to deal in the Software without\n"
268 "restriction, including without limitation the rights to use,\n"
269 "copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
270 "copies of the Software, and to permit persons to whom the\n"
271 "Software is furnished to do so, subject to the following\n"
272 "conditions:\n"
273 "\n"
274 "The above copyright notice and this permission notice shall be\n"
275 "included in all copies or substantial portions of the Software.\n"
276 "\n"
277 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
278 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
279 "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
280 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
281 "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
282 "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
283 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
284 "OTHER DEALINGS IN THE SOFTWARE.\n";
285
286/** --license-mit, alternative wording \#1.
287 * @note This differes from g_szMit in "AUTHORS OR COPYRIGHT HOLDERS" is written
288 * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a
289 * couple of lines shorter. */
290static const char g_szMitAlt1[] =
291 "Permission is hereby granted, free of charge, to any person obtaining a\n"
292 "copy of this software and associated documentation files (the \"Software\"),\n"
293 "to deal in the Software without restriction, including without limitation\n"
294 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
295 "and/or sell copies of the Software, and to permit persons to whom the\n"
296 "Software is furnished to do so, subject to the following conditions:\n"
297 "\n"
298 "The above copyright notice and this permission notice shall be included in\n"
299 "all copies or substantial portions of the Software.\n"
300 "\n"
301 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
302 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
303 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
304 "THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR\n"
305 "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n"
306 "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
307 "OTHER DEALINGS IN THE SOFTWARE.\n";
308
309/** --license-mit, alternative wording \#2.
310 * @note This differes from g_szMit in that "AUTHORS OR COPYRIGHT HOLDERS" is
311 * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".
312 * Its layout is wider, so it is a couple of lines shorter. */
313static const char g_szMitAlt2[] =
314 "Permission is hereby granted, free of charge, to any person obtaining a\n"
315 "copy of this software and associated documentation files (the \"Software\"),\n"
316 "to deal in the Software without restriction, including without limitation\n"
317 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
318 "and/or sell copies of the Software, and to permit persons to whom the\n"
319 "Software is furnished to do so, subject to the following conditions:\n"
320 "\n"
321 "The above copyright notice and this permission notice shall be included in\n"
322 "all copies or substantial portions of the Software.\n"
323 "\n"
324 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
325 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
326 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
327 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
328 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
329 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
330 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n";
331
332/** --license-mit, alternative wording \#3.
333 * @note This differes from g_szMitAlt2 in that the second and third sections
334 * have been switch. */
335static const char g_szMitAlt3[] =
336 "Permission is hereby granted, free of charge, to any person obtaining a\n"
337 "copy of this software and associated documentation files (the \"Software\"),\n"
338 "to deal in the Software without restriction, including without limitation\n"
339 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
340 "and/or sell copies of the Software, and to permit persons to whom the\n"
341 "Software is furnished to do so, subject to the following conditions:\n"
342 "\n"
343 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
344 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
345 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
346 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
347 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
348 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
349 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
350 "\n"
351 "The above copyright notice and this permission notice shall be included in\n"
352 "all copies or substantial portions of the Software.\n";
353
354/** --license-(based-on)mit, alternative wording \#4.
355 * @note This differs from g_szMitAlt2 in injecting "(including the next
356 * paragraph)". */
357static const char g_szMitAlt4[] =
358 "Permission is hereby granted, free of charge, to any person obtaining a\n"
359 "copy of this software and associated documentation files (the \"Software\"),\n"
360 "to deal in the Software without restriction, including without limitation\n"
361 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
362 "and/or sell copies of the Software, and to permit persons to whom the\n"
363 "Software is furnished to do so, subject to the following conditions:\n"
364 "\n"
365 "The above copyright notice and this permission notice (including the next\n"
366 "paragraph) shall be included in all copies or substantial portions of the\n"
367 "Software.\n"
368 "\n"
369 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
370 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
371 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
372 "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
373 "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
374 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
375 "DEALINGS IN THE SOFTWARE.\n";
376
377/** --license-(based-on)mit, alternative wording \#5.
378 * @note This differs from g_szMitAlt3 in using "sub license" instead of
379 * "sublicense" and adding an illogical "(including the next
380 * paragraph)" remark to the final paragraph. (vbox_ttm.c) */
381static const char g_szMitAlt5[] =
382 "Permission is hereby granted, free of charge, to any person obtaining a\n"
383 "copy of this software and associated documentation files (the\n"
384 "\"Software\"), to deal in the Software without restriction, including\n"
385 "without limitation the rights to use, copy, modify, merge, publish,\n"
386 "distribute, sub license, and/or sell copies of the Software, and to\n"
387 "permit persons to whom the Software is furnished to do so, subject to\n"
388 "the following conditions:\n"
389 "\n"
390 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
391 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
392 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
393 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
394 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
395 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
396 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
397 "\n"
398 "The above copyright notice and this permission notice (including the\n"
399 "next paragraph) shall be included in all copies or substantial portions\n"
400 "of the Software.\n";
401
402/** Oracle confidential. */
403static const char g_szOracleConfidential[] =
404 "Oracle Corporation confidential\n";
405
406/** Oracle confidential, old style. */
407static const char g_szOracleConfidentialOld[] =
408 "Oracle Corporation confidential\n"
409 "All rights reserved\n";
410
411/** Licenses to detect when --license-mit isn't used. */
412static const SCMLICENSETEXT g_aLicenses[] =
413{
414 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
415 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseOldGpl2)},
416 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
417 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseOldDualGpl2Cddl) },
418 { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseCddl) },
419 { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseOldCddl) },
420 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
421 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
422 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidentialOld) },
423 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
424};
425
426/** Licenses to detect when --license-mit or --license-based-on-mit are used. */
427static const SCMLICENSETEXT g_aLicensesWithMit[] =
428{
429 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMit) },
430 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt1) },
431 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt2) },
432 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt3) },
433 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt4) },
434 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt5) },
435 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
436 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseOldGpl2)},
437 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
438 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseOldDualGpl2Cddl) },
439 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
440 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
441 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidentialOld) },
442 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
443};
444
445/** Copyright holder. */
446static const char g_szCopyrightHolder[] = "Oracle and/or its affiliates.";
447
448/** Old copyright holder. */
449static const char g_szOldCopyrightHolder[] = "Oracle Corporation";
450
451/** LGPL disclaimer. */
452static const char g_szLgplDisclaimer[] =
453 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice\n"
454 "other than GPL or LGPL is available it will apply instead, Oracle elects to use only\n"
455 "the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where\n"
456 "a choice of LGPL license versions is made available with the language indicating\n"
457 "that LGPLv2 or any later version may be used, or where a choice of which version\n"
458 "of the LGPL is applied is otherwise unspecified.\n";
459
460/** Copyright+license comment start for each SCMCOMMENTSTYLE. */
461static RTSTRTUPLE const g_aCopyrightCommentStart[] =
462{
463 { RT_STR_TUPLE("<invalid> ") },
464 { RT_STR_TUPLE("/*") },
465 { RT_STR_TUPLE("#") },
466 { RT_STR_TUPLE("\"\"\"") },
467 { RT_STR_TUPLE(";") },
468 { RT_STR_TUPLE("REM") },
469 { RT_STR_TUPLE("rem") },
470 { RT_STR_TUPLE("Rem") },
471 { RT_STR_TUPLE("--") },
472 { RT_STR_TUPLE("'") },
473 { RT_STR_TUPLE("<!--") },
474 { RT_STR_TUPLE("<end>") },
475};
476
477/** Copyright+license line prefix for each SCMCOMMENTSTYLE. */
478static RTSTRTUPLE const g_aCopyrightCommentPrefix[] =
479{
480 { RT_STR_TUPLE("<invalid> ") },
481 { RT_STR_TUPLE(" * ") },
482 { RT_STR_TUPLE("# ") },
483 { RT_STR_TUPLE("") },
484 { RT_STR_TUPLE("; ") },
485 { RT_STR_TUPLE("REM ") },
486 { RT_STR_TUPLE("rem ") },
487 { RT_STR_TUPLE("Rem ") },
488 { RT_STR_TUPLE("-- ") },
489 { RT_STR_TUPLE("' ") },
490 { RT_STR_TUPLE(" ") },
491 { RT_STR_TUPLE("<end>") },
492};
493
494/** Copyright+license empty line for each SCMCOMMENTSTYLE. */
495static RTSTRTUPLE const g_aCopyrightCommentEmpty[] =
496{
497 { RT_STR_TUPLE("<invalid>") },
498 { RT_STR_TUPLE(" *") },
499 { RT_STR_TUPLE("#") },
500 { RT_STR_TUPLE("") },
501 { RT_STR_TUPLE(";") },
502 { RT_STR_TUPLE("REM") },
503 { RT_STR_TUPLE("rem") },
504 { RT_STR_TUPLE("Rem") },
505 { RT_STR_TUPLE("--") },
506 { RT_STR_TUPLE("'") },
507 { RT_STR_TUPLE("") },
508 { RT_STR_TUPLE("<end>") },
509};
510
511/** Copyright+license end of comment for each SCMCOMMENTSTYLE. */
512static RTSTRTUPLE const g_aCopyrightCommentEnd[] =
513{
514 { RT_STR_TUPLE("<invalid> ") },
515 { RT_STR_TUPLE(" */") },
516 { RT_STR_TUPLE("#") },
517 { RT_STR_TUPLE("\"\"\"") },
518 { RT_STR_TUPLE(";") },
519 { RT_STR_TUPLE("REM") },
520 { RT_STR_TUPLE("rem") },
521 { RT_STR_TUPLE("Rem") },
522 { RT_STR_TUPLE("--") },
523 { RT_STR_TUPLE("'") },
524 { RT_STR_TUPLE("-->") },
525 { RT_STR_TUPLE("<end>") },
526};
527
528
529/**
530 * Figures out the predominant casing of the "REM" keyword in a batch file.
531 *
532 * @returns Predominant comment style.
533 * @param pIn The file to scan. Will be rewound.
534 */
535static SCMCOMMENTSTYLE determineBatchFileCommentStyle(PSCMSTREAM pIn)
536{
537 /*
538 * Figure out whether it's using upper or lower case REM comments before
539 * doing the work.
540 */
541 uint32_t cUpper = 0;
542 uint32_t cLower = 0;
543 uint32_t cCamel = 0;
544 SCMEOL enmEol;
545 size_t cchLine;
546 const char *pchLine;
547 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
548 {
549 while ( cchLine > 2
550 && RT_C_IS_SPACE(*pchLine))
551 {
552 pchLine++;
553 cchLine--;
554 }
555 if ( ( cchLine > 3
556 && RT_C_IS_SPACE(pchLine[2]))
557 || cchLine == 3)
558 {
559 if ( pchLine[0] == 'R'
560 && pchLine[1] == 'E'
561 && pchLine[2] == 'M')
562 cUpper++;
563 else if ( pchLine[0] == 'r'
564 && pchLine[1] == 'e'
565 && pchLine[2] == 'm')
566 cLower++;
567 else if ( pchLine[0] == 'R'
568 && pchLine[1] == 'e'
569 && pchLine[2] == 'm')
570 cCamel++;
571 }
572 }
573
574 ScmStreamRewindForReading(pIn);
575
576 if (cLower >= cUpper && cLower >= cCamel)
577 return kScmCommentStyle_Rem_Lower;
578 if (cCamel >= cLower && cCamel >= cUpper)
579 return kScmCommentStyle_Rem_Camel;
580 return kScmCommentStyle_Rem_Upper;
581}
582
583
584/**
585 * Calculates the number of spaces from @a offStart to @a offEnd in @a pchLine,
586 * taking tabs into account.
587 */
588static size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings)
589{
590 size_t cchRet = 0;
591 if (offStart < offEnd)
592 {
593 offEnd -= offStart; /* becomes cchLeft now */
594 pchLine += offStart;
595 while (offEnd > 0)
596 {
597 const char *pszTab = (const char *)memchr(pchLine, '\t', offEnd);
598 if (!pszTab)
599 {
600 cchRet += offEnd;
601 break;
602 }
603 size_t offTab = (size_t)(pszTab - pchLine);
604 size_t cchToTab = pSettings->cchTab - offTab % pSettings->cchTab;
605 cchRet += offTab + cchToTab;
606 offEnd -= offTab + 1;
607 pchLine = pszTab + 1;
608 }
609 }
610 return cchRet;
611}
612
613
614/**
615 * Worker for isBlankLine.
616 *
617 * @returns true if blank, false if not.
618 * @param pchLine Pointer to the start of the line.
619 * @param cchLine The (encoded) length of the line, excluding EOL char.
620 */
621static bool isBlankLineSlow(const char *pchLine, size_t cchLine)
622{
623 /*
624 * From the end, more likely to hit a non-blank char there.
625 */
626 while (cchLine-- > 0)
627 if (!RT_C_IS_BLANK(pchLine[cchLine]))
628 return false;
629 return true;
630}
631
632/**
633 * Helper for checking whether a line is blank.
634 *
635 * @returns true if blank, false if not.
636 * @param pchLine Pointer to the start of the line.
637 * @param cchLine The (encoded) length of the line, excluding EOL char.
638 */
639DECLINLINE(bool) isBlankLine(const char *pchLine, size_t cchLine)
640{
641 if (cchLine == 0)
642 return true;
643 /*
644 * We're more likely to fine a non-space char at the end of the line than
645 * at the start, due to source code indentation.
646 */
647 if (pchLine[cchLine - 1])
648 return false;
649
650 /*
651 * Don't bother inlining loop code.
652 */
653 return isBlankLineSlow(pchLine, cchLine);
654}
655
656
657/**
658 * Checks if there are @a cch blanks at @a pch.
659 *
660 * @returns true if span of @a cch blanks, false if not.
661 * @param pch The start of the span to check.
662 * @param cch The length of the span.
663 */
664DECLINLINE(bool) isSpanOfBlanks(const char *pch, size_t cch)
665{
666 while (cch-- > 0)
667 {
668 char const ch = *pch++;
669 if (!RT_C_IS_BLANK(ch))
670 return false;
671 }
672 return true;
673}
674
675
676/**
677 * Strip trailing blanks (space & tab).
678 *
679 * @returns True if modified, false if not.
680 * @param pIn The input stream.
681 * @param pOut The output stream.
682 * @param pSettings The settings.
683 */
684bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
685{
686 if (!pSettings->fStripTrailingBlanks)
687 return false;
688
689 bool fModified = false;
690 SCMEOL enmEol;
691 size_t cchLine;
692 const char *pchLine;
693 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
694 {
695 int rc;
696 if ( cchLine == 0
697 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
698 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
699 else
700 {
701 cchLine--;
702 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
703 cchLine--;
704 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
705 fModified = true;
706 }
707 if (RT_FAILURE(rc))
708 return false;
709 }
710 if (fModified)
711 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
712 return fModified;
713}
714
715/**
716 * Expand tabs.
717 *
718 * @returns True if modified, false if not.
719 * @param pIn The input stream.
720 * @param pOut The output stream.
721 * @param pSettings The settings.
722 */
723bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
724{
725 if (!pSettings->fConvertTabs)
726 return false;
727
728 size_t const cchTab = pSettings->cchTab;
729 bool fModified = false;
730 SCMEOL enmEol;
731 size_t cchLine;
732 const char *pchLine;
733 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
734 {
735 int rc;
736 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
737 if (!pchTab)
738 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
739 else
740 {
741 size_t offTab = 0;
742 const char *pchChunk = pchLine;
743 for (;;)
744 {
745 size_t cchChunk = pchTab - pchChunk;
746 offTab += cchChunk;
747 ScmStreamWrite(pOut, pchChunk, cchChunk);
748
749 size_t cchToTab = cchTab - offTab % cchTab;
750 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
751 offTab += cchToTab;
752
753 pchChunk = pchTab + 1;
754 size_t cchLeft = cchLine - (pchChunk - pchLine);
755 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
756 if (!pchTab)
757 {
758 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
759 break;
760 }
761 }
762
763 fModified = true;
764 }
765 if (RT_FAILURE(rc))
766 return false;
767 }
768 if (fModified)
769 ScmVerbose(pState, 2, " * Expanded tabs\n");
770 return fModified;
771}
772
773/**
774 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
775 *
776 * @returns true if modifications were made, false if not.
777 * @param pIn The input stream.
778 * @param pOut The output stream.
779 * @param pSettings The settings.
780 * @param enmDesiredEol The desired end of line indicator type.
781 * @param pszDesiredSvnEol The desired svn:eol-style.
782 */
783static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
784 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
785{
786 if (!pSettings->fConvertEol)
787 return false;
788
789 bool fModified = false;
790 SCMEOL enmEol;
791 size_t cchLine;
792 const char *pchLine;
793 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
794 {
795 if ( enmEol != enmDesiredEol
796 && enmEol != SCMEOL_NONE)
797 {
798 fModified = true;
799 enmEol = enmDesiredEol;
800 }
801 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
802 if (RT_FAILURE(rc))
803 return false;
804 }
805 if (fModified)
806 ScmVerbose(pState, 2, " * Converted EOL markers\n");
807
808 /* Check svn:eol-style if appropriate */
809 if ( pSettings->fSetSvnEol
810 && ScmSvnIsInWorkingCopy(pState))
811 {
812 char *pszEol;
813 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
814 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
815 || rc == VERR_NOT_FOUND)
816 {
817 if (rc == VERR_NOT_FOUND)
818 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
819 else
820 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
821 int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
822 if (RT_FAILURE(rc2))
823 ScmError(pState, rc2, "ScmSvnSetProperty: %Rrc\n", rc2);
824 }
825 if (RT_SUCCESS(rc))
826 RTStrFree(pszEol);
827 }
828
829 /** @todo also check the subversion svn:eol-style state! */
830 return fModified;
831}
832
833/**
834 * Force native end of line indicator.
835 *
836 * @returns true if modifications were made, false if not.
837 * @param pIn The input stream.
838 * @param pOut The output stream.
839 * @param pSettings The settings.
840 */
841bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
842{
843#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
844 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
845#else
846 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");
847#endif
848}
849
850/**
851 * Force the stream to use LF as the end of line indicator.
852 *
853 * @returns true if modifications were made, false if not.
854 * @param pIn The input stream.
855 * @param pOut The output stream.
856 * @param pSettings The settings.
857 */
858bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
859{
860 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
861}
862
863/**
864 * Force the stream to use CRLF as the end of line indicator.
865 *
866 * @returns true if modifications were made, false if not.
867 * @param pIn The input stream.
868 * @param pOut The output stream.
869 * @param pSettings The settings.
870 */
871bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
872{
873 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
874}
875
876/**
877 * Strip trailing blank lines and/or make sure there is exactly one blank line
878 * at the end of the file.
879 *
880 * @returns true if modifications were made, false if not.
881 * @param pIn The input stream.
882 * @param pOut The output stream.
883 * @param pSettings The settings.
884 *
885 * @remarks ASSUMES trailing white space has been removed already.
886 */
887bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
888{
889 if ( !pSettings->fStripTrailingLines
890 && !pSettings->fForceTrailingLine
891 && !pSettings->fForceFinalEol)
892 return false;
893
894 size_t const cLines = ScmStreamCountLines(pIn);
895
896 /* Empty files remains empty. */
897 if (cLines <= 1)
898 return false;
899
900 /* Figure out if we need to adjust the number of lines or not. */
901 size_t cLinesNew = cLines;
902
903 if ( pSettings->fStripTrailingLines
904 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
905 {
906 while ( cLinesNew > 1
907 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
908 cLinesNew--;
909 }
910
911 if ( pSettings->fForceTrailingLine
912 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
913 cLinesNew++;
914
915 bool fFixMissingEol = pSettings->fForceFinalEol
916 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
917
918 if ( !fFixMissingEol
919 && cLines == cLinesNew)
920 return false;
921
922 /* Copy the number of lines we've arrived at. */
923 ScmStreamRewindForReading(pIn);
924
925 size_t cCopied = RT_MIN(cLinesNew, cLines);
926 ScmStreamCopyLines(pOut, pIn, cCopied);
927
928 if (cCopied != cLinesNew)
929 {
930 while (cCopied++ < cLinesNew)
931 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
932 }
933 /* Fix missing EOL if required. */
934 else if (fFixMissingEol)
935 {
936 if (ScmStreamGetEol(pIn) == SCMEOL_LF)
937 ScmStreamWrite(pOut, "\n", 1);
938 else
939 ScmStreamWrite(pOut, "\r\n", 2);
940 }
941
942 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
943 return true;
944}
945
946/**
947 * Make sure there is no svn:executable property on the current file.
948 *
949 * @returns false - the state carries these kinds of changes.
950 * @param pState The rewriter state.
951 * @param pIn The input stream.
952 * @param pOut The output stream.
953 * @param pSettings The settings.
954 */
955bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
956{
957 RT_NOREF2(pIn, pOut);
958 if ( !pSettings->fSetSvnExecutable
959 || !ScmSvnIsInWorkingCopy(pState))
960 return false;
961
962 int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
963 if (RT_SUCCESS(rc))
964 {
965 ScmVerbose(pState, 2, " * removing svn:executable\n");
966 rc = ScmSvnDelProperty(pState, "svn:executable");
967 if (RT_FAILURE(rc))
968 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
969 }
970 return false;
971}
972
973/**
974 * Make sure there is no svn:keywords property on the current file.
975 *
976 * @returns false - the state carries these kinds of changes.
977 * @param pState The rewriter state.
978 * @param pIn The input stream.
979 * @param pOut The output stream.
980 * @param pSettings The settings.
981 */
982bool rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
983{
984 RT_NOREF2(pIn, pOut);
985 if ( !pSettings->fSetSvnExecutable
986 || !ScmSvnIsInWorkingCopy(pState))
987 return false;
988
989 int rc = ScmSvnQueryProperty(pState, "svn:keywords", NULL);
990 if (RT_SUCCESS(rc))
991 {
992 ScmVerbose(pState, 2, " * removing svn:keywords\n");
993 rc = ScmSvnDelProperty(pState, "svn:keywords");
994 if (RT_FAILURE(rc))
995 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
996 }
997 return false;
998}
999
1000/**
1001 * Make sure there is no svn:eol-style property on the current file.
1002 *
1003 * @returns false - the state carries these kinds of changes.
1004 * @param pState The rewriter state.
1005 * @param pIn The input stream.
1006 * @param pOut The output stream.
1007 * @param pSettings The settings.
1008 */
1009bool rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1010{
1011 RT_NOREF2(pIn, pOut);
1012 if ( !pSettings->fSetSvnExecutable
1013 || !ScmSvnIsInWorkingCopy(pState))
1014 return false;
1015
1016 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", NULL);
1017 if (RT_SUCCESS(rc))
1018 {
1019 ScmVerbose(pState, 2, " * removing svn:eol-style\n");
1020 rc = ScmSvnDelProperty(pState, "svn:eol-style");
1021 if (RT_FAILURE(rc))
1022 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
1023 }
1024 return false;
1025}
1026
1027/**
1028 * Makes sure the svn properties are appropriate for a binary.
1029 *
1030 * @returns false - the state carries these kinds of changes.
1031 * @param pState The rewriter state.
1032 * @param pIn The input stream.
1033 * @param pOut The output stream.
1034 * @param pSettings The settings.
1035 */
1036bool rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1037{
1038 RT_NOREF2(pIn, pOut);
1039 if ( !pSettings->fSetSvnExecutable
1040 || !ScmSvnIsInWorkingCopy(pState))
1041 return false;
1042
1043 /* remove svn:eol-style and svn:keywords */
1044 static const char * const s_apszRemove[] = { "svn:eol-style", "svn:keywords" };
1045 for (uint32_t i = 0; i < RT_ELEMENTS(s_apszRemove); i++)
1046 {
1047 char *pszValue;
1048 int rc = ScmSvnQueryProperty(pState, s_apszRemove[i], &pszValue);
1049 if (RT_SUCCESS(rc))
1050 {
1051 ScmVerbose(pState, 2, " * removing %s=%s\n", s_apszRemove[i], pszValue);
1052 RTStrFree(pszValue);
1053 rc = ScmSvnDelProperty(pState, s_apszRemove[i]);
1054 if (RT_FAILURE(rc))
1055 ScmError(pState, rc, "ScmSvnSetProperty(,%s): %Rrc\n", s_apszRemove[i], rc);
1056 }
1057 else if (rc != VERR_NOT_FOUND)
1058 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
1059 }
1060
1061 /* Make sure there is a svn:mime-type set. */
1062 int rc = ScmSvnQueryProperty(pState, "svn:mime-type", NULL);
1063 if (rc == VERR_NOT_FOUND)
1064 {
1065 ScmVerbose(pState, 2, " * settings svn:mime-type\n");
1066 rc = ScmSvnSetProperty(pState, "svn:mime-type", "application/octet-stream");
1067 if (RT_FAILURE(rc))
1068 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
1069 }
1070 else if (RT_FAILURE(rc))
1071 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
1072
1073 return false;
1074}
1075
1076/**
1077 * Make sure the Id and Revision keywords are expanded.
1078 *
1079 * @returns false - the state carries these kinds of changes.
1080 * @param pState The rewriter state.
1081 * @param pIn The input stream.
1082 * @param pOut The output stream.
1083 * @param pSettings The settings.
1084 */
1085bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1086{
1087 RT_NOREF2(pIn, pOut);
1088 if ( !pSettings->fSetSvnKeywords
1089 || !ScmSvnIsInWorkingCopy(pState))
1090 return false;
1091
1092 char *pszKeywords;
1093 int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
1094 if ( RT_SUCCESS(rc)
1095 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */
1096 || !strstr(pszKeywords, "Revision")) )
1097 {
1098 if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
1099 rc = RTStrAAppend(&pszKeywords, " Id Revision");
1100 else if (!strstr(pszKeywords, "Id"))
1101 rc = RTStrAAppend(&pszKeywords, " Id");
1102 else
1103 rc = RTStrAAppend(&pszKeywords, " Revision");
1104 if (RT_SUCCESS(rc))
1105 {
1106 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
1107 rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
1108 if (RT_FAILURE(rc))
1109 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
1110 }
1111 else
1112 ScmError(pState, rc, "RTStrAppend: %Rrc\n", rc);
1113 RTStrFree(pszKeywords);
1114 }
1115 else if (rc == VERR_NOT_FOUND)
1116 {
1117 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
1118 rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
1119 if (RT_FAILURE(rc))
1120 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
1121 }
1122 else if (RT_SUCCESS(rc))
1123 RTStrFree(pszKeywords);
1124
1125 return false;
1126}
1127
1128/**
1129 * Checks the svn:sync-process value and that parent is exported too.
1130 *
1131 * @returns false - the state carries these kinds of changes.
1132 * @param pState The rewriter state.
1133 * @param pIn The input stream.
1134 * @param pOut The output stream.
1135 * @param pSettings The settings.
1136 */
1137bool rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1138{
1139 RT_NOREF2(pIn, pOut);
1140 if ( pSettings->fSkipSvnSyncProcess
1141 || !ScmSvnIsInWorkingCopy(pState))
1142 return false;
1143
1144 char *pszSyncProcess;
1145 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1146 if (RT_SUCCESS(rc))
1147 {
1148 if (strcmp(pszSyncProcess, "export") == 0)
1149 {
1150 char *pszParentSyncProcess;
1151 rc = ScmSvnQueryParentProperty(pState, "svn:sync-process", &pszParentSyncProcess);
1152 if (RT_SUCCESS(rc))
1153 {
1154 if (strcmp(pszSyncProcess, "export") != 0)
1155 ScmError(pState, VERR_INVALID_STATE,
1156 "svn:sync-process=export, but parent directory differs: %s\n"
1157 "WARNING! Make sure to unexport everything inside the directory first!\n"
1158 " Then you may export the directory and stuff inside it if you want.\n"
1159 " (Just exporting the directory will not make anything inside it externally visible.)\n"
1160 , pszParentSyncProcess);
1161 RTStrFree(pszParentSyncProcess);
1162 }
1163 else if (rc == VERR_NOT_FOUND)
1164 ScmError(pState, VERR_NOT_FOUND,
1165 "svn:sync-process=export, but parent directory is not exported!\n"
1166 "WARNING! Make sure to unexport everything inside the directory first!\n"
1167 " Then you may export the directory and stuff inside it if you want.\n"
1168 " (Just exporting the directory will not make anything inside it externally visible.)\n");
1169 else
1170 ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc);
1171 }
1172 else if (strcmp(pszSyncProcess, "ignore") != 0)
1173 ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess);
1174 RTStrFree(pszSyncProcess);
1175 }
1176 else if (rc != VERR_NOT_FOUND)
1177 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
1178
1179 return false;
1180}
1181
1182/**
1183 * Checks the that there is no bidirectional unicode fun in the file.
1184 *
1185 * @returns false - the state carries these kinds of changes.
1186 * @param pState The rewriter state.
1187 * @param pIn The input stream.
1188 * @param pOut The output stream.
1189 * @param pSettings The settings.
1190 */
1191bool rewrite_UnicodeChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1192{
1193 RT_NOREF2(pIn, pOut);
1194 if (pSettings->fSkipUnicodeChecks)
1195 return false;
1196
1197 /*
1198 * Just scan the input for weird stuff and fail if we find anything we don't like.
1199 */
1200 uint32_t iLine = 0;
1201 SCMEOL enmEol;
1202 size_t cchLine;
1203 const char *pchLine;
1204 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1205 {
1206 iLine++;
1207 const char *pchCur = pchLine;
1208 size_t cchLeft = cchLine;
1209 while (cchLeft > 0)
1210 {
1211 RTUNICP uc = 0;
1212 int rc = RTStrGetCpNEx(&pchCur, &cchLeft, &uc);
1213 if (RT_SUCCESS(rc))
1214 {
1215 const char *pszWhat;
1216 switch (uc)
1217 {
1218 default:
1219 continue;
1220
1221 /* Potentially evil bi-directional control codes (Table I, trojan-source.pdf): */
1222 case 0x202a: pszWhat = "LRE - left-to-right embedding"; break;
1223 case 0x202b: pszWhat = "RLE - right-to-left embedding"; break;
1224 case 0x202d: pszWhat = "LRO - left-to-right override"; break;
1225 case 0x202e: pszWhat = "RLO - right-to-left override"; break;
1226 case 0x2066: pszWhat = "LRI - left-to-right isolate"; break;
1227 case 0x2067: pszWhat = "RLI - right-to-left isolate"; break;
1228 case 0x2068: pszWhat = "FSI - first strong isolate"; break;
1229 case 0x202c: pszWhat = "PDF - pop directional formatting (LRE, RLE, LRO, RLO)"; break;
1230 case 0x2069: pszWhat = "PDI - pop directional isolate (LRI, RLI)"; break;
1231
1232 /** @todo add checks for homoglyphs too. */
1233 }
1234 ScmFixManually(pState, "%u:%zu: Evil unicode codepoint: %s\n", iLine, pchCur - pchLine, pszWhat);
1235 }
1236 else
1237 ScmFixManually(pState, "%u:%zu: Invalid UTF-8 encoding: %Rrc\n", iLine, pchCur - pchLine, rc);
1238 }
1239 }
1240
1241 return false;
1242}
1243
1244
1245
1246/*********************************************************************************************************************************
1247* Copyright & License *
1248*********************************************************************************************************************************/
1249
1250/**
1251 * Compares two strings word-by-word, ignoring spaces, punctuation and case.
1252 *
1253 * Assumes ASCII strings.
1254 *
1255 * @returns true if they match, false if not.
1256 * @param psz1 The first string. This is typically the known one.
1257 * @param psz2 The second string. This is typically the unknown one,
1258 * which is why we return a next pointer for this one.
1259 * @param ppsz2Next Where to return the next part of the 2nd string. If
1260 * this is NULL, the whole string must match.
1261 */
1262static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next)
1263{
1264 for (;;)
1265 {
1266 /* Try compare raw strings first. */
1267 char ch1 = *psz1;
1268 char ch2 = *psz2;
1269 if ( ch1 == ch2
1270 || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
1271 {
1272 if (ch1)
1273 {
1274 psz1++;
1275 psz2++;
1276 }
1277 else
1278 {
1279 if (ppsz2Next)
1280 *ppsz2Next = psz2;
1281 return true;
1282 }
1283 }
1284 else
1285 {
1286 /* Try skip spaces an punctuation. */
1287 while ( RT_C_IS_SPACE(ch1)
1288 || RT_C_IS_PUNCT(ch1))
1289 ch1 = *++psz1;
1290
1291 if (ch1 == '\0' && ppsz2Next)
1292 {
1293 *ppsz2Next = psz2;
1294 return true;
1295 }
1296
1297 while ( RT_C_IS_SPACE(ch2)
1298 || RT_C_IS_PUNCT(ch2))
1299 ch2 = *++psz2;
1300
1301 if ( ch1 != ch2
1302 && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
1303 {
1304 if (ppsz2Next)
1305 *ppsz2Next = psz2;
1306 return false;
1307 }
1308 }
1309 }
1310}
1311
1312/**
1313 * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
1314 * and case.
1315 *
1316 * @returns true if found, false if not.
1317 * @param pszText The haystack to search in.
1318 * @param cchText The length @a pszText.
1319 * @param pszFragment The needle to search for.
1320 * @param ppszStart Where to return the address in @a pszText where
1321 * the fragment was found. Optional.
1322 * @param ppszNext Where to return the pointer to the first char in
1323 * @a pszText after the fragment. Optional.
1324 *
1325 * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
1326 * This character must not be space or punctuation.
1327 */
1328static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment,
1329 const char **ppszStart, const char **ppszNext)
1330{
1331 Assert(!((unsigned)*pszFragment & 0x80));
1332 Assert(pszText[cchText] == '\0');
1333 Assert(!RT_C_IS_BLANK(*pszFragment));
1334 Assert(!RT_C_IS_PUNCT(*pszFragment));
1335
1336 char chLower = RT_C_TO_LOWER(*pszFragment);
1337 char chUpper = RT_C_TO_UPPER(*pszFragment);
1338 for (;;)
1339 {
1340 const char *pszHit = (const char *)memchr(pszText, chLower, cchText);
1341 const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText);
1342 if (!pszHit && !pszHit2)
1343 {
1344 if (ppszStart)
1345 *ppszStart = NULL;
1346 if (ppszNext)
1347 *ppszNext = NULL;
1348 return false;
1349 }
1350
1351 if ( pszHit == NULL
1352 || ( pszHit2 != NULL
1353 && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
1354 pszHit = pszHit2;
1355
1356 const char *pszNext;
1357 if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
1358 {
1359 if (ppszStart)
1360 *ppszStart = pszHit;
1361 if (ppszNext)
1362 *ppszNext = pszNext;
1363 return true;
1364 }
1365
1366 cchText -= pszHit - pszText + 1;
1367 pszText = pszHit + 1;
1368 }
1369}
1370
1371
1372/**
1373 * Counts the number of lines in the given substring.
1374 *
1375 * @returns The number of lines.
1376 * @param psz The start of the substring.
1377 * @param cch The length of the substring.
1378 */
1379static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
1380{
1381 uint32_t cLines = 0;
1382 for (;;)
1383 {
1384 const char *pszEol = (const char *)memchr(psz, '\n', cch);
1385 if (pszEol)
1386 cLines++;
1387 else
1388 return cLines + (*psz != '\0');
1389 cch -= pszEol + 1 - psz;
1390 if (!cch)
1391 return cLines;
1392 psz = pszEol + 1;
1393 }
1394}
1395
1396
1397/**
1398 * Comment parser callback for locating copyright and license.
1399 */
1400static DECLCALLBACK(int)
1401rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
1402{
1403 PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
1404 Assert(strlen(pszBody) == cchBody);
1405 //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
1406 ScmVerbose(pState->pState, 5,
1407 "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
1408 pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
1409 pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
1410
1411 pState->cComments++;
1412
1413 uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
1414
1415 /*
1416 * Look for a 'contributed by' or 'includes contributions from' line, these
1417 * comes first when present.
1418 */
1419 const char *pchContributedBy = NULL;
1420 size_t cchContributedBy = 0;
1421 size_t cBlankLinesAfterContributedBy = 0;
1422 if ( pState->pszContributedBy == NULL
1423 && ( pState->iLineCopyright == UINT32_MAX
1424 || pState->iLineLicense == UINT32_MAX)
1425 && ( ( cchBody > sizeof("Contributed by")
1426 && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
1427 || ( cchBody > sizeof("Includes contributions from")
1428 && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
1429 {
1430 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1431 while (pszNextLine && pszNextLine[1] != '\n')
1432 pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
1433 if (pszNextLine)
1434 {
1435 pchContributedBy = pszBody;
1436 cchContributedBy = pszNextLine - pszBody;
1437
1438 /* Skip the copyright line and any blank lines following it. */
1439 cchBody -= cchContributedBy + 1;
1440 pszBody = pszNextLine + 1;
1441 iLine += 1;
1442 while (*pszBody == '\n')
1443 {
1444 pszBody++;
1445 cchBody--;
1446 iLine++;
1447 cBlankLinesAfterContributedBy++;
1448 }
1449 }
1450 }
1451
1452 /*
1453 * Look for the copyright line.
1454 */
1455 bool fFoundCopyright = false;
1456 uint32_t cBlankLinesAfterCopyright = 0;
1457 if ( pState->iLineCopyright == UINT32_MAX
1458 && cchBody > sizeof("Copyright") + RT_MIN(sizeof(g_szCopyrightHolder), sizeof(g_szOldCopyrightHolder))
1459 && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
1460 {
1461 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1462
1463 /* Oracle copyright? */
1464 const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
1465 while (RT_C_IS_SPACE(pszEnd[-1]))
1466 pszEnd--;
1467 if ( ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
1468 && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
1469 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
1470 || ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szOldCopyrightHolder)
1471 && (*(unsigned char *)(pszEnd - sizeof(g_szOldCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
1472 && RTStrNICmp(pszEnd - sizeof(g_szOldCopyrightHolder) + 1, RT_STR_TUPLE(g_szOldCopyrightHolder)) == 0) )
1473 {
1474 /* Parse out the year(s). */
1475 const char *psz = pszBody + sizeof("copyright");
1476 while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
1477 psz++;
1478 if (RT_C_IS_DIGIT(*psz))
1479 {
1480 char *pszNext;
1481 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
1482 if ( RT_SUCCESS(rc)
1483 && rc != VWRN_NUMBER_TOO_BIG
1484 && rc != VWRN_NEGATIVE_UNSIGNED)
1485 {
1486 if ( pState->uFirstYear < 1975
1487 || pState->uFirstYear > 3000)
1488 {
1489 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
1490 RTStrPurgeEncoding(pszCopy);
1491 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%s')\n",
1492 pState->uFirstYear, pszCopy);
1493 RTStrFree(pszCopy);
1494 pState->uFirstYear = UINT32_MAX;
1495 }
1496
1497 while (RT_C_IS_SPACE(*pszNext))
1498 pszNext++;
1499 if (*pszNext == '-')
1500 {
1501 do
1502 pszNext++;
1503 while (RT_C_IS_SPACE(*pszNext));
1504 rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
1505 if ( RT_SUCCESS(rc)
1506 && rc != VWRN_NUMBER_TOO_BIG
1507 && rc != VWRN_NEGATIVE_UNSIGNED)
1508 {
1509 if ( pState->uLastYear < 1975
1510 || pState->uLastYear > 3000)
1511 {
1512 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
1513 RTStrPurgeEncoding(pszCopy);
1514 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%s')\n",
1515 pState->uLastYear, pszCopy);
1516 RTStrFree(pszCopy);
1517 pState->uLastYear = UINT32_MAX;
1518 }
1519 else if (pState->uFirstYear > pState->uLastYear)
1520 {
1521 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
1522 RTStrPurgeEncoding(pszCopy);
1523 RTMsgWarning("Copyright years switched(?): '%s'\n", pszCopy);
1524 RTStrFree(pszCopy);
1525 uint32_t iTmp = pState->uLastYear;
1526 pState->uLastYear = pState->uFirstYear;
1527 pState->uFirstYear = iTmp;
1528 }
1529 }
1530 else
1531 {
1532 pState->uLastYear = UINT32_MAX;
1533 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
1534 RTStrPurgeEncoding(pszCopy);
1535 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1536 "Failed to parse second copyright year: '%s'\n", pszCopy);
1537 RTMemFree(pszCopy);
1538 }
1539 }
1540 else if (*pszNext != g_szCopyrightHolder[0])
1541 {
1542 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
1543 RTStrPurgeEncoding(pszCopy);
1544 ScmError(pState->pState, VERR_PARSE_ERROR,
1545 "Failed to parse copyright: '%s'\n", pszCopy);
1546 RTMemFree(pszCopy);
1547 } else
1548 pState->uLastYear = pState->uFirstYear;
1549 }
1550 else
1551 {
1552 pState->uFirstYear = UINT32_MAX;
1553 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
1554 RTStrPurgeEncoding(pszCopy);
1555 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1556 "Failed to parse copyright year: '%s'\n", pszCopy);
1557 RTMemFree(pszCopy);
1558 }
1559 }
1560
1561 /* The copyright comment must come before the license. */
1562 if (pState->iLineLicense != UINT32_MAX)
1563 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
1564 iLine, pState->iLineLicense);
1565
1566 /* In C/C++ code, this must be a multiline comment. While in python it
1567 must be a */
1568 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1569 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
1570 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1571 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
1572
1573 /* The copyright must be followed by the license. */
1574 if (!pszNextLine)
1575 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1576
1577 /* Quit if we've flagged a failure. */
1578 if (RT_FAILURE(pState->pState->rc))
1579 return VERR_CALLBACK_RETURN;
1580
1581 /* Check if it's well formed and up to date. */
1582 char szWellFormed[256];
1583 size_t cchWellFormed;
1584 if (pState->uFirstYear == pState->uLastYear)
1585 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
1586 pState->uFirstYear, g_szCopyrightHolder);
1587 else
1588 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
1589 pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
1590 pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
1591 pState->iLineCopyright = iLine;
1592 pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
1593 && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
1594 if (!pState->fWellFormedCopyright)
1595 ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
1596
1597 /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
1598 if (pInfo->cBlankLinesBefore != 1)
1599 {
1600 ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
1601 pInfo->cBlankLinesBefore);
1602 pState->fWellFormedCopyright = false;
1603 }
1604
1605 /* If the comment doesn't start in column 1, trigger rewrite. */
1606 if (pInfo->offStart != 0)
1607 {
1608 ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
1609 pState->fWellFormedCopyright = false;
1610 /** @todo check that there isn't any code preceeding the comment. */
1611 }
1612
1613 if (pchContributedBy)
1614 {
1615 pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
1616 if (cBlankLinesAfterContributedBy != 1)
1617 {
1618 ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
1619 cBlankLinesAfterContributedBy);
1620 pState->fWellFormedCopyright = false;
1621 }
1622 }
1623
1624 fFoundCopyright = true;
1625 ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
1626 pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
1627 }
1628 else
1629 {
1630 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
1631 RTStrPurgeEncoding(pszCopy);
1632 ScmVerbose(pState->pState, 3, "not oracle copyright: '%s'\n", pszCopy);
1633 RTStrFree(pszCopy);
1634 }
1635
1636 if (!pszNextLine)
1637 return VINF_SUCCESS;
1638
1639 /* Skip the copyright line and any blank lines following it. */
1640 cchBody -= pszNextLine - pszBody + 1;
1641 pszBody = pszNextLine + 1;
1642 iLine += 1;
1643 while (*pszBody == '\n')
1644 {
1645 pszBody++;
1646 cchBody--;
1647 iLine++;
1648 cBlankLinesAfterCopyright++;
1649 }
1650
1651 /*
1652 * If we have a based-on-mit scenario, check for the lead in now and
1653 * complain if not found.
1654 */
1655 if ( fFoundCopyright
1656 && pState->enmLicenceOpt == kScmLicense_BasedOnMit
1657 && pState->iLineLicense == UINT32_MAX)
1658 {
1659 if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
1660 {
1661 /* Take down a comment area which goes up to 'this file is based on'.
1662 The license line and length isn't used but gets set to cover the current line. */
1663 pState->iLineComment = pInfo->iLineStart;
1664 pState->cLinesComment = iLine - pInfo->iLineStart;
1665 pState->iLineLicense = iLine;
1666 pState->cLinesLicense = 1;
1667 pState->fExternalLicense = true;
1668 pState->fIsCorrectLicense = true;
1669 pState->fWellFormedLicense = true;
1670
1671 /* Check if we've got a MIT a license here or not. */
1672 pState->pCurrentLicense = NULL;
1673 do
1674 {
1675 const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
1676 if (!pszEol || pszEol[1] == '\0')
1677 {
1678 pszBody += cchBody;
1679 cchBody = 0;
1680 break;
1681 }
1682 cchBody -= pszEol - pszBody + 1;
1683 pszBody = pszEol + 1;
1684 iLine++;
1685
1686 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1687 {
1688 const char *pszNext;
1689 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1690 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1691 {
1692 pState->pCurrentLicense = pCur;
1693 break;
1694 }
1695 }
1696 } while (!pState->pCurrentLicense);
1697 if (!pState->pCurrentLicense)
1698 ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
1699 else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
1700 ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
1701 pState->pCurrentLicense->psz);
1702 }
1703 else
1704 ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
1705 return VINF_SUCCESS;
1706 }
1707 }
1708
1709 /*
1710 * Look for LGPL like text in the comment.
1711 */
1712 if (pState->fCheckforLgpl && cchBody > 128)
1713 {
1714 /* We look for typical LGPL notices. */
1715 if (pState->iLineLgplNotice == UINT32_MAX)
1716 {
1717 static const char * const s_apszFragments[] =
1718 {
1719 "under the terms of the GNU Lesser General Public License",
1720 };
1721 for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
1722 if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
1723 {
1724 pState->iLineLgplNotice = iLine;
1725 pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
1726 ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
1727 break;
1728 }
1729 }
1730
1731 if ( pState->iLineLgplDisclaimer == UINT32_MAX
1732 && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
1733 {
1734 pState->iLineLgplDisclaimer = iLine;
1735 ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
1736 }
1737 }
1738
1739 /*
1740 * Look for the license text.
1741 */
1742 if (pState->iLineLicense == UINT32_MAX)
1743 {
1744 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1745 {
1746 const char *pszNext;
1747 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1748 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1749 {
1750 while ( RT_C_IS_SPACE(*pszNext)
1751 || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
1752 pszNext++;
1753
1754 uint32_t cDashes = 0;
1755 while (*pszNext == '-')
1756 cDashes++, pszNext++;
1757 bool fExternal = cDashes > 10;
1758
1759 if ( *pszNext == '\0'
1760 || fExternal)
1761 {
1762 /* In C/C++ code, this must be a multiline comment. While in python it
1763 must be a doc-string. */
1764 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1765 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
1766 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1767 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
1768
1769 /* Quit if we've flagged a failure. */
1770 if (RT_FAILURE(pState->pState->rc))
1771 return VERR_CALLBACK_RETURN;
1772
1773 /* Record it. */
1774 pState->iLineLicense = iLine;
1775 pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
1776 pState->pCurrentLicense = pCur;
1777 pState->fExternalLicense = fExternal;
1778 pState->fIsCorrectLicense = pCur == pState->pExpectedLicense;
1779 pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
1780 if (!pState->fWellFormedLicense)
1781 ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
1782
1783 /* If there was more than one blank line between the copyright and the
1784 license text, extend the license text area and force a rewrite of it. */
1785 if (cBlankLinesAfterCopyright > 1)
1786 {
1787 ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
1788 cBlankLinesAfterCopyright);
1789 pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
1790 pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
1791 pState->fWellFormedLicense = false;
1792 }
1793
1794 /* If there was more than one blank line after the license, trigger a rewrite. */
1795 if (!fExternal && pInfo->cBlankLinesAfter != 1)
1796 {
1797 ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
1798 pInfo->cBlankLinesAfter);
1799 pState->fWellFormedLicense = false;
1800 }
1801
1802 /** @todo Check that the last comment line doesn't have any code on it. */
1803 /** @todo Check that column 2 contains '*' for C/C++ files. */
1804
1805 ScmVerbose(pState->pState, 3,
1806 "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
1807 pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
1808 pState->fIsCorrectLicense, pState->fWellFormedLicense,
1809 pState->fExternalLicense, pState->fOpenSource);
1810
1811 if (fFoundCopyright)
1812 {
1813 pState->iLineComment = pInfo->iLineStart;
1814 pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
1815 - pInfo->iLineStart;
1816 }
1817 else
1818 ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
1819 break;
1820 }
1821 }
1822 }
1823 }
1824
1825 if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
1826 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1827
1828 /*
1829 * Stop looking for stuff after 100 comments.
1830 */
1831 if (pState->cComments > 100)
1832 return VERR_CALLBACK_RETURN;
1833 return VINF_SUCCESS;
1834}
1835
1836/**
1837 * Writes comment body text.
1838 *
1839 * @returns Stream status.
1840 * @param pOut The output stream.
1841 * @param pszText The text to write.
1842 * @param cchText The length of the text.
1843 * @param enmCommentStyle The comment style.
1844 * @param enmEol The EOL style.
1845 */
1846static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText,
1847 SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol)
1848{
1849 Assert(pszText[cchText - 1] == '\n');
1850 Assert(pszText[cchText - 2] != '\n');
1851 NOREF(cchText);
1852 do
1853 {
1854 const char *pszEol = strchr(pszText, '\n');
1855 if (pszEol != pszText)
1856 {
1857 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1858 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1859 ScmStreamWrite(pOut, pszText, pszEol - pszText);
1860 ScmStreamPutEol(pOut, enmEol);
1861 }
1862 else
1863 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1864 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1865 pszText = pszEol + 1;
1866 } while (*pszText != '\0');
1867 return ScmStreamGetStatus(pOut);
1868}
1869
1870
1871/**
1872 * Updates the copyright year and/or license text.
1873 *
1874 * @returns true if modifications were made, false if not.
1875 * @param pState The rewriter state.
1876 * @param pIn The input stream.
1877 * @param pOut The output stream.
1878 * @param pSettings The settings.
1879 * @param enmCommentStyle The comment style used by the file.
1880 */
1881static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1882 SCMCOMMENTSTYLE enmCommentStyle)
1883{
1884 if ( !pSettings->fUpdateCopyrightYear
1885 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1886 return false;
1887
1888 /*
1889 * Try locate the relevant comments.
1890 */
1891 SCMCOPYRIGHTINFO Info =
1892 {
1893 /*.pState = */ pState,
1894 /*.enmCommentStyle = */ enmCommentStyle,
1895
1896 /*.cComments = */ 0,
1897
1898 /*.pszContributedBy = */ NULL,
1899
1900 /*.iLineComment = */ UINT32_MAX,
1901 /*.cLinesComment = */ 0,
1902
1903 /*.iLineCopyright = */ UINT32_MAX,
1904 /*.uFirstYear = */ UINT32_MAX,
1905 /*.uLastYear = */ UINT32_MAX,
1906 /*.fWellFormedCopyright = */ false,
1907 /*.fUpToDateCopyright = */ false,
1908
1909 /*.fOpenSource = */ true,
1910 /*.pExpectedLicense = */ NULL,
1911 /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit
1912 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
1913 ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
1914 /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
1915 /*.iLineLicense = */ UINT32_MAX,
1916 /*.cLinesLicense = */ 0,
1917 /*.pCurrentLicense = */ NULL,
1918 /*.fIsCorrectLicense = */ false,
1919 /*.fWellFormedLicense = */ false,
1920 /*.fExternalLicense = */ false,
1921
1922 /*.fCheckForLgpl = */ true,
1923 /*.iLineLgplNotice = */ UINT32_MAX,
1924 /*.iLineAfterLgplComment = */ UINT32_MAX,
1925 /*.iLineLgplDisclaimer = */ UINT32_MAX,
1926 };
1927
1928 /* Figure Info.fOpenSource and the desired license: */
1929 char *pszSyncProcess;
1930 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1931 if (RT_SUCCESS(rc))
1932 {
1933 Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
1934 RTStrFree(pszSyncProcess);
1935 }
1936 else if (rc == VERR_NOT_FOUND)
1937 Info.fOpenSource = false;
1938 else
1939 return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
1940
1941 Info.pExpectedLicense = Info.paLicenses;
1942 if (Info.fOpenSource)
1943 {
1944 if ( pSettings->enmUpdateLicense != kScmLicense_Mit
1945 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1946 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
1947 Info.pExpectedLicense++;
1948 else
1949 Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
1950 }
1951 else
1952 while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
1953 Info.pExpectedLicense++;
1954
1955 /* Scan the comments. */
1956 rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
1957 if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
1958 && RT_SUCCESS(pState->rc))
1959 {
1960 /*
1961 * Do conformity checks.
1962 */
1963 bool fAddLgplDisclaimer = false;
1964 if (Info.fCheckforLgpl)
1965 {
1966 if ( Info.iLineLgplNotice != UINT32_MAX
1967 && Info.iLineLgplDisclaimer == UINT32_MAX)
1968 {
1969 if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
1970 ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
1971 Info.iLineLgplNotice + 1);
1972 else
1973 {
1974 ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
1975 fAddLgplDisclaimer = true;
1976 }
1977 }
1978 else if ( Info.iLineLgplNotice == UINT32_MAX
1979 && Info.iLineLgplDisclaimer != UINT32_MAX)
1980 ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
1981 Info.iLineLgplDisclaimer + 1);
1982 }
1983
1984 if (!pSettings->fExternalCopyright)
1985 {
1986 if (Info.iLineCopyright == UINT32_MAX)
1987 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
1988 if (Info.iLineLicense == UINT32_MAX)
1989 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
1990 }
1991 else if (Info.iLineCopyright != UINT32_MAX)
1992 ScmError(pState, VERR_NOT_FOUND,
1993 "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
1994 Info.iLineCopyright + 1);
1995
1996
1997 if (RT_SUCCESS(pState->rc))
1998 {
1999 /*
2000 * Do we need to make any changes?
2001 */
2002 bool fUpdateCopyright = !pSettings->fExternalCopyright
2003 && ( !Info.fWellFormedCopyright
2004 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
2005 bool fUpdateLicense = !pSettings->fExternalCopyright
2006 && Info.enmLicenceOpt != kScmLicense_LeaveAlone
2007 && ( !Info.fWellFormedLicense
2008 || !Info.fIsCorrectLicense);
2009 if ( fUpdateCopyright
2010 || fUpdateLicense
2011 || fAddLgplDisclaimer)
2012 {
2013 Assert(Info.iLineComment != UINT32_MAX);
2014 Assert(Info.cLinesComment > 0);
2015
2016 /*
2017 * Okay, do the work.
2018 */
2019 ScmStreamRewindForReading(pIn);
2020
2021 if (pSettings->fUpdateCopyrightYear)
2022 Info.uLastYear = g_uYear;
2023
2024 uint32_t iLine = 0;
2025 SCMEOL enmEol;
2026 size_t cchLine;
2027 const char *pchLine;
2028 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2029 {
2030 if ( iLine == Info.iLineComment
2031 && (fUpdateCopyright || fUpdateLicense) )
2032 {
2033 /* Leading blank line. */
2034 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
2035 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
2036
2037 /* Contributed by someone? */
2038 if (Info.pszContributedBy)
2039 {
2040 const char *psz = Info.pszContributedBy;
2041 for (;;)
2042 {
2043 const char *pszEol = strchr(psz, '\n');
2044 size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
2045 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
2046 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
2047 ScmStreamWrite(pOut, psz, cchContribLine);
2048 ScmStreamPutEol(pOut, enmEol);
2049 if (!pszEol)
2050 break;
2051 psz = pszEol + 1;
2052 }
2053
2054 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
2055 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
2056 }
2057
2058 /* Write the copyright comment line. */
2059 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
2060 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
2061
2062 char szCopyright[256];
2063 size_t cchCopyright;
2064 if (Info.uFirstYear == Info.uLastYear)
2065 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
2066 Info.uFirstYear, g_szCopyrightHolder);
2067 else
2068 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
2069 Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
2070
2071 ScmStreamWrite(pOut, szCopyright, cchCopyright);
2072 ScmStreamPutEol(pOut, enmEol);
2073
2074 if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
2075 {
2076 /* Blank line separating the two. */
2077 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
2078 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
2079
2080 /* Write the license text. */
2081 scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
2082 enmCommentStyle, enmEol);
2083
2084 /* Final comment line. */
2085 if (!Info.fExternalLicense)
2086 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
2087 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
2088 }
2089 else
2090 Assert(Info.fExternalLicense);
2091
2092 /* Skip the copyright and license text in the input file. */
2093 rc = ScmStreamGetStatus(pOut);
2094 if (RT_SUCCESS(rc))
2095 {
2096 iLine = Info.iLineComment + Info.cLinesComment;
2097 rc = ScmStreamSeekByLine(pIn, iLine);
2098 }
2099 }
2100 /*
2101 * Add LGPL disclaimer?
2102 */
2103 else if ( iLine == Info.iLineAfterLgplComment
2104 && fAddLgplDisclaimer)
2105 {
2106 ScmStreamPutEol(pOut, enmEol);
2107 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
2108 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
2109 scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
2110 enmCommentStyle, enmEol);
2111 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
2112 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
2113
2114 /* put the actual line */
2115 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2116 iLine++;
2117 }
2118 else
2119 {
2120 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2121 iLine++;
2122 }
2123 if (RT_FAILURE(rc))
2124 {
2125 RTStrFree(Info.pszContributedBy);
2126 return false;
2127 }
2128 } /* for each source line */
2129
2130 RTStrFree(Info.pszContributedBy);
2131 return true;
2132 }
2133 }
2134 }
2135 else
2136 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
2137 NOREF(pState); NOREF(pOut);
2138 RTStrFree(Info.pszContributedBy);
2139 return false;
2140}
2141
2142
2143/** Copyright updater for C-style comments. */
2144bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2145{
2146 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
2147}
2148
2149/** Copyright updater for hash-prefixed comments. */
2150bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2151{
2152 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
2153}
2154
2155/** Copyright updater for REM-prefixed comments. */
2156bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2157{
2158 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determineBatchFileCommentStyle(pIn));
2159}
2160
2161/** Copyright updater for python comments. */
2162bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2163{
2164 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
2165}
2166
2167/** Copyright updater for semicolon-prefixed comments. */
2168bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2169{
2170 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
2171}
2172
2173/** Copyright updater for sql comments. */
2174bool rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2175{
2176 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
2177}
2178
2179/** Copyright updater for tick-prefixed comments. */
2180bool rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2181{
2182 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
2183}
2184
2185/** Copyright updater for XML comments. */
2186bool rewrite_Copyright_XmlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2187{
2188 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Xml);
2189}
2190
2191
2192
2193/*********************************************************************************************************************************
2194* kBuild Makefiles *
2195*********************************************************************************************************************************/
2196
2197/**
2198 * Makefile.kup are empty files, enforce this.
2199 *
2200 * @returns true if modifications were made, false if not.
2201 * @param pIn The input stream.
2202 * @param pOut The output stream.
2203 * @param pSettings The settings.
2204 */
2205bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2206{
2207 RT_NOREF2(pOut, pSettings);
2208
2209 /* These files should be zero bytes. */
2210 if (pIn->cb == 0)
2211 return false;
2212 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2213 return true;
2214}
2215
2216typedef enum KMKTOKEN
2217{
2218 kKmkToken_Word = 0,
2219 kKmkToken_Comment,
2220
2221 /* Conditionals: */
2222 kKmkToken_ifeq,
2223 kKmkToken_ifneq,
2224 kKmkToken_if1of,
2225 kKmkToken_ifn1of,
2226 kKmkToken_ifdef,
2227 kKmkToken_ifndef,
2228 kKmkToken_if,
2229 kKmkToken_else,
2230 kKmkToken_endif,
2231
2232 /* Includes: */
2233 kKmkToken_include,
2234 kKmkToken_sinclude,
2235 kKmkToken_dash_include,
2236 kKmkToken_includedep,
2237 kKmkToken_includedep_queue,
2238 kKmkToken_includedep_flush,
2239
2240 /* Others: */
2241 kKmkToken_define,
2242 kKmkToken_endef,
2243 kKmkToken_export,
2244 kKmkToken_unexport,
2245 kKmkToken_local,
2246 kKmkToken_override,
2247 kKmkToken_undefine
2248} KMKTOKEN;
2249
2250typedef struct KMKPARSER
2251{
2252 struct
2253 {
2254 KMKTOKEN enmToken;
2255 uint32_t iLine;
2256 bool fIgnoreNesting;
2257 } aDepth[64];
2258 unsigned iDepth;
2259 unsigned iActualDepth;
2260 bool fInRecipe;
2261
2262 /** The current line number (for error messages and peeking). */
2263 uint32_t iLine;
2264 /** The EOL type of the current line. */
2265 SCMEOL enmEol;
2266 /** The length of the current line. */
2267 size_t cchLine;
2268 /** Pointer to the start of the current line. */
2269 char const *pchLine;
2270
2271 /** @name Only used for rule/assignment parsing.
2272 * @{ */
2273 /** Number of continuation lines at current rule/assignment. */
2274 uint32_t cLines;
2275 /** Characters in continuation lines at current rule/assignment. */
2276 size_t cchTotalLine;
2277 /** @} */
2278
2279 /** The SCM rewriter state. */
2280 PSCMRWSTATE pState;
2281 /** The input stream. */
2282 PSCMSTREAM pIn;
2283 /** The output stream. */
2284 PSCMSTREAM pOut;
2285 /** The settings. */
2286 PCSCMSETTINGSBASE pSettings;
2287 /** Scratch buffer. */
2288 char szBuf[4096];
2289} KMKPARSER;
2290
2291static KMKTOKEN scmKmkIdentifyToken(const char *pchWord, size_t cchWord)
2292{
2293 static struct { const char *psz; uint32_t cch; KMKTOKEN enmToken; } s_aTokens[] =
2294 {
2295 { RT_STR_TUPLE("if"), kKmkToken_if },
2296 { RT_STR_TUPLE("ifeq"), kKmkToken_ifeq },
2297 { RT_STR_TUPLE("ifneq"), kKmkToken_ifneq },
2298 { RT_STR_TUPLE("if1of"), kKmkToken_if1of },
2299 { RT_STR_TUPLE("ifn1of"), kKmkToken_ifn1of },
2300 { RT_STR_TUPLE("ifdef"), kKmkToken_ifdef },
2301 { RT_STR_TUPLE("ifndef"), kKmkToken_ifndef },
2302 { RT_STR_TUPLE("else"), kKmkToken_else },
2303 { RT_STR_TUPLE("endif"), kKmkToken_endif },
2304 { RT_STR_TUPLE("include"), kKmkToken_include },
2305 { RT_STR_TUPLE("sinclude"), kKmkToken_sinclude },
2306 { RT_STR_TUPLE("-include"), kKmkToken_dash_include },
2307 { RT_STR_TUPLE("includedep"), kKmkToken_includedep },
2308 { RT_STR_TUPLE("includedep-queue"), kKmkToken_includedep_queue },
2309 { RT_STR_TUPLE("includedep-flush"), kKmkToken_includedep_flush },
2310 { RT_STR_TUPLE("define"), kKmkToken_define },
2311 { RT_STR_TUPLE("endef"), kKmkToken_endef },
2312 { RT_STR_TUPLE("export"), kKmkToken_export },
2313 { RT_STR_TUPLE("unexport"), kKmkToken_unexport },
2314 { RT_STR_TUPLE("local"), kKmkToken_local },
2315 { RT_STR_TUPLE("override"), kKmkToken_override },
2316 { RT_STR_TUPLE("undefine"), kKmkToken_undefine },
2317 };
2318 char chFirst = *pchWord;
2319 if ( chFirst == 'i'
2320 || chFirst == 'e'
2321 || chFirst == 'd'
2322 || chFirst == 's'
2323 || chFirst == '-'
2324 || chFirst == 'u'
2325 || chFirst == 'l'
2326 || chFirst == 'o')
2327 {
2328 for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
2329 if ( s_aTokens[i].cch == cchWord
2330 && *s_aTokens[i].psz == chFirst
2331 && memcmp(s_aTokens[i].psz, pchWord, cchWord) == 0)
2332 return s_aTokens[i].enmToken;
2333 }
2334#ifdef VBOX_STRICT
2335 else
2336 for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
2337 Assert(chFirst != *s_aTokens[i].psz);
2338#endif
2339
2340 if (chFirst == '#')
2341 return kKmkToken_Comment;
2342 return kKmkToken_Word;
2343}
2344
2345
2346/**
2347 * Gives up on the current line, copying it as it and requesting manual repair.
2348 */
2349static bool scmKmkGiveUp(KMKPARSER *pParser, const char *pszFormat, ...)
2350{
2351 va_list va;
2352 va_start(va, pszFormat);
2353 ScmFixManually(pParser->pState, "%u: %N\n", pParser->iLine, pszFormat, &va);
2354 va_end(va);
2355
2356 ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
2357 return false;
2358}
2359
2360
2361static bool scmKmkIsLineWithContinuationSlow(const char *pchLine, size_t cchLine)
2362{
2363 size_t cchSlashes = 1;
2364 cchLine--;
2365 while (cchSlashes < cchLine && pchLine[cchLine - cchSlashes - 1] == '\\')
2366 cchSlashes++;
2367 return RT_BOOL(cchSlashes & 1);
2368}
2369
2370
2371DECLINLINE(bool) scmKmkIsLineWithContinuation(const char *pchLine, size_t cchLine)
2372{
2373 if (cchLine == 0 || pchLine[cchLine - 1] != '\\')
2374 return false;
2375 return scmKmkIsLineWithContinuationSlow(pchLine, cchLine);
2376}
2377
2378
2379/**
2380 * Finds the length of a line where line continuation is in play.
2381 *
2382 * @returns Length from start of current line to the final unescaped EOL.
2383 * @param pParser The KMK parser state.
2384 * @param pcLine Where to return the number of lines. Optional.
2385 * @param pcchMaxLeadWord Where to return the max lead word length on
2386 * subsequent lines. Used to help balance multi-line
2387 * 'if' statements (imperfect). Optional.
2388 */
2389static size_t scmKmkLineContinuationPeek(KMKPARSER *pParser, uint32_t *pcLines, size_t *pcchMaxLeadWord)
2390{
2391 size_t const offSaved = ScmStreamTell(pParser->pIn);
2392 uint32_t cLines = 1;
2393 size_t cchMaxLeadWord = 0;
2394 const char *pchLine = pParser->pchLine;
2395 size_t cchLine = pParser->cchLine;
2396 SCMEOL enmEol;
2397 for (;;)
2398 {
2399 /* Return if no line continuation (or end of stream): */
2400 if ( cchLine == 0
2401 || !scmKmkIsLineWithContinuation(pchLine, cchLine)
2402 || ScmStreamIsEndOfStream(pParser->pIn))
2403 {
2404 ScmStreamSeekAbsolute(pParser->pIn, offSaved);
2405 if (pcLines)
2406 *pcLines = cLines;
2407 if (pcchMaxLeadWord)
2408 *pcchMaxLeadWord = cchMaxLeadWord;
2409 return (size_t)(pchLine - pParser->pchLine) + cchLine;
2410 }
2411
2412 /* Get the next line: */
2413 pchLine = ScmStreamGetLine(pParser->pIn, &cchLine, &enmEol);
2414 cLines++;
2415
2416 /* Check the length of the first word if requested: */
2417 if (pcchMaxLeadWord)
2418 {
2419 size_t offLine = 0;
2420 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2421 offLine++;
2422
2423 size_t const offStartWord = offLine;
2424 while (offLine < cchLine && !RT_C_IS_BLANK(pchLine[offLine]))
2425 offLine++;
2426
2427 if (offLine - offStartWord > cchMaxLeadWord)
2428 cchMaxLeadWord = offLine - offStartWord;
2429 }
2430 }
2431}
2432
2433
2434static bool scmKmkPushNesting(KMKPARSER *pParser, KMKTOKEN enmToken)
2435{
2436 uint32_t iDepth = pParser->iDepth;
2437 if (iDepth + 1 >= RT_ELEMENTS(pParser->aDepth))
2438 return ScmError(pParser->pState, VERR_ASN1_TOO_DEEPLY_NESTED /*?*/, "%u: Too deep if/define nesting!\n", pParser->iLine);
2439
2440 pParser->aDepth[iDepth].enmToken = enmToken;
2441 pParser->aDepth[iDepth].iLine = pParser->iLine;
2442 pParser->aDepth[iDepth].fIgnoreNesting = false;
2443 pParser->iDepth = iDepth + 1;
2444 pParser->iActualDepth += 1;
2445 return true;
2446}
2447
2448
2449/**
2450 * Skips a string stopping at @a chStop1 or @a chStop2, taking $() and ${} into
2451 * account.
2452 */
2453static size_t scmKmkSkipExpString(const char *pchLine, size_t cchLine, size_t off, char chStop1, char chStop2 = '\0')
2454{
2455 unsigned iExpDepth = 0;
2456 char ch;
2457 while ( off < cchLine
2458 && (ch = pchLine[off])
2459 && ( (ch != chStop1 && ch != chStop2)
2460 || iExpDepth > 0))
2461 {
2462 off++;
2463 if (ch == '$')
2464 {
2465 ch = pchLine[off];
2466 if (ch == '(' || ch == '{')
2467 {
2468 iExpDepth++;
2469 off++;
2470 }
2471 }
2472 else if ((ch == ')' || ch == '}') && iExpDepth > 0)
2473 iExpDepth--;
2474 }
2475 return off;
2476}
2477
2478
2479/** Context for scmKmkWordLength. */
2480typedef enum
2481{
2482 /** Target file or assignment.
2483 * Separators: space, '=', ':' */
2484 kKmkWordCtx_TargetFileOrAssignment,
2485 /** Target file.
2486 * Separators: space, ':' */
2487 kKmkWordCtx_TargetFile,
2488 /** Dependency file or (target variable) assignment.
2489 * Separators: space, '=', ':', '|' */
2490 kKmkWordCtx_DepFileOrAssignment,
2491 /** Dependency file.
2492 * Separators: space, '|' */
2493 kKmkWordCtx_DepFile
2494} KMKWORDCTX;
2495
2496/**
2497 * Finds the length of the word (file) @a offStart.
2498 *
2499 * @returns Length of word starting at @a offStart. Zero if there is whitespace
2500 * at given offset or it's beyond the end of the line (both cases will
2501 * assert).
2502 * @param pchLine The line.
2503 * @param cchLine The line length.
2504 * @param offStart Offset to the start of the word.
2505 */
2506static size_t scmKmkWordLength(const char *pchLine, size_t cchLine, size_t offStart, KMKWORDCTX enmCtx)
2507{
2508 AssertReturn(offStart < cchLine && !RT_C_IS_BLANK(pchLine[offStart]), 0);
2509 size_t off = offStart;
2510 while (off < cchLine)
2511 {
2512 char ch = pchLine[off];
2513 if (RT_C_IS_BLANK(ch))
2514 break;
2515
2516 if (ch == ':')
2517 {
2518 /*
2519 * Check for plain driver letter, omitting the archive member variant.
2520 */
2521 if (off - offStart != 1 || !RT_C_IS_ALPHA(pchLine[off - 1]))
2522 {
2523 if (off == offStart)
2524 {
2525 /* We need to check for single and double colon rules as well as
2526 simple and immediate assignments here. */
2527 off++;
2528 if (pchLine[off] == ':')
2529 {
2530 off++;
2531 if (pchLine[off] == '=')
2532 {
2533 if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
2534 return 3; /* ::= - immediate assignment. */
2535 off++;
2536 }
2537 else if (enmCtx != kKmkWordCtx_DepFile)
2538 return 2; /* :: - double colon rule */
2539 }
2540 else if (pchLine[off] == '=')
2541 {
2542 if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
2543 return 2; /* := - simple assignment. */
2544 off++;
2545 }
2546 else if (enmCtx != kKmkWordCtx_DepFile)
2547 return 1; /* : - regular rule. */
2548 continue;
2549 }
2550 /* ':' is a separator except in DepFile context. */
2551 else if (enmCtx != kKmkWordCtx_DepFile)
2552 return off - offStart;
2553 }
2554 }
2555 else if (ch == '=')
2556 {
2557 /*
2558 * Assignment. We check for the previous character too so we'll catch
2559 * append, prepend and conditional assignments. Simple and immediate
2560 * assignments are handled above.
2561 */
2562 if ( enmCtx == kKmkWordCtx_TargetFileOrAssignment
2563 || enmCtx == kKmkWordCtx_DepFileOrAssignment)
2564 {
2565 if (off > offStart)
2566 {
2567 ch = pchLine[off - 1];
2568 if (ch == '?' || ch == '+' || ch == '>')
2569 off = off - 1 == offStart
2570 ? off + 2 /* return '+=', '?=', '<=' */
2571 : off - 1; /* up to '+=', '?=', '<=' */
2572 else
2573 Assert(ch != ':'); /* handled above */
2574 }
2575 else
2576 off++; /* '=' */
2577 return off - offStart;
2578 }
2579 }
2580 else if (ch == '|')
2581 {
2582 /*
2583 * This is rather straight forward.
2584 */
2585 if (enmCtx == kKmkWordCtx_DepFileOrAssignment && enmCtx == kKmkWordCtx_DepFile)
2586 {
2587 if (off == offStart)
2588 return 1;
2589 return off - offStart;
2590 }
2591 }
2592 off++;
2593 }
2594 return off - offStart;
2595}
2596
2597
2598static bool scmKmkTailComment(KMKPARSER *pParser, const char *pchLine, size_t cchLine, size_t offSrc, char **ppszDst)
2599{
2600 size_t const offSrcStart = offSrc;
2601
2602 /* Skip blanks. */
2603 while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
2604 offSrc++;
2605 if (offSrc >= cchLine)
2606 return true;
2607
2608 /* Is it a comment? */
2609 char *pszDst = *ppszDst;
2610 if (pchLine[offSrc] == '#')
2611 {
2612 /* Try preserve the start column number. */
2613/** @todo tabs */
2614 size_t const offDst = pszDst - pParser->szBuf;
2615 if (offDst < offSrc)
2616 {
2617 memset(pszDst, ' ', offSrc - offDst);
2618 pszDst += offSrc - offDst;
2619 }
2620 else if (offSrc != offSrcStart)
2621 *pszDst++ = ' ';
2622
2623 *ppszDst = pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchLine - offSrc);
2624 return cchLine - offSrcStart != (size_t)(pszDst - &pParser->szBuf[offDst])
2625 || memcmp(&pParser->szBuf[offDst], &pchLine[offSrcStart], cchLine - offSrcStart) != 0;
2626 }
2627
2628 /* Complain and copy out the text unmodified. */
2629 ScmError(pParser->pState, VERR_PARSE_ERROR, "%u:%u: Expected comment, found: %.*s",
2630 pParser->iLine, offSrc, cchLine - offSrc, &pchLine[offSrc]);
2631 *ppszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchLine - offSrcStart);
2632 return false;
2633}
2634
2635
2636/**
2637 * Deals with: ifeq, ifneq, if1of and ifn1of
2638 */
2639static bool scmKmkHandleIfParentheses(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
2640{
2641 const char * const pchLine = pParser->pchLine;
2642 size_t const cchLine = pParser->cchLine;
2643 uint32_t const cchIndent = pParser->iActualDepth
2644 - (fElse && pParser->iDepth > 0 && !pParser->aDepth[pParser->iDepth].fIgnoreNesting);
2645
2646 /*
2647 * Push it onto the stack. All these nestings are relevant.
2648 */
2649 if (!scmKmkPushNesting(pParser, enmToken))
2650 return false;
2651
2652 /*
2653 * We do not allow line continuation for these.
2654 */
2655 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2656 return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
2657
2658 /*
2659 * We stage the modified line in the buffer, so check that the line isn't
2660 * too long (it seriously should be).
2661 */
2662 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
2663 return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", cchToken, &pchLine[offToken], cchLine);
2664 char *pszDst = pParser->szBuf;
2665
2666 /*
2667 * Emit indent and initial token.
2668 */
2669 memset(pszDst, ' ', cchIndent);
2670 pszDst += cchIndent;
2671
2672 if (fElse)
2673 pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
2674
2675 memcpy(pszDst, &pchLine[offToken], cchToken);
2676 pszDst += cchToken;
2677
2678 size_t offSrc = offToken + cchToken;
2679 bool fModified = offSrc != (size_t)(pszDst - &pParser->szBuf[0])
2680 || memcmp(pchLine, pszDst, offSrc) != 0;
2681
2682 /*
2683 * There shall be exactly one space between the token and the opening parenthesis.
2684 */
2685 if (pchLine[offSrc] == ' ' && pchLine[offSrc + 1] == '(')
2686 offSrc += 2;
2687 else
2688 {
2689 fModified = true;
2690 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
2691 offSrc++;
2692 if (pchLine[offSrc] != '(')
2693 return scmKmkGiveUp(pParser, "Expected '(' to follow '%.*s'", cchToken, &pchLine[offToken]);
2694 offSrc++;
2695 }
2696 *pszDst++ = ' ';
2697 *pszDst++ = '(';
2698
2699 /*
2700 * There shall be no blanks after the opening parenthesis.
2701 */
2702 if (RT_C_IS_SPACE(pchLine[offSrc]))
2703 {
2704 fModified = true;
2705 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
2706 offSrc++;
2707 }
2708
2709 /*
2710 * Work up to the ',' separator. It shall likewise not be preceeded by any spaces.
2711 * Need to take $(func 1,2,3) calls into account here, so we trac () and {} while
2712 * skipping ahead.
2713 */
2714 if (pchLine[offSrc] != ',')
2715 {
2716 size_t const offSrcStart = offSrc;
2717 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ',');
2718 if (pchLine[offSrc] != ',')
2719 return scmKmkGiveUp(pParser, "Expected ',' somewhere after '%.*s('", cchToken, &pchLine[offToken]);
2720
2721 size_t cchCopy = offSrc - offSrcStart;
2722 while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
2723 {
2724 fModified = true;
2725 cchCopy--;
2726 }
2727
2728 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
2729 }
2730 /* 'if1of(, stuff)' does not make sense in committed code: */
2731 else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
2732 return scmKmkGiveUp(pParser, "Left set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
2733 offSrc++;
2734 *pszDst++ = ',';
2735
2736 /*
2737 * For if1of and ifn1of we require a space after the comma, whereas ifeq and
2738 * ifneq shall not have any blanks. This is to help tell them apart.
2739 */
2740 if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
2741 {
2742 *pszDst++ = ' ';
2743 if (pchLine[offSrc] == ' ')
2744 offSrc++;
2745 }
2746 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
2747 {
2748 fModified = true;
2749 offSrc++;
2750 }
2751
2752 if (pchLine[offSrc] != ')')
2753 {
2754 size_t const offSrcStart = offSrc;
2755 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ')');
2756 if (pchLine[offSrc] != ')')
2757 return scmKmkGiveUp(pParser, "No closing parenthesis for '%.*s'?", cchToken, &pchLine[offToken]);
2758
2759 size_t cchCopy = offSrc - offSrcStart;
2760 while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
2761 {
2762 fModified = true;
2763 cchCopy--;
2764 }
2765
2766 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
2767 }
2768 /* 'if1of(stuff, )' does not make sense in committed code: */
2769 else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
2770 return scmKmkGiveUp(pParser, "Right set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
2771 offSrc++;
2772 *pszDst++ = ')';
2773
2774 /*
2775 * Handle comment.
2776 */
2777 if (offSrc < cchLine)
2778 fModified |= scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
2779
2780 /*
2781 * Done.
2782 */
2783 *pszDst = '\0';
2784 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
2785 return fModified;
2786}
2787
2788
2789/**
2790 * Deals with: if, ifdef and ifndef
2791 */
2792static bool scmKmkHandleIfSpace(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
2793{
2794 const char *pchLine = pParser->pchLine;
2795 size_t cchLine = pParser->cchLine;
2796 uint32_t const cchIndent = pParser->iActualDepth
2797 - (fElse && pParser->iDepth > 0 && !pParser->aDepth[pParser->iDepth].fIgnoreNesting);
2798
2799 /*
2800 * Push it onto the stack.
2801 *
2802 * For ifndef we ignore the outmost ifndef in non-Makefile.kmk files, if
2803 * the define matches the typical pattern for a file blocker.
2804 */
2805 if (!fElse)
2806 {
2807 if (!scmKmkPushNesting(pParser, enmToken))
2808 return false;
2809 }
2810 else
2811 {
2812 pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
2813 pParser->aDepth[pParser->iDepth - 1].iLine = pParser->iLine;
2814 }
2815 bool fIgnoredNesting = false;
2816 if (enmToken == kKmkToken_ifndef)
2817 {
2818 /** @todo */
2819 }
2820
2821 /*
2822 * We do not allow line continuation for these.
2823 */
2824 uint32_t cLines = 1;
2825 size_t cchMaxLeadWord = 0;
2826 size_t cchTotalLine = cchLine;
2827 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2828 {
2829 if (enmToken != kKmkToken_if)
2830 return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
2831 cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord);
2832 }
2833
2834 /*
2835 * We stage the modified line in the buffer, so check that the line isn't
2836 * too long (plain if can be long, but not ifndef/ifdef).
2837 */
2838 if (cchTotalLine + pParser->iActualDepth + 32 > sizeof(pParser->szBuf))
2839 return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars",
2840 cchToken, &pchLine[offToken], cchTotalLine);
2841 char *pszDst = pParser->szBuf;
2842
2843 /*
2844 * Emit indent and initial token.
2845 */
2846 memset(pszDst, ' ', cchIndent);
2847 pszDst += cchIndent;
2848
2849 if (fElse)
2850 pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
2851
2852 memcpy(pszDst, &pchLine[offToken], cchToken);
2853 pszDst += cchToken;
2854
2855 size_t offSrc = offToken + cchToken;
2856 bool fModified = offSrc != (size_t)(pszDst - &pParser->szBuf[0])
2857 || memcmp(pchLine, pszDst, offSrc) != 0;
2858
2859 /*
2860 * ifndef/ifdef shall have exactly one space. For 'if' we allow up to 4, but
2861 * we'll deal with that further down.
2862 */
2863 size_t cchSpaces = 0;
2864 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
2865 {
2866 fModified |= pchLine[offSrc] != ' ';
2867 cchSpaces++;
2868 offSrc++;
2869 }
2870 if (cchSpaces == 0)
2871 return scmKmkGiveUp(pParser, "Nothing following '%.*s' or bogus line continuation?", cchToken, &pchLine[offToken]);
2872 *pszDst++ = ' ';
2873
2874 /*
2875 * For ifdef and ifndef there now comes a single word.
2876 */
2877 if (enmToken != kKmkToken_if)
2878 {
2879 fModified |= cchSpaces != 1;
2880
2881 size_t const offSrcStart = offSrc;
2882 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ' ', '\t'); /** @todo probably not entirely correct */
2883 if (offSrc == offSrcStart)
2884 return scmKmkGiveUp(pParser, "No word following '%.*s'?", cchToken, &pchLine[offToken]);
2885
2886 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], offSrc - offSrcStart);
2887 }
2888 /*
2889 * While for 'if' things are more complicated, especially if it spans more
2890 * than one line.
2891 */
2892 else if (cLines <= 1)
2893 {
2894 /* Single line expression: Just assume the expression goes up to the
2895 EOL or comment hash. Strip and copy as-is for now. */
2896 fModified |= cchSpaces != 1;
2897
2898 const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
2899 size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
2900 while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
2901 cchExpr--;
2902
2903 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
2904 offSrc += cchExpr;
2905 }
2906 else
2907 {
2908 /* Multi line expression: We normalize leading whitespace using
2909 cchMaxLeadWord for now. Expression on line 2+ are indented by two
2910 extra characters, because we'd otherwise be puttin the operator on
2911 the same level as the 'if', which would be confusing. Thus:
2912
2913 if expr1
2914 + expr2
2915 endif
2916
2917 if expr1
2918 || expr2
2919 endif
2920
2921 if expr3
2922 vtg expr4
2923 endif
2924
2925 We do '#' / EOL handling for the final line the same way as above.
2926
2927 Later we should add the ability to rework the expression properly,
2928 making sure new lines starts with operators and such. */
2929 /** @todo Implement simples expression parser and indenter, possibly also
2930 * removing unnecessary parentheses. Can be shared with C/C++. */
2931 if (cchMaxLeadWord > 3)
2932 return scmKmkGiveUp(pParser,
2933 "Bogus multi-line 'if' expression! Extra lines must start with operator (cchMaxLeadWord=%u).",
2934 cchMaxLeadWord);
2935 fModified |= cchSpaces != cchMaxLeadWord + 1;
2936 memset(pszDst, ' ', cchMaxLeadWord);
2937 pszDst += cchMaxLeadWord;
2938
2939 size_t cchSrcContIndent = offToken + 2;
2940 for (uint32_t iSubLine = 0; iSubLine < cLines - 1; iSubLine++)
2941 {
2942 /* Trim the line. */
2943 size_t offSrcEnd = cchLine;
2944 Assert(pchLine[offSrcEnd - 1] == '\\');
2945 offSrcEnd--;
2946
2947 if (pchLine[offSrcEnd - 1] == '\\')
2948 return scmKmkGiveUp(pParser, "Escaped '\\' before line continuation in 'if' expression is not allowed!");
2949
2950 while (offSrcEnd > offSrc && RT_C_IS_BLANK(pchLine[offSrcEnd - 1]))
2951 offSrcEnd--;
2952
2953 /* Comments with line continuation is not allowed in commited makefiles. */
2954 if (offSrc < offSrcEnd && memchr(&pchLine[offSrc], '#', cchLine - offSrc) != NULL)
2955 return scmKmkGiveUp(pParser, "Comment in multi-line 'if' expression is not allowed to start before the final line!");
2956
2957 /* Output it. */
2958 if (offSrc < offSrcEnd)
2959 {
2960 if (iSubLine > 0 && offSrc > cchSrcContIndent)
2961 {
2962 memset(pszDst, ' ', offSrc - cchSrcContIndent);
2963 pszDst += offSrc - cchSrcContIndent;
2964 }
2965 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], offSrcEnd - offSrc);
2966 *pszDst++ = ' ';
2967 }
2968 else if (iSubLine == 0)
2969 return scmKmkGiveUp(pParser, "Expected expression after 'if', not line continuation!");
2970 *pszDst++ = '\\';
2971 *pszDst = '\0';
2972 size_t cchDst = (size_t)(pszDst - pParser->szBuf);
2973 fModified |= cchDst != cchLine
2974 || memcmp(pParser->szBuf, pchLine, cchLine) != 0;
2975 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
2976
2977 /*
2978 * Fetch the next line and start processing it.
2979 */
2980 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
2981 if (!pchLine)
2982 return ScmError(pParser->pState, VERR_INTERNAL_ERROR_3, "ScmStreamGetLine unexpectedly returned NULL!");
2983 cchLine = pParser->cchLine;
2984 pParser->iLine++;
2985
2986 /* Skip leading whitespace and adjust the source continuation indent: */
2987 offSrc = 0;
2988 while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
2989 offSrc++;
2990 /** @todo tabs */
2991
2992 if (iSubLine == 0)
2993 cchSrcContIndent = offSrc;
2994
2995 /* Initial indent: */
2996 pszDst = pParser->szBuf;
2997 memset(pszDst, ' ', cchIndent + 2);
2998 pszDst += cchIndent + 2;
2999 }
3000
3001 /* Output the expression on the final line. */
3002 const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
3003 size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
3004 while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
3005 cchExpr--;
3006
3007 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
3008 offSrc += cchExpr;
3009 }
3010
3011
3012 /*
3013 * Handle comment.
3014 *
3015 * Here we check for the "scm:ignore-nesting" directive that makes us not
3016 * add indentation for this directive. We do this on the destination buffer
3017 * as that can be zero terminated and is therefore usable with strstr.
3018 */
3019 if (offSrc >= cchLine)
3020 *pszDst = '\0';
3021 else
3022 {
3023 char * const pszDstSrc = pszDst;
3024 fModified |= scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
3025 *pszDst = '\0';
3026
3027 /* Check for special comment making us ignore the nesting. We do this in the
3028
3029 */
3030 if (!fIgnoredNesting && strstr(pszDstSrc, "scm:ignore-nesting") != NULL)
3031 {
3032 pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting = true;
3033 pParser->iActualDepth--;
3034 }
3035 }
3036
3037 /*
3038 * Done.
3039 */
3040 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3041 return fModified;
3042}
3043
3044
3045/**
3046 * Deals with: else
3047 */
3048static bool scmKmkHandleElse(KMKPARSER *pParser, size_t offToken)
3049{
3050 const char * const pchLine = pParser->pchLine;
3051 size_t const cchLine = pParser->cchLine;
3052
3053 if (pParser->iDepth < 1)
3054 return scmKmkGiveUp(pParser, "Lone 'else'");
3055 uint32_t const cchIndent = pParser->iActualDepth - !pParser->aDepth[pParser->iDepth].fIgnoreNesting;
3056
3057 /*
3058 * Look past the else and check if there any ifxxx token following it.
3059 */
3060 size_t offSrc = offToken + 4;
3061 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
3062 offSrc++;
3063 if (offSrc < cchLine)
3064 {
3065 size_t cchWord = 0;
3066 while (offSrc + cchWord < cchLine && RT_C_IS_ALNUM(pchLine[offSrc + cchWord]))
3067 cchWord++;
3068 if (cchWord)
3069 {
3070 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offSrc], cchWord);
3071 switch (enmToken)
3072 {
3073 case kKmkToken_ifeq:
3074 case kKmkToken_ifneq:
3075 case kKmkToken_if1of:
3076 case kKmkToken_ifn1of:
3077 return scmKmkHandleIfParentheses(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
3078
3079 case kKmkToken_ifdef:
3080 case kKmkToken_ifndef:
3081 case kKmkToken_if:
3082 return scmKmkHandleIfSpace(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
3083
3084 default:
3085 break;
3086 }
3087 }
3088 }
3089
3090 /*
3091 * We do not allow line continuation for these.
3092 */
3093 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
3094 return scmKmkGiveUp(pParser, "Line continuation not allowed with 'else' directive.");
3095
3096 /*
3097 * We stage the modified line in the buffer, so check that the line isn't
3098 * too long (it seriously should be).
3099 */
3100 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
3101 return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
3102 char *pszDst = pParser->szBuf;
3103
3104 /*
3105 * Emit indent and initial token.
3106 */
3107 memset(pszDst, ' ', cchIndent);
3108 pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("else"));
3109
3110 offSrc = offToken + 4;
3111 bool fModified = offSrc != (size_t)(pszDst - &pParser->szBuf[0])
3112 || memcmp(pchLine, pszDst, offSrc) != 0;
3113
3114 /*
3115 * Handle comment.
3116 */
3117 if (offSrc < cchLine)
3118 fModified |= scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
3119
3120 /*
3121 * Done.
3122 */
3123 *pszDst = '\0';
3124 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3125 return fModified;
3126}
3127
3128
3129/**
3130 * Deals with: endif
3131 */
3132static bool scmKmkHandleEndif(KMKPARSER *pParser, size_t offToken)
3133{
3134 const char * const pchLine = pParser->pchLine;
3135 size_t const cchLine = pParser->cchLine;
3136
3137 /*
3138 * Pop a nesting.
3139 */
3140 if (pParser->iDepth < 1)
3141 return scmKmkGiveUp(pParser, "Lone 'endif'");
3142 uint32_t iDepth = pParser->iDepth - 1;
3143 pParser->iDepth = iDepth;
3144 if (!pParser->aDepth[iDepth].fIgnoreNesting)
3145 {
3146 AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++);
3147 pParser->iActualDepth -= 1;
3148 }
3149 uint32_t const cchIndent = pParser->iActualDepth;
3150
3151 /*
3152 * We do not allow line continuation for these.
3153 */
3154 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
3155 return scmKmkGiveUp(pParser, "Line continuation not allowed with 'endif' directive.");
3156
3157 /*
3158 * We stage the modified line in the buffer, so check that the line isn't
3159 * too long (it seriously should be).
3160 */
3161 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
3162 return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
3163 char *pszDst = pParser->szBuf;
3164
3165 /*
3166 * Emit indent and initial token.
3167 */
3168 memset(pszDst, ' ', cchIndent);
3169 pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("endif"));
3170
3171 size_t offSrc = offToken + 5;
3172 bool fModified = offSrc != (size_t)(pszDst - &pParser->szBuf[0])
3173 || memcmp(pchLine, pszDst, offSrc) != 0;
3174
3175 /*
3176 * Handle comment.
3177 */
3178 if (offSrc < cchLine)
3179 fModified |= scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
3180
3181 /*
3182 * Done.
3183 */
3184 *pszDst = '\0';
3185 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3186 return fModified;
3187}
3188
3189
3190/**
3191 * Passing thru any line continuation lines following the current one.
3192 */
3193static bool scmKmkPassThruLineContinuationLines(KMKPARSER *pParser)
3194{
3195 while (scmKmkIsLineWithContinuation(pParser->pchLine, pParser->cchLine))
3196 {
3197 pParser->pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3198 if (!pParser->pchLine)
3199 break;
3200 ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
3201 }
3202 return false;
3203}
3204
3205
3206/**
3207 * For dealing with a directive w/o special formatting rules (yet).
3208 */
3209static bool scmKmkHandleSimple(KMKPARSER *pParser, size_t offToken, bool fIndentIt = true)
3210{
3211 const char *pchLine = pParser->pchLine;
3212 size_t cchLine = pParser->cchLine;
3213 uint32_t const cchIndent = fIndentIt ? pParser->iActualDepth : 0;
3214
3215 /*
3216 * Just reindent the statement.
3217 */
3218 ScmStreamWrite(pParser->pOut, g_szSpaces, cchIndent);
3219 ScmStreamWrite(pParser->pOut, &pchLine[offToken], cchLine - offToken);
3220 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
3221
3222 bool fModified = cchIndent != offToken
3223 || !memcmp(pchLine, g_szSpaces, cchIndent);
3224
3225 /*
3226 * Check for line continuation and output concatenated lines.
3227 */
3228 scmKmkPassThruLineContinuationLines(pParser);
3229 return fModified;
3230}
3231
3232
3233static bool scmKmkHandleDefine(KMKPARSER *pParser, size_t offToken)
3234{
3235 /* Assignments takes us out of recipe mode. */
3236 pParser->fInRecipe = false;
3237
3238 return scmKmkHandleSimple(pParser, offToken);
3239}
3240
3241
3242static bool scmKmkHandleEndef(KMKPARSER *pParser, size_t offToken)
3243{
3244 /* Leaving a define resets the recipt mode. */
3245 pParser->fInRecipe = false;
3246
3247 return scmKmkHandleSimple(pParser, offToken);
3248}
3249
3250
3251typedef enum KMKASSIGNTYPE
3252{
3253 kKmkAssignType_Recursive,
3254 kKmkAssignType_Conditional,
3255 kKmkAssignType_Appending,
3256 kKmkAssignType_Prepending,
3257 kKmkAssignType_Simple,
3258 kKmkAssignType_Immediate
3259} KMKASSIGNTYPE;
3260
3261
3262static bool scmKmkHandleAssignment2(KMKPARSER *pParser, size_t offVarStart, size_t offVarEnd, KMKASSIGNTYPE enmType,
3263 size_t offAssignOp, unsigned fFlags)
3264{
3265 unsigned const cchIndent = pParser->iActualDepth;
3266 const char *pchLine = pParser->pchLine;
3267 size_t cchLine = pParser->cchLine;
3268 uint32_t const cLines = pParser->cLines;
3269 uint32_t iSubLine = 0;
3270
3271 RT_NOREF(fFlags);
3272 Assert(offVarStart < cchLine);
3273 Assert(offVarEnd <= cchLine);
3274 Assert(offVarStart < offVarEnd);
3275 Assert(!RT_C_IS_SPACE(pchLine[offVarStart]));
3276 Assert(!RT_C_IS_SPACE(pchLine[offVarEnd - 1]));
3277
3278 /* Assignments takes us out of recipe mode. */
3279 pParser->fInRecipe = false;
3280
3281 /* This is too much hazzle to deal with. */
3282 if (cLines > 0 && pchLine[cchLine - 2] == '\\')
3283 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3284 if (cchLine + 64 > sizeof(pParser->szBuf))
3285 return scmKmkGiveUp(pParser, "Line too long!");
3286
3287 /*
3288 * Indent and output the variable name.
3289 */
3290 char *pszDst = pParser->szBuf;
3291 memset(pszDst, ' ', cchIndent);
3292 pszDst += cchIndent;
3293 pszDst = (char *)mempcpy(pszDst, &pchLine[offVarStart], offVarEnd - offVarStart);
3294
3295 /*
3296 * Try preserve the assignment operator position, but make sure we've got a
3297 * space in front of it.
3298 */
3299 if (offAssignOp < cchLine)
3300 {
3301 size_t offDst = (size_t)(pszDst - pParser->szBuf);
3302 size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings);
3303 if (offDst < offEffAssignOp)
3304 {
3305 size_t cchSpacesToWrite = offEffAssignOp - offDst;
3306 memset(pszDst, ' ', cchSpacesToWrite);
3307 pszDst += cchSpacesToWrite;
3308 }
3309 else
3310 *pszDst++ = ' ';
3311 }
3312 else
3313 {
3314 /* Pull up the assignment operator to the variable line. */
3315 *pszDst++ = ' ';
3316
3317 /* Eat up lines till we hit the operator. */
3318 while (offAssignOp < cchLine)
3319 {
3320 const char * const pchPrevLine = pchLine;
3321 Assert(iSubLine + 1 < cLines);
3322 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3323 AssertReturn(pchLine, true);
3324 cchLine = pParser->cchLine;
3325 iSubLine++;
3326 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3327 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3328
3329 /* Adjust offAssignOp: */
3330 offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
3331 Assert(offAssignOp < ~(size_t)0 / 2);
3332 }
3333
3334 if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf))
3335 return scmKmkGiveUp(pParser, "Line too long!");
3336 }
3337
3338 /*
3339 * Emit the operator.
3340 */
3341 size_t offLine = offAssignOp;
3342 switch (enmType)
3343 {
3344 default: AssertReleaseFailed();
3345 case kKmkAssignType_Recursive:
3346 *pszDst++ = '=';
3347 Assert(pchLine[offLine] == '=');
3348 offLine++;
3349 break;
3350 case kKmkAssignType_Conditional:
3351 *pszDst++ = '?';
3352 *pszDst++ = '=';
3353 Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '=');
3354 offLine += 2;
3355 break;
3356 case kKmkAssignType_Appending:
3357 *pszDst++ = '+';
3358 *pszDst++ = '=';
3359 Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '=');
3360 offLine += 2;
3361 break;
3362 case kKmkAssignType_Prepending:
3363 *pszDst++ = '>';
3364 *pszDst++ = '=';
3365 Assert(pchLine[offLine] == '>'); Assert(pchLine[offLine + 1] == '=');
3366 offLine += 2;
3367 break;
3368 case kKmkAssignType_Immediate:
3369 *pszDst++ = ':';
3370 Assert(pchLine[offLine] == ':');
3371 offLine++;
3372 RT_FALL_THRU();
3373 case kKmkAssignType_Simple:
3374 *pszDst++ = ':';
3375 *pszDst++ = '=';
3376 Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '=');
3377 offLine += 2;
3378 break;
3379 }
3380
3381 /*
3382 * Skip space till we hit the value or comment.
3383 */
3384 size_t cchSpaces = 0;
3385 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3386 cchSpaces++, offLine++;
3387
3388/** @todo this block can probably be merged into the final loop below. */
3389 unsigned cPendingEols = 0;
3390 bool fModified = false;
3391 unsigned const iSubLineStart1 = iSubLine;
3392 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3393 {
3394 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3395 AssertReturn(pchLine, fModified);
3396 cchLine = pParser->cchLine;
3397 iSubLine++;
3398 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3399 {
3400 *pszDst++ = ' ';
3401 *pszDst++ = '\\';
3402 *pszDst = '\0';
3403 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3404 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3405 }
3406 cPendingEols = 1;
3407
3408 /* Deal with indent/whitespace. */
3409 offLine = 0;
3410 if ( memcmp(pchLine, g_szSpaces, cchIndent) == 0
3411 && pchLine[cchIndent] == '\t')
3412 offLine = cchIndent + 1;
3413 cchSpaces = 0;
3414 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3415 cchSpaces++, offLine++;
3416 fModified |= cchSpaces != 0 && pchLine[offLine] != '#';
3417 }
3418 fModified |= iSubLine > iSubLineStart1 + 1;
3419
3420 /*
3421 * Okay, we've gotten to the value / comment part.
3422 */
3423 for (;;)
3424 {
3425 /*
3426 * The end? Flush what we've got.
3427 */
3428 if (offLine == cchLine)
3429 {
3430 Assert(iSubLine + 1 == cLines);
3431 *pszDst = '\0';
3432 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3433 return fModified || cPendingEols > 0;
3434 }
3435
3436 /*
3437 * Output any non-comment stuff, stripping off newlines.
3438 */
3439 const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
3440 if (pchHash != &pchLine[offLine])
3441 {
3442 /* Add space or flush pending EOLs. */
3443 if (!cPendingEols)
3444 *pszDst++ = ' ';
3445 else
3446 {
3447 fModified |= cPendingEols > 2;
3448 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
3449 do
3450 {
3451 *pszDst++ = ' ';
3452 *pszDst++ = '\\';
3453 *pszDst = '\0';
3454 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3455
3456 pszDst = pParser->szBuf;
3457 memset(pszDst, ' ', cchIndent);
3458 pszDst += cchIndent;
3459 *pszDst++ = '\t';
3460 cPendingEols--;
3461 } while (cPendingEols > 0);
3462 }
3463
3464 /* Strip backwards. */
3465 size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
3466 size_t offValueEnd = offValueEnd2;
3467 while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
3468 offValueEnd--;
3469 Assert(offValueEnd > offLine);
3470
3471 fModified |= !pchHash && offValueEnd != cchLine - (iSubLine + 1 < cLines ? 2 : 0);
3472
3473 /* Append the value part we found. */
3474 pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
3475 offLine = offValueEnd2;
3476 }
3477
3478 /*
3479 * If we found a comment hash, emit it and whatever follows just as-is w/o
3480 * any particular reformatting. Comments within a variable definition are
3481 * usually to disable portitions of a property like _DEFS or _SOURCES.
3482 */
3483 if (pchHash != NULL)
3484 {
3485 if (cPendingEols == 0)
3486 scmKmkTailComment(pParser, pchLine, cchLine, offLine - cchSpaces, &pszDst);
3487 size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
3488 *pszDst = '\0';
3489 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
3490 fModified |= cPendingEols > 0
3491 || cchLine != cchDst
3492 || memcmp(pParser->szBuf, pchLine, cchLine) != 0;
3493
3494 if (cPendingEols > 1)
3495 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
3496
3497 if (cPendingEols > 0)
3498 ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
3499 scmKmkPassThruLineContinuationLines(pParser);
3500 return fModified;
3501 }
3502
3503 /*
3504 * Fetch another line, if we've got one.
3505 */
3506 if (iSubLine + 1 >= cLines)
3507 Assert(offLine == cchLine);
3508 else
3509 {
3510 Assert(offLine + 1 == cchLine);
3511 unsigned const iSubLineStart2 = iSubLine;
3512 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3513 {
3514 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3515 AssertReturn(pchLine, fModified);
3516 cchLine = pParser->cchLine;
3517 iSubLine++;
3518 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3519 {
3520 *pszDst++ = ' ';
3521 *pszDst++ = '\\';
3522 *pszDst = '\0';
3523 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3524 if (cPendingEols > 1)
3525 ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
3526 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3527 }
3528 cPendingEols++;
3529
3530 /* Deal with indent/whitespace. */
3531 offLine = 0;
3532 if ( memcmp(pchLine, g_szSpaces, cchIndent) == 0
3533 && pchLine[cchIndent] == '\t')
3534 offLine = cchIndent + 1;
3535 cchSpaces = 0;
3536 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3537 cchSpaces++, offLine++;
3538 fModified |= cchSpaces != 0 && pchLine[offLine] != '#';
3539 }
3540 fModified |= iSubLine > iSubLineStart2 + 1;
3541 }
3542 }
3543}
3544
3545
3546/**
3547 * A rule.
3548 *
3549 * This is a bit involved. Sigh.
3550 */
3551static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon)
3552{
3553 SCMSTREAM *pOut = pParser->pOut;
3554 unsigned const cchIndent = pParser->iActualDepth;
3555 const char *pchLine = pParser->pchLine;
3556 size_t cchLine = pParser->cchLine;
3557 Assert(offFirstWord < cchLine);
3558 uint32_t const cLines = pParser->cLines;
3559 uint32_t iSubLine = 0;
3560
3561 /* Following this, we'll be in recipe-mode. */
3562 pParser->fInRecipe = true;
3563
3564 /* This is too much hazzle to deal with. */
3565 if (cLines > 0 && pchLine[cchLine - 2] == '\\')
3566 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3567
3568 /* Too special case. */
3569 if (offColon <= offFirstWord)
3570 return scmKmkGiveUp(pParser, "Missing target file before colon!");
3571
3572 /*
3573 * Indent it.
3574 */
3575 bool fModified = offFirstWord != cchIndent
3576 || memcmp(pchLine, g_szSpaces, cchIndent) != 0;
3577 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3578 size_t offLine = offFirstWord;
3579
3580 /*
3581 * Process word by word past the colon, taking new lines into account.
3582 *
3583 */
3584 KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment;
3585 bool fPendingEol = false;
3586 for (;;)
3587 {
3588 /*
3589 * Output the next word.
3590 */
3591 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx);
3592 Assert(offLine + cchWord <= offColon);
3593 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
3594 offLine += cchWord;
3595
3596 /* Skip whitespace (if any). */
3597 size_t cchSpaces = 0;
3598 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3599 {
3600 fModified |= pchLine[offLine] != ' ';
3601 cchSpaces++;
3602 offLine++;
3603 }
3604
3605 /* Have we reached the colon already? */
3606 if (offLine >= offColon)
3607 {
3608 fModified |= cchSpaces != 0;
3609
3610 Assert(pchLine[offLine] == ':');
3611 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
3612 offLine += fDoubleColon ? 2 : 1;
3613
3614 ScmStreamPutCh(pOut, ':');
3615 if (fDoubleColon)
3616 ScmStreamPutCh(pOut, ':');
3617 break;
3618 }
3619
3620 /* Deal with new line and emit indentation. */
3621 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3622 {
3623 fModified |= cchSpaces > 1;
3624
3625 /* Get the next input line. */
3626 for (;;)
3627 {
3628 const char * const pchPrevLine = pchLine;
3629 Assert(iSubLine + 1 < cLines);
3630 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3631 AssertReturn(pchLine, fModified);
3632 cchLine = pParser->cchLine;
3633 iSubLine++;
3634 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3635 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3636
3637 /* Adjust offColon: */
3638 offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
3639 Assert(offColon < ~(size_t)0 / 2);
3640
3641 /* Skip leading spaces. */
3642 offLine = 0;
3643 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3644 {
3645 fModified |= pchLine[offLine] != ' ';
3646 offLine++;
3647 }
3648 fModified |= offLine == cchIndent
3649 || memcmp(pchLine, g_szSpaces, cchIndent) != 0;
3650
3651 /* Just drop empty lines. */
3652 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3653 {
3654 fModified = true;
3655 continue;
3656 }
3657
3658 /* Complete the current line and emit indent, unless we reached the colon: */
3659 if (offLine >= offColon)
3660 {
3661 fModified = true;
3662 Assert(pchLine[offLine] == ':');
3663 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
3664 offLine += fDoubleColon ? 2 : 1;
3665
3666 ScmStreamPutCh(pOut, ':');
3667 if (fDoubleColon)
3668 ScmStreamPutCh(pOut, ':');
3669
3670 fPendingEol = true;
3671 break;
3672 }
3673 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
3674 ScmStreamPutEol(pOut, pParser->enmEol);
3675 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3676 }
3677 if (offLine >= offColon)
3678 break;
3679 }
3680 else
3681 {
3682 fModified |= cchSpaces != 1;
3683 ScmStreamPutCh(pOut, ' ');
3684 }
3685 enmCtx = kKmkWordCtx_TargetFile;
3686 }
3687
3688 /*
3689 * We're immediately past the colon now, so eat whitespace and newlines and
3690 * whatever till we get to a solid word.
3691 */
3692 /* Skip spaces - there should be exactly one. */
3693 fModified |= pchLine[offLine] != ' ';
3694 if (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3695 offLine++;
3696 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3697 {
3698 fModified = true;
3699 offLine++;
3700 }
3701
3702 /* Deal with new lines: */
3703 while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3704 {
3705 fPendingEol = true;
3706
3707 Assert(iSubLine + 1 < cLines);
3708 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3709 AssertReturn(pchLine, fModified);
3710 cchLine = pParser->cchLine;
3711 iSubLine++;
3712 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3713 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3714
3715 /* Skip leading spaces. */
3716 offLine = 0;
3717 if (memcmp(pchLine, g_szSpaces, cchIndent) == 0 && pchLine[cchIndent] == '\t' && pchLine[cchIndent + 1] == '\t')
3718 offLine += cchIndent + 2;
3719 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3720 {
3721 fModified = true;
3722 offLine++;
3723 }
3724
3725 /* Just drop empty lines. */
3726 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3727 {
3728 fModified = true;
3729 continue;
3730 }
3731 }
3732
3733 /*
3734 * Special case: No dependencies.
3735 */
3736 if (offLine == cchLine && iSubLine >= cLines)
3737 {
3738 ScmStreamPutEol(pOut, pParser->enmEol);
3739 return fModified;
3740 }
3741
3742 /*
3743 * Work the dependencies word for word. Indent in spaces + two tabs.
3744 * (Pattern rules will also end up here, but we'll just ignore that for now.)
3745 */
3746 /** @todo fModified isn't updated right here. */
3747 enmCtx = kKmkWordCtx_DepFileOrAssignment;
3748 for (;;)
3749 {
3750 /* Indent the next word. */
3751 if (!fPendingEol)
3752 ScmStreamPutCh(pOut, ' ');
3753 else
3754 {
3755 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
3756 ScmStreamPutEol(pOut, pParser->enmEol);
3757 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3758 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
3759 fPendingEol = false;
3760 }
3761
3762 /* Get the next word and output it. */
3763 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx);
3764 Assert(offLine + cchWord <= cchLine);
3765
3766 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
3767 offLine += cchWord;
3768
3769 /* Skip whitespace (if any). */
3770 size_t cchSpaces = 0;
3771 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3772 {
3773 fModified |= pchLine[offLine] != ' ';
3774 cchSpaces++;
3775 offLine++;
3776 }
3777
3778 /* Deal with new line and emit indentation. */
3779 if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3780 {
3781 fModified |= cchSpaces > 1;
3782
3783 /* Get the next input line. */
3784 unsigned cEmptyLines = 0;
3785 for (;;)
3786 {
3787 Assert(iSubLine + 1 < cLines);
3788 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3789 AssertReturn(pchLine, fModified);
3790 cchLine = pParser->cchLine;
3791 iSubLine++;
3792 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3793 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3794
3795 /* Skip leading spaces. */
3796 offLine = 0;
3797 if (memcmp(pchLine, g_szSpaces, cchIndent) == 0 && pchLine[cchIndent] == '\t' && pchLine[cchIndent + 1] == '\t')
3798 offLine += cchIndent + 2;
3799 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3800 {
3801 fModified = true;
3802 offLine++;
3803 }
3804
3805 /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
3806 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3807 {
3808 cEmptyLines++;
3809 continue;
3810 }
3811
3812 fPendingEol = true;
3813 break;
3814 }
3815 cchSpaces = 1;
3816 }
3817
3818 if (offLine < cchLine)
3819 fModified |= cchSpaces != 1;
3820 else
3821 {
3822 /* End of input. */
3823/** @todo deal with comments */
3824 Assert(iSubLine + 1 == cLines);
3825 ScmStreamPutEol(pOut, pParser->enmEol);
3826 return fModified;
3827 }
3828 enmCtx = kKmkWordCtx_DepFile;
3829 }
3830}
3831
3832
3833/**
3834 * Checks if the (extended) line is a variable assignment.
3835 *
3836 * We scan past line continuation stuff here as the assignment operator could be
3837 * on the next line, even if that's very unlikely it is recommened by the coding
3838 * guide lines if the line needs to be split. Fortunately, though, the caller
3839 * already removes empty empty leading lines, so we only have to consider the
3840 * line continuation issue if no '=' was found on the first line.
3841 *
3842 * @returns Modified or not.
3843 * @param pParser The parser.
3844 * @param cLines Number of lines to consider.
3845 * @param cchTotalLine Total length of all the lines to consider.
3846 * @param offWord Where the first word of the line starts.
3847 * @param pfIsAssignment Where to return whether this is an assignment or
3848 * not.
3849 */
3850static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord)
3851{
3852 const char *pchLine = pParser->pchLine;
3853 size_t const cchTotalLine = pParser->cchTotalLine;
3854
3855 /*
3856 * Scan words till we find ':' or '='.
3857 */
3858 uint32_t iWord = 0;
3859 size_t offCurWord = offWord;
3860 size_t offEndPrev = 0;
3861 size_t offLine = offWord;
3862 while (offLine < cchTotalLine)
3863 {
3864 char ch = pchLine[offLine++];
3865 if (ch == '$')
3866 {
3867 /*
3868 * Skip variable expansion.
3869 */
3870 char const chOpen = pchLine[offLine++];
3871 if (chOpen == '(' || chOpen == '{')
3872 {
3873 char const chClose = chOpen == '(' ? ')' : '}';
3874 unsigned cDepth = 1;
3875 while (offLine < cchTotalLine)
3876 {
3877 ch = pchLine[offLine++];
3878 if (ch == chOpen)
3879 cDepth++;
3880 else if (ch == chClose)
3881 if (!--cDepth)
3882 break;
3883 }
3884 }
3885 /* else: $x or $$, so just skip the next character. */
3886 }
3887 else if (RT_C_IS_SPACE(ch))
3888 {
3889 /*
3890 * End of word. Skip whitespace till the next word starts.
3891 */
3892 offEndPrev = offLine - 1;
3893 Assert(offLine != offWord);
3894 while (offLine < cchTotalLine)
3895 {
3896 ch = pchLine[offLine];
3897 if (RT_C_IS_SPACE(ch))
3898 offLine++;
3899 else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n'))
3900 offLine += 2;
3901 else
3902 break;
3903 }
3904 offCurWord = offLine;
3905 iWord++;
3906
3907 /*
3908 * To simplify the assignment operator checks, we just check the
3909 * start of the 2nd word when we're here.
3910 */
3911 if (iWord == 1 && offLine < cchTotalLine)
3912 {
3913 ch = pchLine[offLine];
3914 if (ch == '=')
3915 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
3916 if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
3917 {
3918 if (ch == ':')
3919 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0);
3920 if (ch == '+')
3921 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0);
3922 if (ch == '>')
3923 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0);
3924 if (ch == '?')
3925 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
3926 }
3927 else if ( ch == ':'
3928 && pchLine[offLine + 1] == ':'
3929 && pchLine[offLine + 2] == '=')
3930 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
3931
3932 /* Check for rule while we're here. */
3933 if (ch == ':')
3934 return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
3935 }
3936 }
3937 /*
3938 * If '=' is found in the first word it's an assignment.
3939 */
3940 else if (ch == '=')
3941 {
3942 if (iWord == 0)
3943 {
3944 KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
3945 ch = pchLine[offLine - 2];
3946 if (ch == '+')
3947 enmType = kKmkAssignType_Appending;
3948 else if (ch == '?')
3949 enmType = kKmkAssignType_Conditional;
3950 else if (ch == '>')
3951 enmType = kKmkAssignType_Prepending;
3952 else
3953 Assert(ch != ':');
3954 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
3955 }
3956 }
3957 /*
3958 * When ':' is found it can mean a drive letter, a rule or in the
3959 * first word a simple or immediate assignment.
3960 */
3961 else if (ch == ':')
3962 {
3963 /* Check for drive letters (we ignore the archive form): */
3964 if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
3965 { /* ignore */ }
3966 else
3967 {
3968 /* Simple or immediate assignment? */
3969 ch = pchLine[offLine];
3970 if (iWord == 0)
3971 {
3972 if (ch == '=')
3973 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
3974 if (ch == ':' && pchLine[offLine + 1] == '=')
3975 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
3976 }
3977
3978 /* Okay, it's a rule then. */
3979 return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
3980 }
3981 }
3982 }
3983
3984 /*
3985 * If we didn't find anything, output it as-as.
3986 * We use scmKmkHandleSimple in a special way to do this.
3987 */
3988 ScmVerbose(pParser->pState, 1, "debug: %u: Unable to make sense of this line!", pParser->iLine);
3989 return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
3990}
3991
3992
3993static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord,
3994 bool fMustBeAssignment)
3995{
3996 /* Assignments takes us out of recipe mode. */
3997 pParser->fInRecipe = false;
3998
3999 RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment);
4000 return scmKmkHandleSimple(pParser, offToken);
4001}
4002
4003
4004/**
4005 * Rewrite a kBuild makefile.
4006 *
4007 * @returns true if modifications were made, false if not.
4008 * @param pIn The input stream.
4009 * @param pOut The output stream.
4010 * @param pSettings The settings.
4011 *
4012 * @todo
4013 *
4014 * Ideas for Makefile.kmk and Config.kmk:
4015 * - sort if1of/ifn1of sets.
4016 * - line continuation slashes should only be preceded by one space.
4017 */
4018bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4019{
4020 if (!pSettings->fStandarizeKmk)
4021 return false;
4022
4023 /*
4024 * Parser state.
4025 */
4026 KMKPARSER Parser;
4027 Parser.iDepth = 0;
4028 Parser.iActualDepth = 0;
4029 Parser.fInRecipe = false;
4030 Parser.iLine = 0;
4031 Parser.pState = pState;
4032 Parser.pIn = pIn;
4033 Parser.pOut = pOut;
4034 Parser.pSettings = pSettings;
4035
4036 /*
4037 * Iterate the file.
4038 */
4039 bool fModified = false;
4040 const char *pchLine;
4041 while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL)
4042 {
4043 size_t cchLine = Parser.cchLine;
4044 Parser.iLine++;
4045
4046 /*
4047 * If we're in the command part of a recipe, anything starting with a
4048 * tab is considered another command for the recipe.
4049 */
4050 if (Parser.fInRecipe && *pchLine == '\t')
4051 {
4052 /* Do we do anything here? */
4053 }
4054 else
4055 {
4056 /*
4057 * Skip leading whitespace and check for directives (simplified).
4058 *
4059 * This is simplified in the sense that GNU make first checks for variable
4060 * assignments, so that directive can be used as variable names. We don't
4061 * want that, so we do the variable assignment check later.
4062 */
4063 size_t offLine = 0;
4064 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4065 offLine++;
4066
4067 /* Find end of word (if any): */
4068 size_t cchWord = 0;
4069 while ( offLine + cchWord < cchLine
4070 && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord])
4071 || pchLine[offLine + cchWord] == '-'))
4072 cchWord++;
4073 if (cchWord > 0)
4074 {
4075 /* If the line is just a line continuation slash, simply remove it
4076 (this also makes the parsing a lot easier). */
4077 if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
4078 continue;
4079
4080 /* Unlike the GNU make parser, we won't recognize 'if' or any other
4081 directives as variable names, so we can */
4082 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
4083 switch (enmToken)
4084 {
4085 case kKmkToken_ifeq:
4086 case kKmkToken_ifneq:
4087 case kKmkToken_if1of:
4088 case kKmkToken_ifn1of:
4089 fModified |= scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
4090 continue;
4091
4092 case kKmkToken_ifdef:
4093 case kKmkToken_ifndef:
4094 case kKmkToken_if:
4095 fModified |= scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
4096 continue;
4097
4098 case kKmkToken_else:
4099 fModified |= scmKmkHandleElse(&Parser, offLine);
4100 continue;
4101
4102 case kKmkToken_endif:
4103 fModified |= scmKmkHandleEndif(&Parser, offLine);
4104 continue;
4105
4106 /* Includes: */
4107 case kKmkToken_include:
4108 case kKmkToken_sinclude:
4109 case kKmkToken_dash_include:
4110 case kKmkToken_includedep:
4111 case kKmkToken_includedep_queue:
4112 case kKmkToken_includedep_flush:
4113 fModified |= scmKmkHandleSimple(&Parser, offLine);
4114 continue;
4115
4116 /* Others: */
4117 case kKmkToken_define:
4118 fModified |= scmKmkHandleDefine(&Parser, offLine);
4119 continue;
4120 case kKmkToken_endef:
4121 fModified |= scmKmkHandleEndef(&Parser, offLine);
4122 continue;
4123
4124 case kKmkToken_override:
4125 case kKmkToken_local:
4126 fModified |= scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
4127 continue;
4128
4129 case kKmkToken_export:
4130 fModified |= scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
4131 continue;
4132
4133 case kKmkToken_unexport:
4134 case kKmkToken_undefine:
4135 fModified |= scmKmkHandleSimple(&Parser, offLine);
4136 break;
4137
4138 case kKmkToken_Comment:
4139 break;
4140
4141 /*
4142 * Check if it's perhaps an variable assignment or start of a rule.
4143 * We'll do this in a very simple fashion.
4144 */
4145 case kKmkToken_Word:
4146 {
4147 Parser.cLines = 1;
4148 Parser.cchTotalLine = cchLine;
4149 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
4150 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
4151 fModified |= scmKmkHandleAssignmentOrRule(&Parser, offLine);
4152 continue;
4153 }
4154 }
4155 }
4156 }
4157
4158 /*
4159 * Pass it thru as-is with line continuation.
4160 */
4161 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
4162 {
4163 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
4164 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
4165 if (!pchLine)
4166 break;
4167 cchLine = Parser.cchLine;
4168 }
4169 if (pchLine)
4170 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
4171 }
4172
4173 return fModified;
4174}
4175
4176
4177
4178/*********************************************************************************************************************************
4179* Flower Box Section Markers *
4180*********************************************************************************************************************************/
4181
4182static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
4183 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
4184{
4185 *ppchText = NULL;
4186 *pcchText = 0;
4187 *pfNeedFixing = false;
4188
4189 /*
4190 * The first line.
4191 */
4192 if (pchLine[0] != '/')
4193 return false;
4194 size_t offLine = 1;
4195 while (offLine < cchLine && pchLine[offLine] == '*')
4196 offLine++;
4197 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
4198 return false;
4199 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4200 offLine++;
4201 if (offLine != cchLine)
4202 return false;
4203
4204 size_t const cchBox = cchLine;
4205 *pfNeedFixing = cchBox != cchWidth;
4206
4207 /*
4208 * The next line, extracting the text.
4209 */
4210 SCMEOL enmEol;
4211 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
4212 if (cchLine < cchBox - 3)
4213 return false;
4214
4215 offLine = 0;
4216 if (RT_C_IS_BLANK(pchLine[0]))
4217 {
4218 *pfNeedFixing = true;
4219 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
4220 }
4221
4222 if (pchLine[offLine] != '*')
4223 return false;
4224 offLine++;
4225
4226 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
4227 return false;
4228 offLine++;
4229
4230 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4231 offLine++;
4232 if (offLine >= cchLine)
4233 return false;
4234 if (!RT_C_IS_UPPER(pchLine[offLine]))
4235 return false;
4236
4237 if (offLine != 4 || cchLine != cchBox)
4238 *pfNeedFixing = true;
4239
4240 *ppchText = &pchLine[offLine];
4241 size_t const offText = offLine;
4242
4243 /* From the end now. */
4244 offLine = cchLine - 1;
4245 while (RT_C_IS_BLANK(pchLine[offLine]))
4246 offLine--;
4247
4248 if (pchLine[offLine] != '*')
4249 return false;
4250 offLine--;
4251 if (!RT_C_IS_BLANK(pchLine[offLine]))
4252 return false;
4253 offLine--;
4254 while (RT_C_IS_BLANK(pchLine[offLine]))
4255 offLine--;
4256 *pcchText = offLine - offText + 1;
4257
4258 /*
4259 * Third line closes the box.
4260 */
4261 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
4262 if (cchLine < cchBox - 3)
4263 return false;
4264
4265 offLine = 0;
4266 if (RT_C_IS_BLANK(pchLine[0]))
4267 {
4268 *pfNeedFixing = true;
4269 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
4270 }
4271 while (offLine < cchLine && pchLine[offLine] == '*')
4272 offLine++;
4273 if (offLine < cchBox - 4)
4274 return false;
4275
4276 if (pchLine[offLine] != '/')
4277 return false;
4278 offLine++;
4279
4280 if (offLine != cchBox)
4281 *pfNeedFixing = true;
4282
4283 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4284 offLine++;
4285 if (offLine != cchLine)
4286 return false;
4287
4288 return true;
4289}
4290
4291
4292/**
4293 * Flower box marker comments in C and C++ code.
4294 *
4295 * @returns true if modifications were made, false if not.
4296 * @param pIn The input stream.
4297 * @param pOut The output stream.
4298 * @param pSettings The settings.
4299 */
4300bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4301{
4302 if (!pSettings->fFixFlowerBoxMarkers)
4303 return false;
4304
4305 /*
4306 * Work thru the file line by line looking for flower box markers.
4307 */
4308 size_t cChanges = 0;
4309 size_t cBlankLines = 0;
4310 SCMEOL enmEol;
4311 size_t cchLine;
4312 const char *pchLine;
4313 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4314 {
4315 /*
4316 * Get a likely match for a first line.
4317 */
4318 if ( pchLine[0] == '/'
4319 && cchLine > 20
4320 && pchLine[1] == '*'
4321 && pchLine[2] == '*'
4322 && pchLine[3] == '*')
4323 {
4324 size_t const offSaved = ScmStreamTell(pIn);
4325 char const *pchText;
4326 size_t cchText;
4327 bool fNeedFixing;
4328 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
4329 &pchText, &cchText, &fNeedFixing);
4330 if ( fIsFlowerBoxSection
4331 && ( fNeedFixing
4332 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
4333 {
4334 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
4335 {
4336 ScmStreamPutEol(pOut, enmEol);
4337 cBlankLines++;
4338 }
4339
4340 ScmStreamPutCh(pOut, '/');
4341 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
4342 ScmStreamPutEol(pOut, enmEol);
4343
4344 static const char s_szLead[] = "* ";
4345 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
4346 ScmStreamWrite(pOut, pchText, cchText);
4347 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
4348 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
4349 ScmStreamPutCh(pOut, '*');
4350 ScmStreamPutEol(pOut, enmEol);
4351
4352 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
4353 ScmStreamPutCh(pOut, '/');
4354 ScmStreamPutEol(pOut, enmEol);
4355
4356 cChanges++;
4357 cBlankLines = 0;
4358 continue;
4359 }
4360
4361 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
4362 if (RT_FAILURE(rc))
4363 return false;
4364 }
4365
4366 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4367 if (RT_FAILURE(rc))
4368 return false;
4369
4370 /* Do blank line accounting so we can ensure at least two blank lines
4371 before each section marker. */
4372 if (!isBlankLine(pchLine, cchLine))
4373 cBlankLines = 0;
4374 else
4375 cBlankLines++;
4376 }
4377 if (cChanges > 0)
4378 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
4379 return cChanges != 0;
4380}
4381
4382
4383/**
4384 * Looks for the start of a todo comment.
4385 *
4386 * @returns Offset into the line of the comment start sequence.
4387 * @param pchLine The line to search.
4388 * @param cchLineBeforeTodo The length of the line before the todo.
4389 * @param pfSameLine Indicates whether it's refering to a statemtn on
4390 * the same line comment (true), or the next
4391 * statement (false).
4392 */
4393static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
4394{
4395 *pfSameLine = false;
4396
4397 /* Skip one '@' or '\\'. */
4398 char ch;
4399 if ( cchLineBeforeTodo > 2
4400 && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
4401 || ch == '\\' ) )
4402 cchLineBeforeTodo--;
4403
4404 /* Skip blanks. */
4405 while ( cchLineBeforeTodo > 2
4406 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
4407 cchLineBeforeTodo--;
4408
4409 /* Look for same line indicator. */
4410 if ( cchLineBeforeTodo > 0
4411 && pchLine[cchLineBeforeTodo - 1] == '<')
4412 {
4413 *pfSameLine = true;
4414 cchLineBeforeTodo--;
4415 }
4416
4417 /* Skip *s */
4418 while ( cchLineBeforeTodo > 1
4419 && pchLine[cchLineBeforeTodo - 1] == '*')
4420 cchLineBeforeTodo--;
4421
4422 /* Do we have a comment opening sequence. */
4423 if ( cchLineBeforeTodo > 0
4424 && pchLine[cchLineBeforeTodo - 1] == '/'
4425 && ( ( cchLineBeforeTodo >= 2
4426 && pchLine[cchLineBeforeTodo - 2] == '/')
4427 || pchLine[cchLineBeforeTodo] == '*'))
4428 {
4429 /* Skip slashes at the start. */
4430 while ( cchLineBeforeTodo > 0
4431 && pchLine[cchLineBeforeTodo - 1] == '/')
4432 cchLineBeforeTodo--;
4433
4434 return cchLineBeforeTodo;
4435 }
4436
4437 return ~(size_t)0;
4438}
4439
4440
4441/**
4442 * Looks for a TODO or todo in the given line.
4443 *
4444 * @returns Offset into the line of found, ~(size_t)0 if not.
4445 * @param pchLine The line to search.
4446 * @param cchLine The length of the line.
4447 */
4448static size_t findTodo(char const *pchLine, size_t cchLine)
4449{
4450 if (cchLine >= 4 + 2)
4451 {
4452 /* We don't search the first to chars because we need the start of a comment.
4453 Also, skip the last three chars since we need at least four for a match. */
4454 size_t const cchLineT = cchLine - 3;
4455 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
4456 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
4457 {
4458 for (size_t off = 2; off < cchLineT; off++)
4459 {
4460 char ch = pchLine[off];
4461 if ( ( ch != 't'
4462 && ch != 'T')
4463 || ( (ch = pchLine[off + 1]) != 'o'
4464 && ch != 'O')
4465 || ( (ch = pchLine[off + 2]) != 'd'
4466 && ch != 'D')
4467 || ( (ch = pchLine[off + 3]) != 'o'
4468 && ch != 'O')
4469 || ( off + 4 != cchLine
4470 && (ch = pchLine[off + 4]) != ' '
4471 && ch != '\t'
4472 && ch != ':' /** @todo */
4473 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
4474 ) )
4475 { /* not a hit - likely */ }
4476 else
4477 return off;
4478 }
4479 }
4480 }
4481 return ~(size_t)0;
4482}
4483
4484
4485/**
4486 * Doxygen todos in C and C++ code.
4487 *
4488 * @returns true if modifications were made, false if not.
4489 * @param pState The rewriter state.
4490 * @param pIn The input stream.
4491 * @param pOut The output stream.
4492 * @param pSettings The settings.
4493 */
4494bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4495{
4496 if (!pSettings->fFixTodos)
4497 return false;
4498
4499 /*
4500 * Work thru the file line by line looking for the start of todo comments.
4501 */
4502 size_t cChanges = 0;
4503 SCMEOL enmEol;
4504 size_t cchLine;
4505 const char *pchLine;
4506 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4507 {
4508 /*
4509 * Look for the word 'todo' in the line. We're currently only trying
4510 * to catch comments starting with the word todo and adjust the start of
4511 * the doxygen statement.
4512 */
4513 size_t offTodo = findTodo(pchLine, cchLine);
4514 if ( offTodo != ~(size_t)0
4515 && offTodo >= 2)
4516 {
4517 /* Work backwards to find the start of the comment. */
4518 bool fSameLine = false;
4519 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
4520 if (offCommentStart != ~(size_t)0)
4521 {
4522 char szNew[64];
4523 size_t cchNew = 0;
4524 szNew[cchNew++] = '/';
4525 szNew[cchNew++] = pchLine[offCommentStart + 1];
4526 szNew[cchNew++] = pchLine[offCommentStart + 1];
4527 if (fSameLine)
4528 szNew[cchNew++] = '<';
4529 szNew[cchNew++] = ' ';
4530 szNew[cchNew++] = '@';
4531 szNew[cchNew++] = 't';
4532 szNew[cchNew++] = 'o';
4533 szNew[cchNew++] = 'd';
4534 szNew[cchNew++] = 'o';
4535
4536 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
4537 but need to take into account that we might be at the end of the line before
4538 adding the space. */
4539 size_t offTodoAfter = offTodo + 4;
4540 if ( offTodoAfter < cchLine
4541 && pchLine[offTodoAfter] == ':')
4542 offTodoAfter++;
4543 if ( offTodoAfter < cchLine
4544 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
4545 offTodoAfter++;
4546 if (offTodoAfter < cchLine)
4547 szNew[cchNew++] = ' ';
4548
4549 /* Write it out. */
4550 ScmStreamWrite(pOut, pchLine, offCommentStart);
4551 ScmStreamWrite(pOut, szNew, cchNew);
4552 if (offTodoAfter < cchLine)
4553 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
4554 ScmStreamPutEol(pOut, enmEol);
4555
4556 /* Check whether we actually made any changes. */
4557 if ( cchNew != offTodoAfter - offCommentStart
4558 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
4559 cChanges++;
4560 continue;
4561 }
4562 }
4563
4564 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4565 if (RT_FAILURE(rc))
4566 return false;
4567 }
4568 if (cChanges > 0)
4569 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
4570 return cChanges != 0;
4571}
4572
4573
4574/**
4575 * Tries to parse a C/C++ preprocessor include directive.
4576 *
4577 * This is resonably forgiving and expects sane input.
4578 *
4579 * @retval kScmIncludeDir_Invalid if not a valid include directive.
4580 * @retval kScmIncludeDir_Quoted
4581 * @retval kScmIncludeDir_Bracketed
4582 * @retval kScmIncludeDir_Macro
4583 *
4584 * @param pState The rewriter state (for repording malformed
4585 * directives).
4586 * @param pchLine The line to try parse as an include statement.
4587 * @param cchLine The line length.
4588 * @param ppchFilename Where to return the pointer to the filename part.
4589 * @param pcchFilename Where to return the length of the filename.
4590 */
4591SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
4592 const char **ppchFilename, size_t *pcchFilename)
4593{
4594 /* Skip leading spaces: */
4595 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4596 cchLine--, pchLine++;
4597
4598 /* Check for '#': */
4599 if (cchLine > 0 && *pchLine == '#')
4600 {
4601 cchLine--;
4602 pchLine++;
4603
4604 /* Skip spaces after '#' (optional): */
4605 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4606 cchLine--, pchLine++;
4607
4608 /* Check for 'include': */
4609 static char const s_szInclude[] = "include";
4610 if ( cchLine >= sizeof(s_szInclude)
4611 && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0)
4612 {
4613 cchLine -= sizeof(s_szInclude) - 1;
4614 pchLine += sizeof(s_szInclude) - 1;
4615
4616 /* Skip spaces after 'include' word (optional): */
4617 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4618 cchLine--, pchLine++;
4619 if (cchLine > 0)
4620 {
4621 /* Quoted or bracketed? */
4622 char const chFirst = *pchLine;
4623 if (chFirst == '"' || chFirst == '<')
4624 {
4625 cchLine--;
4626 pchLine++;
4627 const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine);
4628 if (pchEnd)
4629 {
4630 if (ppchFilename)
4631 *ppchFilename = pchLine;
4632 if (pcchFilename)
4633 *pcchFilename = pchEnd - pchLine;
4634 return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed;
4635 }
4636 ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n",
4637 chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine);
4638 }
4639 /* C prepreprocessor macro? */
4640 else if (ScmIsCIdentifierLeadChar(chFirst))
4641 {
4642 size_t cchFilename = 1;
4643 while ( cchFilename < cchLine
4644 && ScmIsCIdentifierChar(pchLine[cchFilename]))
4645 cchFilename++;
4646 if (ppchFilename)
4647 *ppchFilename = pchLine;
4648 if (pcchFilename)
4649 *pcchFilename = cchFilename;
4650 return kScmIncludeDir_Macro;
4651 }
4652 else
4653 ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine);
4654 }
4655 else
4656 ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n");
4657 }
4658 }
4659
4660 if (ppchFilename)
4661 *ppchFilename = NULL;
4662 if (pcchFilename)
4663 *pcchFilename = 0;
4664 return kScmIncludeDir_Invalid;
4665}
4666
4667
4668/**
4669 * Fix err.h/errcore.h usage.
4670 *
4671 * @returns true if modifications were made, false if not.
4672 * @param pIn The input stream.
4673 * @param pOut The output stream.
4674 * @param pSettings The settings.
4675 */
4676bool rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4677{
4678 if (!pSettings->fFixErrH)
4679 return false;
4680
4681 static struct
4682 {
4683 const char *pszHeader;
4684 unsigned cchHeader;
4685 int iLevel;
4686 } const s_aHeaders[] =
4687 {
4688 { RT_STR_TUPLE("iprt/errcore.h"), 1 },
4689 { RT_STR_TUPLE("iprt/err.h"), 2 },
4690 { RT_STR_TUPLE("VBox/err.h"), 3 },
4691 };
4692 static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */
4693 {
4694 { RT_STR_TUPLE("VINF_SUCCESS") },
4695 { RT_STR_TUPLE("VERR_GENERAL_FAILURE") },
4696 { RT_STR_TUPLE("VERR_INVALID_PARAMETER") },
4697 { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") },
4698 { RT_STR_TUPLE("VERR_INVALID_MAGIC") },
4699 { RT_STR_TUPLE("VWRN_INVALID_MAGIC") },
4700 { RT_STR_TUPLE("VERR_INVALID_HANDLE") },
4701 { RT_STR_TUPLE("VWRN_INVALID_HANDLE") },
4702 { RT_STR_TUPLE("VERR_INVALID_POINTER") },
4703 { RT_STR_TUPLE("VERR_NO_MEMORY") },
4704 { RT_STR_TUPLE("VERR_PERMISSION_DENIED") },
4705 { RT_STR_TUPLE("VINF_PERMISSION_DENIED") },
4706 { RT_STR_TUPLE("VERR_VERSION_MISMATCH") },
4707 { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") },
4708 { RT_STR_TUPLE("VERR_INVALID_FLAGS") },
4709 { RT_STR_TUPLE("VERR_WRONG_ORDER") },
4710 { RT_STR_TUPLE("VERR_INVALID_FUNCTION") },
4711 { RT_STR_TUPLE("VERR_NOT_SUPPORTED") },
4712 { RT_STR_TUPLE("VINF_NOT_SUPPORTED") },
4713 { RT_STR_TUPLE("VERR_ACCESS_DENIED") },
4714 { RT_STR_TUPLE("VERR_INTERRUPTED") },
4715 { RT_STR_TUPLE("VINF_INTERRUPTED") },
4716 { RT_STR_TUPLE("VERR_TIMEOUT") },
4717 { RT_STR_TUPLE("VINF_TIMEOUT") },
4718 { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") },
4719 { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") },
4720 { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") },
4721 { RT_STR_TUPLE("VERR_TRY_AGAIN") },
4722 { RT_STR_TUPLE("VINF_TRY_AGAIN") },
4723 { RT_STR_TUPLE("VERR_PARSE_ERROR") },
4724 { RT_STR_TUPLE("VERR_OUT_OF_RANGE") },
4725 { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") },
4726 { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") },
4727 { RT_STR_TUPLE("VERR_CANCELLED") },
4728 { RT_STR_TUPLE("VERR_TRAILING_CHARS") },
4729 { RT_STR_TUPLE("VWRN_TRAILING_CHARS") },
4730 { RT_STR_TUPLE("VERR_TRAILING_SPACES") },
4731 { RT_STR_TUPLE("VWRN_TRAILING_SPACES") },
4732 { RT_STR_TUPLE("VERR_NOT_FOUND") },
4733 { RT_STR_TUPLE("VWRN_NOT_FOUND") },
4734 { RT_STR_TUPLE("VERR_INVALID_STATE") },
4735 { RT_STR_TUPLE("VWRN_INVALID_STATE") },
4736 { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") },
4737 { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") },
4738 { RT_STR_TUPLE("VERR_END_OF_STRING") },
4739 { RT_STR_TUPLE("VERR_CALLBACK_RETURN") },
4740 { RT_STR_TUPLE("VINF_CALLBACK_RETURN") },
4741 { RT_STR_TUPLE("VERR_DUPLICATE") },
4742 { RT_STR_TUPLE("VERR_MISSING") },
4743 { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") },
4744 { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") },
4745 { RT_STR_TUPLE("VERR_NOT_AVAILABLE") },
4746 { RT_STR_TUPLE("VERR_MISMATCH") },
4747 { RT_STR_TUPLE("VERR_WRONG_TYPE") },
4748 { RT_STR_TUPLE("VWRN_WRONG_TYPE") },
4749 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") },
4750 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") },
4751 { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") },
4752 { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") },
4753 { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") },
4754 { RT_STR_TUPLE("VERR_INTERNAL_ERROR") },
4755 { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") },
4756 { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") },
4757 };
4758
4759 /*
4760 * First pass: Scout #include err.h/errcore.h locations and usage.
4761 *
4762 * Note! This isn't entirely optimal since it's also parsing comments and
4763 * strings, not just code. However it does a decent job for now.
4764 */
4765 int iIncludeLevel = 0;
4766 int iUsageLevel = 0;
4767 uint32_t iLine = 0;
4768 SCMEOL enmEol;
4769 size_t cchLine;
4770 const char *pchLine;
4771 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4772 {
4773 iLine++;
4774 if (cchLine < 6)
4775 continue;
4776
4777 /*
4778 * Look for #includes.
4779 */
4780 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
4781 if ( pchHash
4782 && isSpanOfBlanks(pchLine, pchHash - pchLine))
4783 {
4784 const char *pchFilename;
4785 size_t cchFilename;
4786 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
4787 if ( enmIncDir == kScmIncludeDir_Bracketed
4788 || enmIncDir == kScmIncludeDir_Quoted)
4789 {
4790 unsigned i = RT_ELEMENTS(s_aHeaders);
4791 while (i-- > 0)
4792 if ( s_aHeaders[i].cchHeader == cchFilename
4793 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
4794 {
4795 if (iIncludeLevel < s_aHeaders[i].iLevel)
4796 iIncludeLevel = s_aHeaders[i].iLevel;
4797 break;
4798 }
4799
4800 /* Special hack for error info. */
4801 if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0)
4802 iUsageLevel = 4;
4803
4804 /* Special hack for code templates. */
4805 if ( cchFilename >= sizeof(".cpp.h")
4806 && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0)
4807 iUsageLevel = 4;
4808 continue;
4809 }
4810 }
4811 /*
4812 * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line.
4813 */
4814 const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine);
4815 if (pchHit)
4816 {
4817 const char *pchLeft = pchLine;
4818 size_t cchLeft = cchLine;
4819 do
4820 {
4821 size_t cchLeftHit = &pchLeft[cchLeft] - pchHit;
4822 if (cchLeftHit < 6)
4823 break;
4824 if ( pchHit[4] == '_'
4825 && ( pchHit == pchLine
4826 || !ScmIsCIdentifierChar(pchHit[-1]))
4827 && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R')
4828 || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N')
4829 || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) )
4830 {
4831 size_t cchIdentifier = 5;
4832 while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier]))
4833 cchIdentifier++;
4834 ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n",
4835 iLine, pchHit - pchLine, cchIdentifier, pchHit);
4836
4837 if (iUsageLevel <= 1)
4838 {
4839 iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */
4840 for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++)
4841 if ( cchIdentifier == g_aLevel1Statuses[i].cch
4842 && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0)
4843 {
4844 iUsageLevel = 1;
4845 break;
4846 }
4847 }
4848
4849 pchLeft = pchHit + cchIdentifier;
4850 cchLeft = cchLeftHit - cchIdentifier;
4851 }
4852 else
4853 {
4854 pchLeft = pchHit + 1;
4855 cchLeft = cchLeftHit - 1;
4856 }
4857 pchHit = (const char *)memchr(pchLeft, 'V', cchLeft);
4858 } while (pchHit != NULL);
4859 }
4860 }
4861 ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel);
4862
4863 /*
4864 * Second pass: Change err.h to errcore.h if we detected a need for change.
4865 */
4866 if ( iIncludeLevel <= iUsageLevel
4867 || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */)
4868 return false;
4869
4870 unsigned cChanges = 0;
4871 ScmStreamRewindForReading(pIn);
4872 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4873 {
4874 /*
4875 * Look for #includes to modify.
4876 */
4877 if (cchLine >= 6)
4878 {
4879 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
4880 if ( pchHash
4881 && isSpanOfBlanks(pchLine, pchHash - pchLine))
4882 {
4883 const char *pchFilename;
4884 size_t cchFilename;
4885 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
4886 if ( enmIncDir == kScmIncludeDir_Bracketed
4887 || enmIncDir == kScmIncludeDir_Quoted)
4888 {
4889 unsigned i = RT_ELEMENTS(s_aHeaders);
4890 while (i-- > 0)
4891 if ( s_aHeaders[i].cchHeader == cchFilename
4892 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
4893 {
4894 ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1);
4895 ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>"));
4896 size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1];
4897 if (cchTrailing > 0)
4898 ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing);
4899 ScmStreamPutEol(pOut, enmEol);
4900 cChanges++;
4901 pchLine = NULL;
4902 break;
4903 }
4904 if (!pchLine)
4905 continue;
4906 }
4907 }
4908 }
4909
4910 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4911 if (RT_FAILURE(rc))
4912 return false;
4913 }
4914 ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges);
4915 return true;
4916}
4917
4918typedef struct
4919{
4920 const char *pch;
4921 uint8_t cch;
4922 uint8_t cchSpaces; /**< Number of expected spaces before the word. */
4923 bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */
4924 bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
4925} SCMMATCHWORD;
4926
4927
4928int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords,
4929 size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo)
4930{
4931 int rc = VINF_SUCCESS;
4932
4933 size_t offLine = 0;
4934 for (size_t i = 0; i < cWords; i++)
4935 {
4936 SCMMATCHWORD const *pWord = &paWords[i];
4937
4938 /*
4939 * Deal with spaces preceeding the word first:
4940 */
4941 if (pWord->fSpacesBefore)
4942 {
4943 size_t cchSpaces = 0;
4944 size_t cchTabs = 0;
4945 while (offLine < cchLine)
4946 {
4947 const char ch = pchLine[offLine];
4948 if (ch == ' ')
4949 cchSpaces++;
4950 else if (ch == '\t')
4951 cchTabs++;
4952 else
4953 break;
4954 offLine++;
4955 }
4956
4957 if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
4958 { /* likely */ }
4959 else if (cchSpaces == 0 && cchTabs == 0)
4960 return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
4961 else
4962 rc = VWRN_TRAILING_SPACES;
4963 }
4964 else
4965 Assert(pWord->cchSpaces == 0);
4966
4967 /*
4968 * C/C++ identifier?
4969 */
4970 if (pWord->fIdentifier)
4971 {
4972 if (offLine >= cchLine)
4973 return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
4974 "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
4975 pWord->cch, pWord->pch, offLine);
4976 if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
4977 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
4978 pWord->cch, pWord->pch, offLine);
4979 size_t const offStart = offLine++;
4980 while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
4981 offLine++;
4982 if (paIdentifiers)
4983 {
4984 paIdentifiers->cch = offLine - offStart;
4985 paIdentifiers->psz = &pchLine[offStart];
4986 paIdentifiers++;
4987 }
4988 }
4989 /*
4990 * Match the exact word.
4991 */
4992 else if ( pWord->cch == 0
4993 || ( pWord->cch <= cchLine - offLine
4994 && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
4995 offLine += pWord->cch;
4996 else
4997 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
4998 }
4999
5000 /*
5001 * Check for trailing characters/whatnot.
5002 */
5003 if (poffNext)
5004 *poffNext = offLine;
5005 else if (offLine != cchLine)
5006 rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
5007 return rc;
5008}
5009
5010
5011/**
5012 * Fix header file include guards and \#pragma once.
5013 *
5014 * @returns true if modifications were made, false if not.
5015 * @param pIn The input stream.
5016 * @param pOut The output stream.
5017 * @param pSettings The settings.
5018 */
5019bool rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5020{
5021 if (!pSettings->fFixHeaderGuards)
5022 return false;
5023
5024 /* always skip .cpp.h files */
5025 size_t cchFilename = strlen(pState->pszFilename);
5026 if ( cchFilename > sizeof(".cpp.h")
5027 && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0)
5028 return false;
5029
5030 RTERRINFOSTATIC ErrInfo;
5031 char szNormalized[168];
5032 size_t cchNormalized = 0;
5033 int rc;
5034 bool fRet = false;
5035
5036 /*
5037 * Calculate the expected guard for this file, if so tasked.
5038 * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir.
5039 */
5040 szNormalized[0] = '\0';
5041 if (pSettings->pszGuardRelativeToDir)
5042 {
5043 rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix);
5044 if (RT_FAILURE(rc))
5045 return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix);
5046 cchNormalized = strlen(szNormalized);
5047 if (strcmp(pSettings->pszGuardRelativeToDir, "{dir}") == 0)
5048 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
5049 RTPathFilename(pState->pszFilename));
5050 else if (strcmp(pSettings->pszGuardRelativeToDir, "{parent}") == 0)
5051 {
5052 const char *pszSrc = RTPathFilename(pState->pszFilename);
5053 if (!pszSrc || (uintptr_t)&pszSrc[-2] < (uintptr_t)pState->pszFilename || !RTPATH_IS_SLASH(pszSrc[-1]))
5054 return ScmError(pState, VERR_INTERNAL_ERROR, "Error calculating {parent} header guard!\n");
5055 pszSrc -= 2;
5056 while ( (uintptr_t)pszSrc > (uintptr_t)pState->pszFilename
5057 && !RTPATH_IS_SLASH(pszSrc[-1])
5058 && !RTPATH_IS_VOLSEP(pszSrc[-1]))
5059 pszSrc--;
5060 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);
5061 }
5062 else
5063 rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
5064 pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename);
5065 if (RT_FAILURE(rc))
5066 return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
5067 char ch;
5068 while ((ch = szNormalized[cchNormalized]) != '\0')
5069 {
5070 if (!ScmIsCIdentifierChar(ch))
5071 szNormalized[cchNormalized] = '_';
5072 cchNormalized++;
5073 }
5074 }
5075
5076 /*
5077 * First part looks for the #ifndef xxxx paired with #define xxxx.
5078 *
5079 * We blindly assume the first preprocessor directive in the file is the guard
5080 * and will be upset if this isn't the case.
5081 */
5082 RTSTRTUPLE Guard = { NULL, 0 };
5083 uint32_t cBlankLines = 0;
5084 SCMEOL enmEol;
5085 size_t cchLine;
5086 const char *pchLine;
5087 for (;;)
5088 {
5089 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5090 if (pchLine == NULL)
5091 return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
5092 if (cchLine >= 2)
5093 {
5094 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
5095 if ( pchHash
5096 && isSpanOfBlanks(pchLine, pchHash - pchLine))
5097 {
5098 /* #ifndef xxxx */
5099 static const SCMMATCHWORD s_aIfndefGuard[] =
5100 {
5101 { RT_STR_TUPLE("#"), 0, true, false },
5102 { RT_STR_TUPLE("ifndef"), 0, true, false },
5103 { RT_STR_TUPLE("IDENTIFIER"), 1, true, true },
5104 { RT_STR_TUPLE(""), 0, true, false },
5105 };
5106 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
5107 NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
5108 if (RT_FAILURE(rc))
5109 return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
5110 ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
5111 fRet |= rc != VINF_SUCCESS;
5112 ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
5113 ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
5114
5115 /* #define xxxx */
5116 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5117 if (!pchLine)
5118 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
5119 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
5120 const SCMMATCHWORD aDefineGuard[] =
5121 {
5122 { RT_STR_TUPLE("#"), 0, true, false },
5123 { RT_STR_TUPLE("define"), 0, true, false },
5124 { Guard.psz, (uint8_t)Guard.cch, 1, true, false },
5125 { RT_STR_TUPLE(""), 0, true, false },
5126 };
5127 rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
5128 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5129 if (RT_FAILURE(rc))
5130 return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
5131 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
5132 ErrInfo.Core.pszMsg, cchLine, pchLine);
5133 fRet |= rc != VINF_SUCCESS;
5134
5135 if (Guard.cch >= sizeof(szNormalized))
5136 return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
5137 ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
5138
5139 if (szNormalized[0] != '\0')
5140 {
5141 if ( Guard.cch != cchNormalized
5142 || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
5143 {
5144 ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
5145 ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
5146 Guard.cch, Guard.psz, pState->pszFilename);
5147 fRet = true;
5148 }
5149 Guard.psz = szNormalized;
5150 Guard.cch = cchNormalized;
5151 }
5152
5153 /*
5154 * Write guard, making sure we've got a single blank line preceeding it.
5155 */
5156 ScmStreamPutEol(pOut, enmEol);
5157 ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
5158 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
5159 ScmStreamPutEol(pOut, enmEol);
5160 ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
5161 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
5162 rc = ScmStreamPutEol(pOut, enmEol);
5163 if (RT_FAILURE(rc))
5164 return false;
5165 break;
5166 }
5167 }
5168
5169 if (!isBlankLine(pchLine, cchLine))
5170 {
5171 while (cBlankLines-- > 0)
5172 ScmStreamPutEol(pOut, enmEol);
5173 cBlankLines = 0;
5174 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5175 if (RT_FAILURE(rc))
5176 return false;
5177 }
5178 else
5179 cBlankLines++;
5180 }
5181
5182 /*
5183 * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
5184 */
5185 size_t const iPragmaOnce = ScmStreamTellLine(pIn);
5186 static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
5187 {
5188 { RT_STR_TUPLE("#"), 0, true, false },
5189 { RT_STR_TUPLE("ifndef"), 0, true, false },
5190 { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false },
5191 { RT_STR_TUPLE(""), 0, true, false },
5192 };
5193 static const SCMMATCHWORD s_aPragmaOnce[] =
5194 {
5195 { RT_STR_TUPLE("#"), 0, true, false },
5196 { RT_STR_TUPLE("pragma"), 1, true, false },
5197 { RT_STR_TUPLE("once"), 1, true, false},
5198 { RT_STR_TUPLE(""), 0, true, false },
5199 };
5200 static const SCMMATCHWORD s_aEndif[] =
5201 {
5202 { RT_STR_TUPLE("#"), 0, true, false },
5203 { RT_STR_TUPLE("endif"), 0, true, false },
5204 { RT_STR_TUPLE(""), 0, true, false },
5205 };
5206
5207 /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
5208 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5209 if (!pchLine)
5210 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
5211 size_t offNext;
5212 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
5213 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5214 if (RT_SUCCESS(rc))
5215 {
5216 fRet |= rc != VINF_SUCCESS;
5217 if (offNext != cchLine)
5218 return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
5219 iPragmaOnce + 1, cchLine, pchLine);
5220
5221 /* # pragma once */
5222 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5223 if (!pchLine)
5224 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
5225 iPragmaOnce + 2);
5226 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
5227 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5228 if (RT_SUCCESS(rc))
5229 fRet |= rc != VINF_SUCCESS;
5230 else
5231 return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
5232 iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
5233
5234 /* #endif */
5235 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5236 if (!pchLine)
5237 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
5238 iPragmaOnce + 3);
5239 rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
5240 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5241 if (RT_SUCCESS(rc))
5242 fRet |= rc != VINF_SUCCESS;
5243 else
5244 return ScmError(pState, rc,
5245 "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
5246 iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
5247 ScmVerbose(pState, 3, "Found pragma once\n");
5248 fRet |= !pSettings->fPragmaOnce;
5249 }
5250 else
5251 {
5252 rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
5253 if (RT_FAILURE(rc))
5254 return ScmError(pState, rc, "seek error\n");
5255 fRet |= pSettings->fPragmaOnce;
5256 ScmVerbose(pState, pSettings->fPragmaOnce ? 2 : 3, "Missing #pragma once\n");
5257 }
5258
5259 /*
5260 * Write the pragma once stuff.
5261 */
5262 if (pSettings->fPragmaOnce)
5263 {
5264 ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
5265 ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
5266 rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
5267 if (RT_FAILURE(rc))
5268 return false;
5269 }
5270
5271 /*
5272 * Copy the rest of the file and remove pragma once statements, while
5273 * looking for the last #endif in the file.
5274 */
5275 size_t iEndIfIn = 0;
5276 size_t iEndIfOut = 0;
5277 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5278 {
5279 if (cchLine > 2)
5280 {
5281 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
5282 if ( pchHash
5283 && isSpanOfBlanks(pchLine, pchHash - pchLine))
5284 {
5285 size_t off = pchHash - pchLine + 1;
5286 while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
5287 off++;
5288 /* #pragma once */
5289 if ( off + sizeof("pragma") - 1 <= cchLine
5290 && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
5291 {
5292 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
5293 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5294 if (RT_SUCCESS(rc))
5295 {
5296 fRet = true;
5297 continue;
5298 }
5299 }
5300 /* #endif */
5301 else if ( off + sizeof("endif") - 1 <= cchLine
5302 && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
5303 {
5304 iEndIfIn = ScmStreamTellLine(pIn) - 1;
5305 iEndIfOut = ScmStreamTellLine(pOut);
5306 }
5307 }
5308 }
5309
5310 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5311 if (RT_FAILURE(rc))
5312 return false;
5313 }
5314
5315 /*
5316 * Check out the last endif, making sure it's well formed and make sure it has the
5317 * right kind of comment following it.
5318 */
5319 if (pSettings->fFixHeaderGuardEndif)
5320 {
5321 if (iEndIfOut == 0)
5322 return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
5323 rc = ScmStreamSeekByLine(pIn, iEndIfIn);
5324 if (RT_FAILURE(rc))
5325 return false;
5326 rc = ScmStreamSeekByLine(pOut, iEndIfOut);
5327 if (RT_FAILURE(rc))
5328 return false;
5329
5330 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5331 if (!pchLine)
5332 return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
5333
5334 char szTmp[64 + sizeof(szNormalized)];
5335 size_t cchTmp;
5336 if (pSettings->fEndifGuardComment)
5337 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
5338 else
5339 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
5340 fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
5341 rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
5342 if (RT_FAILURE(rc))
5343 return false;
5344
5345 /* Copy out the remaining lines (assumes no #pragma once here). */
5346 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5347 {
5348 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5349 if (RT_FAILURE(rc))
5350 return false;
5351 }
5352 }
5353
5354 return fRet;
5355}
5356
5357
5358/**
5359 * Checks for PAGE_SIZE, PAGE_SHIFT and PAGE_OFFSET_MASK w/o a GUEST_ or HOST_
5360 * prefix as well as banning PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK and
5361 * PAGE_BASE_MASK.
5362 *
5363 * @returns true if modifications were made, false if not.
5364 * @param pIn The input stream.
5365 * @param pOut The output stream.
5366 * @param pSettings The settings.
5367 */
5368bool rewrite_PageChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5369{
5370 RT_NOREF(pOut);
5371 if (!pSettings->fOnlyGuestHostPage && !pSettings->fNoASMMemPageUse)
5372 return false;
5373
5374 static RTSTRTUPLE const g_aWords[] =
5375 {
5376 { RT_STR_TUPLE("PAGE_SIZE") },
5377 { RT_STR_TUPLE("PAGE_SHIFT") },
5378 { RT_STR_TUPLE("PAGE_OFFSET_MASK") },
5379 { RT_STR_TUPLE("PAGE_BASE_MASK") },
5380 { RT_STR_TUPLE("PAGE_BASE_GC_MASK") },
5381 { RT_STR_TUPLE("PAGE_BASE_HC_MASK") },
5382 { RT_STR_TUPLE("PAGE_ADDRESS") },
5383 { RT_STR_TUPLE("PHYS_PAGE_ADDRESS") },
5384 { RT_STR_TUPLE("ASMMemIsZeroPage") },
5385 { RT_STR_TUPLE("ASMMemZeroPage") },
5386 };
5387 size_t const iFirstWord = pSettings->fOnlyGuestHostPage ? 0 : 7;
5388 size_t const iEndWords = pSettings->fNoASMMemPageUse ? 9 : 7;
5389
5390 uint32_t iLine = 0;
5391 SCMEOL enmEol;
5392 size_t cchLine;
5393 const char *pchLine;
5394 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5395 {
5396 iLine++;
5397 for (size_t i = iFirstWord; i < iEndWords; i++)
5398 {
5399 size_t const cchWord = g_aWords[i].cch;
5400 if (cchLine >= cchWord)
5401 {
5402 const char * const pszWord = g_aWords[i].psz;
5403 const char *pchHit = (const char *)memchr(pchLine, *pszWord, cchLine);
5404 while (pchHit)
5405 {
5406 size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
5407 if ( cchLeft >= cchWord
5408 && memcmp(pchHit, pszWord, cchWord) == 0
5409 && ( pchHit == pchLine
5410 || !ScmIsCIdentifierChar(pchHit[-1]))
5411 && ( cchLeft == cchWord
5412 || !ScmIsCIdentifierChar(pchHit[cchWord])) )
5413 {
5414 if (i < 3)
5415 ScmFixManually(pState, "%u:%zu: %s is not allow! Use GUEST_%s or HOST_%s instead.\n",
5416 iLine, pchHit - pchLine + 1, pszWord, pszWord, pszWord);
5417 else if (i < 7)
5418 ScmFixManually(pState, "%u:%zu: %s is not allow! Rewrite using GUEST/HOST_PAGE_OFFSET_MASK.\n",
5419 iLine, pchHit - pchLine + 1, pszWord);
5420 else
5421 ScmFixManually(pState, "%u:%zu: %s is not allow! Use %s with correct page size instead.\n",
5422 iLine, pchHit - pchLine + 1, pszWord, i == 3 ? "ASMMemIsZero" : "RT_BZERO");
5423 }
5424
5425 /* next */
5426 cchLeft -= 1;
5427 if (cchLeft < cchWord)
5428 break;
5429 pchHit = (const char *)memchr(pchHit + 1, *pszWord, cchLeft);
5430 }
5431 }
5432 }
5433 }
5434
5435 return false;
5436}
5437
5438
5439/**
5440 * Checks for usage of rc in code instead of vrc for IPRT status codes (int) and hrc for COM
5441 * status codes (HRESULT).
5442 *
5443 * @returns true if modifications were made, false if not.
5444 * @param pIn The input stream.
5445 * @param pOut The output stream.
5446 * @param pSettings The settings.
5447 *
5448 * @note Used in Main to avoid ambiguity when just using rc.
5449 */
5450bool rewrite_ForceHrcVrcInsteadOfRc(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5451{
5452 RT_NOREF(pOut);
5453 if (!pSettings->fOnlyHrcVrcInsteadOfRc)
5454 return false;
5455
5456 static const SCMMATCHWORD s_aHresultVrc[] =
5457 {
5458 { RT_STR_TUPLE("HRESULT"), 0, true, false },
5459 { RT_STR_TUPLE("vrc"), 1, true, false }
5460 };
5461
5462 static const SCMMATCHWORD s_aIntHrc[] =
5463 {
5464 { RT_STR_TUPLE("int"), 0, true, false },
5465 { RT_STR_TUPLE("hrc"), 1, true, false }
5466 };
5467
5468 uint32_t iLine = 0;
5469 SCMEOL enmEol;
5470 size_t cchLine;
5471 const char *pchLine;
5472 RTERRINFOSTATIC ErrInfo;
5473 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5474 {
5475 iLine++;
5476
5477 /* Look for forbidden declarations first. */
5478 size_t offNext = 0;
5479 int rc = ScmMatchWords(pchLine, cchLine, s_aHresultVrc, RT_ELEMENTS(s_aHresultVrc),
5480 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5481 if (RT_SUCCESS(rc))
5482 {
5483 ScmFixManually(pState, "%u:%zu: 'HRESULT vrc' is not allowed! Use 'HRESULT hrc' instead.\n",
5484 iLine, offNext);
5485 continue;
5486 }
5487
5488 rc = ScmMatchWords(pchLine, cchLine, s_aIntHrc, RT_ELEMENTS(s_aIntHrc),
5489 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5490 if (RT_SUCCESS(rc))
5491 {
5492 ScmFixManually(pState, "%u:%zu: 'int hrc' is not allowed! Use 'int vrc' instead.\n",
5493 iLine, offNext);
5494 continue;
5495 }
5496
5497#if 0 /* This is too broad and triggers on things we don't want to trigger on (like autoCaller.rc()). */
5498 const RTSTRTUPLE RcTuple = { RT_STR_TUPLE("rc") };
5499 size_t const cchWord = RcTuple.cch;
5500 if (cchLine >= cchWord)
5501 {
5502 const char *pchHit = (const char *)memchr(pchLine, *RcTuple.psz, cchLine);
5503 while (pchHit)
5504 {
5505 size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
5506 if ( cchLeft >= cchWord
5507 && memcmp(pchHit, RcTuple.psz, cchWord) == 0
5508 && ( pchHit == pchLine
5509 || !ScmIsCIdentifierChar(pchHit[-1]))
5510 && ( cchLeft == cchWord
5511 || !ScmIsCIdentifierChar(pchHit[cchWord])) )
5512 ScmFixManually(pState, "%u:%zu: %s is not allowed! Use hrc or vrc instead.\n",
5513 iLine, pchHit - pchLine + 1, RcTuple.psz);
5514
5515 /* next */
5516 cchLeft -= 1;
5517 if (cchLeft < cchWord)
5518 break;
5519 pchHit = (const char *)memchr(pchHit + 1, *RcTuple.psz, cchLeft);
5520 }
5521 }
5522#else
5523 /* Trigger on declarations of 'HRESULT rc' and 'int rc'. */
5524 static const SCMMATCHWORD s_aHresultRc[] =
5525 {
5526 { RT_STR_TUPLE("HRESULT"), 0, true, false },
5527 { RT_STR_TUPLE("rc"), 1, true, false }
5528 };
5529
5530 static const SCMMATCHWORD s_aIntRc[] =
5531 {
5532 { RT_STR_TUPLE("int"), 0, true, false },
5533 { RT_STR_TUPLE("rc"), 1, true, false }
5534 };
5535
5536 rc = ScmMatchWords(pchLine, cchLine, s_aHresultRc, RT_ELEMENTS(s_aHresultRc),
5537 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5538 if (RT_SUCCESS(rc))
5539 {
5540 ScmFixManually(pState, "%u:%zu: 'HRESULT rc' is not allowed! Use 'HRESULT hrc' instead.\n",
5541 iLine, offNext);
5542 continue;
5543 }
5544
5545 rc = ScmMatchWords(pchLine, cchLine, s_aIntRc, RT_ELEMENTS(s_aIntRc),
5546 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5547 if (RT_SUCCESS(rc))
5548 {
5549 ScmFixManually(pState, "%u:%zu: 'int rc' is not allowed! Use 'int vrc' instead.\n",
5550 iLine, offNext);
5551 continue;
5552 }
5553#endif
5554 }
5555
5556 return false;
5557}
5558
5559
5560/**
5561 * Rewrite a C/C++ source or header file.
5562 *
5563 * @returns true if modifications were made, false if not.
5564 * @param pIn The input stream.
5565 * @param pOut The output stream.
5566 * @param pSettings The settings.
5567 *
5568 * @todo
5569 *
5570 * Ideas for C/C++:
5571 * - space after if, while, for, switch
5572 * - spaces in for (i=0;i<x;i++)
5573 * - complex conditional, bird style.
5574 * - remove unnecessary parentheses.
5575 * - sort defined RT_OS_*|| and RT_ARCH
5576 * - sizeof without parenthesis.
5577 * - defined without parenthesis.
5578 * - trailing spaces.
5579 * - parameter indentation.
5580 * - space after comma.
5581 * - while (x--); -> multi line + comment.
5582 * - else statement;
5583 * - space between function and left parenthesis.
5584 * - TODO, XXX, @todo cleanup.
5585 * - Space before/after '*'.
5586 * - ensure new line at end of file.
5587 * - Indentation of precompiler statements (#ifdef, #defines).
5588 * - space between functions.
5589 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
5590 */
5591bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5592{
5593
5594 RT_NOREF4(pState, pIn, pOut, pSettings);
5595 return false;
5596}
5597
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