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