/****************************************************************************
**
** 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 tools applications 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$
**
****************************************************************************/

/*
  qscodeparser.cpp
*/

#include <qfile.h>
#include <qregexp.h>

#include "config.h"
#include "qscodeparser.h"
#include "text.h"
#include "tokenizer.h"
#include "tree.h"

QT_BEGIN_NAMESPACE

#define CONFIG_QUICK                    "quick"
#define CONFIG_REPLACES                 "replaces"

#define COMMAND_BRIEF                   Doc::alias( "brief")
#define COMMAND_CODE                    Doc::alias( "code")
#define COMMAND_ENDCODE                 Doc::alias( "endcode")
#define COMMAND_ENDQUICKCODE            Doc::alias( "endquickcode")
#define COMMAND_FILE                    Doc::alias( "file")
#define COMMAND_GROUP                   Doc::alias( "group")
#define COMMAND_MODULE                  Doc::alias( "module")
#define COMMAND_PAGE                    Doc::alias( "page")
#define COMMAND_QUICKCLASS              Doc::alias( "quickclass")
#define COMMAND_QUICKCODE               Doc::alias( "quickcode")
#define COMMAND_QUICKENUM               Doc::alias( "quickenum")
#define COMMAND_QUICKFN                 Doc::alias( "quickfn")
#define COMMAND_QUICKIFY                Doc::alias( "quickify")
#define COMMAND_QUICKPROPERTY           Doc::alias( "quickproperty")
#define COMMAND_PROTECTED               Doc::alias( "protected")
#define COMMAND_REPLACE                 Doc::alias( "replace")

static QString balancedParens = "(?:[^()]+|\\([^()]*\\))*";

QsCodeParser::QsCodeParser(Tree *cppTree)
    : cppTre(cppTree), qsTre(0), replaceRegExp("/(.+)/([^/]*)/")
{
}

QsCodeParser::~QsCodeParser()
{
}

void QsCodeParser::initializeParser(const Config& config)
{
    CppCodeParser::initializeParser(config);

    nodeTypeMap.insert(COMMAND_QUICKCLASS, Node::Class);
    nodeTypeMap.insert(COMMAND_QUICKENUM, Node::Enum);
    nodeTypeMap.insert(COMMAND_QUICKPROPERTY, Node::Property);
    nodeTypeMap.insert(COMMAND_QUICKFN, Node::Function);

    QString quickDotReplaces = CONFIG_QUICK + Config::dot + CONFIG_REPLACES;
    QStringList replaces = config.getStringList(quickDotReplaces);
    QStringList::ConstIterator r = replaces.begin();
    while (r != replaces.end()) {
	if (replaceRegExp.exactMatch(*r)) {
	    QRegExp before(replaceRegExp.cap(1));
	    before.setMinimal(true);
	    QString after = replaceRegExp.cap(2);

	    if (before.isValid()) {
		replaceBefores << before;
		replaceAfters << after;
	    }
            else {
		config.lastLocation().warning(
			tr("Invalid regular expression '%1'")
			.arg(before.pattern()));
	    }
	}
        else {
	    config.lastLocation().warning(tr("Bad syntax in '%1'")
					   .arg(quickDotReplaces));
	}
	++r;
    }
}

void QsCodeParser::terminateParser()
{
    nodeTypeMap.clear();
    classesWithNoQuickDoc.clear();
    replaceBefores.clear();
    replaceAfters.clear();
    CppCodeParser::terminateParser();
}

QString QsCodeParser::language()
{
    return "Qt Script";
}

QString QsCodeParser::headerFileNameFilter()
{
    return "*";
}

QString QsCodeParser::sourceFileNameFilter()
{
    return "*.qs *.qsd";
}

