1 /***************************************************************************
2 **
3 ** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB)
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the utilities of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "xmlspecparser.h"
30 
31 #include <QDebug>
32 #include <QFile>
33 #include <QRegularExpression>
34 #include <QStringList>
35 #include <QTextStream>
36 #include <QXmlStreamReader>
37 
38 #ifdef SPECPARSER_DEBUG
39 #define qXmlSpecParserDebug qDebug
40 #else
41 #define qXmlSpecParserDebug QT_NO_QDEBUG_MACRO
42 #endif
43 
parse()44 bool XmlSpecParser::parse()
45 {
46     // Open up a stream on the actual OpenGL function spec file
47     QFile file(specFileName());
48     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
49         qWarning() << "Failed to open spec file:" << specFileName() << "Aborting";
50         return false;
51     }
52 
53     QXmlStreamReader stream(&file);
54 
55     // Extract the info that we need
56     parseFunctions(stream);
57     return true;
58 }
59 
parseParam(QXmlStreamReader & stream,Function & func)60 void XmlSpecParser::parseParam(QXmlStreamReader &stream, Function &func)
61 {
62     Argument arg;
63     arg.type = QString();
64 
65     while (!stream.isEndDocument()) {
66         stream.readNext();
67 
68         if (stream.isStartElement()) {
69             QString tag = stream.name().toString();
70 
71             if (tag == "ptype") {
72                 if (stream.readNext() == QXmlStreamReader::Characters)
73                     arg.type.append(stream.text().toString());
74             }
75 
76             else if (tag == "name") {
77                 if (stream.readNext() == QXmlStreamReader::Characters)
78                     arg.name = stream.text().toString().trimmed();
79             }
80         } else if (stream.isCharacters()) {
81             arg.type.append(stream.text().toString());
82         } else if (stream.isEndElement()) {
83             QString tag = stream.name().toString();
84 
85             if (tag == "param") {
86                 // compatibility with old spec
87                 QRegularExpression typeRegExp("(const )?(.+)(?<!\\*)((?:(?!\\*$)\\*)*)(\\*)?");
88 
89                 // remove extra whitespace
90                 arg.type = arg.type.trimmed();
91 
92                 // set default
93                 arg.direction = Argument::In;
94                 arg.mode = Argument::Value;
95 
96                 QRegularExpressionMatch exp = typeRegExp.match(arg.type);
97                 if (exp.hasMatch()) {
98                     if (!exp.captured(4).isEmpty()) {
99                         arg.mode = Argument::Reference;
100 
101                         if (exp.captured(1).isEmpty())
102                             arg.direction = Argument::Out;
103                     }
104 
105                     arg.type = exp.captured(2) + exp.captured(3);
106                 }
107 
108                 break;
109             }
110         }
111     }
112 
113     // remove any excess whitespace
114     arg.type = arg.type.trimmed();
115     arg.name = arg.name.trimmed();
116 
117     // maybe some checks?
118     func.arguments.append(arg);
119 }
120 
parseCommand(QXmlStreamReader & stream)121 void XmlSpecParser::parseCommand(QXmlStreamReader &stream)
122 {
123     Function func;
124 
125     while (!stream.isEndDocument()) {
126         stream.readNext();
127 
128         if (stream.isStartElement()) {
129             QString tag = stream.name().toString();
130 
131             if (tag == "proto") {
132                 while (!stream.isEndDocument()) {
133                     stream.readNext();
134                     if (stream.isStartElement() && (stream.name().toString() == "name")) {
135                         if (stream.readNext() == QXmlStreamReader::Characters)
136                             func.name = stream.text().toString();
137                     } else if (stream.isCharacters()) {
138                         func.returnType.append(stream.text().toString());
139                     } else if (stream.isEndElement() && (stream.name().toString() == "proto")) {
140                         break;
141                     }
142                 }
143             }
144 
145             if (tag == "param")
146                 parseParam(stream, func);
147         }
148 
149         else if (stream.isEndElement()) {
150             QString tag = stream.name().toString();
151 
152             if (tag == "command")
153                 break;
154         }
155     }
156 
157     // maybe checks?
158     func.returnType = func.returnType.trimmed();
159 
160     // for compatibility with old spec
161     if (func.name.startsWith("gl"))
162         func.name.remove(0, 2);
163 
164     m_functionList.insert(func.name, func);
165 }
166 
parseCommands(QXmlStreamReader & stream)167 void XmlSpecParser::parseCommands(QXmlStreamReader &stream)
168 {
169     while (!stream.isEndDocument()) {
170         stream.readNext();
171 
172         if (stream.isStartElement()) {
173             QString tag = stream.name().toString();
174 
175             if (tag == "command")
176                 parseCommand(stream);
177         }
178 
179         else if (stream.isEndElement()) {
180             QString tag = stream.name().toString();
181 
182             if (tag == "commands")
183                 break;
184         }
185     }
186 }
187 
parseRequire(QXmlStreamReader & stream,FunctionList & funcs)188 void XmlSpecParser::parseRequire(QXmlStreamReader &stream, FunctionList &funcs)
189 {
190     while (!stream.isEndDocument()) {
191         stream.readNext();
192 
193         if (stream.isStartElement()) {
194             QString tag = stream.name().toString();
195 
196             if (tag == "command") {
197                 QString func = stream.attributes().value("name").toString();
198 
199                 // for compatibility with old spec
200                 if (func.startsWith("gl"))
201                     func.remove(0, 2);
202 
203                 funcs.append(m_functionList[func]);
204             }
205         } else if (stream.isEndElement()) {
206             QString tag = stream.name().toString();
207 
208             if (tag == "require")
209                 break;
210         }
211     }
212 }
213 
parseRemoveCore(QXmlStreamReader & stream)214 void XmlSpecParser::parseRemoveCore(QXmlStreamReader &stream)
215 {
216     while (!stream.isEndDocument()) {
217         stream.readNext();
218 
219         if (stream.isStartElement()) {
220             QString tag = stream.name().toString();
221 
222             if (tag == "command") {
223                 QString func = stream.attributes().value("name").toString();
224 
225                 // for compatibility with old spec
226                 if (func.startsWith("gl"))
227                     func.remove(0, 2);
228 
229                 // go through list of version and mark as incompatible
230                 Q_FOREACH (const Version& v, m_versions) {
231                     // Combine version and profile for this subset of functions
232                     VersionProfile version;
233                     version.version = v;
234                     version.profile = VersionProfile::CoreProfile;
235 
236                     // Fetch the functions and add to collection for this class
237                     Q_FOREACH (const Function& f, m_functions.values(version)) {
238                         if (f.name == func) {
239                             VersionProfile newVersion;
240                             newVersion.version = v;
241                             newVersion.profile = VersionProfile::CompatibilityProfile;
242 
243                             m_functions.insert(newVersion, f);
244                             m_functions.remove(version, f);
245                         }
246                     }
247                 }
248             }
249         } else if (stream.isEndElement()) {
250             QString tag = stream.name().toString();
251 
252             if (tag == "remove")
253                 break;
254         }
255     }
256 }
257 
parseFeature(QXmlStreamReader & stream)258 void XmlSpecParser::parseFeature(QXmlStreamReader &stream)
259 {
260     QRegularExpression versionRegExp("(\\d).(\\d)");
261     QXmlStreamAttributes attributes = stream.attributes();
262 
263     QRegularExpressionMatch match = versionRegExp.match(attributes.value("number").toString());
264 
265     if (!match.hasMatch()) {
266         qWarning() << "Malformed version indicator";
267         return;
268     }
269 
270     if (attributes.value("api").toString() != "gl")
271         return;
272 
273     int majorVersion = match.captured(1).toInt();
274     int minorVersion = match.captured(2).toInt();
275 
276     Version v;
277     VersionProfile core, compat;
278 
279     v.major = majorVersion;
280     v.minor = minorVersion;
281     core.version = compat.version = v;
282     core.profile = VersionProfile::CoreProfile;
283     compat.profile = VersionProfile::CompatibilityProfile;
284 
285     while (!stream.isEndDocument()) {
286         stream.readNext();
287 
288         if (stream.isStartElement()) {
289             QString tag = stream.name().toString();
290 
291             if (tag == "require") {
292                 FunctionList funcs;
293 
294                 if (stream.attributes().value("profile").toString() == "compatibility") {
295                     parseRequire(stream, funcs);
296                     Q_FOREACH (const Function& f, funcs) {
297                         m_functions.insert(compat, f);
298                     }
299                 } else {
300                     parseRequire(stream, funcs);
301                     Q_FOREACH (const Function& f, funcs) {
302                         m_functions.insert(core, f);
303                     }
304                 }
305             } else if (tag == "remove") {
306                 if (stream.attributes().value("profile").toString() == "core")
307                     parseRemoveCore(stream);
308             }
309         } else if (stream.isEndElement()) {
310             QString tag = stream.name().toString();
311 
312             if (tag == "feature")
313                 break;
314         }
315     }
316 
317     m_versions.append(v);
318 }
319 
parseExtension(QXmlStreamReader & stream)320 void XmlSpecParser::parseExtension(QXmlStreamReader &stream)
321 {
322     QXmlStreamAttributes attributes = stream.attributes();
323     QString name = attributes.value("name").toString();
324 
325     while (!stream.isEndDocument()) {
326         stream.readNext();
327 
328         if (stream.isStartElement()) {
329             QString tag = stream.name().toString();
330 
331             if (tag == "require") {
332                 if (stream.attributes().value("profile").toString() == "compatibility") {
333                     FunctionList funcs;
334                     parseRequire(stream, funcs);
335 
336                     Q_FOREACH (const Function& f, funcs) {
337                         FunctionProfile fp;
338                         fp.function = f;
339                         fp.profile = VersionProfile::CompatibilityProfile;
340                         m_extensionFunctions.insert(name, fp);
341                     }
342                 } else {
343                     FunctionList funcs;
344                     parseRequire(stream, funcs);
345                     Q_FOREACH (const Function& f, funcs) {
346                         FunctionProfile fp;
347                         fp.function = f;
348                         fp.profile = VersionProfile::CoreProfile;
349                         m_extensionFunctions.insert(name, fp);
350                     }
351                 }
352 
353 
354             }
355         } else if (stream.isEndElement()) {
356             QString tag = stream.name().toString();
357 
358             if (tag == "extension")
359                 break;
360         }
361     }
362 }
363 
parseFunctions(QXmlStreamReader & stream)364 void XmlSpecParser::parseFunctions(QXmlStreamReader &stream)
365 {
366     while (!stream.isEndDocument()) {
367         stream.readNext();
368 
369         if (stream.isStartElement()) {
370             QString tag = stream.name().toString();
371 
372             if (tag == "feature") {
373                 parseFeature(stream);
374             } else if (tag == "commands") {
375                 parseCommands(stream);
376             } else if (tag == "extension") {
377                 parseExtension(stream);
378             }
379         } else if (stream.isEndElement()) {
380             stream.readNext();
381         }
382     }
383 
384     // hack - add GL_ARB_imaging to every version after 1.2 inclusive
385     Version versionThreshold;
386     versionThreshold.major = 1;
387     versionThreshold.minor = 2;
388     QList<FunctionProfile> funcs = m_extensionFunctions.values("GL_ARB_imaging");
389 
390     VersionProfile vp;
391     vp.version = versionThreshold;
392 
393     Q_FOREACH (const FunctionProfile& fp, funcs) {
394         vp.profile = fp.profile;
395         m_functions.insert(vp, fp.function);
396     }
397 
398     // now we will prune any duplicates
399     QSet<QString> funcset;
400 
401     Q_FOREACH (const Version& v, m_versions) {
402         // check compatibility first
403         VersionProfile vp;
404         vp.version = v;
405 
406         vp.profile = VersionProfile::CompatibilityProfile;
407 
408         Q_FOREACH (const Function& f, m_functions.values(vp)) {
409             // remove duplicate
410             if (funcset.contains(f.name))
411                 m_functions.remove(vp, f);
412 
413             funcset.insert(f.name);
414         }
415 
416         vp.profile = VersionProfile::CoreProfile;
417 
418         Q_FOREACH (const Function& f, m_functions.values(vp)) {
419 
420             // remove duplicate
421             if (funcset.contains(f.name))
422                 m_functions.remove(vp, f);
423 
424             funcset.insert(f.name);
425         }
426     }
427 }
428