/**************************************************************************** ** ** Copyright (C) 2011 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 #include #include #include 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 ¤tFile, 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 being able to emit from const function" PreprocessorCache *cache = const_cast(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 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 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, 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 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