void QsCodeParser::parseHeaderFile(const Location& location,
                                   const QString& filePath,
                                   Tree *tree)
{
    qsTre = tree;

    FILE *in = fopen(QFile::encodeName(filePath), "r");
    if (in == 0) {
	location.error(tr("Cannot open Qt Script class list '%1'")
			.arg(filePath));
	return;
    }

    Location fileLocation(filePath);
    Tokenizer fileTokenizer(fileLocation, in);
    int tok = fileTokenizer.getToken();
    while (tok != Tok_Eoi) {
	if (tok == Tok_Ident) {
	    ClassNode *quickClass = new ClassNode(qsTre->root(),
						   fileTokenizer.lexeme());
	    quickClass->setLocation(fileTokenizer.location());
	}
        else {
	    fileTokenizer.location().error(tr("Unexpected token '%1' in Qt"
					       " Script class list")
					    .arg(fileTokenizer.lexeme()));
	    break;
	}
	tok = fileTokenizer.getToken();
    }
    fclose(in);
}

void QsCodeParser::parseSourceFile(const Location& location,
                                   const QString& filePath,
                                   Tree *tree)
{
    qsTre = tree;
    CppCodeParser::parseSourceFile(location, filePath, tree);
}

void QsCodeParser::doneParsingHeaderFiles(Tree *tree)
{
    NodeList::ConstIterator c = tree->root()->childNodes().begin();
    while (c != tree->root()->childNodes().end()) {
	if ((*c)->type() == Node::Class)
	    quickifyClass((ClassNode *) *c);
	++c;
    }
    cppTre->root()->deleteChildren(); // save memory
    tree->resolveInheritance();
    tree->resolveProperties();
}

void QsCodeParser::doneParsingSourceFiles(Tree *tree)
{
    tree->root()->normalizeOverloads();

    NodeList::ConstIterator c = tree->root()->childNodes().begin();
    while (c != tree->root()->childNodes().end()) {
	if ((*c)->type() == Node::Class) {
	    QMap<QString, Node *>::ConstIterator cwnqd =
		    classesWithNoQuickDoc.find((*c)->name());
	    if (cwnqd != classesWithNoQuickDoc.end()) {
		(*cwnqd)->location().warning(tr("No '\\%1' documentation for"
						 " class '%2'")
					      .arg(COMMAND_QUICKCLASS)
					      .arg(cwnqd.key()));
		(*cwnqd)->setDoc(Doc(), true);
	    }
	}
	++c;
    }

    // ### check which enum types are used
}

FunctionNode *QsCodeParser::findFunctionNode(const QString& synopsis,
					      Tree *tree)
{
    QStringList parentPath;
    FunctionNode *clone;
    FunctionNode *func = 0;

    if (makeFunctionNode(synopsis, &parentPath, &clone)) {
	func = tree->findFunctionNode(parentPath, clone);
	delete clone;
    }
    return func;
}

QSet<QString> QsCodeParser::topicCommands()
{
    return QSet<QString>() << COMMAND_FILE << COMMAND_GROUP << COMMAND_MODULE
			   << COMMAND_PAGE << COMMAND_QUICKCLASS
			   << COMMAND_QUICKENUM << COMMAND_QUICKFN
			   << COMMAND_QUICKPROPERTY;
}

