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