1 /*
2 SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de>
3 SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6 */
7
8 #ifndef KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H
9 #define KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H
10
11 #include <cstdlib>
12 #include <cstdio>
13
14 #ifndef Q_OS_WIN
15 #include <unistd.h> // for isatty
16 #endif
17
18 #include <tests/autotestshell.h>
19 #include <language/duchain/duchain.h>
20 #include <language/duchain/problem.h>
21 #include <language/codegen/coderepresentation.h>
22 #include <tests/testcore.h>
23
24 #include <KAboutData>
25 #include <KLocalizedString>
26
27 #include <QCoreApplication>
28 #include <QCommandLineParser>
29 #include <QCommandLineOption>
30 #include <QTextStream>
31
32 namespace KDevelopUtils {
33 QTextStream qout(stdout);
34 QTextStream qerr(stderr);
35 QTextStream qin(stdin);
36
37 using TokenTextFunc = QString (*)(int);
38 /**
39 * This class is a pure helper to use for binaries that you can
40 * run on short snippets of test code or whole files and let
41 * it print the generated tokens or AST.
42 *
43 * It should work fine for any KDevelop-PG-Qt based parser.
44 *
45 *
46 * @tparam SessionT the parse session for your language.
47 * @tparam TokenStreamT the token stream for your language, based on KDevPG::TokenStreamBase.
48 * @tparam TokenT the token class for your language, based on KDevPG::Token.
49 * @tparam LexerT the Lexer for your language.
50 * @tparam StartAstT the AST node that is returned from @c SessionT::parse().
51 * @tparam DebugVisitorT the debug visitor for your language.
52 * @tparam TokenTextT function pointer to the function that returns a string representation for an integral token.
53 */
54 template <class SessionT, class TokenStreamT, class TokenT, class LexerT,
55 class StartAstT, class DebugVisitorT, TokenTextFunc TokenTextT>
56 class DebugLanguageParserHelper
57 {
58 using TextStreamFunction = QTextStream& (*)(QTextStream&);
59 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
60 static constexpr TextStreamFunction endl = Qt::endl;
61 #else
62 static constexpr TextStreamFunction endl = ::endl;
63 #endif
64
65 public:
DebugLanguageParserHelper(const bool printAst,const bool printTokens)66 DebugLanguageParserHelper(const bool printAst, const bool printTokens)
67 : m_printAst(printAst)
68 , m_printTokens(printTokens)
69 {
70 m_session.setDebug(printAst);
71 }
72
73 /// parse contents of a file
parseFile(const QString & fileName)74 void parseFile(const QString& fileName)
75 {
76 if (!m_session.readFile(fileName, "utf-8")) {
77 qerr << "Can't open file " << fileName << endl;
78 std::exit(255);
79 } else {
80 qout << "Parsing file " << fileName << endl;
81 }
82 runSession();
83 }
84
85 /// parse code directly
parseCode(const QString & code)86 void parseCode(const QString& code)
87 {
88 m_session.setContents(code);
89
90 qout << "Parsing input" << endl;
91 runSession();
92 }
93
94 private:
95 /**
96 * actually run the parse session
97 */
runSession()98 void runSession()
99 {
100 if (m_printTokens) {
101 TokenStreamT tokenStream;
102 LexerT lexer(&tokenStream, m_session.contents());
103 int token;
104 while ((token = lexer.nextTokenKind())) {
105 TokenT& t = tokenStream.push();
106 t.begin = lexer.tokenBegin();
107 t.end = lexer.tokenEnd();
108 t.kind = token;
109 printToken(token, lexer);
110 }
111 printToken(token, lexer);
112 if (tokenStream.size() > 0) {
113 qint64 line;
114 qint64 column;
115 tokenStream.endPosition(tokenStream.size() - 1, &line, &column);
116 qDebug() << "last token endPosition: line" << line << "column" << column;
117 } else {
118 qDebug() << "empty token stream";
119 }
120 }
121
122 StartAstT* ast = 0;
123 if (!m_session.parse(&ast)) {
124 qerr << "no AST tree could be generated" << endl;
125 } else {
126 qout << "AST tree successfully generated" << endl;
127 if (m_printAst) {
128 DebugVisitorT debugVisitor(m_session.tokenStream(), m_session.contents());
129 debugVisitor.visitStart(ast);
130 }
131 }
132 const auto problems = m_session.problems();
133 if (!problems.isEmpty()) {
134 qout << endl << "problems encountered during parsing:" << endl;
135 for (auto& p : problems) {
136 qout << p->description() << endl;
137 }
138 } else {
139 qout << "no problems encountered during parsing" << endl;
140 }
141
142 if (!ast) {
143 exit(255);
144 }
145 }
146
printToken(int token,const LexerT & lexer)147 void printToken(int token, const LexerT& lexer) const
148 {
149 int begin = lexer.tokenBegin();
150 int end = lexer.tokenEnd();
151 qout << m_session.contents().mid(begin, end - begin + 1).replace('\n', "\\n")
152 << ' ' << TokenTextT(token) << endl;
153 }
154
155 SessionT m_session;
156 const bool m_printAst;
157 const bool m_printTokens;
158 };
159
160 template <class ParserT>
setupCustomArgs(QCommandLineParser * parser)161 void setupCustomArgs(QCommandLineParser* parser)
162 {
163 Q_UNUSED(parser);
164 }
165
166 template <class ParserT>
setCustomArgs(ParserT * parser,QCommandLineParser * commandLineParser)167 void setCustomArgs(ParserT* parser, QCommandLineParser* commandLineParser)
168 {
169 Q_UNUSED(parser);
170 Q_UNUSED(commandLineParser);
171 }
172
173 /// call this after setting up @p aboutData in your @c main() function.
174 template <class ParserT>
initAndRunParser(KAboutData & aboutData,int argc,char * argv[])175 int initAndRunParser(KAboutData& aboutData, int argc, char* argv[])
176 {
177 qout.setCodec("UTF-8");
178 qerr.setCodec("UTF-8");
179 qin.setCodec("UTF-8");
180
181 QCoreApplication app(argc, argv);
182
183 KAboutData::setApplicationData(aboutData);
184
185 QCommandLineParser parser;
186 aboutData.setupCommandLine(&parser);
187
188 parser.addPositionalArgument("files",
189 i18n(
190 "files or - to read from STDIN, the latter is the default if nothing is provided"),
191 "[FILE...]");
192
193 parser.addOption(QCommandLineOption{QStringList{"a", "print-ast"}, i18n("print generated AST tree")});
194 parser.addOption(QCommandLineOption{QStringList{"t", "print-tokens"}, i18n("print generated token stream")});
195 parser.addOption(QCommandLineOption{QStringList{"c", "code"}, i18n("code to parse"), "code"});
196 setupCustomArgs<ParserT>(&parser);
197
198 parser.process(app);
199 aboutData.processCommandLine(&parser);
200
201 QStringList files = parser.positionalArguments();
202 bool printAst = parser.isSet("print-ast");
203 bool printTokens = parser.isSet("print-tokens");
204
205 KDevelop::AutoTestShell::init();
206 KDevelop::TestCore::initialize(KDevelop::Core::NoUi);
207
208 KDevelop::DUChain::self()->disablePersistentStorage();
209 KDevelop::CodeRepresentation::setDiskChangesForbidden(true);
210
211 ParserT parserT(printAst, printTokens);
212 setCustomArgs(&parserT, &parser);
213
214 if (parser.isSet("code")) {
215 parserT.parseCode(parser.value("code"));
216 } else if (files.isEmpty()) {
217 files << "-";
218 }
219
220 for (const QString& fileName : qAsConst(files)) {
221 if (fileName == "-") {
222 #ifndef Q_OS_WIN
223 if (isatty(STDIN_FILENO)) {
224 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
225 qerr << "no STDIN given" << Qt::endl;
226 #else
227 qerr << "no STDIN given" << endl;
228 #endif
229 return 255;
230 }
231 #endif
232 parserT.parseCode(qin.readAll().toUtf8());
233 } else {
234 parserT.parseFile(QFileInfo(fileName).absoluteFilePath());
235 }
236 }
237
238 KDevelop::TestCore::shutdown();
239
240 return 0;
241 }
242 }
243
244 #endif // KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H
245