Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
cppgenerator.cpp
Go to the documentation of this file.
1// REUSE-IgnoreStart
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
4// Qt-Security score:insignificant reason:build-tool
5// REUSE-IgnoreEnd
6
7#include "cppgenerator.h"
8
9#include "lalr.h"
10#include "recognizer.h"
11
12#include <QtCore/qbitarray.h>
13#include <QtCore/qtextstream.h>
14#include <QtCore/qfile.h>
15#include <QtCore/qmap.h>
16#include <QtCore/private/qconfig_p.h>
17
18#include <iterator>
19
20using namespace Qt::StringLiterals;
21
22namespace {
23
24void generateSeparator(int i, QTextStream &out)
25{
26 if (!(i % 10)) {
27 if (i)
28 out << ",";
29 out << Qt::endl << " ";
30 } else {
31 out << ", ";
32 }
33}
34
35void generateList(const QList<int> &list, QTextStream &out)
36{
37 for (int i = 0; i < list.size(); ++i) {
38 generateSeparator(i, out);
39
40 out << list[i];
41 }
42}
43
44} // unnamed namespace
45
46
47auto CppGenerator::SecurityHeader::parse(QStringView input)
48 -> std::optional<SecurityHeader>
49{
50 QStringView score;
51 QStringView reason;
52
53 if (const auto colon = input.indexOf(u':'); colon >= 0) {
54 score = input.first(colon);
55 reason = input.sliced(colon + 1);
56 } else {
57 score = input;
58 }
59
60 if (score == "critical"_L1)
61 return SecurityHeader{Security::Critical, reason.isEmpty() ? "data-parser"_ba : reason.toUtf8()};
62
63 if (score == "significant"_L1)
64 return SecurityHeader{Security::Significant, reason.isEmpty() ? "default"_ba : reason.toUtf8()};
65
66 if (score == "insignificant"_L1)
67 return SecurityHeader{Security::Insignificant, reason.toUtf8()}; // no default
68
69 return std::nullopt;
70}
71
73{
74 QByteArray res;
75
76 using Security = CppGenerator::SecurityHeader::Security;
77
78 res += "// Qt-Security score:";
79 switch (score) {
80 case Security::Critical: res += "critical"; break;
81 case Security::Significant: res += "significant"; break;
82 case Security::Insignificant: res += "insignificant"; break;
83 }
84
85 if (!reason.isEmpty()) {
86 res += " reason:";
87 res += reason;
88 }
89
90 res += '\n';
91
92 return res;
93}
94
95// REUSE-IgnoreStart
96QByteArray CppGenerator::copyrightHeader() const
97{
98 return
99 "// " QT_COPYRIGHT "\n"
100 "// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0\n"
101 + (security ? security->print() : QByteArray())
102 + '\n';
103}
104// REUSE-IgnoreEnd
105
106QString CppGenerator::privateCopyrightHeader() const
107{
108 return
109 "//\n"
110 "// W A R N I N G\n"
111 "// -------------\n"
112 "//\n"
113 "// This file is not part of the Qt API. It exists for the convenience\n"
114 "// of other Qt classes. This header file may change from version to\n"
115 "// version without notice, or even be removed.\n"
116 "//\n"
117 "// We mean it.\n"
118 "//\n"_L1;
119}
120
121static QString includeGuard(const QString &fileName)
122{
123 return fileName.toUpper().replace(u'.', u'_');
124}
125
126QString CppGenerator::startIncludeGuard(const QString &fileName) const
127{
128 if (use_pragma_once)
129 return u"#pragma once\n"_s;
130
131 const QString normalized = includeGuard(fileName);
132
133 return QString::fromLatin1("#ifndef %1\n"
134 "#define %2\n").arg(normalized, normalized);
135}
136
137QString CppGenerator::endIncludeGuard(const QString &fileName) const
138{
139 if (use_pragma_once)
140 return QString();
141
142 const QString normalized = includeGuard(fileName);
143
144 return QString::fromLatin1("#endif // %1\n").arg(normalized);
145}
146
148{
149 // action table...
150 state_count = static_cast<int>(aut.states.size());
151 terminal_count = static_cast<int>(grammar.terminals.size());
152 non_terminal_count = static_cast<int>(grammar.non_terminals.size());
153
154#define ACTION(i, j) table [(i) * terminal_count + (j)]
155#define GOTO(i, j) pgoto [(i) * non_terminal_count + (j)]
156
157 int *table = new int [state_count * terminal_count];
158 ::memset (table, 0, state_count * terminal_count * sizeof (int));
159
160 int *pgoto = new int [state_count * non_terminal_count];
161 ::memset (pgoto, 0, state_count * non_terminal_count * sizeof (int));
162
163 accept_state = -1;
164 int shift_reduce_conflict_count = 0;
165 int reduce_reduce_conflict_count = 0;
166
167 for (StatePointer state = aut.states.begin (); state != aut.states.end (); ++state)
168 {
169 int q = aut.id (state);
170
171 for (Bundle::iterator a = state->bundle.begin (); a != state->bundle.end (); ++a)
172 {
173 int symbol = aut.id (a.key ());
174 int r = aut.id (a.value ());
175
176 Q_ASSERT (r < state_count);
177
178 if (grammar.isNonTerminal (a.key ()))
179 {
180 Q_ASSERT(symbol >= terminal_count && symbol < static_cast<int>(grammar.names.size()));
181 GOTO (q, symbol - terminal_count) = r;
182 }
183
184 else
185 ACTION (q, symbol) = r;
186 }
187
188 for (ItemPointer item = state->closure.begin (); item != state->closure.end (); ++item)
189 {
190 if (item->dot != item->end_rhs ())
191 continue;
192
193 int r = aut.id (item->rule);
194
195 const NameSet lookaheads = aut.lookaheads.value (item);
196
197 if (item->rule == grammar.goal)
198 accept_state = q;
199
200 for (const Name &s : lookaheads)
201 {
202 int &u = ACTION (q, aut.id (s));
203
204 if (u == 0)
205 u = - r;
206
207 else if (u < 0)
208 {
209 if (verbose)
210 qout() << "*** Warning. Found a reduce/reduce conflict in state " << q << " on token ``" << s << "'' between rule "
211 << r << " and " << -u << Qt::endl;
212
213 ++reduce_reduce_conflict_count;
214
215 u = qMax (u, -r);
216
217 if (verbose)
218 qout() << "\tresolved using rule " << -u << Qt::endl;
219 }
220
221 else if (u > 0)
222 {
223 if (item->rule->prec != grammar.names.end() && grammar.token_info.contains (s))
224 {
225 Grammar::TokenInfo info_r = grammar.token_info.value (item->rule->prec);
226 Grammar::TokenInfo info_s = grammar.token_info.value (s);
227
228 if (info_r.prec > info_s.prec)
229 u = -r;
230 else if (info_r.prec == info_s.prec)
231 {
232 switch (info_r.assoc) {
233 case Grammar::Left:
234 u = -r;
235 break;
236 case Grammar::Right:
237 // shift... nothing to do
238 break;
239 case Grammar::NonAssoc:
240 u = 0;
241 break;
242 } // switch
243 }
244 }
245
246 else
247 {
248 ++shift_reduce_conflict_count;
249
250 if (verbose)
251 qout() << "*** Warning. Found a shift/reduce conflict in state " << q << " on token ``" << s << "'' with rule " << r << Qt::endl;
252 }
253 }
254 }
255 }
256 }
257
258 if (shift_reduce_conflict_count || reduce_reduce_conflict_count)
259 {
260 if (shift_reduce_conflict_count != grammar.expected_shift_reduce
261 || reduce_reduce_conflict_count != grammar.expected_reduce_reduce)
262 {
263 qerr() << "*** Conflicts: " << shift_reduce_conflict_count << " shift/reduce, " << reduce_reduce_conflict_count << " reduce/reduce" << Qt::endl;
264 if (warnings_are_errors)
265 {
266 qerr() << "qlalr: error: warning occurred, treating as error due to "
267 "--exit-on-warn." << Qt::endl;
268 exit(2);
269 }
270 }
271
272 if (verbose)
273 qout() << Qt::endl << "*** Conflicts: " << shift_reduce_conflict_count << " shift/reduce, " << reduce_reduce_conflict_count << " reduce/reduce" << Qt::endl
274 << Qt::endl;
275 }
276
277 QBitArray used_rules{static_cast<int>(grammar.rules.size())};
278
279 int q = 0;
280 for (StatePointer state = aut.states.begin (); state != aut.states.end (); ++state, ++q)
281 {
282 for (int j = 0; j < terminal_count; ++j)
283 {
284 int &u = ACTION (q, j);
285
286 if (u < 0)
287 used_rules.setBit (-u - 1);
288 }
289 }
290
291 auto rule = grammar.rules.begin();
292 for (int i = 0; i < used_rules.size(); ++i, ++rule)
293 {
294 if (! used_rules.testBit (i))
295 {
296 if (rule != grammar.goal)
297 {
298 qerr() << "*** Warning: Rule ``" << *rule << "'' is useless!" << Qt::endl;
299 if (warnings_are_errors)
300 {
301 qerr() << "qlalr: error: warning occurred, treating as error due to "
302 "--exit-on-warn." << Qt::endl;
303 exit(2);
304 }
305 }
306 }
307 }
308
309 q = 0;
310 for (StatePointer state = aut.states.begin (); state != aut.states.end (); ++state, ++q)
311 {
312 for (int j = 0; j < terminal_count; ++j)
313 {
314 int &u = ACTION (q, j);
315
316 if (u >= 0)
317 continue;
318
319 RulePointer rule = std::next(grammar.rules.begin(), - u - 1);
320
321 if (state->defaultReduce == rule)
322 u = 0;
323 }
324 }
325
326 // ... compress the goto table
327 defgoto.resize (non_terminal_count);
328 for (int j = 0; j < non_terminal_count; ++j)
329 {
330 count.fill (0, state_count);
331
332 int &mx = defgoto [j];
333
334 for (int i = 0; i < state_count; ++i)
335 {
336 int r = GOTO (i, j);
337
338 if (! r)
339 continue;
340
341 ++count [r];
342
343 if (count [r] > count [mx])
344 mx = r;
345 }
346 }
347
348 for (int i = 0; i < state_count; ++i)
349 {
350 for (int j = 0; j < non_terminal_count; ++j)
351 {
352 int &r = GOTO (i, j);
353
354 if (r == defgoto [j])
355 r = 0;
356 }
357 }
358
359 compressed_action (table, state_count, terminal_count);
360 compressed_goto (pgoto, state_count, non_terminal_count);
361
362 delete[] table;
363 table = nullptr;
364
365 delete[] pgoto;
366 pgoto = nullptr;
367
368#undef ACTION
369#undef GOTO
370
371 if (! grammar.merged_output.isEmpty())
372 {
373 QFile f(grammar.merged_output);
374 if (! f.open (QFile::WriteOnly))
375 {
376 fprintf (stderr, "*** cannot create %s\n", qPrintable(grammar.merged_output));
377 return;
378 }
379
380 QTextStream out (&f);
381
382 // copyright headers must come first, otherwise the headers tests will fail
383 if (copyright)
384 {
385 out << copyrightHeader()
386 << privateCopyrightHeader()
387 << Qt::endl;
388 }
389
390 out << "// This file was generated by qlalr - DO NOT EDIT!\n";
391
392 out << startIncludeGuard(grammar.merged_output) << Qt::endl;
393
394 if (copyright) {
395 out << "#if defined(ERROR)" << Qt::endl
396 << "# undef ERROR" << Qt::endl
397 << "#endif" << Qt::endl << Qt::endl;
398 }
399
400 generateDecl (out);
401 generateImpl (out);
402 out << p.decls();
403 out << p.impls();
404 out << Qt::endl;
405
406 out << endIncludeGuard(grammar.merged_output) << Qt::endl;
407
408 return;
409 }
410
411 // default behaviour
412 QString declFileName = grammar.table_name.toLower () + "_p.h"_L1;
413 QString bitsFileName = grammar.table_name.toLower () + ".cpp"_L1;
414
415 { // decls...
416 QFile f (declFileName);
417 if (! f.open (QFile::WriteOnly))
418 {
419 fprintf (stderr, "*** cannot create %s: %s\n",
420 qPrintable(declFileName), qPrintable(f.errorString()));
421 return;
422 }
423 QTextStream out (&f);
424
425 // copyright headers must come first, otherwise the headers tests will fail
426 if (copyright)
427 {
428 out << copyrightHeader()
429 << privateCopyrightHeader()
430 << Qt::endl;
431 }
432
433 out << "// This file was generated by qlalr - DO NOT EDIT!\n";
434
435 out << startIncludeGuard(declFileName) << Qt::endl;
436
437 if (copyright) {
438 out << "#include <QtCore/qglobal.h>" << Qt::endl << Qt::endl;
439 out << "QT_BEGIN_NAMESPACE" << Qt::endl << Qt::endl;
440 }
441 generateDecl (out);
442 if (copyright)
443 out << "QT_END_NAMESPACE" << Qt::endl;
444
445 out << endIncludeGuard(declFileName) << Qt::endl;
446 } // end decls
447
448 { // bits...
449 QFile f (bitsFileName);
450 if (! f.open (QFile::WriteOnly))
451 {
452 fprintf (stderr, "*** cannot create %s: %s\n",
453 qPrintable(bitsFileName), qPrintable(f.errorString()));
454 return;
455 }
456 QTextStream out (&f);
457
458 // copyright headers must come first, otherwise the headers tests will fail
459 if (copyright)
460 out << copyrightHeader();
461
462 out << "// This file was generated by qlalr - DO NOT EDIT!\n";
463
464 out << "#include \"" << declFileName << "\"" << Qt::endl << Qt::endl;
465 if (copyright)
466 out << "QT_BEGIN_NAMESPACE" << Qt::endl << Qt::endl;
467 generateImpl(out);
468 if (copyright)
469 out << "QT_END_NAMESPACE" << Qt::endl;
470
471 } // end bits
472
473 if (! grammar.decl_file_name.isEmpty ())
474 {
475 QFile f (grammar.decl_file_name);
476 if (! f.open (QFile::WriteOnly))
477 {
478 fprintf (stderr, "*** cannot create %s: %s\n",
479 qPrintable(grammar.decl_file_name), qPrintable(f.errorString()));
480 return;
481 }
482 QTextStream out (&f);
483 out << p.decls();
484 }
485
486 if (! grammar.impl_file_name.isEmpty ())
487 {
488 QFile f (grammar.impl_file_name);
489 if (! f.open (QFile::WriteOnly))
490 {
491 fprintf (stderr, "*** cannot create %s: %s\n",
492 qPrintable(grammar.impl_file_name), qPrintable(f.errorString()));
493 return;
494 }
495 QTextStream out (&f);
496 out << p.impls();
497 }
498}
499
500QString CppGenerator::debugInfoProt() const
501{
502 QString prot = "QLALR_NO_"_L1;
503 prot += grammar.table_name.toUpper();
504 prot += "_DEBUG_INFO"_L1;
505 return prot;
506}
507
508void CppGenerator::generateDecl (QTextStream &out)
509{
510 out << "class " << grammar.table_name << Qt::endl
511 << "{" << Qt::endl
512 << "public:" << Qt::endl
513 << " enum VariousConstants {" << Qt::endl;
514
515 for (const Name &t : std::as_const(grammar.terminals))
516 {
517 QString name = *t;
518 int value = std::distance (grammar.names.begin (), t);
519
520 if (name == "$end"_L1)
521 name = "EOF_SYMBOL"_L1;
522
523 else if (name == "$accept"_L1)
524 name = "ACCEPT_SYMBOL"_L1;
525
526 else
527 name.prepend (grammar.token_prefix);
528
529 out << " " << name << " = " << value << "," << Qt::endl;
530 }
531
532 out << Qt::endl
533 << " ACCEPT_STATE = " << accept_state << "," << Qt::endl
534 << " RULE_COUNT = " << grammar.rules.size () << "," << Qt::endl
535 << " STATE_COUNT = " << state_count << "," << Qt::endl
536 << " TERMINAL_COUNT = " << terminal_count << "," << Qt::endl
537 << " NON_TERMINAL_COUNT = " << non_terminal_count << "," << Qt::endl
538 << Qt::endl
539 << " GOTO_INDEX_OFFSET = " << compressed_action.index.size () << "," << Qt::endl
540 << " GOTO_INFO_OFFSET = " << compressed_action.info.size () << "," << Qt::endl
541 << " GOTO_CHECK_OFFSET = " << compressed_action.check.size () << Qt::endl
542 << " };" << Qt::endl
543 << Qt::endl
544 << " static const char *const spell[];" << Qt::endl
545 << " static const short lhs[];" << Qt::endl
546 << " static const short rhs[];" << Qt::endl;
547
548 if (debug_info)
549 {
550 QString prot = debugInfoProt();
551
552 out << Qt::endl << "#ifndef " << prot << Qt::endl
553 << " static const int rule_index[];" << Qt::endl
554 << " static const int rule_info[];" << Qt::endl
555 << "#endif // " << prot << Qt::endl << Qt::endl;
556 }
557
558 out << " static const short goto_default[];" << Qt::endl
559 << " static const short action_default[];" << Qt::endl
560 << " static const short action_index[];" << Qt::endl
561 << " static const short action_info[];" << Qt::endl
562 << " static const short action_check[];" << Qt::endl
563 << Qt::endl
564 << " static inline int nt_action (int state, int nt)" << Qt::endl
565 << " {" << Qt::endl
566 << " const int yyn = action_index [GOTO_INDEX_OFFSET + state] + nt;" << Qt::endl
567 << " if (yyn < 0 || action_check [GOTO_CHECK_OFFSET + yyn] != nt)" << Qt::endl
568 << " return goto_default [nt];" << Qt::endl
569 << Qt::endl
570 << " return action_info [GOTO_INFO_OFFSET + yyn];" << Qt::endl
571 << " }" << Qt::endl
572 << Qt::endl
573 << " static inline int t_action (int state, int token)" << Qt::endl
574 << " {" << Qt::endl
575 << " const int yyn = action_index [state] + token;" << Qt::endl
576 << Qt::endl
577 << " if (yyn < 0 || action_check [yyn] != token)" << Qt::endl
578 << " return - action_default [state];" << Qt::endl
579 << Qt::endl
580 << " return action_info [yyn];" << Qt::endl
581 << " }" << Qt::endl
582 << "};" << Qt::endl
583 << Qt::endl
584 << Qt::endl;
585}
586
587void CppGenerator::generateImpl (QTextStream &out)
588{
589 int idx = 0;
590
591 out << "const char *const " << grammar.table_name << "::spell [] = {";
592 idx = 0;
593
594 QMap<Name, int> name_ids;
595 bool first_nt = true;
596
597 for (Name t = grammar.names.begin (); t != grammar.names.end (); ++t, ++idx)
598 {
599 bool terminal = grammar.isTerminal (t);
600
601 if (! (debug_info || terminal))
602 break;
603
604 name_ids.insert (t, idx);
605
606 generateSeparator(idx, out);
607
608 if (terminal)
609 {
610 QString spell = grammar.spells.value (t);
611
612 if (spell.isEmpty ())
613 out << "nullptr";
614 else
615 out << "\"" << spell << "\"";
616 }
617 else
618 {
619 if (first_nt)
620 {
621 first_nt = false;
622 QString prot = debugInfoProt();
623 out << Qt::endl << "#ifndef " << prot << Qt::endl;
624 }
625 out << "\"" << *t << "\"";
626 }
627 }
628
629 if (debug_info)
630 out << Qt::endl << "#endif // " << debugInfoProt() << Qt::endl;
631
632 out << Qt::endl << "};" << Qt::endl << Qt::endl;
633
634 out << "const short " << grammar.table_name << "::lhs [] = {";
635 idx = 0;
636 for (RulePointer rule = grammar.rules.begin (); rule != grammar.rules.end (); ++rule, ++idx)
637 {
638 generateSeparator(idx, out);
639
640 out << aut.id (rule->lhs);
641 }
642 out << Qt::endl << "};" << Qt::endl << Qt::endl;
643
644 out << "const short " << grammar.table_name << "::rhs [] = {";
645 idx = 0;
646 for (RulePointer rule = grammar.rules.begin (); rule != grammar.rules.end (); ++rule, ++idx)
647 {
648 generateSeparator(idx, out);
649
650 out << rule->rhs.size ();
651 }
652 out << Qt::endl << "};" << Qt::endl << Qt::endl;
653
654 if (debug_info)
655 {
656 QString prot = debugInfoProt();
657
658 out << Qt::endl << "#ifndef " << prot << Qt::endl;
659 out << "const int " << grammar.table_name << "::rule_info [] = {";
660 idx = 0;
661 for (auto rule = grammar.rules.cbegin (); rule != grammar.rules.cend (); ++rule, ++idx)
662 {
663 generateSeparator(idx, out);
664
665 out << name_ids.value(rule->lhs);
666
667 for (const Name &n : rule->rhs)
668 out << ", " << name_ids.value (n);
669 }
670 out << Qt::endl << "};" << Qt::endl << Qt::endl;
671
672 out << "const int " << grammar.table_name << "::rule_index [] = {";
673 idx = 0;
674 size_t offset = 0;
675 for (RulePointer rule = grammar.rules.begin (); rule != grammar.rules.end (); ++rule, ++idx)
676 {
677 generateSeparator(idx, out);
678
679 out << offset;
680 offset += rule->rhs.size () + 1;
681 }
682 out << Qt::endl << "};" << Qt::endl
683 << "#endif // " << prot << Qt::endl << Qt::endl;
684 }
685
686 out << "const short " << grammar.table_name << "::action_default [] = {";
687 idx = 0;
688 for (StatePointer state = aut.states.begin (); state != aut.states.end (); ++state, ++idx)
689 {
690 generateSeparator(idx, out);
691
692 if (state->defaultReduce != grammar.rules.end ())
693 out << aut.id (state->defaultReduce);
694 else
695 out << "0";
696 }
697 out << Qt::endl << "};" << Qt::endl << Qt::endl;
698
699 out << "const short " << grammar.table_name << "::goto_default [] = {";
700 generateList(defgoto, out);
701 out << Qt::endl << "};" << Qt::endl << Qt::endl;
702
703 out << "const short " << grammar.table_name << "::action_index [] = {";
704 generateList(compressed_action.index, out);
705 out << "," << Qt::endl;
706 generateList(compressed_goto.index, out);
707 out << Qt::endl << "};" << Qt::endl << Qt::endl;
708
709 out << "const short " << grammar.table_name << "::action_info [] = {";
710 generateList(compressed_action.info, out);
711 out << "," << Qt::endl;
712 generateList(compressed_goto.info, out);
713 out << Qt::endl << "};" << Qt::endl << Qt::endl;
714
715 out << "const short " << grammar.table_name << "::action_check [] = {";
716 generateList(compressed_action.check, out);
717 out << "," << Qt::endl;
718 generateList(compressed_goto.check, out);
719 out << Qt::endl << "};" << Qt::endl << Qt::endl;
720}
int id(RulePointer rule)
Definition lalr.cpp:243
StateList states
Definition lalr.h:352
int expected_reduce_reduce
Definition lalr.h:249
NameSet terminals
Definition lalr.h:239
debug_infot rules
Definition lalr.h:242
bool isNonTerminal(Name name) const
Definition lalr.h:225
RulePointer goal
Definition lalr.h:244
NameSet non_terminals
Definition lalr.h:240
int expected_shift_reduce
Definition lalr.h:248
#define GOTO(i, j)
#define ACTION(i, j)
static QString includeGuard(const QString &fileName)
QT_FORWARD_DECLARE_CLASS(QTextStream)
std::set< Name > NameSet
Definition lalr.h:30
ItemList::iterator ItemPointer
Definition lalr.h:34
StateList::iterator StatePointer
Definition lalr.h:43
debug_infot::iterator RulePointer
Definition lalr.h:38