VirtualBox

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

Last change on this file since 98366 was 98366, checked in by vboxsync, 2 years ago

scm: More on the kmk makefile cleanup. [build fix] 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 98366 2023-01-31 15:10:29Z 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.215389.xyz.
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.215389.xyz.\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.215389.xyz. 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.215389.xyz.\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.215389.xyz. 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.215389.xyz.\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.215389.xyz. 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:
3345 AssertReleaseFailed();
3346 RT_FALL_THRU();
3347 case kKmkAssignType_Recursive:
3348 *pszDst++ = '=';
3349 Assert(pchLine[offLine] == '=');
3350 offLine++;
3351 break;
3352 case kKmkAssignType_Conditional:
3353 *pszDst++ = '?';
3354 *pszDst++ = '=';
3355 Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '=');
3356 offLine += 2;
3357 break;
3358 case kKmkAssignType_Appending:
3359 *pszDst++ = '+';
3360 *pszDst++ = '=';
3361 Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '=');
3362 offLine += 2;
3363 break;
3364 case kKmkAssignType_Prepending:
3365 *pszDst++ = '>';
3366 *pszDst++ = '=';
3367 Assert(pchLine[offLine] == '>'); Assert(pchLine[offLine + 1] == '=');
3368 offLine += 2;
3369 break;
3370 case kKmkAssignType_Immediate:
3371 *pszDst++ = ':';
3372 Assert(pchLine[offLine] == ':');
3373 offLine++;
3374 RT_FALL_THRU();
3375 case kKmkAssignType_Simple:
3376 *pszDst++ = ':';
3377 *pszDst++ = '=';
3378 Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '=');
3379 offLine += 2;
3380 break;
3381 }
3382
3383 /*
3384 * Skip space till we hit the value or comment.
3385 */
3386 size_t cchSpaces = 0;
3387 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3388 cchSpaces++, offLine++;
3389
3390/** @todo this block can probably be merged into the final loop below. */
3391 unsigned cPendingEols = 0;
3392 bool fModified = false;
3393 unsigned const iSubLineStart1 = iSubLine;
3394 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3395 {
3396 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3397 AssertReturn(pchLine, fModified);
3398 cchLine = pParser->cchLine;
3399 iSubLine++;
3400 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3401 {
3402 *pszDst++ = ' ';
3403 *pszDst++ = '\\';
3404 *pszDst = '\0';
3405 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3406 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3407 }
3408 cPendingEols = 1;
3409
3410 /* Deal with indent/whitespace. */
3411 offLine = 0;
3412 if ( memcmp(pchLine, g_szSpaces, cchIndent) == 0
3413 && pchLine[cchIndent] == '\t')
3414 offLine = cchIndent + 1;
3415 cchSpaces = 0;
3416 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3417 cchSpaces++, offLine++;
3418 fModified |= cchSpaces != 0 && pchLine[offLine] != '#';
3419 }
3420 fModified |= iSubLine > iSubLineStart1 + 1;
3421
3422 /*
3423 * Okay, we've gotten to the value / comment part.
3424 */
3425 for (;;)
3426 {
3427 /*
3428 * The end? Flush what we've got.
3429 */
3430 if (offLine == cchLine)
3431 {
3432 Assert(iSubLine + 1 == cLines);
3433 *pszDst = '\0';
3434 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3435 return fModified || cPendingEols > 0;
3436 }
3437
3438 /*
3439 * Output any non-comment stuff, stripping off newlines.
3440 */
3441 const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
3442 if (pchHash != &pchLine[offLine])
3443 {
3444 /* Add space or flush pending EOLs. */
3445 if (!cPendingEols)
3446 *pszDst++ = ' ';
3447 else
3448 {
3449 fModified |= cPendingEols > 2;
3450 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
3451 do
3452 {
3453 *pszDst++ = ' ';
3454 *pszDst++ = '\\';
3455 *pszDst = '\0';
3456 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3457
3458 pszDst = pParser->szBuf;
3459 memset(pszDst, ' ', cchIndent);
3460 pszDst += cchIndent;
3461 *pszDst++ = '\t';
3462 cPendingEols--;
3463 } while (cPendingEols > 0);
3464 }
3465
3466 /* Strip backwards. */
3467 size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
3468 size_t offValueEnd = offValueEnd2;
3469 while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
3470 offValueEnd--;
3471 Assert(offValueEnd > offLine);
3472
3473 fModified |= !pchHash && offValueEnd != cchLine - (iSubLine + 1 < cLines ? 2 : 0);
3474
3475 /* Append the value part we found. */
3476 pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
3477 offLine = offValueEnd2;
3478 }
3479
3480 /*
3481 * If we found a comment hash, emit it and whatever follows just as-is w/o
3482 * any particular reformatting. Comments within a variable definition are
3483 * usually to disable portitions of a property like _DEFS or _SOURCES.
3484 */
3485 if (pchHash != NULL)
3486 {
3487 if (cPendingEols == 0)
3488 scmKmkTailComment(pParser, pchLine, cchLine, offLine - cchSpaces, &pszDst);
3489 size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
3490 *pszDst = '\0';
3491 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
3492 fModified |= cPendingEols > 0
3493 || cchLine != cchDst
3494 || memcmp(pParser->szBuf, pchLine, cchLine) != 0;
3495
3496 if (cPendingEols > 1)
3497 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
3498
3499 if (cPendingEols > 0)
3500 ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
3501 scmKmkPassThruLineContinuationLines(pParser);
3502 return fModified;
3503 }
3504
3505 /*
3506 * Fetch another line, if we've got one.
3507 */
3508 if (iSubLine + 1 >= cLines)
3509 Assert(offLine == cchLine);
3510 else
3511 {
3512 Assert(offLine + 1 == cchLine);
3513 unsigned const iSubLineStart2 = iSubLine;
3514 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3515 {
3516 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3517 AssertReturn(pchLine, fModified);
3518 cchLine = pParser->cchLine;
3519 iSubLine++;
3520 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3521 {
3522 *pszDst++ = ' ';
3523 *pszDst++ = '\\';
3524 *pszDst = '\0';
3525 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3526 if (cPendingEols > 1)
3527 ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
3528 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3529 }
3530 cPendingEols++;
3531
3532 /* Deal with indent/whitespace. */
3533 offLine = 0;
3534 if ( memcmp(pchLine, g_szSpaces, cchIndent) == 0
3535 && pchLine[cchIndent] == '\t')
3536 offLine = cchIndent + 1;
3537 cchSpaces = 0;
3538 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3539 cchSpaces++, offLine++;
3540 fModified |= cchSpaces != 0 && pchLine[offLine] != '#';
3541 }
3542 fModified |= iSubLine > iSubLineStart2 + 1;
3543 }
3544 }
3545}
3546
3547
3548/**
3549 * A rule.
3550 *
3551 * This is a bit involved. Sigh.
3552 */
3553static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon)
3554{
3555 SCMSTREAM *pOut = pParser->pOut;
3556 unsigned const cchIndent = pParser->iActualDepth;
3557 const char *pchLine = pParser->pchLine;
3558 size_t cchLine = pParser->cchLine;
3559 Assert(offFirstWord < cchLine);
3560 uint32_t const cLines = pParser->cLines;
3561 uint32_t iSubLine = 0;
3562
3563 /* Following this, we'll be in recipe-mode. */
3564 pParser->fInRecipe = true;
3565
3566 /* This is too much hazzle to deal with. */
3567 if (cLines > 0 && pchLine[cchLine - 2] == '\\')
3568 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3569
3570 /* Too special case. */
3571 if (offColon <= offFirstWord)
3572 return scmKmkGiveUp(pParser, "Missing target file before colon!");
3573
3574 /*
3575 * Indent it.
3576 */
3577 bool fModified = offFirstWord != cchIndent
3578 || memcmp(pchLine, g_szSpaces, cchIndent) != 0;
3579 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3580 size_t offLine = offFirstWord;
3581
3582 /*
3583 * Process word by word past the colon, taking new lines into account.
3584 *
3585 */
3586 KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment;
3587 bool fPendingEol = false;
3588 for (;;)
3589 {
3590 /*
3591 * Output the next word.
3592 */
3593 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx);
3594 Assert(offLine + cchWord <= offColon);
3595 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
3596 offLine += cchWord;
3597
3598 /* Skip whitespace (if any). */
3599 size_t cchSpaces = 0;
3600 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3601 {
3602 fModified |= pchLine[offLine] != ' ';
3603 cchSpaces++;
3604 offLine++;
3605 }
3606
3607 /* Have we reached the colon already? */
3608 if (offLine >= offColon)
3609 {
3610 fModified |= cchSpaces != 0;
3611
3612 Assert(pchLine[offLine] == ':');
3613 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
3614 offLine += fDoubleColon ? 2 : 1;
3615
3616 ScmStreamPutCh(pOut, ':');
3617 if (fDoubleColon)
3618 ScmStreamPutCh(pOut, ':');
3619 break;
3620 }
3621
3622 /* Deal with new line and emit indentation. */
3623 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3624 {
3625 fModified |= cchSpaces > 1;
3626
3627 /* Get the next input line. */
3628 for (;;)
3629 {
3630 const char * const pchPrevLine = pchLine;
3631 Assert(iSubLine + 1 < cLines);
3632 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3633 AssertReturn(pchLine, fModified);
3634 cchLine = pParser->cchLine;
3635 iSubLine++;
3636 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3637 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3638
3639 /* Adjust offColon: */
3640 offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
3641 Assert(offColon < ~(size_t)0 / 2);
3642
3643 /* Skip leading spaces. */
3644 offLine = 0;
3645 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3646 {
3647 fModified |= pchLine[offLine] != ' ';
3648 offLine++;
3649 }
3650 fModified |= offLine == cchIndent
3651 || memcmp(pchLine, g_szSpaces, cchIndent) != 0;
3652
3653 /* Just drop empty lines. */
3654 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3655 {
3656 fModified = true;
3657 continue;
3658 }
3659
3660 /* Complete the current line and emit indent, unless we reached the colon: */
3661 if (offLine >= offColon)
3662 {
3663 fModified = true;
3664 Assert(pchLine[offLine] == ':');
3665 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
3666 offLine += fDoubleColon ? 2 : 1;
3667
3668 ScmStreamPutCh(pOut, ':');
3669 if (fDoubleColon)
3670 ScmStreamPutCh(pOut, ':');
3671
3672 fPendingEol = true;
3673 break;
3674 }
3675 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
3676 ScmStreamPutEol(pOut, pParser->enmEol);
3677 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3678 }
3679 if (offLine >= offColon)
3680 break;
3681 }
3682 else
3683 {
3684 fModified |= cchSpaces != 1;
3685 ScmStreamPutCh(pOut, ' ');
3686 }
3687 enmCtx = kKmkWordCtx_TargetFile;
3688 }
3689
3690 /*
3691 * We're immediately past the colon now, so eat whitespace and newlines and
3692 * whatever till we get to a solid word.
3693 */
3694 /* Skip spaces - there should be exactly one. */
3695 fModified |= pchLine[offLine] != ' ';
3696 if (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3697 offLine++;
3698 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3699 {
3700 fModified = true;
3701 offLine++;
3702 }
3703
3704 /* Deal with new lines: */
3705 while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3706 {
3707 fPendingEol = true;
3708
3709 Assert(iSubLine + 1 < cLines);
3710 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3711 AssertReturn(pchLine, fModified);
3712 cchLine = pParser->cchLine;
3713 iSubLine++;
3714 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3715 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3716
3717 /* Skip leading spaces. */
3718 offLine = 0;
3719 if (memcmp(pchLine, g_szSpaces, cchIndent) == 0 && pchLine[cchIndent] == '\t' && pchLine[cchIndent + 1] == '\t')
3720 offLine += cchIndent + 2;
3721 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3722 {
3723 fModified = true;
3724 offLine++;
3725 }
3726
3727 /* Just drop empty lines. */
3728 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3729 {
3730 fModified = true;
3731 continue;
3732 }
3733 }
3734
3735 /*
3736 * Special case: No dependencies.
3737 */
3738 if (offLine == cchLine && iSubLine >= cLines)
3739 {
3740 ScmStreamPutEol(pOut, pParser->enmEol);
3741 return fModified;
3742 }
3743
3744 /*
3745 * Work the dependencies word for word. Indent in spaces + two tabs.
3746 * (Pattern rules will also end up here, but we'll just ignore that for now.)
3747 */
3748 /** @todo fModified isn't updated right here. */
3749 enmCtx = kKmkWordCtx_DepFileOrAssignment;
3750 for (;;)
3751 {
3752 /* Indent the next word. */
3753 if (!fPendingEol)
3754 ScmStreamPutCh(pOut, ' ');
3755 else
3756 {
3757 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
3758 ScmStreamPutEol(pOut, pParser->enmEol);
3759 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3760 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
3761 fPendingEol = false;
3762 }
3763
3764 /* Get the next word and output it. */
3765 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx);
3766 Assert(offLine + cchWord <= cchLine);
3767
3768 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
3769 offLine += cchWord;
3770
3771 /* Skip whitespace (if any). */
3772 size_t cchSpaces = 0;
3773 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3774 {
3775 fModified |= pchLine[offLine] != ' ';
3776 cchSpaces++;
3777 offLine++;
3778 }
3779
3780 /* Deal with new line and emit indentation. */
3781 if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3782 {
3783 fModified |= cchSpaces > 1;
3784
3785 /* Get the next input line. */
3786 unsigned cEmptyLines = 0;
3787 for (;;)
3788 {
3789 Assert(iSubLine + 1 < cLines);
3790 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3791 AssertReturn(pchLine, fModified);
3792 cchLine = pParser->cchLine;
3793 iSubLine++;
3794 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3795 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3796
3797 /* Skip leading spaces. */
3798 offLine = 0;
3799 if (memcmp(pchLine, g_szSpaces, cchIndent) == 0 && pchLine[cchIndent] == '\t' && pchLine[cchIndent + 1] == '\t')
3800 offLine += cchIndent + 2;
3801 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3802 {
3803 fModified = true;
3804 offLine++;
3805 }
3806
3807 /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
3808 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3809 {
3810 cEmptyLines++;
3811 continue;
3812 }
3813
3814 fPendingEol = true;
3815 break;
3816 }
3817 cchSpaces = 1;
3818 }
3819
3820 if (offLine < cchLine)
3821 fModified |= cchSpaces != 1;
3822 else
3823 {
3824 /* End of input. */
3825/** @todo deal with comments */
3826 Assert(iSubLine + 1 == cLines);
3827 ScmStreamPutEol(pOut, pParser->enmEol);
3828 return fModified;
3829 }
3830 enmCtx = kKmkWordCtx_DepFile;
3831 }
3832}
3833
3834
3835/**
3836 * Checks if the (extended) line is a variable assignment.
3837 *
3838 * We scan past line continuation stuff here as the assignment operator could be
3839 * on the next line, even if that's very unlikely it is recommened by the coding
3840 * guide lines if the line needs to be split. Fortunately, though, the caller
3841 * already removes empty empty leading lines, so we only have to consider the
3842 * line continuation issue if no '=' was found on the first line.
3843 *
3844 * @returns Modified or not.
3845 * @param pParser The parser.
3846 * @param cLines Number of lines to consider.
3847 * @param cchTotalLine Total length of all the lines to consider.
3848 * @param offWord Where the first word of the line starts.
3849 * @param pfIsAssignment Where to return whether this is an assignment or
3850 * not.
3851 */
3852static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord)
3853{
3854 const char *pchLine = pParser->pchLine;
3855 size_t const cchTotalLine = pParser->cchTotalLine;
3856
3857 /*
3858 * Scan words till we find ':' or '='.
3859 */
3860 uint32_t iWord = 0;
3861 size_t offCurWord = offWord;
3862 size_t offEndPrev = 0;
3863 size_t offLine = offWord;
3864 while (offLine < cchTotalLine)
3865 {
3866 char ch = pchLine[offLine++];
3867 if (ch == '$')
3868 {
3869 /*
3870 * Skip variable expansion.
3871 */
3872 char const chOpen = pchLine[offLine++];
3873 if (chOpen == '(' || chOpen == '{')
3874 {
3875 char const chClose = chOpen == '(' ? ')' : '}';
3876 unsigned cDepth = 1;
3877 while (offLine < cchTotalLine)
3878 {
3879 ch = pchLine[offLine++];
3880 if (ch == chOpen)
3881 cDepth++;
3882 else if (ch == chClose)
3883 if (!--cDepth)
3884 break;
3885 }
3886 }
3887 /* else: $x or $$, so just skip the next character. */
3888 }
3889 else if (RT_C_IS_SPACE(ch))
3890 {
3891 /*
3892 * End of word. Skip whitespace till the next word starts.
3893 */
3894 offEndPrev = offLine - 1;
3895 Assert(offLine != offWord);
3896 while (offLine < cchTotalLine)
3897 {
3898 ch = pchLine[offLine];
3899 if (RT_C_IS_SPACE(ch))
3900 offLine++;
3901 else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n'))
3902 offLine += 2;
3903 else
3904 break;
3905 }
3906 offCurWord = offLine;
3907 iWord++;
3908
3909 /*
3910 * To simplify the assignment operator checks, we just check the
3911 * start of the 2nd word when we're here.
3912 */
3913 if (iWord == 1 && offLine < cchTotalLine)
3914 {
3915 ch = pchLine[offLine];
3916 if (ch == '=')
3917 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
3918 if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
3919 {
3920 if (ch == ':')
3921 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0);
3922 if (ch == '+')
3923 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0);
3924 if (ch == '>')
3925 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0);
3926 if (ch == '?')
3927 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
3928 }
3929 else if ( ch == ':'
3930 && pchLine[offLine + 1] == ':'
3931 && pchLine[offLine + 2] == '=')
3932 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
3933
3934 /* Check for rule while we're here. */
3935 if (ch == ':')
3936 return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
3937 }
3938 }
3939 /*
3940 * If '=' is found in the first word it's an assignment.
3941 */
3942 else if (ch == '=')
3943 {
3944 if (iWord == 0)
3945 {
3946 KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
3947 ch = pchLine[offLine - 2];
3948 if (ch == '+')
3949 enmType = kKmkAssignType_Appending;
3950 else if (ch == '?')
3951 enmType = kKmkAssignType_Conditional;
3952 else if (ch == '>')
3953 enmType = kKmkAssignType_Prepending;
3954 else
3955 Assert(ch != ':');
3956 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
3957 }
3958 }
3959 /*
3960 * When ':' is found it can mean a drive letter, a rule or in the
3961 * first word a simple or immediate assignment.
3962 */
3963 else if (ch == ':')
3964 {
3965 /* Check for drive letters (we ignore the archive form): */
3966 if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
3967 { /* ignore */ }
3968 else
3969 {
3970 /* Simple or immediate assignment? */
3971 ch = pchLine[offLine];
3972 if (iWord == 0)
3973 {
3974 if (ch == '=')
3975 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
3976 if (ch == ':' && pchLine[offLine + 1] == '=')
3977 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
3978 }
3979
3980 /* Okay, it's a rule then. */
3981 return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
3982 }
3983 }
3984 }
3985
3986 /*
3987 * If we didn't find anything, output it as-as.
3988 * We use scmKmkHandleSimple in a special way to do this.
3989 */
3990 ScmVerbose(pParser->pState, 1, "debug: %u: Unable to make sense of this line!", pParser->iLine);
3991 return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
3992}
3993
3994
3995static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord,
3996 bool fMustBeAssignment)
3997{
3998 /* Assignments takes us out of recipe mode. */
3999 pParser->fInRecipe = false;
4000
4001 RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment);
4002 return scmKmkHandleSimple(pParser, offToken);
4003}
4004
4005
4006/**
4007 * Rewrite a kBuild makefile.
4008 *
4009 * @returns true if modifications were made, false if not.
4010 * @param pIn The input stream.
4011 * @param pOut The output stream.
4012 * @param pSettings The settings.
4013 *
4014 * @todo
4015 *
4016 * Ideas for Makefile.kmk and Config.kmk:
4017 * - sort if1of/ifn1of sets.
4018 * - line continuation slashes should only be preceded by one space.
4019 */
4020bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4021{
4022 if (!pSettings->fStandarizeKmk)
4023 return false;
4024
4025 /*
4026 * Parser state.
4027 */
4028 KMKPARSER Parser;
4029 Parser.iDepth = 0;
4030 Parser.iActualDepth = 0;
4031 Parser.fInRecipe = false;
4032 Parser.iLine = 0;
4033 Parser.pState = pState;
4034 Parser.pIn = pIn;
4035 Parser.pOut = pOut;
4036 Parser.pSettings = pSettings;
4037
4038 /*
4039 * Iterate the file.
4040 */
4041 bool fModified = false;
4042 const char *pchLine;
4043 while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL)
4044 {
4045 size_t cchLine = Parser.cchLine;
4046 Parser.iLine++;
4047
4048 /*
4049 * If we're in the command part of a recipe, anything starting with a
4050 * tab is considered another command for the recipe.
4051 */
4052 if (Parser.fInRecipe && *pchLine == '\t')
4053 {
4054 /* Do we do anything here? */
4055 }
4056 else
4057 {
4058 /*
4059 * Skip leading whitespace and check for directives (simplified).
4060 *
4061 * This is simplified in the sense that GNU make first checks for variable
4062 * assignments, so that directive can be used as variable names. We don't
4063 * want that, so we do the variable assignment check later.
4064 */
4065 size_t offLine = 0;
4066 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4067 offLine++;
4068
4069 /* Find end of word (if any): */
4070 size_t cchWord = 0;
4071 while ( offLine + cchWord < cchLine
4072 && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord])
4073 || pchLine[offLine + cchWord] == '-'))
4074 cchWord++;
4075 if (cchWord > 0)
4076 {
4077 /* If the line is just a line continuation slash, simply remove it
4078 (this also makes the parsing a lot easier). */
4079 if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
4080 continue;
4081
4082 /* Unlike the GNU make parser, we won't recognize 'if' or any other
4083 directives as variable names, so we can */
4084 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
4085 switch (enmToken)
4086 {
4087 case kKmkToken_ifeq:
4088 case kKmkToken_ifneq:
4089 case kKmkToken_if1of:
4090 case kKmkToken_ifn1of:
4091 fModified |= scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
4092 continue;
4093
4094 case kKmkToken_ifdef:
4095 case kKmkToken_ifndef:
4096 case kKmkToken_if:
4097 fModified |= scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
4098 continue;
4099
4100 case kKmkToken_else:
4101 fModified |= scmKmkHandleElse(&Parser, offLine);
4102 continue;
4103
4104 case kKmkToken_endif:
4105 fModified |= scmKmkHandleEndif(&Parser, offLine);
4106 continue;
4107
4108 /* Includes: */
4109 case kKmkToken_include:
4110 case kKmkToken_sinclude:
4111 case kKmkToken_dash_include:
4112 case kKmkToken_includedep:
4113 case kKmkToken_includedep_queue:
4114 case kKmkToken_includedep_flush:
4115 fModified |= scmKmkHandleSimple(&Parser, offLine);
4116 continue;
4117
4118 /* Others: */
4119 case kKmkToken_define:
4120 fModified |= scmKmkHandleDefine(&Parser, offLine);
4121 continue;
4122 case kKmkToken_endef:
4123 fModified |= scmKmkHandleEndef(&Parser, offLine);
4124 continue;
4125
4126 case kKmkToken_override:
4127 case kKmkToken_local:
4128 fModified |= scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
4129 continue;
4130
4131 case kKmkToken_export:
4132 fModified |= scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
4133 continue;
4134
4135 case kKmkToken_unexport:
4136 case kKmkToken_undefine:
4137 fModified |= scmKmkHandleSimple(&Parser, offLine);
4138 break;
4139
4140 case kKmkToken_Comment:
4141 break;
4142
4143 /*
4144 * Check if it's perhaps an variable assignment or start of a rule.
4145 * We'll do this in a very simple fashion.
4146 */
4147 case kKmkToken_Word:
4148 {
4149 Parser.cLines = 1;
4150 Parser.cchTotalLine = cchLine;
4151 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
4152 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
4153 fModified |= scmKmkHandleAssignmentOrRule(&Parser, offLine);
4154 continue;
4155 }
4156 }
4157 }
4158 }
4159
4160 /*
4161 * Pass it thru as-is with line continuation.
4162 */
4163 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
4164 {
4165 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
4166 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
4167 if (!pchLine)
4168 break;
4169 cchLine = Parser.cchLine;
4170 }
4171 if (pchLine)
4172 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
4173 }
4174
4175 return fModified;
4176}
4177
4178
4179
4180/*********************************************************************************************************************************
4181* Flower Box Section Markers *
4182*********************************************************************************************************************************/
4183
4184static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
4185 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
4186{
4187 *ppchText = NULL;
4188 *pcchText = 0;
4189 *pfNeedFixing = false;
4190
4191 /*
4192 * The first line.
4193 */
4194 if (pchLine[0] != '/')
4195 return false;
4196 size_t offLine = 1;
4197 while (offLine < cchLine && pchLine[offLine] == '*')
4198 offLine++;
4199 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
4200 return false;
4201 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4202 offLine++;
4203 if (offLine != cchLine)
4204 return false;
4205
4206 size_t const cchBox = cchLine;
4207 *pfNeedFixing = cchBox != cchWidth;
4208
4209 /*
4210 * The next line, extracting the text.
4211 */
4212 SCMEOL enmEol;
4213 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
4214 if (cchLine < cchBox - 3)
4215 return false;
4216
4217 offLine = 0;
4218 if (RT_C_IS_BLANK(pchLine[0]))
4219 {
4220 *pfNeedFixing = true;
4221 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
4222 }
4223
4224 if (pchLine[offLine] != '*')
4225 return false;
4226 offLine++;
4227
4228 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
4229 return false;
4230 offLine++;
4231
4232 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4233 offLine++;
4234 if (offLine >= cchLine)
4235 return false;
4236 if (!RT_C_IS_UPPER(pchLine[offLine]))
4237 return false;
4238
4239 if (offLine != 4 || cchLine != cchBox)
4240 *pfNeedFixing = true;
4241
4242 *ppchText = &pchLine[offLine];
4243 size_t const offText = offLine;
4244
4245 /* From the end now. */
4246 offLine = cchLine - 1;
4247 while (RT_C_IS_BLANK(pchLine[offLine]))
4248 offLine--;
4249
4250 if (pchLine[offLine] != '*')
4251 return false;
4252 offLine--;
4253 if (!RT_C_IS_BLANK(pchLine[offLine]))
4254 return false;
4255 offLine--;
4256 while (RT_C_IS_BLANK(pchLine[offLine]))
4257 offLine--;
4258 *pcchText = offLine - offText + 1;
4259
4260 /*
4261 * Third line closes the box.
4262 */
4263 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
4264 if (cchLine < cchBox - 3)
4265 return false;
4266
4267 offLine = 0;
4268 if (RT_C_IS_BLANK(pchLine[0]))
4269 {
4270 *pfNeedFixing = true;
4271 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
4272 }
4273 while (offLine < cchLine && pchLine[offLine] == '*')
4274 offLine++;
4275 if (offLine < cchBox - 4)
4276 return false;
4277
4278 if (pchLine[offLine] != '/')
4279 return false;
4280 offLine++;
4281
4282 if (offLine != cchBox)
4283 *pfNeedFixing = true;
4284
4285 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4286 offLine++;
4287 if (offLine != cchLine)
4288 return false;
4289
4290 return true;
4291}
4292
4293
4294/**
4295 * Flower box marker comments in C and C++ code.
4296 *
4297 * @returns true if modifications were made, false if not.
4298 * @param pIn The input stream.
4299 * @param pOut The output stream.
4300 * @param pSettings The settings.
4301 */
4302bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4303{
4304 if (!pSettings->fFixFlowerBoxMarkers)
4305 return false;
4306
4307 /*
4308 * Work thru the file line by line looking for flower box markers.
4309 */
4310 size_t cChanges = 0;
4311 size_t cBlankLines = 0;
4312 SCMEOL enmEol;
4313 size_t cchLine;
4314 const char *pchLine;
4315 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4316 {
4317 /*
4318 * Get a likely match for a first line.
4319 */
4320 if ( pchLine[0] == '/'
4321 && cchLine > 20
4322 && pchLine[1] == '*'
4323 && pchLine[2] == '*'
4324 && pchLine[3] == '*')
4325 {
4326 size_t const offSaved = ScmStreamTell(pIn);
4327 char const *pchText;
4328 size_t cchText;
4329 bool fNeedFixing;
4330 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
4331 &pchText, &cchText, &fNeedFixing);
4332 if ( fIsFlowerBoxSection
4333 && ( fNeedFixing
4334 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
4335 {
4336 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
4337 {
4338 ScmStreamPutEol(pOut, enmEol);
4339 cBlankLines++;
4340 }
4341
4342 ScmStreamPutCh(pOut, '/');
4343 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
4344 ScmStreamPutEol(pOut, enmEol);
4345
4346 static const char s_szLead[] = "* ";
4347 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
4348 ScmStreamWrite(pOut, pchText, cchText);
4349 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
4350 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
4351 ScmStreamPutCh(pOut, '*');
4352 ScmStreamPutEol(pOut, enmEol);
4353
4354 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
4355 ScmStreamPutCh(pOut, '/');
4356 ScmStreamPutEol(pOut, enmEol);
4357
4358 cChanges++;
4359 cBlankLines = 0;
4360 continue;
4361 }
4362
4363 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
4364 if (RT_FAILURE(rc))
4365 return false;
4366 }
4367
4368 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4369 if (RT_FAILURE(rc))
4370 return false;
4371
4372 /* Do blank line accounting so we can ensure at least two blank lines
4373 before each section marker. */
4374 if (!isBlankLine(pchLine, cchLine))
4375 cBlankLines = 0;
4376 else
4377 cBlankLines++;
4378 }
4379 if (cChanges > 0)
4380 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
4381 return cChanges != 0;
4382}
4383
4384
4385/**
4386 * Looks for the start of a todo comment.
4387 *
4388 * @returns Offset into the line of the comment start sequence.
4389 * @param pchLine The line to search.
4390 * @param cchLineBeforeTodo The length of the line before the todo.
4391 * @param pfSameLine Indicates whether it's refering to a statemtn on
4392 * the same line comment (true), or the next
4393 * statement (false).
4394 */
4395static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
4396{
4397 *pfSameLine = false;
4398
4399 /* Skip one '@' or '\\'. */
4400 char ch;
4401 if ( cchLineBeforeTodo > 2
4402 && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
4403 || ch == '\\' ) )
4404 cchLineBeforeTodo--;
4405
4406 /* Skip blanks. */
4407 while ( cchLineBeforeTodo > 2
4408 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
4409 cchLineBeforeTodo--;
4410
4411 /* Look for same line indicator. */
4412 if ( cchLineBeforeTodo > 0
4413 && pchLine[cchLineBeforeTodo - 1] == '<')
4414 {
4415 *pfSameLine = true;
4416 cchLineBeforeTodo--;
4417 }
4418
4419 /* Skip *s */
4420 while ( cchLineBeforeTodo > 1
4421 && pchLine[cchLineBeforeTodo - 1] == '*')
4422 cchLineBeforeTodo--;
4423
4424 /* Do we have a comment opening sequence. */
4425 if ( cchLineBeforeTodo > 0
4426 && pchLine[cchLineBeforeTodo - 1] == '/'
4427 && ( ( cchLineBeforeTodo >= 2
4428 && pchLine[cchLineBeforeTodo - 2] == '/')
4429 || pchLine[cchLineBeforeTodo] == '*'))
4430 {
4431 /* Skip slashes at the start. */
4432 while ( cchLineBeforeTodo > 0
4433 && pchLine[cchLineBeforeTodo - 1] == '/')
4434 cchLineBeforeTodo--;
4435
4436 return cchLineBeforeTodo;
4437 }
4438
4439 return ~(size_t)0;
4440}
4441
4442
4443/**
4444 * Looks for a TODO or todo in the given line.
4445 *
4446 * @returns Offset into the line of found, ~(size_t)0 if not.
4447 * @param pchLine The line to search.
4448 * @param cchLine The length of the line.
4449 */
4450static size_t findTodo(char const *pchLine, size_t cchLine)
4451{
4452 if (cchLine >= 4 + 2)
4453 {
4454 /* We don't search the first to chars because we need the start of a comment.
4455 Also, skip the last three chars since we need at least four for a match. */
4456 size_t const cchLineT = cchLine - 3;
4457 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
4458 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
4459 {
4460 for (size_t off = 2; off < cchLineT; off++)
4461 {
4462 char ch = pchLine[off];
4463 if ( ( ch != 't'
4464 && ch != 'T')
4465 || ( (ch = pchLine[off + 1]) != 'o'
4466 && ch != 'O')
4467 || ( (ch = pchLine[off + 2]) != 'd'
4468 && ch != 'D')
4469 || ( (ch = pchLine[off + 3]) != 'o'
4470 && ch != 'O')
4471 || ( off + 4 != cchLine
4472 && (ch = pchLine[off + 4]) != ' '
4473 && ch != '\t'
4474 && ch != ':' /** @todo */
4475 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
4476 ) )
4477 { /* not a hit - likely */ }
4478 else
4479 return off;
4480 }
4481 }
4482 }
4483 return ~(size_t)0;
4484}
4485
4486
4487/**
4488 * Doxygen todos in C and C++ code.
4489 *
4490 * @returns true if modifications were made, false if not.
4491 * @param pState The rewriter state.
4492 * @param pIn The input stream.
4493 * @param pOut The output stream.
4494 * @param pSettings The settings.
4495 */
4496bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4497{
4498 if (!pSettings->fFixTodos)
4499 return false;
4500
4501 /*
4502 * Work thru the file line by line looking for the start of todo comments.
4503 */
4504 size_t cChanges = 0;
4505 SCMEOL enmEol;
4506 size_t cchLine;
4507 const char *pchLine;
4508 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4509 {
4510 /*
4511 * Look for the word 'todo' in the line. We're currently only trying
4512 * to catch comments starting with the word todo and adjust the start of
4513 * the doxygen statement.
4514 */
4515 size_t offTodo = findTodo(pchLine, cchLine);
4516 if ( offTodo != ~(size_t)0
4517 && offTodo >= 2)
4518 {
4519 /* Work backwards to find the start of the comment. */
4520 bool fSameLine = false;
4521 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
4522 if (offCommentStart != ~(size_t)0)
4523 {
4524 char szNew[64];
4525 size_t cchNew = 0;
4526 szNew[cchNew++] = '/';
4527 szNew[cchNew++] = pchLine[offCommentStart + 1];
4528 szNew[cchNew++] = pchLine[offCommentStart + 1];
4529 if (fSameLine)
4530 szNew[cchNew++] = '<';
4531 szNew[cchNew++] = ' ';
4532 szNew[cchNew++] = '@';
4533 szNew[cchNew++] = 't';
4534 szNew[cchNew++] = 'o';
4535 szNew[cchNew++] = 'd';
4536 szNew[cchNew++] = 'o';
4537
4538 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
4539 but need to take into account that we might be at the end of the line before
4540 adding the space. */
4541 size_t offTodoAfter = offTodo + 4;
4542 if ( offTodoAfter < cchLine
4543 && pchLine[offTodoAfter] == ':')
4544 offTodoAfter++;
4545 if ( offTodoAfter < cchLine
4546 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
4547 offTodoAfter++;
4548 if (offTodoAfter < cchLine)
4549 szNew[cchNew++] = ' ';
4550
4551 /* Write it out. */
4552 ScmStreamWrite(pOut, pchLine, offCommentStart);
4553 ScmStreamWrite(pOut, szNew, cchNew);
4554 if (offTodoAfter < cchLine)
4555 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
4556 ScmStreamPutEol(pOut, enmEol);
4557
4558 /* Check whether we actually made any changes. */
4559 if ( cchNew != offTodoAfter - offCommentStart
4560 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
4561 cChanges++;
4562 continue;
4563 }
4564 }
4565
4566 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4567 if (RT_FAILURE(rc))
4568 return false;
4569 }
4570 if (cChanges > 0)
4571 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
4572 return cChanges != 0;
4573}
4574
4575
4576/**
4577 * Tries to parse a C/C++ preprocessor include directive.
4578 *
4579 * This is resonably forgiving and expects sane input.
4580 *
4581 * @retval kScmIncludeDir_Invalid if not a valid include directive.
4582 * @retval kScmIncludeDir_Quoted
4583 * @retval kScmIncludeDir_Bracketed
4584 * @retval kScmIncludeDir_Macro
4585 *
4586 * @param pState The rewriter state (for repording malformed
4587 * directives).
4588 * @param pchLine The line to try parse as an include statement.
4589 * @param cchLine The line length.
4590 * @param ppchFilename Where to return the pointer to the filename part.
4591 * @param pcchFilename Where to return the length of the filename.
4592 */
4593SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
4594 const char **ppchFilename, size_t *pcchFilename)
4595{
4596 /* Skip leading spaces: */
4597 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4598 cchLine--, pchLine++;
4599
4600 /* Check for '#': */
4601 if (cchLine > 0 && *pchLine == '#')
4602 {
4603 cchLine--;
4604 pchLine++;
4605
4606 /* Skip spaces after '#' (optional): */
4607 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4608 cchLine--, pchLine++;
4609
4610 /* Check for 'include': */
4611 static char const s_szInclude[] = "include";
4612 if ( cchLine >= sizeof(s_szInclude)
4613 && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0)
4614 {
4615 cchLine -= sizeof(s_szInclude) - 1;
4616 pchLine += sizeof(s_szInclude) - 1;
4617
4618 /* Skip spaces after 'include' word (optional): */
4619 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4620 cchLine--, pchLine++;
4621 if (cchLine > 0)
4622 {
4623 /* Quoted or bracketed? */
4624 char const chFirst = *pchLine;
4625 if (chFirst == '"' || chFirst == '<')
4626 {
4627 cchLine--;
4628 pchLine++;
4629 const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine);
4630 if (pchEnd)
4631 {
4632 if (ppchFilename)
4633 *ppchFilename = pchLine;
4634 if (pcchFilename)
4635 *pcchFilename = pchEnd - pchLine;
4636 return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed;
4637 }
4638 ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n",
4639 chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine);
4640 }
4641 /* C prepreprocessor macro? */
4642 else if (ScmIsCIdentifierLeadChar(chFirst))
4643 {
4644 size_t cchFilename = 1;
4645 while ( cchFilename < cchLine
4646 && ScmIsCIdentifierChar(pchLine[cchFilename]))
4647 cchFilename++;
4648 if (ppchFilename)
4649 *ppchFilename = pchLine;
4650 if (pcchFilename)
4651 *pcchFilename = cchFilename;
4652 return kScmIncludeDir_Macro;
4653 }
4654 else
4655 ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine);
4656 }
4657 else
4658 ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n");
4659 }
4660 }
4661
4662 if (ppchFilename)
4663 *ppchFilename = NULL;
4664 if (pcchFilename)
4665 *pcchFilename = 0;
4666 return kScmIncludeDir_Invalid;
4667}
4668
4669
4670/**
4671 * Fix err.h/errcore.h usage.
4672 *
4673 * @returns true if modifications were made, false if not.
4674 * @param pIn The input stream.
4675 * @param pOut The output stream.
4676 * @param pSettings The settings.
4677 */
4678bool rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4679{
4680 if (!pSettings->fFixErrH)
4681 return false;
4682
4683 static struct
4684 {
4685 const char *pszHeader;
4686 unsigned cchHeader;
4687 int iLevel;
4688 } const s_aHeaders[] =
4689 {
4690 { RT_STR_TUPLE("iprt/errcore.h"), 1 },
4691 { RT_STR_TUPLE("iprt/err.h"), 2 },
4692 { RT_STR_TUPLE("VBox/err.h"), 3 },
4693 };
4694 static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */
4695 {
4696 { RT_STR_TUPLE("VINF_SUCCESS") },
4697 { RT_STR_TUPLE("VERR_GENERAL_FAILURE") },
4698 { RT_STR_TUPLE("VERR_INVALID_PARAMETER") },
4699 { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") },
4700 { RT_STR_TUPLE("VERR_INVALID_MAGIC") },
4701 { RT_STR_TUPLE("VWRN_INVALID_MAGIC") },
4702 { RT_STR_TUPLE("VERR_INVALID_HANDLE") },
4703 { RT_STR_TUPLE("VWRN_INVALID_HANDLE") },
4704 { RT_STR_TUPLE("VERR_INVALID_POINTER") },
4705 { RT_STR_TUPLE("VERR_NO_MEMORY") },
4706 { RT_STR_TUPLE("VERR_PERMISSION_DENIED") },
4707 { RT_STR_TUPLE("VINF_PERMISSION_DENIED") },
4708 { RT_STR_TUPLE("VERR_VERSION_MISMATCH") },
4709 { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") },
4710 { RT_STR_TUPLE("VERR_INVALID_FLAGS") },
4711 { RT_STR_TUPLE("VERR_WRONG_ORDER") },
4712 { RT_STR_TUPLE("VERR_INVALID_FUNCTION") },
4713 { RT_STR_TUPLE("VERR_NOT_SUPPORTED") },
4714 { RT_STR_TUPLE("VINF_NOT_SUPPORTED") },
4715 { RT_STR_TUPLE("VERR_ACCESS_DENIED") },
4716 { RT_STR_TUPLE("VERR_INTERRUPTED") },
4717 { RT_STR_TUPLE("VINF_INTERRUPTED") },
4718 { RT_STR_TUPLE("VERR_TIMEOUT") },
4719 { RT_STR_TUPLE("VINF_TIMEOUT") },
4720 { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") },
4721 { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") },
4722 { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") },
4723 { RT_STR_TUPLE("VERR_TRY_AGAIN") },
4724 { RT_STR_TUPLE("VINF_TRY_AGAIN") },
4725 { RT_STR_TUPLE("VERR_PARSE_ERROR") },
4726 { RT_STR_TUPLE("VERR_OUT_OF_RANGE") },
4727 { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") },
4728 { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") },
4729 { RT_STR_TUPLE("VERR_CANCELLED") },
4730 { RT_STR_TUPLE("VERR_TRAILING_CHARS") },
4731 { RT_STR_TUPLE("VWRN_TRAILING_CHARS") },
4732 { RT_STR_TUPLE("VERR_TRAILING_SPACES") },
4733 { RT_STR_TUPLE("VWRN_TRAILING_SPACES") },
4734 { RT_STR_TUPLE("VERR_NOT_FOUND") },
4735 { RT_STR_TUPLE("VWRN_NOT_FOUND") },
4736 { RT_STR_TUPLE("VERR_INVALID_STATE") },
4737 { RT_STR_TUPLE("VWRN_INVALID_STATE") },
4738 { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") },
4739 { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") },
4740 { RT_STR_TUPLE("VERR_END_OF_STRING") },
4741 { RT_STR_TUPLE("VERR_CALLBACK_RETURN") },
4742 { RT_STR_TUPLE("VINF_CALLBACK_RETURN") },
4743 { RT_STR_TUPLE("VERR_DUPLICATE") },
4744 { RT_STR_TUPLE("VERR_MISSING") },
4745 { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") },
4746 { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") },
4747 { RT_STR_TUPLE("VERR_NOT_AVAILABLE") },
4748 { RT_STR_TUPLE("VERR_MISMATCH") },
4749 { RT_STR_TUPLE("VERR_WRONG_TYPE") },
4750 { RT_STR_TUPLE("VWRN_WRONG_TYPE") },
4751 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") },
4752 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") },
4753 { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") },
4754 { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") },
4755 { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") },
4756 { RT_STR_TUPLE("VERR_INTERNAL_ERROR") },
4757 { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") },
4758 { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") },
4759 };
4760
4761 /*
4762 * First pass: Scout #include err.h/errcore.h locations and usage.
4763 *
4764 * Note! This isn't entirely optimal since it's also parsing comments and
4765 * strings, not just code. However it does a decent job for now.
4766 */
4767 int iIncludeLevel = 0;
4768 int iUsageLevel = 0;
4769 uint32_t iLine = 0;
4770 SCMEOL enmEol;
4771 size_t cchLine;
4772 const char *pchLine;
4773 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4774 {
4775 iLine++;
4776 if (cchLine < 6)
4777 continue;
4778
4779 /*
4780 * Look for #includes.
4781 */
4782 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
4783 if ( pchHash
4784 && isSpanOfBlanks(pchLine, pchHash - pchLine))
4785 {
4786 const char *pchFilename;
4787 size_t cchFilename;
4788 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
4789 if ( enmIncDir == kScmIncludeDir_Bracketed
4790 || enmIncDir == kScmIncludeDir_Quoted)
4791 {
4792 unsigned i = RT_ELEMENTS(s_aHeaders);
4793 while (i-- > 0)
4794 if ( s_aHeaders[i].cchHeader == cchFilename
4795 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
4796 {
4797 if (iIncludeLevel < s_aHeaders[i].iLevel)
4798 iIncludeLevel = s_aHeaders[i].iLevel;
4799 break;
4800 }
4801
4802 /* Special hack for error info. */
4803 if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0)
4804 iUsageLevel = 4;
4805
4806 /* Special hack for code templates. */
4807 if ( cchFilename >= sizeof(".cpp.h")
4808 && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0)
4809 iUsageLevel = 4;
4810 continue;
4811 }
4812 }
4813 /*
4814 * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line.
4815 */
4816 const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine);
4817 if (pchHit)
4818 {
4819 const char *pchLeft = pchLine;
4820 size_t cchLeft = cchLine;
4821 do
4822 {
4823 size_t cchLeftHit = &pchLeft[cchLeft] - pchHit;
4824 if (cchLeftHit < 6)
4825 break;
4826 if ( pchHit[4] == '_'
4827 && ( pchHit == pchLine
4828 || !ScmIsCIdentifierChar(pchHit[-1]))
4829 && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R')
4830 || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N')
4831 || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) )
4832 {
4833 size_t cchIdentifier = 5;
4834 while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier]))
4835 cchIdentifier++;
4836 ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n",
4837 iLine, pchHit - pchLine, cchIdentifier, pchHit);
4838
4839 if (iUsageLevel <= 1)
4840 {
4841 iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */
4842 for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++)
4843 if ( cchIdentifier == g_aLevel1Statuses[i].cch
4844 && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0)
4845 {
4846 iUsageLevel = 1;
4847 break;
4848 }
4849 }
4850
4851 pchLeft = pchHit + cchIdentifier;
4852 cchLeft = cchLeftHit - cchIdentifier;
4853 }
4854 else
4855 {
4856 pchLeft = pchHit + 1;
4857 cchLeft = cchLeftHit - 1;
4858 }
4859 pchHit = (const char *)memchr(pchLeft, 'V', cchLeft);
4860 } while (pchHit != NULL);
4861 }
4862 }
4863 ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel);
4864
4865 /*
4866 * Second pass: Change err.h to errcore.h if we detected a need for change.
4867 */
4868 if ( iIncludeLevel <= iUsageLevel
4869 || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */)
4870 return false;
4871
4872 unsigned cChanges = 0;
4873 ScmStreamRewindForReading(pIn);
4874 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4875 {
4876 /*
4877 * Look for #includes to modify.
4878 */
4879 if (cchLine >= 6)
4880 {
4881 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
4882 if ( pchHash
4883 && isSpanOfBlanks(pchLine, pchHash - pchLine))
4884 {
4885 const char *pchFilename;
4886 size_t cchFilename;
4887 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
4888 if ( enmIncDir == kScmIncludeDir_Bracketed
4889 || enmIncDir == kScmIncludeDir_Quoted)
4890 {
4891 unsigned i = RT_ELEMENTS(s_aHeaders);
4892 while (i-- > 0)
4893 if ( s_aHeaders[i].cchHeader == cchFilename
4894 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
4895 {
4896 ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1);
4897 ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>"));
4898 size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1];
4899 if (cchTrailing > 0)
4900 ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing);
4901 ScmStreamPutEol(pOut, enmEol);
4902 cChanges++;
4903 pchLine = NULL;
4904 break;
4905 }
4906 if (!pchLine)
4907 continue;
4908 }
4909 }
4910 }
4911
4912 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4913 if (RT_FAILURE(rc))
4914 return false;
4915 }
4916 ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges);
4917 return true;
4918}
4919
4920typedef struct
4921{
4922 const char *pch;
4923 uint8_t cch;
4924 uint8_t cchSpaces; /**< Number of expected spaces before the word. */
4925 bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */
4926 bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
4927} SCMMATCHWORD;
4928
4929
4930int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords,
4931 size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo)
4932{
4933 int rc = VINF_SUCCESS;
4934
4935 size_t offLine = 0;
4936 for (size_t i = 0; i < cWords; i++)
4937 {
4938 SCMMATCHWORD const *pWord = &paWords[i];
4939
4940 /*
4941 * Deal with spaces preceeding the word first:
4942 */
4943 if (pWord->fSpacesBefore)
4944 {
4945 size_t cchSpaces = 0;
4946 size_t cchTabs = 0;
4947 while (offLine < cchLine)
4948 {
4949 const char ch = pchLine[offLine];
4950 if (ch == ' ')
4951 cchSpaces++;
4952 else if (ch == '\t')
4953 cchTabs++;
4954 else
4955 break;
4956 offLine++;
4957 }
4958
4959 if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
4960 { /* likely */ }
4961 else if (cchSpaces == 0 && cchTabs == 0)
4962 return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
4963 else
4964 rc = VWRN_TRAILING_SPACES;
4965 }
4966 else
4967 Assert(pWord->cchSpaces == 0);
4968
4969 /*
4970 * C/C++ identifier?
4971 */
4972 if (pWord->fIdentifier)
4973 {
4974 if (offLine >= cchLine)
4975 return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
4976 "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
4977 pWord->cch, pWord->pch, offLine);
4978 if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
4979 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
4980 pWord->cch, pWord->pch, offLine);
4981 size_t const offStart = offLine++;
4982 while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
4983 offLine++;
4984 if (paIdentifiers)
4985 {
4986 paIdentifiers->cch = offLine - offStart;
4987 paIdentifiers->psz = &pchLine[offStart];
4988 paIdentifiers++;
4989 }
4990 }
4991 /*
4992 * Match the exact word.
4993 */
4994 else if ( pWord->cch == 0
4995 || ( pWord->cch <= cchLine - offLine
4996 && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
4997 offLine += pWord->cch;
4998 else
4999 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
5000 }
5001
5002 /*
5003 * Check for trailing characters/whatnot.
5004 */
5005 if (poffNext)
5006 *poffNext = offLine;
5007 else if (offLine != cchLine)
5008 rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
5009 return rc;
5010}
5011
5012
5013/**
5014 * Fix header file include guards and \#pragma once.
5015 *
5016 * @returns true if modifications were made, false if not.
5017 * @param pIn The input stream.
5018 * @param pOut The output stream.
5019 * @param pSettings The settings.
5020 */
5021bool rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5022{
5023 if (!pSettings->fFixHeaderGuards)
5024 return false;
5025
5026 /* always skip .cpp.h files */
5027 size_t cchFilename = strlen(pState->pszFilename);
5028 if ( cchFilename > sizeof(".cpp.h")
5029 && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0)
5030 return false;
5031
5032 RTERRINFOSTATIC ErrInfo;
5033 char szNormalized[168];
5034 size_t cchNormalized = 0;
5035 int rc;
5036 bool fRet = false;
5037
5038 /*
5039 * Calculate the expected guard for this file, if so tasked.
5040 * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir.
5041 */
5042 szNormalized[0] = '\0';
5043 if (pSettings->pszGuardRelativeToDir)
5044 {
5045 rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix);
5046 if (RT_FAILURE(rc))
5047 return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix);
5048 cchNormalized = strlen(szNormalized);
5049 if (strcmp(pSettings->pszGuardRelativeToDir, "{dir}") == 0)
5050 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
5051 RTPathFilename(pState->pszFilename));
5052 else if (strcmp(pSettings->pszGuardRelativeToDir, "{parent}") == 0)
5053 {
5054 const char *pszSrc = RTPathFilename(pState->pszFilename);
5055 if (!pszSrc || (uintptr_t)&pszSrc[-2] < (uintptr_t)pState->pszFilename || !RTPATH_IS_SLASH(pszSrc[-1]))
5056 return ScmError(pState, VERR_INTERNAL_ERROR, "Error calculating {parent} header guard!\n");
5057 pszSrc -= 2;
5058 while ( (uintptr_t)pszSrc > (uintptr_t)pState->pszFilename
5059 && !RTPATH_IS_SLASH(pszSrc[-1])
5060 && !RTPATH_IS_VOLSEP(pszSrc[-1]))
5061 pszSrc--;
5062 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);
5063 }
5064 else
5065 rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
5066 pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename);
5067 if (RT_FAILURE(rc))
5068 return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
5069 char ch;
5070 while ((ch = szNormalized[cchNormalized]) != '\0')
5071 {
5072 if (!ScmIsCIdentifierChar(ch))
5073 szNormalized[cchNormalized] = '_';
5074 cchNormalized++;
5075 }
5076 }
5077
5078 /*
5079 * First part looks for the #ifndef xxxx paired with #define xxxx.
5080 *
5081 * We blindly assume the first preprocessor directive in the file is the guard
5082 * and will be upset if this isn't the case.
5083 */
5084 RTSTRTUPLE Guard = { NULL, 0 };
5085 uint32_t cBlankLines = 0;
5086 SCMEOL enmEol;
5087 size_t cchLine;
5088 const char *pchLine;
5089 for (;;)
5090 {
5091 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5092 if (pchLine == NULL)
5093 return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
5094 if (cchLine >= 2)
5095 {
5096 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
5097 if ( pchHash
5098 && isSpanOfBlanks(pchLine, pchHash - pchLine))
5099 {
5100 /* #ifndef xxxx */
5101 static const SCMMATCHWORD s_aIfndefGuard[] =
5102 {
5103 { RT_STR_TUPLE("#"), 0, true, false },
5104 { RT_STR_TUPLE("ifndef"), 0, true, false },
5105 { RT_STR_TUPLE("IDENTIFIER"), 1, true, true },
5106 { RT_STR_TUPLE(""), 0, true, false },
5107 };
5108 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
5109 NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
5110 if (RT_FAILURE(rc))
5111 return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
5112 ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
5113 fRet |= rc != VINF_SUCCESS;
5114 ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
5115 ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
5116
5117 /* #define xxxx */
5118 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5119 if (!pchLine)
5120 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
5121 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
5122 const SCMMATCHWORD aDefineGuard[] =
5123 {
5124 { RT_STR_TUPLE("#"), 0, true, false },
5125 { RT_STR_TUPLE("define"), 0, true, false },
5126 { Guard.psz, (uint8_t)Guard.cch, 1, true, false },
5127 { RT_STR_TUPLE(""), 0, true, false },
5128 };
5129 rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
5130 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5131 if (RT_FAILURE(rc))
5132 return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
5133 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
5134 ErrInfo.Core.pszMsg, cchLine, pchLine);
5135 fRet |= rc != VINF_SUCCESS;
5136
5137 if (Guard.cch >= sizeof(szNormalized))
5138 return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
5139 ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
5140
5141 if (szNormalized[0] != '\0')
5142 {
5143 if ( Guard.cch != cchNormalized
5144 || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
5145 {
5146 ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
5147 ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
5148 Guard.cch, Guard.psz, pState->pszFilename);
5149 fRet = true;
5150 }
5151 Guard.psz = szNormalized;
5152 Guard.cch = cchNormalized;
5153 }
5154
5155 /*
5156 * Write guard, making sure we've got a single blank line preceeding it.
5157 */
5158 ScmStreamPutEol(pOut, enmEol);
5159 ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
5160 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
5161 ScmStreamPutEol(pOut, enmEol);
5162 ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
5163 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
5164 rc = ScmStreamPutEol(pOut, enmEol);
5165 if (RT_FAILURE(rc))
5166 return false;
5167 break;
5168 }
5169 }
5170
5171 if (!isBlankLine(pchLine, cchLine))
5172 {
5173 while (cBlankLines-- > 0)
5174 ScmStreamPutEol(pOut, enmEol);
5175 cBlankLines = 0;
5176 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5177 if (RT_FAILURE(rc))
5178 return false;
5179 }
5180 else
5181 cBlankLines++;
5182 }
5183
5184 /*
5185 * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
5186 */
5187 size_t const iPragmaOnce = ScmStreamTellLine(pIn);
5188 static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
5189 {
5190 { RT_STR_TUPLE("#"), 0, true, false },
5191 { RT_STR_TUPLE("ifndef"), 0, true, false },
5192 { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false },
5193 { RT_STR_TUPLE(""), 0, true, false },
5194 };
5195 static const SCMMATCHWORD s_aPragmaOnce[] =
5196 {
5197 { RT_STR_TUPLE("#"), 0, true, false },
5198 { RT_STR_TUPLE("pragma"), 1, true, false },
5199 { RT_STR_TUPLE("once"), 1, true, false},
5200 { RT_STR_TUPLE(""), 0, true, false },
5201 };
5202 static const SCMMATCHWORD s_aEndif[] =
5203 {
5204 { RT_STR_TUPLE("#"), 0, true, false },
5205 { RT_STR_TUPLE("endif"), 0, true, false },
5206 { RT_STR_TUPLE(""), 0, true, false },
5207 };
5208
5209 /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
5210 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5211 if (!pchLine)
5212 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
5213 size_t offNext;
5214 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
5215 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5216 if (RT_SUCCESS(rc))
5217 {
5218 fRet |= rc != VINF_SUCCESS;
5219 if (offNext != cchLine)
5220 return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
5221 iPragmaOnce + 1, cchLine, pchLine);
5222
5223 /* # pragma once */
5224 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5225 if (!pchLine)
5226 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
5227 iPragmaOnce + 2);
5228 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
5229 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5230 if (RT_SUCCESS(rc))
5231 fRet |= rc != VINF_SUCCESS;
5232 else
5233 return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
5234 iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
5235
5236 /* #endif */
5237 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5238 if (!pchLine)
5239 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
5240 iPragmaOnce + 3);
5241 rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
5242 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5243 if (RT_SUCCESS(rc))
5244 fRet |= rc != VINF_SUCCESS;
5245 else
5246 return ScmError(pState, rc,
5247 "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
5248 iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
5249 ScmVerbose(pState, 3, "Found pragma once\n");
5250 fRet |= !pSettings->fPragmaOnce;
5251 }
5252 else
5253 {
5254 rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
5255 if (RT_FAILURE(rc))
5256 return ScmError(pState, rc, "seek error\n");
5257 fRet |= pSettings->fPragmaOnce;
5258 ScmVerbose(pState, pSettings->fPragmaOnce ? 2 : 3, "Missing #pragma once\n");
5259 }
5260
5261 /*
5262 * Write the pragma once stuff.
5263 */
5264 if (pSettings->fPragmaOnce)
5265 {
5266 ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
5267 ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
5268 rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
5269 if (RT_FAILURE(rc))
5270 return false;
5271 }
5272
5273 /*
5274 * Copy the rest of the file and remove pragma once statements, while
5275 * looking for the last #endif in the file.
5276 */
5277 size_t iEndIfIn = 0;
5278 size_t iEndIfOut = 0;
5279 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5280 {
5281 if (cchLine > 2)
5282 {
5283 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
5284 if ( pchHash
5285 && isSpanOfBlanks(pchLine, pchHash - pchLine))
5286 {
5287 size_t off = pchHash - pchLine + 1;
5288 while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
5289 off++;
5290 /* #pragma once */
5291 if ( off + sizeof("pragma") - 1 <= cchLine
5292 && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
5293 {
5294 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
5295 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5296 if (RT_SUCCESS(rc))
5297 {
5298 fRet = true;
5299 continue;
5300 }
5301 }
5302 /* #endif */
5303 else if ( off + sizeof("endif") - 1 <= cchLine
5304 && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
5305 {
5306 iEndIfIn = ScmStreamTellLine(pIn) - 1;
5307 iEndIfOut = ScmStreamTellLine(pOut);
5308 }
5309 }
5310 }
5311
5312 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5313 if (RT_FAILURE(rc))
5314 return false;
5315 }
5316
5317 /*
5318 * Check out the last endif, making sure it's well formed and make sure it has the
5319 * right kind of comment following it.
5320 */
5321 if (pSettings->fFixHeaderGuardEndif)
5322 {
5323 if (iEndIfOut == 0)
5324 return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
5325 rc = ScmStreamSeekByLine(pIn, iEndIfIn);
5326 if (RT_FAILURE(rc))
5327 return false;
5328 rc = ScmStreamSeekByLine(pOut, iEndIfOut);
5329 if (RT_FAILURE(rc))
5330 return false;
5331
5332 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5333 if (!pchLine)
5334 return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
5335
5336 char szTmp[64 + sizeof(szNormalized)];
5337 size_t cchTmp;
5338 if (pSettings->fEndifGuardComment)
5339 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
5340 else
5341 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
5342 fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
5343 rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
5344 if (RT_FAILURE(rc))
5345 return false;
5346
5347 /* Copy out the remaining lines (assumes no #pragma once here). */
5348 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5349 {
5350 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5351 if (RT_FAILURE(rc))
5352 return false;
5353 }
5354 }
5355
5356 return fRet;
5357}
5358
5359
5360/**
5361 * Checks for PAGE_SIZE, PAGE_SHIFT and PAGE_OFFSET_MASK w/o a GUEST_ or HOST_
5362 * prefix as well as banning PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK and
5363 * PAGE_BASE_MASK.
5364 *
5365 * @returns true if modifications were made, false if not.
5366 * @param pIn The input stream.
5367 * @param pOut The output stream.
5368 * @param pSettings The settings.
5369 */
5370bool rewrite_PageChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5371{
5372 RT_NOREF(pOut);
5373 if (!pSettings->fOnlyGuestHostPage && !pSettings->fNoASMMemPageUse)
5374 return false;
5375
5376 static RTSTRTUPLE const g_aWords[] =
5377 {
5378 { RT_STR_TUPLE("PAGE_SIZE") },
5379 { RT_STR_TUPLE("PAGE_SHIFT") },
5380 { RT_STR_TUPLE("PAGE_OFFSET_MASK") },
5381 { RT_STR_TUPLE("PAGE_BASE_MASK") },
5382 { RT_STR_TUPLE("PAGE_BASE_GC_MASK") },
5383 { RT_STR_TUPLE("PAGE_BASE_HC_MASK") },
5384 { RT_STR_TUPLE("PAGE_ADDRESS") },
5385 { RT_STR_TUPLE("PHYS_PAGE_ADDRESS") },
5386 { RT_STR_TUPLE("ASMMemIsZeroPage") },
5387 { RT_STR_TUPLE("ASMMemZeroPage") },
5388 };
5389 size_t const iFirstWord = pSettings->fOnlyGuestHostPage ? 0 : 7;
5390 size_t const iEndWords = pSettings->fNoASMMemPageUse ? 9 : 7;
5391
5392 uint32_t iLine = 0;
5393 SCMEOL enmEol;
5394 size_t cchLine;
5395 const char *pchLine;
5396 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5397 {
5398 iLine++;
5399 for (size_t i = iFirstWord; i < iEndWords; i++)
5400 {
5401 size_t const cchWord = g_aWords[i].cch;
5402 if (cchLine >= cchWord)
5403 {
5404 const char * const pszWord = g_aWords[i].psz;
5405 const char *pchHit = (const char *)memchr(pchLine, *pszWord, cchLine);
5406 while (pchHit)
5407 {
5408 size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
5409 if ( cchLeft >= cchWord
5410 && memcmp(pchHit, pszWord, cchWord) == 0
5411 && ( pchHit == pchLine
5412 || !ScmIsCIdentifierChar(pchHit[-1]))
5413 && ( cchLeft == cchWord
5414 || !ScmIsCIdentifierChar(pchHit[cchWord])) )
5415 {
5416 if (i < 3)
5417 ScmFixManually(pState, "%u:%zu: %s is not allow! Use GUEST_%s or HOST_%s instead.\n",
5418 iLine, pchHit - pchLine + 1, pszWord, pszWord, pszWord);
5419 else if (i < 7)
5420 ScmFixManually(pState, "%u:%zu: %s is not allow! Rewrite using GUEST/HOST_PAGE_OFFSET_MASK.\n",
5421 iLine, pchHit - pchLine + 1, pszWord);
5422 else
5423 ScmFixManually(pState, "%u:%zu: %s is not allow! Use %s with correct page size instead.\n",
5424 iLine, pchHit - pchLine + 1, pszWord, i == 3 ? "ASMMemIsZero" : "RT_BZERO");
5425 }
5426
5427 /* next */
5428 cchLeft -= 1;
5429 if (cchLeft < cchWord)
5430 break;
5431 pchHit = (const char *)memchr(pchHit + 1, *pszWord, cchLeft);
5432 }
5433 }
5434 }
5435 }
5436
5437 return false;
5438}
5439
5440
5441/**
5442 * Checks for usage of rc in code instead of vrc for IPRT status codes (int) and hrc for COM
5443 * status codes (HRESULT).
5444 *
5445 * @returns true if modifications were made, false if not.
5446 * @param pIn The input stream.
5447 * @param pOut The output stream.
5448 * @param pSettings The settings.
5449 *
5450 * @note Used in Main to avoid ambiguity when just using rc.
5451 */
5452bool rewrite_ForceHrcVrcInsteadOfRc(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5453{
5454 RT_NOREF(pOut);
5455 if (!pSettings->fOnlyHrcVrcInsteadOfRc)
5456 return false;
5457
5458 static const SCMMATCHWORD s_aHresultVrc[] =
5459 {
5460 { RT_STR_TUPLE("HRESULT"), 0, true, false },
5461 { RT_STR_TUPLE("vrc"), 1, true, false }
5462 };
5463
5464 static const SCMMATCHWORD s_aIntHrc[] =
5465 {
5466 { RT_STR_TUPLE("int"), 0, true, false },
5467 { RT_STR_TUPLE("hrc"), 1, true, false }
5468 };
5469
5470 uint32_t iLine = 0;
5471 SCMEOL enmEol;
5472 size_t cchLine;
5473 const char *pchLine;
5474 RTERRINFOSTATIC ErrInfo;
5475 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5476 {
5477 iLine++;
5478
5479 /* Look for forbidden declarations first. */
5480 size_t offNext = 0;
5481 int rc = ScmMatchWords(pchLine, cchLine, s_aHresultVrc, RT_ELEMENTS(s_aHresultVrc),
5482 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5483 if (RT_SUCCESS(rc))
5484 {
5485 ScmFixManually(pState, "%u:%zu: 'HRESULT vrc' is not allowed! Use 'HRESULT hrc' instead.\n",
5486 iLine, offNext);
5487 continue;
5488 }
5489
5490 rc = ScmMatchWords(pchLine, cchLine, s_aIntHrc, RT_ELEMENTS(s_aIntHrc),
5491 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5492 if (RT_SUCCESS(rc))
5493 {
5494 ScmFixManually(pState, "%u:%zu: 'int hrc' is not allowed! Use 'int vrc' instead.\n",
5495 iLine, offNext);
5496 continue;
5497 }
5498
5499#if 0 /* This is too broad and triggers on things we don't want to trigger on (like autoCaller.rc()). */
5500 const RTSTRTUPLE RcTuple = { RT_STR_TUPLE("rc") };
5501 size_t const cchWord = RcTuple.cch;
5502 if (cchLine >= cchWord)
5503 {
5504 const char *pchHit = (const char *)memchr(pchLine, *RcTuple.psz, cchLine);
5505 while (pchHit)
5506 {
5507 size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
5508 if ( cchLeft >= cchWord
5509 && memcmp(pchHit, RcTuple.psz, cchWord) == 0
5510 && ( pchHit == pchLine
5511 || !ScmIsCIdentifierChar(pchHit[-1]))
5512 && ( cchLeft == cchWord
5513 || !ScmIsCIdentifierChar(pchHit[cchWord])) )
5514 ScmFixManually(pState, "%u:%zu: %s is not allowed! Use hrc or vrc instead.\n",
5515 iLine, pchHit - pchLine + 1, RcTuple.psz);
5516
5517 /* next */
5518 cchLeft -= 1;
5519 if (cchLeft < cchWord)
5520 break;
5521 pchHit = (const char *)memchr(pchHit + 1, *RcTuple.psz, cchLeft);
5522 }
5523 }
5524#else
5525 /* Trigger on declarations of 'HRESULT rc' and 'int rc'. */
5526 static const SCMMATCHWORD s_aHresultRc[] =
5527 {
5528 { RT_STR_TUPLE("HRESULT"), 0, true, false },
5529 { RT_STR_TUPLE("rc"), 1, true, false }
5530 };
5531
5532 static const SCMMATCHWORD s_aIntRc[] =
5533 {
5534 { RT_STR_TUPLE("int"), 0, true, false },
5535 { RT_STR_TUPLE("rc"), 1, true, false }
5536 };
5537
5538 rc = ScmMatchWords(pchLine, cchLine, s_aHresultRc, RT_ELEMENTS(s_aHresultRc),
5539 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5540 if (RT_SUCCESS(rc))
5541 {
5542 ScmFixManually(pState, "%u:%zu: 'HRESULT rc' is not allowed! Use 'HRESULT hrc' instead.\n",
5543 iLine, offNext);
5544 continue;
5545 }
5546
5547 rc = ScmMatchWords(pchLine, cchLine, s_aIntRc, RT_ELEMENTS(s_aIntRc),
5548 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5549 if (RT_SUCCESS(rc))
5550 {
5551 ScmFixManually(pState, "%u:%zu: 'int rc' is not allowed! Use 'int vrc' instead.\n",
5552 iLine, offNext);
5553 continue;
5554 }
5555#endif
5556 }
5557
5558 return false;
5559}
5560
5561
5562/**
5563 * Rewrite a C/C++ source or header file.
5564 *
5565 * @returns true if modifications were made, false if not.
5566 * @param pIn The input stream.
5567 * @param pOut The output stream.
5568 * @param pSettings The settings.
5569 *
5570 * @todo
5571 *
5572 * Ideas for C/C++:
5573 * - space after if, while, for, switch
5574 * - spaces in for (i=0;i<x;i++)
5575 * - complex conditional, bird style.
5576 * - remove unnecessary parentheses.
5577 * - sort defined RT_OS_*|| and RT_ARCH
5578 * - sizeof without parenthesis.
5579 * - defined without parenthesis.
5580 * - trailing spaces.
5581 * - parameter indentation.
5582 * - space after comma.
5583 * - while (x--); -> multi line + comment.
5584 * - else statement;
5585 * - space between function and left parenthesis.
5586 * - TODO, XXX, @todo cleanup.
5587 * - Space before/after '*'.
5588 * - ensure new line at end of file.
5589 * - Indentation of precompiler statements (#ifdef, #defines).
5590 * - space between functions.
5591 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
5592 */
5593bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5594{
5595
5596 RT_NOREF4(pState, pIn, pOut, pSettings);
5597 return false;
5598}
5599
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