Node *QsCodeParser::processTopicCommand(const Doc& doc,
                                        const QString& command,
                                        const QString& arg)
{
    if (command == COMMAND_QUICKFN) {
	QStringList parentPath;
	FunctionNode *quickFunc = 0;
	FunctionNode *clone;

	if (makeFunctionNode(arg, &parentPath, &clone)) {
	    FunctionNode *kernelFunc = findKernelFunction(parentPath,
							   clone->name());
	    if (kernelFunc != 0)
		kernelFunc->setAccess(Node::Private);

	    quickFunc = qsTre->findFunctionNode(parentPath, clone);
	    if (quickFunc == 0 && kernelFunc != 0) {
		quickFunc = new FunctionNode(kernelFunc->parent(),
					      kernelFunc->name());
		quickFunc->setLocation(kernelFunc->location());
		quickFunc->setReturnType(clone->returnType());
		quickFunc->setParameters(clone->parameters());
	    }

	    if (quickFunc == 0) {
		doc.location().warning(tr("Cannot find '%1' specified with '\\%2'")
					.arg(arg).arg(command));
	    }
            else {
		quickFunc->setAccess(Node::Public);
		QStringList qtParams = quickFunc->parameterNames();
		quickFunc->borrowParameterNames(clone);
		QStringList quickParams = quickFunc->parameterNames();
		setQuickDoc(quickFunc, doc, qtParams, quickParams);
	    }
	    delete clone;
	}
        else {
	    doc.location().warning(tr("Cannot find '%1' specified with '\\%2'")
				    .arg(arg).arg(command));
	}
	return 0;
    }
    else if (nodeTypeMap.contains(command)) {
	QStringList subArgs = arg.split(" ");
	QString dataType;

	if (subArgs.count() == 3 && subArgs[1] == ":") {
	    dataType = subArgs[2];
	}
        else if (subArgs.count() != 1) {
	    doc.location().warning(tr("Invalid syntax in '\\%1'")
				    .arg(command));
	}

	QStringList path = subArgs[0].split(".");
	Node *quickNode = qsTre->findNode(path, nodeTypeMap[command]);
	if (quickNode == 0) {
	    doc.location().warning(tr("Cannot find '%1' specified with '\\%2'")
				    .arg(arg).arg(command));
	}
        else {
	    setQuickDoc(quickNode, doc);
	    if (quickNode->type() == Node::Class) {
		classesWithNoQuickDoc.remove(quickNode->name());
		if (doc.briefText().isEmpty())
		    doc.location().warning(tr("Missing '\\%1' for class '%2'")
					    .arg(COMMAND_BRIEF)
					    .arg(quickNode->name()));
	    }
            else if (quickNode->type() == Node::Property) {
		PropertyNode *quickProperty = (PropertyNode *) quickNode;
		if (quickProperty->dataType() == "Object") {
		    if (dataType.isEmpty()) {
			doc.location().warning(tr("Missing data type in '\\%1'"
						   " (assuming 'Object')")
						.arg(command));
		    }
                    else {
			quickProperty->setDataType(dataType);
		    }
		}
                else if (dataType != quickProperty->dataType()) {
		    doc.location().warning(tr("Ignored contradictory data type"
					       " in '\\%1'")
					    .arg(command));
		}
	    }
	}
	return 0;
    }
    else {
	return CppCodeParser::processTopicCommand(doc, command, arg);
    }
}

QSet<QString> QsCodeParser::otherMetaCommands()
{
    return commonMetaCommands() << COMMAND_ENDQUICKCODE << COMMAND_QUICKCODE
				<< COMMAND_QUICKIFY << COMMAND_REPLACE;
}

void QsCodeParser::processOtherMetaCommand(const Doc& doc,
                                           const QString& command,
                                           const QString& arg,
                                           Node *node)
{
    if (command == COMMAND_PROTECTED) {
	doc.location().warning(tr("Cannot use '\\%1' in %2")
				.arg(COMMAND_PROTECTED).arg(language()));
    }
    else {
	CppCodeParser::processOtherMetaCommand(doc,command,arg,node);
    }
}

ClassNode *QsCodeParser::tryClass(const QString& className)
{
    return (ClassNode*) cppTre->findNode(QStringList(className),Node::Class);
}

FunctionNode *QsCodeParser::findKernelFunction(const QStringList& parentPath,
                                               const QString& name)
{
    FunctionNode clone(0, name);
    clone.setReturnType("Object");
    clone.addParameter(Parameter("..."));
    return qsTre->findFunctionNode(parentPath, &clone);
}

