7#include <qstringlist.h>
11#include <qvarlengtharray.h>
15using namespace QtMiscUtils;
26 result.resize(input.size());
27 const char *data = input.constData();
28 const char *end = input.constData() + input.size();
29 char *output = result.data();
35 bool takeLine = (*data ==
'#');
36 if (*data ==
'%' && *(data+1) ==
':') {
43 do ++data;
while (data != end &&
is_space(*data
));
48 if (*(data + 1) ==
'\r') {
51 if (data != end && (*(data + 1) ==
'\n' || (*data) ==
'\r')) {
54 if (data != end && *data !=
'\r')
58 }
else if (*data ==
'\r' && *(data + 1) ==
'\n') {
84 result.resize(output - result.constData());
91 while(index < symbols.size() - 1 && symbols.at(index).token != PP_ENDIF){
92 switch (symbols.at(index).token) {
108 while (index < symbols.size() - 1
109 && (symbols.at(index).token != PP_ENDIF
110 && symbols.at(index).token != PP_ELIF
111 && symbols.at(index).token != PP_ELSE)
113 switch (symbols.at(index).token) {
125 return (index < symbols.size() - 1);
136 symbols.reserve(input.size() / 16);
137 const char *begin = input.constData();
138 const char *data = begin;
143 const char *lexem = data;
145 Token token = NOTOKEN;
147 if (
static_cast<
signed char>(*data) < 0) {
151 int nextindex = keywords[state]
.next;
153 if (*data == keywords[state]
.defchar)
155 else if (!state || nextindex)
160 token = keywords[state]
.token;
166 token = keywords[state]
.ident;
168 if (token == NOTOKEN) {
180 if (token > SPECIAL_TREATMENT_MARK) {
184 token = STRING_LITERAL;
188 && !symbols.isEmpty()
189 && symbols.constLast().token == STRING_LITERAL) {
191 const QByteArray newString
193 + symbols.constLast().unquotedLexemView()
194 + input.mid(lexem - begin + 1, data - lexem - 2)
196 symbols.last() =
Symbol(symbols.constLast().lineNum,
203 while (*data && (*data !=
'\''
205 && *(data-2)!=
'\\')))
209 token = CHARACTER_LITERAL;
218 bool hasSeenTokenSeparator =
false;;
219 while (isAsciiDigit(*data) || (hasSeenTokenSeparator = *data ==
'\''))
221 if (!*data || *data !=
'.') {
222 token = INTEGER_LITERAL;
223 if (data - lexem == 1 &&
224 (*data ==
'x' || *data ==
'X'
225 || *data ==
'b' || *data ==
'B')
228 while (isHexDigit(*data) || (hasSeenTokenSeparator = *data ==
'\''))
230 }
else if (*data ==
'L')
232 if (!hasSeenTokenSeparator) {
240 token = FLOATING_LITERAL;
244 case FLOATING_LITERAL:
245 while (isAsciiDigit(*data) || *data ==
'\'')
247 if (*data ==
'+' || *data ==
'-')
249 if (*data ==
'e' || *data ==
'E') {
251 while (isAsciiDigit(*data) || *data ==
'\'')
254 if (*data ==
'f' || *data ==
'F'
255 || *data ==
'l' || *data ==
'L')
261 while (*data && (*data ==
' ' || *data ==
'\t'))
282 const char *rewind = data;
283 while (*data && (*data ==
' ' || *data ==
'\t'))
285 if (*data && *data ==
'\n') {
307 while (*data && (*(data-1) !=
'/' || *(data-2) !=
'*')) {
317 while (*data && (*data ==
' ' || *data ==
'\t'))
323 while (*data && *data !=
'\n')
330 symbols +=
Symbol(lineNum, token, input, lexem-begin, data-lexem);
334 const char *lexem = data;
336 Token token = NOTOKEN;
342 if (
static_cast<
signed char>(*data) < 0) {
346 int nextindex = pp_keywords[state]
.next;
348 if (*data == pp_keywords[state]
.defchar)
350 else if (!state || nextindex)
355 token = pp_keywords[state]
.token;
360 token = pp_keywords[state]
.ident;
380 case PP_INCLUDE_NEXT:
385 token = PP_STRING_LITERAL;
388 while (*data && (*data !=
'\''
390 && *(data-2)!=
'\\')))
394 token = PP_CHARACTER_LITERAL;
397 while (isAsciiDigit(*data) || *data ==
'\'')
399 if (!*data || *data !=
'.') {
400 token = PP_INTEGER_LITERAL;
401 if (data - lexem == 1 &&
402 (*data ==
'x' || *data ==
'X')
405 while (isHexDigit(*data) || *data ==
'\'')
407 }
else if (*data ==
'L')
411 token = PP_FLOATING_LITERAL;
414 case PP_FLOATING_LITERAL:
415 while (isAsciiDigit(*data) || *data ==
'\'')
417 if (*data ==
'+' || *data ==
'-')
419 if (*data ==
'e' || *data ==
'E') {
421 while (isAsciiDigit(*data) || *data ==
'\'')
424 if (*data ==
'f' || *data ==
'F'
425 || *data ==
'l' || *data ==
'L')
437 token = PP_IDENTIFIER;
440 symbols +=
Symbol(lineNum, token, input, lexem-begin, data-lexem);
461 while (*data && (*(data-1) !=
'/' || *(data-2) !=
'*')) {
466 token = PP_WHITESPACE;
469 while (*data && (*data ==
' ' || *data ==
'\t'))
473 while (*data && *data !=
'\n')
482 const char *rewind = data;
483 while (*data && (*data ==
' ' || *data ==
'\t'))
485 if (*data && *data ==
'\n') {
494 token = PP_STRING_LITERAL;
495 while (*data && *data !=
'\n' && *(data-1) !=
'>')
503 symbols +=
Symbol(lineNum, token, input, lexem-begin, data-lexem);
511 int lineNum,
bool one,
const QSet<QByteArray> &excludeSymbols)
516 sf.symbols = toExpand;
518 sf.excludedSymbols = excludeSymbols;
519 symbols.push(
std::move(sf));
521 if (toExpand.isEmpty())
526 Symbols newSyms = macroExpandIdentifier(that, symbols, lineNum, ¯o);
528 if (macro.isEmpty()) {
535 sf.symbols = newSyms;
537 sf.expandedMacro = macro;
538 symbols.push(
std::move(sf));
540 if (!symbols
.hasNext() || (one && symbols.size() == 1))
546 index = symbols.top().index;
548 index = toExpand.size();
557 if (s
.token != PP_IDENTIFIER || !that
->macros.contains(s) || symbols.dontReplaceSymbol(s.lexem())) {
562 *macroName = s.lexem();
566 expansion = macro.symbols;
568 bool haveSpace =
false;
569 while (symbols
.test(PP_WHITESPACE
)) { haveSpace =
true; }
570 if (!symbols
.test(PP_LPAREN
)) {
571 *macroName = QByteArray();
576 syms.last().lineNum = lineNum;
583 while (symbols
.test(PP_WHITESPACE
)) {}
585 bool vararg = macro
.isVariadic && (arguments.size() == macro.arguments.size() - 1);
588 if (t == PP_LPAREN) {
590 }
else if (t == PP_RPAREN) {
594 }
else if (t == PP_COMMA && nesting == 0) {
600 arguments += argument;
605 that->error(
"missing ')' in macro usage");
609 if (macro
.isVariadic && arguments.size() == macro.arguments.size() - 1)
610 arguments += Symbols();
619 const auto end = macro.symbols.cend();
620 auto it = macro.symbols.cbegin();
621 const auto lastSym =
std::prev(macro.symbols.cend(), !macro.symbols.isEmpty() ? 1 : 0);
622 for (; it != end; ++it) {
625 mode = (s
.token == HASH ? Hash : HashHash);
628 const qsizetype index = macro.arguments.indexOf(s);
629 if (mode == Normal) {
630 if (index >= 0 && index < arguments.size()) {
632 if (it == lastSym ||
std::next(it)->token != PP_HASHHASH) {
633 Symbols arg = arguments.at(index);
635 macroExpand(&expansion, that, arg, idx, lineNum,
false, symbols.excludeSymbols());
637 expansion += arguments.at(index);
642 }
else if (mode == Hash) {
644 that->error(
"'#' is not followed by a macro parameter");
646 }
else if (index >= arguments.size()) {
647 that->error(
"Macro invoked with too few parameters for a use of '#'");
651 const Symbols &arg = arguments.at(index);
652 QByteArray stringified;
654 stringified = arg.front().lexem();
655 for (
auto it = arg.cbegin();
std::next(it) != arg.cend(); ++it) {
656 const auto next =
std::next(it);
657 if (next->from - (it->from + it->len) > 0)
658 stringified +=
' ' + next->lexem();
660 stringified += next->lexem();
663 stringified.replace(
'"',
"\\\"");
664 stringified.prepend(
'"');
665 stringified.append(
'"');
668 expansion +=
Symbol(lineNum, STRING_LITERAL, stringified);
669 }
else if (mode == HashHash){
670 if (s
.token == WHITESPACE)
673 while (expansion.size() && expansion.constLast().token == PP_WHITESPACE)
674 expansion.pop_back();
677 if (index >= 0 && index < arguments.size()) {
678 const Symbols &arg = arguments.at(index);
679 if (arg.size() == 0) {
686 if (!expansion.isEmpty() && expansion.constLast().token == s
.token
687 && expansion.constLast().token != STRING_LITERAL) {
688 Symbol last = expansion.takeLast();
690 QByteArray lexem = last.lexem() + next.lexem();
696 if (index >= 0 && index < arguments.size()) {
697 const Symbols &arg = arguments.at(index);
699 expansion.append(arg.cbegin() + 1, arg.cend());
705 that->error(
"'#' or '##' found at the end of a macro argument");
715 Token token = next();
716 if (token == PP_IDENTIFIER) {
717 macroExpand(&substituted,
this, symbols, index, symbol().lineNum,
true);
718 }
else if (token == PP_DEFINED) {
719 bool braces = test(PP_LPAREN);
720 if (test(PP_HAS_INCLUDE) || test(PP_HAS_INCLUDE_NEXT)) {
722 Symbol definedOrNotDefined = symbol();
723 definedOrNotDefined
.token = PP_MOC_TRUE;
724 substituted += definedOrNotDefined;
727 Symbol definedOrNotDefined = symbol();
728 definedOrNotDefined
.token =
macros.contains(definedOrNotDefined)? PP_MOC_TRUE : PP_MOC_FALSE;
729 substituted += definedOrNotDefined;
734 }
else if (token == PP_NEWLINE) {
735 substituted += symbol();
737 }
else if (token == PP_HAS_INCLUDE || token == PP_HAS_INCLUDE_NEXT) {
738 const bool isIncludeNext = (token == PP_HAS_INCLUDE_NEXT);
739 const Symbol hasIncludeSymbol = symbol();
746 const Token tok = next();
747 if (tok == PP_RPAREN && nesting == 0)
749 if (tok == PP_NEWLINE || tok == NOTOKEN) {
750 const QByteArray msg =
"missing ')' in " + hasIncludeSymbol.lexemView();
751 error(hasIncludeSymbol, msg.constData());
753 if (tok == PP_LPAREN)
755 else if (tok == PP_RPAREN)
757 argument += symbol();
763 const auto isHeaderName = [](
const Symbols &syms) {
764 if (syms.size() == 1)
765 return syms.constFirst().token == PP_STRING_LITERAL;
766 return syms.size() > 2 && syms.constFirst().token == PP_LANGLE
767 && syms.constLast().token == PP_RANGLE;
770 if (!isHeaderName(argument)) {
773 macroExpand(&expanded,
this, argument, pos, hasIncludeSymbol.lineNum,
false);
775 expanded.removeIf([](
const Symbol &s) {
return s
.token == PP_WHITESPACE; });
777 if (!isHeaderName(argument)) {
778 const QByteArray msg =
"Invalid argument to " + hasIncludeSymbol.lexemView();
779 error(hasIncludeSymbol, msg.constData());
783 const bool usesAngleInclude = argument.constFirst().token == PP_LANGLE;
784 QByteArray includeAsString;
785 if (usesAngleInclude) {
786 for (qsizetype i = 1; i < argument.size() - 1; ++i)
787 includeAsString += argument.at(i).lexem();
789 includeAsString = argument.constFirst().unquotedLexem();
794 result = !resolveIncludeNext(includeAsString, includeNextStartIndex()).isNull();
796 const QByteArray relative = usesAngleInclude ? QByteArray() : currentFilenames.top();
797 result = !resolveInclude(includeAsString, relative).isNull();
799 Symbol definedOrNotDefined = hasIncludeSymbol;
800 definedOrNotDefined
.token = result ? PP_MOC_TRUE : PP_MOC_FALSE;
801 substituted += definedOrNotDefined;
803 substituted += symbol();
834 if (test(PP_QUESTION)) {
837 return value ? alt1 : alt2;
959 return remainder ? value % remainder : 0;
964 return div ? value / div : 0;
1001 || t == PP_DEFINED);
1007 if (test(PP_LPAREN)) {
1012 auto lexView = lexemView();
1013 if (lexView.endsWith(
'L'))
1015 value = lexView.toInt(
nullptr, 0);
1023 return (t == PP_IDENTIFIER
1024 || t == PP_INTEGER_LITERAL
1025 || t == PP_FLOATING_LITERAL
1027 || t == PP_MOC_FALSE
1034 expression.currentFilenames = currentFilenames;
1043 const qint64 size = file->size();
1044 char *rawInput =
reinterpret_cast<
char*>(file->map(0, size));
1045 return rawInput ? QByteArray::fromRawData(rawInput, size) : file->readAll();
1051 Q_ASSERT(from + len <= lex.size());
1052 Q_ASSERT(next.len >= 2);
1053 Q_ASSERT(next.from + next.len <= next.lex.size());
1055 if (len != lex.size()) {
1057 QByteArray l = lexemView().chopped(1) % next.lexemView().sliced(1);
1062 const auto unquoted = next.unquotedLexemView();
1063 lex.insert(from + len - 1,
1073 const auto mergeable = [](
const Symbol &lhs,
const Symbol &rhs) {
1074 return lhs
.token == STRING_LITERAL && rhs
.token == STRING_LITERAL;
1077 auto end = symbols.end();
1078 auto it =
std::adjacent_find(symbols.begin(), symbols.end(), mergeable);
1088 lit->mergeStringLiteral(*it);
1090 while (++it != end) {
1096 if (it->token == STRING_LITERAL) {
1098 lit->mergeStringLiteral(*it);
1100 *++dst =
std::move(*it);
1104 *++dst =
std::move(*it);
1110 symbols.erase(dst, end);
1117 const QByteArray &include,
1118 qsizetype startIndex,
1119 const bool debugIncludes)
1122 qsizetype foundIndex = -1;
1124 if (Q_UNLIKELY(debugIncludes)) {
1125 fprintf(stderr,
"debug-includes: searching for '%s'\n", include.constData());
1128 for (qsizetype i = startIndex; i < includepaths.size(); ++i) {
1129 const Parser::IncludePath &p = includepaths.at(i);
1133 if (p.isFrameworkPath) {
1138 const qsizetype slashPos = include.indexOf(
'/');
1141 fi.setFile(QString::fromLocal8Bit(p.path +
'/' + include.left(slashPos) +
".framework/Headers/"),
1142 QString::fromLocal8Bit(include.mid(slashPos + 1)));
1144 fi.setFile(QString::fromLocal8Bit(p.path), QString::fromLocal8Bit(include));
1147 if (Q_UNLIKELY(debugIncludes)) {
1148 const auto candidate = fi.filePath().toLocal8Bit();
1149 fprintf(stderr,
"debug-includes: considering '%s'\n", candidate.constData());
1161 if (!fi.exists() || fi.isDir()) {
1162 if (Q_UNLIKELY(debugIncludes)) {
1163 fprintf(stderr,
"debug-includes: can't find '%s'\n", include.constData());
1168 const auto result = fi.canonicalFilePath().toLocal8Bit();
1170 if (Q_UNLIKELY(debugIncludes)) {
1171 fprintf(stderr,
"debug-includes: found '%s'\n", result.constData());
1174 return { result, foundIndex };
1178 qsizetype *foundIndex)
1183 if (!relativeTo.isEmpty()) {
1185 fi.setFile(QFileInfo(QString::fromLocal8Bit(relativeTo)).dir(), QString::fromLocal8Bit(include));
1186 if (fi.exists() && !fi.isDir()) {
1191 if (foundIndex && !currentIncludeDirIndex.empty())
1192 *foundIndex = currentIncludeDirIndex.top();
1193 return fi.canonicalFilePath().toLocal8Bit();
1197 auto it = nonlocalIncludePathResolutionCache.find(include);
1198 if (it == nonlocalIncludePathResolutionCache.end())
1199 it = nonlocalIncludePathResolutionCache.insert(include,
1206 *foundIndex = it.value().foundIndex;
1207 return it.value().path;
1211 qsizetype *foundIndex)
1215 const IncludeResolution resolution = searchIncludePaths(includes, include, startIndex, debugIncludes);
1217 *foundIndex = resolution.foundIndex;
1218 return resolution.path;
1230 const qsizetype currentDirIndex = currentIncludeDirIndex.top();
1231 if (currentDirIndex < 0) {
1232 if (Q_UNLIKELY(debugIncludes)) {
1233 fprintf(stderr,
"debug-includes: '%s' was not found via the include path; "
1234 "#include_next will search from the start\n",
1235 currentFilenames.top().constData());
1239 return currentDirIndex + 1;
1242void Preprocessor::preprocess(
const QByteArray &filename,
Symbols &preprocessed, qsizetype includeDirIndex)
1244 currentFilenames.push(filename);
1245 currentIncludeDirIndex.push(includeDirIndex);
1246 preprocessed.reserve(preprocessed.size() + symbols.size());
1248 Token token = next();
1252 case PP_INCLUDE_NEXT:
1254 const bool includeNext = (token == PP_INCLUDE_NEXT);
1255 int lineNum = symbol().lineNum;
1258 if (test(PP_STRING_LITERAL)) {
1259 local = lexemView().startsWith(
'\"');
1260 include = unquotedLexem();
1265 qsizetype foundIndex = -1;
1267 include = resolveIncludeNext(include, includeNextStartIndex(), &foundIndex);
1269 include = resolveInclude(include, local ? filename : QByteArray(), &foundIndex);
1273 if (include.isNull())
1276 if (Preprocessor::preprocessedIncludes.contains(include))
1278 Preprocessor::preprocessedIncludes.insert(include);
1280 QFile file(QString::fromLocal8Bit(include.constData()));
1281 if (!file.open(QFile::ReadOnly))
1284 QByteArray input = readOrMapFile(&file);
1287 if (input.isEmpty())
1290 Symbols saveSymbols = symbols;
1291 qsizetype saveIndex = index;
1294 input = cleaned(input);
1297 symbols = tokenize(input);
1303 preprocessed +=
Symbol(0, MOC_INCLUDE_BEGIN, include);
1304 preprocess(include, preprocessed, foundIndex);
1305 preprocessed +=
Symbol(lineNum, MOC_INCLUDE_END, include);
1307 symbols = saveSymbols;
1314 QByteArray name = lexem();
1315 if (name.isEmpty() || !is_ident_start(name[0]))
1326 qsizetype start = index;
1328 macro.symbols.reserve(index - start - 1);
1332 Token lastToken = HASH;
1333 for (qsizetype i = start; i < index - 1; ++i) {
1334 Token token = symbols.at(i).token;
1335 if (token == WHITESPACE) {
1336 if (lastToken == PP_HASH || lastToken == HASH ||
1337 lastToken == PP_HASHHASH ||
1338 lastToken == WHITESPACE)
1340 }
else if (token == PP_HASHHASH) {
1341 if (!macro.symbols.isEmpty() &&
1342 lastToken == WHITESPACE)
1343 macro.symbols.pop_back();
1345 macro.symbols.append(symbols.at(i));
1349 while (!macro.symbols.isEmpty() &&
1350 (macro.symbols.constLast().token == PP_WHITESPACE || macro.symbols.constLast().token == WHITESPACE))
1351 macro.symbols.pop_back();
1353 if (!macro.symbols.isEmpty()) {
1354 if (macro.symbols.constFirst().token == PP_HASHHASH ||
1355 macro.symbols.constLast().token == PP_HASHHASH) {
1356 error(
"'##' cannot appear at either end of a macro expansion");
1359 macros.insert(name, macro);
1364 QByteArray name = lexem();
1369 case PP_IDENTIFIER: {
1371 macroExpand(&preprocessed,
this, symbols, index, symbol().lineNum,
true);
1383 if (test(PP_ELIF)) {
1402 if (
macros.contains(
"QT_NO_KEYWORDS"))
1405 sym
.token = (token == SIGNALS ? Q_SIGNALS_TOKEN : Q_SLOTS_TOKEN);
1406 preprocessed += sym;
1411 preprocessed += symbol();
1414 currentIncludeDirIndex.pop();
1415 currentFilenames.pop();
1420 QByteArray input = readOrMapFile(file);
1422 if (input.isEmpty())
1426 input = cleaned(input);
1430 symbols = tokenize(input);
1433 for (
int j = 0; j < symbols.size(); ++j)
1434 fprintf(stderr,
"line %d: %s(%s)\n",
1436 symbols[j].lexem().constData(),
1437 tokenTypeName(symbols[j].token));
1445 result.reserve(file->size() / 300000);
1446 preprocess(filename, result);
1447 mergeStringLiterals(result);
1450 for (
int j = 0; j < result.size(); ++j)
1451 fprintf(stderr,
"line %d: %s(%s)\n",
1453 result[j].lexem().constData(),
1454 tokenTypeName(result[j].token));
1464 while (test(PP_WHITESPACE)) {}
1468 if (t != PP_IDENTIFIER) {
1469 QByteArrayView l = lexemView();
1472 arguments +=
Symbol(symbol().lineNum, PP_IDENTIFIER,
"__VA_ARGS__");
1473 while (test(PP_WHITESPACE)) {}
1474 if (!test(PP_RPAREN))
1475 error(
"missing ')' in macro argument list");
1477 }
else if (!is_identifier(l.constData(), l.size())) {
1478 error(
"Unexpected character in macro argument list.");
1483 if (arguments.contains(arg))
1484 error(
"Duplicate macro parameter.");
1485 arguments += symbol();
1487 while (test(PP_WHITESPACE)) {}
1493 if (lexemView() ==
"...") {
1497 while (test(PP_WHITESPACE)) {}
1498 if (!test(PP_RPAREN))
1499 error(
"missing ')' in macro argument list");
1502 error(
"Unexpected character in macro argument list.");
1504 m->arguments = arguments;
1505 while (test(PP_WHITESPACE)) {}
1510 while(hasNext() && next() != t)
1516 debugIncludes = value;
int relational_expression()
int exclusive_OR_expression()
bool unary_expression_lookup()
int logical_OR_expression()
int equality_expression()
int logical_AND_expression()
int additive_expression()
int multiplicative_expression()
int conditional_expression()
bool primary_expression_lookup()
int inclusive_OR_expression()
QByteArray resolveIncludeNext(const QByteArray &filename, qsizetype startIndex, qsizetype *foundIndex=nullptr)
void setDebugIncludes(bool value)
void parseDefineArguments(Macro *m)
Symbols preprocessed(const QByteArray &filename, QFile *device)
void substituteUntilNewline(Symbols &substituted)
static bool preprocessOnly
QByteArray resolveInclude(const QByteArray &filename, const QByteArray &relativeTo, qsizetype *foundIndex=nullptr)
@ PreparePreprocessorStatement
@ TokenizePreprocessorStatement
const Symbol & symbol() const
static const short keyword_trans[][128]
Combined button and popup list for selecting options.
static const short pp_keyword_trans[][128]
static QByteArray readOrMapFile(QFile *file)
static IncludeResolution searchIncludePaths(const QList< Parser::IncludePath > &includepaths, const QByteArray &include, qsizetype startIndex, const bool debugIncludes)
static QByteArray cleaned(const QByteArray &input)
static void mergeStringLiterals(Symbols &symbols)
Simple structure used by the Doc and DocParser classes.
Symbol(int lineNum, Token token)
void mergeStringLiteral(const Symbol &next)