428 lines
13 KiB
C++
428 lines
13 KiB
C++
|
/***************************************************************************
|
||
|
**
|
||
|
** 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);
|
||
|
}
|
||
|
}
|
||
|
}
|