void QsCodeParser::extractRegExp(const QRegExp& regExp,
                                 QString& source,
                                 const Doc& doc)
{
    QRegExp blankLineRegExp(
	    "[ \t]*(?:\n(?:[ \t]*\n)+[ \t]*|[ \n\t]*\\\\code|"
	    "\\\\endcode[ \n\t]*)");
    QStringList paras = source.trimmed().split(blankLineRegExp);
    paras = paras.filter(regExp);
    if (paras.count() == 0) {
	doc.location().warning(tr("Cannot find regular expression '%1'")
				.arg(regExp.pattern()));
    }
    else if (paras.count() > 1) {
	doc.location().warning(tr("Regular rexpression '%1' matches multiple"
				   "times").arg(regExp.pattern()));
    }
    else {
	source = paras.first() + "\n\n";
    }
}

void QsCodeParser::extractTarget(const QString& target,
                                 QString& source,
                                 const Doc& doc)
{
    QRegExp targetRegExp(
	    "(\\\\target\\s+(\\S+)[^\n]*\n"
	    "(?:(?!\\s*\\\\code)[^\n]+\n|\\s*\\\\code.*\\\\endcode\\s*\n)*)"
	    "(?:\\s*\n|[^\n]*$)");
    targetRegExp.setMinimal(true);

    int pos = 0;
    while ((pos = source.indexOf(targetRegExp, pos)) != -1) {
	if (targetRegExp.cap(2) == target) {
	    source = targetRegExp.cap(1) + "\n\n";
	    return;
	}
	pos += targetRegExp.matchedLength();
    }
    doc.location().warning(tr("Cannot find target '%1'").arg(target));
}

void QsCodeParser::renameParameters(QString& source,
                                    const Doc& /* doc */,
                                    const QStringList& qtParams,
                                    const QStringList& quickParams)
{
    QRegExp paramRegExp("(\\\\a\\s*\\{?\\s*)([A-Za-z0-9_]+)");

    int pos = 0;
    while ((pos = paramRegExp.indexIn(source, pos)) != -1) {
	pos += paramRegExp.cap(1).length();
	QString before = paramRegExp.cap(2);
	int index = qtParams.indexOf(before);
	if (index != -1) {
	    QString after = quickParams[index];
	    source.replace(pos, before.size(), after);
	}
    }
}

void QsCodeParser::applyReplacementList(QString& source, const Doc& doc)
{
    QStringList args = doc.metaCommandArgs(COMMAND_REPLACE);
    QStringList::ConstIterator a = args.begin();
    while (a != args.end()) {
	if (replaceRegExp.exactMatch(*a)) {
	    QRegExp before(replaceRegExp.cap(1));
	    before.setMinimal(true);
	    QString after = replaceRegExp.cap(2);

	    if (before.isValid()) {
		int oldLen = source.size();
		source.replace(before, after);

		// this condition is sufficient but not necessary
		if (oldLen == source.size() && !source.contains(after))
		    doc.location().warning(
			    tr("Regular expression '%1' did not match anything")
			    .arg(before.pattern()));
	    }
            else {
		doc.location().warning(
			tr("Invalid regular expression '%1'")
			.arg(before.pattern()));
	    }
	}
        else {
	    doc.location().warning(tr("Bad syntax in '\\%1'")
				    .arg(COMMAND_REPLACE));
	}
	++a;
    }

    QRegExp codeRegExp("\\\\" + COMMAND_CODE + "(.*)\\\\" + COMMAND_ENDCODE);
    codeRegExp.setMinimal(true);

    QRegExp quickcodeRegExp(
	    "\\\\" + COMMAND_QUICKCODE + "(.*)\\\\" + COMMAND_ENDQUICKCODE);
    quickcodeRegExp.setMinimal(true);

    int quickcodePos = doc.source().indexOf(quickcodeRegExp);
    if (quickcodePos != -1) {
	int codePos = source.indexOf(codeRegExp);
	if (codePos == -1) {
	    doc.location().warning(
		    tr("Cannot find any '\\%1' snippet corresponding to '\\%2'")
		    .arg(COMMAND_CODE).arg(COMMAND_QUICKCODE));
	}
        else {
	    source.replace(codeRegExp.pos(1), codeRegExp.cap(1).length(),
			    quickcodeRegExp.cap(1));
	    codePos = codeRegExp.pos(1) + quickcodeRegExp.cap(1).length();

	    if (doc.source().indexOf(quickcodeRegExp, quickcodePos + 1) != -1) {
		doc.location().warning(
			tr("Cannot use '\\%1' twice in a row")
			.arg(COMMAND_QUICKCODE));
	    }
            else if (source.indexOf(codeRegExp, codePos + 1) != -1) {
		doc.location().warning(tr("Ambiguous '\\%1'")
					.arg(COMMAND_QUICKCODE));
	    }
	}
    }
}

