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