blob: c322018169c45c639858294bfe67e4e541b0ff1d [file] [log] [blame]
/***************************************************************************
**
** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB)
** Contact: https://www.qt.io/licensing/
**
** This file is part of the utilities of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "xmlspecparser.h"
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
#include <QStringList>
#include <QTextStream>
#include <QXmlStreamReader>
#ifdef SPECPARSER_DEBUG
#define qXmlSpecParserDebug qDebug
#else
#define qXmlSpecParserDebug QT_NO_QDEBUG_MACRO
#endif
bool XmlSpecParser::parse()
{
// Open up a stream on the actual OpenGL function spec file
QFile file(specFileName());
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Failed to open spec file:" << specFileName() << "Aborting";
return false;
}
QXmlStreamReader stream(&file);
// Extract the info that we need
parseFunctions(stream);
return true;
}
void XmlSpecParser::parseParam(QXmlStreamReader &stream, Function &func)
{
Argument arg;
arg.type = QString();
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement()) {
QString tag = stream.name().toString();
if (tag == "ptype") {
if (stream.readNext() == QXmlStreamReader::Characters)
arg.type.append(stream.text().toString());
}
else if (tag == "name") {
if (stream.readNext() == QXmlStreamReader::Characters)
arg.name = stream.text().toString().trimmed();
}
} else if (stream.isCharacters()) {
arg.type.append(stream.text().toString());
} else if (stream.isEndElement()) {
QString tag = stream.name().toString();
if (tag == "param") {
// compatibility with old spec
QRegularExpression typeRegExp("(const )?(.+)(?<!\\*)((?:(?!\\*$)\\*)*)(\\*)?");
// remove extra whitespace
arg.type = arg.type.trimmed();
// set default
arg.direction = Argument::In;
arg.mode = Argument::Value;
QRegularExpressionMatch exp = typeRegExp.match(arg.type);
if (exp.hasMatch()) {
if (!exp.captured(4).isEmpty()) {
arg.mode = Argument::Reference;
if (exp.captured(1).isEmpty())
arg.direction = Argument::Out;
}
arg.type = exp.captured(2) + exp.captured(3);
}
break;
}
}
}
// remove any excess whitespace
arg.type = arg.type.trimmed();
arg.name = arg.name.trimmed();
// maybe some checks?
func.arguments.append(arg);
}
void XmlSpecParser::parseCommand(QXmlStreamReader &stream)
{
Function func;
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement()) {
QString tag = stream.name().toString();
if (tag == "proto") {
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement() && (stream.name().toString() == "name")) {
if (stream.readNext() == QXmlStreamReader::Characters)
func.name = stream.text().toString();
} else if (stream.isCharacters()) {
func.returnType.append(stream.text().toString());
} else if (stream.isEndElement() && (stream.name().toString() == "proto")) {
break;
}
}
}
if (tag == "param")
parseParam(stream, func);
}
else if (stream.isEndElement()) {
QString tag = stream.name().toString();
if (tag == "command")
break;
}
}
// maybe checks?
func.returnType = func.returnType.trimmed();
// for compatibility with old spec
if (func.name.startsWith("gl"))
func.name.remove(0, 2);
m_functionList.insert(func.name, func);
}
void XmlSpecParser::parseCommands(QXmlStreamReader &stream)
{
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement()) {
QString tag = stream.name().toString();
if (tag == "command")
parseCommand(stream);
}
else if (stream.isEndElement()) {
QString tag = stream.name().toString();
if (tag == "commands")
break;
}
}
}
void XmlSpecParser::parseRequire(QXmlStreamReader &stream, FunctionList &funcs)
{
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement()) {
QString tag = stream.name().toString();
if (tag == "command") {
QString func = stream.attributes().value("name").toString();
// for compatibility with old spec
if (func.startsWith("gl"))
func.remove(0, 2);
funcs.append(m_functionList[func]);
}
} else if (stream.isEndElement()) {
QString tag = stream.name().toString();
if (tag == "require")
break;
}
}
}
void XmlSpecParser::parseRemoveCore(QXmlStreamReader &stream)
{
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement()) {
QString tag = stream.name().toString();
if (tag == "command") {
QString func = stream.attributes().value("name").toString();
// for compatibility with old spec
if (func.startsWith("gl"))
func.remove(0, 2);
// go through list of version and mark as incompatible
Q_FOREACH (const Version& v, m_versions) {
// Combine version and profile for this subset of functions
VersionProfile version;
version.version = v;
version.profile = VersionProfile::CoreProfile;
// Fetch the functions and add to collection for this class
Q_FOREACH (const Function& f, m_functions.values(version)) {
if (f.name == func) {
VersionProfile newVersion;
newVersion.version = v;
newVersion.profile = VersionProfile::CompatibilityProfile;
m_functions.insert(newVersion, f);
m_functions.remove(version, f);
}
}
}
}
} else if (stream.isEndElement()) {
QString tag = stream.name().toString();
if (tag == "remove")
break;
}
}
}
void XmlSpecParser::parseFeature(QXmlStreamReader &stream)
{
QRegularExpression versionRegExp("(\\d).(\\d)");
QXmlStreamAttributes attributes = stream.attributes();
QRegularExpressionMatch match = versionRegExp.match(attributes.value("number").toString());
if (!match.hasMatch()) {
qWarning() << "Malformed version indicator";
return;
}
if (attributes.value("api").toString() != "gl")
return;
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
Version v;
VersionProfile core, compat;
v.major = majorVersion;
v.minor = minorVersion;
core.version = compat.version = v;
core.profile = VersionProfile::CoreProfile;
compat.profile = VersionProfile::CompatibilityProfile;
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement()) {
QString tag = stream.name().toString();
if (tag == "require") {
FunctionList funcs;
if (stream.attributes().value("profile").toString() == "compatibility") {
parseRequire(stream, funcs);
Q_FOREACH (const Function& f, funcs) {
m_functions.insert(compat, f);
}
} else {
parseRequire(stream, funcs);
Q_FOREACH (const Function& f, funcs) {
m_functions.insert(core, f);
}
}
} else if (tag == "remove") {
if (stream.attributes().value("profile").toString() == "core")
parseRemoveCore(stream);
}
} else if (stream.isEndElement()) {
QString tag = stream.name().toString();
if (tag == "feature")
break;
}
}
m_versions.append(v);
}
void XmlSpecParser::parseExtension(QXmlStreamReader &stream)
{
QXmlStreamAttributes attributes = stream.attributes();
QString name = attributes.value("name").toString();
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement()) {
QString tag = stream.name().toString();
if (tag == "require") {
if (stream.attributes().value("profile").toString() == "compatibility") {
FunctionList funcs;
parseRequire(stream, funcs);
Q_FOREACH (const Function& f, funcs) {
FunctionProfile fp;
fp.function = f;
fp.profile = VersionProfile::CompatibilityProfile;
m_extensionFunctions.insert(name, fp);
}
} else {
FunctionList funcs;
parseRequire(stream, funcs);
Q_FOREACH (const Function& f, funcs) {
FunctionProfile fp;
fp.function = f;
fp.profile = VersionProfile::CoreProfile;
m_extensionFunctions.insert(name, fp);
}
}
}
} else if (stream.isEndElement()) {
QString tag = stream.name().toString();
if (tag == "extension")
break;
}
}
}
void XmlSpecParser::parseFunctions(QXmlStreamReader &stream)
{
while (!stream.isEndDocument()) {
stream.readNext();
if (stream.isStartElement()) {
QString tag = stream.name().toString();
if (tag == "feature") {
parseFeature(stream);
} else if (tag == "commands") {
parseCommands(stream);
} else if (tag == "extension") {
parseExtension(stream);
}
} else if (stream.isEndElement()) {
stream.readNext();
}
}
// hack - add GL_ARB_imaging to every version after 1.2 inclusive
Version versionThreshold;
versionThreshold.major = 1;
versionThreshold.minor = 2;
QList<FunctionProfile> funcs = m_extensionFunctions.values("GL_ARB_imaging");
VersionProfile vp;
vp.version = versionThreshold;
Q_FOREACH (const FunctionProfile& fp, funcs) {
vp.profile = fp.profile;
m_functions.insert(vp, fp.function);
}
// now we will prune any duplicates
QSet<QString> funcset;
Q_FOREACH (const Version& v, m_versions) {
// check compatibility first
VersionProfile vp;
vp.version = v;
vp.profile = VersionProfile::CompatibilityProfile;
Q_FOREACH (const Function& f, m_functions.values(vp)) {
// remove duplicate
if (funcset.contains(f.name))
m_functions.remove(vp, f);
funcset.insert(f.name);
}
vp.profile = VersionProfile::CoreProfile;
Q_FOREACH (const Function& f, m_functions.values(vp)) {
// remove duplicate
if (funcset.contains(f.name))
m_functions.remove(vp, f);
funcset.insert(f.name);
}
}
}