void QsCodeParser::quickifyClass(ClassNode *quickClass)
{
    QString qtClassName = quickClass->name();
    QString bare = quickClass->name();
    if (bare != "Qt" && bare != "Object") {
	if (bare.startsWith("Q")) {
	    bare = bare.mid(1);
	}
        else {
	    qtClassName.prepend("Q");
	    classesWithNoQ.insert(bare);
	}
    }

    ClassNode *qtClass = 0;
    ClassNode *wrapperClass = 0;

    if ((wrapperClass = tryClass("Quick" + bare)) != 0 ||
	 (wrapperClass = tryClass("QS" + bare + "Class")) != 0) {
	qtClass = tryClass(qtClassName);
	if (qtClass == 0) {
	    qtClass = wrapperClass;
	    wrapperClass = 0;
	}
    }
    else if ((wrapperClass = tryClass("Quick" + bare + "Ptr")) != 0) {
	QRegExp ptrToQtType("(Q[A-Za-z0-9_]+)\\s*\\*");
	FunctionNode *ctor =
		wrapperClass->findFunctionNode(wrapperClass->name());
	if (ctor != 0 && !ctor->parameters().isEmpty() &&
	     ptrToQtType.exactMatch(ctor->parameters().first().leftType()))
	    qtClassName = ptrToQtType.cap(1);
	qtClass = tryClass(qtClassName);
    }
    else {
	wrapperClass = tryClass("Q" + bare + "Ptr");
	if (wrapperClass == 0)
	    wrapperClass = tryClass("Quick" + bare + "Interface");
	qtClass = tryClass(qtClassName);
    }

    if (qtClass == 0) {
	if (wrapperClass == 0) {
	    quickClass->location().warning(tr("Cannot find Qt class '%1'")
					    .arg(qtClassName));
	}
        else {
	    quickClass->location().warning(tr("Cannot find Qt class '%1'"
					       " wrapped by '%2'")
					    .arg(qtClassName)
					    .arg(wrapperClass->name()));
	}
	return;
    }

    QList<RelatedClass>::ConstIterator r = qtClass->baseClasses().begin();
    while (r != qtClass->baseClasses().end()) {
	ClassNode *quickBaseClass = cpp2qs.findClassNode(qsTre,
                                                         (*r).node->name());
	if (quickBaseClass)
	    quickClass->addBaseClass((*r).access, quickBaseClass);
	++r;
    }
    if (quickClass->baseClasses().isEmpty() && quickClass->name() != "Object")
	quickClass->addBaseClass(Node::Public,
                                 cpp2qs.findClassNode(qsTre,"Object"));

    QSet<QString> funcBlackList;
    QSet<QString> propertyBlackList;

    NodeList children;
    if (wrapperClass != 0) {
	children = wrapperClass->childNodes();

	funcBlackList.insert(wrapperClass->name());
	funcBlackList.insert("~" + wrapperClass->name());
    }
    children += qtClass->childNodes();

    for (int pass = 0; pass < 2; pass++) {
	NodeList::ConstIterator c = children.begin();
	while (c != children.end()) {
	    if ((*c)->access() != Node::Private &&
		 (*c)->status() == Node::Commendable) {
		if (pass == 0) {
		    if ((*c)->type() == Node::Enum) {
			EnumNode *enume = (EnumNode *) *c;
			quickifyEnum(quickClass, enume);
		    }
                    else if ((*c)->type() == Node::Property) {
			if (!propertyBlackList.contains((*c)->name())) {
			    PropertyNode *property = (PropertyNode *) *c;
			    quickifyProperty(quickClass, qtClass, property);
			    if (!property->getters().isEmpty())
				funcBlackList.insert(property->getters().first()->name());
			    if (!property->setters().isEmpty())
				funcBlackList.insert(property->setters().first()->name());
			    if (!property->resetters().isEmpty())
				funcBlackList.insert(property->resetters().first()->name());
			    propertyBlackList.insert(property->name());
			}
		    }
		}
                else if ((*c)->type() == Node::Function)  {
		    FunctionNode *func = (FunctionNode *) *c;
		    quickifyFunction(quickClass, qtClass, func,
				      funcBlackList.contains((*c)->name()) &&
				      func->parameters().count() < 2);
		}
	    }
	    ++c;
	}
    }
    setQtDoc(quickClass, qtClass->doc());
    classesWithNoQuickDoc.insert(quickClass->name(), quickClass);
}

