VirtualBox

Changeset 98374 in vbox


Ignore:
Timestamp:
Feb 1, 2023 9:48:59 AM (2 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
155616
Message:

scm: Split the ~2000 lines of .kmk rewriting code out of scmrw.cpp and into a separate file. bugref:10348

Location:
trunk/src/bldprogs
Files:
3 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/bldprogs/Makefile.kmk

    r98103 r98374  
    5656        scmdiff.cpp \
    5757        scmrw.cpp \
     58        scmrw-kmk.cpp \
    5859       scmparser.cpp \
    5960        scmstream.cpp \
  • trunk/src/bldprogs/scm.h

    r98368 r98374  
    207207}
    208208
     209size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings);
    209210
    210211/** @} */
  • trunk/src/bldprogs/scmrw-kmk.cpp

    r98373 r98374  
    11/* $Id$ */
    22/** @file
    3  * IPRT Testcase / Tool - Source Code Massager.
     3 * IPRT Testcase / Tool - Source Code Massager, Makefile.kmk/kup.
    44 */
    55
     
    5252*   Structures and Typedefs                                                                                                      *
    5353*********************************************************************************************************************************/
    54 /** License types. */
    55 typedef enum SCMLICENSETYPE
    56 {
    57     kScmLicenseType_Invalid = 0,
    58     kScmLicenseType_OseGpl,
    59     kScmLicenseType_OseDualGplCddl,
    60     kScmLicenseType_OseCddl,
    61     kScmLicenseType_VBoxLgpl,
    62     kScmLicenseType_Mit,
    63     kScmLicenseType_Confidential
    64 } SCMLICENSETYPE;
    65 
    66 /** A license. */
    67 typedef struct SCMLICENSETEXT
    68 {
    69     /** The license type. */
    70     SCMLICENSETYPE  enmType;
    71     /** The license option. */
    72     SCMLICENSE      enmOpt;
    73     /** The license text.   */
    74     const char     *psz;
    75     /** The license text length. */
    76     size_t          cch;
    77 } SCMLICENSETEXT;
    78 /** Pointer to a license. */
    79 typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;
    80 
    81 /**
    82  * Copyright + license rewriter state.
    83  */
    84 typedef struct SCMCOPYRIGHTINFO
    85 {
    86     /** State. */
    87     PSCMRWSTATE         pState;                 /**< input */
    88     /** The comment style (neede for C/C++). */
    89     SCMCOMMENTSTYLE     enmCommentStyle;        /**< input */
    90 
    91     /** Number of comments we've parsed. */
    92     uint32_t            cComments;
    93 
    94     /** Copy of the contributed-by line if present. */
    95     char               *pszContributedBy;
    96 
    97     /** @name Common info
    98      * @{ */
    99     uint32_t            iLineComment;
    100     uint32_t            cLinesComment;          /**< This excludes any external license lines. */
    101     /** @} */
    102 
    103     /** @name Copyright info
    104      * @{ */
    105     uint32_t            iLineCopyright;
    106     uint32_t            uFirstYear;
    107     uint32_t            uLastYear;
    108     bool                fWellFormedCopyright;
    109     bool                fUpToDateCopyright;
    110     /** @} */
    111 
    112     /** @name License info
    113      * @{ */
    114     bool                fOpenSource;            /**< input */
    115     PCSCMLICENSETEXT    pExpectedLicense;       /**< input */
    116     PCSCMLICENSETEXT    paLicenses;             /**< input */
    117     SCMLICENSE          enmLicenceOpt;          /**< input */
    118     uint32_t            iLineLicense;
    119     uint32_t            cLinesLicense;
    120     PCSCMLICENSETEXT    pCurrentLicense;
    121     bool                fIsCorrectLicense;
    122     bool                fWellFormedLicense;
    123     bool                fExternalLicense;
    124     /** @} */
    125 
    126     /** @name LGPL licence notice and disclaimer info
    127      * @{ */
    128     /** Wheter to check for LGPL license notices and disclaimers. */
    129     bool                fCheckforLgpl;
    130     /** The approximate line we found the (first) LGPL licence notice on. */
    131     uint32_t            iLineLgplNotice;
    132     /** The line number after the LGPL notice comment. */
    133     uint32_t            iLineAfterLgplComment;
    134     /** The LGPL disclaimer line. */
    135     uint32_t            iLineLgplDisclaimer;
    136     /** @} */
    137 
    138 } SCMCOPYRIGHTINFO;
    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-mit
    262  * @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 written
    288  *       "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a
    289  *       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" is
    311  *       replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".
    312  *       Its layout is wider, so it is a couple of lines shorter. */
    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 sections
    334  *       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 next
    356  *       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 of
    379  *       "sublicense" and adding an illogical "(including the next
    380  *       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 before
    539      * doing the work.
    540      */
    541     uint32_t    cUpper = 0;
    542     uint32_t    cLower = 0;
    543     uint32_t    cCamel = 0;
    544     SCMEOL      enmEol;
    545     size_t      cchLine;
    546     const char *pchLine;
    547     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    548     {
    549         while (   cchLine > 2
    550                && RT_C_IS_SPACE(*pchLine))
    551         {
    552             pchLine++;
    553             cchLine--;
    554         }
    555         if (   (   cchLine > 3
    556                 && RT_C_IS_SPACE(pchLine[2]))
    557             || cchLine == 3)
    558         {
    559             if (   pchLine[0] == 'R'
    560                 && pchLine[1] == 'E'
    561                 && pchLine[2] == 'M')
    562                 cUpper++;
    563             else if (   pchLine[0] == 'r'
    564                      && pchLine[1] == 'e'
    565                      && pchLine[2] == 'm')
    566                 cLower++;
    567             else if (   pchLine[0] == 'R'
    568                      && pchLine[1] == 'e'
    569                      && pchLine[2] == 'm')
    570                 cCamel++;
    571         }
    572     }
    573 
    574     ScmStreamRewindForReading(pIn);
    575 
    576     if (cLower >= cUpper && cLower >= cCamel)
    577         return kScmCommentStyle_Rem_Lower;
    578     if (cCamel >= cLower && cCamel >= cUpper)
    579         return kScmCommentStyle_Rem_Camel;
    580     return kScmCommentStyle_Rem_Upper;
    581 }
    582 
    583 
    584 /**
    585  * Calculates the number of spaces from @a offStart to @a offEnd in @a pchLine,
    586  * taking tabs into account.
    587  */
    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 than
    645      * at the start, due to source code indentation.
    646      */
    647     if (pchLine[cchLine - 1])
    648         return false;
    649 
    650     /*
    651      * Don't bother inlining loop code.
    652      */
    653     return isBlankLineSlow(pchLine, cchLine);
    654 }
    655 
    656 
    657 /**
    658  * Checks if there are @a cch blanks at @a pch.
    659  *
    660  * @returns true if span of @a cch blanks, false if not.
    661  * @param   pch                 The start of the span to check.
    662  * @param   cch                 The length of the span.
    663  */
    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 == 0
    697             ||  !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
    698             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    699         else
    700         {
    701             cchLine--;
    702             while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
    703                 cchLine--;
    704             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    705             fModified = true;
    706         }
    707         if (RT_FAILURE(rc))
    708             return kScmUnmodified;
    709     }
    710     if (fModified)
    711         ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
    712     return fModified ? kScmModified : kScmUnmodified;
    713 }
    714 
    715 /**
    716  * Expand tabs.
    717  *
    718  * @returns Modification state.
    719  * @param   pIn                 The input stream.
    720  * @param   pOut                The output stream.
    721  * @param   pSettings           The settings.
    722  */
    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         else
    740         {
    741             size_t      offTab   = 0;
    742             const char *pchChunk = pchLine;
    743             for (;;)
    744             {
    745                 size_t  cchChunk = pchTab - pchChunk;
    746                 offTab += cchChunk;
    747                 ScmStreamWrite(pOut, pchChunk, cchChunk);
    748 
    749                 size_t  cchToTab = cchTab - offTab % cchTab;
    750                 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
    751                 offTab += cchToTab;
    752 
    753                 pchChunk = pchTab + 1;
    754                 size_t  cchLeft  = cchLine - (pchChunk - pchLine);
    755                 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
    756                 if (!pchTab)
    757                 {
    758                     rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
    759                     break;
    760                 }
    761             }
    762 
    763             fModified = true;
    764         }
    765         if (RT_FAILURE(rc))
    766             return kScmUnmodified;
    767     }
    768     if (fModified)
    769         ScmVerbose(pState, 2, " * Expanded tabs\n");
    770     return fModified ? kScmModified : kScmUnmodified;
    771 }
    772 
    773 /**
    774  * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
    775  *
    776  * @returns Modification state.
    777  * @param   pIn                 The input stream.
    778  * @param   pOut                The output stream.
    779  * @param   pSettings           The settings.
    780  * @param   enmDesiredEol       The desired end of line indicator type.
    781  * @param   pszDesiredSvnEol    The desired svn:eol-style.
    782  */
    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 != enmDesiredEol
    796             && enmEol != SCMEOL_NONE)
    797         {
    798             fModified = true;
    799             enmEol = enmDesiredEol;
    800         }
    801         int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    802         if (RT_FAILURE(rc))
    803             return kScmUnmodified;
    804     }
    805     if (fModified)
    806         ScmVerbose(pState, 2, " * Converted EOL markers\n");
    807 
    808     /* Check svn:eol-style if appropriate */
    809     if (   pSettings->fSetSvnEol
    810         && ScmSvnIsInWorkingCopy(pState))
    811     {
    812         char *pszEol;
    813         int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
    814         if (   (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
    815             || rc == VERR_NOT_FOUND)
    816         {
    817             if (rc == VERR_NOT_FOUND)
    818                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
    819             else
    820                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
    821             int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
    822             if (RT_FAILURE(rc2))
    823                 ScmError(pState, rc2, "ScmSvnSetProperty: %Rrc\n", rc2);
    824         }
    825         if (RT_SUCCESS(rc))
    826             RTStrFree(pszEol);
    827     }
    828 
    829     /** @todo also check the subversion svn:eol-style state! */
    830     return fModified ? kScmModified : kScmUnmodified;
    831 }
    832 
    833 /**
    834  * Force native end of line indicator.
    835  *
    836  * @returns Modification state.
    837  * @param   pIn                 The input stream.
    838  * @param   pOut                The output stream.
    839  * @param   pSettings           The settings.
    840  */
    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 #else
    846     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF,   "native");
    847 #endif
    848 }
    849 
    850 /**
    851  * Force the stream to use LF as the end of line indicator.
    852  *
    853  * @returns Modification state.
    854  * @param   pIn                 The input stream.
    855  * @param   pOut                The output stream.
    856  * @param   pSettings           The settings.
    857  */
    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 line
    878  * at the end of the file.
    879  *
    880  * @returns Modification state.
    881  * @param   pIn                 The input stream.
    882  * @param   pOut                The output stream.
    883  * @param   pSettings           The settings.
    884  *
    885  * @remarks ASSUMES trailing white space has been removed already.
    886  */
    887 SCMREWRITERRES rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    888 {
    889     if (   !pSettings->fStripTrailingLines
    890         && !pSettings->fForceTrailingLine
    891         && !pSettings->fForceFinalEol)
    892         return kScmUnmodified;
    893 
    894     size_t const cLines = ScmStreamCountLines(pIn);
    895 
    896     /* Empty files remains empty. */
    897     if (cLines <= 1)
    898         return kScmUnmodified;
    899 
    900     /* Figure out if we need to adjust the number of lines or not. */
    901     size_t cLinesNew = cLines;
    902 
    903     if (   pSettings->fStripTrailingLines
    904         && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    905     {
    906         while (   cLinesNew > 1
    907                && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
    908             cLinesNew--;
    909     }
    910 
    911     if (    pSettings->fForceTrailingLine
    912         && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    913         cLinesNew++;
    914 
    915     bool fFixMissingEol = pSettings->fForceFinalEol
    916                        && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
    917 
    918     if (   !fFixMissingEol
    919         && cLines == cLinesNew)
    920         return kScmUnmodified;
    921 
    922     /* Copy the number of lines we've arrived at. */
    923     ScmStreamRewindForReading(pIn);
    924 
    925     size_t cCopied = RT_MIN(cLinesNew, cLines);
    926     ScmStreamCopyLines(pOut, pIn, cCopied);
    927 
    928     if (cCopied != cLinesNew)
    929     {
    930         while (cCopied++ < cLinesNew)
    931             ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
    932     }
    933     /* Fix missing EOL if required. */
    934     else if (fFixMissingEol)
    935     {
    936         if (ScmStreamGetEol(pIn) == SCMEOL_LF)
    937             ScmStreamWrite(pOut, "\n", 1);
    938         else
    939             ScmStreamWrite(pOut, "\r\n", 2);
    940     }
    941 
    942     ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
    943     return kScmModified;
    944 }
    945 
    946 /**
    947  * Make sure there is no svn:executable property on the current file.
    948  *
    949  * @returns kScmUnmodified - the state carries these kinds of changes.
    950  * @param   pState              The rewriter state.
    951  * @param   pIn                 The input stream.
    952  * @param   pOut                The output stream.
    953  * @param   pSettings           The settings.
    954  */
    955 SCMREWRITERRES rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    956 {
    957     RT_NOREF2(pIn, pOut);
    958     if (   !pSettings->fSetSvnExecutable
    959         || !ScmSvnIsInWorkingCopy(pState))
    960         return kScmUnmodified;
    961 
    962     int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
    963     if (RT_SUCCESS(rc))
    964     {
    965         ScmVerbose(pState, 2, " * removing svn:executable\n");
    966         rc = ScmSvnDelProperty(pState, "svn:executable");
    967         if (RT_FAILURE(rc))
    968             ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
    969     }
    970     return kScmUnmodified;
    971 }
    972 
    973 /**
    974  * Make sure there is no svn:keywords property on the current file.
    975  *
    976  * @returns kScmUnmodified - the state carries these kinds of changes.
    977  * @param   pState              The rewriter state.
    978  * @param   pIn                 The input stream.
    979  * @param   pOut                The output stream.
    980  * @param   pSettings           The settings.
    981  */
    982 SCMREWRITERRES rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    983 {
    984     RT_NOREF2(pIn, pOut);
    985     if (   !pSettings->fSetSvnExecutable
    986         || !ScmSvnIsInWorkingCopy(pState))
    987         return kScmUnmodified;
    988 
    989     int rc = ScmSvnQueryProperty(pState, "svn:keywords", NULL);
    990     if (RT_SUCCESS(rc))
    991     {
    992         ScmVerbose(pState, 2, " * removing svn:keywords\n");
    993         rc = ScmSvnDelProperty(pState, "svn:keywords");
    994         if (RT_FAILURE(rc))
    995             ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
    996     }
    997     return kScmUnmodified;
    998 }
    999 
    1000 /**
    1001  * Make sure there is no svn:eol-style property on the current file.
    1002  *
    1003  * @returns kScmUnmodified - the state carries these kinds of changes.
    1004  * @param   pState              The rewriter state.
    1005  * @param   pIn                 The input stream.
    1006  * @param   pOut                The output stream.
    1007  * @param   pSettings           The settings.
    1008  */
    1009 SCMREWRITERRES rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1010 {
    1011     RT_NOREF2(pIn, pOut);
    1012     if (   !pSettings->fSetSvnExecutable
    1013         || !ScmSvnIsInWorkingCopy(pState))
    1014         return kScmUnmodified;
    1015 
    1016     int rc = ScmSvnQueryProperty(pState, "svn:eol-style", NULL);
    1017     if (RT_SUCCESS(rc))
    1018     {
    1019         ScmVerbose(pState, 2, " * removing svn:eol-style\n");
    1020         rc = ScmSvnDelProperty(pState, "svn:eol-style");
    1021         if (RT_FAILURE(rc))
    1022             ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
    1023     }
    1024     return kScmUnmodified;
    1025 }
    1026 
    1027 /**
    1028  * Makes sure the svn properties are appropriate for a binary.
    1029  *
    1030  * @returns kScmUnmodified - the state carries these kinds of changes.
    1031  * @param   pState              The rewriter state.
    1032  * @param   pIn                 The input stream.
    1033  * @param   pOut                The output stream.
    1034  * @param   pSettings           The settings.
    1035  */
    1036 SCMREWRITERRES rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1037 {
    1038     RT_NOREF2(pIn, pOut);
    1039     if (   !pSettings->fSetSvnExecutable
    1040         || !ScmSvnIsInWorkingCopy(pState))
    1041         return kScmUnmodified;
    1042 
    1043     /* remove svn:eol-style and svn:keywords */
    1044     static const char * const s_apszRemove[] = { "svn:eol-style", "svn:keywords" };
    1045     for (uint32_t i = 0; i < RT_ELEMENTS(s_apszRemove); i++)
    1046     {
    1047         char *pszValue;
    1048         int rc = ScmSvnQueryProperty(pState, s_apszRemove[i], &pszValue);
    1049         if (RT_SUCCESS(rc))
    1050         {
    1051             ScmVerbose(pState, 2, " * removing %s=%s\n", s_apszRemove[i], pszValue);
    1052             RTStrFree(pszValue);
    1053             rc = ScmSvnDelProperty(pState, s_apszRemove[i]);
    1054             if (RT_FAILURE(rc))
    1055                 ScmError(pState, rc, "ScmSvnSetProperty(,%s): %Rrc\n", s_apszRemove[i], rc);
    1056         }
    1057         else if (rc != VERR_NOT_FOUND)
    1058             ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
    1059     }
    1060 
    1061     /* Make sure there is a svn:mime-type set. */
    1062     int rc = ScmSvnQueryProperty(pState, "svn:mime-type", NULL);
    1063     if (rc == VERR_NOT_FOUND)
    1064     {
    1065         ScmVerbose(pState, 2, " * settings svn:mime-type\n");
    1066         rc = ScmSvnSetProperty(pState, "svn:mime-type", "application/octet-stream");
    1067         if (RT_FAILURE(rc))
    1068             ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
    1069     }
    1070     else if (RT_FAILURE(rc))
    1071         ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
    1072 
    1073     return kScmUnmodified;
    1074 }
    1075 
    1076 /**
    1077  * Make sure the Id and Revision keywords are expanded.
    1078  *
    1079  * @returns kScmUnmodified - the state carries these kinds of changes.
    1080  * @param   pState              The rewriter state.
    1081  * @param   pIn                 The input stream.
    1082  * @param   pOut                The output stream.
    1083  * @param   pSettings           The settings.
    1084  */
    1085 SCMREWRITERRES rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1086 {
    1087     RT_NOREF2(pIn, pOut);
    1088     if (   !pSettings->fSetSvnKeywords
    1089         || !ScmSvnIsInWorkingCopy(pState))
    1090         return kScmUnmodified;
    1091 
    1092     char *pszKeywords;
    1093     int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
    1094     if (    RT_SUCCESS(rc)
    1095         && (   !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string.  */
    1096             || !strstr(pszKeywords, "Revision")) )
    1097     {
    1098         if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
    1099             rc = RTStrAAppend(&pszKeywords, " Id Revision");
    1100         else if (!strstr(pszKeywords, "Id"))
    1101             rc = RTStrAAppend(&pszKeywords, " Id");
    1102         else
    1103             rc = RTStrAAppend(&pszKeywords, " Revision");
    1104         if (RT_SUCCESS(rc))
    1105         {
    1106             ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
    1107             rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
    1108             if (RT_FAILURE(rc))
    1109                 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
    1110         }
    1111         else
    1112             ScmError(pState, rc, "RTStrAppend: %Rrc\n", rc);
    1113         RTStrFree(pszKeywords);
    1114     }
    1115     else if (rc == VERR_NOT_FOUND)
    1116     {
    1117         ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
    1118         rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
    1119         if (RT_FAILURE(rc))
    1120             ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
    1121     }
    1122     else if (RT_SUCCESS(rc))
    1123         RTStrFree(pszKeywords);
    1124 
    1125     return kScmUnmodified;
    1126 }
    1127 
    1128 /**
    1129  * Checks the svn:sync-process value and that parent is exported too.
    1130  *
    1131  * @returns kScmUnmodified - the state carries these kinds of changes.
    1132  * @param   pState              The rewriter state.
    1133  * @param   pIn                 The input stream.
    1134  * @param   pOut                The output stream.
    1135  * @param   pSettings           The settings.
    1136  */
    1137 SCMREWRITERRES rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1138 {
    1139     RT_NOREF2(pIn, pOut);
    1140     if (   pSettings->fSkipSvnSyncProcess
    1141         || !ScmSvnIsInWorkingCopy(pState))
    1142         return kScmUnmodified;
    1143 
    1144     char *pszSyncProcess;
    1145     int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
    1146     if (RT_SUCCESS(rc))
    1147     {
    1148         if (strcmp(pszSyncProcess, "export") == 0)
    1149         {
    1150             char *pszParentSyncProcess;
    1151             rc = ScmSvnQueryParentProperty(pState, "svn:sync-process", &pszParentSyncProcess);
    1152             if (RT_SUCCESS(rc))
    1153             {
    1154                 if (strcmp(pszSyncProcess, "export") != 0)
    1155                     ScmError(pState, VERR_INVALID_STATE,
    1156                              "svn:sync-process=export, but parent directory differs: %s\n"
    1157                              "WARNING! Make sure to unexport everything inside the directory first!\n"
    1158                              "         Then you may export the directory and stuff inside it if you want.\n"
    1159                              "         (Just exporting the directory will not make anything inside it externally visible.)\n"
    1160                              , pszParentSyncProcess);
    1161                 RTStrFree(pszParentSyncProcess);
    1162             }
    1163             else if (rc == VERR_NOT_FOUND)
    1164                 ScmError(pState, VERR_NOT_FOUND,
    1165                          "svn:sync-process=export, but parent directory is not exported!\n"
    1166                          "WARNING! Make sure to unexport everything inside the directory first!\n"
    1167                          "         Then you may export the directory and stuff inside it if you want.\n"
    1168                          "         (Just exporting the directory will not make anything inside it externally visible.)\n");
    1169             else
    1170                 ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc);
    1171         }
    1172         else if (strcmp(pszSyncProcess, "ignore") != 0)
    1173             ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess);
    1174         RTStrFree(pszSyncProcess);
    1175     }
    1176     else if (rc != VERR_NOT_FOUND)
    1177         ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
    1178 
    1179     return kScmUnmodified;
    1180 }
    1181 
    1182 /**
    1183  * Checks the that there is no bidirectional unicode fun in the file.
    1184  *
    1185  * @returns kScmUnmodified - the state carries these kinds of changes.
    1186  * @param   pState              The rewriter state.
    1187  * @param   pIn                 The input stream.
    1188  * @param   pOut                The output stream.
    1189  * @param   pSettings           The settings.
    1190  */
    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             else
    1237                 ScmFixManually(pState, "%u:%zu: Invalid UTF-8 encoding: %Rrc\n", iLine, pchCur - pchLine, rc);
    1238         }
    1239     }
    1240 
    1241     return kScmUnmodified;
    1242 }
    1243 
    1244 
    1245 
    1246 /*********************************************************************************************************************************
    1247 *   Copyright & License                                                                                                          *
    1248 *********************************************************************************************************************************/
    1249 
    1250 /**
    1251  * Compares two strings word-by-word, ignoring spaces, punctuation and case.
    1252  *
    1253  * Assumes ASCII strings.
    1254  *
    1255  * @returns true if they match, false if not.
    1256  * @param   psz1        The first string.  This is typically the known one.
    1257  * @param   psz2        The second string.  This is typically the unknown one,
    1258  *                      which is why we return a next pointer for this one.
    1259  * @param   ppsz2Next   Where to return the next part of the 2nd string.  If
    1260  *                      this is NULL, the whole string must match.
    1261  */
    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 == ch2
    1270             || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
    1271         {
    1272             if (ch1)
    1273             {
    1274                 psz1++;
    1275                 psz2++;
    1276             }
    1277             else
    1278             {
    1279                 if (ppsz2Next)
    1280                     *ppsz2Next = psz2;
    1281                 return true;
    1282             }
    1283         }
    1284         else
    1285         {
    1286             /* Try skip spaces an punctuation. */
    1287             while (   RT_C_IS_SPACE(ch1)
    1288                    || RT_C_IS_PUNCT(ch1))
    1289                 ch1 = *++psz1;
    1290 
    1291             if (ch1 == '\0' && ppsz2Next)
    1292             {
    1293                 *ppsz2Next = psz2;
    1294                 return true;
    1295             }
    1296 
    1297             while (   RT_C_IS_SPACE(ch2)
    1298                    || RT_C_IS_PUNCT(ch2))
    1299                 ch2 = *++psz2;
    1300 
    1301             if (   ch1 != ch2
    1302                 && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
    1303             {
    1304                 if (ppsz2Next)
    1305                     *ppsz2Next = psz2;
    1306                 return false;
    1307             }
    1308         }
    1309     }
    1310 }
    1311 
    1312 /**
    1313  * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
    1314  * and case.
    1315  *
    1316  * @returns true if found, false if not.
    1317  * @param   pszText             The haystack to search in.
    1318  * @param   cchText             The length @a pszText.
    1319  * @param   pszFragment         The needle to search for.
    1320  * @param   ppszStart           Where to return the address in @a pszText where
    1321  *                              the fragment was found.  Optional.
    1322  * @param   ppszNext            Where to return the pointer to the first char in
    1323  *                              @a pszText after the fragment.  Optional.
    1324  *
    1325  * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
    1326  *          This character must not be space or punctuation.
    1327  */
    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 == NULL
    1352             || (   pszHit2 != NULL
    1353                 && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
    1354             pszHit = pszHit2;
    1355 
    1356         const char *pszNext;
    1357         if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
    1358         {
    1359             if (ppszStart)
    1360                 *ppszStart = pszHit;
    1361             if (ppszNext)
    1362                 *ppszNext = pszNext;
    1363             return true;
    1364         }
    1365 
    1366         cchText -= pszHit - pszText + 1;
    1367         pszText = pszHit + 1;
    1368     }
    1369 }
    1370 
    1371 
    1372 /**
    1373  * Counts the number of lines in the given substring.
    1374  *
    1375  * @returns The number of lines.
    1376  * @param   psz          The start of the substring.
    1377  * @param   cch          The length of the substring.
    1378  */
    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         else
    1388             return cLines + (*psz != '\0');
    1389         cch -= pszEol + 1 - psz;
    1390         if (!cch)
    1391             return cLines;
    1392         psz  = pszEol + 1;
    1393     }
    1394 }
    1395 
    1396 
    1397 /**
    1398  * Comment parser callback for locating copyright and license.
    1399  */
    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, these
    1417      * comes first when present.
    1418      */
    1419     const char *pchContributedBy = NULL;
    1420     size_t      cchContributedBy = 0;
    1421     size_t      cBlankLinesAfterContributedBy = 0;
    1422     if (    pState->pszContributedBy == NULL
    1423         && (   pState->iLineCopyright == UINT32_MAX
    1424             || pState->iLineLicense == UINT32_MAX)
    1425         && (   (    cchBody > sizeof("Contributed by")
    1426                 && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
    1427             || (    cchBody > sizeof("Includes contributions from")
    1428                 && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
    1429     {
    1430         const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
    1431         while (pszNextLine && pszNextLine[1] != '\n')
    1432             pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
    1433         if (pszNextLine)
    1434         {
    1435             pchContributedBy = pszBody;
    1436             cchContributedBy = pszNextLine - pszBody;
    1437 
    1438             /* Skip the copyright line and any blank lines following it. */
    1439             cchBody -= cchContributedBy + 1;
    1440             pszBody  = pszNextLine + 1;
    1441             iLine   += 1;
    1442             while (*pszBody == '\n')
    1443             {
    1444                 pszBody++;
    1445                 cchBody--;
    1446                 iLine++;
    1447                 cBlankLinesAfterContributedBy++;
    1448             }
    1449         }
    1450     }
    1451 
    1452     /*
    1453      * Look for the copyright line.
    1454      */
    1455     bool     fFoundCopyright = false;
    1456     uint32_t cBlankLinesAfterCopyright = 0;
    1457     if (   pState->iLineCopyright == UINT32_MAX
    1458         && cchBody > sizeof("Copyright") + RT_MIN(sizeof(g_szCopyrightHolder), sizeof(g_szOldCopyrightHolder))
    1459         && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
    1460     {
    1461         const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
    1462 
    1463         /* Oracle copyright? */
    1464         const char *pszEnd  = pszNextLine ? pszNextLine : &pszBody[cchBody];
    1465         while (RT_C_IS_SPACE(pszEnd[-1]))
    1466             pszEnd--;
    1467         if (   (   (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
    1468                 && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
    1469                 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
    1470             || (   (uintptr_t)(pszEnd - pszBody) > sizeof(g_szOldCopyrightHolder)
    1471                 && (*(unsigned char *)(pszEnd - sizeof(g_szOldCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
    1472                 && RTStrNICmp(pszEnd - sizeof(g_szOldCopyrightHolder) + 1, RT_STR_TUPLE(g_szOldCopyrightHolder)) == 0) )
    1473         {
    1474             /* Parse out the year(s). */
    1475             const char *psz = pszBody + sizeof("copyright");
    1476             while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
    1477                 psz++;
    1478             if (RT_C_IS_DIGIT(*psz))
    1479             {
    1480                 char *pszNext;
    1481                 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
    1482                 if (   RT_SUCCESS(rc)
    1483                     && rc != VWRN_NUMBER_TOO_BIG
    1484                     && rc != VWRN_NEGATIVE_UNSIGNED)
    1485                 {
    1486                     if (   pState->uFirstYear < 1975
    1487                         || pState->uFirstYear > 3000)
    1488                     {
    1489                         char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
    1490                         RTStrPurgeEncoding(pszCopy);
    1491                         ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%s')\n",
    1492                                  pState->uFirstYear, pszCopy);
    1493                         RTStrFree(pszCopy);
    1494                         pState->uFirstYear = UINT32_MAX;
    1495                     }
    1496 
    1497                     while (RT_C_IS_SPACE(*pszNext))
    1498                         pszNext++;
    1499                     if (*pszNext == '-')
    1500                     {
    1501                         do
    1502                             pszNext++;
    1503                         while (RT_C_IS_SPACE(*pszNext));
    1504                         rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
    1505                         if (   RT_SUCCESS(rc)
    1506                             && rc != VWRN_NUMBER_TOO_BIG
    1507                             && rc != VWRN_NEGATIVE_UNSIGNED)
    1508                         {
    1509                             if (   pState->uLastYear < 1975
    1510                                 || pState->uLastYear > 3000)
    1511                             {
    1512                                 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
    1513                                 RTStrPurgeEncoding(pszCopy);
    1514                                 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%s')\n",
    1515                                          pState->uLastYear, pszCopy);
    1516                                 RTStrFree(pszCopy);
    1517                                 pState->uLastYear = UINT32_MAX;
    1518                             }
    1519                             else if (pState->uFirstYear > pState->uLastYear)
    1520                             {
    1521                                 char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
    1522                                 RTStrPurgeEncoding(pszCopy);
    1523                                 RTMsgWarning("Copyright years switched(?): '%s'\n", pszCopy);
    1524                                 RTStrFree(pszCopy);
    1525                                 uint32_t iTmp = pState->uLastYear;
    1526                                 pState->uLastYear = pState->uFirstYear;
    1527                                 pState->uFirstYear = iTmp;
    1528                             }
    1529                         }
    1530                         else
    1531                         {
    1532                             pState->uLastYear = UINT32_MAX;
    1533                             char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
    1534                             RTStrPurgeEncoding(pszCopy);
    1535                             ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
    1536                                      "Failed to parse second copyright year: '%s'\n", pszCopy);
    1537                             RTMemFree(pszCopy);
    1538                         }
    1539                     }
    1540                     else if (*pszNext != g_szCopyrightHolder[0])
    1541                     {
    1542                         char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
    1543                         RTStrPurgeEncoding(pszCopy);
    1544                         ScmError(pState->pState, VERR_PARSE_ERROR,
    1545                                  "Failed to parse copyright: '%s'\n", pszCopy);
    1546                         RTMemFree(pszCopy);
    1547                     } else
    1548                         pState->uLastYear = pState->uFirstYear;
    1549                 }
    1550                 else
    1551                 {
    1552                     pState->uFirstYear = UINT32_MAX;
    1553                     char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
    1554                     RTStrPurgeEncoding(pszCopy);
    1555                     ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
    1556                              "Failed to parse copyright year: '%s'\n", pszCopy);
    1557                     RTMemFree(pszCopy);
    1558                 }
    1559             }
    1560 
    1561             /* The copyright comment must come before the license. */
    1562             if (pState->iLineLicense != UINT32_MAX)
    1563                 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
    1564                          iLine, pState->iLineLicense);
    1565 
    1566             /* In C/C++ code, this must be a multiline comment.  While in python it
    1567                must be a */
    1568             if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
    1569                 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
    1570             else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
    1571                 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
    1572 
    1573             /* The copyright must be followed by the license. */
    1574             if (!pszNextLine)
    1575                 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
    1576 
    1577             /* Quit if we've flagged a failure. */
    1578             if (RT_FAILURE(pState->pState->rc))
    1579                 return VERR_CALLBACK_RETURN;
    1580 
    1581             /* Check if it's well formed and up to date. */
    1582             char   szWellFormed[256];
    1583             size_t cchWellFormed;
    1584             if (pState->uFirstYear == pState->uLastYear)
    1585                 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
    1586                                             pState->uFirstYear, g_szCopyrightHolder);
    1587             else
    1588                 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
    1589                                             pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
    1590             pState->fUpToDateCopyright   = pState->uLastYear == g_uYear;
    1591             pState->iLineCopyright       = iLine;
    1592             pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
    1593                                         && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
    1594             if (!pState->fWellFormedCopyright)
    1595                 ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
    1596 
    1597             /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
    1598             if (pInfo->cBlankLinesBefore != 1)
    1599             {
    1600                 ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
    1601                            pInfo->cBlankLinesBefore);
    1602                 pState->fWellFormedCopyright = false;
    1603             }
    1604 
    1605             /* If the comment doesn't start in column 1, trigger rewrite. */
    1606             if (pInfo->offStart != 0)
    1607             {
    1608                 ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
    1609                 pState->fWellFormedCopyright = false;
    1610                 /** @todo check that there isn't any code preceeding the comment. */
    1611             }
    1612 
    1613             if (pchContributedBy)
    1614             {
    1615                 pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
    1616                 if (cBlankLinesAfterContributedBy != 1)
    1617                 {
    1618                     ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
    1619                                cBlankLinesAfterContributedBy);
    1620                     pState->fWellFormedCopyright = false;
    1621                 }
    1622             }
    1623 
    1624             fFoundCopyright = true;
    1625             ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
    1626                        pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
    1627         }
    1628         else
    1629         {
    1630             char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
    1631             RTStrPurgeEncoding(pszCopy);
    1632             ScmVerbose(pState->pState, 3, "not oracle copyright: '%s'\n", pszCopy);
    1633             RTStrFree(pszCopy);
    1634         }
    1635 
    1636         if (!pszNextLine)
    1637             return VINF_SUCCESS;
    1638 
    1639         /* Skip the copyright line and any blank lines following it. */
    1640         cchBody -= pszNextLine - pszBody + 1;
    1641         pszBody  = pszNextLine + 1;
    1642         iLine   += 1;
    1643         while (*pszBody == '\n')
    1644         {
    1645             pszBody++;
    1646             cchBody--;
    1647             iLine++;
    1648             cBlankLinesAfterCopyright++;
    1649         }
    1650 
    1651         /*
    1652          * If we have a based-on-mit scenario, check for the lead in now and
    1653          * complain if not found.
    1654          */
    1655         if (   fFoundCopyright
    1656             && pState->enmLicenceOpt == kScmLicense_BasedOnMit
    1657             && pState->iLineLicense == UINT32_MAX)
    1658         {
    1659             if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
    1660             {
    1661                 /* Take down a comment area which goes up to 'this file is based on'.
    1662                    The license line and length isn't used but gets set to cover the current line. */
    1663                 pState->iLineComment        = pInfo->iLineStart;
    1664                 pState->cLinesComment       = iLine - pInfo->iLineStart;
    1665                 pState->iLineLicense        = iLine;
    1666                 pState->cLinesLicense       = 1;
    1667                 pState->fExternalLicense    = true;
    1668                 pState->fIsCorrectLicense   = true;
    1669                 pState->fWellFormedLicense  = true;
    1670 
    1671                 /* Check if we've got a MIT a license here or not. */
    1672                 pState->pCurrentLicense     = NULL;
    1673                 do
    1674                 {
    1675                     const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
    1676                     if (!pszEol || pszEol[1] == '\0')
    1677                     {
    1678                         pszBody += cchBody;
    1679                         cchBody = 0;
    1680                         break;
    1681                     }
    1682                     cchBody -= pszEol - pszBody + 1;
    1683                     pszBody  = pszEol + 1;
    1684                     iLine++;
    1685 
    1686                     for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
    1687                     {
    1688                         const char *pszNext;
    1689                         if (   pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
    1690                             && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
    1691                         {
    1692                             pState->pCurrentLicense = pCur;
    1693                             break;
    1694                         }
    1695                     }
    1696                 } while (!pState->pCurrentLicense);
    1697                 if (!pState->pCurrentLicense)
    1698                     ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
    1699                 else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
    1700                     ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
    1701                              pState->pCurrentLicense->psz);
    1702             }
    1703             else
    1704                 ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
    1705             return VINF_SUCCESS;
    1706         }
    1707     }
    1708 
    1709     /*
    1710      * Look for LGPL like text in the comment.
    1711      */
    1712     if (pState->fCheckforLgpl && cchBody > 128)
    1713     {
    1714         /* We look for typical LGPL notices. */
    1715         if (pState->iLineLgplNotice == UINT32_MAX)
    1716         {
    1717             static const char * const s_apszFragments[] =
    1718             {
    1719                 "under the terms of the GNU Lesser General Public License",
    1720             };
    1721             for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
    1722                 if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
    1723                 {
    1724                     pState->iLineLgplNotice = iLine;
    1725                     pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
    1726                     ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
    1727                     break;
    1728                 }
    1729         }
    1730 
    1731         if (   pState->iLineLgplDisclaimer == UINT32_MAX
    1732             && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
    1733         {
    1734             pState->iLineLgplDisclaimer = iLine;
    1735             ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
    1736         }
    1737     }
    1738 
    1739     /*
    1740      * Look for the license text.
    1741      */
    1742     if (pState->iLineLicense == UINT32_MAX)
    1743     {
    1744         for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
    1745         {
    1746             const char *pszNext;
    1747             if (   pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
    1748                 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
    1749             {
    1750                 while (   RT_C_IS_SPACE(*pszNext)
    1751                        || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
    1752                     pszNext++;
    1753 
    1754                 uint32_t cDashes = 0;
    1755                 while (*pszNext == '-')
    1756                     cDashes++, pszNext++;
    1757                 bool fExternal = cDashes > 10;
    1758 
    1759                 if (   *pszNext == '\0'
    1760                     || fExternal)
    1761                 {
    1762                     /* In C/C++ code, this must be a multiline comment.  While in python it
    1763                        must be a doc-string. */
    1764                     if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
    1765                         ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
    1766                     else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
    1767                         ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
    1768 
    1769                     /* Quit if we've flagged a failure. */
    1770                     if (RT_FAILURE(pState->pState->rc))
    1771                         return VERR_CALLBACK_RETURN;
    1772 
    1773                     /* Record it. */
    1774                     pState->iLineLicense        = iLine;
    1775                     pState->cLinesLicense       = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
    1776                     pState->pCurrentLicense     = pCur;
    1777                     pState->fExternalLicense    = fExternal;
    1778                     pState->fIsCorrectLicense   = pCur == pState->pExpectedLicense;
    1779                     pState->fWellFormedLicense  = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
    1780                     if (!pState->fWellFormedLicense)
    1781                         ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
    1782 
    1783                     /* If there was more than one blank line between the copyright and the
    1784                        license text, extend the license text area and force a rewrite of it. */
    1785                     if (cBlankLinesAfterCopyright > 1)
    1786                     {
    1787                         ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
    1788                                    cBlankLinesAfterCopyright);
    1789                         pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
    1790                         pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
    1791                         pState->fWellFormedLicense = false;
    1792                     }
    1793 
    1794                     /* If there was more than one blank line after the license, trigger a rewrite. */
    1795                     if (!fExternal && pInfo->cBlankLinesAfter != 1)
    1796                     {
    1797                         ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
    1798                                    pInfo->cBlankLinesAfter);
    1799                         pState->fWellFormedLicense = false;
    1800                     }
    1801 
    1802                     /** @todo Check that the last comment line doesn't have any code on it. */
    1803                     /** @todo Check that column 2 contains '*' for C/C++ files. */
    1804 
    1805                     ScmVerbose(pState->pState, 3,
    1806                                "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
    1807                                pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
    1808                                pState->fIsCorrectLicense, pState->fWellFormedLicense,
    1809                                pState->fExternalLicense, pState->fOpenSource);
    1810 
    1811                     if (fFoundCopyright)
    1812                     {
    1813                         pState->iLineComment  = pInfo->iLineStart;
    1814                         pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
    1815                                               - pInfo->iLineStart;
    1816                     }
    1817                     else
    1818                         ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
    1819                     break;
    1820                 }
    1821             }
    1822         }
    1823     }
    1824 
    1825     if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
    1826         ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
    1827 
    1828     /*
    1829      * Stop looking for stuff after 100 comments.
    1830      */
    1831     if (pState->cComments > 100)
    1832         return VERR_CALLBACK_RETURN;
    1833     return VINF_SUCCESS;
    1834 }
    1835 
    1836 /**
    1837  * Writes comment body text.
    1838  *
    1839  * @returns Stream status.
    1840  * @param   pOut                The output stream.
    1841  * @param   pszText             The text to write.
    1842  * @param   cchText             The length of the text.
    1843  * @param   enmCommentStyle     The comment style.
    1844  * @param   enmEol              The EOL style.
    1845  */
    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     do
    1853     {
    1854         const char *pszEol = strchr(pszText, '\n');
    1855         if (pszEol != pszText)
    1856         {
    1857             ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
    1858                            g_aCopyrightCommentPrefix[enmCommentStyle].cch);
    1859             ScmStreamWrite(pOut, pszText, pszEol - pszText);
    1860             ScmStreamPutEol(pOut, enmEol);
    1861         }
    1862         else
    1863             ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
    1864                              g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
    1865         pszText = pszEol + 1;
    1866     } while (*pszText != '\0');
    1867     return ScmStreamGetStatus(pOut);
    1868 }
    1869 
    1870 
    1871 /**
    1872  * Updates the copyright year and/or license text.
    1873  *
    1874  * @returns Modification state.
    1875  * @param   pState              The rewriter state.
    1876  * @param   pIn                 The input stream.
    1877  * @param   pOut                The output stream.
    1878  * @param   pSettings           The settings.
    1879  * @param   enmCommentStyle     The comment style used by the file.
    1880  */
    1881 static SCMREWRITERRES rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut,
    1882                                                  PCSCMSETTINGSBASE pSettings, SCMCOMMENTSTYLE enmCommentStyle)
    1883 {
    1884     if (   !pSettings->fUpdateCopyrightYear
    1885         && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
    1886         return kScmUnmodified;
    1887 
    1888     /*
    1889      * Try locate the relevant comments.
    1890      */
    1891     SCMCOPYRIGHTINFO Info =
    1892     {
    1893         /*.pState = */                  pState,
    1894         /*.enmCommentStyle = */         enmCommentStyle,
    1895 
    1896         /*.cComments = */               0,
    1897 
    1898         /*.pszContributedBy = */        NULL,
    1899 
    1900         /*.iLineComment = */            UINT32_MAX,
    1901         /*.cLinesComment = */           0,
    1902 
    1903         /*.iLineCopyright = */          UINT32_MAX,
    1904         /*.uFirstYear = */              UINT32_MAX,
    1905         /*.uLastYear = */               UINT32_MAX,
    1906         /*.fWellFormedCopyright = */    false,
    1907         /*.fUpToDateCopyright = */      false,
    1908 
    1909         /*.fOpenSource = */             true,
    1910         /*.pExpectedLicense = */        NULL,
    1911         /*.paLicenses = */                   pSettings->enmUpdateLicense != kScmLicense_Mit
    1912                                           && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
    1913                                         ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
    1914         /*.enmLicenceOpt = */           pSettings->enmUpdateLicense,
    1915         /*.iLineLicense = */            UINT32_MAX,
    1916         /*.cLinesLicense = */           0,
    1917         /*.pCurrentLicense = */         NULL,
    1918         /*.fIsCorrectLicense = */       false,
    1919         /*.fWellFormedLicense = */      false,
    1920         /*.fExternalLicense = */        false,
    1921 
    1922         /*.fCheckForLgpl = */           true,
    1923         /*.iLineLgplNotice = */         UINT32_MAX,
    1924         /*.iLineAfterLgplComment = */   UINT32_MAX,
    1925         /*.iLineLgplDisclaimer = */     UINT32_MAX,
    1926     };
    1927 
    1928     /* Figure Info.fOpenSource and the desired license: */
    1929     char *pszSyncProcess;
    1930     int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
    1931     if (RT_SUCCESS(rc))
    1932     {
    1933         Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
    1934         RTStrFree(pszSyncProcess);
    1935     }
    1936     else if (rc == VERR_NOT_FOUND)
    1937         Info.fOpenSource = false;
    1938     else
    1939         return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
    1940 
    1941     Info.pExpectedLicense = Info.paLicenses;
    1942     if (Info.fOpenSource)
    1943     {
    1944         if (   pSettings->enmUpdateLicense != kScmLicense_Mit
    1945             && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
    1946             while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
    1947                 Info.pExpectedLicense++;
    1948         else
    1949             Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
    1950     }
    1951     else
    1952         while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
    1953             Info.pExpectedLicense++;
    1954 
    1955     /* Scan the comments. */
    1956     rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
    1957     if (   (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
    1958         && RT_SUCCESS(pState->rc))
    1959     {
    1960         /*
    1961          * Do conformity checks.
    1962          */
    1963         bool fAddLgplDisclaimer = false;
    1964         if (Info.fCheckforLgpl)
    1965         {
    1966             if (   Info.iLineLgplNotice != UINT32_MAX
    1967                 && Info.iLineLgplDisclaimer == UINT32_MAX)
    1968             {
    1969                 if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
    1970                     ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
    1971                              Info.iLineLgplNotice + 1);
    1972                 else
    1973                 {
    1974                     ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
    1975                     fAddLgplDisclaimer = true;
    1976                 }
    1977             }
    1978             else if (   Info.iLineLgplNotice == UINT32_MAX
    1979                      && Info.iLineLgplDisclaimer != UINT32_MAX)
    1980                 ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
    1981                          Info.iLineLgplDisclaimer + 1);
    1982         }
    1983 
    1984         if (!pSettings->fExternalCopyright)
    1985         {
    1986             if (Info.iLineCopyright == UINT32_MAX)
    1987                 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
    1988             if (Info.iLineLicense == UINT32_MAX)
    1989                 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
    1990         }
    1991         else if (Info.iLineCopyright != UINT32_MAX)
    1992             ScmError(pState, VERR_NOT_FOUND,
    1993                      "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
    1994                      Info.iLineCopyright + 1);
    1995 
    1996 
    1997         if (RT_SUCCESS(pState->rc))
    1998         {
    1999             /*
    2000              * Do we need to make any changes?
    2001              */
    2002             bool fUpdateCopyright = !pSettings->fExternalCopyright
    2003                                  && (   !Info.fWellFormedCopyright
    2004                                      || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
    2005             bool fUpdateLicense   = !pSettings->fExternalCopyright
    2006                                  && Info.enmLicenceOpt != kScmLicense_LeaveAlone
    2007                                  && (   !Info.fWellFormedLicense
    2008                                      || !Info.fIsCorrectLicense);
    2009             if (   fUpdateCopyright
    2010                 || fUpdateLicense
    2011                 || fAddLgplDisclaimer)
    2012             {
    2013                 Assert(Info.iLineComment != UINT32_MAX);
    2014                 Assert(Info.cLinesComment > 0);
    2015 
    2016                 /*
    2017                  * Okay, do the work.
    2018                  */
    2019                 ScmStreamRewindForReading(pIn);
    2020 
    2021                 if (pSettings->fUpdateCopyrightYear)
    2022                     Info.uLastYear = g_uYear;
    2023 
    2024                 uint32_t    iLine = 0;
    2025                 SCMEOL      enmEol;
    2026                 size_t      cchLine;
    2027                 const char *pchLine;
    2028                 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    2029                 {
    2030                     if (   iLine == Info.iLineComment
    2031                         && (fUpdateCopyright || fUpdateLicense) )
    2032                     {
    2033                         /* Leading blank line. */
    2034                         ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
    2035                                          g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
    2036 
    2037                         /* Contributed by someone? */
    2038                         if (Info.pszContributedBy)
    2039                         {
    2040                             const char *psz = Info.pszContributedBy;
    2041                             for (;;)
    2042                             {
    2043                                 const char *pszEol = strchr(psz, '\n');
    2044                                 size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
    2045                                 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
    2046                                                g_aCopyrightCommentPrefix[enmCommentStyle].cch);
    2047                                 ScmStreamWrite(pOut, psz, cchContribLine);
    2048                                 ScmStreamPutEol(pOut, enmEol);
    2049                                 if (!pszEol)
    2050                                     break;
    2051                                 psz = pszEol + 1;
    2052                             }
    2053 
    2054                             ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
    2055                                              g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
    2056                         }
    2057 
    2058                         /* Write the copyright comment line. */
    2059                         ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
    2060                                        g_aCopyrightCommentPrefix[enmCommentStyle].cch);
    2061 
    2062                         char   szCopyright[256];
    2063                         size_t cchCopyright;
    2064                         if (Info.uFirstYear == Info.uLastYear)
    2065                             cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
    2066                                                        Info.uFirstYear, g_szCopyrightHolder);
    2067                         else
    2068                             cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
    2069                                                        Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
    2070 
    2071                         ScmStreamWrite(pOut, szCopyright, cchCopyright);
    2072                         ScmStreamPutEol(pOut, enmEol);
    2073 
    2074                         if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
    2075                         {
    2076                             /* Blank line separating the two. */
    2077                             ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
    2078                                              g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
    2079 
    2080                             /* Write the license text. */
    2081                             scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
    2082                                                 enmCommentStyle, enmEol);
    2083 
    2084                             /* Final comment line. */
    2085                             if (!Info.fExternalLicense)
    2086                                 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
    2087                                                  g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
    2088                         }
    2089                         else
    2090                             Assert(Info.fExternalLicense);
    2091 
    2092                         /* Skip the copyright and license text in the input file. */
    2093                         rc = ScmStreamGetStatus(pOut);
    2094                         if (RT_SUCCESS(rc))
    2095                         {
    2096                             iLine = Info.iLineComment + Info.cLinesComment;
    2097                             rc = ScmStreamSeekByLine(pIn, iLine);
    2098                         }
    2099                     }
    2100                     /*
    2101                      * Add LGPL disclaimer?
    2102                      */
    2103                     else if (   iLine == Info.iLineAfterLgplComment
    2104                              && fAddLgplDisclaimer)
    2105                     {
    2106                         ScmStreamPutEol(pOut, enmEol);
    2107                         ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
    2108                                          g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
    2109                         scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
    2110                                             enmCommentStyle, enmEol);
    2111                         ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
    2112                                          g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
    2113 
    2114                         /* put the actual line */
    2115                         rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2116                         iLine++;
    2117                     }
    2118                     else
    2119                     {
    2120                         rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2121                         iLine++;
    2122                     }
    2123                     if (RT_FAILURE(rc))
    2124                     {
    2125                         RTStrFree(Info.pszContributedBy);
    2126                         return kScmUnmodified;
    2127                     }
    2128                 } /* for each source line */
    2129 
    2130                 RTStrFree(Info.pszContributedBy);
    2131                 return kScmModified;
    2132             }
    2133         }
    2134     }
    2135     else
    2136         ScmError(pState, rc,  "ScmEnumerateComments: %Rrc\n", rc);
    2137     NOREF(pState); NOREF(pOut);
    2138     RTStrFree(Info.pszContributedBy);
    2139     return kScmUnmodified;
    2140 }
    2141 
    2142 
    2143 /** Copyright updater for C-style comments.   */
    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 
    221754typedef enum KMKTOKEN
    221855{
     
    2289126    char                szBuf[4096];
    2290127} KMKPARSER;
     128
     129
    2291130
    2292131static KMKTOKEN scmKmkIdentifyToken(const char *pchWord, size_t cchWord)
     
    33781217
    33791218/** @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;
    33821220    while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
    33831221    {
     
    34871325        {
    34881326            Assert(offLine + 1 == cchLine);
    3489             unsigned const iSubLineStart2 = iSubLine;
    34901327            while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
    34911328            {
     
    40991936
    41001937
    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 
    42161938/**
    4217  * Flower box marker comments in C and C++ code.
     1939 * Makefile.kup are empty files, enforce this.
    42181940 *
    4219  * @returns Modification state.
     1941 * @returns true if modifications were made, false if not.
    42201942 * @param   pIn                 The input stream.
    42211943 * @param   pOut                The output stream.
    42221944 * @param   pSettings           The settings.
    42231945 */
    4224 SCMREWRITERRES rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    4225 {
    4226     if (!pSettings->fFixFlowerBoxMarkers)
     1946SCMREWRITERRES 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)
    42271952        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");
    48391954    return kScmModified;
    48401955}
    48411956
    4842 typedef struct
    4843 {
    4844     const char     *pch;
    4845     uint8_t         cch;
    4846     uint8_t         cchSpaces;          /**< Number of expected spaces before the word. */
    4847     bool            fSpacesBefore : 1;  /**< Whether there may be spaces or tabs before the word. */
    4848     bool            fIdentifier   : 1;  /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
    4849 } SCMMATCHWORD;
    4850 
    4851 
    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                 else
    4877                     break;
    4878                 offLine++;
    4879             }
    4880 
    4881             if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
    4882             { /* likely */ }
    4883             else if (cchSpaces == 0 && cchTabs == 0)
    4884                 return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
    4885             else
    4886                 rc = VWRN_TRAILING_SPACES;
    4887         }
    4888         else
    4889             Assert(pWord->cchSpaces == 0);
    4890 
    4891         /*
    4892          * C/C++ identifier?
    4893          */
    4894         if (pWord->fIdentifier)
    4895         {
    4896             if (offLine >= cchLine)
    4897                 return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
    4898                                      "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
    4899                                      pWord->cch, pWord->pch, offLine);
    4900             if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
    4901                 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
    4902                                      pWord->cch, pWord->pch, offLine);
    4903             size_t const offStart = offLine++;
    4904             while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
    4905                 offLine++;
    4906             if (paIdentifiers)
    4907             {
    4908                 paIdentifiers->cch = offLine - offStart;
    4909                 paIdentifiers->psz = &pchLine[offStart];
    4910                 paIdentifiers++;
    4911             }
    4912         }
    4913         /*
    4914          * Match the exact word.
    4915          */
    4916         else if (   pWord->cch == 0
    4917                  || (   pWord->cch <= cchLine - offLine
    4918                      && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
    4919             offLine += pWord->cch;
    4920         else
    4921             return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
    4922     }
    4923 
    4924     /*
    4925      * Check for trailing characters/whatnot.
    4926      */
    4927     if (poffNext)
    4928         *poffNext = offLine;
    4929     else if (offLine != cchLine)
    4930         rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
    4931     return rc;
    4932 }
    4933 
    4934 
    4935 /**
    4936  * Fix header file include guards and \#pragma once.
    4937  *
    4938  * @returns Modification state.
    4939  * @param   pIn                 The input stream.
    4940  * @param   pOut                The output stream.
    4941  * @param   pSettings           The settings.
    4942  */
    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->pszFilename
    4981                    && !RTPATH_IS_SLASH(pszSrc[-1])
    4982                    && !RTPATH_IS_VOLSEP(pszSrc[-1]))
    4983                 pszSrc--;
    4984             rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);
    4985         }
    4986         else
    4987             rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
    4988                                     pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename);
    4989         if (RT_FAILURE(rc))
    4990             return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
    4991         char ch;
    4992         while ((ch = szNormalized[cchNormalized]) != '\0')
    4993         {
    4994             if (!ScmIsCIdentifierChar(ch))
    4995                 szNormalized[cchNormalized] = '_';
    4996             cchNormalized++;
    4997         }
    4998     }
    4999 
    5000     /*
    5001      * First part looks for the #ifndef xxxx paired with #define xxxx.
    5002      *
    5003      * We blindly assume the first preprocessor directive in the file is the guard
    5004      * and will be upset if this isn't the case.
    5005      */
    5006     RTSTRTUPLE  Guard       = { NULL, 0 };
    5007     uint32_t    cBlankLines = 0;
    5008     SCMEOL      enmEol;
    5009     size_t      cchLine;
    5010     const char *pchLine;
    5011     for (;;)
    5012     {
    5013         pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
    5014         if (pchLine == NULL)
    5015             return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
    5016         if (cchLine >= 2)
    5017         {
    5018             const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
    5019             if (    pchHash
    5020                 && isSpanOfBlanks(pchLine, pchHash - pchLine))
    5021             {
    5022                 /* #ifndef xxxx */
    5023                 static const SCMMATCHWORD s_aIfndefGuard[] =
    5024                 {
    5025                     { RT_STR_TUPLE("#"),                0, true, false },
    5026                     { RT_STR_TUPLE("ifndef"),           0, true, false },
    5027                     { RT_STR_TUPLE("IDENTIFIER"),       1, true, true },
    5028                     { RT_STR_TUPLE(""),                 0, true, false },
    5029                 };
    5030                 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
    5031                                    NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
    5032                 if (RT_FAILURE(rc))
    5033                     return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
    5034                                     ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
    5035                 fRet |= rc != VINF_SUCCESS;
    5036                 ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
    5037                            ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
    5038 
    5039                 /* #define xxxx */
    5040                 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
    5041                 if (!pchLine)
    5042                     return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
    5043                                     ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
    5044                 const SCMMATCHWORD aDefineGuard[] =
    5045                 {
    5046                     { RT_STR_TUPLE("#"),                0, true, false },
    5047                     { RT_STR_TUPLE("define"),           0, true, false },
    5048                     { Guard.psz, (uint8_t)Guard.cch,    1, true, false },
    5049                     { RT_STR_TUPLE(""),                 0, true, false },
    5050                 };
    5051                 rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
    5052                                    NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
    5053                 if (RT_FAILURE(rc))
    5054                     return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
    5055                                     ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
    5056                                     ErrInfo.Core.pszMsg, cchLine, pchLine);
    5057                 fRet |= rc != VINF_SUCCESS;
    5058 
    5059                 if (Guard.cch >= sizeof(szNormalized))
    5060                     return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
    5061                                     ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
    5062 
    5063                 if (szNormalized[0] != '\0')
    5064                 {
    5065                     if (   Guard.cch != cchNormalized
    5066                         || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
    5067                     {
    5068                         ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
    5069                         ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
    5070                                    Guard.cch, Guard.psz, pState->pszFilename);
    5071                         fRet = true;
    5072                     }
    5073                     Guard.psz = szNormalized;
    5074                     Guard.cch = cchNormalized;
    5075                 }
    5076 
    5077                 /*
    5078                  * Write guard, making sure we've got a single blank line preceeding it.
    5079                  */
    5080                 ScmStreamPutEol(pOut, enmEol);
    5081                 ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
    5082                 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
    5083                 ScmStreamPutEol(pOut, enmEol);
    5084                 ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
    5085                 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
    5086                 rc = ScmStreamPutEol(pOut, enmEol);
    5087                 if (RT_FAILURE(rc))
    5088                     return kScmUnmodified;
    5089                 break;
    5090             }
    5091         }
    5092 
    5093         if (!isBlankLine(pchLine, cchLine))
    5094         {
    5095             while (cBlankLines-- > 0)
    5096                 ScmStreamPutEol(pOut, enmEol);
    5097             cBlankLines = 0;
    5098             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    5099             if (RT_FAILURE(rc))
    5100                 return kScmUnmodified;
    5101         }
    5102         else
    5103             cBlankLines++;
    5104     }
    5105 
    5106     /*
    5107      * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
    5108      */
    5109     size_t const iPragmaOnce = ScmStreamTellLine(pIn);
    5110     static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
    5111     {
    5112         { RT_STR_TUPLE("#"),                        0, true, false },
    5113         { RT_STR_TUPLE("ifndef"),                   0, true, false },
    5114         { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"),   1, true, false },
    5115         { RT_STR_TUPLE(""),                         0, true, false },
    5116     };
    5117     static const SCMMATCHWORD s_aPragmaOnce[] =
    5118     {
    5119         { RT_STR_TUPLE("#"),                        0, true, false },
    5120         { RT_STR_TUPLE("pragma"),                   1, true, false },
    5121         { RT_STR_TUPLE("once"),                     1, true, false},
    5122         { RT_STR_TUPLE(""),                         0, true, false },
    5123     };
    5124     static const SCMMATCHWORD s_aEndif[] =
    5125     {
    5126         { RT_STR_TUPLE("#"),                        0, true, false },
    5127         { RT_STR_TUPLE("endif"),                    0, true, false },
    5128         { RT_STR_TUPLE(""),                         0, true, false },
    5129     };
    5130 
    5131     /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
    5132     pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
    5133     if (!pchLine)
    5134         return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
    5135     size_t offNext;
    5136     rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
    5137                        &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
    5138     if (RT_SUCCESS(rc))
    5139     {
    5140         fRet |= rc != VINF_SUCCESS;
    5141         if (offNext != cchLine)
    5142             return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
    5143                             iPragmaOnce + 1, cchLine, pchLine);
    5144 
    5145         /* # pragma once */
    5146         pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
    5147         if (!pchLine)
    5148             return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
    5149                             iPragmaOnce + 2);
    5150         rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
    5151                            NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
    5152         if (RT_SUCCESS(rc))
    5153             fRet |= rc != VINF_SUCCESS;
    5154         else
    5155             return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
    5156                             iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
    5157 
    5158         /* #endif */
    5159         pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
    5160         if (!pchLine)
    5161             return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
    5162                             iPragmaOnce + 3);
    5163         rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
    5164                            NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
    5165         if (RT_SUCCESS(rc))
    5166             fRet |= rc != VINF_SUCCESS;
    5167         else
    5168             return ScmError(pState, rc,
    5169                             "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
    5170                             iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
    5171         ScmVerbose(pState, 3, "Found pragma once\n");
    5172         fRet |= !pSettings->fPragmaOnce;
    5173     }
    5174     else
    5175     {
    5176         rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
    5177         if (RT_FAILURE(rc))
    5178             return ScmError(pState, rc, "seek error\n");
    5179         fRet |= pSettings->fPragmaOnce;
    5180         ScmVerbose(pState, pSettings->fPragmaOnce ? 2 : 3, "Missing #pragma once\n");
    5181     }
    5182 
    5183     /*
    5184      * Write the pragma once stuff.
    5185      */
    5186     if (pSettings->fPragmaOnce)
    5187     {
    5188         ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
    5189         ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
    5190         rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
    5191         if (RT_FAILURE(rc))
    5192             return kScmUnmodified;
    5193     }
    5194 
    5195     /*
    5196      * Copy the rest of the file and remove pragma once statements, while
    5197      * looking for the last #endif in the file.
    5198      */
    5199     size_t iEndIfIn = 0;
    5200     size_t iEndIfOut = 0;
    5201     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    5202     {
    5203         if (cchLine > 2)
    5204         {
    5205             const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
    5206             if (    pchHash
    5207                 && isSpanOfBlanks(pchLine, pchHash - pchLine))
    5208             {
    5209                 size_t off = pchHash - pchLine + 1;
    5210                 while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
    5211                     off++;
    5212                 /* #pragma once */
    5213                 if (   off + sizeof("pragma") - 1 <= cchLine
    5214                     && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
    5215                 {
    5216                     rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
    5217                                        &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
    5218                     if (RT_SUCCESS(rc))
    5219                     {
    5220                         fRet = true;
    5221                         continue;
    5222                     }
    5223                 }
    5224                 /* #endif */
    5225                 else if (   off + sizeof("endif") - 1 <= cchLine
    5226                          && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
    5227                 {
    5228                     iEndIfIn  = ScmStreamTellLine(pIn) - 1;
    5229                     iEndIfOut = ScmStreamTellLine(pOut);
    5230                 }
    5231             }
    5232         }
    5233 
    5234         rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    5235         if (RT_FAILURE(rc))
    5236             return kScmUnmodified;
    5237     }
    5238 
    5239     /*
    5240      * Check out the last endif, making sure it's well formed and make sure it has the
    5241      * right kind of comment following it.
    5242      */
    5243     if (pSettings->fFixHeaderGuardEndif)
    5244     {
    5245         if (iEndIfOut == 0)
    5246             return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
    5247         rc = ScmStreamSeekByLine(pIn, iEndIfIn);
    5248         if (RT_FAILURE(rc))
    5249             return kScmUnmodified;
    5250         rc = ScmStreamSeekByLine(pOut, iEndIfOut);
    5251         if (RT_FAILURE(rc))
    5252             return kScmUnmodified;
    5253 
    5254         pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
    5255         if (!pchLine)
    5256             return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
    5257 
    5258         char   szTmp[64 + sizeof(szNormalized)];
    5259         size_t cchTmp;
    5260         if (pSettings->fEndifGuardComment)
    5261             cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
    5262         else
    5263             cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
    5264         fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
    5265         rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
    5266         if (RT_FAILURE(rc))
    5267             return kScmUnmodified;
    5268 
    5269         /* Copy out the remaining lines (assumes no #pragma once here). */
    5270         while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    5271         {
    5272             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    5273             if (RT_FAILURE(rc))
    5274                 return kScmUnmodified;
    5275         }
    5276     }
    5277 
    5278     return fRet ? kScmModified : kScmUnmodified;
    5279 }
    5280 
    5281 
    5282 /**
    5283  * Checks for PAGE_SIZE, PAGE_SHIFT and PAGE_OFFSET_MASK w/o a GUEST_ or HOST_
    5284  * prefix as well as banning PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK and
    5285  * PAGE_BASE_MASK.
    5286  *
    5287  * @returns kScmUnmodified - requires manual fix.
    5288  * @param   pIn                 The input stream.
    5289  * @param   pOut                The output stream.
    5290  * @param   pSettings           The settings.
    5291  */
    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 >= cchWord
    5332                         && memcmp(pchHit, pszWord, cchWord) == 0
    5333                         && (   pchHit == pchLine
    5334                             || !ScmIsCIdentifierChar(pchHit[-1]))
    5335                         && (   cchLeft == cchWord
    5336                             || !ScmIsCIdentifierChar(pchHit[cchWord])) )
    5337                     {
    5338                         if (i < 3)
    5339                             ScmFixManually(pState, "%u:%zu: %s is not allow! Use GUEST_%s or HOST_%s instead.\n",
    5340                                            iLine, pchHit - pchLine + 1, pszWord, pszWord, pszWord);
    5341                         else if (i < 7)
    5342                             ScmFixManually(pState, "%u:%zu: %s is not allow! Rewrite using GUEST/HOST_PAGE_OFFSET_MASK.\n",
    5343                                            iLine, pchHit - pchLine + 1, pszWord);
    5344                         else
    5345                             ScmFixManually(pState, "%u:%zu: %s is not allow! Use %s with correct page size instead.\n",
    5346                                            iLine, pchHit - pchLine + 1, pszWord, i == 3 ? "ASMMemIsZero" : "RT_BZERO");
    5347                     }
    5348 
    5349                     /* next */
    5350                     cchLeft -= 1;
    5351                     if (cchLeft < cchWord)
    5352                         break;
    5353                     pchHit = (const char *)memchr(pchHit + 1, *pszWord, cchLeft);
    5354                 }
    5355             }
    5356         }
    5357     }
    5358 
    5359     return kScmUnmodified;
    5360 }
    5361 
    5362 
    5363 /**
    5364  * Checks for usage of rc in code instead of vrc for IPRT status codes (int) and hrc for COM
    5365  * status codes (HRESULT).
    5366  *
    5367  * @returns kScmUnmodified - requires manual fix.
    5368  * @param   pIn                 The input stream.
    5369  * @param   pOut                The output stream.
    5370  * @param   pSettings           The settings.
    5371  *
    5372  * @note Used in Main to avoid ambiguity when just using rc.
    5373  */
    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 >= cchWord
    5431                     && memcmp(pchHit, RcTuple.psz, cchWord) == 0
    5432                     && (   pchHit == pchLine
    5433                         || !ScmIsCIdentifierChar(pchHit[-1]))
    5434                     && (   cchLeft == cchWord
    5435                         || !ScmIsCIdentifierChar(pchHit[cchWord])) )
    5436                     ScmFixManually(pState, "%u:%zu: %s is not allowed! Use hrc or vrc instead.\n",
    5437                                    iLine, pchHit - pchLine + 1, RcTuple.psz);
    5438 
    5439                 /* next */
    5440                 cchLeft -= 1;
    5441                 if (cchLeft < cchWord)
    5442                     break;
    5443                 pchHit = (const char *)memchr(pchHit + 1, *RcTuple.psz, cchLeft);
    5444             }
    5445         }
    5446 #else
    5447         /* Trigger on declarations of 'HRESULT rc' and 'int rc'. */
    5448         static const SCMMATCHWORD s_aHresultRc[] =
    5449         {
    5450             { RT_STR_TUPLE("HRESULT"),                  0, true, false },
    5451             { RT_STR_TUPLE("rc"),                       1, true, false }
    5452         };
    5453 
    5454         static const SCMMATCHWORD s_aIntRc[] =
    5455         {
    5456             { RT_STR_TUPLE("int"),                      0, true, false },
    5457             { RT_STR_TUPLE("rc"),                       1, true, false }
    5458         };
    5459 
    5460         rc = ScmMatchWords(pchLine, cchLine, s_aHresultRc, RT_ELEMENTS(s_aHresultRc),
    5461                            &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
    5462         if (RT_SUCCESS(rc))
    5463         {
    5464             ScmFixManually(pState, "%u:%zu: 'HRESULT rc' is not allowed! Use 'HRESULT hrc' instead.\n",
    5465                            iLine, offNext);
    5466             continue;
    5467         }
    5468 
    5469         rc = ScmMatchWords(pchLine, cchLine, s_aIntRc, RT_ELEMENTS(s_aIntRc),
    5470                            &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
    5471         if (RT_SUCCESS(rc))
    5472         {
    5473             ScmFixManually(pState, "%u:%zu: 'int rc' is not allowed! Use 'int vrc' instead.\n",
    5474                            iLine, offNext);
    5475             continue;
    5476         }
    5477 #endif
    5478     }
    5479 
    5480     return kScmUnmodified;
    5481 }
    5482 
    5483 
    5484 /**
    5485  * Rewrite a C/C++ source or header file.
    5486  *
    5487  * @returns Modification state.
    5488  * @param   pIn                 The input stream.
    5489  * @param   pOut                The output stream.
    5490  * @param   pSettings           The settings.
    5491  *
    5492  * @todo
    5493  *
    5494  * Ideas for C/C++:
    5495  *      - space after if, while, for, switch
    5496  *      - spaces in for (i=0;i<x;i++)
    5497  *      - complex conditional, bird style.
    5498  *      - remove unnecessary parentheses.
    5499  *      - sort defined RT_OS_*||  and RT_ARCH
    5500  *      - sizeof without parenthesis.
    5501  *      - defined without parenthesis.
    5502  *      - trailing spaces.
    5503  *      - parameter indentation.
    5504  *      - space after comma.
    5505  *      - while (x--); -> multi line + comment.
    5506  *      - else statement;
    5507  *      - space between function and left parenthesis.
    5508  *      - TODO, XXX, @todo cleanup.
    5509  *      - Space before/after '*'.
    5510  *      - ensure new line at end of file.
    5511  *      - Indentation of precompiler statements (#ifdef, #defines).
    5512  *      - space between functions.
    5513  *      - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
    5514  */
    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  
    586586 * taking tabs into account.
    587587 */
    588 static size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings)
     588size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings)
    589589{
    590590    size_t cchRet = 0;
     
    21882188{
    21892189    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 KMKTOKEN
    2218 {
    2219     kKmkToken_Word = 0,
    2220     kKmkToken_Comment,
    2221 
    2222     /* Conditionals: */
    2223     kKmkToken_ifeq,
    2224     kKmkToken_ifneq,
    2225     kKmkToken_if1of,
    2226     kKmkToken_ifn1of,
    2227     kKmkToken_ifdef,
    2228     kKmkToken_ifndef,
    2229     kKmkToken_if,
    2230     kKmkToken_else,
    2231     kKmkToken_endif,
    2232 
    2233     /* Includes: */
    2234     kKmkToken_include,
    2235     kKmkToken_sinclude,
    2236     kKmkToken_dash_include,
    2237     kKmkToken_includedep,
    2238     kKmkToken_includedep_queue,
    2239     kKmkToken_includedep_flush,
    2240 
    2241     /* Others: */
    2242     kKmkToken_define,
    2243     kKmkToken_endef,
    2244     kKmkToken_export,
    2245     kKmkToken_unexport,
    2246     kKmkToken_local,
    2247     kKmkToken_override,
    2248     kKmkToken_undefine
    2249 } KMKTOKEN;
    2250 
    2251 typedef struct KMKPARSER
    2252 {
    2253     struct
    2254     {
    2255         KMKTOKEN        enmToken;
    2256         uint32_t        iLine;
    2257         bool            fIgnoreNesting;
    2258     } aDepth[64];
    2259     unsigned            iDepth;
    2260     unsigned            iActualDepth;
    2261     bool                fInRecipe;
    2262 
    2263     /** The current line number (for error messages and peeking).   */
    2264     uint32_t            iLine;
    2265     /** The EOL type of the current line. */
    2266     SCMEOL              enmEol;
    2267     /** The length of the current line. */
    2268     size_t              cchLine;
    2269     /** Pointer to the start of the current line. */
    2270     char const         *pchLine;
    2271 
    2272     /** @name Only used for rule/assignment parsing.
    2273      * @{ */
    2274     /** Number of continuation lines at current rule/assignment. */
    2275     uint32_t            cLines;
    2276     /** Characters in continuation lines at current rule/assignment. */
    2277     size_t              cchTotalLine;
    2278     /** @} */
    2279 
    2280     /** The SCM rewriter state. */
    2281     PSCMRWSTATE         pState;
    2282     /** The input stream. */
    2283     PSCMSTREAM          pIn;
    2284     /** The output stream. */
    2285     PSCMSTREAM          pOut;
    2286     /** The settings. */
    2287     PCSCMSETTINGSBASE   pSettings;
    2288     /** Scratch buffer. */
    2289     char                szBuf[4096];
    2290 } KMKPARSER;
    2291 
    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 == cchWord
    2331                 && *s_aTokens[i].psz == chFirst
    2332                 && memcmp(s_aTokens[i].psz, pchWord, cchWord) == 0)
    2333                 return s_aTokens[i].enmToken;
    2334     }
    2335 #ifdef VBOX_STRICT
    2336     else
    2337         for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
    2338             Assert(chFirst != *s_aTokens[i].psz);
    2339 #endif
    2340 
    2341     if (chFirst == '#')
    2342         return kKmkToken_Comment;
    2343     return kKmkToken_Word;
    2344 }
    2345 
    2346 
    2347 /**
    2348  * Gives up on the current line, copying it as it and requesting manual repair.
    2349  */
    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 on
    2387  *                          subsequent lines. Used to help balance multi-line
    2388  *                          '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 == 0
    2402             || !scmKmkIsLineWithContinuation(pchLine, cchLine)
    2403             || ScmStreamIsEndOfStream(pParser->pIn))
    2404         {
    2405             ScmStreamSeekAbsolute(pParser->pIn, offSaved);
    2406             if (pcLines)
    2407                 *pcLines = cLines;
    2408             if (pcchMaxLeadWord)
    2409                 *pcchMaxLeadWord = cchMaxLeadWord;
    2410             return (size_t)(pchLine - pParser->pchLine) + cchLine;
    2411         }
    2412 
    2413         /* Get the next line: */
    2414         pchLine = ScmStreamGetLine(pParser->pIn, &cchLine, &enmEol);
    2415         cLines++;
    2416 
    2417         /* Check the length of the first word if requested: */
    2418         if (pcchMaxLeadWord)
    2419         {
    2420             size_t offLine = 0;
    2421             while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
    2422                 offLine++;
    2423 
    2424             size_t const offStartWord = offLine;
    2425             while (offLine < cchLine && !RT_C_IS_BLANK(pchLine[offLine]))
    2426                 offLine++;
    2427 
    2428             if (offLine - offStartWord > cchMaxLeadWord)
    2429                 cchMaxLeadWord = offLine - offStartWord;
    2430         }
    2431     }
    2432 }
    2433 
    2434 
    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 ${} into
    2455  * 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 < cchLine
    2462            && (ch = pchLine[off])
    2463            && (    (ch != chStop1 && ch != chStop2)
    2464                 || iExpDepth > 0))
    2465     {
    2466         off++;
    2467         if (ch == '$')
    2468         {
    2469             ch = pchLine[off];
    2470             if (ch == '(' || ch == '{')
    2471             {
    2472                 iExpDepth++;
    2473                 off++;
    2474             }
    2475         }
    2476         else if ((ch == ')' || ch == '}') && iExpDepth > 0)
    2477             iExpDepth--;
    2478     }
    2479     return off;
    2480 }
    2481 
    2482 
    2483 /** Context for scmKmkWordLength. */
    2484 typedef enum
    2485 {
    2486     /** Target file or assignment.
    2487      *  Separators: space, '=', ':' */
    2488     kKmkWordCtx_TargetFileOrAssignment,
    2489     /** Target file.
    2490      *  Separators: space, ':' */
    2491     kKmkWordCtx_TargetFile,
    2492     /** Dependency file or (target variable) assignment.
    2493      *  Separators: space, '=', ':', '|' */
    2494     kKmkWordCtx_DepFileOrAssignment,
    2495     /** Dependency file.
    2496      *  Separators: space, '|' */
    2497     kKmkWordCtx_DepFile
    2498 } KMKWORDCTX;
    2499 
    2500 /**
    2501  * Finds the length of the word (file) @a offStart.
    2502  *
    2503  * @returns Length of word starting at @a offStart. Zero if there is whitespace
    2504  *          at given offset or it's beyond the end of the line (both cases will
    2505  *          assert).
    2506  * @param   pchLine             The line.
    2507  * @param   cchLine             The line length.
    2508  * @param   offStart            Offset to the start of the word.
    2509  */
    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 as
    2530                        simple and immediate assignments here. */
    2531                     off++;
    2532                     if (pchLine[off] == ':')
    2533                     {
    2534                         off++;
    2535                         if (pchLine[off] == '=')
    2536                         {
    2537                             if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
    2538                                 return 3;   /* ::=  - immediate assignment. */
    2539                             off++;
    2540                         }
    2541                         else if (enmCtx != kKmkWordCtx_DepFile)
    2542                             return 2;       /* ::   - double colon rule */
    2543                     }
    2544                     else if (pchLine[off] == '=')
    2545                     {
    2546                         if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
    2547                             return 2;       /* :=   - simple assignment. */
    2548                         off++;
    2549                     }
    2550                     else if (enmCtx != kKmkWordCtx_DepFile)
    2551                         return 1;           /* :    - regular rule. */
    2552                     continue;
    2553                 }
    2554                 /* ':' is a separator except in DepFile context. */
    2555                 else if (enmCtx != kKmkWordCtx_DepFile)
    2556                     return off - offStart;
    2557             }
    2558         }
    2559         else if (ch == '=')
    2560         {
    2561             /*
    2562              * Assignment.  We check for the previous character too so we'll catch
    2563              * append, prepend and conditional assignments.  Simple and immediate
    2564              * assignments are handled above.
    2565              */
    2566             if (   enmCtx == kKmkWordCtx_TargetFileOrAssignment
    2567                 || enmCtx == kKmkWordCtx_DepFileOrAssignment)
    2568             {
    2569                 if (off > offStart)
    2570                 {
    2571                     ch = pchLine[off - 1];
    2572                     if (ch == '?' || ch == '+' || ch == '>')
    2573                         off = off - 1 == offStart
    2574                             ? off + 2  /* return '+=', '?=', '<=' */
    2575                             : off - 1; /* up to '+=', '?=', '<=' */
    2576                     else
    2577                         Assert(ch != ':'); /* handled above */
    2578                 }
    2579                 else
    2580                     off++;  /* '=' */
    2581                 return off - offStart;
    2582             }
    2583         }
    2584         else if (ch == '|')
    2585         {
    2586             /*
    2587              * This is rather straight forward.
    2588              */
    2589             if (enmCtx == kKmkWordCtx_DepFileOrAssignment || enmCtx == kKmkWordCtx_DepFile)
    2590             {
    2591                 if (off == offStart)
    2592                     return 1;
    2593                 return off - offStart;
    2594             }
    2595         }
    2596         off++;
    2597     }
    2598     return off - offStart;
    2599 }
    2600 
    2601 
    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 ifn1of
    2645  *
    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->iActualDepth
    2653                                  - (fElse && pParser->iDepth > 0 && !pParser->aDepth[pParser->iDepth].fIgnoreNesting);
    2654 
    2655     /*
    2656      * Push it onto the stack.  All these nestings are relevant.
    2657      */
    2658     if (!scmKmkPushNesting(pParser, enmToken))
    2659         return false;
    2660 
    2661     /*
    2662      * We do not allow line continuation for these.
    2663      */
    2664     if (scmKmkIsLineWithContinuation(pchLine, cchLine))
    2665         return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
    2666 
    2667     /*
    2668      * We stage the modified line in the buffer, so check that the line isn't
    2669      * too long (it seriously should be).
    2670      */
    2671     if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
    2672         return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", cchToken, &pchLine[offToken], cchLine);
    2673     char *pszDst = pParser->szBuf;
    2674 
    2675     /*
    2676      * Emit indent and initial token.
    2677      */
    2678     memset(pszDst, ' ', cchIndent);
    2679     pszDst += cchIndent;
    2680 
    2681     if (fElse)
    2682         pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
    2683 
    2684     memcpy(pszDst, &pchLine[offToken], cchToken);
    2685     pszDst += cchToken;
    2686 
    2687     size_t offSrc = offToken + cchToken;
    2688 
    2689     /*
    2690      * There shall be exactly one space between the token and the opening parenthesis.
    2691      */
    2692     if (pchLine[offSrc] == ' ' && pchLine[offSrc + 1] == '(')
    2693         offSrc += 2;
    2694     else
    2695     {
    2696         while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
    2697             offSrc++;
    2698         if (pchLine[offSrc] != '(')
    2699             return scmKmkGiveUp(pParser, "Expected '(' to follow '%.*s'", cchToken, &pchLine[offToken]);
    2700         offSrc++;
    2701     }
    2702     *pszDst++ = ' ';
    2703     *pszDst++ = '(';
    2704 
    2705     /*
    2706      * Skip spaces after the opening parenthesis.
    2707      */
    2708     while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
    2709         offSrc++;
    2710 
    2711     /*
    2712      * Work up to the ',' separator.  It shall likewise not be preceeded by any spaces.
    2713      * Need to take $(func 1,2,3) calls into account here, so we trac () and {} while
    2714      * skipping ahead.
    2715      */
    2716     if (pchLine[offSrc] != ',')
    2717     {
    2718         size_t const offSrcStart = offSrc;
    2719         offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ',');
    2720         if (pchLine[offSrc] != ',')
    2721             return scmKmkGiveUp(pParser, "Expected ',' somewhere after '%.*s('", cchToken, &pchLine[offToken]);
    2722 
    2723         size_t cchCopy = offSrc - offSrcStart;
    2724         while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
    2725             cchCopy--;
    2726 
    2727         pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
    2728     }
    2729     /* 'if1of(, stuff)' does not make sense in committed code: */
    2730     else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
    2731         return scmKmkGiveUp(pParser, "Left set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
    2732     offSrc++;
    2733     *pszDst++ = ',';
    2734 
    2735     /*
    2736      * For if1of and ifn1of we require a space after the comma, whereas ifeq and
    2737      * ifneq shall not have any blanks.  This is to help tell them apart.
    2738      */
    2739     if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
    2740     {
    2741         *pszDst++ = ' ';
    2742         if (pchLine[offSrc] == ' ')
    2743             offSrc++;
    2744     }
    2745     while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
    2746         offSrc++;
    2747 
    2748     if (pchLine[offSrc] != ')')
    2749     {
    2750         size_t const offSrcStart = offSrc;
    2751         offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ')');
    2752         if (pchLine[offSrc] != ')')
    2753             return scmKmkGiveUp(pParser, "No closing parenthesis for '%.*s'?", cchToken, &pchLine[offToken]);
    2754 
    2755         size_t cchCopy = offSrc - offSrcStart;
    2756         while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
    2757             cchCopy--;
    2758 
    2759         pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
    2760     }
    2761     /* 'if1of(stuff, )' does not make sense in committed code: */
    2762     else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
    2763         return scmKmkGiveUp(pParser, "Right set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
    2764     offSrc++;
    2765     *pszDst++ = ')';
    2766 
    2767     /*
    2768      * Handle comment.
    2769      */
    2770     if (offSrc < cchLine)
    2771         scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
    2772 
    2773     /*
    2774      * Done.
    2775      */
    2776     *pszDst = '\0';
    2777     ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
    2778     return false; /* dummy */
    2779 }
    2780 
    2781 
    2782 /**
    2783  * Deals with: if, ifdef and ifndef
    2784  *
    2785  * @returns dummy (false) to facility return + call.
    2786  */
    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->iActualDepth
    2792                               - (fElse && pParser->iDepth > 0 && !pParser->aDepth[pParser->iDepth].fIgnoreNesting);
    2793 
    2794     /*
    2795      * Push it onto the stack.
    2796      *
    2797      * For ifndef we ignore the outmost ifndef in non-Makefile.kmk files, if
    2798      * the define matches the typical pattern for a file blocker.
    2799      */
    2800     if (!fElse)
    2801     {
    2802         if (!scmKmkPushNesting(pParser, enmToken))
    2803             return false;
    2804     }
    2805     else
    2806     {
    2807         pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
    2808         pParser->aDepth[pParser->iDepth - 1].iLine    = pParser->iLine;
    2809     }
    2810     bool fIgnoredNesting = false;
    2811     if (enmToken == kKmkToken_ifndef)
    2812     {
    2813         /** @todo */
    2814     }
    2815 
    2816     /*
    2817      * We do not allow line continuation for these.
    2818      */
    2819     uint32_t cLines         = 1;
    2820     size_t   cchMaxLeadWord = 0;
    2821     size_t   cchTotalLine   = cchLine;
    2822     if (scmKmkIsLineWithContinuation(pchLine, cchLine))
    2823     {
    2824         if (enmToken != kKmkToken_if)
    2825             return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
    2826         cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord);
    2827     }
    2828 
    2829     /*
    2830      * We stage the modified line in the buffer, so check that the line isn't
    2831      * too long (plain if can be long, but not ifndef/ifdef).
    2832      */
    2833     if (cchTotalLine + pParser->iActualDepth + 32 > sizeof(pParser->szBuf))
    2834         return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars",
    2835                             cchToken, &pchLine[offToken], cchTotalLine);
    2836     char *pszDst = pParser->szBuf;
    2837 
    2838     /*
    2839      * Emit indent and initial token.
    2840      */
    2841     memset(pszDst, ' ', cchIndent);
    2842     pszDst += cchIndent;
    2843 
    2844     if (fElse)
    2845         pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
    2846 
    2847     memcpy(pszDst, &pchLine[offToken], cchToken);
    2848     pszDst += cchToken;
    2849 
    2850     size_t offSrc = offToken + cchToken;
    2851 
    2852     /*
    2853      * ifndef/ifdef shall have exactly one space.  For 'if' we allow up to 4, but
    2854      * we'll deal with that further down.
    2855      */
    2856     size_t cchSpaces = 0;
    2857     while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
    2858     {
    2859         cchSpaces++;
    2860         offSrc++;
    2861     }
    2862     if (cchSpaces == 0)
    2863         return scmKmkGiveUp(pParser, "Nothing following '%.*s' or bogus line continuation?", cchToken, &pchLine[offToken]);
    2864     *pszDst++ = ' ';
    2865 
    2866     /*
    2867      * For ifdef and ifndef there now comes a single word.
    2868      */
    2869     if (enmToken != kKmkToken_if)
    2870     {
    2871         size_t const offSrcStart = offSrc;
    2872         offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ' ', '\t'); /** @todo probably not entirely correct */
    2873         if (offSrc == offSrcStart)
    2874             return scmKmkGiveUp(pParser, "No word following '%.*s'?", cchToken, &pchLine[offToken]);
    2875 
    2876         pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], offSrc - offSrcStart);
    2877     }
    2878     /*
    2879      * While for 'if' things are more complicated, especially if it spans more
    2880      * than one line.
    2881      */
    2882     else if (cLines <= 1)
    2883     {
    2884         /* Single line expression: Just assume the expression goes up to the
    2885            EOL or comment hash. Strip and copy as-is for now. */
    2886         const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
    2887         size_t      cchExpr    = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
    2888         while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
    2889             cchExpr--;
    2890 
    2891         pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
    2892         offSrc += cchExpr;
    2893     }
    2894     else
    2895     {
    2896         /* Multi line expression: We normalize leading whitespace using
    2897            cchMaxLeadWord for now.  Expression on line 2+ are indented by two
    2898            extra characters, because we'd otherwise be puttin the operator on
    2899            the same level as the 'if', which would be confusing.  Thus:
    2900 
    2901                 if  expr1
    2902                   + expr2
    2903                 endif
    2904 
    2905                 if   expr1
    2906                   || expr2
    2907                 endif
    2908 
    2909                 if    expr3
    2910                   vtg expr4
    2911                 endif
    2912 
    2913            We do '#' / EOL handling for the final line the same way as above.
    2914 
    2915            Later we should add the ability to rework the expression properly,
    2916            making sure new lines starts with operators and such. */
    2917         /** @todo Implement simples expression parser and indenter, possibly also
    2918          *        removing unnecessary parentheses.  Can be shared with C/C++. */
    2919         if (cchMaxLeadWord > 3)
    2920             return scmKmkGiveUp(pParser,
    2921                                 "Bogus multi-line 'if' expression! Extra lines must start with operator (cchMaxLeadWord=%u).",
    2922                                 cchMaxLeadWord);
    2923         memset(pszDst, ' ', cchMaxLeadWord);
    2924         pszDst += cchMaxLeadWord;
    2925 
    2926         size_t cchSrcContIndent = offToken + 2;
    2927         for (uint32_t iSubLine = 0; iSubLine < cLines - 1; iSubLine++)
    2928         {
    2929             /* Trim the line. */
    2930             size_t offSrcEnd = cchLine;
    2931             Assert(pchLine[offSrcEnd - 1] == '\\');
    2932             offSrcEnd--;
    2933 
    2934             if (pchLine[offSrcEnd - 1] == '\\')
    2935                 return scmKmkGiveUp(pParser, "Escaped '\\' before line continuation in 'if' expression is not allowed!");
    2936 
    2937             while (offSrcEnd > offSrc && RT_C_IS_BLANK(pchLine[offSrcEnd - 1]))
    2938                 offSrcEnd--;
    2939 
    2940             /* Comments with line continuation is not allowed in commited makefiles. */
    2941             if (offSrc < offSrcEnd && memchr(&pchLine[offSrc], '#', cchLine - offSrc) != NULL)
    2942                 return scmKmkGiveUp(pParser, "Comment in multi-line 'if' expression is not allowed to start before the final line!");
    2943 
    2944             /* Output it. */
    2945             if (offSrc < offSrcEnd)
    2946             {
    2947                 if (iSubLine > 0 && offSrc > cchSrcContIndent)
    2948                 {
    2949                     memset(pszDst, ' ', offSrc - cchSrcContIndent);
    2950                     pszDst += offSrc - cchSrcContIndent;
    2951                 }
    2952                 pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], offSrcEnd - offSrc);
    2953                 *pszDst++ = ' ';
    2954             }
    2955             else if (iSubLine == 0)
    2956                 return scmKmkGiveUp(pParser, "Expected expression after 'if', not line continuation!");
    2957             *pszDst++ = '\\';
    2958             *pszDst   = '\0';
    2959             size_t cchDst = (size_t)(pszDst - pParser->szBuf);
    2960             ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
    2961 
    2962             /*
    2963              * Fetch the next line and start processing it.
    2964              */
    2965             pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
    2966             if (!pchLine)
    2967             {
    2968                 ScmError(pParser->pState, VERR_INTERNAL_ERROR_3, "ScmStreamGetLine unexpectedly returned NULL!");
    2969                 return false;
    2970             }
    2971             cchLine = pParser->cchLine;
    2972             pParser->iLine++;
    2973 
    2974             /* Skip leading whitespace and adjust the source continuation indent: */
    2975             offSrc = 0;
    2976             while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
    2977                 offSrc++;
    2978             /** @todo tabs */
    2979 
    2980             if (iSubLine == 0)
    2981                 cchSrcContIndent = offSrc;
    2982 
    2983             /* Initial indent: */
    2984             pszDst = pParser->szBuf;
    2985             memset(pszDst, ' ', cchIndent + 2);
    2986             pszDst += cchIndent + 2;
    2987         }
    2988 
    2989         /* Output the expression on the final line. */
    2990         const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
    2991         size_t      cchExpr    = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
    2992         while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
    2993             cchExpr--;
    2994 
    2995         pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
    2996         offSrc += cchExpr;
    2997     }
    2998 
    2999 
    3000     /*
    3001      * Handle comment.
    3002      *
    3003      * Here we check for the "scm:ignore-nesting" directive that makes us not
    3004      * add indentation for this directive.  We do this on the destination buffer
    3005      * as that can be zero terminated and is therefore usable with strstr.
    3006      */
    3007     if (offSrc >= cchLine)
    3008         *pszDst = '\0';
    3009     else
    3010     {
    3011         char * const pszDstSrc = pszDst;
    3012         scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
    3013         *pszDst = '\0';
    3014 
    3015         /* Check for special comment making us ignore the nesting. We do this in the
    3016 
    3017            */
    3018         if (!fIgnoredNesting && strstr(pszDstSrc, "scm:ignore-nesting") != NULL)
    3019         {
    3020             pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting = true;
    3021             pParser->iActualDepth--;
    3022         }
    3023     }
    3024 
    3025     /*
    3026      * Done.
    3027      */
    3028     ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
    3029     return false; /* dummy */
    3030 }
    3031 
    3032 
    3033 /**
    3034  * Deals with: else
    3035  *
    3036  * @returns dummy (false) to facility return + call.
    3037  */
    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't
    3088      * too long (it seriously should be).
    3089      */
    3090     if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
    3091         return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
    3092     char *pszDst = pParser->szBuf;
    3093 
    3094     /*
    3095      * Emit indent and initial token.
    3096      */
    3097     memset(pszDst, ' ', cchIndent);
    3098     pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("else"));
    3099 
    3100     offSrc = offToken + 4;
    3101 
    3102     /*
    3103      * Handle comment.
    3104      */
    3105     if (offSrc < cchLine)
    3106         scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
    3107 
    3108     /*
    3109      * Done.
    3110      */
    3111     *pszDst = '\0';
    3112     ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
    3113     return false; /* dummy */
    3114 }
    3115 
    3116 
    3117 /**
    3118  * Deals with: endif
    3119  *
    3120  * @returns dummy (false) to facility return + call.
    3121  */
    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't
    3149      * too long (it seriously should be).
    3150      */
    3151     if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
    3152         return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
    3153     char *pszDst = pParser->szBuf;
    3154 
    3155     /*
    3156      * Emit indent and initial token.
    3157      */
    3158     memset(pszDst, ' ', cchIndent);
    3159     pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("endif"));
    3160 
    3161     size_t offSrc = offToken + 5;
    3162 
    3163     /*
    3164      * Handle comment.
    3165      */
    3166     if (offSrc < cchLine)
    3167         scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
    3168 
    3169     /*
    3170      * Done.
    3171      */
    3172     *pszDst = '\0';
    3173     ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
    3174     return false; /* dummy */
    3175 }
    3176 
    3177 
    3178 /**
    3179  * Passing thru any line continuation lines following the current one.
    3180  */
    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 KMKASSIGNTYPE
    3239 {
    3240     kKmkAssignType_Recursive,
    3241     kKmkAssignType_Conditional,
    3242     kKmkAssignType_Appending,
    3243     kKmkAssignType_Prepending,
    3244     kKmkAssignType_Simple,
    3245     kKmkAssignType_Immediate
    3246 } KMKASSIGNTYPE;
    3247 
    3248 
    3249 /**
    3250  * @returns dummy (false) to facility return + call.
    3251  */
    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 a
    3287      * space in front of it.
    3288      */
    3289     if (offAssignOp < cchLine)
    3290     {
    3291         size_t offDst         = (size_t)(pszDst - pParser->szBuf);
    3292         size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings);
    3293         if (offDst < offEffAssignOp)
    3294         {
    3295             size_t cchSpacesToWrite = offEffAssignOp - offDst;
    3296             memset(pszDst, ' ', cchSpacesToWrite);
    3297             pszDst += cchSpacesToWrite;
    3298         }
    3299         else
    3300             *pszDst++ = ' ';
    3301     }
    3302     else
    3303     {
    3304         /* Pull up the assignment operator to the variable line. */
    3305         *pszDst++ = ' ';
    3306 
    3307         /* Eat up lines till we hit the operator. */
    3308         while (offAssignOp < cchLine)
    3309         {
    3310             const char * const pchPrevLine = pchLine;
    3311             Assert(iSubLine + 1 < cLines);
    3312             pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
    3313             AssertReturn(pchLine, false /*dummy*/);
    3314             cchLine = pParser->cchLine;
    3315             iSubLine++;
    3316             if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
    3317                 return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
    3318 
    3319             /* Adjust offAssignOp: */
    3320             offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
    3321             Assert(offAssignOp < ~(size_t)0 / 2);
    3322         }
    3323 
    3324         if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf))
    3325             return scmKmkGiveUp(pParser, "Line too long!");
    3326     }
    3327 
    3328     /*
    3329      * Emit the operator.
    3330      */
    3331     size_t offLine = offAssignOp;
    3332     switch (enmType)
    3333     {
    3334         default:
    3335             AssertReleaseFailed();
    3336             RT_FALL_THRU();
    3337         case kKmkAssignType_Recursive:
    3338             *pszDst++ = '=';
    3339             Assert(pchLine[offLine] == '=');
    3340             offLine++;
    3341             break;
    3342         case kKmkAssignType_Conditional:
    3343             *pszDst++ = '?';
    3344             *pszDst++ = '=';
    3345             Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '=');
    3346             offLine += 2;
    3347             break;
    3348         case kKmkAssignType_Appending:
    3349             *pszDst++ = '+';
    3350             *pszDst++ = '=';
    3351             Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '=');
    3352             offLine += 2;
    3353             break;
    3354         case kKmkAssignType_Prepending:
    3355             *pszDst++ = '>';
    3356             *pszDst++ = '=';
    3357             Assert(pchLine[offLine] == '>'); Assert(pchLine[offLine + 1] == '=');
    3358             offLine += 2;
    3359             break;
    3360         case kKmkAssignType_Immediate:
    3361             *pszDst++ = ':';
    3362             Assert(pchLine[offLine] == ':');
    3363             offLine++;
    3364             RT_FALL_THRU();
    3365         case kKmkAssignType_Simple:
    3366             *pszDst++ = ':';
    3367             *pszDst++ = '=';
    3368             Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '=');
    3369             offLine += 2;
    3370             break;
    3371     }
    3372 
    3373     /*
    3374      * Skip space till we hit the value or comment.
    3375      */
    3376     while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
    3377         offLine++;
    3378 
    3379 /** @todo this block can probably be merged into the final loop below. */
    3380     unsigned       cPendingEols    = 0;
    3381     unsigned const iSubLineStart1 = iSubLine;
    3382     while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
    3383     {
    3384         pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
    3385         AssertReturn(pchLine, false /*dummy*/);
    3386         cchLine = pParser->cchLine;
    3387         iSubLine++;
    3388         if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
    3389         {
    3390             *pszDst++ = ' ';
    3391             *pszDst++ = '\\';
    3392             *pszDst   = '\0';
    3393             ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
    3394             return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
    3395         }
    3396         cPendingEols = 1;
    3397 
    3398         /* Skip indent/whitespace. */
    3399         offLine = 0;
    3400         while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
    3401             offLine++;
    3402     }
    3403 
    3404     /*
    3405      * Okay, we've gotten to the value / comment part.
    3406      */
    3407     for (;;)
    3408     {
    3409         /*
    3410          * The end? Flush what we've got.
    3411          */
    3412         if (offLine == cchLine)
    3413         {
    3414             Assert(iSubLine + 1 == cLines);
    3415             *pszDst = '\0';
    3416             ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
    3417             return false; /* dummy */
    3418         }
    3419 
    3420         /*
    3421          * Output any non-comment stuff, stripping off newlines.
    3422          */
    3423         const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
    3424         if (pchHash != &pchLine[offLine])
    3425         {
    3426             /* Add space or flush pending EOLs. */
    3427             if (!cPendingEols)
    3428                 *pszDst++ = ' ';
    3429             else
    3430             {
    3431                 cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
    3432                 do
    3433                 {
    3434                     *pszDst++ = ' ';
    3435                     *pszDst++ = '\\';
    3436                     *pszDst = '\0';
    3437                     ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
    3438 
    3439                     pszDst = pParser->szBuf;
    3440                     memset(pszDst, ' ', cchIndent);
    3441                     pszDst += cchIndent;
    3442                     *pszDst++ = '\t';
    3443                     cPendingEols--;
    3444                 } while (cPendingEols > 0);
    3445             }
    3446 
    3447             /* Strip backwards. */
    3448             size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
    3449             size_t       offValueEnd  = offValueEnd2;
    3450             while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
    3451                 offValueEnd--;
    3452             Assert(offValueEnd > offLine);
    3453 
    3454             /* Append the value part we found. */
    3455             pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
    3456             offLine = offValueEnd2;
    3457         }
    3458 
    3459         /*
    3460          * If we found a comment hash, emit it and whatever follows just as-is w/o
    3461          * any particular reformatting.  Comments within a variable definition are
    3462          * usually to disable portitions of a property like _DEFS or _SOURCES.
    3463          */
    3464         if (pchHash != NULL)
    3465         {
    3466             if (cPendingEols == 0)
    3467                 scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst);
    3468             size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
    3469             *pszDst = '\0';
    3470             ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
    3471 
    3472             if (cPendingEols > 1)
    3473                 ScmStreamPutEol(pParser->pOut, pParser->enmEol);
    3474 
    3475             if (cPendingEols > 0)
    3476                 ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
    3477             scmKmkPassThruLineContinuationLines(pParser);
    3478             return false; /* dummy */
    3479         }
    3480 
    3481         /*
    3482          * Fetch another line, if we've got one.
    3483          */
    3484         if (iSubLine + 1 >= cLines)
    3485             Assert(offLine == cchLine);
    3486         else
    3487         {
    3488             Assert(offLine + 1 == cchLine);
    3489             unsigned const iSubLineStart2 = iSubLine;
    3490             while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
    3491             {
    3492                 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
    3493                 AssertReturn(pchLine, false /*dummy*/);
    3494                 cchLine = pParser->cchLine;
    3495                 iSubLine++;
    3496                 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
    3497                 {
    3498                     *pszDst++ = ' ';
    3499                     *pszDst++ = '\\';
    3500                     *pszDst   = '\0';
    3501                     ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
    3502                     if (cPendingEols > 1)
    3503                         ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
    3504                     return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
    3505                 }
    3506                 cPendingEols++;
    3507 
    3508                 /* Deal with indent/whitespace. */
    3509                 offLine = 0;
    3510                 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
    3511                     offLine++;
    3512             }
    3513         }
    3514     }
    3515 }
    3516 
    3517 
    3518 /**
    3519  * A rule.
    3520  *
    3521  * This is a bit involved. Sigh.
    3522  *
    3523  * @returns dummy (false) to facility return + call.
    3524  */
    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         else
    3635             ScmStreamPutCh(pOut, ' ');
    3636         enmCtx = kKmkWordCtx_TargetFile;
    3637     }
    3638 
    3639     /*
    3640      * We're immediately past the colon now, so eat whitespace and newlines and
    3641      * whatever till we get to a solid word.
    3642      */
    3643     /* Skip spaces - there should be exactly one. */
    3644     while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
    3645         offLine++;
    3646 
    3647     /* Deal with new lines: */
    3648     while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
    3649     {
    3650         fPendingEol = true;
    3651 
    3652         Assert(iSubLine + 1 < cLines);
    3653         pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
    3654         AssertReturn(pchLine, false /*dummy*/);
    3655         cchLine = pParser->cchLine;
    3656         iSubLine++;
    3657         if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
    3658             return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
    3659 
    3660          /* Skip leading spaces. */
    3661          offLine = 0;
    3662          while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
    3663              offLine++;
    3664 
    3665          /* Just drop empty lines. */
    3666          if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
    3667              continue;
    3668     }
    3669 
    3670     /*
    3671      * Special case: No dependencies.
    3672      */
    3673     if (offLine == cchLine && iSubLine >= cLines)
    3674     {
    3675         ScmStreamPutEol(pOut, pParser->enmEol);
    3676         return false /*dummy*/;
    3677     }
    3678 
    3679     /*
    3680      * Work the dependencies word for word.  Indent in spaces + two tabs.
    3681      * (Pattern rules will also end up here, but we'll just ignore that for now.)
    3682      */
    3683     enmCtx = kKmkWordCtx_DepFileOrAssignment;
    3684     for (;;)
    3685     {
    3686         /* Indent the next word. */
    3687         if (!fPendingEol)
    3688             ScmStreamPutCh(pOut, ' ');
    3689         else
    3690         {
    3691             ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
    3692             ScmStreamPutEol(pOut, pParser->enmEol);
    3693             ScmStreamWrite(pOut, g_szSpaces, cchIndent);
    3694             ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
    3695             fPendingEol = false;
    3696         }
    3697 
    3698         /* Get the next word and output it. */
    3699         size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx);
    3700         Assert(offLine + cchWord <= cchLine);
    3701 
    3702         ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
    3703         offLine += cchWord;
    3704 
    3705         /* Skip whitespace (if any). */
    3706         size_t cchSpaces = 0;
    3707         while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
    3708         {
    3709             cchSpaces++;
    3710             offLine++;
    3711         }
    3712 
    3713         /* Deal with new line and emit indentation. */
    3714         if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
    3715         {
    3716             /* Get the next input line. */
    3717             unsigned cEmptyLines = 0;
    3718             for (;;)
    3719             {
    3720                 Assert(iSubLine + 1 < cLines);
    3721                 pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
    3722                 AssertReturn(pchLine, false /*dummy*/);
    3723                 cchLine = pParser->cchLine;
    3724                 iSubLine++;
    3725                 if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
    3726                     return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
    3727 
    3728                 /* Skip leading spaces. */
    3729                 offLine = 0;
    3730                 while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
    3731                     offLine++;
    3732 
    3733                 /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
    3734                 if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
    3735                 {
    3736                     cEmptyLines++;
    3737                     continue;
    3738                 }
    3739 
    3740                 fPendingEol = true;
    3741                 break;
    3742             }
    3743         }
    3744 
    3745         if (offLine >= cchLine)
    3746         {
    3747             /* End of input. */
    3748 /** @todo deal with comments */
    3749             Assert(iSubLine + 1 == cLines);
    3750             ScmStreamPutEol(pOut, pParser->enmEol);
    3751             return false; /* dummmy */
    3752         }
    3753         enmCtx = kKmkWordCtx_DepFile;
    3754     }
    3755 }
    3756 
    3757 
    3758 /**
    3759  * Checks if the (extended) line is a variable assignment.
    3760  *
    3761  * We scan past line continuation stuff here as the assignment operator could be
    3762  * on the next line, even if that's very unlikely it is recommened by the coding
    3763  * guide lines if the line needs to be split.  Fortunately, though, the caller
    3764  * already removes empty empty leading lines, so we only have to consider the
    3765  * line continuation issue if no '=' was found on the first line.
    3766  *
    3767  * @returns Modified or not.
    3768  * @param   pParser         The parser.
    3769  * @param   cLines          Number of lines to consider.
    3770  * @param   cchTotalLine    Total length of all the lines to consider.
    3771  * @param   offWord         Where the first word of the line starts.
    3772  * @param   pfIsAssignment  Where to return whether this is an assignment or
    3773  *                          not.
    3774  */
    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                 else
    3827                     break;
    3828             }
    3829             offCurWord = offLine;
    3830             iWord++;
    3831 
    3832             /*
    3833              * To simplify the assignment operator checks, we just check the
    3834              * start of the 2nd word when we're here.
    3835              */
    3836             if (iWord == 1 && offLine < cchTotalLine)
    3837             {
    3838                 ch = pchLine[offLine];
    3839                 if (ch == '=')
    3840                     return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
    3841                 if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
    3842                 {
    3843                     if (ch == ':')
    3844                         return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple,      offLine, 0);
    3845                     if (ch == '+')
    3846                         return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending,   offLine, 0);
    3847                     if (ch == '>')
    3848                         return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending,  offLine, 0);
    3849                     if (ch == '?')
    3850                         return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
    3851                 }
    3852                 else if (   ch                   == ':'
    3853                          && pchLine[offLine + 1] == ':'
    3854                          && pchLine[offLine + 2] == '=')
    3855                     return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
    3856 
    3857                 /* Check for rule while we're here. */
    3858                 if (ch == ':')
    3859                     return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
    3860             }
    3861         }
    3862         /*
    3863          * If '=' is found in the first word it's an assignment.
    3864          */
    3865         else if (ch == '=')
    3866         {
    3867             if (iWord == 0)
    3868             {
    3869                 KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
    3870                 ch = pchLine[offLine - 2];
    3871                 if (ch == '+')
    3872                     enmType = kKmkAssignType_Appending;
    3873                 else if (ch == '?')
    3874                     enmType = kKmkAssignType_Conditional;
    3875                 else if (ch == '>')
    3876                     enmType = kKmkAssignType_Prepending;
    3877                 else
    3878                     Assert(ch != ':');
    3879                 return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
    3880             }
    3881         }
    3882         /*
    3883          * When ':' is found it can mean a drive letter, a rule or in the
    3884          * first word a simple or immediate assignment.
    3885          */
    3886         else if (ch == ':')
    3887         {
    3888             /* Check for drive letters (we ignore the archive form): */
    3889             if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
    3890             {  /* ignore */ }
    3891             else
    3892             {
    3893                 /* Simple or immediate assignment? */
    3894                 ch = pchLine[offLine];
    3895                 if (iWord == 0)
    3896                 {
    3897                     if (ch == '=')
    3898                         return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
    3899                     if (ch == ':' && pchLine[offLine + 1] == '=')
    3900                         return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
    3901                 }
    3902 
    3903                 /* Okay, it's a rule then. */
    3904                 return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
    3905             }
    3906         }
    3907     }
    3908 
    3909     /*
    3910      * If we didn't find anything, output it as-as.
    3911      * We use scmKmkHandleSimple in a special way to do this.
    3912      */
    3913     ScmVerbose(pParser->pState, 1, "debug: %u: Unable to make sense of this line!", pParser->iLine);
    3914     return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
    3915 }
    3916 
    3917 
    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  * @todo
    3938  *
    3939  * Ideas for Makefile.kmk and Config.kmk:
    3940  *      - sort if1of/ifn1of sets.
    3941  *      - line continuation slashes should only be preceded by one space.
    3942  */
    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 a
    3972          * tab is considered another command for the recipe.
    3973          */
    3974         if (Parser.fInRecipe && *pchLine == '\t')
    3975         {
    3976             /* Do we do anything here? */
    3977         }
    3978         else
    3979         {
    3980             /*
    3981              * Skip leading whitespace and check for directives (simplified).
    3982              *
    3983              * This is simplified in the sense that GNU make first checks for variable
    3984              * assignments, so that directive can be used as variable names.  We don't
    3985              * want that, so we do the variable assignment check later.
    3986              */
    3987             size_t offLine = 0;
    3988             while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
    3989                 offLine++;
    3990 
    3991             /* Find end of word (if any): */
    3992             size_t cchWord = 0;
    3993             while (   offLine + cchWord < cchLine
    3994                    && (   RT_C_IS_ALNUM(pchLine[offLine + cchWord])
    3995                        || pchLine[offLine + cchWord] == '-'))
    3996                 cchWord++;
    3997             if (cchWord > 0)
    3998             {
    3999                 /* If the line is just a line continuation slash, simply remove it
    4000                    (this also makes the parsing a lot easier). */
    4001                 if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
    4002                     continue;
    4003 
    4004                 /* Unlike the GNU make parser, we won't recognize 'if' or any other
    4005                    directives as variable names, so we can  */
    4006                 KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
    4007                 switch (enmToken)
    4008                 {
    4009                     case kKmkToken_ifeq:
    4010                     case kKmkToken_ifneq:
    4011                     case kKmkToken_if1of:
    4012                     case kKmkToken_ifn1of:
    4013                         scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
    4014                         continue;
    4015 
    4016                     case kKmkToken_ifdef:
    4017                     case kKmkToken_ifndef:
    4018                     case kKmkToken_if:
    4019                         scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
    4020                         continue;
    4021 
    4022                     case kKmkToken_else:
    4023                         scmKmkHandleElse(&Parser, offLine);
    4024                         continue;
    4025 
    4026                     case kKmkToken_endif:
    4027                         scmKmkHandleEndif(&Parser, offLine);
    4028                         continue;
    4029 
    4030                     /* Includes: */
    4031                     case kKmkToken_include:
    4032                     case kKmkToken_sinclude:
    4033                     case kKmkToken_dash_include:
    4034                     case kKmkToken_includedep:
    4035                     case kKmkToken_includedep_queue:
    4036                     case kKmkToken_includedep_flush:
    4037                         scmKmkHandleSimple(&Parser, offLine);
    4038                         continue;
    4039 
    4040                     /* Others: */
    4041                     case kKmkToken_define:
    4042                         scmKmkHandleDefine(&Parser, offLine);
    4043                         continue;
    4044                     case kKmkToken_endef:
    4045                         scmKmkHandleEndef(&Parser, offLine);
    4046                         continue;
    4047 
    4048                     case kKmkToken_override:
    4049                     case kKmkToken_local:
    4050                         scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
    4051                         continue;
    4052 
    4053                     case kKmkToken_export:
    4054                         scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
    4055                         continue;
    4056 
    4057                     case kKmkToken_unexport:
    4058                     case kKmkToken_undefine:
    4059                         scmKmkHandleSimple(&Parser, offLine);
    4060                         break;
    4061 
    4062                     case kKmkToken_Comment:
    4063                         break;
    4064 
    4065                     /*
    4066                      * Check if it's perhaps an variable assignment or start of a rule.
    4067                      * We'll do this in a very simple fashion.
    4068                      */
    4069                     case kKmkToken_Word:
    4070                     {
    4071                         Parser.cLines       = 1;
    4072                         Parser.cchTotalLine = cchLine;
    4073                         if (scmKmkIsLineWithContinuation(pchLine, cchLine))
    4074                             Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
    4075                         scmKmkHandleAssignmentOrRule(&Parser, offLine);
    4076                         continue;
    4077                     }
    4078                 }
    4079             }
    4080         }
    4081 
    4082         /*
    4083          * Pass it thru as-is with line continuation.
    4084          */
    4085         while (scmKmkIsLineWithContinuation(pchLine, cchLine))
    4086         {
    4087             ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
    4088             Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
    4089             if (!pchLine)
    4090                 break;
    4091             cchLine = Parser.cchLine;
    4092         }
    4093         if (pchLine)
    4094             ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
    4095     }
    4096 
    4097     return kScmMaybeModified; /* Make the caller check */
    40982190}
    40992191
Note: See TracChangeset for help on using the changeset viewer.

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