VirtualBox

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

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

scm: Dropped the efforts in rewrite_Makefile_kmk to detect whether it made modifications and instead just return kScmMaybeModified. bugref:10348

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 210.3 KB
Line 
1/* $Id: scmrw.cpp 98373 2023-02-01 09:44:12Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <iprt/assert.h>
33#include <iprt/ctype.h>
34#include <iprt/dir.h>
35#include <iprt/env.h>
36#include <iprt/file.h>
37#include <iprt/err.h>
38#include <iprt/getopt.h>
39#include <iprt/initterm.h>
40#include <iprt/mem.h>
41#include <iprt/message.h>
42#include <iprt/param.h>
43#include <iprt/path.h>
44#include <iprt/process.h>
45#include <iprt/stream.h>
46#include <iprt/string.h>
47
48#include "scm.h"
49
50
51/*********************************************************************************************************************************
52* Structures and Typedefs *
53*********************************************************************************************************************************/
54/** License types. */
55typedef enum SCMLICENSETYPE
56{
57 kScmLicenseType_Invalid = 0,
58 kScmLicenseType_OseGpl,
59 kScmLicenseType_OseDualGplCddl,
60 kScmLicenseType_OseCddl,
61 kScmLicenseType_VBoxLgpl,
62 kScmLicenseType_Mit,
63 kScmLicenseType_Confidential
64} SCMLICENSETYPE;
65
66/** A license. */
67typedef struct SCMLICENSETEXT
68{
69 /** The license type. */
70 SCMLICENSETYPE enmType;
71 /** The license option. */
72 SCMLICENSE enmOpt;
73 /** The license text. */
74 const char *psz;
75 /** The license text length. */
76 size_t cch;
77} SCMLICENSETEXT;
78/** Pointer to a license. */
79typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;
80
81/**
82 * Copyright + license rewriter state.
83 */
84typedef struct SCMCOPYRIGHTINFO
85{
86 /** State. */
87 PSCMRWSTATE pState; /**< input */
88 /** The comment style (neede for C/C++). */
89 SCMCOMMENTSTYLE enmCommentStyle; /**< input */
90
91 /** Number of comments we've parsed. */
92 uint32_t cComments;
93
94 /** Copy of the contributed-by line if present. */
95 char *pszContributedBy;
96
97 /** @name Common info
98 * @{ */
99 uint32_t iLineComment;
100 uint32_t cLinesComment; /**< This excludes any external license lines. */
101 /** @} */
102
103 /** @name Copyright info
104 * @{ */
105 uint32_t iLineCopyright;
106 uint32_t uFirstYear;
107 uint32_t uLastYear;
108 bool fWellFormedCopyright;
109 bool fUpToDateCopyright;
110 /** @} */
111
112 /** @name License info
113 * @{ */
114 bool fOpenSource; /**< input */
115 PCSCMLICENSETEXT pExpectedLicense; /**< input */
116 PCSCMLICENSETEXT paLicenses; /**< input */
117 SCMLICENSE enmLicenceOpt; /**< input */
118 uint32_t iLineLicense;
119 uint32_t cLinesLicense;
120 PCSCMLICENSETEXT pCurrentLicense;
121 bool fIsCorrectLicense;
122 bool fWellFormedLicense;
123 bool fExternalLicense;
124 /** @} */
125
126 /** @name LGPL licence notice and disclaimer info
127 * @{ */
128 /** Wheter to check for LGPL license notices and disclaimers. */
129 bool fCheckforLgpl;
130 /** The approximate line we found the (first) LGPL licence notice on. */
131 uint32_t iLineLgplNotice;
132 /** The line number after the LGPL notice comment. */
133 uint32_t iLineAfterLgplComment;
134 /** The LGPL disclaimer line. */
135 uint32_t iLineLgplDisclaimer;
136 /** @} */
137
138} SCMCOPYRIGHTINFO;
139typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO;
140
141
142/*********************************************************************************************************************************
143* Global Variables *
144*********************************************************************************************************************************/
145/** --license-ose-gpl */
146static const char g_szVBoxOseGpl[] =
147 "This file is part of VirtualBox base platform packages, as\n"
148 "available from https://www.virtualbox.org.\n"
149 "\n"
150 "This program is free software; you can redistribute it and/or\n"
151 "modify it under the terms of the GNU General Public License\n"
152 "as published by the Free Software Foundation, in version 3 of the\n"
153 "License.\n"
154 "\n"
155 "This program is distributed in the hope that it will be useful, but\n"
156 "WITHOUT ANY WARRANTY; without even the implied warranty of\n"
157 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
158 "General Public License for more details.\n"
159 "\n"
160 "You should have received a copy of the GNU General Public License\n"
161 "along with this program; if not, see <https://www.gnu.org/licenses>.\n"
162 "\n"
163 "SPDX-License-Identifier: GPL-3.0-only\n";
164
165static const char g_szVBoxOseOldGpl2[] =
166 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
167 "available from http://www.virtualbox.org. This file is free software;\n"
168 "you can redistribute it and/or modify it under the terms of the GNU\n"
169 "General Public License (GPL) as published by the Free Software\n"
170 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
171 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
172 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n";
173
174/** --license-ose-dual */
175static const char g_szVBoxOseDualGplCddl[] =
176 "This file is part of VirtualBox base platform packages, as\n"
177 "available from https://www.virtualbox.org.\n"
178 "\n"
179 "This program is free software; you can redistribute it and/or\n"
180 "modify it under the terms of the GNU General Public License\n"
181 "as published by the Free Software Foundation, in version 3 of the\n"
182 "License.\n"
183 "\n"
184 "This program is distributed in the hope that it will be useful, but\n"
185 "WITHOUT ANY WARRANTY; without even the implied warranty of\n"
186 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
187 "General Public License for more details.\n"
188 "\n"
189 "You should have received a copy of the GNU General Public License\n"
190 "along with this program; if not, see <https://www.gnu.org/licenses>.\n"
191 "\n"
192 "The contents of this file may alternatively be used under the terms\n"
193 "of the Common Development and Distribution License Version 1.0\n"
194 "(CDDL), a copy of it is provided in the \"COPYING.CDDL\" file included\n"
195 "in the VirtualBox distribution, in which case the provisions of the\n"
196 "CDDL are applicable instead of those of the GPL.\n"
197 "\n"
198 "You may elect to license modified versions of this file under the\n"
199 "terms and conditions of either the GPL or the CDDL or both.\n"
200 "\n"
201 "SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0\n";
202
203static const char g_szVBoxOseOldDualGpl2Cddl[] =
204 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
205 "available from http://www.virtualbox.org. This file is free software;\n"
206 "you can redistribute it and/or modify it under the terms of the GNU\n"
207 "General Public License (GPL) as published by the Free Software\n"
208 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
209 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
210 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n"
211 "\n"
212 "The contents of this file may alternatively be used under the terms\n"
213 "of the Common Development and Distribution License Version 1.0\n"
214 "(CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n"
215 "VirtualBox OSE distribution, in which case the provisions of the\n"
216 "CDDL are applicable instead of those of the GPL.\n"
217 "\n"
218 "You may elect to license modified versions of this file under the\n"
219 "terms and conditions of either the GPL or the CDDL or both.\n";
220
221/** --license-ose-cddl */
222static const char g_szVBoxOseCddl[] =
223 "This file is part of VirtualBox base platform packages, as\n"
224 "available from http://www.virtualbox.org.\n"
225 "\n"
226 "The contents of this file are subject to the terms of the Common\n"
227 "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
228 "comes in the \"COPYING.CDDL\" file of the VirtualBox distribution.\n"
229 "\n"
230 "SPDX-License-Identifier: CDDL-1.0\n";
231
232static const char g_szVBoxOseOldCddl[] =
233 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
234 "available from http://www.virtualbox.org. This file is free software;\n"
235 "you can redistribute it and/or modify it under the terms of the Common\n"
236 "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
237 "comes in the \"COPYING.CDDL\" file of the VirtualBox OSE distribution.\n"
238 "VirtualBox OSE is distributed in the hope that it will be useful, but\n"
239 "WITHOUT ANY WARRANTY of any kind.\n";
240
241/** --license-lgpl */
242static const char g_szVBoxLgpl[] =
243 "This file is part of a free software library; you can redistribute\n"
244 "it and/or modify it under the terms of the GNU Lesser General\n"
245 "Public License version 2.1 as published by the Free Software\n"
246 "Foundation and shipped in the \"COPYING.LIB\" file with this library.\n"
247 "The library is distributed in the hope that it will be useful,\n"
248 "but WITHOUT ANY WARRANTY of any kind.\n"
249 "\n"
250 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if\n"
251 "any license choice other than GPL or LGPL is available it will\n"
252 "apply instead, Oracle elects to use only the Lesser General Public\n"
253 "License version 2.1 (LGPLv2) at this time for any software where\n"
254 "a choice of LGPL license versions is made available with the\n"
255 "language indicating that LGPLv2 or any later version may be used,\n"
256 "or where a choice of which version of the LGPL is applied is\n"
257 "otherwise unspecified.\n"
258 "\n"
259 "SPDX-License-Identifier: LGPL-2.1-only\n";
260
261/** --license-mit
262 * @note This isn't detectable as VirtualBox or Oracle specific.
263 */
264static const char g_szMit[] =
265 "Permission is hereby granted, free of charge, to any person\n"
266 "obtaining a copy of this software and associated documentation\n"
267 "files (the \"Software\"), to deal in the Software without\n"
268 "restriction, including without limitation the rights to use,\n"
269 "copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
270 "copies of the Software, and to permit persons to whom the\n"
271 "Software is furnished to do so, subject to the following\n"
272 "conditions:\n"
273 "\n"
274 "The above copyright notice and this permission notice shall be\n"
275 "included in all copies or substantial portions of the Software.\n"
276 "\n"
277 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
278 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
279 "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
280 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
281 "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
282 "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
283 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
284 "OTHER DEALINGS IN THE SOFTWARE.\n";
285
286/** --license-mit, alternative wording \#1.
287 * @note This differes from g_szMit in "AUTHORS OR COPYRIGHT HOLDERS" is written
288 * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a
289 * couple of lines shorter. */
290static const char g_szMitAlt1[] =
291 "Permission is hereby granted, free of charge, to any person obtaining a\n"
292 "copy of this software and associated documentation files (the \"Software\"),\n"
293 "to deal in the Software without restriction, including without limitation\n"
294 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
295 "and/or sell copies of the Software, and to permit persons to whom the\n"
296 "Software is furnished to do so, subject to the following conditions:\n"
297 "\n"
298 "The above copyright notice and this permission notice shall be included in\n"
299 "all copies or substantial portions of the Software.\n"
300 "\n"
301 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
302 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
303 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
304 "THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR\n"
305 "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n"
306 "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
307 "OTHER DEALINGS IN THE SOFTWARE.\n";
308
309/** --license-mit, alternative wording \#2.
310 * @note This differes from g_szMit in that "AUTHORS OR COPYRIGHT HOLDERS" is
311 * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".
312 * Its layout is wider, so it is a couple of lines shorter. */
313static const char g_szMitAlt2[] =
314 "Permission is hereby granted, free of charge, to any person obtaining a\n"
315 "copy of this software and associated documentation files (the \"Software\"),\n"
316 "to deal in the Software without restriction, including without limitation\n"
317 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
318 "and/or sell copies of the Software, and to permit persons to whom the\n"
319 "Software is furnished to do so, subject to the following conditions:\n"
320 "\n"
321 "The above copyright notice and this permission notice shall be included in\n"
322 "all copies or substantial portions of the Software.\n"
323 "\n"
324 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
325 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
326 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
327 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
328 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
329 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
330 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n";
331
332/** --license-mit, alternative wording \#3.
333 * @note This differes from g_szMitAlt2 in that the second and third sections
334 * have been switch. */
335static const char g_szMitAlt3[] =
336 "Permission is hereby granted, free of charge, to any person obtaining a\n"
337 "copy of this software and associated documentation files (the \"Software\"),\n"
338 "to deal in the Software without restriction, including without limitation\n"
339 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
340 "and/or sell copies of the Software, and to permit persons to whom the\n"
341 "Software is furnished to do so, subject to the following conditions:\n"
342 "\n"
343 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
344 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
345 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
346 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
347 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
348 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
349 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
350 "\n"
351 "The above copyright notice and this permission notice shall be included in\n"
352 "all copies or substantial portions of the Software.\n";
353
354/** --license-(based-on)mit, alternative wording \#4.
355 * @note This differs from g_szMitAlt2 in injecting "(including the next
356 * paragraph)". */
357static const char g_szMitAlt4[] =
358 "Permission is hereby granted, free of charge, to any person obtaining a\n"
359 "copy of this software and associated documentation files (the \"Software\"),\n"
360 "to deal in the Software without restriction, including without limitation\n"
361 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
362 "and/or sell copies of the Software, and to permit persons to whom the\n"
363 "Software is furnished to do so, subject to the following conditions:\n"
364 "\n"
365 "The above copyright notice and this permission notice (including the next\n"
366 "paragraph) shall be included in all copies or substantial portions of the\n"
367 "Software.\n"
368 "\n"
369 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
370 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
371 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
372 "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
373 "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
374 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
375 "DEALINGS IN THE SOFTWARE.\n";
376
377/** --license-(based-on)mit, alternative wording \#5.
378 * @note This differs from g_szMitAlt3 in using "sub license" instead of
379 * "sublicense" and adding an illogical "(including the next
380 * paragraph)" remark to the final paragraph. (vbox_ttm.c) */
381static const char g_szMitAlt5[] =
382 "Permission is hereby granted, free of charge, to any person obtaining a\n"
383 "copy of this software and associated documentation files (the\n"
384 "\"Software\"), to deal in the Software without restriction, including\n"
385 "without limitation the rights to use, copy, modify, merge, publish,\n"
386 "distribute, sub license, and/or sell copies of the Software, and to\n"
387 "permit persons to whom the Software is furnished to do so, subject to\n"
388 "the following conditions:\n"
389 "\n"
390 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
391 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
392 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
393 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
394 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
395 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
396 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
397 "\n"
398 "The above copyright notice and this permission notice (including the\n"
399 "next paragraph) shall be included in all copies or substantial portions\n"
400 "of the Software.\n";
401
402/** Oracle confidential. */
403static const char g_szOracleConfidential[] =
404 "Oracle Corporation confidential\n";
405
406/** Oracle confidential, old style. */
407static const char g_szOracleConfidentialOld[] =
408 "Oracle Corporation confidential\n"
409 "All rights reserved\n";
410
411/** Licenses to detect when --license-mit isn't used. */
412static const SCMLICENSETEXT g_aLicenses[] =
413{
414 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
415 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseOldGpl2)},
416 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
417 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseOldDualGpl2Cddl) },
418 { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseCddl) },
419 { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseOldCddl) },
420 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
421 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
422 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidentialOld) },
423 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
424};
425
426/** Licenses to detect when --license-mit or --license-based-on-mit are used. */
427static const SCMLICENSETEXT g_aLicensesWithMit[] =
428{
429 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMit) },
430 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt1) },
431 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt2) },
432 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt3) },
433 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt4) },
434 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt5) },
435 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
436 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseOldGpl2)},
437 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
438 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseOldDualGpl2Cddl) },
439 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
440 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
441 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidentialOld) },
442 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
443};
444
445/** Copyright holder. */
446static const char g_szCopyrightHolder[] = "Oracle and/or its affiliates.";
447
448/** Old copyright holder. */
449static const char g_szOldCopyrightHolder[] = "Oracle Corporation";
450
451/** LGPL disclaimer. */
452static const char g_szLgplDisclaimer[] =
453 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice\n"
454 "other than GPL or LGPL is available it will apply instead, Oracle elects to use only\n"
455 "the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where\n"
456 "a choice of LGPL license versions is made available with the language indicating\n"
457 "that LGPLv2 or any later version may be used, or where a choice of which version\n"
458 "of the LGPL is applied is otherwise unspecified.\n";
459
460/** Copyright+license comment start for each SCMCOMMENTSTYLE. */
461static RTSTRTUPLE const g_aCopyrightCommentStart[] =
462{
463 { RT_STR_TUPLE("<invalid> ") },
464 { RT_STR_TUPLE("/*") },
465 { RT_STR_TUPLE("#") },
466 { RT_STR_TUPLE("\"\"\"") },
467 { RT_STR_TUPLE(";") },
468 { RT_STR_TUPLE("REM") },
469 { RT_STR_TUPLE("rem") },
470 { RT_STR_TUPLE("Rem") },
471 { RT_STR_TUPLE("--") },
472 { RT_STR_TUPLE("'") },
473 { RT_STR_TUPLE("<!--") },
474 { RT_STR_TUPLE("<end>") },
475};
476
477/** Copyright+license line prefix for each SCMCOMMENTSTYLE. */
478static RTSTRTUPLE const g_aCopyrightCommentPrefix[] =
479{
480 { RT_STR_TUPLE("<invalid> ") },
481 { RT_STR_TUPLE(" * ") },
482 { RT_STR_TUPLE("# ") },
483 { RT_STR_TUPLE("") },
484 { RT_STR_TUPLE("; ") },
485 { RT_STR_TUPLE("REM ") },
486 { RT_STR_TUPLE("rem ") },
487 { RT_STR_TUPLE("Rem ") },
488 { RT_STR_TUPLE("-- ") },
489 { RT_STR_TUPLE("' ") },
490 { RT_STR_TUPLE(" ") },
491 { RT_STR_TUPLE("<end>") },
492};
493
494/** Copyright+license empty line for each SCMCOMMENTSTYLE. */
495static RTSTRTUPLE const g_aCopyrightCommentEmpty[] =
496{
497 { RT_STR_TUPLE("<invalid>") },
498 { RT_STR_TUPLE(" *") },
499 { RT_STR_TUPLE("#") },
500 { RT_STR_TUPLE("") },
501 { RT_STR_TUPLE(";") },
502 { RT_STR_TUPLE("REM") },
503 { RT_STR_TUPLE("rem") },
504 { RT_STR_TUPLE("Rem") },
505 { RT_STR_TUPLE("--") },
506 { RT_STR_TUPLE("'") },
507 { RT_STR_TUPLE("") },
508 { RT_STR_TUPLE("<end>") },
509};
510
511/** Copyright+license end of comment for each SCMCOMMENTSTYLE. */
512static RTSTRTUPLE const g_aCopyrightCommentEnd[] =
513{
514 { RT_STR_TUPLE("<invalid> ") },
515 { RT_STR_TUPLE(" */") },
516 { RT_STR_TUPLE("#") },
517 { RT_STR_TUPLE("\"\"\"") },
518 { RT_STR_TUPLE(";") },
519 { RT_STR_TUPLE("REM") },
520 { RT_STR_TUPLE("rem") },
521 { RT_STR_TUPLE("Rem") },
522 { RT_STR_TUPLE("--") },
523 { RT_STR_TUPLE("'") },
524 { RT_STR_TUPLE("-->") },
525 { RT_STR_TUPLE("<end>") },
526};
527
528
529/**
530 * Figures out the predominant casing of the "REM" keyword in a batch file.
531 *
532 * @returns Predominant comment style.
533 * @param pIn The file to scan. Will be rewound.
534 */
535static SCMCOMMENTSTYLE determineBatchFileCommentStyle(PSCMSTREAM pIn)
536{
537 /*
538 * Figure out whether it's using upper or lower case REM comments before
539 * doing the work.
540 */
541 uint32_t cUpper = 0;
542 uint32_t cLower = 0;
543 uint32_t cCamel = 0;
544 SCMEOL enmEol;
545 size_t cchLine;
546 const char *pchLine;
547 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
548 {
549 while ( cchLine > 2
550 && RT_C_IS_SPACE(*pchLine))
551 {
552 pchLine++;
553 cchLine--;
554 }
555 if ( ( cchLine > 3
556 && RT_C_IS_SPACE(pchLine[2]))
557 || cchLine == 3)
558 {
559 if ( pchLine[0] == 'R'
560 && pchLine[1] == 'E'
561 && pchLine[2] == 'M')
562 cUpper++;
563 else if ( pchLine[0] == 'r'
564 && pchLine[1] == 'e'
565 && pchLine[2] == 'm')
566 cLower++;
567 else if ( pchLine[0] == 'R'
568 && pchLine[1] == 'e'
569 && pchLine[2] == 'm')
570 cCamel++;
571 }
572 }
573
574 ScmStreamRewindForReading(pIn);
575
576 if (cLower >= cUpper && cLower >= cCamel)
577 return kScmCommentStyle_Rem_Lower;
578 if (cCamel >= cLower && cCamel >= cUpper)
579 return kScmCommentStyle_Rem_Camel;
580 return kScmCommentStyle_Rem_Upper;
581}
582
583
584/**
585 * Calculates the number of spaces from @a offStart to @a offEnd in @a pchLine,
586 * taking tabs into account.
587 */
588static size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings)
589{
590 size_t cchRet = 0;
591 if (offStart < offEnd)
592 {
593 offEnd -= offStart; /* becomes cchLeft now */
594 pchLine += offStart;
595 while (offEnd > 0)
596 {
597 const char *pszTab = (const char *)memchr(pchLine, '\t', offEnd);
598 if (!pszTab)
599 {
600 cchRet += offEnd;
601 break;
602 }
603 size_t offTab = (size_t)(pszTab - pchLine);
604 size_t cchToTab = pSettings->cchTab - offTab % pSettings->cchTab;
605 cchRet += offTab + cchToTab;
606 offEnd -= offTab + 1;
607 pchLine = pszTab + 1;
608 }
609 }
610 return cchRet;
611}
612
613
614/**
615 * Worker for isBlankLine.
616 *
617 * @returns true if blank, false if not.
618 * @param pchLine Pointer to the start of the line.
619 * @param cchLine The (encoded) length of the line, excluding EOL char.
620 */
621static bool isBlankLineSlow(const char *pchLine, size_t cchLine)
622{
623 /*
624 * From the end, more likely to hit a non-blank char there.
625 */
626 while (cchLine-- > 0)
627 if (!RT_C_IS_BLANK(pchLine[cchLine]))
628 return false;
629 return true;
630}
631
632/**
633 * Helper for checking whether a line is blank.
634 *
635 * @returns true if blank, false if not.
636 * @param pchLine Pointer to the start of the line.
637 * @param cchLine The (encoded) length of the line, excluding EOL char.
638 */
639DECLINLINE(bool) isBlankLine(const char *pchLine, size_t cchLine)
640{
641 if (cchLine == 0)
642 return true;
643 /*
644 * We're more likely to fine a non-space char at the end of the line than
645 * at the start, due to source code indentation.
646 */
647 if (pchLine[cchLine - 1])
648 return false;
649
650 /*
651 * Don't bother inlining loop code.
652 */
653 return isBlankLineSlow(pchLine, cchLine);
654}
655
656
657/**
658 * Checks if there are @a cch blanks at @a pch.
659 *
660 * @returns true if span of @a cch blanks, false if not.
661 * @param pch The start of the span to check.
662 * @param cch The length of the span.
663 */
664DECLINLINE(bool) isSpanOfBlanks(const char *pch, size_t cch)
665{
666 while (cch-- > 0)
667 {
668 char const ch = *pch++;
669 if (!RT_C_IS_BLANK(ch))
670 return false;
671 }
672 return true;
673}
674
675
676/**
677 * Strip trailing blanks (space & tab).
678 *
679 * @returns Modification state.
680 * @param pIn The input stream.
681 * @param pOut The output stream.
682 * @param pSettings The settings.
683 */
684SCMREWRITERRES rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
685{
686 if (!pSettings->fStripTrailingBlanks)
687 return kScmUnmodified;
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 kScmUnmodified;
709 }
710 if (fModified)
711 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
712 return fModified ? kScmModified : kScmUnmodified;
713}
714
715/**
716 * Expand tabs.
717 *
718 * @returns Modification state.
719 * @param pIn The input stream.
720 * @param pOut The output stream.
721 * @param pSettings The settings.
722 */
723SCMREWRITERRES rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
724{
725 if (!pSettings->fConvertTabs)
726 return kScmUnmodified;
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 kScmUnmodified;
767 }
768 if (fModified)
769 ScmVerbose(pState, 2, " * Expanded tabs\n");
770 return fModified ? kScmModified : kScmUnmodified;
771}
772
773/**
774 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
775 *
776 * @returns Modification state.
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 SCMREWRITERRES rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
784 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
785{
786 if (!pSettings->fConvertEol)
787 return kScmUnmodified;
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 kScmUnmodified;
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 ? kScmModified : kScmUnmodified;
831}
832
833/**
834 * Force native end of line indicator.
835 *
836 * @returns Modification state.
837 * @param pIn The input stream.
838 * @param pOut The output stream.
839 * @param pSettings The settings.
840 */
841SCMREWRITERRES 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 Modification state.
854 * @param pIn The input stream.
855 * @param pOut The output stream.
856 * @param pSettings The settings.
857 */
858SCMREWRITERRES 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 Modification state.
867 * @param pIn The input stream.
868 * @param pOut The output stream.
869 * @param pSettings The settings.
870 */
871SCMREWRITERRES 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 Modification state.
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 */
887SCMREWRITERRES rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
888{
889 if ( !pSettings->fStripTrailingLines
890 && !pSettings->fForceTrailingLine
891 && !pSettings->fForceFinalEol)
892 return kScmUnmodified;
893
894 size_t const cLines = ScmStreamCountLines(pIn);
895
896 /* Empty files remains empty. */
897 if (cLines <= 1)
898 return kScmUnmodified;
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 kScmUnmodified;
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 kScmModified;
944}
945
946/**
947 * Make sure there is no svn:executable property on the current file.
948 *
949 * @returns kScmUnmodified - 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 */
955SCMREWRITERRES 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 kScmUnmodified;
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 kScmUnmodified;
971}
972
973/**
974 * Make sure there is no svn:keywords property on the current file.
975 *
976 * @returns kScmUnmodified - 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 */
982SCMREWRITERRES 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 kScmUnmodified;
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 kScmUnmodified;
998}
999
1000/**
1001 * Make sure there is no svn:eol-style property on the current file.
1002 *
1003 * @returns kScmUnmodified - 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 */
1009SCMREWRITERRES 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 kScmUnmodified;
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 kScmUnmodified;
1025}
1026
1027/**
1028 * Makes sure the svn properties are appropriate for a binary.
1029 *
1030 * @returns kScmUnmodified - 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 */
1036SCMREWRITERRES 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 kScmUnmodified;
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 kScmUnmodified;
1074}
1075
1076/**
1077 * Make sure the Id and Revision keywords are expanded.
1078 *
1079 * @returns kScmUnmodified - 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 */
1085SCMREWRITERRES 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 kScmUnmodified;
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 kScmUnmodified;
1126}
1127
1128/**
1129 * Checks the svn:sync-process value and that parent is exported too.
1130 *
1131 * @returns kScmUnmodified - 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 */
1137SCMREWRITERRES 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 kScmUnmodified;
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 kScmUnmodified;
1180}
1181
1182/**
1183 * Checks the that there is no bidirectional unicode fun in the file.
1184 *
1185 * @returns kScmUnmodified - 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 */
1191SCMREWRITERRES rewrite_UnicodeChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1192{
1193 RT_NOREF2(pIn, pOut);
1194 if (pSettings->fSkipUnicodeChecks)
1195 return kScmUnmodified;
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 kScmUnmodified;
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 Modification state.
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 SCMREWRITERRES rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut,
1882 PCSCMSETTINGSBASE pSettings, SCMCOMMENTSTYLE enmCommentStyle)
1883{
1884 if ( !pSettings->fUpdateCopyrightYear
1885 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1886 return kScmUnmodified;
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 kScmUnmodified;
2127 }
2128 } /* for each source line */
2129
2130 RTStrFree(Info.pszContributedBy);
2131 return kScmModified;
2132 }
2133 }
2134 }
2135 else
2136 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
2137 NOREF(pState); NOREF(pOut);
2138 RTStrFree(Info.pszContributedBy);
2139 return kScmUnmodified;
2140}
2141
2142
2143/** Copyright updater for C-style comments. */
2144SCMREWRITERRES 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. */
2150SCMREWRITERRES 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. */
2156SCMREWRITERRES 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. */
2162SCMREWRITERRES 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. */
2168SCMREWRITERRES rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut,
2169 PCSCMSETTINGSBASE pSettings)
2170{
2171 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
2172}
2173
2174/** Copyright updater for sql comments. */
2175SCMREWRITERRES rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2176{
2177 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
2178}
2179
2180/** Copyright updater for tick-prefixed comments. */
2181SCMREWRITERRES rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2182{
2183 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
2184}
2185
2186/** Copyright updater for XML comments. */
2187SCMREWRITERRES rewrite_Copyright_XmlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2188{
2189 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Xml);
2190}
2191
2192
2193
2194/*********************************************************************************************************************************
2195* kBuild Makefiles *
2196*********************************************************************************************************************************/
2197
2198/**
2199 * Makefile.kup are empty files, enforce this.
2200 *
2201 * @returns true if modifications were made, false if not.
2202 * @param pIn The input stream.
2203 * @param pOut The output stream.
2204 * @param pSettings The settings.
2205 */
2206SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2207{
2208 RT_NOREF2(pOut, pSettings);
2209
2210 /* These files should be zero bytes. */
2211 if (pIn->cb == 0)
2212 return kScmUnmodified;
2213 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2214 return kScmModified;
2215}
2216
2217typedef enum KMKTOKEN
2218{
2219 kKmkToken_Word = 0,
2220 kKmkToken_Comment,
2221
2222 /* Conditionals: */
2223 kKmkToken_ifeq,
2224 kKmkToken_ifneq,
2225 kKmkToken_if1of,
2226 kKmkToken_ifn1of,
2227 kKmkToken_ifdef,
2228 kKmkToken_ifndef,
2229 kKmkToken_if,
2230 kKmkToken_else,
2231 kKmkToken_endif,
2232
2233 /* Includes: */
2234 kKmkToken_include,
2235 kKmkToken_sinclude,
2236 kKmkToken_dash_include,
2237 kKmkToken_includedep,
2238 kKmkToken_includedep_queue,
2239 kKmkToken_includedep_flush,
2240
2241 /* Others: */
2242 kKmkToken_define,
2243 kKmkToken_endef,
2244 kKmkToken_export,
2245 kKmkToken_unexport,
2246 kKmkToken_local,
2247 kKmkToken_override,
2248 kKmkToken_undefine
2249} KMKTOKEN;
2250
2251typedef struct KMKPARSER
2252{
2253 struct
2254 {
2255 KMKTOKEN enmToken;
2256 uint32_t iLine;
2257 bool fIgnoreNesting;
2258 } aDepth[64];
2259 unsigned iDepth;
2260 unsigned iActualDepth;
2261 bool fInRecipe;
2262
2263 /** The current line number (for error messages and peeking). */
2264 uint32_t iLine;
2265 /** The EOL type of the current line. */
2266 SCMEOL enmEol;
2267 /** The length of the current line. */
2268 size_t cchLine;
2269 /** Pointer to the start of the current line. */
2270 char const *pchLine;
2271
2272 /** @name Only used for rule/assignment parsing.
2273 * @{ */
2274 /** Number of continuation lines at current rule/assignment. */
2275 uint32_t cLines;
2276 /** Characters in continuation lines at current rule/assignment. */
2277 size_t cchTotalLine;
2278 /** @} */
2279
2280 /** The SCM rewriter state. */
2281 PSCMRWSTATE pState;
2282 /** The input stream. */
2283 PSCMSTREAM pIn;
2284 /** The output stream. */
2285 PSCMSTREAM pOut;
2286 /** The settings. */
2287 PCSCMSETTINGSBASE pSettings;
2288 /** Scratch buffer. */
2289 char szBuf[4096];
2290} KMKPARSER;
2291
2292static KMKTOKEN scmKmkIdentifyToken(const char *pchWord, size_t cchWord)
2293{
2294 static struct { const char *psz; uint32_t cch; KMKTOKEN enmToken; } s_aTokens[] =
2295 {
2296 { RT_STR_TUPLE("if"), kKmkToken_if },
2297 { RT_STR_TUPLE("ifeq"), kKmkToken_ifeq },
2298 { RT_STR_TUPLE("ifneq"), kKmkToken_ifneq },
2299 { RT_STR_TUPLE("if1of"), kKmkToken_if1of },
2300 { RT_STR_TUPLE("ifn1of"), kKmkToken_ifn1of },
2301 { RT_STR_TUPLE("ifdef"), kKmkToken_ifdef },
2302 { RT_STR_TUPLE("ifndef"), kKmkToken_ifndef },
2303 { RT_STR_TUPLE("else"), kKmkToken_else },
2304 { RT_STR_TUPLE("endif"), kKmkToken_endif },
2305 { RT_STR_TUPLE("include"), kKmkToken_include },
2306 { RT_STR_TUPLE("sinclude"), kKmkToken_sinclude },
2307 { RT_STR_TUPLE("-include"), kKmkToken_dash_include },
2308 { RT_STR_TUPLE("includedep"), kKmkToken_includedep },
2309 { RT_STR_TUPLE("includedep-queue"), kKmkToken_includedep_queue },
2310 { RT_STR_TUPLE("includedep-flush"), kKmkToken_includedep_flush },
2311 { RT_STR_TUPLE("define"), kKmkToken_define },
2312 { RT_STR_TUPLE("endef"), kKmkToken_endef },
2313 { RT_STR_TUPLE("export"), kKmkToken_export },
2314 { RT_STR_TUPLE("unexport"), kKmkToken_unexport },
2315 { RT_STR_TUPLE("local"), kKmkToken_local },
2316 { RT_STR_TUPLE("override"), kKmkToken_override },
2317 { RT_STR_TUPLE("undefine"), kKmkToken_undefine },
2318 };
2319 char chFirst = *pchWord;
2320 if ( chFirst == 'i'
2321 || chFirst == 'e'
2322 || chFirst == 'd'
2323 || chFirst == 's'
2324 || chFirst == '-'
2325 || chFirst == 'u'
2326 || chFirst == 'l'
2327 || chFirst == 'o')
2328 {
2329 for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
2330 if ( s_aTokens[i].cch == cchWord
2331 && *s_aTokens[i].psz == chFirst
2332 && memcmp(s_aTokens[i].psz, pchWord, cchWord) == 0)
2333 return s_aTokens[i].enmToken;
2334 }
2335#ifdef VBOX_STRICT
2336 else
2337 for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
2338 Assert(chFirst != *s_aTokens[i].psz);
2339#endif
2340
2341 if (chFirst == '#')
2342 return kKmkToken_Comment;
2343 return kKmkToken_Word;
2344}
2345
2346
2347/**
2348 * Gives up on the current line, copying it as it and requesting manual repair.
2349 */
2350static bool scmKmkGiveUp(KMKPARSER *pParser, const char *pszFormat, ...)
2351{
2352 va_list va;
2353 va_start(va, pszFormat);
2354 ScmFixManually(pParser->pState, "%u: %N\n", pParser->iLine, pszFormat, &va);
2355 va_end(va);
2356
2357 ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
2358 return false;
2359}
2360
2361
2362static bool scmKmkIsLineWithContinuationSlow(const char *pchLine, size_t cchLine)
2363{
2364 size_t cchSlashes = 1;
2365 cchLine--;
2366 while (cchSlashes < cchLine && pchLine[cchLine - cchSlashes - 1] == '\\')
2367 cchSlashes++;
2368 return RT_BOOL(cchSlashes & 1);
2369}
2370
2371
2372DECLINLINE(bool) scmKmkIsLineWithContinuation(const char *pchLine, size_t cchLine)
2373{
2374 if (cchLine == 0 || pchLine[cchLine - 1] != '\\')
2375 return false;
2376 return scmKmkIsLineWithContinuationSlow(pchLine, cchLine);
2377}
2378
2379
2380/**
2381 * Finds the length of a line where line continuation is in play.
2382 *
2383 * @returns Length from start of current line to the final unescaped EOL.
2384 * @param pParser The KMK parser state.
2385 * @param pcLine Where to return the number of lines. Optional.
2386 * @param pcchMaxLeadWord Where to return the max lead word length on
2387 * subsequent lines. Used to help balance multi-line
2388 * 'if' statements (imperfect). Optional.
2389 */
2390static size_t scmKmkLineContinuationPeek(KMKPARSER *pParser, uint32_t *pcLines, size_t *pcchMaxLeadWord)
2391{
2392 size_t const offSaved = ScmStreamTell(pParser->pIn);
2393 uint32_t cLines = 1;
2394 size_t cchMaxLeadWord = 0;
2395 const char *pchLine = pParser->pchLine;
2396 size_t cchLine = pParser->cchLine;
2397 SCMEOL enmEol;
2398 for (;;)
2399 {
2400 /* Return if no line continuation (or end of stream): */
2401 if ( cchLine == 0
2402 || !scmKmkIsLineWithContinuation(pchLine, cchLine)
2403 || ScmStreamIsEndOfStream(pParser->pIn))
2404 {
2405 ScmStreamSeekAbsolute(pParser->pIn, offSaved);
2406 if (pcLines)
2407 *pcLines = cLines;
2408 if (pcchMaxLeadWord)
2409 *pcchMaxLeadWord = cchMaxLeadWord;
2410 return (size_t)(pchLine - pParser->pchLine) + cchLine;
2411 }
2412
2413 /* Get the next line: */
2414 pchLine = ScmStreamGetLine(pParser->pIn, &cchLine, &enmEol);
2415 cLines++;
2416
2417 /* Check the length of the first word if requested: */
2418 if (pcchMaxLeadWord)
2419 {
2420 size_t offLine = 0;
2421 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2422 offLine++;
2423
2424 size_t const offStartWord = offLine;
2425 while (offLine < cchLine && !RT_C_IS_BLANK(pchLine[offLine]))
2426 offLine++;
2427
2428 if (offLine - offStartWord > cchMaxLeadWord)
2429 cchMaxLeadWord = offLine - offStartWord;
2430 }
2431 }
2432}
2433
2434
2435static bool scmKmkPushNesting(KMKPARSER *pParser, KMKTOKEN enmToken)
2436{
2437 uint32_t iDepth = pParser->iDepth;
2438 if (iDepth + 1 >= RT_ELEMENTS(pParser->aDepth))
2439 {
2440 ScmError(pParser->pState, VERR_ASN1_TOO_DEEPLY_NESTED /*?*/, "%u: Too deep if/define nesting!\n", pParser->iLine);
2441 return false;
2442 }
2443
2444 pParser->aDepth[iDepth].enmToken = enmToken;
2445 pParser->aDepth[iDepth].iLine = pParser->iLine;
2446 pParser->aDepth[iDepth].fIgnoreNesting = false;
2447 pParser->iDepth = iDepth + 1;
2448 pParser->iActualDepth += 1;
2449 return true;
2450}
2451
2452
2453/**
2454 * Skips a string stopping at @a chStop1 or @a chStop2, taking $() and ${} into
2455 * account.
2456 */
2457static size_t scmKmkSkipExpString(const char *pchLine, size_t cchLine, size_t off, char chStop1, char chStop2 = '\0')
2458{
2459 unsigned iExpDepth = 0;
2460 char ch;
2461 while ( off < cchLine
2462 && (ch = pchLine[off])
2463 && ( (ch != chStop1 && ch != chStop2)
2464 || iExpDepth > 0))
2465 {
2466 off++;
2467 if (ch == '$')
2468 {
2469 ch = pchLine[off];
2470 if (ch == '(' || ch == '{')
2471 {
2472 iExpDepth++;
2473 off++;
2474 }
2475 }
2476 else if ((ch == ')' || ch == '}') && iExpDepth > 0)
2477 iExpDepth--;
2478 }
2479 return off;
2480}
2481
2482
2483/** Context for scmKmkWordLength. */
2484typedef enum
2485{
2486 /** Target file or assignment.
2487 * Separators: space, '=', ':' */
2488 kKmkWordCtx_TargetFileOrAssignment,
2489 /** Target file.
2490 * Separators: space, ':' */
2491 kKmkWordCtx_TargetFile,
2492 /** Dependency file or (target variable) assignment.
2493 * Separators: space, '=', ':', '|' */
2494 kKmkWordCtx_DepFileOrAssignment,
2495 /** Dependency file.
2496 * Separators: space, '|' */
2497 kKmkWordCtx_DepFile
2498} KMKWORDCTX;
2499
2500/**
2501 * Finds the length of the word (file) @a offStart.
2502 *
2503 * @returns Length of word starting at @a offStart. Zero if there is whitespace
2504 * at given offset or it's beyond the end of the line (both cases will
2505 * assert).
2506 * @param pchLine The line.
2507 * @param cchLine The line length.
2508 * @param offStart Offset to the start of the word.
2509 */
2510static size_t scmKmkWordLength(const char *pchLine, size_t cchLine, size_t offStart, KMKWORDCTX enmCtx)
2511{
2512 AssertReturn(offStart < cchLine && !RT_C_IS_BLANK(pchLine[offStart]), 0);
2513 size_t off = offStart;
2514 while (off < cchLine)
2515 {
2516 char ch = pchLine[off];
2517 if (RT_C_IS_BLANK(ch))
2518 break;
2519
2520 if (ch == ':')
2521 {
2522 /*
2523 * Check for plain driver letter, omitting the archive member variant.
2524 */
2525 if (off - offStart != 1 || !RT_C_IS_ALPHA(pchLine[off - 1]))
2526 {
2527 if (off == offStart)
2528 {
2529 /* We need to check for single and double colon rules as well as
2530 simple and immediate assignments here. */
2531 off++;
2532 if (pchLine[off] == ':')
2533 {
2534 off++;
2535 if (pchLine[off] == '=')
2536 {
2537 if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
2538 return 3; /* ::= - immediate assignment. */
2539 off++;
2540 }
2541 else if (enmCtx != kKmkWordCtx_DepFile)
2542 return 2; /* :: - double colon rule */
2543 }
2544 else if (pchLine[off] == '=')
2545 {
2546 if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
2547 return 2; /* := - simple assignment. */
2548 off++;
2549 }
2550 else if (enmCtx != kKmkWordCtx_DepFile)
2551 return 1; /* : - regular rule. */
2552 continue;
2553 }
2554 /* ':' is a separator except in DepFile context. */
2555 else if (enmCtx != kKmkWordCtx_DepFile)
2556 return off - offStart;
2557 }
2558 }
2559 else if (ch == '=')
2560 {
2561 /*
2562 * Assignment. We check for the previous character too so we'll catch
2563 * append, prepend and conditional assignments. Simple and immediate
2564 * assignments are handled above.
2565 */
2566 if ( enmCtx == kKmkWordCtx_TargetFileOrAssignment
2567 || enmCtx == kKmkWordCtx_DepFileOrAssignment)
2568 {
2569 if (off > offStart)
2570 {
2571 ch = pchLine[off - 1];
2572 if (ch == '?' || ch == '+' || ch == '>')
2573 off = off - 1 == offStart
2574 ? off + 2 /* return '+=', '?=', '<=' */
2575 : off - 1; /* up to '+=', '?=', '<=' */
2576 else
2577 Assert(ch != ':'); /* handled above */
2578 }
2579 else
2580 off++; /* '=' */
2581 return off - offStart;
2582 }
2583 }
2584 else if (ch == '|')
2585 {
2586 /*
2587 * This is rather straight forward.
2588 */
2589 if (enmCtx == kKmkWordCtx_DepFileOrAssignment || enmCtx == kKmkWordCtx_DepFile)
2590 {
2591 if (off == offStart)
2592 return 1;
2593 return off - offStart;
2594 }
2595 }
2596 off++;
2597 }
2598 return off - offStart;
2599}
2600
2601
2602static bool scmKmkTailComment(KMKPARSER *pParser, const char *pchLine, size_t cchLine, size_t offSrc, char **ppszDst)
2603{
2604 /* Wind back offSrc to the first blank space (not all callers can do this). */
2605 Assert(offSrc <= cchLine);
2606 while (offSrc > 0 && RT_C_IS_SPACE(pchLine[offSrc - 1]))
2607 offSrc--;
2608 size_t const offSrcStart = offSrc;
2609
2610 /* Skip blanks. */
2611 while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
2612 offSrc++;
2613 if (offSrc >= cchLine)
2614 return true;
2615
2616 /* Is it a comment? */
2617 char *pszDst = *ppszDst;
2618 if (pchLine[offSrc] == '#')
2619 {
2620 /* Try preserve the start column number. */
2621/** @todo tabs */
2622 size_t const offDst = pszDst - pParser->szBuf;
2623 if (offDst < offSrc)
2624 {
2625 memset(pszDst, ' ', offSrc - offDst);
2626 pszDst += offSrc - offDst;
2627 }
2628 else if (offSrc != offSrcStart)
2629 *pszDst++ = ' ';
2630
2631 *ppszDst = pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchLine - offSrc);
2632 return false; /*dummy*/
2633 }
2634
2635 /* Complain and copy out the text unmodified. */
2636 ScmError(pParser->pState, VERR_PARSE_ERROR, "%u:%u: Expected comment, found: %.*s",
2637 pParser->iLine, offSrc, cchLine - offSrc, &pchLine[offSrc]);
2638 *ppszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchLine - offSrcStart);
2639 return false; /*dummy*/
2640}
2641
2642
2643/**
2644 * Deals with: ifeq, ifneq, if1of and ifn1of
2645 *
2646 * @returns dummy (false) to facility return + call.
2647 */
2648static bool scmKmkHandleIfParentheses(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
2649{
2650 const char * const pchLine = pParser->pchLine;
2651 size_t const cchLine = pParser->cchLine;
2652 uint32_t const cchIndent = pParser->iActualDepth
2653 - (fElse && pParser->iDepth > 0 && !pParser->aDepth[pParser->iDepth].fIgnoreNesting);
2654
2655 /*
2656 * Push it onto the stack. All these nestings are relevant.
2657 */
2658 if (!scmKmkPushNesting(pParser, enmToken))
2659 return false;
2660
2661 /*
2662 * We do not allow line continuation for these.
2663 */
2664 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2665 return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
2666
2667 /*
2668 * We stage the modified line in the buffer, so check that the line isn't
2669 * too long (it seriously should be).
2670 */
2671 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
2672 return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", cchToken, &pchLine[offToken], cchLine);
2673 char *pszDst = pParser->szBuf;
2674
2675 /*
2676 * Emit indent and initial token.
2677 */
2678 memset(pszDst, ' ', cchIndent);
2679 pszDst += cchIndent;
2680
2681 if (fElse)
2682 pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
2683
2684 memcpy(pszDst, &pchLine[offToken], cchToken);
2685 pszDst += cchToken;
2686
2687 size_t offSrc = offToken + cchToken;
2688
2689 /*
2690 * There shall be exactly one space between the token and the opening parenthesis.
2691 */
2692 if (pchLine[offSrc] == ' ' && pchLine[offSrc + 1] == '(')
2693 offSrc += 2;
2694 else
2695 {
2696 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
2697 offSrc++;
2698 if (pchLine[offSrc] != '(')
2699 return scmKmkGiveUp(pParser, "Expected '(' to follow '%.*s'", cchToken, &pchLine[offToken]);
2700 offSrc++;
2701 }
2702 *pszDst++ = ' ';
2703 *pszDst++ = '(';
2704
2705 /*
2706 * Skip spaces after the opening parenthesis.
2707 */
2708 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
2709 offSrc++;
2710
2711 /*
2712 * Work up to the ',' separator. It shall likewise not be preceeded by any spaces.
2713 * Need to take $(func 1,2,3) calls into account here, so we trac () and {} while
2714 * skipping ahead.
2715 */
2716 if (pchLine[offSrc] != ',')
2717 {
2718 size_t const offSrcStart = offSrc;
2719 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ',');
2720 if (pchLine[offSrc] != ',')
2721 return scmKmkGiveUp(pParser, "Expected ',' somewhere after '%.*s('", cchToken, &pchLine[offToken]);
2722
2723 size_t cchCopy = offSrc - offSrcStart;
2724 while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
2725 cchCopy--;
2726
2727 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
2728 }
2729 /* 'if1of(, stuff)' does not make sense in committed code: */
2730 else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
2731 return scmKmkGiveUp(pParser, "Left set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
2732 offSrc++;
2733 *pszDst++ = ',';
2734
2735 /*
2736 * For if1of and ifn1of we require a space after the comma, whereas ifeq and
2737 * ifneq shall not have any blanks. This is to help tell them apart.
2738 */
2739 if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
2740 {
2741 *pszDst++ = ' ';
2742 if (pchLine[offSrc] == ' ')
2743 offSrc++;
2744 }
2745 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
2746 offSrc++;
2747
2748 if (pchLine[offSrc] != ')')
2749 {
2750 size_t const offSrcStart = offSrc;
2751 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ')');
2752 if (pchLine[offSrc] != ')')
2753 return scmKmkGiveUp(pParser, "No closing parenthesis for '%.*s'?", cchToken, &pchLine[offToken]);
2754
2755 size_t cchCopy = offSrc - offSrcStart;
2756 while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
2757 cchCopy--;
2758
2759 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
2760 }
2761 /* 'if1of(stuff, )' does not make sense in committed code: */
2762 else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
2763 return scmKmkGiveUp(pParser, "Right set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
2764 offSrc++;
2765 *pszDst++ = ')';
2766
2767 /*
2768 * Handle comment.
2769 */
2770 if (offSrc < cchLine)
2771 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
2772
2773 /*
2774 * Done.
2775 */
2776 *pszDst = '\0';
2777 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
2778 return false; /* dummy */
2779}
2780
2781
2782/**
2783 * Deals with: if, ifdef and ifndef
2784 *
2785 * @returns dummy (false) to facility return + call.
2786 */
2787static bool scmKmkHandleIfSpace(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
2788{
2789 const char *pchLine = pParser->pchLine;
2790 size_t cchLine = pParser->cchLine;
2791 uint32_t const cchIndent = pParser->iActualDepth
2792 - (fElse && pParser->iDepth > 0 && !pParser->aDepth[pParser->iDepth].fIgnoreNesting);
2793
2794 /*
2795 * Push it onto the stack.
2796 *
2797 * For ifndef we ignore the outmost ifndef in non-Makefile.kmk files, if
2798 * the define matches the typical pattern for a file blocker.
2799 */
2800 if (!fElse)
2801 {
2802 if (!scmKmkPushNesting(pParser, enmToken))
2803 return false;
2804 }
2805 else
2806 {
2807 pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
2808 pParser->aDepth[pParser->iDepth - 1].iLine = pParser->iLine;
2809 }
2810 bool fIgnoredNesting = false;
2811 if (enmToken == kKmkToken_ifndef)
2812 {
2813 /** @todo */
2814 }
2815
2816 /*
2817 * We do not allow line continuation for these.
2818 */
2819 uint32_t cLines = 1;
2820 size_t cchMaxLeadWord = 0;
2821 size_t cchTotalLine = cchLine;
2822 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
2823 {
2824 if (enmToken != kKmkToken_if)
2825 return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
2826 cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord);
2827 }
2828
2829 /*
2830 * We stage the modified line in the buffer, so check that the line isn't
2831 * too long (plain if can be long, but not ifndef/ifdef).
2832 */
2833 if (cchTotalLine + pParser->iActualDepth + 32 > sizeof(pParser->szBuf))
2834 return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars",
2835 cchToken, &pchLine[offToken], cchTotalLine);
2836 char *pszDst = pParser->szBuf;
2837
2838 /*
2839 * Emit indent and initial token.
2840 */
2841 memset(pszDst, ' ', cchIndent);
2842 pszDst += cchIndent;
2843
2844 if (fElse)
2845 pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
2846
2847 memcpy(pszDst, &pchLine[offToken], cchToken);
2848 pszDst += cchToken;
2849
2850 size_t offSrc = offToken + cchToken;
2851
2852 /*
2853 * ifndef/ifdef shall have exactly one space. For 'if' we allow up to 4, but
2854 * we'll deal with that further down.
2855 */
2856 size_t cchSpaces = 0;
2857 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
2858 {
2859 cchSpaces++;
2860 offSrc++;
2861 }
2862 if (cchSpaces == 0)
2863 return scmKmkGiveUp(pParser, "Nothing following '%.*s' or bogus line continuation?", cchToken, &pchLine[offToken]);
2864 *pszDst++ = ' ';
2865
2866 /*
2867 * For ifdef and ifndef there now comes a single word.
2868 */
2869 if (enmToken != kKmkToken_if)
2870 {
2871 size_t const offSrcStart = offSrc;
2872 offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ' ', '\t'); /** @todo probably not entirely correct */
2873 if (offSrc == offSrcStart)
2874 return scmKmkGiveUp(pParser, "No word following '%.*s'?", cchToken, &pchLine[offToken]);
2875
2876 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], offSrc - offSrcStart);
2877 }
2878 /*
2879 * While for 'if' things are more complicated, especially if it spans more
2880 * than one line.
2881 */
2882 else if (cLines <= 1)
2883 {
2884 /* Single line expression: Just assume the expression goes up to the
2885 EOL or comment hash. Strip and copy as-is for now. */
2886 const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
2887 size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
2888 while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
2889 cchExpr--;
2890
2891 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
2892 offSrc += cchExpr;
2893 }
2894 else
2895 {
2896 /* Multi line expression: We normalize leading whitespace using
2897 cchMaxLeadWord for now. Expression on line 2+ are indented by two
2898 extra characters, because we'd otherwise be puttin the operator on
2899 the same level as the 'if', which would be confusing. Thus:
2900
2901 if expr1
2902 + expr2
2903 endif
2904
2905 if expr1
2906 || expr2
2907 endif
2908
2909 if expr3
2910 vtg expr4
2911 endif
2912
2913 We do '#' / EOL handling for the final line the same way as above.
2914
2915 Later we should add the ability to rework the expression properly,
2916 making sure new lines starts with operators and such. */
2917 /** @todo Implement simples expression parser and indenter, possibly also
2918 * removing unnecessary parentheses. Can be shared with C/C++. */
2919 if (cchMaxLeadWord > 3)
2920 return scmKmkGiveUp(pParser,
2921 "Bogus multi-line 'if' expression! Extra lines must start with operator (cchMaxLeadWord=%u).",
2922 cchMaxLeadWord);
2923 memset(pszDst, ' ', cchMaxLeadWord);
2924 pszDst += cchMaxLeadWord;
2925
2926 size_t cchSrcContIndent = offToken + 2;
2927 for (uint32_t iSubLine = 0; iSubLine < cLines - 1; iSubLine++)
2928 {
2929 /* Trim the line. */
2930 size_t offSrcEnd = cchLine;
2931 Assert(pchLine[offSrcEnd - 1] == '\\');
2932 offSrcEnd--;
2933
2934 if (pchLine[offSrcEnd - 1] == '\\')
2935 return scmKmkGiveUp(pParser, "Escaped '\\' before line continuation in 'if' expression is not allowed!");
2936
2937 while (offSrcEnd > offSrc && RT_C_IS_BLANK(pchLine[offSrcEnd - 1]))
2938 offSrcEnd--;
2939
2940 /* Comments with line continuation is not allowed in commited makefiles. */
2941 if (offSrc < offSrcEnd && memchr(&pchLine[offSrc], '#', cchLine - offSrc) != NULL)
2942 return scmKmkGiveUp(pParser, "Comment in multi-line 'if' expression is not allowed to start before the final line!");
2943
2944 /* Output it. */
2945 if (offSrc < offSrcEnd)
2946 {
2947 if (iSubLine > 0 && offSrc > cchSrcContIndent)
2948 {
2949 memset(pszDst, ' ', offSrc - cchSrcContIndent);
2950 pszDst += offSrc - cchSrcContIndent;
2951 }
2952 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], offSrcEnd - offSrc);
2953 *pszDst++ = ' ';
2954 }
2955 else if (iSubLine == 0)
2956 return scmKmkGiveUp(pParser, "Expected expression after 'if', not line continuation!");
2957 *pszDst++ = '\\';
2958 *pszDst = '\0';
2959 size_t cchDst = (size_t)(pszDst - pParser->szBuf);
2960 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
2961
2962 /*
2963 * Fetch the next line and start processing it.
2964 */
2965 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
2966 if (!pchLine)
2967 {
2968 ScmError(pParser->pState, VERR_INTERNAL_ERROR_3, "ScmStreamGetLine unexpectedly returned NULL!");
2969 return false;
2970 }
2971 cchLine = pParser->cchLine;
2972 pParser->iLine++;
2973
2974 /* Skip leading whitespace and adjust the source continuation indent: */
2975 offSrc = 0;
2976 while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
2977 offSrc++;
2978 /** @todo tabs */
2979
2980 if (iSubLine == 0)
2981 cchSrcContIndent = offSrc;
2982
2983 /* Initial indent: */
2984 pszDst = pParser->szBuf;
2985 memset(pszDst, ' ', cchIndent + 2);
2986 pszDst += cchIndent + 2;
2987 }
2988
2989 /* Output the expression on the final line. */
2990 const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
2991 size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
2992 while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
2993 cchExpr--;
2994
2995 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
2996 offSrc += cchExpr;
2997 }
2998
2999
3000 /*
3001 * Handle comment.
3002 *
3003 * Here we check for the "scm:ignore-nesting" directive that makes us not
3004 * add indentation for this directive. We do this on the destination buffer
3005 * as that can be zero terminated and is therefore usable with strstr.
3006 */
3007 if (offSrc >= cchLine)
3008 *pszDst = '\0';
3009 else
3010 {
3011 char * const pszDstSrc = pszDst;
3012 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
3013 *pszDst = '\0';
3014
3015 /* Check for special comment making us ignore the nesting. We do this in the
3016
3017 */
3018 if (!fIgnoredNesting && strstr(pszDstSrc, "scm:ignore-nesting") != NULL)
3019 {
3020 pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting = true;
3021 pParser->iActualDepth--;
3022 }
3023 }
3024
3025 /*
3026 * Done.
3027 */
3028 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3029 return false; /* dummy */
3030}
3031
3032
3033/**
3034 * Deals with: else
3035 *
3036 * @returns dummy (false) to facility return + call.
3037 */
3038static bool scmKmkHandleElse(KMKPARSER *pParser, size_t offToken)
3039{
3040 const char * const pchLine = pParser->pchLine;
3041 size_t const cchLine = pParser->cchLine;
3042
3043 if (pParser->iDepth < 1)
3044 return scmKmkGiveUp(pParser, "Lone 'else'");
3045 uint32_t const cchIndent = pParser->iActualDepth - !pParser->aDepth[pParser->iDepth].fIgnoreNesting;
3046
3047 /*
3048 * Look past the else and check if there any ifxxx token following it.
3049 */
3050 size_t offSrc = offToken + 4;
3051 while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
3052 offSrc++;
3053 if (offSrc < cchLine)
3054 {
3055 size_t cchWord = 0;
3056 while (offSrc + cchWord < cchLine && RT_C_IS_ALNUM(pchLine[offSrc + cchWord]))
3057 cchWord++;
3058 if (cchWord)
3059 {
3060 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offSrc], cchWord);
3061 switch (enmToken)
3062 {
3063 case kKmkToken_ifeq:
3064 case kKmkToken_ifneq:
3065 case kKmkToken_if1of:
3066 case kKmkToken_ifn1of:
3067 return scmKmkHandleIfParentheses(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
3068
3069 case kKmkToken_ifdef:
3070 case kKmkToken_ifndef:
3071 case kKmkToken_if:
3072 return scmKmkHandleIfSpace(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
3073
3074 default:
3075 break;
3076 }
3077 }
3078 }
3079
3080 /*
3081 * We do not allow line continuation for these.
3082 */
3083 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
3084 return scmKmkGiveUp(pParser, "Line continuation not allowed with 'else' directive.");
3085
3086 /*
3087 * We stage the modified line in the buffer, so check that the line isn't
3088 * too long (it seriously should be).
3089 */
3090 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
3091 return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
3092 char *pszDst = pParser->szBuf;
3093
3094 /*
3095 * Emit indent and initial token.
3096 */
3097 memset(pszDst, ' ', cchIndent);
3098 pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("else"));
3099
3100 offSrc = offToken + 4;
3101
3102 /*
3103 * Handle comment.
3104 */
3105 if (offSrc < cchLine)
3106 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
3107
3108 /*
3109 * Done.
3110 */
3111 *pszDst = '\0';
3112 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3113 return false; /* dummy */
3114}
3115
3116
3117/**
3118 * Deals with: endif
3119 *
3120 * @returns dummy (false) to facility return + call.
3121 */
3122static bool scmKmkHandleEndif(KMKPARSER *pParser, size_t offToken)
3123{
3124 const char * const pchLine = pParser->pchLine;
3125 size_t const cchLine = pParser->cchLine;
3126
3127 /*
3128 * Pop a nesting.
3129 */
3130 if (pParser->iDepth < 1)
3131 return scmKmkGiveUp(pParser, "Lone 'endif'");
3132 uint32_t iDepth = pParser->iDepth - 1;
3133 pParser->iDepth = iDepth;
3134 if (!pParser->aDepth[iDepth].fIgnoreNesting)
3135 {
3136 AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++);
3137 pParser->iActualDepth -= 1;
3138 }
3139 uint32_t const cchIndent = pParser->iActualDepth;
3140
3141 /*
3142 * We do not allow line continuation for these.
3143 */
3144 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
3145 return scmKmkGiveUp(pParser, "Line continuation not allowed with 'endif' directive.");
3146
3147 /*
3148 * We stage the modified line in the buffer, so check that the line isn't
3149 * too long (it seriously should be).
3150 */
3151 if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
3152 return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
3153 char *pszDst = pParser->szBuf;
3154
3155 /*
3156 * Emit indent and initial token.
3157 */
3158 memset(pszDst, ' ', cchIndent);
3159 pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("endif"));
3160
3161 size_t offSrc = offToken + 5;
3162
3163 /*
3164 * Handle comment.
3165 */
3166 if (offSrc < cchLine)
3167 scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
3168
3169 /*
3170 * Done.
3171 */
3172 *pszDst = '\0';
3173 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3174 return false; /* dummy */
3175}
3176
3177
3178/**
3179 * Passing thru any line continuation lines following the current one.
3180 */
3181static bool scmKmkPassThruLineContinuationLines(KMKPARSER *pParser)
3182{
3183 while (scmKmkIsLineWithContinuation(pParser->pchLine, pParser->cchLine))
3184 {
3185 pParser->pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3186 if (!pParser->pchLine)
3187 break;
3188 ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
3189 }
3190 return false; /* dummy */
3191}
3192
3193
3194/**
3195 * For dealing with a directive w/o special formatting rules (yet).
3196 *
3197 * @returns dummy (false) to facility return + call.
3198 */
3199static bool scmKmkHandleSimple(KMKPARSER *pParser, size_t offToken, bool fIndentIt = true)
3200{
3201 const char *pchLine = pParser->pchLine;
3202 size_t cchLine = pParser->cchLine;
3203 uint32_t const cchIndent = fIndentIt ? pParser->iActualDepth : 0;
3204
3205 /*
3206 * Just reindent the statement.
3207 */
3208 ScmStreamWrite(pParser->pOut, g_szSpaces, cchIndent);
3209 ScmStreamWrite(pParser->pOut, &pchLine[offToken], cchLine - offToken);
3210 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
3211
3212 /*
3213 * Check for line continuation and output concatenated lines.
3214 */
3215 scmKmkPassThruLineContinuationLines(pParser);
3216 return false; /* dummy */
3217}
3218
3219
3220static bool scmKmkHandleDefine(KMKPARSER *pParser, size_t offToken)
3221{
3222 /* Assignments takes us out of recipe mode. */
3223 pParser->fInRecipe = false;
3224
3225 return scmKmkHandleSimple(pParser, offToken);
3226}
3227
3228
3229static bool scmKmkHandleEndef(KMKPARSER *pParser, size_t offToken)
3230{
3231 /* Leaving a define resets the recipt mode. */
3232 pParser->fInRecipe = false;
3233
3234 return scmKmkHandleSimple(pParser, offToken);
3235}
3236
3237
3238typedef enum KMKASSIGNTYPE
3239{
3240 kKmkAssignType_Recursive,
3241 kKmkAssignType_Conditional,
3242 kKmkAssignType_Appending,
3243 kKmkAssignType_Prepending,
3244 kKmkAssignType_Simple,
3245 kKmkAssignType_Immediate
3246} KMKASSIGNTYPE;
3247
3248
3249/**
3250 * @returns dummy (false) to facility return + call.
3251 */
3252static bool scmKmkHandleAssignment2(KMKPARSER *pParser, size_t offVarStart, size_t offVarEnd, KMKASSIGNTYPE enmType,
3253 size_t offAssignOp, unsigned fFlags)
3254{
3255 unsigned const cchIndent = pParser->iActualDepth;
3256 const char *pchLine = pParser->pchLine;
3257 size_t cchLine = pParser->cchLine;
3258 uint32_t const cLines = pParser->cLines;
3259 uint32_t iSubLine = 0;
3260
3261 RT_NOREF(fFlags);
3262 Assert(offVarStart < cchLine);
3263 Assert(offVarEnd <= cchLine);
3264 Assert(offVarStart < offVarEnd);
3265 Assert(!RT_C_IS_SPACE(pchLine[offVarStart]));
3266 Assert(!RT_C_IS_SPACE(pchLine[offVarEnd - 1]));
3267
3268 /* Assignments takes us out of recipe mode. */
3269 pParser->fInRecipe = false;
3270
3271 /* This is too much hazzle to deal with. */
3272 if (cLines > 0 && pchLine[cchLine - 2] == '\\')
3273 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3274 if (cchLine + 64 > sizeof(pParser->szBuf))
3275 return scmKmkGiveUp(pParser, "Line too long!");
3276
3277 /*
3278 * Indent and output the variable name.
3279 */
3280 char *pszDst = pParser->szBuf;
3281 memset(pszDst, ' ', cchIndent);
3282 pszDst += cchIndent;
3283 pszDst = (char *)mempcpy(pszDst, &pchLine[offVarStart], offVarEnd - offVarStart);
3284
3285 /*
3286 * Try preserve the assignment operator position, but make sure we've got a
3287 * space in front of it.
3288 */
3289 if (offAssignOp < cchLine)
3290 {
3291 size_t offDst = (size_t)(pszDst - pParser->szBuf);
3292 size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings);
3293 if (offDst < offEffAssignOp)
3294 {
3295 size_t cchSpacesToWrite = offEffAssignOp - offDst;
3296 memset(pszDst, ' ', cchSpacesToWrite);
3297 pszDst += cchSpacesToWrite;
3298 }
3299 else
3300 *pszDst++ = ' ';
3301 }
3302 else
3303 {
3304 /* Pull up the assignment operator to the variable line. */
3305 *pszDst++ = ' ';
3306
3307 /* Eat up lines till we hit the operator. */
3308 while (offAssignOp < cchLine)
3309 {
3310 const char * const pchPrevLine = pchLine;
3311 Assert(iSubLine + 1 < cLines);
3312 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3313 AssertReturn(pchLine, false /*dummy*/);
3314 cchLine = pParser->cchLine;
3315 iSubLine++;
3316 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3317 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3318
3319 /* Adjust offAssignOp: */
3320 offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
3321 Assert(offAssignOp < ~(size_t)0 / 2);
3322 }
3323
3324 if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf))
3325 return scmKmkGiveUp(pParser, "Line too long!");
3326 }
3327
3328 /*
3329 * Emit the operator.
3330 */
3331 size_t offLine = offAssignOp;
3332 switch (enmType)
3333 {
3334 default:
3335 AssertReleaseFailed();
3336 RT_FALL_THRU();
3337 case kKmkAssignType_Recursive:
3338 *pszDst++ = '=';
3339 Assert(pchLine[offLine] == '=');
3340 offLine++;
3341 break;
3342 case kKmkAssignType_Conditional:
3343 *pszDst++ = '?';
3344 *pszDst++ = '=';
3345 Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '=');
3346 offLine += 2;
3347 break;
3348 case kKmkAssignType_Appending:
3349 *pszDst++ = '+';
3350 *pszDst++ = '=';
3351 Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '=');
3352 offLine += 2;
3353 break;
3354 case kKmkAssignType_Prepending:
3355 *pszDst++ = '>';
3356 *pszDst++ = '=';
3357 Assert(pchLine[offLine] == '>'); Assert(pchLine[offLine + 1] == '=');
3358 offLine += 2;
3359 break;
3360 case kKmkAssignType_Immediate:
3361 *pszDst++ = ':';
3362 Assert(pchLine[offLine] == ':');
3363 offLine++;
3364 RT_FALL_THRU();
3365 case kKmkAssignType_Simple:
3366 *pszDst++ = ':';
3367 *pszDst++ = '=';
3368 Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '=');
3369 offLine += 2;
3370 break;
3371 }
3372
3373 /*
3374 * Skip space till we hit the value or comment.
3375 */
3376 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3377 offLine++;
3378
3379/** @todo this block can probably be merged into the final loop below. */
3380 unsigned cPendingEols = 0;
3381 unsigned const iSubLineStart1 = iSubLine;
3382 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3383 {
3384 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3385 AssertReturn(pchLine, false /*dummy*/);
3386 cchLine = pParser->cchLine;
3387 iSubLine++;
3388 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3389 {
3390 *pszDst++ = ' ';
3391 *pszDst++ = '\\';
3392 *pszDst = '\0';
3393 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3394 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3395 }
3396 cPendingEols = 1;
3397
3398 /* Skip indent/whitespace. */
3399 offLine = 0;
3400 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3401 offLine++;
3402 }
3403
3404 /*
3405 * Okay, we've gotten to the value / comment part.
3406 */
3407 for (;;)
3408 {
3409 /*
3410 * The end? Flush what we've got.
3411 */
3412 if (offLine == cchLine)
3413 {
3414 Assert(iSubLine + 1 == cLines);
3415 *pszDst = '\0';
3416 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3417 return false; /* dummy */
3418 }
3419
3420 /*
3421 * Output any non-comment stuff, stripping off newlines.
3422 */
3423 const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
3424 if (pchHash != &pchLine[offLine])
3425 {
3426 /* Add space or flush pending EOLs. */
3427 if (!cPendingEols)
3428 *pszDst++ = ' ';
3429 else
3430 {
3431 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
3432 do
3433 {
3434 *pszDst++ = ' ';
3435 *pszDst++ = '\\';
3436 *pszDst = '\0';
3437 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3438
3439 pszDst = pParser->szBuf;
3440 memset(pszDst, ' ', cchIndent);
3441 pszDst += cchIndent;
3442 *pszDst++ = '\t';
3443 cPendingEols--;
3444 } while (cPendingEols > 0);
3445 }
3446
3447 /* Strip backwards. */
3448 size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
3449 size_t offValueEnd = offValueEnd2;
3450 while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
3451 offValueEnd--;
3452 Assert(offValueEnd > offLine);
3453
3454 /* Append the value part we found. */
3455 pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
3456 offLine = offValueEnd2;
3457 }
3458
3459 /*
3460 * If we found a comment hash, emit it and whatever follows just as-is w/o
3461 * any particular reformatting. Comments within a variable definition are
3462 * usually to disable portitions of a property like _DEFS or _SOURCES.
3463 */
3464 if (pchHash != NULL)
3465 {
3466 if (cPendingEols == 0)
3467 scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst);
3468 size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
3469 *pszDst = '\0';
3470 ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
3471
3472 if (cPendingEols > 1)
3473 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
3474
3475 if (cPendingEols > 0)
3476 ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
3477 scmKmkPassThruLineContinuationLines(pParser);
3478 return false; /* dummy */
3479 }
3480
3481 /*
3482 * Fetch another line, if we've got one.
3483 */
3484 if (iSubLine + 1 >= cLines)
3485 Assert(offLine == cchLine);
3486 else
3487 {
3488 Assert(offLine + 1 == cchLine);
3489 unsigned const iSubLineStart2 = iSubLine;
3490 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3491 {
3492 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3493 AssertReturn(pchLine, false /*dummy*/);
3494 cchLine = pParser->cchLine;
3495 iSubLine++;
3496 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3497 {
3498 *pszDst++ = ' ';
3499 *pszDst++ = '\\';
3500 *pszDst = '\0';
3501 ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
3502 if (cPendingEols > 1)
3503 ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
3504 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3505 }
3506 cPendingEols++;
3507
3508 /* Deal with indent/whitespace. */
3509 offLine = 0;
3510 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3511 offLine++;
3512 }
3513 }
3514 }
3515}
3516
3517
3518/**
3519 * A rule.
3520 *
3521 * This is a bit involved. Sigh.
3522 *
3523 * @returns dummy (false) to facility return + call.
3524 */
3525static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon)
3526{
3527 SCMSTREAM *pOut = pParser->pOut;
3528 unsigned const cchIndent = pParser->iActualDepth;
3529 const char *pchLine = pParser->pchLine;
3530 size_t cchLine = pParser->cchLine;
3531 Assert(offFirstWord < cchLine);
3532 uint32_t const cLines = pParser->cLines;
3533 uint32_t iSubLine = 0;
3534
3535 /* Following this, we'll be in recipe-mode. */
3536 pParser->fInRecipe = true;
3537
3538 /* This is too much hazzle to deal with. */
3539 if (cLines > 0 && pchLine[cchLine - 2] == '\\')
3540 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3541
3542 /* Too special case. */
3543 if (offColon <= offFirstWord)
3544 return scmKmkGiveUp(pParser, "Missing target file before colon!");
3545
3546 /*
3547 * Indent it.
3548 */
3549 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3550 size_t offLine = offFirstWord;
3551
3552 /*
3553 * Process word by word past the colon, taking new lines into account.
3554 *
3555 */
3556 KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment;
3557 bool fPendingEol = false;
3558 for (;;)
3559 {
3560 /*
3561 * Output the next word.
3562 */
3563 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx);
3564 Assert(offLine + cchWord <= offColon);
3565 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
3566 offLine += cchWord;
3567
3568 /* Skip whitespace (if any). */
3569 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3570 offLine++;
3571
3572 /* Have we reached the colon already? */
3573 if (offLine >= offColon)
3574 {
3575 Assert(pchLine[offLine] == ':');
3576 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
3577 offLine += fDoubleColon ? 2 : 1;
3578
3579 ScmStreamPutCh(pOut, ':');
3580 if (fDoubleColon)
3581 ScmStreamPutCh(pOut, ':');
3582 break;
3583 }
3584
3585 /* Deal with new line and emit indentation. */
3586 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3587 {
3588 /* Get the next input line. */
3589 for (;;)
3590 {
3591 const char * const pchPrevLine = pchLine;
3592 Assert(iSubLine + 1 < cLines);
3593 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3594 AssertReturn(pchLine, false /*dummy*/);
3595 cchLine = pParser->cchLine;
3596 iSubLine++;
3597 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3598 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3599
3600 /* Adjust offColon: */
3601 offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
3602 Assert(offColon < ~(size_t)0 / 2);
3603
3604 /* Skip leading spaces. */
3605 offLine = 0;
3606 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3607 offLine++;
3608
3609 /* Just drop empty lines. */
3610 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3611 continue;
3612
3613 /* Complete the current line and emit indent, unless we reached the colon: */
3614 if (offLine >= offColon)
3615 {
3616 Assert(pchLine[offLine] == ':');
3617 Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
3618 offLine += fDoubleColon ? 2 : 1;
3619
3620 ScmStreamPutCh(pOut, ':');
3621 if (fDoubleColon)
3622 ScmStreamPutCh(pOut, ':');
3623
3624 fPendingEol = true;
3625 break;
3626 }
3627 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
3628 ScmStreamPutEol(pOut, pParser->enmEol);
3629 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3630 }
3631 if (offLine >= offColon)
3632 break;
3633 }
3634 else
3635 ScmStreamPutCh(pOut, ' ');
3636 enmCtx = kKmkWordCtx_TargetFile;
3637 }
3638
3639 /*
3640 * We're immediately past the colon now, so eat whitespace and newlines and
3641 * whatever till we get to a solid word.
3642 */
3643 /* Skip spaces - there should be exactly one. */
3644 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3645 offLine++;
3646
3647 /* Deal with new lines: */
3648 while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3649 {
3650 fPendingEol = true;
3651
3652 Assert(iSubLine + 1 < cLines);
3653 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3654 AssertReturn(pchLine, false /*dummy*/);
3655 cchLine = pParser->cchLine;
3656 iSubLine++;
3657 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3658 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3659
3660 /* Skip leading spaces. */
3661 offLine = 0;
3662 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3663 offLine++;
3664
3665 /* Just drop empty lines. */
3666 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3667 continue;
3668 }
3669
3670 /*
3671 * Special case: No dependencies.
3672 */
3673 if (offLine == cchLine && iSubLine >= cLines)
3674 {
3675 ScmStreamPutEol(pOut, pParser->enmEol);
3676 return false /*dummy*/;
3677 }
3678
3679 /*
3680 * Work the dependencies word for word. Indent in spaces + two tabs.
3681 * (Pattern rules will also end up here, but we'll just ignore that for now.)
3682 */
3683 enmCtx = kKmkWordCtx_DepFileOrAssignment;
3684 for (;;)
3685 {
3686 /* Indent the next word. */
3687 if (!fPendingEol)
3688 ScmStreamPutCh(pOut, ' ');
3689 else
3690 {
3691 ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
3692 ScmStreamPutEol(pOut, pParser->enmEol);
3693 ScmStreamWrite(pOut, g_szSpaces, cchIndent);
3694 ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
3695 fPendingEol = false;
3696 }
3697
3698 /* Get the next word and output it. */
3699 size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx);
3700 Assert(offLine + cchWord <= cchLine);
3701
3702 ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
3703 offLine += cchWord;
3704
3705 /* Skip whitespace (if any). */
3706 size_t cchSpaces = 0;
3707 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3708 {
3709 cchSpaces++;
3710 offLine++;
3711 }
3712
3713 /* Deal with new line and emit indentation. */
3714 if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
3715 {
3716 /* Get the next input line. */
3717 unsigned cEmptyLines = 0;
3718 for (;;)
3719 {
3720 Assert(iSubLine + 1 < cLines);
3721 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
3722 AssertReturn(pchLine, false /*dummy*/);
3723 cchLine = pParser->cchLine;
3724 iSubLine++;
3725 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
3726 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
3727
3728 /* Skip leading spaces. */
3729 offLine = 0;
3730 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
3731 offLine++;
3732
3733 /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
3734 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
3735 {
3736 cEmptyLines++;
3737 continue;
3738 }
3739
3740 fPendingEol = true;
3741 break;
3742 }
3743 }
3744
3745 if (offLine >= cchLine)
3746 {
3747 /* End of input. */
3748/** @todo deal with comments */
3749 Assert(iSubLine + 1 == cLines);
3750 ScmStreamPutEol(pOut, pParser->enmEol);
3751 return false; /* dummmy */
3752 }
3753 enmCtx = kKmkWordCtx_DepFile;
3754 }
3755}
3756
3757
3758/**
3759 * Checks if the (extended) line is a variable assignment.
3760 *
3761 * We scan past line continuation stuff here as the assignment operator could be
3762 * on the next line, even if that's very unlikely it is recommened by the coding
3763 * guide lines if the line needs to be split. Fortunately, though, the caller
3764 * already removes empty empty leading lines, so we only have to consider the
3765 * line continuation issue if no '=' was found on the first line.
3766 *
3767 * @returns Modified or not.
3768 * @param pParser The parser.
3769 * @param cLines Number of lines to consider.
3770 * @param cchTotalLine Total length of all the lines to consider.
3771 * @param offWord Where the first word of the line starts.
3772 * @param pfIsAssignment Where to return whether this is an assignment or
3773 * not.
3774 */
3775static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord)
3776{
3777 const char *pchLine = pParser->pchLine;
3778 size_t const cchTotalLine = pParser->cchTotalLine;
3779
3780 /*
3781 * Scan words till we find ':' or '='.
3782 */
3783 uint32_t iWord = 0;
3784 size_t offCurWord = offWord;
3785 size_t offEndPrev = 0;
3786 size_t offLine = offWord;
3787 while (offLine < cchTotalLine)
3788 {
3789 char ch = pchLine[offLine++];
3790 if (ch == '$')
3791 {
3792 /*
3793 * Skip variable expansion.
3794 */
3795 char const chOpen = pchLine[offLine++];
3796 if (chOpen == '(' || chOpen == '{')
3797 {
3798 char const chClose = chOpen == '(' ? ')' : '}';
3799 unsigned cDepth = 1;
3800 while (offLine < cchTotalLine)
3801 {
3802 ch = pchLine[offLine++];
3803 if (ch == chOpen)
3804 cDepth++;
3805 else if (ch == chClose)
3806 if (!--cDepth)
3807 break;
3808 }
3809 }
3810 /* else: $x or $$, so just skip the next character. */
3811 }
3812 else if (RT_C_IS_SPACE(ch))
3813 {
3814 /*
3815 * End of word. Skip whitespace till the next word starts.
3816 */
3817 offEndPrev = offLine - 1;
3818 Assert(offLine != offWord);
3819 while (offLine < cchTotalLine)
3820 {
3821 ch = pchLine[offLine];
3822 if (RT_C_IS_SPACE(ch))
3823 offLine++;
3824 else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n'))
3825 offLine += 2;
3826 else
3827 break;
3828 }
3829 offCurWord = offLine;
3830 iWord++;
3831
3832 /*
3833 * To simplify the assignment operator checks, we just check the
3834 * start of the 2nd word when we're here.
3835 */
3836 if (iWord == 1 && offLine < cchTotalLine)
3837 {
3838 ch = pchLine[offLine];
3839 if (ch == '=')
3840 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
3841 if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
3842 {
3843 if (ch == ':')
3844 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0);
3845 if (ch == '+')
3846 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0);
3847 if (ch == '>')
3848 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0);
3849 if (ch == '?')
3850 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
3851 }
3852 else if ( ch == ':'
3853 && pchLine[offLine + 1] == ':'
3854 && pchLine[offLine + 2] == '=')
3855 return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
3856
3857 /* Check for rule while we're here. */
3858 if (ch == ':')
3859 return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
3860 }
3861 }
3862 /*
3863 * If '=' is found in the first word it's an assignment.
3864 */
3865 else if (ch == '=')
3866 {
3867 if (iWord == 0)
3868 {
3869 KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
3870 ch = pchLine[offLine - 2];
3871 if (ch == '+')
3872 enmType = kKmkAssignType_Appending;
3873 else if (ch == '?')
3874 enmType = kKmkAssignType_Conditional;
3875 else if (ch == '>')
3876 enmType = kKmkAssignType_Prepending;
3877 else
3878 Assert(ch != ':');
3879 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
3880 }
3881 }
3882 /*
3883 * When ':' is found it can mean a drive letter, a rule or in the
3884 * first word a simple or immediate assignment.
3885 */
3886 else if (ch == ':')
3887 {
3888 /* Check for drive letters (we ignore the archive form): */
3889 if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
3890 { /* ignore */ }
3891 else
3892 {
3893 /* Simple or immediate assignment? */
3894 ch = pchLine[offLine];
3895 if (iWord == 0)
3896 {
3897 if (ch == '=')
3898 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
3899 if (ch == ':' && pchLine[offLine + 1] == '=')
3900 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
3901 }
3902
3903 /* Okay, it's a rule then. */
3904 return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
3905 }
3906 }
3907 }
3908
3909 /*
3910 * If we didn't find anything, output it as-as.
3911 * We use scmKmkHandleSimple in a special way to do this.
3912 */
3913 ScmVerbose(pParser->pState, 1, "debug: %u: Unable to make sense of this line!", pParser->iLine);
3914 return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
3915}
3916
3917
3918static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord,
3919 bool fMustBeAssignment)
3920{
3921 /* Assignments takes us out of recipe mode. */
3922 pParser->fInRecipe = false;
3923
3924 RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment);
3925 return scmKmkHandleSimple(pParser, offToken);
3926}
3927
3928
3929/**
3930 * Rewrite a kBuild makefile.
3931 *
3932 * @returns kScmMaybeModified or kScmUnmodified.
3933 * @param pIn The input stream.
3934 * @param pOut The output stream.
3935 * @param pSettings The settings.
3936 *
3937 * @todo
3938 *
3939 * Ideas for Makefile.kmk and Config.kmk:
3940 * - sort if1of/ifn1of sets.
3941 * - line continuation slashes should only be preceded by one space.
3942 */
3943SCMREWRITERRES rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3944{
3945 if (!pSettings->fStandarizeKmk)
3946 return kScmUnmodified;
3947
3948 /*
3949 * Parser state.
3950 */
3951 KMKPARSER Parser;
3952 Parser.iDepth = 0;
3953 Parser.iActualDepth = 0;
3954 Parser.fInRecipe = false;
3955 Parser.iLine = 0;
3956 Parser.pState = pState;
3957 Parser.pIn = pIn;
3958 Parser.pOut = pOut;
3959 Parser.pSettings = pSettings;
3960
3961 /*
3962 * Iterate the file.
3963 */
3964 const char *pchLine;
3965 while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL)
3966 {
3967 size_t cchLine = Parser.cchLine;
3968 Parser.iLine++;
3969
3970 /*
3971 * If we're in the command part of a recipe, anything starting with a
3972 * tab is considered another command for the recipe.
3973 */
3974 if (Parser.fInRecipe && *pchLine == '\t')
3975 {
3976 /* Do we do anything here? */
3977 }
3978 else
3979 {
3980 /*
3981 * Skip leading whitespace and check for directives (simplified).
3982 *
3983 * This is simplified in the sense that GNU make first checks for variable
3984 * assignments, so that directive can be used as variable names. We don't
3985 * want that, so we do the variable assignment check later.
3986 */
3987 size_t offLine = 0;
3988 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
3989 offLine++;
3990
3991 /* Find end of word (if any): */
3992 size_t cchWord = 0;
3993 while ( offLine + cchWord < cchLine
3994 && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord])
3995 || pchLine[offLine + cchWord] == '-'))
3996 cchWord++;
3997 if (cchWord > 0)
3998 {
3999 /* If the line is just a line continuation slash, simply remove it
4000 (this also makes the parsing a lot easier). */
4001 if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
4002 continue;
4003
4004 /* Unlike the GNU make parser, we won't recognize 'if' or any other
4005 directives as variable names, so we can */
4006 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
4007 switch (enmToken)
4008 {
4009 case kKmkToken_ifeq:
4010 case kKmkToken_ifneq:
4011 case kKmkToken_if1of:
4012 case kKmkToken_ifn1of:
4013 scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
4014 continue;
4015
4016 case kKmkToken_ifdef:
4017 case kKmkToken_ifndef:
4018 case kKmkToken_if:
4019 scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
4020 continue;
4021
4022 case kKmkToken_else:
4023 scmKmkHandleElse(&Parser, offLine);
4024 continue;
4025
4026 case kKmkToken_endif:
4027 scmKmkHandleEndif(&Parser, offLine);
4028 continue;
4029
4030 /* Includes: */
4031 case kKmkToken_include:
4032 case kKmkToken_sinclude:
4033 case kKmkToken_dash_include:
4034 case kKmkToken_includedep:
4035 case kKmkToken_includedep_queue:
4036 case kKmkToken_includedep_flush:
4037 scmKmkHandleSimple(&Parser, offLine);
4038 continue;
4039
4040 /* Others: */
4041 case kKmkToken_define:
4042 scmKmkHandleDefine(&Parser, offLine);
4043 continue;
4044 case kKmkToken_endef:
4045 scmKmkHandleEndef(&Parser, offLine);
4046 continue;
4047
4048 case kKmkToken_override:
4049 case kKmkToken_local:
4050 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
4051 continue;
4052
4053 case kKmkToken_export:
4054 scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
4055 continue;
4056
4057 case kKmkToken_unexport:
4058 case kKmkToken_undefine:
4059 scmKmkHandleSimple(&Parser, offLine);
4060 break;
4061
4062 case kKmkToken_Comment:
4063 break;
4064
4065 /*
4066 * Check if it's perhaps an variable assignment or start of a rule.
4067 * We'll do this in a very simple fashion.
4068 */
4069 case kKmkToken_Word:
4070 {
4071 Parser.cLines = 1;
4072 Parser.cchTotalLine = cchLine;
4073 if (scmKmkIsLineWithContinuation(pchLine, cchLine))
4074 Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
4075 scmKmkHandleAssignmentOrRule(&Parser, offLine);
4076 continue;
4077 }
4078 }
4079 }
4080 }
4081
4082 /*
4083 * Pass it thru as-is with line continuation.
4084 */
4085 while (scmKmkIsLineWithContinuation(pchLine, cchLine))
4086 {
4087 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
4088 Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
4089 if (!pchLine)
4090 break;
4091 cchLine = Parser.cchLine;
4092 }
4093 if (pchLine)
4094 ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
4095 }
4096
4097 return kScmMaybeModified; /* Make the caller check */
4098}
4099
4100
4101
4102/*********************************************************************************************************************************
4103* Flower Box Section Markers *
4104*********************************************************************************************************************************/
4105
4106static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
4107 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
4108{
4109 *ppchText = NULL;
4110 *pcchText = 0;
4111 *pfNeedFixing = false;
4112
4113 /*
4114 * The first line.
4115 */
4116 if (pchLine[0] != '/')
4117 return false;
4118 size_t offLine = 1;
4119 while (offLine < cchLine && pchLine[offLine] == '*')
4120 offLine++;
4121 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
4122 return false;
4123 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4124 offLine++;
4125 if (offLine != cchLine)
4126 return false;
4127
4128 size_t const cchBox = cchLine;
4129 *pfNeedFixing = cchBox != cchWidth;
4130
4131 /*
4132 * The next line, extracting the text.
4133 */
4134 SCMEOL enmEol;
4135 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
4136 if (cchLine < cchBox - 3)
4137 return false;
4138
4139 offLine = 0;
4140 if (RT_C_IS_BLANK(pchLine[0]))
4141 {
4142 *pfNeedFixing = true;
4143 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
4144 }
4145
4146 if (pchLine[offLine] != '*')
4147 return false;
4148 offLine++;
4149
4150 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
4151 return false;
4152 offLine++;
4153
4154 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4155 offLine++;
4156 if (offLine >= cchLine)
4157 return false;
4158 if (!RT_C_IS_UPPER(pchLine[offLine]))
4159 return false;
4160
4161 if (offLine != 4 || cchLine != cchBox)
4162 *pfNeedFixing = true;
4163
4164 *ppchText = &pchLine[offLine];
4165 size_t const offText = offLine;
4166
4167 /* From the end now. */
4168 offLine = cchLine - 1;
4169 while (RT_C_IS_BLANK(pchLine[offLine]))
4170 offLine--;
4171
4172 if (pchLine[offLine] != '*')
4173 return false;
4174 offLine--;
4175 if (!RT_C_IS_BLANK(pchLine[offLine]))
4176 return false;
4177 offLine--;
4178 while (RT_C_IS_BLANK(pchLine[offLine]))
4179 offLine--;
4180 *pcchText = offLine - offText + 1;
4181
4182 /*
4183 * Third line closes the box.
4184 */
4185 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
4186 if (cchLine < cchBox - 3)
4187 return false;
4188
4189 offLine = 0;
4190 if (RT_C_IS_BLANK(pchLine[0]))
4191 {
4192 *pfNeedFixing = true;
4193 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
4194 }
4195 while (offLine < cchLine && pchLine[offLine] == '*')
4196 offLine++;
4197 if (offLine < cchBox - 4)
4198 return false;
4199
4200 if (pchLine[offLine] != '/')
4201 return false;
4202 offLine++;
4203
4204 if (offLine != cchBox)
4205 *pfNeedFixing = true;
4206
4207 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
4208 offLine++;
4209 if (offLine != cchLine)
4210 return false;
4211
4212 return true;
4213}
4214
4215
4216/**
4217 * Flower box marker comments in C and C++ code.
4218 *
4219 * @returns Modification state.
4220 * @param pIn The input stream.
4221 * @param pOut The output stream.
4222 * @param pSettings The settings.
4223 */
4224SCMREWRITERRES rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4225{
4226 if (!pSettings->fFixFlowerBoxMarkers)
4227 return kScmUnmodified;
4228
4229 /*
4230 * Work thru the file line by line looking for flower box markers.
4231 */
4232 size_t cChanges = 0;
4233 size_t cBlankLines = 0;
4234 SCMEOL enmEol;
4235 size_t cchLine;
4236 const char *pchLine;
4237 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4238 {
4239 /*
4240 * Get a likely match for a first line.
4241 */
4242 if ( pchLine[0] == '/'
4243 && cchLine > 20
4244 && pchLine[1] == '*'
4245 && pchLine[2] == '*'
4246 && pchLine[3] == '*')
4247 {
4248 size_t const offSaved = ScmStreamTell(pIn);
4249 char const *pchText;
4250 size_t cchText;
4251 bool fNeedFixing;
4252 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
4253 &pchText, &cchText, &fNeedFixing);
4254 if ( fIsFlowerBoxSection
4255 && ( fNeedFixing
4256 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
4257 {
4258 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
4259 {
4260 ScmStreamPutEol(pOut, enmEol);
4261 cBlankLines++;
4262 }
4263
4264 ScmStreamPutCh(pOut, '/');
4265 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
4266 ScmStreamPutEol(pOut, enmEol);
4267
4268 static const char s_szLead[] = "* ";
4269 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
4270 ScmStreamWrite(pOut, pchText, cchText);
4271 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
4272 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
4273 ScmStreamPutCh(pOut, '*');
4274 ScmStreamPutEol(pOut, enmEol);
4275
4276 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
4277 ScmStreamPutCh(pOut, '/');
4278 ScmStreamPutEol(pOut, enmEol);
4279
4280 cChanges++;
4281 cBlankLines = 0;
4282 continue;
4283 }
4284
4285 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
4286 if (RT_FAILURE(rc))
4287 return kScmUnmodified;
4288 }
4289
4290 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4291 if (RT_FAILURE(rc))
4292 return kScmUnmodified;
4293
4294 /* Do blank line accounting so we can ensure at least two blank lines
4295 before each section marker. */
4296 if (!isBlankLine(pchLine, cchLine))
4297 cBlankLines = 0;
4298 else
4299 cBlankLines++;
4300 }
4301 if (cChanges > 0)
4302 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
4303 return cChanges != 0 ? kScmModified : kScmUnmodified;
4304}
4305
4306
4307/**
4308 * Looks for the start of a todo comment.
4309 *
4310 * @returns Offset into the line of the comment start sequence.
4311 * @param pchLine The line to search.
4312 * @param cchLineBeforeTodo The length of the line before the todo.
4313 * @param pfSameLine Indicates whether it's refering to a statemtn on
4314 * the same line comment (true), or the next
4315 * statement (false).
4316 */
4317static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
4318{
4319 *pfSameLine = false;
4320
4321 /* Skip one '@' or '\\'. */
4322 char ch;
4323 if ( cchLineBeforeTodo > 2
4324 && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
4325 || ch == '\\' ) )
4326 cchLineBeforeTodo--;
4327
4328 /* Skip blanks. */
4329 while ( cchLineBeforeTodo > 2
4330 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
4331 cchLineBeforeTodo--;
4332
4333 /* Look for same line indicator. */
4334 if ( cchLineBeforeTodo > 0
4335 && pchLine[cchLineBeforeTodo - 1] == '<')
4336 {
4337 *pfSameLine = true;
4338 cchLineBeforeTodo--;
4339 }
4340
4341 /* Skip *s */
4342 while ( cchLineBeforeTodo > 1
4343 && pchLine[cchLineBeforeTodo - 1] == '*')
4344 cchLineBeforeTodo--;
4345
4346 /* Do we have a comment opening sequence. */
4347 if ( cchLineBeforeTodo > 0
4348 && pchLine[cchLineBeforeTodo - 1] == '/'
4349 && ( ( cchLineBeforeTodo >= 2
4350 && pchLine[cchLineBeforeTodo - 2] == '/')
4351 || pchLine[cchLineBeforeTodo] == '*'))
4352 {
4353 /* Skip slashes at the start. */
4354 while ( cchLineBeforeTodo > 0
4355 && pchLine[cchLineBeforeTodo - 1] == '/')
4356 cchLineBeforeTodo--;
4357
4358 return cchLineBeforeTodo;
4359 }
4360
4361 return ~(size_t)0;
4362}
4363
4364
4365/**
4366 * Looks for a TODO or todo in the given line.
4367 *
4368 * @returns Offset into the line of found, ~(size_t)0 if not.
4369 * @param pchLine The line to search.
4370 * @param cchLine The length of the line.
4371 */
4372static size_t findTodo(char const *pchLine, size_t cchLine)
4373{
4374 if (cchLine >= 4 + 2)
4375 {
4376 /* We don't search the first to chars because we need the start of a comment.
4377 Also, skip the last three chars since we need at least four for a match. */
4378 size_t const cchLineT = cchLine - 3;
4379 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
4380 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
4381 {
4382 for (size_t off = 2; off < cchLineT; off++)
4383 {
4384 char ch = pchLine[off];
4385 if ( ( ch != 't'
4386 && ch != 'T')
4387 || ( (ch = pchLine[off + 1]) != 'o'
4388 && ch != 'O')
4389 || ( (ch = pchLine[off + 2]) != 'd'
4390 && ch != 'D')
4391 || ( (ch = pchLine[off + 3]) != 'o'
4392 && ch != 'O')
4393 || ( off + 4 != cchLine
4394 && (ch = pchLine[off + 4]) != ' '
4395 && ch != '\t'
4396 && ch != ':' /** @todo */
4397 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
4398 ) )
4399 { /* not a hit - likely */ }
4400 else
4401 return off;
4402 }
4403 }
4404 }
4405 return ~(size_t)0;
4406}
4407
4408
4409/**
4410 * Doxygen todos in C and C++ code.
4411 *
4412 * @returns Modification state.
4413 * @param pState The rewriter state.
4414 * @param pIn The input stream.
4415 * @param pOut The output stream.
4416 * @param pSettings The settings.
4417 */
4418SCMREWRITERRES rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4419{
4420 if (!pSettings->fFixTodos)
4421 return kScmUnmodified;
4422
4423 /*
4424 * Work thru the file line by line looking for the start of todo comments.
4425 */
4426 size_t cChanges = 0;
4427 SCMEOL enmEol;
4428 size_t cchLine;
4429 const char *pchLine;
4430 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4431 {
4432 /*
4433 * Look for the word 'todo' in the line. We're currently only trying
4434 * to catch comments starting with the word todo and adjust the start of
4435 * the doxygen statement.
4436 */
4437 size_t offTodo = findTodo(pchLine, cchLine);
4438 if ( offTodo != ~(size_t)0
4439 && offTodo >= 2)
4440 {
4441 /* Work backwards to find the start of the comment. */
4442 bool fSameLine = false;
4443 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
4444 if (offCommentStart != ~(size_t)0)
4445 {
4446 char szNew[64];
4447 size_t cchNew = 0;
4448 szNew[cchNew++] = '/';
4449 szNew[cchNew++] = pchLine[offCommentStart + 1];
4450 szNew[cchNew++] = pchLine[offCommentStart + 1];
4451 if (fSameLine)
4452 szNew[cchNew++] = '<';
4453 szNew[cchNew++] = ' ';
4454 szNew[cchNew++] = '@';
4455 szNew[cchNew++] = 't';
4456 szNew[cchNew++] = 'o';
4457 szNew[cchNew++] = 'd';
4458 szNew[cchNew++] = 'o';
4459
4460 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
4461 but need to take into account that we might be at the end of the line before
4462 adding the space. */
4463 size_t offTodoAfter = offTodo + 4;
4464 if ( offTodoAfter < cchLine
4465 && pchLine[offTodoAfter] == ':')
4466 offTodoAfter++;
4467 if ( offTodoAfter < cchLine
4468 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
4469 offTodoAfter++;
4470 if (offTodoAfter < cchLine)
4471 szNew[cchNew++] = ' ';
4472
4473 /* Write it out. */
4474 ScmStreamWrite(pOut, pchLine, offCommentStart);
4475 ScmStreamWrite(pOut, szNew, cchNew);
4476 if (offTodoAfter < cchLine)
4477 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
4478 ScmStreamPutEol(pOut, enmEol);
4479
4480 /* Check whether we actually made any changes. */
4481 if ( cchNew != offTodoAfter - offCommentStart
4482 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
4483 cChanges++;
4484 continue;
4485 }
4486 }
4487
4488 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4489 if (RT_FAILURE(rc))
4490 return kScmUnmodified;
4491 }
4492 if (cChanges > 0)
4493 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
4494 return cChanges != 0 ? kScmModified : kScmUnmodified;
4495}
4496
4497
4498/**
4499 * Tries to parse a C/C++ preprocessor include directive.
4500 *
4501 * This is resonably forgiving and expects sane input.
4502 *
4503 * @retval kScmIncludeDir_Invalid if not a valid include directive.
4504 * @retval kScmIncludeDir_Quoted
4505 * @retval kScmIncludeDir_Bracketed
4506 * @retval kScmIncludeDir_Macro
4507 *
4508 * @param pState The rewriter state (for repording malformed
4509 * directives).
4510 * @param pchLine The line to try parse as an include statement.
4511 * @param cchLine The line length.
4512 * @param ppchFilename Where to return the pointer to the filename part.
4513 * @param pcchFilename Where to return the length of the filename.
4514 */
4515SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
4516 const char **ppchFilename, size_t *pcchFilename)
4517{
4518 /* Skip leading spaces: */
4519 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4520 cchLine--, pchLine++;
4521
4522 /* Check for '#': */
4523 if (cchLine > 0 && *pchLine == '#')
4524 {
4525 cchLine--;
4526 pchLine++;
4527
4528 /* Skip spaces after '#' (optional): */
4529 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4530 cchLine--, pchLine++;
4531
4532 /* Check for 'include': */
4533 static char const s_szInclude[] = "include";
4534 if ( cchLine >= sizeof(s_szInclude)
4535 && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0)
4536 {
4537 cchLine -= sizeof(s_szInclude) - 1;
4538 pchLine += sizeof(s_szInclude) - 1;
4539
4540 /* Skip spaces after 'include' word (optional): */
4541 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
4542 cchLine--, pchLine++;
4543 if (cchLine > 0)
4544 {
4545 /* Quoted or bracketed? */
4546 char const chFirst = *pchLine;
4547 if (chFirst == '"' || chFirst == '<')
4548 {
4549 cchLine--;
4550 pchLine++;
4551 const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine);
4552 if (pchEnd)
4553 {
4554 if (ppchFilename)
4555 *ppchFilename = pchLine;
4556 if (pcchFilename)
4557 *pcchFilename = pchEnd - pchLine;
4558 return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed;
4559 }
4560 ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n",
4561 chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine);
4562 }
4563 /* C prepreprocessor macro? */
4564 else if (ScmIsCIdentifierLeadChar(chFirst))
4565 {
4566 size_t cchFilename = 1;
4567 while ( cchFilename < cchLine
4568 && ScmIsCIdentifierChar(pchLine[cchFilename]))
4569 cchFilename++;
4570 if (ppchFilename)
4571 *ppchFilename = pchLine;
4572 if (pcchFilename)
4573 *pcchFilename = cchFilename;
4574 return kScmIncludeDir_Macro;
4575 }
4576 else
4577 ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine);
4578 }
4579 else
4580 ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n");
4581 }
4582 }
4583
4584 if (ppchFilename)
4585 *ppchFilename = NULL;
4586 if (pcchFilename)
4587 *pcchFilename = 0;
4588 return kScmIncludeDir_Invalid;
4589}
4590
4591
4592/**
4593 * Fix err.h/errcore.h usage.
4594 *
4595 * @returns Modification state.
4596 * @param pIn The input stream.
4597 * @param pOut The output stream.
4598 * @param pSettings The settings.
4599 */
4600SCMREWRITERRES rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4601{
4602 if (!pSettings->fFixErrH)
4603 return kScmUnmodified;
4604
4605 static struct
4606 {
4607 const char *pszHeader;
4608 unsigned cchHeader;
4609 int iLevel;
4610 } const s_aHeaders[] =
4611 {
4612 { RT_STR_TUPLE("iprt/errcore.h"), 1 },
4613 { RT_STR_TUPLE("iprt/err.h"), 2 },
4614 { RT_STR_TUPLE("VBox/err.h"), 3 },
4615 };
4616 static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */
4617 {
4618 { RT_STR_TUPLE("VINF_SUCCESS") },
4619 { RT_STR_TUPLE("VERR_GENERAL_FAILURE") },
4620 { RT_STR_TUPLE("VERR_INVALID_PARAMETER") },
4621 { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") },
4622 { RT_STR_TUPLE("VERR_INVALID_MAGIC") },
4623 { RT_STR_TUPLE("VWRN_INVALID_MAGIC") },
4624 { RT_STR_TUPLE("VERR_INVALID_HANDLE") },
4625 { RT_STR_TUPLE("VWRN_INVALID_HANDLE") },
4626 { RT_STR_TUPLE("VERR_INVALID_POINTER") },
4627 { RT_STR_TUPLE("VERR_NO_MEMORY") },
4628 { RT_STR_TUPLE("VERR_PERMISSION_DENIED") },
4629 { RT_STR_TUPLE("VINF_PERMISSION_DENIED") },
4630 { RT_STR_TUPLE("VERR_VERSION_MISMATCH") },
4631 { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") },
4632 { RT_STR_TUPLE("VERR_INVALID_FLAGS") },
4633 { RT_STR_TUPLE("VERR_WRONG_ORDER") },
4634 { RT_STR_TUPLE("VERR_INVALID_FUNCTION") },
4635 { RT_STR_TUPLE("VERR_NOT_SUPPORTED") },
4636 { RT_STR_TUPLE("VINF_NOT_SUPPORTED") },
4637 { RT_STR_TUPLE("VERR_ACCESS_DENIED") },
4638 { RT_STR_TUPLE("VERR_INTERRUPTED") },
4639 { RT_STR_TUPLE("VINF_INTERRUPTED") },
4640 { RT_STR_TUPLE("VERR_TIMEOUT") },
4641 { RT_STR_TUPLE("VINF_TIMEOUT") },
4642 { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") },
4643 { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") },
4644 { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") },
4645 { RT_STR_TUPLE("VERR_TRY_AGAIN") },
4646 { RT_STR_TUPLE("VINF_TRY_AGAIN") },
4647 { RT_STR_TUPLE("VERR_PARSE_ERROR") },
4648 { RT_STR_TUPLE("VERR_OUT_OF_RANGE") },
4649 { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") },
4650 { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") },
4651 { RT_STR_TUPLE("VERR_CANCELLED") },
4652 { RT_STR_TUPLE("VERR_TRAILING_CHARS") },
4653 { RT_STR_TUPLE("VWRN_TRAILING_CHARS") },
4654 { RT_STR_TUPLE("VERR_TRAILING_SPACES") },
4655 { RT_STR_TUPLE("VWRN_TRAILING_SPACES") },
4656 { RT_STR_TUPLE("VERR_NOT_FOUND") },
4657 { RT_STR_TUPLE("VWRN_NOT_FOUND") },
4658 { RT_STR_TUPLE("VERR_INVALID_STATE") },
4659 { RT_STR_TUPLE("VWRN_INVALID_STATE") },
4660 { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") },
4661 { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") },
4662 { RT_STR_TUPLE("VERR_END_OF_STRING") },
4663 { RT_STR_TUPLE("VERR_CALLBACK_RETURN") },
4664 { RT_STR_TUPLE("VINF_CALLBACK_RETURN") },
4665 { RT_STR_TUPLE("VERR_DUPLICATE") },
4666 { RT_STR_TUPLE("VERR_MISSING") },
4667 { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") },
4668 { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") },
4669 { RT_STR_TUPLE("VERR_NOT_AVAILABLE") },
4670 { RT_STR_TUPLE("VERR_MISMATCH") },
4671 { RT_STR_TUPLE("VERR_WRONG_TYPE") },
4672 { RT_STR_TUPLE("VWRN_WRONG_TYPE") },
4673 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") },
4674 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") },
4675 { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") },
4676 { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") },
4677 { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") },
4678 { RT_STR_TUPLE("VERR_INTERNAL_ERROR") },
4679 { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") },
4680 { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") },
4681 };
4682
4683 /*
4684 * First pass: Scout #include err.h/errcore.h locations and usage.
4685 *
4686 * Note! This isn't entirely optimal since it's also parsing comments and
4687 * strings, not just code. However it does a decent job for now.
4688 */
4689 int iIncludeLevel = 0;
4690 int iUsageLevel = 0;
4691 uint32_t iLine = 0;
4692 SCMEOL enmEol;
4693 size_t cchLine;
4694 const char *pchLine;
4695 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4696 {
4697 iLine++;
4698 if (cchLine < 6)
4699 continue;
4700
4701 /*
4702 * Look for #includes.
4703 */
4704 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
4705 if ( pchHash
4706 && isSpanOfBlanks(pchLine, pchHash - pchLine))
4707 {
4708 const char *pchFilename;
4709 size_t cchFilename;
4710 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
4711 if ( enmIncDir == kScmIncludeDir_Bracketed
4712 || enmIncDir == kScmIncludeDir_Quoted)
4713 {
4714 unsigned i = RT_ELEMENTS(s_aHeaders);
4715 while (i-- > 0)
4716 if ( s_aHeaders[i].cchHeader == cchFilename
4717 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
4718 {
4719 if (iIncludeLevel < s_aHeaders[i].iLevel)
4720 iIncludeLevel = s_aHeaders[i].iLevel;
4721 break;
4722 }
4723
4724 /* Special hack for error info. */
4725 if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0)
4726 iUsageLevel = 4;
4727
4728 /* Special hack for code templates. */
4729 if ( cchFilename >= sizeof(".cpp.h")
4730 && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0)
4731 iUsageLevel = 4;
4732 continue;
4733 }
4734 }
4735 /*
4736 * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line.
4737 */
4738 const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine);
4739 if (pchHit)
4740 {
4741 const char *pchLeft = pchLine;
4742 size_t cchLeft = cchLine;
4743 do
4744 {
4745 size_t cchLeftHit = &pchLeft[cchLeft] - pchHit;
4746 if (cchLeftHit < 6)
4747 break;
4748 if ( pchHit[4] == '_'
4749 && ( pchHit == pchLine
4750 || !ScmIsCIdentifierChar(pchHit[-1]))
4751 && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R')
4752 || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N')
4753 || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) )
4754 {
4755 size_t cchIdentifier = 5;
4756 while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier]))
4757 cchIdentifier++;
4758 ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n",
4759 iLine, pchHit - pchLine, cchIdentifier, pchHit);
4760
4761 if (iUsageLevel <= 1)
4762 {
4763 iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */
4764 for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++)
4765 if ( cchIdentifier == g_aLevel1Statuses[i].cch
4766 && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0)
4767 {
4768 iUsageLevel = 1;
4769 break;
4770 }
4771 }
4772
4773 pchLeft = pchHit + cchIdentifier;
4774 cchLeft = cchLeftHit - cchIdentifier;
4775 }
4776 else
4777 {
4778 pchLeft = pchHit + 1;
4779 cchLeft = cchLeftHit - 1;
4780 }
4781 pchHit = (const char *)memchr(pchLeft, 'V', cchLeft);
4782 } while (pchHit != NULL);
4783 }
4784 }
4785 ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel);
4786
4787 /*
4788 * Second pass: Change err.h to errcore.h if we detected a need for change.
4789 */
4790 if ( iIncludeLevel <= iUsageLevel
4791 || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */)
4792 return kScmUnmodified;
4793
4794 unsigned cChanges = 0;
4795 ScmStreamRewindForReading(pIn);
4796 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
4797 {
4798 /*
4799 * Look for #includes to modify.
4800 */
4801 if (cchLine >= 6)
4802 {
4803 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
4804 if ( pchHash
4805 && isSpanOfBlanks(pchLine, pchHash - pchLine))
4806 {
4807 const char *pchFilename;
4808 size_t cchFilename;
4809 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
4810 if ( enmIncDir == kScmIncludeDir_Bracketed
4811 || enmIncDir == kScmIncludeDir_Quoted)
4812 {
4813 unsigned i = RT_ELEMENTS(s_aHeaders);
4814 while (i-- > 0)
4815 if ( s_aHeaders[i].cchHeader == cchFilename
4816 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
4817 {
4818 ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1);
4819 ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>"));
4820 size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1];
4821 if (cchTrailing > 0)
4822 ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing);
4823 ScmStreamPutEol(pOut, enmEol);
4824 cChanges++;
4825 pchLine = NULL;
4826 break;
4827 }
4828 if (!pchLine)
4829 continue;
4830 }
4831 }
4832 }
4833
4834 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
4835 if (RT_FAILURE(rc))
4836 return kScmUnmodified;
4837 }
4838 ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges);
4839 return kScmModified;
4840}
4841
4842typedef struct
4843{
4844 const char *pch;
4845 uint8_t cch;
4846 uint8_t cchSpaces; /**< Number of expected spaces before the word. */
4847 bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */
4848 bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
4849} SCMMATCHWORD;
4850
4851
4852int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords,
4853 size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo)
4854{
4855 int rc = VINF_SUCCESS;
4856
4857 size_t offLine = 0;
4858 for (size_t i = 0; i < cWords; i++)
4859 {
4860 SCMMATCHWORD const *pWord = &paWords[i];
4861
4862 /*
4863 * Deal with spaces preceeding the word first:
4864 */
4865 if (pWord->fSpacesBefore)
4866 {
4867 size_t cchSpaces = 0;
4868 size_t cchTabs = 0;
4869 while (offLine < cchLine)
4870 {
4871 const char ch = pchLine[offLine];
4872 if (ch == ' ')
4873 cchSpaces++;
4874 else if (ch == '\t')
4875 cchTabs++;
4876 else
4877 break;
4878 offLine++;
4879 }
4880
4881 if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
4882 { /* likely */ }
4883 else if (cchSpaces == 0 && cchTabs == 0)
4884 return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
4885 else
4886 rc = VWRN_TRAILING_SPACES;
4887 }
4888 else
4889 Assert(pWord->cchSpaces == 0);
4890
4891 /*
4892 * C/C++ identifier?
4893 */
4894 if (pWord->fIdentifier)
4895 {
4896 if (offLine >= cchLine)
4897 return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
4898 "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
4899 pWord->cch, pWord->pch, offLine);
4900 if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
4901 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
4902 pWord->cch, pWord->pch, offLine);
4903 size_t const offStart = offLine++;
4904 while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
4905 offLine++;
4906 if (paIdentifiers)
4907 {
4908 paIdentifiers->cch = offLine - offStart;
4909 paIdentifiers->psz = &pchLine[offStart];
4910 paIdentifiers++;
4911 }
4912 }
4913 /*
4914 * Match the exact word.
4915 */
4916 else if ( pWord->cch == 0
4917 || ( pWord->cch <= cchLine - offLine
4918 && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
4919 offLine += pWord->cch;
4920 else
4921 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
4922 }
4923
4924 /*
4925 * Check for trailing characters/whatnot.
4926 */
4927 if (poffNext)
4928 *poffNext = offLine;
4929 else if (offLine != cchLine)
4930 rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
4931 return rc;
4932}
4933
4934
4935/**
4936 * Fix header file include guards and \#pragma once.
4937 *
4938 * @returns Modification state.
4939 * @param pIn The input stream.
4940 * @param pOut The output stream.
4941 * @param pSettings The settings.
4942 */
4943SCMREWRITERRES rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
4944{
4945 if (!pSettings->fFixHeaderGuards)
4946 return kScmUnmodified;
4947
4948 /* always skip .cpp.h files */
4949 size_t cchFilename = strlen(pState->pszFilename);
4950 if ( cchFilename > sizeof(".cpp.h")
4951 && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0)
4952 return kScmUnmodified;
4953
4954 RTERRINFOSTATIC ErrInfo;
4955 char szNormalized[168];
4956 size_t cchNormalized = 0;
4957 int rc;
4958 bool fRet = false;
4959
4960 /*
4961 * Calculate the expected guard for this file, if so tasked.
4962 * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir.
4963 */
4964 szNormalized[0] = '\0';
4965 if (pSettings->pszGuardRelativeToDir)
4966 {
4967 rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix);
4968 if (RT_FAILURE(rc))
4969 return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix);
4970 cchNormalized = strlen(szNormalized);
4971 if (strcmp(pSettings->pszGuardRelativeToDir, "{dir}") == 0)
4972 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
4973 RTPathFilename(pState->pszFilename));
4974 else if (strcmp(pSettings->pszGuardRelativeToDir, "{parent}") == 0)
4975 {
4976 const char *pszSrc = RTPathFilename(pState->pszFilename);
4977 if (!pszSrc || (uintptr_t)&pszSrc[-2] < (uintptr_t)pState->pszFilename || !RTPATH_IS_SLASH(pszSrc[-1]))
4978 return ScmError(pState, VERR_INTERNAL_ERROR, "Error calculating {parent} header guard!\n");
4979 pszSrc -= 2;
4980 while ( (uintptr_t)pszSrc > (uintptr_t)pState->pszFilename
4981 && !RTPATH_IS_SLASH(pszSrc[-1])
4982 && !RTPATH_IS_VOLSEP(pszSrc[-1]))
4983 pszSrc--;
4984 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);
4985 }
4986 else
4987 rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
4988 pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename);
4989 if (RT_FAILURE(rc))
4990 return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
4991 char ch;
4992 while ((ch = szNormalized[cchNormalized]) != '\0')
4993 {
4994 if (!ScmIsCIdentifierChar(ch))
4995 szNormalized[cchNormalized] = '_';
4996 cchNormalized++;
4997 }
4998 }
4999
5000 /*
5001 * First part looks for the #ifndef xxxx paired with #define xxxx.
5002 *
5003 * We blindly assume the first preprocessor directive in the file is the guard
5004 * and will be upset if this isn't the case.
5005 */
5006 RTSTRTUPLE Guard = { NULL, 0 };
5007 uint32_t cBlankLines = 0;
5008 SCMEOL enmEol;
5009 size_t cchLine;
5010 const char *pchLine;
5011 for (;;)
5012 {
5013 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5014 if (pchLine == NULL)
5015 return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
5016 if (cchLine >= 2)
5017 {
5018 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
5019 if ( pchHash
5020 && isSpanOfBlanks(pchLine, pchHash - pchLine))
5021 {
5022 /* #ifndef xxxx */
5023 static const SCMMATCHWORD s_aIfndefGuard[] =
5024 {
5025 { RT_STR_TUPLE("#"), 0, true, false },
5026 { RT_STR_TUPLE("ifndef"), 0, true, false },
5027 { RT_STR_TUPLE("IDENTIFIER"), 1, true, true },
5028 { RT_STR_TUPLE(""), 0, true, false },
5029 };
5030 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
5031 NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
5032 if (RT_FAILURE(rc))
5033 return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
5034 ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
5035 fRet |= rc != VINF_SUCCESS;
5036 ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
5037 ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
5038
5039 /* #define xxxx */
5040 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5041 if (!pchLine)
5042 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
5043 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
5044 const SCMMATCHWORD aDefineGuard[] =
5045 {
5046 { RT_STR_TUPLE("#"), 0, true, false },
5047 { RT_STR_TUPLE("define"), 0, true, false },
5048 { Guard.psz, (uint8_t)Guard.cch, 1, true, false },
5049 { RT_STR_TUPLE(""), 0, true, false },
5050 };
5051 rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
5052 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5053 if (RT_FAILURE(rc))
5054 return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
5055 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
5056 ErrInfo.Core.pszMsg, cchLine, pchLine);
5057 fRet |= rc != VINF_SUCCESS;
5058
5059 if (Guard.cch >= sizeof(szNormalized))
5060 return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
5061 ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
5062
5063 if (szNormalized[0] != '\0')
5064 {
5065 if ( Guard.cch != cchNormalized
5066 || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
5067 {
5068 ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
5069 ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
5070 Guard.cch, Guard.psz, pState->pszFilename);
5071 fRet = true;
5072 }
5073 Guard.psz = szNormalized;
5074 Guard.cch = cchNormalized;
5075 }
5076
5077 /*
5078 * Write guard, making sure we've got a single blank line preceeding it.
5079 */
5080 ScmStreamPutEol(pOut, enmEol);
5081 ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
5082 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
5083 ScmStreamPutEol(pOut, enmEol);
5084 ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
5085 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
5086 rc = ScmStreamPutEol(pOut, enmEol);
5087 if (RT_FAILURE(rc))
5088 return kScmUnmodified;
5089 break;
5090 }
5091 }
5092
5093 if (!isBlankLine(pchLine, cchLine))
5094 {
5095 while (cBlankLines-- > 0)
5096 ScmStreamPutEol(pOut, enmEol);
5097 cBlankLines = 0;
5098 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5099 if (RT_FAILURE(rc))
5100 return kScmUnmodified;
5101 }
5102 else
5103 cBlankLines++;
5104 }
5105
5106 /*
5107 * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
5108 */
5109 size_t const iPragmaOnce = ScmStreamTellLine(pIn);
5110 static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
5111 {
5112 { RT_STR_TUPLE("#"), 0, true, false },
5113 { RT_STR_TUPLE("ifndef"), 0, true, false },
5114 { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false },
5115 { RT_STR_TUPLE(""), 0, true, false },
5116 };
5117 static const SCMMATCHWORD s_aPragmaOnce[] =
5118 {
5119 { RT_STR_TUPLE("#"), 0, true, false },
5120 { RT_STR_TUPLE("pragma"), 1, true, false },
5121 { RT_STR_TUPLE("once"), 1, true, false},
5122 { RT_STR_TUPLE(""), 0, true, false },
5123 };
5124 static const SCMMATCHWORD s_aEndif[] =
5125 {
5126 { RT_STR_TUPLE("#"), 0, true, false },
5127 { RT_STR_TUPLE("endif"), 0, true, false },
5128 { RT_STR_TUPLE(""), 0, true, false },
5129 };
5130
5131 /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
5132 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5133 if (!pchLine)
5134 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
5135 size_t offNext;
5136 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
5137 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5138 if (RT_SUCCESS(rc))
5139 {
5140 fRet |= rc != VINF_SUCCESS;
5141 if (offNext != cchLine)
5142 return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
5143 iPragmaOnce + 1, cchLine, pchLine);
5144
5145 /* # pragma once */
5146 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5147 if (!pchLine)
5148 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
5149 iPragmaOnce + 2);
5150 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
5151 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5152 if (RT_SUCCESS(rc))
5153 fRet |= rc != VINF_SUCCESS;
5154 else
5155 return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
5156 iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
5157
5158 /* #endif */
5159 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5160 if (!pchLine)
5161 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
5162 iPragmaOnce + 3);
5163 rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
5164 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5165 if (RT_SUCCESS(rc))
5166 fRet |= rc != VINF_SUCCESS;
5167 else
5168 return ScmError(pState, rc,
5169 "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
5170 iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
5171 ScmVerbose(pState, 3, "Found pragma once\n");
5172 fRet |= !pSettings->fPragmaOnce;
5173 }
5174 else
5175 {
5176 rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
5177 if (RT_FAILURE(rc))
5178 return ScmError(pState, rc, "seek error\n");
5179 fRet |= pSettings->fPragmaOnce;
5180 ScmVerbose(pState, pSettings->fPragmaOnce ? 2 : 3, "Missing #pragma once\n");
5181 }
5182
5183 /*
5184 * Write the pragma once stuff.
5185 */
5186 if (pSettings->fPragmaOnce)
5187 {
5188 ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
5189 ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
5190 rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
5191 if (RT_FAILURE(rc))
5192 return kScmUnmodified;
5193 }
5194
5195 /*
5196 * Copy the rest of the file and remove pragma once statements, while
5197 * looking for the last #endif in the file.
5198 */
5199 size_t iEndIfIn = 0;
5200 size_t iEndIfOut = 0;
5201 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5202 {
5203 if (cchLine > 2)
5204 {
5205 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
5206 if ( pchHash
5207 && isSpanOfBlanks(pchLine, pchHash - pchLine))
5208 {
5209 size_t off = pchHash - pchLine + 1;
5210 while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
5211 off++;
5212 /* #pragma once */
5213 if ( off + sizeof("pragma") - 1 <= cchLine
5214 && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
5215 {
5216 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
5217 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5218 if (RT_SUCCESS(rc))
5219 {
5220 fRet = true;
5221 continue;
5222 }
5223 }
5224 /* #endif */
5225 else if ( off + sizeof("endif") - 1 <= cchLine
5226 && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
5227 {
5228 iEndIfIn = ScmStreamTellLine(pIn) - 1;
5229 iEndIfOut = ScmStreamTellLine(pOut);
5230 }
5231 }
5232 }
5233
5234 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5235 if (RT_FAILURE(rc))
5236 return kScmUnmodified;
5237 }
5238
5239 /*
5240 * Check out the last endif, making sure it's well formed and make sure it has the
5241 * right kind of comment following it.
5242 */
5243 if (pSettings->fFixHeaderGuardEndif)
5244 {
5245 if (iEndIfOut == 0)
5246 return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
5247 rc = ScmStreamSeekByLine(pIn, iEndIfIn);
5248 if (RT_FAILURE(rc))
5249 return kScmUnmodified;
5250 rc = ScmStreamSeekByLine(pOut, iEndIfOut);
5251 if (RT_FAILURE(rc))
5252 return kScmUnmodified;
5253
5254 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
5255 if (!pchLine)
5256 return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
5257
5258 char szTmp[64 + sizeof(szNormalized)];
5259 size_t cchTmp;
5260 if (pSettings->fEndifGuardComment)
5261 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
5262 else
5263 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
5264 fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
5265 rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
5266 if (RT_FAILURE(rc))
5267 return kScmUnmodified;
5268
5269 /* Copy out the remaining lines (assumes no #pragma once here). */
5270 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5271 {
5272 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
5273 if (RT_FAILURE(rc))
5274 return kScmUnmodified;
5275 }
5276 }
5277
5278 return fRet ? kScmModified : kScmUnmodified;
5279}
5280
5281
5282/**
5283 * Checks for PAGE_SIZE, PAGE_SHIFT and PAGE_OFFSET_MASK w/o a GUEST_ or HOST_
5284 * prefix as well as banning PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK and
5285 * PAGE_BASE_MASK.
5286 *
5287 * @returns kScmUnmodified - requires manual fix.
5288 * @param pIn The input stream.
5289 * @param pOut The output stream.
5290 * @param pSettings The settings.
5291 */
5292SCMREWRITERRES rewrite_PageChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5293{
5294 RT_NOREF(pOut);
5295 if (!pSettings->fOnlyGuestHostPage && !pSettings->fNoASMMemPageUse)
5296 return kScmUnmodified;
5297
5298 static RTSTRTUPLE const g_aWords[] =
5299 {
5300 { RT_STR_TUPLE("PAGE_SIZE") },
5301 { RT_STR_TUPLE("PAGE_SHIFT") },
5302 { RT_STR_TUPLE("PAGE_OFFSET_MASK") },
5303 { RT_STR_TUPLE("PAGE_BASE_MASK") },
5304 { RT_STR_TUPLE("PAGE_BASE_GC_MASK") },
5305 { RT_STR_TUPLE("PAGE_BASE_HC_MASK") },
5306 { RT_STR_TUPLE("PAGE_ADDRESS") },
5307 { RT_STR_TUPLE("PHYS_PAGE_ADDRESS") },
5308 { RT_STR_TUPLE("ASMMemIsZeroPage") },
5309 { RT_STR_TUPLE("ASMMemZeroPage") },
5310 };
5311 size_t const iFirstWord = pSettings->fOnlyGuestHostPage ? 0 : 7;
5312 size_t const iEndWords = pSettings->fNoASMMemPageUse ? 9 : 7;
5313
5314 uint32_t iLine = 0;
5315 SCMEOL enmEol;
5316 size_t cchLine;
5317 const char *pchLine;
5318 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5319 {
5320 iLine++;
5321 for (size_t i = iFirstWord; i < iEndWords; i++)
5322 {
5323 size_t const cchWord = g_aWords[i].cch;
5324 if (cchLine >= cchWord)
5325 {
5326 const char * const pszWord = g_aWords[i].psz;
5327 const char *pchHit = (const char *)memchr(pchLine, *pszWord, cchLine);
5328 while (pchHit)
5329 {
5330 size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
5331 if ( cchLeft >= cchWord
5332 && memcmp(pchHit, pszWord, cchWord) == 0
5333 && ( pchHit == pchLine
5334 || !ScmIsCIdentifierChar(pchHit[-1]))
5335 && ( cchLeft == cchWord
5336 || !ScmIsCIdentifierChar(pchHit[cchWord])) )
5337 {
5338 if (i < 3)
5339 ScmFixManually(pState, "%u:%zu: %s is not allow! Use GUEST_%s or HOST_%s instead.\n",
5340 iLine, pchHit - pchLine + 1, pszWord, pszWord, pszWord);
5341 else if (i < 7)
5342 ScmFixManually(pState, "%u:%zu: %s is not allow! Rewrite using GUEST/HOST_PAGE_OFFSET_MASK.\n",
5343 iLine, pchHit - pchLine + 1, pszWord);
5344 else
5345 ScmFixManually(pState, "%u:%zu: %s is not allow! Use %s with correct page size instead.\n",
5346 iLine, pchHit - pchLine + 1, pszWord, i == 3 ? "ASMMemIsZero" : "RT_BZERO");
5347 }
5348
5349 /* next */
5350 cchLeft -= 1;
5351 if (cchLeft < cchWord)
5352 break;
5353 pchHit = (const char *)memchr(pchHit + 1, *pszWord, cchLeft);
5354 }
5355 }
5356 }
5357 }
5358
5359 return kScmUnmodified;
5360}
5361
5362
5363/**
5364 * Checks for usage of rc in code instead of vrc for IPRT status codes (int) and hrc for COM
5365 * status codes (HRESULT).
5366 *
5367 * @returns kScmUnmodified - requires manual fix.
5368 * @param pIn The input stream.
5369 * @param pOut The output stream.
5370 * @param pSettings The settings.
5371 *
5372 * @note Used in Main to avoid ambiguity when just using rc.
5373 */
5374SCMREWRITERRES rewrite_ForceHrcVrcInsteadOfRc(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5375{
5376 RT_NOREF(pOut);
5377 if (!pSettings->fOnlyHrcVrcInsteadOfRc)
5378 return kScmUnmodified;
5379
5380 static const SCMMATCHWORD s_aHresultVrc[] =
5381 {
5382 { RT_STR_TUPLE("HRESULT"), 0, true, false },
5383 { RT_STR_TUPLE("vrc"), 1, true, false }
5384 };
5385
5386 static const SCMMATCHWORD s_aIntHrc[] =
5387 {
5388 { RT_STR_TUPLE("int"), 0, true, false },
5389 { RT_STR_TUPLE("hrc"), 1, true, false }
5390 };
5391
5392 uint32_t iLine = 0;
5393 SCMEOL enmEol;
5394 size_t cchLine;
5395 const char *pchLine;
5396 RTERRINFOSTATIC ErrInfo;
5397 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
5398 {
5399 iLine++;
5400
5401 /* Look for forbidden declarations first. */
5402 size_t offNext = 0;
5403 int rc = ScmMatchWords(pchLine, cchLine, s_aHresultVrc, RT_ELEMENTS(s_aHresultVrc),
5404 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5405 if (RT_SUCCESS(rc))
5406 {
5407 ScmFixManually(pState, "%u:%zu: 'HRESULT vrc' is not allowed! Use 'HRESULT hrc' instead.\n",
5408 iLine, offNext);
5409 continue;
5410 }
5411
5412 rc = ScmMatchWords(pchLine, cchLine, s_aIntHrc, RT_ELEMENTS(s_aIntHrc),
5413 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5414 if (RT_SUCCESS(rc))
5415 {
5416 ScmFixManually(pState, "%u:%zu: 'int hrc' is not allowed! Use 'int vrc' instead.\n",
5417 iLine, offNext);
5418 continue;
5419 }
5420
5421#if 0 /* This is too broad and triggers on things we don't want to trigger on (like autoCaller.rc()). */
5422 const RTSTRTUPLE RcTuple = { RT_STR_TUPLE("rc") };
5423 size_t const cchWord = RcTuple.cch;
5424 if (cchLine >= cchWord)
5425 {
5426 const char *pchHit = (const char *)memchr(pchLine, *RcTuple.psz, cchLine);
5427 while (pchHit)
5428 {
5429 size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
5430 if ( cchLeft >= cchWord
5431 && memcmp(pchHit, RcTuple.psz, cchWord) == 0
5432 && ( pchHit == pchLine
5433 || !ScmIsCIdentifierChar(pchHit[-1]))
5434 && ( cchLeft == cchWord
5435 || !ScmIsCIdentifierChar(pchHit[cchWord])) )
5436 ScmFixManually(pState, "%u:%zu: %s is not allowed! Use hrc or vrc instead.\n",
5437 iLine, pchHit - pchLine + 1, RcTuple.psz);
5438
5439 /* next */
5440 cchLeft -= 1;
5441 if (cchLeft < cchWord)
5442 break;
5443 pchHit = (const char *)memchr(pchHit + 1, *RcTuple.psz, cchLeft);
5444 }
5445 }
5446#else
5447 /* Trigger on declarations of 'HRESULT rc' and 'int rc'. */
5448 static const SCMMATCHWORD s_aHresultRc[] =
5449 {
5450 { RT_STR_TUPLE("HRESULT"), 0, true, false },
5451 { RT_STR_TUPLE("rc"), 1, true, false }
5452 };
5453
5454 static const SCMMATCHWORD s_aIntRc[] =
5455 {
5456 { RT_STR_TUPLE("int"), 0, true, false },
5457 { RT_STR_TUPLE("rc"), 1, true, false }
5458 };
5459
5460 rc = ScmMatchWords(pchLine, cchLine, s_aHresultRc, RT_ELEMENTS(s_aHresultRc),
5461 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5462 if (RT_SUCCESS(rc))
5463 {
5464 ScmFixManually(pState, "%u:%zu: 'HRESULT rc' is not allowed! Use 'HRESULT hrc' instead.\n",
5465 iLine, offNext);
5466 continue;
5467 }
5468
5469 rc = ScmMatchWords(pchLine, cchLine, s_aIntRc, RT_ELEMENTS(s_aIntRc),
5470 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
5471 if (RT_SUCCESS(rc))
5472 {
5473 ScmFixManually(pState, "%u:%zu: 'int rc' is not allowed! Use 'int vrc' instead.\n",
5474 iLine, offNext);
5475 continue;
5476 }
5477#endif
5478 }
5479
5480 return kScmUnmodified;
5481}
5482
5483
5484/**
5485 * Rewrite a C/C++ source or header file.
5486 *
5487 * @returns Modification state.
5488 * @param pIn The input stream.
5489 * @param pOut The output stream.
5490 * @param pSettings The settings.
5491 *
5492 * @todo
5493 *
5494 * Ideas for C/C++:
5495 * - space after if, while, for, switch
5496 * - spaces in for (i=0;i<x;i++)
5497 * - complex conditional, bird style.
5498 * - remove unnecessary parentheses.
5499 * - sort defined RT_OS_*|| and RT_ARCH
5500 * - sizeof without parenthesis.
5501 * - defined without parenthesis.
5502 * - trailing spaces.
5503 * - parameter indentation.
5504 * - space after comma.
5505 * - while (x--); -> multi line + comment.
5506 * - else statement;
5507 * - space between function and left parenthesis.
5508 * - TODO, XXX, @todo cleanup.
5509 * - Space before/after '*'.
5510 * - ensure new line at end of file.
5511 * - Indentation of precompiler statements (#ifdef, #defines).
5512 * - space between functions.
5513 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
5514 */
5515SCMREWRITERRES rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
5516{
5517
5518 RT_NOREF4(pState, pIn, pOut, pSettings);
5519 return kScmUnmodified;
5520}
5521
Note: See TracBrowser for help on using the repository browser.

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