void QsCodeParser::quickifyEnum(ClassNode *quickClass, EnumNode *enume)
{
    EnumNode *quickEnum = new EnumNode(quickClass, enume->name());
    quickEnum->setLocation(enume->location());
#if 0 // ### not yet
    quickEnum->setAccess(Node::Protected);
#endif

    QList<EnumItem>::ConstIterator it = enume->items().begin();
    while (it != enume->items().end()) {
	QString name = (*it).name();
	QString value = (*it).value();
	quickEnum->addItem(EnumItem(name, value));
	++it;
    }
    setQtDoc(quickEnum, enume->doc());
}

void QsCodeParser::quickifyFunction(ClassNode *quickClass, ClassNode *qtClass,
				     FunctionNode *func, bool onBlackList)
{
    if (func->metaness() == FunctionNode::Dtor)
	return;

    FunctionNode *kernelFunc = findKernelFunction(
	    QStringList() << quickClass->name(), func->name());

    QString quickName = func->name();
    if (func->metaness() == FunctionNode::Ctor)
	quickName = quickClass->name();
    FunctionNode *quickFunc = new FunctionNode(quickClass, quickName);
    quickFunc->setLocation(func->location());

    if (onBlackList) {
	quickFunc->setAccess(Node::Protected);
    }
    else {
	if (kernelFunc != 0 && func->numOverloads() == 1 &&
	     (func->parameters().count() == 0 ||
	      func->parameters().last().defaultValue().isEmpty())) {
	    kernelFunc->setAccess(Node::Private);
	}
        else {
	    if (func->metaness() == FunctionNode::Plain)
		quickFunc->setAccess(Node::Protected);
	}
    }

    quickFunc->setReturnType(cpp2qs.convertedDataType(qsTre,
						       func->returnType()));
    if (func->metaness() != FunctionNode::Slot)
	quickFunc->setMetaness(func->metaness());
    quickFunc->setVirtualness(FunctionNode::ImpureVirtual);
    quickFunc->setOverload(func->isOverload());

    QList<Parameter>::ConstIterator q = func->parameters().begin();
    while (q != func->parameters().end()) {
	QString dataType = cpp2qs.convertedDataType(qsTre, (*q).leftType(),
						     (*q).rightType());
	if (dataType.isEmpty()) {
	    dataType = "UNKNOWN";
	    quickFunc->setAccess(Node::Private);
	}
	Parameter param(dataType, "", (*q).name(),
			 (*q).defaultValue().isEmpty() ? "" : "undefined");
	quickFunc->addParameter(param);
	++q;
    }

    if (func->doc().isEmpty()) {
	if (func->parent() != (InnerNode *) qtClass) {
	    func = qtClass->findFunctionNode(func);
	    if (func != 0)
		setQtDoc(quickFunc, func->doc());
	}
    }
    else {
	setQtDoc(quickFunc, func->doc());
    }
}

