File indexing completed on 2024-05-19 15:45:09

0001 /*
0002     SPDX-FileCopyrightText: 2006 Matt Rogers <mattr@kde.org>
0003     SPDX-FileCopyrightText: 2008 Aleix Pol <aleixpol@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "cmakelistsparser.h"
0009 #include <debug.h>
0010 
0011 #include <util/stack.h>
0012 #include <QDir>
0013 
0014 QMap<QChar, QChar> whatToScape()
0015 {
0016     //Only add those where we're not scaping the next character
0017     QMap<QChar, QChar> ret{
0018         {QLatin1Char('n'), QLatin1Char('\n')},
0019         {QLatin1Char('r'), QLatin1Char('\r')},
0020         {QLatin1Char('t'), QLatin1Char('\t')},
0021     };
0022     return ret;
0023 }
0024 
0025 const QMap<QChar, QChar> CMakeFunctionArgument::scapings=whatToScape();
0026 
0027 constexpr QChar scapingChar = QLatin1Char('\\');
0028 
0029 QString CMakeFunctionArgument::unescapeValue(const QString& value)
0030 {
0031     int firstScape=value.indexOf(scapingChar);
0032     if (firstScape<0)
0033     {
0034         return value;
0035     }
0036 
0037     QString newValue;
0038     int last=0;
0039     QMap<QChar, QChar>::const_iterator itEnd = scapings.constEnd();
0040     for(int i=firstScape; i<value.size()-1 && i>=0; i=value.indexOf(scapingChar, i+2))
0041     {
0042         newValue+=value.midRef(last, i-last);
0043         const QChar current=value[i+1];
0044         QMap<QChar, QChar>::const_iterator it = scapings.constFind(current);
0045 
0046         if(it!=itEnd)
0047             newValue += *it;
0048         else
0049             newValue += current;
0050 
0051         last=i+2;
0052     }
0053     newValue+=value.midRef(last, value.size());
0054 //     qCDebug(CMAKE) << "escaping" << value << newValue;
0055     return newValue;
0056 }
0057 
0058 void CMakeFunctionDesc::addArguments( const QStringList& args, bool addEvenIfEmpty )
0059 {
0060     if(addEvenIfEmpty && args.isEmpty())
0061         arguments += CMakeFunctionArgument();
0062     else {
0063         arguments.reserve(arguments.size() + args.size());
0064         for (const auto& arg : args) {
0065             CMakeFunctionArgument cmakeArg(arg);
0066             arguments.append(cmakeArg);
0067         }
0068     }
0069 }
0070 
0071 QString CMakeFunctionDesc::writeBack() const
0072 {
0073     QStringList args;
0074     args.reserve(arguments.size());
0075     for (const auto& arg : arguments) {
0076         if (arg.quoted) {
0077             args.append(QLatin1Char('"') + arg.value + QLatin1Char('"'));
0078         } else {
0079             args.append(arg.value);
0080         }
0081     }
0082     return name + QLatin1String("( ") + args.join(QLatin1Char(' ')) + QLatin1String(" )");
0083 }
0084 
0085 namespace CMakeListsParser
0086 {
0087 
0088 static bool readCMakeFunction( cmListFileLexer* lexer, CMakeFunctionDesc& func);
0089 
0090 CMakeFileContent readCMakeFile(const QString & _fileName)
0091 {
0092     cmListFileLexer* lexer = cmListFileLexer_New();
0093     if ( !lexer )
0094         return CMakeFileContent();
0095     if ( !cmListFileLexer_SetFileName( lexer, qPrintable( _fileName ), nullptr ) ) {
0096         qCDebug(CMAKE) << "cmake read error. could not read " << _fileName;
0097         cmListFileLexer_Delete(lexer);
0098         return CMakeFileContent();
0099     }
0100 
0101     CMakeFileContent ret;
0102     QString fileName = QDir::cleanPath(_fileName);
0103 
0104     bool readError = false, haveNewline = true;
0105     cmListFileLexer_Token* token;
0106 
0107     while(!readError && (token = cmListFileLexer_Scan(lexer)))
0108     {
0109         readError=false;
0110         if(token->type == cmListFileLexer_Token_Newline)
0111         {
0112             readError=false;
0113             haveNewline = true;
0114         }
0115         else if(token->type == cmListFileLexer_Token_Identifier)
0116         {
0117             if(haveNewline)
0118             {
0119                 haveNewline = false;
0120                 CMakeFunctionDesc function;
0121                 function.name = QString::fromLocal8Bit(token->text, token->length).toLower();
0122                 function.filePath = fileName;
0123                 function.line = token->line;
0124                 function.column = token->column;
0125 
0126                 readError = !readCMakeFunction( lexer, function);
0127                 ret.append(function);
0128 
0129                 if(readError)
0130                 {
0131                     qCDebug(CMAKE) << "Error while parsing:" << function.name << "at" << function.line;
0132                 }
0133             }
0134         }
0135     }
0136     cmListFileLexer_Delete(lexer);
0137 
0138     return ret;
0139 }
0140 
0141 }
0142 
0143 bool CMakeListsParser::readCMakeFunction(cmListFileLexer *lexer, CMakeFunctionDesc &func)
0144 {
0145         // Command name has already been parsed.  Read the left paren.
0146     cmListFileLexer_Token* token;
0147     if(!(token = cmListFileLexer_Scan(lexer)))
0148     {
0149         return false;
0150     }
0151     if(token->type != cmListFileLexer_Token_ParenLeft)
0152     {
0153         return false;
0154     }
0155 
0156     // Arguments.
0157     int parenthesis=1;
0158     while((token = cmListFileLexer_Scan(lexer)))
0159     {
0160         switch(token->type)
0161         {
0162             case cmListFileLexer_Token_ParenRight:
0163                 parenthesis--;
0164                 if(parenthesis==0) {
0165                     func.endLine=token->line;
0166                     func.endColumn=token->column;
0167                     return true;
0168                 } else if(parenthesis<0)
0169                     return false;
0170                 else
0171                     func.arguments << CMakeFunctionArgument( QString::fromLocal8Bit(token->text, token->length), false, token->line, token->column );
0172                 break;
0173             case cmListFileLexer_Token_ParenLeft:
0174                 parenthesis++;
0175                 func.arguments << CMakeFunctionArgument( QString::fromLocal8Bit(token->text, token->length), false, token->line, token->column );
0176                 break;
0177             case cmListFileLexer_Token_Identifier:
0178             case cmListFileLexer_Token_ArgumentBracket:
0179             case cmListFileLexer_Token_ArgumentUnquoted:
0180                 func.arguments << CMakeFunctionArgument( QString::fromLocal8Bit(token->text, token->length), false, token->line, token->column );
0181                 break;
0182             case cmListFileLexer_Token_ArgumentQuoted:
0183                 func.arguments << CMakeFunctionArgument( QString::fromLocal8Bit(token->text, token->length), true, token->line, token->column+1 );
0184                 break;
0185             case cmListFileLexer_Token_Space:
0186             case cmListFileLexer_Token_Newline:
0187                 break;
0188             default:
0189                 return false;
0190         }
0191     }
0192 
0193     return false;
0194 }
0195 
0196 CMakeFunctionDesc::CMakeFunctionDesc(const QString& name, const QStringList& args)
0197     : name(name)
0198 {
0199     addArguments(args);
0200 }
0201 
0202 CMakeFunctionDesc::CMakeFunctionDesc()
0203 {}
0204 
0205 bool CMakeFunctionDesc::operator==(const CMakeFunctionDesc & other) const
0206 {
0207     if(other.arguments.count()!=arguments.count() || name!=other.name)
0208         return false;
0209 
0210     auto it=arguments.constBegin();
0211     auto itOther=other.arguments.constBegin();
0212     for(;it!=arguments.constEnd(); ++it, ++itOther)
0213     {
0214         if(*it!=*itOther)
0215             return false;
0216     }
0217     return true;
0218 }
0219 
0220 CMakeFunctionArgument::CMakeFunctionArgument(const QString& v, bool q, quint32 l, quint32 c)
0221     : value(unescapeValue(v)), quoted(q), line(l), column(c)
0222 {
0223 }
0224 
0225 CMakeFunctionArgument::CMakeFunctionArgument(const QString& v)
0226     : value(v)
0227 {
0228 }