tools/porting/src/preprocessorcontrol.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/porting/src/preprocessorcontrol.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,430 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the qt3to4 porting application of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#include "preprocessorcontrol.h"
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <QTemporaryFile>
+
+QT_BEGIN_NAMESPACE
+using namespace TokenEngine;
+using namespace Rpp;
+
+IncludeFiles::IncludeFiles(const QString &basePath, const QStringList &searchPaths)
+:m_basePath(basePath)
+{
+    //prepend basePath to all relative paths in searchPaths
+    foreach (QString path, searchPaths) {
+        QString finalPath;
+        if (QDir::isAbsolutePath(path))
+            finalPath = QDir::cleanPath(path);
+        else
+            finalPath = QDir::cleanPath(m_basePath + QLatin1String("/") + path);
+
+        if(QFile::exists(finalPath))
+            m_searchPaths.append(finalPath);
+    }
+}
+
+/*
+    Performs an #include "..." style file lookup.
+    Aboslute filenames are checked directly. Relative filenames are first
+    looked for relative to the current file path, then the includepaths
+    are searched if not found.
+*/
+QString IncludeFiles::quoteLookup(const QString &currentFile,
+                                  const QString &includeFile) const
+{
+    //if includeFile is absolute, check if it exists
+    if (QDir::isAbsolutePath(includeFile)) {
+        if(QFile::exists(includeFile))
+            return includeFile;
+        else
+            return QString();
+    }
+
+    //If currentFile is not an absolute file path, make it one by
+    //prepending m_baspath
+    QString currentFilePath;
+    if(QDir::isAbsolutePath(currentFile))
+        currentFilePath = currentFile;
+    else
+        currentFilePath = QDir::cleanPath(m_basePath + QLatin1String("/") + currentFile);
+
+    //Check if it includeFile exists in the same dir as currentFilePath
+    const QString currentPath = QFileInfo(currentFilePath).path();
+    QString localFile = QDir::cleanPath(currentPath + QLatin1String("/") + includeFile);
+    if(QFile::exists(localFile))
+        return localFile;
+
+    return searchIncludePaths(includeFile);
+}
+
+/*
+    Performs an #include <...> style file lookup.
+    Aboslute filenames are checked directly.
+    Relative paths are searched for in the includepaths.
+*/
+QString IncludeFiles::angleBracketLookup(const QString &includeFile) const
+{
+    //if includeFile is absolute, check if it exists
+    if (QDir::isAbsolutePath(includeFile)) {
+        if(QFile::exists(includeFile))
+            return includeFile;
+        else
+            return QString();
+    }
+
+    return searchIncludePaths(includeFile);
+}
+
+QString IncludeFiles::resolve(const QString &filename) const
+{
+    if(QDir::isAbsolutePath(filename))
+        return filename;
+
+    QString prepended = QDir::cleanPath(m_basePath + QLatin1String("/") + filename);
+    if(QFile::exists(prepended))
+        return prepended;
+    else
+        return QString();
+}
+
+
+/*
+    Searches for includeFile paths by appending it to all includePaths
+    and checking if the file exists. Returns QString() if the file is not
+    found.
+*/
+QString IncludeFiles::searchIncludePaths(const QString &includeFile) const
+{
+    QString foundFile;
+    foreach(QString includePath, m_searchPaths) {
+        QString testFile = includePath + QLatin1String("/") + includeFile;
+        if(QFile::exists(testFile)){
+            foundFile = testFile;
+            break;
+        }
+    }
+    return foundFile;
+}
+
+QByteArray PreprocessorCache::readFile(const QString &filename) const
+{
+    // If anybody is connected to the readFile signal we tell them to
+    // read the file for us.
+    if (receivers(SIGNAL(readFile(QByteArray&,QString))) > 0) {
+        QByteArray array;
+        // Workaround for "not beeing able to emit from const function"
+        PreprocessorCache *cache = const_cast<PreprocessorCache *>(this);
+        emit cache->readFile(array, filename);
+        return array;
+    }
+
+    QFile f(filename);
+    if (!f.exists())
+        return QByteArray();
+    f.open(QIODevice::ReadOnly);
+    if (!f.isOpen())
+        return QByteArray();
+    return f.readAll();
+}
+
+PreprocessorCache::PreprocessorCache()
+{
+    connect(&m_preprocessor, SIGNAL(error(QString,QString)),
+            this, SIGNAL(error(QString,QString)));
+}
+
+
+/*
+    Return a TokenSequence with the contents of filname.
+    Assumens filename exists and is readable, returns a empty
+    TokenSequence if not.
+
+    The result is cached.
+*/
+TokenContainer PreprocessorCache::sourceTokens(const QString &filename)
+{
+    // Check if the source tokens are already in the cache.
+    if(m_sourceTokens.contains(filename))
+        return m_sourceTokens.value(filename);
+
+    // Read and tokenize file.
+    QByteArray fileContents = readFile(filename);
+    if(fileContents == QByteArray())
+        return TokenContainer();
+
+    QVector<TokenEngine::Token> tokenList = m_tokenizer.tokenize(fileContents);
+
+    // Create a FileInfo object that holds the filename for this container.
+    FileInfo *containterFileInfo = new FileInfo;
+    containterFileInfo->filename = filename;
+
+    // Create container.
+    TokenContainer tokenContainer(fileContents, tokenList, containterFileInfo);
+
+    // Insert into cache.
+    m_sourceTokens.insert(filename, tokenContainer);
+    return tokenContainer;
+}
+
+/*
+    Return a Source* tree representing the contents of filename.
+    Assumens filename exists and is readable, returns a empty
+    Source object if not.
+
+    The result is cached.
+*/
+Source *PreprocessorCache::sourceTree(const QString &filename)
+{
+    // Check if the Rpp tree for this file is already in the cache.
+    if(m_sourceTrees.contains(filename))
+        return m_sourceTrees.value(filename);
+
+    // Get the tokens for the contents of this file.
+    TokenContainer tokenContainer = sourceTokens(filename);
+
+    // Run lexer and the preprocessor-parser.
+    QVector<Type> tokenTypes = m_lexer.lex(tokenContainer);
+    Source *source = m_preprocessor.parse(tokenContainer, tokenTypes, &m_memoryPool);
+    source->setFileName(filename);
+
+    // Insert into cache.
+    if(tokenContainer.count() > 0) //don't cache empty files.
+        m_sourceTrees.insert(filename, source);
+
+    return source;
+}
+
+
+/*
+    Returns whether the cache contains a TokenContainer for the given filename.
+*/
+bool PreprocessorCache::containsSourceTokens(const QString &filename)
+{
+    return m_sourceTokens.contains(filename);
+}
+
+/*
+    Returns whether the cache contains a Preprocessor tree for the given filename.
+*/
+bool PreprocessorCache::containsSourceTree(const QString &filename)
+{
+    return m_sourceTrees.contains(filename);
+}
+
+PreprocessorController::PreprocessorController(IncludeFiles includeFiles,
+        PreprocessorCache &preprocessorCache,
+        QStringList preLoadFilesFilenames)
+:m_includeFiles(includeFiles),
+ m_preprocessorCache(preprocessorCache)
+ {
+    // Load qt3 headers from resources. The headers are stored as
+    // QHash<QString, QByteArray>, serialized using QDataStream. The hash
+    // maps filename -> contents.
+    if (preLoadFilesFilenames != QStringList()) {
+        foreach (QString filename,  preLoadFilesFilenames) {
+            QFile f(filename);
+            if (f.open(QIODevice::ReadOnly)) {
+                QByteArray buffer = f.readAll();
+                f.close();
+                QDataStream stream(buffer);
+                QHash<QString, QByteArray> files;
+                stream >> files;
+                m_preLoadFiles.unite(files);
+            }
+        }
+    }
+    
+    //connect include callback
+    connect(&m_rppTreeEvaluator,
+        SIGNAL(includeCallback(Rpp::Source *&, const Rpp::Source *,
+        const QString &, Rpp::RppTreeEvaluator::IncludeType)),
+        SLOT(includeSlot(Rpp::Source *&, const Rpp::Source *,
+        const QString &, Rpp::RppTreeEvaluator::IncludeType)));
+
+    // connect readFile callback
+    connect(&m_preprocessorCache, SIGNAL(readFile(QByteArray&,QString)),
+        SLOT(readFile(QByteArray&,QString)));
+
+    //connect error handlers
+    connect(&m_preprocessorCache , SIGNAL(error(QString,QString)),
+            this, SIGNAL(error(QString,QString)));
+}
+
+/*
+    Callback from RppTreeEvaluator, called when we evaluate an #include
+    directive. We do a filename lookup based on the type of include, and then ask
+    the cache to give us the source tree for that file.
+*/
+void PreprocessorController::includeSlot(Source *&includee,
+                                         const Source *includer,
+                                         const QString &filename,
+                                         RppTreeEvaluator::IncludeType includeType)
+{
+    QString newFilename;
+    if(includeType == RppTreeEvaluator::QuoteInclude)
+        newFilename = m_includeFiles.quoteLookup(includer->fileName(), filename);
+    else //AngleBracketInclude
+        newFilename = m_includeFiles.angleBracketLookup(filename);
+
+    if (QFile::exists(newFilename)) {
+        includee = m_preprocessorCache.sourceTree(newFilename);
+        return;
+    }
+
+    if (m_preLoadFiles.contains(filename)) {
+        includee = m_preprocessorCache.sourceTree(filename);
+        return;
+    }
+
+    includee = m_preprocessorCache.sourceTree(newFilename);
+    emit error(QLatin1String("Error"), QLatin1String("Could not find file ") + filename);
+}
+
+/*
+    Callback connected to preprocessorCache. Tries to load a file from
+    m_preLoadFiles before going to disk.
+*/
+void PreprocessorController::readFile(QByteArray &contents, QString filename)
+{
+    if (m_preLoadFiles.contains(filename)) {
+        contents = m_preLoadFiles.value(filename);
+        return;
+    }
+
+    QFile f(filename);
+    if (!f.exists())
+        return;
+    f.open(QIODevice::ReadOnly);
+    if (!f.isOpen())
+        return;
+    contents = f.readAll();
+}
+
+/*
+    Preprocess file give by filename. Filename is resloved agains the basepath
+    set in IncludeFiles.
+*/
+TokenSectionSequence PreprocessorController::evaluate(const QString &filename, Rpp::DefineMap *activedefinitions)
+{
+    QString resolvedFilename = m_includeFiles.resolve(filename);
+    if(!QFile::exists(resolvedFilename))
+        emit error(QLatin1String("Error"), QLatin1String("Could not find file: ") + filename);
+    Source *source  = m_preprocessorCache.sourceTree(resolvedFilename);
+
+    return m_rppTreeEvaluator.evaluate(source, activedefinitions);
+}
+
+QByteArray defaultDefines =
+    "#define __attribute__(a...)  \n \
+         #define __attribute__ \n \
+         #define __extension \n \
+         #define __extension__ \n \
+         #define __restrict \n \
+         #define __restrict__      \n \
+         #define __volatile         volatile\n \
+         #define __volatile__       volatile\n \
+         #define __inline             inline\n \
+         #define __inline__           inline\n \
+         #define __const               const\n \
+         #define __const__             const\n \
+         #define __asm               asm\n \
+         #define __asm__             asm\n \
+         #define __GNUC__                 2\n \
+         #define __GNUC_MINOR__          95\n  \
+         #define __cplusplus \n \
+         #define __linux__ \n";
+
+
+/*
+    Returns a DefineMap containing the above macro definitions. The DefineMap
+    will contain pointers to data stored in the provided cache object.
+*/
+Rpp::DefineMap *defaultMacros(PreprocessorCache &cache)
+{
+    DefineMap *defineMap = new DefineMap();
+    //write out default macros to a temp file
+    QTemporaryFile tempfile;
+    tempfile.open();
+    tempfile.write(defaultDefines);
+    tempfile.flush();
+
+    IncludeFiles *includeFiles = new IncludeFiles(QString(), QStringList());
+    PreprocessorController preprocessorController(*includeFiles, cache);
+    //evaluate default macro file.
+    preprocessorController.evaluate(tempfile.fileName(), defineMap);
+    delete includeFiles;
+    return defineMap;
+}
+
+void StandardOutErrorHandler::error(QString type, QString text)
+{
+    Q_UNUSED(type);
+    puts(qPrintable(text));
+}
+
+/*
+    RppPreprocessor is a convenience class that contains all the components
+    needed to preprocess files. Error messages are printed to standard out.
+*/
+RppPreprocessor::RppPreprocessor(QString basePath, QStringList includePaths, QStringList preLoadFilesFilenames)
+:m_includeFiles(basePath, includePaths)
+,m_activeDefinitions(defaultMacros(m_cache))
+,m_controller(m_includeFiles, m_cache, preLoadFilesFilenames)
+{
+    QObject::connect(&m_controller, SIGNAL(error(QString,QString)), &m_errorHandler, SLOT(error(QString,QString)));
+}
+
+RppPreprocessor::~RppPreprocessor()
+{
+    delete m_activeDefinitions;
+}
+
+TokenEngine::TokenSectionSequence RppPreprocessor::evaluate(const QString &filename)
+{
+    DefineMap defMap = *m_activeDefinitions;
+    return m_controller.evaluate(filename, &defMap);
+}
+
+QT_END_NAMESPACE