void QsCodeParser::quickifyProperty(ClassNode *quickClass,
                                    ClassNode * /* qtClass */,
				    PropertyNode *property)
{
    PropertyNode *quickProperty = new PropertyNode(quickClass,
                                                   property->name());
    quickProperty->setLocation(property->location());
    quickProperty->setDataType(cpp2qs.convertedDataType(qsTre,
                                                        property->dataType()));
#if 0
    quickProperty->setGetter(property->getter());
    quickProperty->setSetter(property->setter());
    quickProperty->setResetter(property->resetter());
#endif
    quickProperty->setStored(property->isStored());
    quickProperty->setDesignable(property->isDesignable());

    setQtDoc(quickProperty, property->doc());
}

QString QsCodeParser::quickifiedDoc(const QString& source)
{
    QString result;
    int i = 0;

    while (i < (int) source.length()) {
	if (leftWordBoundary(source, i)) {
	    if (source[i] == 'Q') {
		if (source[i + 1] == 'C' && source.mid(i, 8) == "QCString") {
		    i += 2;
		}
                else {
		    int end = i + 1;
		    while (isWord(source[end]))
			++end;
		    if (!classesWithNoQ.contains(
				  source.mid(i + 1, end - (i + 1))))
			result += "Q";
		    i++;
		}
	    }
            else if (source[i] == 'T' && source.mid(i, 4) == "TRUE" &&
			rightWordBoundary(source, i + 4)) {
		result += "\\c{true}";
		i += 4;
	    }
            else if (source[i] == 'F' && source.mid(i, 5) == "FALSE" &&
			rightWordBoundary(source, i + 5)) {
		result += "\\c{false}";
		i += 5;
	    }
            else if (source[i] == 'c' && source.mid(i, 6) == "const ") {
		i += 6;
	    }
            else {
		result += source[i++];
	    }
	}
        else if ((source[i] == ':' && source[i + 1] == ':') ||
		    (source[i] == '-' && source[i + 1] == '>')) {
	    result += '.';
	    i += 2;
	}
        else if (source[i] == '\\') {
	    // ### make independent of the command name
	    if (source.mid(i, 5) == "\\code") {
		do {
		    result += source[i++];
		} while (source[i - 1] != '\n');

		int begin = i;
		int end = source.indexOf("\\endcode", i);
		if (end != -1) {
		    QString code = source.mid(begin, end - begin);
		    result += cpp2qs.convertedCode(qsTre, code,
						    classesWithNoQ);
		    i = end;
		}
	    }
            else {
		result += source[i++];
	    }
	}
        else {
	    result += source[i++];
	}
    }

    QList<QRegExp>::ConstIterator b = replaceBefores.begin();
    QStringList::ConstIterator a = replaceAfters.begin();
    while (a != replaceAfters.end()) {
	result.replace(*b, *a);
	++b;
	++a;
    }
    return result;
}

void QsCodeParser::setQtDoc(Node *quickNode, const Doc& doc)
{
    if (!doc.isEmpty()) {
	Doc quickDoc(doc.location(), doc.location(),
                     quickifiedDoc(doc.source()),
		     CppCodeParser::topicCommands() +
                     CppCodeParser::otherMetaCommands());
	quickNode->setDoc(quickDoc, true);
    }
}

