mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-25 19:32:54 +00:00
1282 lines
32 KiB
C++
1282 lines
32 KiB
C++
/*
|
|
Copyright 2005 Roberto Raggi <roberto@kdevelop.org>
|
|
Copyright 2006 Hamish Rodda <rodda@kde.org>
|
|
Copyright 2007-2009 David Nolden <david.nolden.kdevelop@art-master.de>
|
|
|
|
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-engine.h"
|
|
|
|
#include <QFile>
|
|
#include <QTextStream>
|
|
|
|
#include <kdebug.h>
|
|
#include <klocale.h>
|
|
|
|
#include <language/duchain/duchain.h>
|
|
|
|
#include "pp-internal.h"
|
|
#include "preprocessor.h"
|
|
#include "pp-environment.h"
|
|
#include "pp-location.h"
|
|
#include "chartools.h"
|
|
#include "macrorepository.h"
|
|
|
|
using namespace rpp;
|
|
|
|
#define RETURN_ON_FAIL(x) if(!(x)) { ++input; kDebug(9007) << "Preprocessor: Condition not satisfied"; return; }
|
|
|
|
pp::pp(Preprocessor* preprocessor)
|
|
: m_environment(new Environment)
|
|
, expand(this, 0, true)
|
|
, m_preprocessor(preprocessor)
|
|
, nextToken(0)
|
|
, haveNextToken(false)
|
|
, hideNext(false)
|
|
, hadGuardCandidate(false)
|
|
, checkGuardEnd(false)
|
|
{
|
|
iflevel = 0;
|
|
_M_skipping[iflevel] = 0;
|
|
_M_true_test[iflevel] = 0;
|
|
}
|
|
|
|
pp::~pp()
|
|
{
|
|
delete m_environment;
|
|
}
|
|
|
|
Preprocessor* pp::preprocessor() {
|
|
return m_preprocessor;
|
|
}
|
|
|
|
PreprocessedContents pp::processFile(const QString& fileName)
|
|
{
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::ReadOnly))
|
|
{
|
|
kWarning(9007) << "file '" << fileName << "' not found!" ;
|
|
return PreprocessedContents();
|
|
}
|
|
|
|
PreprocessedContents result;
|
|
processFileInternal(fileName, file.readAll(), result);
|
|
return result;
|
|
}
|
|
|
|
PreprocessedContents pp::processFile(const QString& fileName, const QByteArray& data)
|
|
{
|
|
PreprocessedContents result;
|
|
processFileInternal(fileName, data, result);
|
|
return result;
|
|
}
|
|
|
|
void pp::processFileInternal(const QString& fileName, const QByteArray& fileContents, PreprocessedContents& result)
|
|
{
|
|
m_files.push(KDevelop::IndexedString(fileName));
|
|
// Guestimate as to how much expansion will occur
|
|
result.reserve(int(fileContents.length() * 1.2));
|
|
PreprocessedContents contents = tokenizeFromByteArray(fileContents);
|
|
{
|
|
Stream is(&contents);
|
|
Stream rs(&result, m_environment->locationTable());
|
|
operator () (is, rs);
|
|
}
|
|
result.squeeze();
|
|
}
|
|
|
|
void pp::handle_directive(uint directive, Stream& input, Stream& output)
|
|
{
|
|
static const uint ifDirective = KDevelop::IndexedString("if").index();
|
|
static const uint elseDirective = KDevelop::IndexedString("else").index();
|
|
static const uint elifDirective = KDevelop::IndexedString("elif").index();
|
|
static const uint ifdefDirective = KDevelop::IndexedString("ifdef").index();
|
|
static const uint undefDirective = KDevelop::IndexedString("undef").index();
|
|
static const uint endifDirective = KDevelop::IndexedString("endif").index();
|
|
static const uint ifndefDirective = KDevelop::IndexedString("ifndef").index();
|
|
static const uint defineDirective = KDevelop::IndexedString("define").index();
|
|
static const uint includeDirective = KDevelop::IndexedString("include").index();
|
|
static const uint includeNextDirective = KDevelop::IndexedString("include_next").index();
|
|
|
|
skip_blanks (input, output);
|
|
while (!input.atEnd() && input != '\n' && input == '/' && input.peekNextCharacter() == '*')
|
|
{
|
|
skip_comment_or_divop(input, output);
|
|
skip_blanks (input, output);
|
|
}
|
|
|
|
if(!(directive == ifndefDirective)) {
|
|
hadGuardCandidate = true; //Too late, the guard must be the first directive
|
|
}
|
|
if(checkGuardEnd) {
|
|
guardCandidate = KDevelop::IndexedString();
|
|
checkGuardEnd = false;
|
|
}
|
|
|
|
if(directive == defineDirective)
|
|
if (! skipping ())
|
|
return handle_define(input);
|
|
|
|
if(directive == includeDirective || directive == includeNextDirective)
|
|
if (! skipping ())
|
|
return handle_include (directive == includeNextDirective, input, output);
|
|
|
|
if(directive == undefDirective)
|
|
if (! skipping ())
|
|
return handle_undef(input);
|
|
|
|
if(directive == elifDirective)
|
|
return handle_elif(input);
|
|
|
|
if(directive == elseDirective)
|
|
return handle_else(input.inputPosition().line);
|
|
|
|
if(directive == endifDirective)
|
|
return handle_endif(input, output);
|
|
|
|
if(directive == ifDirective)
|
|
return handle_if(input);
|
|
|
|
if(directive == ifdefDirective)
|
|
return handle_ifdef(false, input);
|
|
|
|
if(directive == ifndefDirective)
|
|
return handle_ifdef(true, input);
|
|
}
|
|
|
|
void pp::handle_include(bool skip_current_path, Stream& input, Stream& output)
|
|
{
|
|
if (input.atEnd()) {
|
|
createProblem(input, i18n("invalid include directive"));
|
|
return;
|
|
}
|
|
QByteArray bytes = KDevelop::IndexedString::fromIndex(input.current()).byteArray();
|
|
if (bytes.size() > 0 && (isLetter(bytes.at(0)) || bytes.at(0) == '_')) {
|
|
pp_macro_expander expand_include(this);
|
|
|
|
Anchor inputPosition = input.inputPosition();
|
|
KDevelop::CursorInRevision originalInputPosition = input.originalInputPosition();
|
|
PreprocessedContents includeString;
|
|
{
|
|
Stream cs(&includeString);
|
|
expand_include(input, cs);
|
|
}
|
|
|
|
skip_blanks(input, devnull());
|
|
RETURN_ON_FAIL(!includeString.isEmpty() && (includeString.first() == indexFromCharacter('<') || includeString.first() == indexFromCharacter('"')));
|
|
|
|
// Filter out whitespaces
|
|
PreprocessedContents filteredIncludeString;
|
|
if(!includeString.empty() && includeString.front() == indexFromCharacter('"'))
|
|
{
|
|
// Don't filter if it is a string token
|
|
filteredIncludeString = includeString;
|
|
}
|
|
else
|
|
{
|
|
// Filter out whitespaces, as the preprocessor adds them in random places to prevent implicit token merging
|
|
foreach(uint index, includeString)
|
|
if(index != indexFromCharacter(' '))
|
|
filteredIncludeString.push_back(index);
|
|
}
|
|
|
|
Stream newInput(&filteredIncludeString, inputPosition);
|
|
newInput.setOriginalInputPosition(originalInputPosition);
|
|
handle_include(skip_current_path, newInput, output);
|
|
return;
|
|
}
|
|
|
|
RETURN_ON_FAIL(input == '<' || input == '"');
|
|
char quote((input == '"') ? '"' : '>');
|
|
++input;
|
|
|
|
PreprocessedContents includeNameB;
|
|
|
|
while (!input.atEnd() && input != quote) {
|
|
RETURN_ON_FAIL(input != '\n');
|
|
|
|
includeNameB.append(input);
|
|
++input;
|
|
}
|
|
|
|
QString includeName(QString::fromUtf8(stringFromContents(includeNameB)));
|
|
|
|
Stream* include = m_preprocessor->sourceNeeded(includeName, quote == '"' ? Preprocessor::IncludeLocal : Preprocessor::IncludeGlobal, input.inputPosition().line, skip_current_path);
|
|
Q_ASSERT(!include);
|
|
|
|
/*if (include && !include->atEnd()) {
|
|
m_files.push(includeName);
|
|
|
|
output.mark(includeName, 0);
|
|
|
|
operator()(*include, output);
|
|
|
|
// restore the file name and sync the buffer
|
|
output.mark(m_files.pop(), input.inputPosition().line);
|
|
}*/
|
|
|
|
delete include;
|
|
}
|
|
|
|
void pp::operator () (Stream& input, Stream& output)
|
|
{
|
|
int previousIfLevel = iflevel;
|
|
|
|
forever
|
|
{
|
|
haveNextToken = false;
|
|
|
|
if (skipping()) {
|
|
skip_blanks(input, devnull());
|
|
|
|
} else {
|
|
skip_blanks(input, output);
|
|
}
|
|
|
|
if (input.atEnd()) {
|
|
break;
|
|
|
|
} else if (input == '#') {
|
|
skip_blanks(++input, devnull());
|
|
|
|
uint directive = skip_identifier(input);
|
|
|
|
skip_blanks(input, devnull());
|
|
|
|
Anchor inputPosition = input.inputPosition();
|
|
KDevelop::CursorInRevision originalInputPosition = input.originalInputPosition();
|
|
|
|
PreprocessedContents skipped;
|
|
{
|
|
Stream ss(&skipped);
|
|
skip (input, ss);
|
|
}
|
|
|
|
Stream ss(&skipped, inputPosition);
|
|
ss.setOriginalInputPosition(originalInputPosition);
|
|
handle_directive(directive, ss, output);
|
|
|
|
} else if (input == '\n') {
|
|
output << input;
|
|
++input;
|
|
|
|
} else if (skipping ()) {
|
|
skip (input, devnull());
|
|
|
|
} else {
|
|
output.mark(input.inputPosition());
|
|
if(checkGuardEnd) {
|
|
expand.startSignificantContentSearch();
|
|
}
|
|
|
|
expand (input, output);
|
|
if(checkGuardEnd) {
|
|
if(expand.foundSignificantContent() || !input.atEnd()) {
|
|
guardCandidate = KDevelop::IndexedString();
|
|
}
|
|
checkGuardEnd = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!guardCandidate.isEmpty())
|
|
preprocessor()->foundHeaderGuard(input, guardCandidate);
|
|
|
|
if (iflevel != previousIfLevel && !input.skippedToEnd())
|
|
createProblem(input, i18n("Unterminated #if statement"));
|
|
}
|
|
|
|
void pp::createProblem(Stream& input, const QString& description) {
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 0)));
|
|
problem->setDescription(description);
|
|
problemEncountered(problem);
|
|
}
|
|
|
|
void pp::handle_define (Stream& input)
|
|
{
|
|
pp_macro* macro = new pp_macro;
|
|
macro->file = currentFileName();
|
|
macro->sourceLine = input.originalInputPosition().line;
|
|
|
|
skip_blanks (input, devnull());
|
|
macro->name = KDevelop::IndexedString::fromIndex(skip_identifier(input, true)); //@todo make macros utf8 too
|
|
|
|
if (!input.atEnd() && input == '(')
|
|
{
|
|
macro->function_like = true;
|
|
|
|
skip_blanks (++input, devnull()); // skip '('
|
|
uint formal = skip_identifier(input);
|
|
|
|
skip_blanks(input, devnull());
|
|
|
|
if (input == '.') {
|
|
macro->variadics = true;
|
|
|
|
do {
|
|
++input;
|
|
|
|
} while (input == '.');
|
|
}
|
|
if (formal && !macro->variadics)
|
|
macro->formalsList().append( KDevelop::IndexedString::fromIndex(formal) );
|
|
|
|
|
|
while (!input.atEnd() && input == ',')
|
|
{
|
|
skip_blanks(++input, devnull());
|
|
|
|
uint formal = skip_identifier(input);
|
|
|
|
skip_blanks (input, devnull());
|
|
|
|
if (input == '.') {
|
|
macro->variadics = true;
|
|
|
|
do {
|
|
++input;
|
|
|
|
} while (input == '.');
|
|
}
|
|
|
|
if (formal && !macro->variadics)
|
|
macro->formalsList().append( KDevelop::IndexedString::fromIndex(formal) );
|
|
}
|
|
|
|
RETURN_ON_FAIL(input == ')');
|
|
++input;
|
|
}
|
|
|
|
skip_blanks (input, devnull());
|
|
|
|
while (!input.atEnd() && input != '\n')
|
|
{
|
|
if(input == '/' && (input.peekNextCharacter() == '/' || input.peekNextCharacter() == '*')) {
|
|
skip_comment_or_divop(input, devnull());
|
|
if(!input.atEnd() && input != '\n')
|
|
skip_blanks (input, devnull());
|
|
continue;
|
|
}
|
|
if (input == '\\')
|
|
{
|
|
int pos = input.offset();
|
|
skip_blanks (++input, devnull());
|
|
|
|
if (!input.atEnd() && input == '\n')
|
|
{
|
|
skip_blanks(++input, devnull());
|
|
macro->definitionList().append(KDevelop::IndexedString::fromIndex(indexFromCharacter(' ')));
|
|
continue;
|
|
|
|
} else {
|
|
// Whoops, rewind :)
|
|
input.seek(pos);
|
|
}
|
|
}
|
|
if (input == '"')
|
|
{
|
|
do {
|
|
if (input == '\\' && input.peekNextCharacter() == '"') {
|
|
// skip escaped close quote
|
|
macro->definitionList().append(KDevelop::IndexedString::fromIndex(input.current()));
|
|
++input;
|
|
if(input.atEnd())
|
|
break;
|
|
}
|
|
macro->definitionList().append(KDevelop::IndexedString::fromIndex(input.current()));
|
|
++input;
|
|
} while (!input.atEnd() && input != '"' && input != '\n');
|
|
|
|
if(!input.atEnd())
|
|
{
|
|
macro->definitionList().append(KDevelop::IndexedString::fromIndex(input.current()));
|
|
++input;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
macro->definitionList().append(KDevelop::IndexedString::fromIndex(input.current()));
|
|
++input;
|
|
}
|
|
|
|
m_environment->setMacro(macro);
|
|
}
|
|
|
|
|
|
void pp::skip (Stream& input, Stream& output, bool outputText)
|
|
{
|
|
pp_skip_string_literal skip_string_literal;
|
|
pp_skip_char_literal skip_char_literal;
|
|
|
|
while (!input.atEnd() && input != '\n')
|
|
{
|
|
if (input == '/')
|
|
{
|
|
skip_comment_or_divop (input, output, outputText);
|
|
}
|
|
else if (input == '"')
|
|
{
|
|
skip_string_literal (input, output);
|
|
}
|
|
else if (input == '\'')
|
|
{
|
|
skip_char_literal (input, output);
|
|
}
|
|
else if (input == '\\')
|
|
{
|
|
output << input;
|
|
skip_blanks (++input, output);
|
|
|
|
if (!input.atEnd() && input == '\n')
|
|
{
|
|
output << input;
|
|
++input;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
output << input;
|
|
++input;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline bool pp::test_if_level()
|
|
{
|
|
bool result = !_M_skipping[iflevel++];
|
|
_M_skipping[iflevel] = _M_skipping[iflevel - 1];
|
|
_M_true_test[iflevel] = false;
|
|
return result;
|
|
}
|
|
|
|
inline int pp::skipping() const
|
|
{ return _M_skipping[iflevel]; }
|
|
|
|
Value pp::eval_primary(Stream& input)
|
|
{
|
|
int start = input.offset();
|
|
bool expect_paren = false;
|
|
int token = next_token_accept(input);
|
|
Value result;
|
|
|
|
switch (token) {
|
|
case TOKEN_NUMBER:
|
|
result.set_long(token_value);
|
|
break;
|
|
|
|
case TOKEN_UNUMBER:
|
|
result.set_ulong(token_uvalue);
|
|
break;
|
|
|
|
case TOKEN_DEFINED:
|
|
token = next_token_accept(input);
|
|
|
|
if (token == '(')
|
|
{
|
|
expect_paren = true;
|
|
token = next_token_accept(input);
|
|
}
|
|
|
|
if (token != TOKEN_IDENTIFIER)
|
|
{
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 1)));
|
|
QChar tk(token);
|
|
problem->setDescription(i18n("Expected \"identifier\", found: %1", (tk < TOKENS_END && tk > TOKENS_START) ? QString(tk) : i18n("character %1", token)));
|
|
problem->setExplanation(i18n("<h5>Token text</h5><pre>%1</pre><h5>Input</h5><pre>%2</pre>", token_text.str(), QString::fromUtf8(input.stringFrom(start))));
|
|
problemEncountered(problem);
|
|
break;
|
|
}
|
|
|
|
{
|
|
pp_macro* m = m_environment->retrieveMacro(token_text, true);
|
|
result.set_long( (m && !m->isUndef()) ? 1 : 0);
|
|
}
|
|
|
|
token = next_token(input); // skip '('
|
|
|
|
if (expect_paren) {
|
|
if (token != ')') {
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 0)));
|
|
QChar tk(token);
|
|
problem->setDescription(i18n("Expected \")\", found %1", tk.isLetterOrNumber() ? QString(tk) : i18n("character %1", token)));
|
|
problem->setExplanation(i18n("<h5>Token text</h5><pre>%1</pre><h5>Input</h5><pre>%2</pre>", token_text.str(), QString::fromUtf8(input.stringFrom(start))));
|
|
problemEncountered(problem);
|
|
} else {
|
|
accept_token();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TOKEN_IDENTIFIER:
|
|
break;
|
|
|
|
case '-':
|
|
result.set_long(- eval_primary(input).l);
|
|
break;
|
|
|
|
case '+':
|
|
result.set_long(+ eval_primary(input).l);
|
|
break;
|
|
|
|
case '!':
|
|
result.set_long(eval_primary(input).is_zero());
|
|
break;
|
|
|
|
case '~':
|
|
result.set_long(~ eval_primary(input).l);
|
|
break;
|
|
|
|
case '(':
|
|
result = eval_constant_expression(input);
|
|
token = next_token(input);
|
|
|
|
if (token != ')') {
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 1)));
|
|
QChar tk(token);
|
|
problem->setDescription(i18n("Expected \")\", found %1", tk.isLetterOrNumber() ? QString(tk) : i18n("character %1", token)));
|
|
problem->setExplanation(i18n("<h5>Token text</h5><pre>%1</pre><h5>Input</h5><pre>%2</pre>", token_text.str(), QString::fromUtf8(input.stringFrom(start))));
|
|
problemEncountered(problem);
|
|
} else {
|
|
accept_token();
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Value pp::eval_multiplicative(Stream& input)
|
|
{
|
|
int start = input.offset();
|
|
|
|
Value result = eval_primary(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
while (token == '*' || token == '/' || token == '%') {
|
|
accept_token();
|
|
|
|
Value value = eval_primary(input);
|
|
|
|
if (token == '*') {
|
|
result *= value;
|
|
|
|
} else if (token == '/') {
|
|
if (value.is_zero() || (!value.is_ulong() && !result.is_ulong() && value.l == -1 && result.l == LLONG_MIN)) {
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 0)));
|
|
problem->setDescription(i18n("Division by zero"));
|
|
problem->setDescription(i18n("Input text: %1", QString::fromUtf8(input.stringFrom(start))));
|
|
problemEncountered(problem);
|
|
result.set_long(0);
|
|
|
|
} else {
|
|
result /= value;
|
|
}
|
|
|
|
} else {
|
|
if (value.is_zero() || (!value.is_ulong() && !result.is_ulong() && value.l == -1 && result.l == LLONG_MIN)) {
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 0)));
|
|
problem->setDescription(i18n("Division by zero"));
|
|
problem->setDescription(i18n("Input text: %1", QString::fromUtf8(input.stringFrom(start))));
|
|
problemEncountered(problem);
|
|
result.set_long(0);
|
|
|
|
} else {
|
|
result %= value;
|
|
}
|
|
}
|
|
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Value pp::eval_additive(Stream& input)
|
|
{
|
|
Value result = eval_multiplicative(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
while (token == '+' || token == '-') {
|
|
accept_token();
|
|
|
|
Value value = eval_multiplicative(input);
|
|
|
|
if (token == '+')
|
|
result += value;
|
|
else
|
|
result -= value;
|
|
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_shift(Stream& input)
|
|
{
|
|
Value result = eval_additive(input);
|
|
|
|
int token;
|
|
token = next_token(input);
|
|
|
|
while (token == TOKEN_LT_LT || token == TOKEN_GT_GT) {
|
|
accept_token();
|
|
|
|
Value value = eval_additive(input);
|
|
|
|
if (token == TOKEN_LT_LT)
|
|
result <<= value;
|
|
else
|
|
result >>= value;
|
|
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_relational(Stream& input)
|
|
{
|
|
Value result = eval_shift(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
while (token == '<'
|
|
|| token == '>'
|
|
|| token == TOKEN_LT_EQ
|
|
|| token == TOKEN_GT_EQ)
|
|
{
|
|
accept_token();
|
|
Value value = eval_shift(input);
|
|
|
|
switch (token)
|
|
{
|
|
default:
|
|
Q_ASSERT(0);
|
|
break;
|
|
|
|
case '<':
|
|
result = result < value;
|
|
break;
|
|
|
|
case '>':
|
|
result = result > value;
|
|
break;
|
|
|
|
case TOKEN_LT_EQ:
|
|
result = result <= value;
|
|
break;
|
|
|
|
case TOKEN_GT_EQ:
|
|
result = result >= value;
|
|
break;
|
|
}
|
|
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_equality(Stream& input)
|
|
{
|
|
Value result = eval_relational(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
while (token == TOKEN_EQ_EQ || token == TOKEN_NOT_EQ) {
|
|
accept_token();
|
|
Value value = eval_relational(input);
|
|
|
|
if (token == TOKEN_EQ_EQ)
|
|
result = result == value;
|
|
else
|
|
result = result != value;
|
|
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_and(Stream& input)
|
|
{
|
|
Value result = eval_equality(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
while (token == '&') {
|
|
accept_token();
|
|
Value value = eval_equality(input);
|
|
result &= value;
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_xor(Stream& input)
|
|
{
|
|
Value result = eval_and(input);
|
|
|
|
int token;
|
|
token = next_token(input);
|
|
|
|
while (token == '^') {
|
|
accept_token();
|
|
Value value = eval_and(input);
|
|
result ^= value;
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_or(Stream& input)
|
|
{
|
|
Value result = eval_xor(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
while (token == '|') {
|
|
accept_token();
|
|
Value value = eval_xor(input);
|
|
result |= value;
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_logical_and(Stream& input)
|
|
{
|
|
Value result = eval_or(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
while (token == TOKEN_AND_AND) {
|
|
accept_token();
|
|
Value value = eval_or(input);
|
|
result = result && value;
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_logical_or(Stream& input)
|
|
{
|
|
Value result = eval_logical_and(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
while (token == TOKEN_OR_OR) {
|
|
accept_token();
|
|
Value value = eval_logical_and(input);
|
|
result = result || value;
|
|
token = next_token(input);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_constant_expression(Stream& input)
|
|
{
|
|
Value result = eval_logical_or(input);
|
|
|
|
int token = next_token(input);
|
|
|
|
if (token == '?')
|
|
{
|
|
accept_token();
|
|
Value left_value = eval_constant_expression(input);
|
|
skip_blanks(input, devnull());
|
|
|
|
token = next_token_accept(input);
|
|
if (token == ':')
|
|
{
|
|
Value right_value = eval_constant_expression(input);
|
|
|
|
result = !result.is_zero() ? left_value : right_value;
|
|
}
|
|
else
|
|
{
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 1)));
|
|
problem->setDescription(i18n("expected ``:'' = %1", int(token)));
|
|
problemEncountered(problem);
|
|
result = left_value;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
Value pp::eval_expression(Stream& input)
|
|
{
|
|
skip_blanks(input, devnull());
|
|
return eval_constant_expression(input);
|
|
}
|
|
|
|
|
|
void pp::handle_if (Stream& input)
|
|
{
|
|
if (test_if_level())
|
|
{
|
|
pp_macro_expander expand_condition(this);
|
|
skip_blanks(input, devnull());
|
|
|
|
Anchor inputPosition = input.inputPosition();
|
|
KDevelop::CursorInRevision originalInputPosition = input.originalInputPosition();
|
|
PreprocessedContents condition;
|
|
{
|
|
Stream cs(&condition);
|
|
expand_condition(input, cs);
|
|
}
|
|
|
|
Stream cs(&condition, inputPosition);
|
|
cs.setOriginalInputPosition(originalInputPosition);
|
|
Value result = eval_expression(cs);
|
|
|
|
_M_true_test[iflevel] = !result.is_zero();
|
|
_M_skipping[iflevel] = result.is_zero();
|
|
|
|
} else {
|
|
// Capture info for precompiled macros
|
|
pp_macro_expander expand_condition(this);
|
|
skip_blanks(input, devnull());
|
|
PreprocessedContents condition;
|
|
{
|
|
Stream cs(&condition);
|
|
expand_condition(input, cs);
|
|
}
|
|
|
|
_M_true_test[iflevel] = true;
|
|
_M_skipping[iflevel] = true;
|
|
}
|
|
}
|
|
|
|
|
|
void pp::handle_else(int sourceLine)
|
|
{
|
|
if(iflevel == 1)
|
|
guardCandidate = KDevelop::IndexedString();
|
|
|
|
if (iflevel == 0 && !skipping ())
|
|
{
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(KDevelop::SimpleCursor(sourceLine, 0), 0)));
|
|
problem->setDescription(i18n("#else without #if"));
|
|
problemEncountered(problem);
|
|
}
|
|
else if (iflevel > 0 && _M_skipping[iflevel - 1])
|
|
{
|
|
_M_skipping[iflevel] = true;
|
|
}
|
|
else
|
|
{
|
|
_M_skipping[iflevel] = _M_true_test[iflevel];
|
|
}
|
|
}
|
|
|
|
|
|
void pp::handle_elif(Stream& input)
|
|
{
|
|
if(iflevel == 1)
|
|
guardCandidate = KDevelop::IndexedString();
|
|
|
|
RETURN_ON_FAIL(iflevel > 0);
|
|
|
|
if (iflevel == 0 && !skipping())
|
|
{
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 0)));
|
|
problem->setDescription(i18n("#else without #if"));
|
|
problemEncountered(problem);
|
|
}
|
|
else
|
|
{
|
|
pp_macro_expander expand_condition(this);
|
|
skip_blanks(input, devnull());
|
|
|
|
Anchor inputPosition = input.inputPosition();
|
|
KDevelop::CursorInRevision originalInputPosition = input.originalInputPosition();
|
|
PreprocessedContents condition;
|
|
{
|
|
Stream cs(&condition);
|
|
cs.setOriginalInputPosition(originalInputPosition);
|
|
expand_condition(input, cs);
|
|
}
|
|
|
|
if (!_M_true_test[iflevel] && !_M_skipping[iflevel - 1])
|
|
{
|
|
Stream cs(&condition, inputPosition);
|
|
Value result = eval_expression(cs);
|
|
_M_true_test[iflevel] = !result.is_zero();
|
|
_M_skipping[iflevel] = result.is_zero();
|
|
}
|
|
else
|
|
{
|
|
_M_skipping[iflevel] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void pp::handle_endif(Stream& input, Stream& output)
|
|
{
|
|
if (iflevel == 0 && !skipping())
|
|
{
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(input.originalInputPosition().castToSimpleCursor(), 0)));
|
|
problem->setDescription(i18n("#endif without #if at output line %1", m_environment->locationTable()->anchorForOffset(output.offset()).anchor.line));
|
|
problemEncountered(problem);
|
|
}
|
|
else
|
|
{
|
|
_M_skipping[iflevel] = 0;
|
|
_M_true_test[iflevel] = 0;
|
|
|
|
--iflevel;
|
|
if(iflevel == 0) {
|
|
if(!guardCandidate.isEmpty()) {
|
|
checkGuardEnd = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint pp::branchingHash() const
|
|
{
|
|
uint hash = 0;
|
|
for( int a = 0; a <= iflevel; a++ ) {
|
|
hash *= 19;
|
|
if( _M_skipping[a] )
|
|
hash += 3;
|
|
if( _M_true_test[a] )
|
|
hash += 7;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
void pp::handle_ifdef (bool check_undefined, Stream& input)
|
|
{
|
|
KDevelop::IndexedString macro_name = KDevelop::IndexedString::fromIndex(skip_identifier(input));
|
|
///@todo eventually fix the block description
|
|
if(check_undefined && expand.in_header_section() && guardCandidate.isEmpty() && !hadGuardCandidate && iflevel == 0) {
|
|
//It's the first #ifndef and the header-section hasn't ended yet, assume it to be the header-guard
|
|
guardCandidate = macro_name;
|
|
}
|
|
|
|
hadGuardCandidate = true;
|
|
|
|
if (test_if_level())
|
|
{
|
|
pp_macro* macro = m_environment->retrieveMacro(macro_name, true);
|
|
bool value = false;
|
|
if( macro && macro->defined )
|
|
value = true;
|
|
|
|
if (check_undefined)
|
|
value = !value;
|
|
|
|
_M_true_test[iflevel] = value;
|
|
_M_skipping[iflevel] = !value;
|
|
}
|
|
}
|
|
|
|
|
|
void pp::handle_undef(Stream& input)
|
|
{
|
|
skip_blanks (input, devnull());
|
|
|
|
KDevelop::IndexedString macro_name = KDevelop::IndexedString::fromIndex(skip_identifier(input));
|
|
RETURN_ON_FAIL(!macro_name.isEmpty());
|
|
|
|
pp_macro* macro = new pp_macro;
|
|
macro->file = currentFileName();
|
|
macro->name = macro_name;
|
|
macro->sourceLine = input.originalInputPosition().line;
|
|
|
|
macro->defined = false;
|
|
|
|
m_environment->setMacro(macro);
|
|
}
|
|
|
|
int pp::next_token (Stream& input)
|
|
{
|
|
if (haveNextToken)
|
|
return nextToken;
|
|
|
|
skip_blanks(input, devnull());
|
|
|
|
if (input.atEnd())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
char ch = 0;
|
|
if(isCharacter(input.current()))
|
|
ch = characterFromIndex(input.current());
|
|
char ch2 = input.peekNextCharacter();
|
|
|
|
nextToken = 0;
|
|
|
|
switch (ch) {
|
|
case '/':
|
|
if (ch2 == '/' || ch2 == '*')
|
|
{
|
|
skip_comment_or_divop(input, devnull());
|
|
return next_token(input);
|
|
}
|
|
++input;
|
|
nextToken = '/';
|
|
break;
|
|
|
|
case '<':
|
|
++input;
|
|
if (ch2 == '<')
|
|
{
|
|
++input;
|
|
nextToken = TOKEN_LT_LT;
|
|
}
|
|
else if (ch2 == '=')
|
|
{
|
|
++input;
|
|
nextToken = TOKEN_LT_EQ;
|
|
}
|
|
else
|
|
nextToken = '<';
|
|
|
|
break;
|
|
|
|
case '>':
|
|
++input;
|
|
if (ch2 == '>')
|
|
{
|
|
++input;
|
|
nextToken = TOKEN_GT_GT;
|
|
}
|
|
else if (ch2 == '=')
|
|
{
|
|
++input;
|
|
nextToken = TOKEN_GT_EQ;
|
|
}
|
|
else
|
|
nextToken = '>';
|
|
|
|
break;
|
|
|
|
case '!':
|
|
++input;
|
|
if (ch2 == '=')
|
|
{
|
|
++input;
|
|
nextToken = TOKEN_NOT_EQ;
|
|
}
|
|
else
|
|
nextToken = '!';
|
|
|
|
break;
|
|
|
|
case '=':
|
|
++input;
|
|
if (ch2 == '=')
|
|
{
|
|
++input;
|
|
nextToken = TOKEN_EQ_EQ;
|
|
}
|
|
else
|
|
nextToken = '=';
|
|
|
|
break;
|
|
|
|
case '|':
|
|
++input;
|
|
if (ch2 == '|')
|
|
{
|
|
++input;
|
|
nextToken = TOKEN_OR_OR;
|
|
}
|
|
else
|
|
nextToken = '|';
|
|
|
|
break;
|
|
|
|
case '&':
|
|
++input;
|
|
if (ch2 == '&')
|
|
{
|
|
++input;
|
|
nextToken = TOKEN_AND_AND;
|
|
}
|
|
else
|
|
nextToken = '&';
|
|
|
|
break;
|
|
|
|
default:
|
|
if (isLetter(ch) || ch == '_' || !isCharacter(input.current()))
|
|
{
|
|
token_text = KDevelop::IndexedString::fromIndex( skip_identifier (input) );
|
|
static const KDevelop::IndexedString definedText("defined");
|
|
if (token_text == definedText)
|
|
nextToken = TOKEN_DEFINED;
|
|
else
|
|
nextToken = TOKEN_IDENTIFIER;
|
|
}
|
|
else if (isNumber(ch))
|
|
{
|
|
KDevelop::SimpleCursor numericBeginPosition = input.inputPosition().castToSimpleCursor();
|
|
|
|
PreprocessedContents byteNumber;
|
|
{
|
|
Stream ns(&byteNumber);
|
|
skip_number(input, ns);
|
|
}
|
|
|
|
KDevelop::SimpleCursor numericEndPosition = input.inputPosition().castToSimpleCursor();
|
|
|
|
QString number(QString::fromUtf8(stringFromContents(byteNumber)));
|
|
int base = 10;
|
|
if (number.startsWith("0x")) {
|
|
base = 16;
|
|
} else if (number.startsWith('0')) {
|
|
base = 8;
|
|
}
|
|
|
|
uint countUnsigned = 0, countLong = 0, countLongLong = 0;
|
|
while (true) {
|
|
if (number.endsWith("u", Qt::CaseInsensitive)) {
|
|
++countUnsigned;
|
|
number.chop(1);
|
|
} else if (number.endsWith("ll", Qt::CaseInsensitive)) { // "ll" before "l"
|
|
++countLongLong;
|
|
number.chop(2);
|
|
} else if (number.endsWith("l", Qt::CaseInsensitive)) {
|
|
++countLong;
|
|
number.chop (1);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((countUnsigned > 1) || ((countLong + countLongLong) > 1)) {
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(numericBeginPosition, numericEndPosition)));
|
|
problem->setDescription(i18n("Invalid suffix combination"));
|
|
problemEncountered(problem);
|
|
}
|
|
|
|
bool parsedOk = 0;
|
|
if (countUnsigned) {
|
|
token_uvalue = number.toULongLong (&parsedOk, base);
|
|
nextToken = TOKEN_UNUMBER;
|
|
} else {
|
|
token_value = number.toLongLong (&parsedOk, base);
|
|
nextToken = TOKEN_NUMBER;
|
|
}
|
|
|
|
if (!parsedOk) {
|
|
KDevelop::ProblemPointer problem(new KDevelop::Problem);
|
|
problem->setFinalLocation(KDevelop::DocumentRange(currentFileName(), KDevelop::SimpleRange(numericBeginPosition, numericEndPosition)));
|
|
problem->setDescription(i18n("Invalid numeric value"));
|
|
problemEncountered(problem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(isCharacter(input.current()))
|
|
nextToken = characterFromIndex(input.current());
|
|
else
|
|
nextToken = TOKEN_IDENTIFIER;
|
|
++input;
|
|
}
|
|
}
|
|
|
|
//kDebug(9007) << "Next token '" << ch << ch2 << "'" << nextToken << "txt" << token_text << "val" << token_value;
|
|
|
|
haveNextToken = true;
|
|
return nextToken;
|
|
}
|
|
|
|
int pp::next_token_accept (Stream& input)
|
|
{
|
|
int result = next_token(input);
|
|
accept_token();
|
|
return result;
|
|
}
|
|
|
|
void pp::accept_token()
|
|
{
|
|
haveNextToken = false;
|
|
nextToken = 0;
|
|
}
|
|
|
|
bool pp::hideNextMacro( ) const
|
|
{
|
|
return hideNext;
|
|
}
|
|
|
|
void pp::setHideNextMacro( bool h )
|
|
{
|
|
hideNext = h;
|
|
}
|
|
|
|
Environment* pp::environment( ) const
|
|
{
|
|
return m_environment;
|
|
}
|
|
|
|
void pp::setEnvironment(Environment* env)
|
|
{
|
|
delete m_environment;
|
|
m_environment = env;
|
|
}
|
|
|
|
const QList< KDevelop::ProblemPointer > & rpp::pp::problems() const
|
|
{
|
|
return m_problems;
|
|
}
|
|
|
|
void rpp::pp::problemEncountered(const KDevelop::ProblemPointer & problem)
|
|
{
|
|
m_problems.append(problem);
|
|
}
|