/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2014 Olivier Goffart ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "preprocessor.h" #include "utils.h" #include #include #include #include QT_BEGIN_NAMESPACE #include "ppkeywords.cpp" #include "keywords.cpp" // transform \r\n into \n // \r into \n (os9 style) // backslash-newlines into newlines static QByteArray cleaned(const QByteArray &input) { QByteArray result; result.resize(input.size()); const char *data = input.constData(); const char *end = input.constData() + input.size(); char *output = result.data(); int newlines = 0; while (data != end) { while (data != end && is_space(*data)) ++data; bool takeLine = (*data == '#'); if (*data == '%' && *(data+1) == ':') { takeLine = true; ++data; } if (takeLine) { *output = '#'; ++output; do ++data; while (data != end && is_space(*data)); } while (data != end) { // handle \\\n, \\\r\n and \\\r if (*data == '\\') { if (*(data + 1) == '\r') { ++data; } if (data != end && (*(data + 1) == '\n' || (*data) == '\r')) { ++newlines; data += 1; if (data != end && *data != '\r') data += 1; continue; } } else if (*data == '\r' && *(data + 1) == '\n') { // reduce \r\n to \n ++data; } if (data == end) break; char ch = *data; if (ch == '\r') // os9: replace \r with \n ch = '\n'; *output = ch; ++output; if (*data == '\n') { // output additional newlines to keep the correct line-numbering // for the lines following the backslash-newline sequence(s) while (newlines) { *output = '\n'; ++output; --newlines; } ++data; break; } ++data; } } result.resize(output - result.constData()); return result; } bool Preprocessor::preprocessOnly = false; void Preprocessor::skipUntilEndif() { while(index < symbols.size() - 1 && symbols.at(index).token != PP_ENDIF){ switch (symbols.at(index).token) { case PP_IF: case PP_IFDEF: case PP_IFNDEF: ++index; skipUntilEndif(); break; default: ; } ++index; } } bool Preprocessor::skipBranch() { while (index < symbols.size() - 1 && (symbols.at(index).token != PP_ENDIF && symbols.at(index).token != PP_ELIF && symbols.at(index).token != PP_ELSE) ){ switch (symbols.at(index).token) { case PP_IF: case PP_IFDEF: case PP_IFNDEF: ++index; skipUntilEndif(); break; default: ; } ++index; } return (index < symbols.size() - 1); } Symbols Preprocessor::tokenize(const QByteArray& input, int lineNum, Preprocessor::TokenizeMode mode) { Symbols symbols; // Preallocate some space to speed up the code below. // The magic divisor value was found by calculating the average ratio between // input size and the final size of symbols. // This yielded a value of 16.x when compiling Qt Base. symbols.reserve(input.size() / 16); const char *begin = input.constData(); const char *data = begin; while (*data) { if (mode == TokenizeCpp || mode == TokenizeDefine) { int column = 0; const char *lexem = data; int state = 0; Token token = NOTOKEN; for (;;) { if (static_cast(*data) < 0) { ++data; continue; } int nextindex = keywords[state].next; int next = 0; if (*data == keywords[state].defchar) next = keywords[state].defnext; else if (!state || nextindex) next = keyword_trans[nextindex][(int)*data]; if (!next) break; state = next; token = keywords[state].token; ++data; } // suboptimal, is_ident_char should use a table if (keywords[state].ident && is_ident_char(*data)) token = keywords[state].ident; if (token == NOTOKEN) { if (*data) ++data; // an error really, but let's ignore this input // to not confuse moc later. However in pre-processor // only mode let's continue. if (!Preprocessor::preprocessOnly) continue; } ++column; if (token > SPECIAL_TREATMENT_MARK) { switch (token) { case QUOTE: data = skipQuote(data); token = STRING_LITERAL; // concatenate multi-line strings for easier // STRING_LITERAL handling in moc if (!Preprocessor::preprocessOnly && !symbols.isEmpty() && symbols.constLast().token == STRING_LITERAL) { const QByteArray newString = '\"' + symbols.constLast().unquotedLexem() + input.mid(lexem - begin + 1, data - lexem - 2) + '\"'; symbols.last() = Symbol(symbols.constLast().lineNum, STRING_LITERAL, newString); continue; } break; case SINGLEQUOTE: while (*data && (*data != '\'' || (*(data-1)=='\\' && *(data-2)!='\\'))) ++data; if (*data) ++data; token = CHARACTER_LITERAL; break; case LANGLE_SCOPE: // split <:: into two tokens, < and :: token = LANGLE; data -= 2; break; case DIGIT: while (is_digit_char(*data) || *data == '\'') ++data; if (!*data || *data != '.') { token = INTEGER_LITERAL; if (data - lexem == 1 && (*data == 'x' || *data == 'X') && *lexem == '0') { ++data; while (is_hex_char(*data) || *data == '\'') ++data; } break; } token = FLOATING_LITERAL; ++data; Q_FALLTHROUGH(); case FLOATING_LITERAL: while (is_digit_char(*data) || *data == '\'') ++data; if (*data == '+' || *data == '-') ++data; if (*data == 'e' || *data == 'E') { ++data; while (is_digit_char(*data) || *data == '\'') ++data; } if (*data == 'f' || *data == 'F' || *data == 'l' || *data == 'L') ++data; break; case HASH: if (column == 1 && mode == TokenizeCpp) { mode = PreparePreprocessorStatement; while (*data && (*data == ' ' || *data == '\t')) ++data; if (is_ident_char(*data)) mode = TokenizePreprocessorStatement; continue; } break; case PP_HASHHASH: if (mode == TokenizeCpp) continue; break; case NEWLINE: ++lineNum; if (mode == TokenizeDefine) { mode = TokenizeCpp; // emit the newline token break; } continue; case BACKSLASH: { const char *rewind = data; while (*data && (*data == ' ' || *data == '\t')) ++data; if (*data && *data == '\n') { ++data; continue; } data = rewind; } break; case CHARACTER: while (is_ident_char(*data)) ++data; token = IDENTIFIER; break; case C_COMMENT: if (*data) { if (*data == '\n') ++lineNum; ++data; if (*data) { if (*data == '\n') ++lineNum; ++data; } } while (*data && (*(data-1) != '/' || *(data-2) != '*')) { if (*data == '\n') ++lineNum; ++data; } token = WHITESPACE; // one comment, one whitespace Q_FALLTHROUGH(); case WHITESPACE: if (column == 1) column = 0; while (*data && (*data == ' ' || *data == '\t')) ++data; if (Preprocessor::preprocessOnly) // tokenize whitespace break; continue; case CPP_COMMENT: while (*data && *data != '\n') ++data; continue; // ignore safely, the newline is a separator default: continue; //ignore } } #ifdef USE_LEXEM_STORE if (!Preprocessor::preprocessOnly && token != IDENTIFIER && token != STRING_LITERAL && token != FLOATING_LITERAL && token != INTEGER_LITERAL) symbols += Symbol(lineNum, token); else #endif symbols += Symbol(lineNum, token, input, lexem-begin, data-lexem); } else { // Preprocessor const char *lexem = data; int state = 0; Token token = NOTOKEN; if (mode == TokenizePreprocessorStatement) { state = pp_keyword_trans[0][(int)'#']; mode = TokenizePreprocessor; } for (;;) { if (static_cast(*data) < 0) { ++data; continue; } int nextindex = pp_keywords[state].next; int next = 0; if (*data == pp_keywords[state].defchar) next = pp_keywords[state].defnext; else if (!state || nextindex) next = pp_keyword_trans[nextindex][(int)*data]; if (!next) break; state = next; token = pp_keywords[state].token; ++data; } // suboptimal, is_ident_char should use a table if (pp_keywords[state].ident && is_ident_char(*data)) token = pp_keywords[state].ident; switch (token) { case NOTOKEN: if (*data) ++data; break; case PP_DEFINE: mode = PrepareDefine; break; case PP_IFDEF: symbols += Symbol(lineNum, PP_IF); symbols += Symbol(lineNum, PP_DEFINED); continue; case PP_IFNDEF: symbols += Symbol(lineNum, PP_IF); symbols += Symbol(lineNum, PP_NOT); symbols += Symbol(lineNum, PP_DEFINED); continue; case PP_INCLUDE: mode = TokenizeInclude; break; case PP_QUOTE: data = skipQuote(data); token = PP_STRING_LITERAL; break; case PP_SINGLEQUOTE: while (*data && (*data != '\'' || (*(data-1)=='\\' && *(data-2)!='\\'))) ++data; if (*data) ++data; token = PP_CHARACTER_LITERAL; break; case PP_DIGIT: while (is_digit_char(*data) || *data == '\'') ++data; if (!*data || *data != '.') { token = PP_INTEGER_LITERAL; if (data - lexem == 1 && (*data == 'x' || *data == 'X') && *lexem == '0') { ++data; while (is_hex_char(*data) || *data == '\'') ++data; } break; } token = PP_FLOATING_LITERAL; ++data; Q_FALLTHROUGH(); case PP_FLOATING_LITERAL: while (is_digit_char(*data) || *data == '\'') ++data; if (*data == '+' || *data == '-') ++data; if (*data == 'e' || *data == 'E') { ++data; while (is_digit_char(*data) || *data == '\'') ++data; } if (*data == 'f' || *data == 'F' || *data == 'l' || *data == 'L') ++data; break; case PP_CHARACTER: if (mode == PreparePreprocessorStatement) { // rewind entire token to begin data = lexem; mode = TokenizePreprocessorStatement; continue; } while (is_ident_char(*data)) ++data; token = PP_IDENTIFIER; if (mode == PrepareDefine) { symbols += Symbol(lineNum, token, input, lexem-begin, data-lexem); // make sure we explicitly add the whitespace here if the next char // is not an opening brace, so we can distinguish correctly between // regular and function macros if (*data != '(') symbols += Symbol(lineNum, WHITESPACE); mode = TokenizeDefine; continue; } break; case PP_C_COMMENT: if (*data) { if (*data == '\n') ++lineNum; ++data; if (*data) { if (*data == '\n') ++lineNum; ++data; } } while (*data && (*(data-1) != '/' || *(data-2) != '*')) { if (*data == '\n') ++lineNum; ++data; } token = PP_WHITESPACE; // one comment, one whitespace Q_FALLTHROUGH(); case PP_WHITESPACE: while (*data && (*data == ' ' || *data == '\t')) ++data; continue; // the preprocessor needs no whitespace case PP_CPP_COMMENT: while (*data && *data != '\n') ++data; continue; // ignore safely, the newline is a separator case PP_NEWLINE: ++lineNum; mode = TokenizeCpp; break; case PP_BACKSLASH: { const char *rewind = data; while (*data && (*data == ' ' || *data == '\t')) ++data; if (*data && *data == '\n') { ++data; continue; } data = rewind; } break; case PP_LANGLE: if (mode != TokenizeInclude) break; token = PP_STRING_LITERAL; while (*data && *data != '\n' && *(data-1) != '>') ++data; break; default: break; } if (mode == PreparePreprocessorStatement) continue; #ifdef USE_LEXEM_STORE if (token != PP_IDENTIFIER && token != PP_STRING_LITERAL && token != PP_FLOATING_LITERAL && token != PP_INTEGER_LITERAL) symbols += Symbol(lineNum, token); else #endif symbols += Symbol(lineNum, token, input, lexem-begin, data-lexem); } } symbols += Symbol(); // eof symbol return symbols; } void Preprocessor::macroExpand(Symbols *into, Preprocessor *that, const Symbols &toExpand, int &index, int lineNum, bool one, const QSet &excludeSymbols) { SymbolStack symbols; SafeSymbols sf; sf.symbols = toExpand; sf.index = index; sf.excludedSymbols = excludeSymbols; symbols.push(sf); if (toExpand.isEmpty()) return; for (;;) { QByteArray macro; Symbols newSyms = macroExpandIdentifier(that, symbols, lineNum, ¯o); if (macro.isEmpty()) { // not a macro Symbol s = symbols.symbol(); s.lineNum = lineNum; *into += s; } else { SafeSymbols sf; sf.symbols = newSyms; sf.index = 0; sf.expandedMacro = macro; symbols.push(sf); } if (!symbols.hasNext() || (one && symbols.size() == 1)) break; symbols.next(); } if (symbols.size()) index = symbols.top().index; else index = toExpand.size(); } Symbols Preprocessor::macroExpandIdentifier(Preprocessor *that, SymbolStack &symbols, int lineNum, QByteArray *macroName) { Symbol s = symbols.symbol(); // not a macro if (s.token != PP_IDENTIFIER || !that->macros.contains(s) || symbols.dontReplaceSymbol(s.lexem())) { return Symbols(); } const Macro ¯o = that->macros.value(s); *macroName = s.lexem(); Symbols expansion; if (!macro.isFunction) { expansion = macro.symbols; } else { bool haveSpace = false; while (symbols.test(PP_WHITESPACE)) { haveSpace = true; } if (!symbols.test(PP_LPAREN)) { *macroName = QByteArray(); Symbols syms; if (haveSpace) syms += Symbol(lineNum, PP_WHITESPACE); syms += s; syms.last().lineNum = lineNum; return syms; } QVarLengthArray arguments; while (symbols.hasNext()) { Symbols argument; // strip leading space while (symbols.test(PP_WHITESPACE)) {} int nesting = 0; bool vararg = macro.isVariadic && (arguments.size() == macro.arguments.size() - 1); while (symbols.hasNext()) { Token t = symbols.next(); if (t == PP_LPAREN) { ++nesting; } else if (t == PP_RPAREN) { --nesting; if (nesting < 0) break; } else if (t == PP_COMMA && nesting == 0) { if (!vararg) break; } argument += symbols.symbol(); } arguments += argument; if (nesting < 0) break; else if (!symbols.hasNext()) that->error("missing ')' in macro usage"); } // empty VA_ARGS if (macro.isVariadic && arguments.size() == macro.arguments.size() - 1) arguments += Symbols(); // now replace the macro arguments with the expanded arguments enum Mode { Normal, Hash, HashHash } mode = Normal; for (int i = 0; i < macro.symbols.size(); ++i) { const Symbol &s = macro.symbols.at(i); if (s.token == HASH || s.token == PP_HASHHASH) { mode = (s.token == HASH ? Hash : HashHash); continue; } int index = macro.arguments.indexOf(s); if (mode == Normal) { if (index >= 0 && index < arguments.size()) { // each argument undoergoes macro expansion if it's not used as part of a # or ## if (i == macro.symbols.size() - 1 || macro.symbols.at(i + 1).token != PP_HASHHASH) { Symbols arg = arguments.at(index); int idx = 1; macroExpand(&expansion, that, arg, idx, lineNum, false, symbols.excludeSymbols()); } else { expansion += arguments.at(index); } } else { expansion += s; } } else if (mode == Hash) { if (index < 0) { that->error("'#' is not followed by a macro parameter"); continue; } else if (index >= arguments.size()) { that->error("Macro invoked with too few parameters for a use of '#'"); continue; } const Symbols &arg = arguments.at(index); QByteArray stringified; for (int i = 0; i < arg.size(); ++i) { stringified += arg.at(i).lexem(); } stringified.replace('"', "\\\""); stringified.prepend('"'); stringified.append('"'); expansion += Symbol(lineNum, STRING_LITERAL, stringified); } else if (mode == HashHash){ if (s.token == WHITESPACE) continue; while (expansion.size() && expansion.constLast().token == PP_WHITESPACE) expansion.pop_back(); Symbol next = s; if (index >= 0 && index < arguments.size()) { const Symbols &arg = arguments.at(index); if (arg.size() == 0) { mode = Normal; continue; } next = arg.at(0); } if (!expansion.isEmpty() && expansion.constLast().token == s.token && expansion.constLast().token != STRING_LITERAL) { Symbol last = expansion.takeLast(); QByteArray lexem = last.lexem() + next.lexem(); expansion += Symbol(lineNum, last.token, lexem); } else { expansion += next; } if (index >= 0 && index < arguments.size()) { const Symbols &arg = arguments.at(index); for (int i = 1; i < arg.size(); ++i) expansion += arg.at(i); } } mode = Normal; } if (mode != Normal) that->error("'#' or '##' found at the end of a macro argument"); } return expansion; } void Preprocessor::substituteUntilNewline(Symbols &substituted) { while (hasNext()) { Token token = next(); if (token == PP_IDENTIFIER) { macroExpand(&substituted, this, symbols, index, symbol().lineNum, true); } else if (token == PP_DEFINED) { bool braces = test(PP_LPAREN); next(PP_IDENTIFIER); Symbol definedOrNotDefined = symbol(); definedOrNotDefined.token = macros.contains(definedOrNotDefined)? PP_MOC_TRUE : PP_MOC_FALSE; substituted += definedOrNotDefined; if (braces) test(PP_RPAREN); continue; } else if (token == PP_NEWLINE) { substituted += symbol(); break; } else { substituted += symbol(); } } } class PP_Expression : public Parser { public: int value() { index = 0; return unary_expression_lookup() ? conditional_expression() : 0; } int conditional_expression(); int logical_OR_expression(); int logical_AND_expression(); int inclusive_OR_expression(); int exclusive_OR_expression(); int AND_expression(); int equality_expression(); int relational_expression(); int shift_expression(); int additive_expression(); int multiplicative_expression(); int unary_expression(); bool unary_expression_lookup(); int primary_expression(); bool primary_expression_lookup(); }; int PP_Expression::conditional_expression() { int value = logical_OR_expression(); if (test(PP_QUESTION)) { int alt1 = conditional_expression(); int alt2 = test(PP_COLON) ? conditional_expression() : 0; return value ? alt1 : alt2; } return value; } int PP_Expression::logical_OR_expression() { int value = logical_AND_expression(); if (test(PP_OROR)) return logical_OR_expression() || value; return value; } int PP_Expression::logical_AND_expression() { int value = inclusive_OR_expression(); if (test(PP_ANDAND)) return logical_AND_expression() && value; return value; } int PP_Expression::inclusive_OR_expression() { int value = exclusive_OR_expression(); if (test(PP_OR)) return value | inclusive_OR_expression(); return value; } int PP_Expression::exclusive_OR_expression() { int value = AND_expression(); if (test(PP_HAT)) return value ^ exclusive_OR_expression(); return value; } int PP_Expression::AND_expression() { int value = equality_expression(); if (test(PP_AND)) return value & AND_expression(); return value; } int PP_Expression::equality_expression() { int value = relational_expression(); switch (next()) { case PP_EQEQ: return value == equality_expression(); case PP_NE: return value != equality_expression(); default: prev(); return value; } } int PP_Expression::relational_expression() { int value = shift_expression(); switch (next()) { case PP_LANGLE: return value < relational_expression(); case PP_RANGLE: return value > relational_expression(); case PP_LE: return value <= relational_expression(); case PP_GE: return value >= relational_expression(); default: prev(); return value; } } int PP_Expression::shift_expression() { int value = additive_expression(); switch (next()) { case PP_LTLT: return value << shift_expression(); case PP_GTGT: return value >> shift_expression(); default: prev(); return value; } } int PP_Expression::additive_expression() { int value = multiplicative_expression(); switch (next()) { case PP_PLUS: return value + additive_expression(); case PP_MINUS: return value - additive_expression(); default: prev(); return value; } } int PP_Expression::multiplicative_expression() { int value = unary_expression(); switch (next()) { case PP_STAR: return value * multiplicative_expression(); case PP_PERCENT: { int remainder = multiplicative_expression(); return remainder ? value % remainder : 0; } case PP_SLASH: { int div = multiplicative_expression(); return div ? value / div : 0; } default: prev(); return value; }; } int PP_Expression::unary_expression() { switch (next()) { case PP_PLUS: return unary_expression(); case PP_MINUS: return -unary_expression(); case PP_NOT: return !unary_expression(); case PP_TILDE: return ~unary_expression(); case PP_MOC_TRUE: return 1; case PP_MOC_FALSE: return 0; default: prev(); return primary_expression(); } } bool PP_Expression::unary_expression_lookup() { Token t = lookup(); return (primary_expression_lookup() || t == PP_PLUS || t == PP_MINUS || t == PP_NOT || t == PP_TILDE || t == PP_DEFINED); } int PP_Expression::primary_expression() { int value; if (test(PP_LPAREN)) { value = conditional_expression(); test(PP_RPAREN); } else { next(); value = lexem().toInt(0, 0); } return value; } bool PP_Expression::primary_expression_lookup() { Token t = lookup(); return (t == PP_IDENTIFIER || t == PP_INTEGER_LITERAL || t == PP_FLOATING_LITERAL || t == PP_MOC_TRUE || t == PP_MOC_FALSE || t == PP_LPAREN); } int Preprocessor::evaluateCondition() { PP_Expression expression; expression.currentFilenames = currentFilenames; substituteUntilNewline(expression.symbols); return expression.value(); } static QByteArray readOrMapFile(QFile *file) { const qint64 size = file->size(); char *rawInput = reinterpret_cast(file->map(0, size)); return rawInput ? QByteArray::fromRawData(rawInput, size) : file->readAll(); } static void mergeStringLiterals(Symbols *_symbols) { Symbols &symbols = *_symbols; for (Symbols::iterator i = symbols.begin(); i != symbols.end(); ++i) { if (i->token == STRING_LITERAL) { Symbols::Iterator mergeSymbol = i; int literalsLength = mergeSymbol->len; while (++i != symbols.end() && i->token == STRING_LITERAL) literalsLength += i->len - 2; // no quotes if (literalsLength != mergeSymbol->len) { QByteArray mergeSymbolOriginalLexem = mergeSymbol->unquotedLexem(); QByteArray &mergeSymbolLexem = mergeSymbol->lex; mergeSymbolLexem.resize(0); mergeSymbolLexem.reserve(literalsLength); mergeSymbolLexem.append('"'); mergeSymbolLexem.append(mergeSymbolOriginalLexem); for (Symbols::const_iterator j = mergeSymbol + 1; j != i; ++j) mergeSymbolLexem.append(j->lex.constData() + j->from + 1, j->len - 2); // append j->unquotedLexem() mergeSymbolLexem.append('"'); mergeSymbol->len = mergeSymbol->lex.length(); mergeSymbol->from = 0; i = symbols.erase(mergeSymbol + 1, i); } if (i == symbols.end()) break; } } } static QByteArray searchIncludePaths(const QList &includepaths, const QByteArray &include) { QFileInfo fi; for (int j = 0; j < includepaths.size() && !fi.exists(); ++j) { const Parser::IncludePath &p = includepaths.at(j); if (p.isFrameworkPath) { const int slashPos = include.indexOf('/'); if (slashPos == -1) continue; fi.setFile(QString::fromLocal8Bit(p.path + '/' + include.left(slashPos) + ".framework/Headers/"), QString::fromLocal8Bit(include.mid(slashPos + 1))); } else { fi.setFile(QString::fromLocal8Bit(p.path), QString::fromLocal8Bit(include)); } // try again, maybe there's a file later in the include paths with the same name // (186067) if (fi.isDir()) { fi = QFileInfo(); continue; } } if (!fi.exists() || fi.isDir()) return QByteArray(); return fi.canonicalFilePath().toLocal8Bit(); } QByteArray Preprocessor::resolveInclude(const QByteArray &include, const QByteArray &relativeTo) { if (!relativeTo.isEmpty()) { QFileInfo fi; fi.setFile(QFileInfo(QString::fromLocal8Bit(relativeTo)).dir(), QString::fromLocal8Bit(include)); if (fi.exists() && !fi.isDir()) return fi.canonicalFilePath().toLocal8Bit(); } auto it = nonlocalIncludePathResolutionCache.find(include); if (it == nonlocalIncludePathResolutionCache.end()) it = nonlocalIncludePathResolutionCache.insert(include, searchIncludePaths(includes, include)); return it.value(); } void Preprocessor::preprocess(const QByteArray &filename, Symbols &preprocessed) { currentFilenames.push(filename); preprocessed.reserve(preprocessed.size() + symbols.size()); while (hasNext()) { Token token = next(); switch (token) { case PP_INCLUDE: { int lineNum = symbol().lineNum; QByteArray include; bool local = false; if (test(PP_STRING_LITERAL)) { local = lexem().startsWith('\"'); include = unquotedLexem(); } else continue; until(PP_NEWLINE); include = resolveInclude(include, local ? filename : QByteArray()); if (include.isNull()) continue; if (Preprocessor::preprocessedIncludes.contains(include)) continue; Preprocessor::preprocessedIncludes.insert(include); QFile file(QString::fromLocal8Bit(include.constData())); if (!file.open(QFile::ReadOnly)) continue; QByteArray input = readOrMapFile(&file); file.close(); if (input.isEmpty()) continue; Symbols saveSymbols = symbols; int saveIndex = index; // phase 1: get rid of backslash-newlines input = cleaned(input); // phase 2: tokenize for the preprocessor symbols = tokenize(input); input.clear(); index = 0; // phase 3: preprocess conditions and substitute macros preprocessed += Symbol(0, MOC_INCLUDE_BEGIN, include); preprocess(include, preprocessed); preprocessed += Symbol(lineNum, MOC_INCLUDE_END, include); symbols = saveSymbols; index = saveIndex; continue; } case PP_DEFINE: { next(IDENTIFIER); QByteArray name = lexem(); Macro macro; macro.isVariadic = false; Token t = next(); if (t == LPAREN) { // we have a function macro macro.isFunction = true; parseDefineArguments(¯o); } else if (t == PP_WHITESPACE){ macro.isFunction = false; } else { error("Moc: internal error"); } int start = index; until(PP_NEWLINE); macro.symbols.reserve(index - start - 1); // remove whitespace where there shouldn't be any: // Before and after the macro, after a # and around ## Token lastToken = HASH; // skip shitespace at the beginning for (int i = start; i < index - 1; ++i) { Token token = symbols.at(i).token; if (token == PP_WHITESPACE || token == WHITESPACE) { if (lastToken == PP_HASH || lastToken == HASH || lastToken == PP_HASHHASH || lastToken == PP_WHITESPACE || lastToken == WHITESPACE) continue; } else if (token == PP_HASHHASH) { if (!macro.symbols.isEmpty() && (lastToken == PP_WHITESPACE || lastToken == WHITESPACE)) macro.symbols.pop_back(); } macro.symbols.append(symbols.at(i)); lastToken = token; } // remove trailing whitespace while (!macro.symbols.isEmpty() && (macro.symbols.constLast().token == PP_WHITESPACE || macro.symbols.constLast().token == WHITESPACE)) macro.symbols.pop_back(); if (!macro.symbols.isEmpty()) { if (macro.symbols.constFirst().token == PP_HASHHASH || macro.symbols.constLast().token == PP_HASHHASH) { error("'##' cannot appear at either end of a macro expansion"); } } macros.insert(name, macro); continue; } case PP_UNDEF: { next(IDENTIFIER); QByteArray name = lexem(); until(PP_NEWLINE); macros.remove(name); continue; } case PP_IDENTIFIER: { // substitute macros macroExpand(&preprocessed, this, symbols, index, symbol().lineNum, true); continue; } case PP_HASH: until(PP_NEWLINE); continue; // skip unknown preprocessor statement case PP_IFDEF: case PP_IFNDEF: case PP_IF: while (!evaluateCondition()) { if (!skipBranch()) break; if (test(PP_ELIF)) { } else { until(PP_NEWLINE); break; } } continue; case PP_ELIF: case PP_ELSE: skipUntilEndif(); Q_FALLTHROUGH(); case PP_ENDIF: until(PP_NEWLINE); continue; case PP_NEWLINE: continue; case SIGNALS: case SLOTS: { Symbol sym = symbol(); if (macros.contains("QT_NO_KEYWORDS")) sym.token = IDENTIFIER; else sym.token = (token == SIGNALS ? Q_SIGNALS_TOKEN : Q_SLOTS_TOKEN); preprocessed += sym; } continue; default: break; } preprocessed += symbol(); } currentFilenames.pop(); } Symbols Preprocessor::preprocessed(const QByteArray &filename, QFile *file) { QByteArray input = readOrMapFile(file); if (input.isEmpty()) return symbols; // phase 1: get rid of backslash-newlines input = cleaned(input); // phase 2: tokenize for the preprocessor index = 0; symbols = tokenize(input); #if 0 for (int j = 0; j < symbols.size(); ++j) fprintf(stderr, "line %d: %s(%s)\n", symbols[j].lineNum, symbols[j].lexem().constData(), tokenTypeName(symbols[j].token)); #endif // phase 3: preprocess conditions and substitute macros Symbols result; // Preallocate some space to speed up the code below. // The magic value was found by logging the final size // and calculating an average when running moc over FOSS projects. result.reserve(file->size() / 300000); preprocess(filename, result); mergeStringLiterals(&result); #if 0 for (int j = 0; j < result.size(); ++j) fprintf(stderr, "line %d: %s(%s)\n", result[j].lineNum, result[j].lexem().constData(), tokenTypeName(result[j].token)); #endif return result; } void Preprocessor::parseDefineArguments(Macro *m) { Symbols arguments; while (hasNext()) { while (test(PP_WHITESPACE)) {} Token t = next(); if (t == PP_RPAREN) break; if (t != PP_IDENTIFIER) { QByteArray l = lexem(); if (l == "...") { m->isVariadic = true; arguments += Symbol(symbol().lineNum, PP_IDENTIFIER, "__VA_ARGS__"); while (test(PP_WHITESPACE)) {} if (!test(PP_RPAREN)) error("missing ')' in macro argument list"); break; } else if (!is_identifier(l.constData(), l.length())) { error("Unexpected character in macro argument list."); } } Symbol arg = symbol(); if (arguments.contains(arg)) error("Duplicate macro parameter."); arguments += symbol(); while (test(PP_WHITESPACE)) {} t = next(); if (t == PP_RPAREN) break; if (t == PP_COMMA) continue; if (lexem() == "...") { //GCC extension: #define FOO(x, y...) x(y) // The last argument was already parsed. Just mark the macro as variadic. m->isVariadic = true; while (test(PP_WHITESPACE)) {} if (!test(PP_RPAREN)) error("missing ')' in macro argument list"); break; } error("Unexpected character in macro argument list."); } m->arguments = arguments; while (test(PP_WHITESPACE)) {} } void Preprocessor::until(Token t) { while(hasNext() && next() != t) ; } QT_END_NAMESPACE