void QsCodeParser::setQuickDoc(Node *quickNode,
                               const Doc& doc,
                               const QStringList& qtParams,
                               const QStringList& quickParams)
{
    QRegExp quickifyCommand("\\\\" + COMMAND_QUICKIFY + "([^\n]*)(?:\n|$)");

    if (quickNode->type() == Node::Function) {
	FunctionNode *quickFunc = (FunctionNode *) quickNode;
	quickFunc->setOverload(false);
    }

    if (doc.metaCommandsUsed().contains(COMMAND_QUICKIFY)) {
	QString source = doc.source();
	int pos = source.indexOf(quickifyCommand);
	if (pos != -1) {
	    QString quickifiedSource = quickNode->doc().source();
	    if (!qtParams.isEmpty() && qtParams != quickParams)
		renameParameters(quickifiedSource, doc, qtParams,
				  quickParams);
	    applyReplacementList(quickifiedSource, doc);

	    do {
		QString extract = quickifiedSource;
		QString arg = quickifyCommand.cap(1).simplified();
		if (!arg.isEmpty()) {
		    if (arg.startsWith("/") && arg.endsWith("/") &&
			 arg.length() > 2) {
			QString pattern = arg.mid(1, arg.length() - 2);
			extractRegExp(QRegExp(pattern), extract, doc);
		    }
                    else {
			extractTarget(arg, extract, doc);
		    }
		}
		source.replace(pos, quickifyCommand.matchedLength(), extract);
		pos += extract.length();
	    } while ((pos = source.indexOf(quickifyCommand, pos)) != -1);

	    QRegExp quickcodeRegExp(
		    "\\\\" + COMMAND_QUICKCODE + "(.*)\\\\" +
		    COMMAND_ENDQUICKCODE);
	    quickcodeRegExp.setMinimal(true);
	    source.replace(quickcodeRegExp, "");
	}

	Doc quickDoc(doc.location(),
                     doc.location(),
                     source,
                     (CppCodeParser::topicCommands() + topicCommands() +
                      CppCodeParser::otherMetaCommands()) << COMMAND_REPLACE);
	quickNode->setDoc(quickDoc, true);
	processOtherMetaCommands(quickDoc, quickNode);
    }
    else {
	quickNode->setDoc(doc, true);
	processOtherMetaCommands(doc, quickNode);
    }
}

bool QsCodeParser::makeFunctionNode(const QString& synopsis,
				     QStringList *parentPathPtr,
				     FunctionNode **funcPtr)
{
    QRegExp funcRegExp(
	    "\\s*([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\s*\\((" +
            balancedParens +
	    ")\\)(?:\\s*:\\s*([A-Za-z0-9_]+))?\\s*");
    QRegExp paramRegExp(
	    "\\s*(\\[)?\\s*(?:([A-Za-z0-9_]+)\\s*:\\s*)?"
	    "([A-Za-z0-9_]+|\\.\\.\\.)\\s*(\\[)?[\\s\\]]*");

    if (!funcRegExp.exactMatch(synopsis))
	return false;

    ClassNode *classe = (ClassNode*)
        qsTre->findNode(QStringList(funcRegExp.cap(1)),Node::Class);
    if (classe == 0)
	return false;

    FunctionNode *clone = new FunctionNode(0, funcRegExp.cap(2));
    bool optional = false;

    QString paramStr = funcRegExp.cap(3);
    QStringList params = paramStr.split(",");
    QStringList::ConstIterator p = params.begin();
    while (p != params.end()) {
	if (paramRegExp.exactMatch(*p)) {
	    if (!paramRegExp.cap(1).isEmpty())
		optional = true;
	    clone->addParameter(Parameter(paramRegExp.cap(3),
                                          "",
                                          paramRegExp.cap(2),
                                          optional ? "undefined" : ""));
	    if (!paramRegExp.cap(4).isEmpty())
		optional = true;
	}
        else {
	    delete clone;
	    return false;
	}
	++p;
    }
    QString returnType = funcRegExp.cap(4);
    if (!returnType.isEmpty())
	clone->setReturnType(returnType);
    if (parentPathPtr != 0)
	*parentPathPtr = QStringList() << classe->name();
    if (funcPtr != 0) {
	*funcPtr = clone;
    }
    else {
	delete clone;
    }
    return true;
}

bool QsCodeParser::isWord(QChar ch)
{
    return ch.isLetterOrNumber() || ch == QChar('_');
}

bool QsCodeParser::leftWordBoundary(const QString& str, int pos)
{
    return !isWord(str[pos - 1]) && isWord(str[pos]);
}

bool QsCodeParser::rightWordBoundary(const QString& str, int pos)
{
    return isWord(str[pos - 1]) && !isWord(str[pos]);
}

QT_END_NAMESPACE
