Changeset 98374 in vbox
- Timestamp:
- Feb 1, 2023 9:48:59 AM (2 years ago)
- svn:sync-xref-src-repo-rev:
- 155616
- Location:
- trunk/src/bldprogs
- Files:
-
- 3 edited
- 1 copied
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/bldprogs/Makefile.kmk
r98103 r98374 56 56 scmdiff.cpp \ 57 57 scmrw.cpp \ 58 scmrw-kmk.cpp \ 58 59 scmparser.cpp \ 59 60 scmstream.cpp \ -
trunk/src/bldprogs/scm.h
r98368 r98374 207 207 } 208 208 209 size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings); 209 210 210 211 /** @} */ -
trunk/src/bldprogs/scmrw-kmk.cpp
r98373 r98374 1 1 /* $Id$ */ 2 2 /** @file 3 * IPRT Testcase / Tool - Source Code Massager .3 * IPRT Testcase / Tool - Source Code Massager, Makefile.kmk/kup. 4 4 */ 5 5 … … 52 52 * Structures and Typedefs * 53 53 *********************************************************************************************************************************/ 54 /** License types. */55 typedef enum SCMLICENSETYPE56 {57 kScmLicenseType_Invalid = 0,58 kScmLicenseType_OseGpl,59 kScmLicenseType_OseDualGplCddl,60 kScmLicenseType_OseCddl,61 kScmLicenseType_VBoxLgpl,62 kScmLicenseType_Mit,63 kScmLicenseType_Confidential64 } SCMLICENSETYPE;65 66 /** A license. */67 typedef struct SCMLICENSETEXT68 {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. */79 typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;80 81 /**82 * Copyright + license rewriter state.83 */84 typedef struct SCMCOPYRIGHTINFO85 {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 info98 * @{ */99 uint32_t iLineComment;100 uint32_t cLinesComment; /**< This excludes any external license lines. */101 /** @} */102 103 /** @name Copyright info104 * @{ */105 uint32_t iLineCopyright;106 uint32_t uFirstYear;107 uint32_t uLastYear;108 bool fWellFormedCopyright;109 bool fUpToDateCopyright;110 /** @} */111 112 /** @name License info113 * @{ */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 info127 * @{ */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;139 typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO;140 141 142 /*********************************************************************************************************************************143 * Global Variables *144 *********************************************************************************************************************************/145 /** --license-ose-gpl */146 static 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 165 static 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 */175 static 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 203 static 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 */222 static 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 232 static 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 */242 static 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-mit262 * @note This isn't detectable as VirtualBox or Oracle specific.263 */264 static 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 written288 * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a289 * couple of lines shorter. */290 static 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" is311 * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".312 * Its layout is wider, so it is a couple of lines shorter. */313 static 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 sections334 * have been switch. */335 static 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 next356 * paragraph)". */357 static 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 of379 * "sublicense" and adding an illogical "(including the next380 * paragraph)" remark to the final paragraph. (vbox_ttm.c) */381 static 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. */403 static const char g_szOracleConfidential[] =404 "Oracle Corporation confidential\n";405 406 /** Oracle confidential, old style. */407 static 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. */412 static 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. */427 static 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. */446 static const char g_szCopyrightHolder[] = "Oracle and/or its affiliates.";447 448 /** Old copyright holder. */449 static const char g_szOldCopyrightHolder[] = "Oracle Corporation";450 451 /** LGPL disclaimer. */452 static 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. */461 static 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. */478 static 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. */495 static 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. */512 static 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 */535 static SCMCOMMENTSTYLE determineBatchFileCommentStyle(PSCMSTREAM pIn)536 {537 /*538 * Figure out whether it's using upper or lower case REM comments before539 * 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 > 2550 && RT_C_IS_SPACE(*pchLine))551 {552 pchLine++;553 cchLine--;554 }555 if ( ( cchLine > 3556 && 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 */588 static 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 */621 static 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 */639 DECLINLINE(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 than645 * 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 */664 DECLINLINE(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 */684 SCMREWRITERRES 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 == 0697 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )698 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);699 else700 {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 */723 SCMREWRITERRES 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 else740 {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 */783 static 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 != enmDesiredEol796 && 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->fSetSvnEol810 && 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 else820 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 */841 SCMREWRITERRES 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 #else846 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");847 #endif848 }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 */858 SCMREWRITERRES 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 */871 SCMREWRITERRES 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 line878 * 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 */887 SCMREWRITERRES rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)888 {889 if ( !pSettings->fStripTrailingLines890 && !pSettings->fForceTrailingLine891 && !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->fStripTrailingLines904 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))905 {906 while ( cLinesNew > 1907 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))908 cLinesNew--;909 }910 911 if ( pSettings->fForceTrailingLine912 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))913 cLinesNew++;914 915 bool fFixMissingEol = pSettings->fForceFinalEol916 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;917 918 if ( !fFixMissingEol919 && 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 else939 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 */955 SCMREWRITERRES rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)956 {957 RT_NOREF2(pIn, pOut);958 if ( !pSettings->fSetSvnExecutable959 || !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 */982 SCMREWRITERRES rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)983 {984 RT_NOREF2(pIn, pOut);985 if ( !pSettings->fSetSvnExecutable986 || !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 */1009 SCMREWRITERRES rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)1010 {1011 RT_NOREF2(pIn, pOut);1012 if ( !pSettings->fSetSvnExecutable1013 || !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 */1036 SCMREWRITERRES rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)1037 {1038 RT_NOREF2(pIn, pOut);1039 if ( !pSettings->fSetSvnExecutable1040 || !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 */1085 SCMREWRITERRES rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)1086 {1087 RT_NOREF2(pIn, pOut);1088 if ( !pSettings->fSetSvnKeywords1089 || !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 else1103 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 else1112 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 */1137 SCMREWRITERRES rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)1138 {1139 RT_NOREF2(pIn, pOut);1140 if ( pSettings->fSkipSvnSyncProcess1141 || !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 else1170 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 */1191 SCMREWRITERRES 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 else1237 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. If1260 * this is NULL, the whole string must match.1261 */1262 static 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 == ch21270 || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))1271 {1272 if (ch1)1273 {1274 psz1++;1275 psz2++;1276 }1277 else1278 {1279 if (ppsz2Next)1280 *ppsz2Next = psz2;1281 return true;1282 }1283 }1284 else1285 {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 != ch21302 && 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, punctuation1314 * 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 where1321 * the fragment was found. Optional.1322 * @param ppszNext Where to return the pointer to the first char in1323 * @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 */1328 static 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 == NULL1352 || ( pszHit2 != NULL1353 && ((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 */1379 static 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 else1388 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 */1400 static DECLCALLBACK(int)1401 rewrite_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, these1417 * comes first when present.1418 */1419 const char *pchContributedBy = NULL;1420 size_t cchContributedBy = 0;1421 size_t cBlankLinesAfterContributedBy = 0;1422 if ( pState->pszContributedBy == NULL1423 && ( pState->iLineCopyright == UINT32_MAX1424 || 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_MAX1458 && 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_BIG1484 && rc != VWRN_NEGATIVE_UNSIGNED)1485 {1486 if ( pState->uFirstYear < 19751487 || 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 do1502 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_BIG1507 && rc != VWRN_NEGATIVE_UNSIGNED)1508 {1509 if ( pState->uLastYear < 19751510 || 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 else1531 {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 } else1548 pState->uLastYear = pState->uFirstYear;1549 }1550 else1551 {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 it1567 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 else1588 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 else1629 {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 and1653 * complain if not found.1654 */1655 if ( fFoundCopyright1656 && pState->enmLicenceOpt == kScmLicense_BasedOnMit1657 && 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 do1674 {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 else1704 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_MAX1732 && 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 it1763 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 the1784 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 else1818 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 */1846 static 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 do1853 {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 else1863 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 */1881 static SCMREWRITERRES rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut,1882 PCSCMSETTINGSBASE pSettings, SCMCOMMENTSTYLE enmCommentStyle)1883 {1884 if ( !pSettings->fUpdateCopyrightYear1885 && 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_Mit1912 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit1913 ? &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 else1939 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_Mit1945 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)1946 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)1947 Info.pExpectedLicense++;1948 else1949 Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);1950 }1951 else1952 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_MAX1967 && 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 else1973 {1974 ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");1975 fAddLgplDisclaimer = true;1976 }1977 }1978 else if ( Info.iLineLgplNotice == UINT32_MAX1979 && 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->fExternalCopyright2003 && ( !Info.fWellFormedCopyright2004 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));2005 bool fUpdateLicense = !pSettings->fExternalCopyright2006 && Info.enmLicenceOpt != kScmLicense_LeaveAlone2007 && ( !Info.fWellFormedLicense2008 || !Info.fIsCorrectLicense);2009 if ( fUpdateCopyright2010 || fUpdateLicense2011 || 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.iLineComment2031 && (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 else2068 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 else2090 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.iLineAfterLgplComment2104 && 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 else2119 {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 else2136 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. */2144 SCMREWRITERRES 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. */2150 SCMREWRITERRES 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. */2156 SCMREWRITERRES 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. */2162 SCMREWRITERRES 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. */2168 SCMREWRITERRES 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. */2175 SCMREWRITERRES 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. */2181 SCMREWRITERRES 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. */2187 SCMREWRITERRES 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 */2206 SCMREWRITERRES 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 2217 54 typedef enum KMKTOKEN 2218 55 { … … 2289 126 char szBuf[4096]; 2290 127 } KMKPARSER; 128 129 2291 130 2292 131 static KMKTOKEN scmKmkIdentifyToken(const char *pchWord, size_t cchWord) … … 3378 1217 3379 1218 /** @todo this block can probably be merged into the final loop below. */ 3380 unsigned cPendingEols = 0; 3381 unsigned const iSubLineStart1 = iSubLine; 1219 unsigned cPendingEols = 0; 3382 1220 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\') 3383 1221 { … … 3487 1325 { 3488 1326 Assert(offLine + 1 == cchLine); 3489 unsigned const iSubLineStart2 = iSubLine;3490 1327 while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\') 3491 1328 { … … 4099 1936 4100 1937 4101 4102 /*********************************************************************************************************************************4103 * Flower Box Section Markers *4104 *********************************************************************************************************************************/4105 4106 static 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 1938 /** 4217 * Flower box marker comments in C and C++ code.1939 * Makefile.kup are empty files, enforce this. 4218 1940 * 4219 * @returns Modification state.1941 * @returns true if modifications were made, false if not. 4220 1942 * @param pIn The input stream. 4221 1943 * @param pOut The output stream. 4222 1944 * @param pSettings The settings. 4223 1945 */ 4224 SCMREWRITERRES rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 4225 { 4226 if (!pSettings->fFixFlowerBoxMarkers) 1946 SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1947 { 1948 RT_NOREF2(pOut, pSettings); 1949 1950 /* These files should be zero bytes. */ 1951 if (pIn->cb == 0) 4227 1952 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 */ 4317 static 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 */ 4372 static 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 */ 4418 SCMREWRITERRES 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 */ 4515 SCMINCLUDEDIR 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 */ 4600 SCMREWRITERRES 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); 1953 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n"); 4839 1954 return kScmModified; 4840 1955 } 4841 1956 4842 typedef struct4843 {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 4852 int 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 else4877 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 else4886 rc = VWRN_TRAILING_SPACES;4887 }4888 else4889 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 == 04917 || ( pWord->cch <= cchLine - offLine4918 && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))4919 offLine += pWord->cch;4920 else4921 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 */4943 SCMREWRITERRES 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->pszFilename4981 && !RTPATH_IS_SLASH(pszSrc[-1])4982 && !RTPATH_IS_VOLSEP(pszSrc[-1]))4983 pszSrc--;4984 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);4985 }4986 else4987 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 guard5004 * 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 ( pchHash5020 && 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 != cchNormalized5066 || 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 else5103 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 else5155 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 else5168 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 else5175 {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, while5197 * 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 ( pchHash5207 && 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 <= cchLine5214 && !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 <= cchLine5226 && !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 the5241 * 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 else5263 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 and5285 * 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 */5292 SCMREWRITERRES 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 >= cchWord5332 && memcmp(pchHit, pszWord, cchWord) == 05333 && ( pchHit == pchLine5334 || !ScmIsCIdentifierChar(pchHit[-1]))5335 && ( cchLeft == cchWord5336 || !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 else5345 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 COM5365 * 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 */5374 SCMREWRITERRES 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 >= cchWord5431 && memcmp(pchHit, RcTuple.psz, cchWord) == 05432 && ( pchHit == pchLine5433 || !ScmIsCIdentifierChar(pchHit[-1]))5434 && ( cchLeft == cchWord5435 || !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 #else5447 /* 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 #endif5478 }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 * @todo5493 *5494 * Ideas for C/C++:5495 * - space after if, while, for, switch5496 * - spaces in for (i=0;i<x;i++)5497 * - complex conditional, bird style.5498 * - remove unnecessary parentheses.5499 * - sort defined RT_OS_*|| and RT_ARCH5500 * - 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 */5515 SCMREWRITERRES 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 -
trunk/src/bldprogs/scmrw.cpp
r98373 r98374 586 586 * taking tabs into account. 587 587 */ 588 s tatic size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings)588 size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings) 589 589 { 590 590 size_t cchRet = 0; … … 2188 2188 { 2189 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 */2206 SCMREWRITERRES 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 2217 typedef enum KMKTOKEN2218 {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_undefine2249 } KMKTOKEN;2250 2251 typedef struct KMKPARSER2252 {2253 struct2254 {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 2292 static 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 == cchWord2331 && *s_aTokens[i].psz == chFirst2332 && memcmp(s_aTokens[i].psz, pchWord, cchWord) == 0)2333 return s_aTokens[i].enmToken;2334 }2335 #ifdef VBOX_STRICT2336 else2337 for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)2338 Assert(chFirst != *s_aTokens[i].psz);2339 #endif2340 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 */2350 static 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 2362 static 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 2372 DECLINLINE(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 on2387 * subsequent lines. Used to help balance multi-line2388 * 'if' statements (imperfect). Optional.2389 */2390 static 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 == 02402 || !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 2435 static 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 ${} into2455 * account.2456 */2457 static 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 < cchLine2462 && (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. */2484 typedef enum2485 {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_DepFile2498 } 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 whitespace2504 * at given offset or it's beyond the end of the line (both cases will2505 * assert).2506 * @param pchLine The line.2507 * @param cchLine The line length.2508 * @param offStart Offset to the start of the word.2509 */2510 static 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 as2530 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 catch2563 * append, prepend and conditional assignments. Simple and immediate2564 * assignments are handled above.2565 */2566 if ( enmCtx == kKmkWordCtx_TargetFileOrAssignment2567 || enmCtx == kKmkWordCtx_DepFileOrAssignment)2568 {2569 if (off > offStart)2570 {2571 ch = pchLine[off - 1];2572 if (ch == '?' || ch == '+' || ch == '>')2573 off = off - 1 == offStart2574 ? off + 2 /* return '+=', '?=', '<=' */2575 : off - 1; /* up to '+=', '?=', '<=' */2576 else2577 Assert(ch != ':'); /* handled above */2578 }2579 else2580 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 2602 static 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 ifn1of2645 *2646 * @returns dummy (false) to facility return + call.2647 */2648 static 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->iActualDepth2653 - (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't2669 * 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 else2695 {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 {} while2714 * 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 and2737 * 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 ifndef2784 *2785 * @returns dummy (false) to facility return + call.2786 */2787 static 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->iActualDepth2792 - (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, if2798 * 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 else2806 {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't2831 * 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, but2854 * 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 more2880 * than one line.2881 */2882 else if (cLines <= 1)2883 {2884 /* Single line expression: Just assume the expression goes up to the2885 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 else2895 {2896 /* Multi line expression: We normalize leading whitespace using2897 cchMaxLeadWord for now. Expression on line 2+ are indented by two2898 extra characters, because we'd otherwise be puttin the operator on2899 the same level as the 'if', which would be confusing. Thus:2900 2901 if expr12902 + expr22903 endif2904 2905 if expr12906 || expr22907 endif2908 2909 if expr32910 vtg expr42911 endif2912 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 also2918 * 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 not3004 * add indentation for this directive. We do this on the destination buffer3005 * as that can be zero terminated and is therefore usable with strstr.3006 */3007 if (offSrc >= cchLine)3008 *pszDst = '\0';3009 else3010 {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 the3016 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: else3035 *3036 * @returns dummy (false) to facility return + call.3037 */3038 static 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't3088 * 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: endif3119 *3120 * @returns dummy (false) to facility return + call.3121 */3122 static 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't3149 * 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 */3181 static 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 */3199 static 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 3220 static 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 3229 static 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 3238 typedef enum KMKASSIGNTYPE3239 {3240 kKmkAssignType_Recursive,3241 kKmkAssignType_Conditional,3242 kKmkAssignType_Appending,3243 kKmkAssignType_Prepending,3244 kKmkAssignType_Simple,3245 kKmkAssignType_Immediate3246 } KMKASSIGNTYPE;3247 3248 3249 /**3250 * @returns dummy (false) to facility return + call.3251 */3252 static 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 a3287 * 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 else3300 *pszDst++ = ' ';3301 }3302 else3303 {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 else3430 {3431 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */3432 do3433 {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/o3461 * any particular reformatting. Comments within a variable definition are3462 * 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 else3487 {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 */3525 static 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 else3635 ScmStreamPutCh(pOut, ' ');3636 enmCtx = kKmkWordCtx_TargetFile;3637 }3638 3639 /*3640 * We're immediately past the colon now, so eat whitespace and newlines and3641 * 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 else3690 {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 be3762 * on the next line, even if that's very unlikely it is recommened by the coding3763 * guide lines if the line needs to be split. Fortunately, though, the caller3764 * already removes empty empty leading lines, so we only have to consider the3765 * 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 or3773 * not.3774 */3775 static 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 else3827 break;3828 }3829 offCurWord = offLine;3830 iWord++;3831 3832 /*3833 * To simplify the assignment operator checks, we just check the3834 * 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 else3878 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 the3884 * 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 else3892 {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 3918 static 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 * @todo3938 *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 */3943 SCMREWRITERRES 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 a3972 * tab is considered another command for the recipe.3973 */3974 if (Parser.fInRecipe && *pchLine == '\t')3975 {3976 /* Do we do anything here? */3977 }3978 else3979 {3980 /*3981 * Skip leading whitespace and check for directives (simplified).3982 *3983 * This is simplified in the sense that GNU make first checks for variable3984 * assignments, so that directive can be used as variable names. We don't3985 * 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 < cchLine3994 && ( 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 it4000 (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 other4005 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 2190 } 4099 2191
Note:
See TracChangeset
for help on using the changeset viewer.