diff -r 000000000000 -r 876b1a06bc25 tools/icheck/parser/src/libs/cplusplus/pp-engine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/icheck/parser/src/libs/cplusplus/pp-engine.cpp Wed Aug 25 15:49:42 2010 +0300 @@ -0,0 +1,1424 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +/* + Copyright 2005 Roberto Raggi + + Permission to use, copy, modify, distribute, and sell this software and its + documentation for any purpose is hereby granted without fee, provided that + the above copyright notice appear in all copies and that both that + copyright notice and this permission notice appear in supporting + documentation. + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "pp.h" +#include "pp-cctype.h" + +#include +#include +#include +#include + +#include +#include + +namespace CPlusPlus { + +struct Value +{ + enum Kind { + Kind_Long, + Kind_ULong + }; + + Kind kind; + + union { + long l; + unsigned long ul; + }; + + + Value() + : kind(Kind_Long), l(0) + { } + + inline bool is_ulong () const + { return kind == Kind_ULong; } + + inline void set_ulong (unsigned long v) + { + ul = v; + kind = Kind_ULong; + } + + inline void set_long (long v) + { + l = v; + kind = Kind_Long; + } + + inline bool is_zero () const + { return l == 0; } + +#define PP_DEFINE_BIN_OP(name, op) \ + inline Value operator op(const Value &other) const \ + { \ + Value v = *this; \ + if (v.is_ulong () || other.is_ulong ()) \ + v.set_ulong (v.ul op other.ul); \ + else \ + v.set_long (v.l op other.l); \ + return v; \ + } + + PP_DEFINE_BIN_OP(op_add, +) + PP_DEFINE_BIN_OP(op_sub, -) + PP_DEFINE_BIN_OP(op_mult, *) + PP_DEFINE_BIN_OP(op_div, /) + PP_DEFINE_BIN_OP(op_mod, %) + PP_DEFINE_BIN_OP(op_lhs, <<) + PP_DEFINE_BIN_OP(op_rhs, >>) + PP_DEFINE_BIN_OP(op_lt, <) + PP_DEFINE_BIN_OP(op_gt, >) + PP_DEFINE_BIN_OP(op_le, <=) + PP_DEFINE_BIN_OP(op_ge, >=) + PP_DEFINE_BIN_OP(op_eq, ==) + PP_DEFINE_BIN_OP(op_ne, !=) + PP_DEFINE_BIN_OP(op_bit_and, &) + PP_DEFINE_BIN_OP(op_bit_or, |) + PP_DEFINE_BIN_OP(op_bit_xor, ^) + PP_DEFINE_BIN_OP(op_and, &&) + PP_DEFINE_BIN_OP(op_or, ||) + +#undef PP_DEFINE_BIN_OP +}; + +} // end of namespace CPlusPlus + + +using namespace CPlusPlus; + + +namespace { + +bool isMacroDefined(QByteArray name, unsigned offset, Environment *env, Client *client) +{ + Macro *m = env->resolve(name); + if (client) { + if (m) + client->passedMacroDefinitionCheck(offset, *m); + else + client->failedMacroDefinitionCheck(offset, name); + } + return m != 0; +} + +class RangeLexer +{ + const Token *first; + const Token *last; + Token trivial; + +public: + inline RangeLexer(const Token *first, const Token *last) + : first(first), last(last) + { + // WARN: `last' must be a valid iterator. + trivial.offset = last->offset; + } + + inline operator bool() const + { return first != last; } + + inline bool isValid() const + { return first != last; } + + inline int size() const + { return std::distance(first, last); } + + inline const Token *dot() const + { return first; } + + inline const Token &operator*() const + { + if (first != last) + return *first; + + return trivial; + } + + inline const Token *operator->() const + { + if (first != last) + return first; + + return &trivial; + } + + inline RangeLexer &operator++() + { + ++first; + return *this; + } +}; + +class ExpressionEvaluator +{ + ExpressionEvaluator(const ExpressionEvaluator &other); + void operator = (const ExpressionEvaluator &other); + +public: + ExpressionEvaluator(Client *client, Environment *env) + : client(client), env(env), _lex(0) + { } + + Value operator()(const Token *firstToken, const Token *lastToken, + const QByteArray &source) + { + this->source = source; + const Value previousValue = switchValue(Value()); + RangeLexer tmp(firstToken, lastToken); + RangeLexer *previousLex = _lex; + _lex = &tmp; + process_expression(); + _lex = previousLex; + return switchValue(previousValue); + } + +protected: + Value switchValue(const Value &value) + { + Value previousValue = _value; + _value = value; + return previousValue; + } + + bool isTokenDefined() const + { + if ((*_lex)->isNot(T_IDENTIFIER)) + return false; + const QByteArray spell = tokenSpell(); + if (spell.size() != 7) + return false; + return spell == "defined"; + } + + QByteArray tokenSpell() const + { + const QByteArray text = QByteArray::fromRawData(source.constData() + (*_lex)->offset, + (*_lex)->f.length); + return text; + } + + inline void process_expression() + { process_constant_expression(); } + + void process_primary() + { + if ((*_lex)->is(T_NUMERIC_LITERAL)) { + int base = 10; + const QByteArray spell = tokenSpell(); + if (spell.at(0) == '0') { + if (spell.size() > 1 && (spell.at(1) == 'x' || spell.at(1) == 'X')) + base = 16; + else + base = 8; + } + _value.set_long(tokenSpell().toLong(0, base)); + ++(*_lex); + } else if (isTokenDefined()) { + ++(*_lex); + if ((*_lex)->is(T_IDENTIFIER)) { + _value.set_long(isMacroDefined(tokenSpell(), (*_lex)->offset, env, client)); + ++(*_lex); + } else if ((*_lex)->is(T_LPAREN)) { + ++(*_lex); + if ((*_lex)->is(T_IDENTIFIER)) { + _value.set_long(isMacroDefined(tokenSpell(), (*_lex)->offset, env, client)); + ++(*_lex); + if ((*_lex)->is(T_RPAREN)) { + ++(*_lex); + } + } + } + } else if ((*_lex)->is(T_IDENTIFIER)) { + _value.set_long(0); + ++(*_lex); + } else if ((*_lex)->is(T_MINUS)) { + ++(*_lex); + process_primary(); + _value.set_long(- _value.l); + } else if ((*_lex)->is(T_PLUS)) { + ++(*_lex); + process_primary(); + } else if ((*_lex)->is(T_EXCLAIM)) { + ++(*_lex); + process_primary(); + _value.set_long(_value.is_zero()); + } else if ((*_lex)->is(T_LPAREN)) { + ++(*_lex); + process_expression(); + if ((*_lex)->is(T_RPAREN)) + ++(*_lex); + } + } + + Value process_expression_with_operator_precedence(const Value &lhs, int minPrecedence) + { + Value result = lhs; + + while (precedence((*_lex)->kind()) >= minPrecedence) { + const int oper = (*_lex)->kind(); + const int operPrecedence = precedence(oper); + ++(*_lex); + process_primary(); + Value rhs = _value; + + for (int LA_token_kind = (*_lex)->kind(), LA_precedence = precedence(LA_token_kind); + LA_precedence > operPrecedence && isBinaryOperator(LA_token_kind); + LA_token_kind = (*_lex)->kind(), LA_precedence = precedence(LA_token_kind)) { + rhs = process_expression_with_operator_precedence(rhs, LA_precedence); + } + + result = evaluate_expression(oper, result, rhs); + } + + return result; + } + + void process_constant_expression() + { + process_primary(); + _value = process_expression_with_operator_precedence(_value, precedence(T_PIPE_PIPE)); + + if ((*_lex)->is(T_QUESTION)) { + const Value cond = _value; + ++(*_lex); + process_constant_expression(); + Value left = _value, right; + if ((*_lex)->is(T_COLON)) { + ++(*_lex); + process_constant_expression(); + right = _value; + } + _value = ! cond.is_zero() ? left : right; + } + } + +private: + inline int precedence(int tokenKind) const + { + switch (tokenKind) { + case T_PIPE_PIPE: return 0; + case T_AMPER_AMPER: return 1; + case T_PIPE: return 2; + case T_CARET: return 3; + case T_AMPER: return 4; + case T_EQUAL_EQUAL: + case T_EXCLAIM_EQUAL: return 5; + case T_GREATER: + case T_LESS: + case T_LESS_EQUAL: + case T_GREATER_EQUAL: return 6; + case T_LESS_LESS: + case T_GREATER_GREATER: return 7; + case T_PLUS: + case T_MINUS: return 8; + case T_STAR: + case T_SLASH: + case T_PERCENT: return 9; + + default: + return -1; + } + } + + static inline bool isBinaryOperator(int tokenKind) + { + switch (tokenKind) { + case T_PIPE_PIPE: + case T_AMPER_AMPER: + case T_PIPE: + case T_CARET: + case T_AMPER: + case T_EQUAL_EQUAL: + case T_EXCLAIM_EQUAL: + case T_GREATER: + case T_LESS: + case T_LESS_EQUAL: + case T_GREATER_EQUAL: + case T_LESS_LESS: + case T_GREATER_GREATER: + case T_PLUS: + case T_MINUS: + case T_STAR: + case T_SLASH: + case T_PERCENT: + return true; + + default: + return false; + } + } + + static inline Value evaluate_expression(int tokenKind, const Value &lhs, const Value &rhs) + { + switch (tokenKind) { + case T_PIPE_PIPE: return lhs || rhs; + case T_AMPER_AMPER: return lhs && rhs; + case T_PIPE: return lhs | rhs; + case T_CARET: return lhs ^ rhs; + case T_AMPER: return lhs & rhs; + case T_EQUAL_EQUAL: return lhs == rhs; + case T_EXCLAIM_EQUAL: return lhs != rhs; + case T_GREATER: return lhs > rhs; + case T_LESS: return lhs < rhs; + case T_LESS_EQUAL: return lhs <= rhs; + case T_GREATER_EQUAL: return lhs >= rhs; + case T_LESS_LESS: return lhs << rhs; + case T_GREATER_GREATER: return lhs >> rhs; + case T_PLUS: return lhs + rhs; + case T_MINUS: return lhs - rhs; + case T_STAR: return lhs * rhs; + case T_SLASH: return rhs.is_zero() ? Value() : lhs / rhs; + case T_PERCENT: return rhs.is_zero() ? Value() : lhs % rhs; + + default: + return Value(); + } + } + +private: + Client *client; + Environment *env; + QByteArray source; + RangeLexer *_lex; + Value _value; +}; + +} // end of anonymous namespace + + +Preprocessor::Preprocessor(Client *client, Environment *env) + : client(client), + env(env), + _expand(env), + _skipping(MAX_LEVEL), + _trueTest(MAX_LEVEL), + _dot(_tokens.end()), + _result(0), + _markGeneratedTokens(false), + _expandMacros(true) +{ + resetIfLevel (); +} + +void Preprocessor::pushState(const State &s) +{ + _savedStates.append(state()); + _source = s.source; + _tokens = s.tokens; + _dot = s.dot; +} + +Preprocessor::State Preprocessor::state() const +{ + State state; + state.source = _source; + state.tokens = _tokens; + state.dot = _dot; + return state; +} + +void Preprocessor::popState() +{ + const State &state = _savedStates.last(); + _source = state.source; + _tokens = state.tokens; + _dot = state.dot; + _savedStates.removeLast(); +} + +QByteArray Preprocessor::operator()(const QString &fileName, const QString &source) +{ + const QString previousOriginalSource = _originalSource; + _originalSource = source; + const QByteArray bytes = source.toLatin1(); + const QByteArray preprocessedCode = operator()(fileName, bytes); + _originalSource = previousOriginalSource; + return preprocessedCode; +} + +QByteArray Preprocessor::operator()(const QString &fileName, + const QByteArray &source) +{ + QByteArray preprocessed; + preprocess(fileName, source, &preprocessed); + return preprocessed; +} + +QByteArray Preprocessor::expand(const QByteArray &source) +{ + QByteArray result; + result.reserve(256); + expand(source, &result); + return result; +} + +void Preprocessor::expand(const QByteArray &source, QByteArray *result) +{ + if (result) + _expand(source, result); +} + +void Preprocessor::expand(const char *first, const char *last, QByteArray *result) +{ + const QByteArray source = QByteArray::fromRawData(first, last - first); + return expand(source, result); +} + +void Preprocessor::out(const QByteArray &text) +{ + if (_result) + _result->append(text); +} + +void Preprocessor::out(char ch) +{ + if (_result) + _result->append(ch); +} + +void Preprocessor::out(const char *s) +{ + if (_result) + _result->append(s); +} + +bool Preprocessor::expandMacros() const +{ + return _expandMacros; +} + +void Preprocessor::setExpandMacros(bool expandMacros) +{ + _expandMacros = expandMacros; +} + +Preprocessor::State Preprocessor::createStateFromSource(const QByteArray &source) const +{ + State state; + state.source = source; + Lexer lex(state.source.constBegin(), state.source.constEnd()); + lex.setScanKeywords(false); + Token tok; + do { + lex(&tok); + state.tokens.append(tok); + } while (tok.isNot(T_EOF_SYMBOL)); + state.dot = state.tokens.constBegin(); + return state; +} + +void Preprocessor::processNewline(bool force) +{ + if (_dot != _tokens.constBegin()) { + TokenIterator prevTok = _dot - 1; + + if (prevTok->isLiteral()) { + const char *ptr = _source.constBegin() + prevTok->begin(); + const char *end = ptr + prevTok->length(); + + for (; ptr != end; ++ptr) { + if (*ptr == '\n') + ++env->currentLine; + } + } + } + + if (! force && env->currentLine == _dot->lineno) + return; + + if (force || env->currentLine > _dot->lineno) { + out("\n# "); + out(QByteArray::number(_dot->lineno)); + out(' '); + out('"'); + out(env->currentFile.toUtf8()); + out('"'); + out('\n'); + } else { + for (unsigned i = env->currentLine; i < _dot->lineno; ++i) + out('\n'); + } + + env->currentLine = _dot->lineno; +} + +void Preprocessor::processSkippingBlocks(bool skippingBlocks, + TokenIterator start, TokenIterator /*end*/) +{ + if (! client) + return; + + if (skippingBlocks != _skipping[iflevel]) { + unsigned offset = start->offset; + + if (_skipping[iflevel]) { + if (_dot->f.newline) + ++offset; + + client->startSkippingBlocks(offset); + + } else { + if (offset) + --offset; + + client->stopSkippingBlocks(offset); + } + } +} + +bool Preprocessor::markGeneratedTokens(bool markGeneratedTokens, + TokenIterator dot) +{ + bool previous = _markGeneratedTokens; + _markGeneratedTokens = markGeneratedTokens; + + if (previous != _markGeneratedTokens) { + if (! dot) + dot = _dot; + + if (_markGeneratedTokens) + out("\n#gen true"); + else + out("\n#gen false"); + + processNewline(/*force = */ true); + + const char *begin = _source.constBegin(); + const char *end = begin; + + if (markGeneratedTokens) + end += dot->begin(); + else + end += (dot - 1)->end(); + + const char *it = end - 1; + for (; it != begin - 1; --it) { + if (*it == '\n') + break; + } + ++it; + + for (; it != end; ++it) { + if (! pp_isspace(*it)) + out(' '); + + else + out(*it); + } + + if (! markGeneratedTokens && dot->f.newline) + processNewline(/*force = */ true); + } + + return previous; +} + +void Preprocessor::preprocess(const QString &fileName, const QByteArray &source, + QByteArray *result) +{ + const int previousIfLevel = iflevel; + + QByteArray *previousResult = _result; + _result = result; + + pushState(createStateFromSource(source)); + + const QString previousFileName = env->currentFile; + env->currentFile = fileName; + + const unsigned previousCurrentLine = env->currentLine; + env->currentLine = 0; + + while (true) { + + if (_dot->f.joined) + out("\\"); + + processNewline(); + + if (_dot->is(T_EOF_SYMBOL)) { + break; + + } else if (_dot->is(T_POUND) && (! _dot->f.joined && _dot->f.newline)) { + // handle the preprocessor directive + + TokenIterator start = _dot; + do { + ++_dot; + } while (_dot->isNot(T_EOF_SYMBOL) && (_dot->f.joined || ! _dot->f.newline)); + + const bool skippingBlocks = _skipping[iflevel]; + + processDirective(start, _dot); + processSkippingBlocks(skippingBlocks, start, _dot); + + } else if (skipping()) { + // skip the current line + + do { + ++_dot; + } while (_dot->isNot(T_EOF_SYMBOL) && (_dot->f.joined || ! _dot->f.newline)); + + } else { + + if (_dot->f.whitespace) { + unsigned endOfPreviousToken = 0; + + if (_dot != _tokens.constBegin()) + endOfPreviousToken = (_dot - 1)->end(); + + const unsigned beginOfToken = _dot->begin(); + + const char *start = _source.constBegin() + endOfPreviousToken; + const char *end = _source.constBegin() + beginOfToken; + + const char *it = end - 1; + for (; it != start - 1; --it) { + if (*it == '\n') + break; + } + ++it; + + for (; it != end; ++it) { + if (pp_isspace(*it)) + out(*it); + + else + out(' '); + } + } + + if (_dot->isNot(T_IDENTIFIER)) { + out(tokenSpell(*_dot)); + ++_dot; + + } else { + const TokenIterator identifierToken = _dot; + ++_dot; // skip T_IDENTIFIER + + const QByteArray spell = tokenSpell(*identifierToken); + if (! _expandMacros) { + if (! env->isBuiltinMacro(spell)) { + Macro *m = env->resolve(spell); + if (m && ! m->isFunctionLike()) { + QByteArray expandedDefinition; + expandObjectLikeMacro(identifierToken, spell, m, &expandedDefinition); + if (expandedDefinition.trimmed().isEmpty()) { + out(QByteArray(spell.length(), ' ')); + continue; + } + } + } + out(spell); + continue; + } + + else if (env->isBuiltinMacro(spell)) + expandBuiltinMacro(identifierToken, spell); + + else { +#ifdef ICHECK_BUILD + if(spell != "Q_PROPERTY" && spell != "Q_INVOKABLE" && spell != "Q_ENUMS" + && spell != "Q_FLAGS" && spell != "Q_DECLARE_FLAGS"){ +#endif + if (Macro *m = env->resolve(spell)) { + if (! m->isFunctionLike()) { + if (0 == (m = processObjectLikeMacro(identifierToken, spell, m))) + continue; + + // the macro expansion generated something that looks like + // a function-like macro. + } + + // `m' is function-like macro. + if (_dot->is(T_LPAREN)) { + QVector actuals; + collectActualArguments(&actuals); + + if (_dot->is(T_RPAREN)) { + expandFunctionLikeMacro(identifierToken, m, actuals); + continue; + } + } + } +#ifdef ICHECK_BUILD + } +#endif + // it's not a function or object-like macro. + out(spell); + } + } + } + } + + popState(); + + env->currentFile = previousFileName; + env->currentLine = previousCurrentLine; + _result = previousResult; + + iflevel = previousIfLevel; +} + +void Preprocessor::collectActualArguments(QVector *actuals) +{ + if (_dot->isNot(T_LPAREN)) + return; + + ++_dot; + + if (_dot->is(T_RPAREN)) + return; + + actuals->append(collectOneActualArgument()); + + while (_dot->is(T_COMMA)) { + ++_dot; + + actuals->append(collectOneActualArgument()); + } +} + +MacroArgumentReference Preprocessor::collectOneActualArgument() +{ + const unsigned position = _dot->begin(); + + while (_dot->isNot(T_EOF_SYMBOL)) { + if (_dot->is(T_COMMA) || _dot->is(T_RPAREN)) + break; + + if (_dot->isNot(T_LPAREN)) + ++_dot; + + else { + int count = 0; + + for (; _dot->isNot(T_EOF_SYMBOL); ++_dot) { + if (_dot->is(T_LPAREN)) + ++count; + + else if (_dot->is(T_RPAREN)) { + if (! --count) { + ++_dot; + break; + } + } + } + } + } + + const unsigned end = _dot->begin(); + + return MacroArgumentReference(position, end - position); +} + +Macro *Preprocessor::processObjectLikeMacro(TokenIterator identifierToken, + const QByteArray &spell, + Macro *m) +{ + QByteArray tmp; + expandObjectLikeMacro(identifierToken, spell, m, &tmp); + + if (_dot->is(T_LPAREN)) { + // check if the expension generated a function-like macro. + + m = 0; // reset the active the macro + + pushState(createStateFromSource(tmp)); + + if (_dot->is(T_IDENTIFIER)) { + const QByteArray id = tokenSpell(*_dot); + + if (Macro *macro = env->resolve(id)) { + if (macro->isFunctionLike()) + m = macro; + } + } + + popState(); + + if (m != 0) + return m; + } + + const bool was = markGeneratedTokens(true, identifierToken); + out(tmp); + (void) markGeneratedTokens(was); + return 0; +} + +void Preprocessor::expandBuiltinMacro(TokenIterator identifierToken, + const QByteArray &spell) +{ + const bool was = markGeneratedTokens(true, identifierToken); + expand(spell, _result); + (void) markGeneratedTokens(was); +} + +void Preprocessor::expandObjectLikeMacro(TokenIterator identifierToken, + const QByteArray &spell, + Macro *m, + QByteArray *result) +{ + if (client) + client->startExpandingMacro(identifierToken->offset, + *m, spell, false); + + m->setHidden(true); + expand(m->definition(), result); + m->setHidden(false); + + if (client) + client->stopExpandingMacro(_dot->offset, *m); +} + +void Preprocessor::expandFunctionLikeMacro(TokenIterator identifierToken, + Macro *m, + const QVector &actuals) +{ + const char *beginOfText = startOfToken(*identifierToken); + const char *endOfText = endOfToken(*_dot); + ++_dot; // skip T_RPAREN + + if (client) { + const QByteArray text = + QByteArray::fromRawData(beginOfText, + endOfText - beginOfText); + + client->startExpandingMacro(identifierToken->offset, + *m, text, false, actuals); + } + + const bool was = markGeneratedTokens(true, identifierToken); + expand(beginOfText, endOfText, _result); + (void) markGeneratedTokens(was); + + if (client) + client->stopExpandingMacro(_dot->offset, *m); +} + +const char *Preprocessor::startOfToken(const Token &token) const +{ return _source.constBegin() + token.begin(); } + +const char *Preprocessor::endOfToken(const Token &token) const +{ return _source.constBegin() + token.end(); } + +QByteArray Preprocessor::tokenSpell(const Token &token) const +{ + const QByteArray text = QByteArray::fromRawData(_source.constBegin() + token.offset, + token.f.length); + return text; +} + +QByteArray Preprocessor::tokenText(const Token &token) const +{ + const QByteArray text(_source.constBegin() + token.offset, + token.f.length); + return text; +} + +void Preprocessor::processDirective(TokenIterator firstToken, TokenIterator lastToken) +{ + RangeLexer tk(firstToken, lastToken); + ++tk; // skip T_POUND + + if (tk->is(T_IDENTIFIER)) { + const QByteArray directive = tokenSpell(*tk); + switch (PP_DIRECTIVE_TYPE d = classifyDirective(directive)) { + case PP_DEFINE: + if (! skipping()) + processDefine(firstToken, lastToken); + break; + + case PP_INCLUDE: + case PP_INCLUDE_NEXT: + case PP_IMPORT: + if (! skipping()) + processInclude(d == PP_INCLUDE_NEXT, firstToken, lastToken); + break; + + case PP_UNDEF: + if (! skipping()) + processUndef(firstToken, lastToken); + break; + + case PP_ELIF: + processElif(firstToken, lastToken); + break; + + case PP_ELSE: + processElse(firstToken, lastToken); + break; + + case PP_ENDIF: + processEndif(firstToken, lastToken); + break; + + case PP_IF: + processIf(firstToken, lastToken); + break; + + case PP_IFDEF: + case PP_IFNDEF: + processIfdef(d == PP_IFNDEF, firstToken, lastToken); + break; + + default: + break; + } // switch + } +} + +QVector Preprocessor::tokenize(const QByteArray &text) const +{ + QVector tokens; + Lexer lex(text.constBegin(), text.constEnd()); + lex.setScanKeywords(false); + Token tk; + do { + lex(&tk); + tokens.append(tk); + } while (tk.isNot(T_EOF_SYMBOL)); + return tokens; +} + +void Preprocessor::processInclude(bool, TokenIterator firstToken, + TokenIterator lastToken, bool acceptMacros) +{ + if (! client) + return; // nothing to do. + + RangeLexer tk(firstToken, lastToken); + ++tk; // skip T_POUND + ++tk; // skip `include|nclude_next' + + if (acceptMacros && tk->is(T_IDENTIFIER)) { + // ### TODO: implement me +#if 0 + QByteArray name; + name.reserve(256); + MacroExpander expandInclude(env); + expandInclude(startOfToken(tokens.at(2)), + startOfToken(tokens.last()), + &name); + const QByteArray previousSource = switchSource(name); + //processInclude(skipCurentPath, tokenize(name), /*accept macros=*/ false); + (void) switchSource(previousSource); +#endif + + } else if (tk->is(T_LESS)) { + + TokenIterator start = tk.dot(); + + for (; tk->isNot(T_EOF_SYMBOL); ++tk) { + if (tk->is(T_GREATER)) + break; + } + + const char *beginOfPath = endOfToken(*start); + const char *endOfPath = startOfToken(*tk); + + QString fn = string(beginOfPath, endOfPath - beginOfPath); + client->sourceNeeded(fn, Client::IncludeGlobal, firstToken->lineno); + + } else if (tk->is(T_ANGLE_STRING_LITERAL) || tk->is(T_STRING_LITERAL)) { + + const QByteArray spell = tokenSpell(*tk); + const char *beginOfPath = spell.constBegin(); + const char *endOfPath = spell.constEnd(); + const char quote = *beginOfPath; + + if (beginOfPath + 1 != endOfPath && ((quote == '"' && endOfPath[-1] == '"') || + (quote == '<' && endOfPath[-1] == '>'))) { + + QString fn = string(beginOfPath + 1, spell.length() - 2); + client->sourceNeeded(fn, Client::IncludeLocal, firstToken->lineno); + } + } +} + +void Preprocessor::processDefine(TokenIterator firstToken, TokenIterator lastToken) +{ + RangeLexer tk(firstToken, lastToken); + + if (tk.size() < 3) + return; // nothing to do + + ++tk; // skip T_POUND + ++tk; // skip T_DEFINE + + if (tk->isNot(T_IDENTIFIER)) { + // ### warning expected an `identifier' + return; + } + + Macro macro; + macro.setFileName(env->currentFile); + macro.setLine(env->currentLine); + macro.setName(tokenText(*tk)); + macro.setOffset(firstToken->offset); + macro.setLength(endOfToken(lastToken[- 1]) - startOfToken(*firstToken)); + ++tk; // skip T_IDENTIFIER + + if (tk->is(T_LPAREN) && ! tk->f.whitespace) { + // a function-like macro definition + macro.setFunctionLike(true); + + ++tk; // skip T_LPAREN + if (tk->is(T_IDENTIFIER)) { + macro.addFormal(tokenText(*tk)); + ++tk; // skip T_IDENTIFIER + while (tk->is(T_COMMA)) { + ++tk;// skip T_COMMA + if (tk->isNot(T_IDENTIFIER)) + break; + macro.addFormal(tokenText(*tk)); + ++tk; // skip T_IDENTIFIER + } + } + + if (tk->is(T_DOT_DOT_DOT)) { + macro.setVariadic(true); + ++tk; // skip T_DOT_DOT_DOT + } + + if (tk->isNot(T_RPAREN)) { + // ### warning expected `)' + return; + } + + ++tk; // skip T_RPAREN + } + + if (isQtReservedWord(macro.name())) { + QByteArray macroId = macro.name(); + + if (macro.isFunctionLike()) { + macroId += '('; + bool fst = true; + foreach (const QByteArray formal, macro.formals()) { + if (! fst) + macroId += ", "; + fst = false; + macroId += formal; + } + macroId += ')'; + } + + macro.setDefinition(macroId); + } else { + // ### make me fast! + const char *startOfDefinition = startOfToken(*tk); + const char *endOfDefinition = endOfToken(lastToken[- 1]); + QByteArray definition(startOfDefinition, + endOfDefinition - startOfDefinition); + definition.replace("\\\n", " "); + definition.replace('\n', ' '); + macro.setDefinition(definition.trimmed()); + } + + env->bind(macro); + + if (client) + client->macroAdded(macro); +} + +void Preprocessor::processIf(TokenIterator firstToken, TokenIterator lastToken) +{ + RangeLexer tk(firstToken, lastToken); + + ++tk; // skip T_POUND + ++tk; // skipt `if' + + if (testIfLevel()) { + const char *first = startOfToken(*tk); + const char *last = startOfToken(*lastToken); + + MacroExpander expandCondition (env, 0, client, tk.dot()->offset); + QByteArray condition; + condition.reserve(256); + expandCondition(first, last, &condition); + + QVector tokens = tokenize(condition); + + const Value result = evalExpression(tokens.constBegin(), + tokens.constEnd() - 1, + condition); + + _trueTest[iflevel] = ! result.is_zero (); + _skipping[iflevel] = result.is_zero (); + } +} + +void Preprocessor::processElse(TokenIterator firstToken, TokenIterator lastToken) +{ + RangeLexer tk(firstToken, lastToken); + + if (iflevel == 0 && !skipping ()) { + // std::cerr << "*** WARNING #else without #if" << std::endl; + } else if (iflevel > 0 && _skipping[iflevel - 1]) { + _skipping[iflevel] = true; + } else { + _skipping[iflevel] = _trueTest[iflevel]; + } +} + +void Preprocessor::processElif(TokenIterator firstToken, TokenIterator lastToken) +{ + RangeLexer tk(firstToken, lastToken); + ++tk; // skip T_POUND + ++tk; // skipt `elif' + + if (! (iflevel > 0)) { + // std::cerr << "*** WARNING: " << __FILE__ << __LINE__ << std::endl; + } else if (iflevel == 0 && !skipping()) { + // std::cerr << "*** WARNING #else without #if" << std::endl; + } else if (!_trueTest[iflevel] && !_skipping[iflevel - 1]) { + + const char *first = startOfToken(*tk); + const char *last = startOfToken(*lastToken); + + MacroExpander expandCondition (env, 0, client, tk.dot()->offset); + QByteArray condition; + condition.reserve(256); + expandCondition(first, last, &condition); + + QVector tokens = tokenize(condition); + + const Value result = evalExpression(tokens.constBegin(), + tokens.constEnd() - 1, + condition); + + _trueTest[iflevel] = ! result.is_zero (); + _skipping[iflevel] = result.is_zero (); + } else { + _skipping[iflevel] = true; + } +} + +void Preprocessor::processEndif(TokenIterator, TokenIterator) +{ + if (iflevel == 0 && !skipping()) { + // std::cerr << "*** WARNING #endif without #if" << std::endl; + } else { + _skipping[iflevel] = false; + _trueTest[iflevel] = false; + + --iflevel; + } +} + +void Preprocessor::processIfdef(bool checkUndefined, + TokenIterator firstToken, + TokenIterator lastToken) +{ + RangeLexer tk(firstToken, lastToken); + + ++tk; // skip T_POUND + ++tk; // skip `ifdef' + if (testIfLevel()) { + if (tk->is(T_IDENTIFIER)) { + const QByteArray macroName = tokenSpell(*tk); + bool value = isMacroDefined(macroName, tk->offset, env, client) || env->isBuiltinMacro(macroName); + + if (checkUndefined) + value = ! value; + + _trueTest[iflevel] = value; + _skipping [iflevel] = ! value; + } + } +} + +void Preprocessor::processUndef(TokenIterator firstToken, TokenIterator lastToken) +{ + RangeLexer tk(firstToken, lastToken); + + ++tk; // skip T_POUND + ++tk; // skip `undef' + + if (tk->is(T_IDENTIFIER)) { + const QByteArray macroName = tokenText(*tk); + const Macro *macro = env->remove(macroName); + + if (client && macro) + client->macroAdded(*macro); + } +} + +void Preprocessor::resetIfLevel () +{ + iflevel = 0; + _skipping[iflevel] = false; + _trueTest[iflevel] = false; +} + +Preprocessor::PP_DIRECTIVE_TYPE Preprocessor::classifyDirective(const QByteArray &directive) const +{ + switch (directive.size()) + { + case 2: + if (directive[0] == 'i' && directive[1] == 'f') + return PP_IF; + break; + + case 4: + if (directive[0] == 'e' && directive == "elif") + return PP_ELIF; + else if (directive[0] == 'e' && directive == "else") + return PP_ELSE; + break; + + case 5: + if (directive[0] == 'i' && directive == "ifdef") + return PP_IFDEF; + else if (directive[0] == 'u' && directive == "undef") + return PP_UNDEF; + else if (directive[0] == 'e' && directive == "endif") + return PP_ENDIF; + break; + + case 6: + if (directive[0] == 'i' && directive == "ifndef") + return PP_IFNDEF; + else if (directive[0] == 'i' && directive == "import") + return PP_IMPORT; + else if (directive[0] == 'd' && directive == "define") + return PP_DEFINE; + break; + + case 7: + if (directive[0] == 'i' && directive == "include") + return PP_INCLUDE; + break; + + case 12: + if (directive[0] == 'i' && directive == "include_next") + return PP_INCLUDE_NEXT; + break; + + default: + break; + } + + return PP_UNKNOWN_DIRECTIVE; +} + +bool Preprocessor::testIfLevel() +{ + const bool result = !_skipping[iflevel++]; + _skipping[iflevel] = _skipping[iflevel - 1]; + _trueTest[iflevel] = false; + return result; +} + +int Preprocessor::skipping() const +{ return _skipping[iflevel]; } + +Value Preprocessor::evalExpression(TokenIterator firstToken, TokenIterator lastToken, + const QByteArray &source) const +{ + ExpressionEvaluator eval(client, env); + const Value result = eval(firstToken, lastToken, source); + return result; +} + +bool Preprocessor::isQtReservedWord(const QByteArray ¯oId) const +{ + const int size = macroId.size(); + if (size == 9 && macroId.at(0) == 'Q' && macroId == "Q_SIGNALS") + return true; + else if (size == 9 && macroId.at(0) == 'Q' && macroId == "Q_FOREACH") + return true; + else if (size == 7 && macroId.at(0) == 'Q' && macroId == "Q_SLOTS") + return true; + else if (size == 8 && macroId.at(0) == 'Q' && macroId == "Q_SIGNAL") + return true; + else if (size == 6 && macroId.at(0) == 'Q' && macroId == "Q_SLOT") + return true; + else if (size == 3 && macroId.at(0) == 'Q' && macroId == "Q_D") + return true; + else if (size == 3 && macroId.at(0) == 'Q' && macroId == "Q_Q") + return true; + else if (size == 6 && macroId.at(0) == 'S' && macroId == "SIGNAL") + return true; + else if (size == 4 && macroId.at(0) == 'S' && macroId == "SLOT") + return true; + else if (size == 7 && macroId.at(0) == 's' && macroId == "signals") + return true; + else if (size == 7 && macroId.at(0) == 'f' && macroId == "foreach") + return true; + else if (size == 5 && macroId.at(0) == 's' && macroId == "slots") + return true; + return false; +} + +QString Preprocessor::string(const char *first, int length) const +{ + if (_originalSource.isEmpty()) + return QString::fromUtf8(first, length); + + const int position = first - _source.constData(); + return _originalSource.mid(position, length); +}