File indexing completed on 2024-05-12 04:38:08

0001 /*
0002     SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de>
0003     SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #ifndef KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H
0009 #define KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H
0010 
0011 #include <cstdlib>
0012 #include <cstdio>
0013 
0014 #ifndef Q_OS_WIN
0015 #include <unistd.h> // for isatty
0016 #endif
0017 
0018 #include <tests/autotestshell.h>
0019 #include <language/duchain/duchain.h>
0020 #include <language/duchain/problem.h>
0021 #include <language/codegen/coderepresentation.h>
0022 #include <tests/testcore.h>
0023 
0024 #include <KAboutData>
0025 #include <KLocalizedString>
0026 
0027 #include <QCoreApplication>
0028 #include <QCommandLineParser>
0029 #include <QCommandLineOption>
0030 #include <QTextStream>
0031 
0032 namespace KDevelopUtils {
0033 QTextStream qout(stdout);
0034 QTextStream qerr(stderr);
0035 QTextStream qin(stdin);
0036 
0037 using TokenTextFunc = QString (*)(int);
0038 /**
0039  * This class is a pure helper to use for binaries that you can
0040  * run on short snippets of test code or whole files and let
0041  * it print the generated tokens or AST.
0042  *
0043  * It should work fine for any KDevelop-PG-Qt based parser.
0044  *
0045  *
0046  * @tparam SessionT the parse session for your language.
0047  * @tparam TokenStreamT the token stream for your language, based on KDevPG::TokenStreamBase.
0048  * @tparam TokenT the token class for your language, based on KDevPG::Token.
0049  * @tparam LexerT the Lexer for your language.
0050  * @tparam StartAstT the AST node that is returned from @c SessionT::parse().
0051  * @tparam DebugVisitorT the debug visitor for your language.
0052  * @tparam TokenTextT function pointer to the function that returns a string representation for an integral token.
0053  */
0054 template <class SessionT, class TokenStreamT, class TokenT, class LexerT,
0055     class StartAstT, class DebugVisitorT, TokenTextFunc TokenTextT>
0056 class DebugLanguageParserHelper
0057 {
0058 public:
0059     DebugLanguageParserHelper(const bool printAst, const bool printTokens)
0060         : m_printAst(printAst)
0061         , m_printTokens(printTokens)
0062     {
0063         m_session.setDebug(printAst);
0064     }
0065 
0066     /// parse contents of a file
0067     void parseFile(const QString& fileName)
0068     {
0069         if (!m_session.readFile(fileName, "utf-8")) {
0070             qerr << "Can't open file " << fileName << Qt::endl;
0071             std::exit(255);
0072         } else {
0073             qout << "Parsing file " << fileName << Qt::endl;
0074         }
0075         runSession();
0076     }
0077 
0078     /// parse code directly
0079     void parseCode(const QString& code)
0080     {
0081         m_session.setContents(code);
0082 
0083         qout << "Parsing input" << Qt::endl;
0084         runSession();
0085     }
0086 
0087 private:
0088     /**
0089      * actually run the parse session
0090      */
0091     void runSession()
0092     {
0093         if (m_printTokens) {
0094             TokenStreamT tokenStream;
0095             LexerT lexer(&tokenStream, m_session.contents());
0096             int token;
0097             while ((token = lexer.nextTokenKind())) {
0098                 TokenT& t = tokenStream.push();
0099                 t.begin = lexer.tokenBegin();
0100                 t.end = lexer.tokenEnd();
0101                 t.kind = token;
0102                 printToken(token, lexer);
0103             }
0104             printToken(token, lexer);
0105             if (tokenStream.size() > 0) {
0106                 qint64 line;
0107                 qint64 column;
0108                 tokenStream.endPosition(tokenStream.size() - 1, &line, &column);
0109                 qDebug() << "last token endPosition: line" << line << "column" << column;
0110             } else {
0111                 qDebug() << "empty token stream";
0112             }
0113         }
0114 
0115         StartAstT* ast = 0;
0116         if (!m_session.parse(&ast)) {
0117             qerr << "no AST tree could be generated" << Qt::endl;
0118         } else {
0119             qout << "AST tree successfully generated" << Qt::endl;
0120             if (m_printAst) {
0121                 DebugVisitorT debugVisitor(m_session.tokenStream(), m_session.contents());
0122                 debugVisitor.visitStart(ast);
0123             }
0124         }
0125         const auto problems = m_session.problems();
0126         if (!problems.isEmpty()) {
0127             qout << "\nproblems encountered during parsing:" << Qt::endl;
0128             for (auto& p : problems) {
0129                 qout << p->description() << Qt::endl;
0130             }
0131         } else {
0132             qout << "no problems encountered during parsing" << Qt::endl;
0133         }
0134 
0135         if (!ast) {
0136             exit(255);
0137         }
0138     }
0139 
0140     void printToken(int token, const LexerT& lexer) const
0141     {
0142         int begin = lexer.tokenBegin();
0143         int end = lexer.tokenEnd();
0144         qout << m_session.contents().mid(begin, end - begin + 1).replace('\n', "\\n") << ' ' << TokenTextT(token)
0145              << Qt::endl;
0146     }
0147 
0148     SessionT m_session;
0149     const bool m_printAst;
0150     const bool m_printTokens;
0151 };
0152 
0153 template <class ParserT>
0154 void setupCustomArgs(QCommandLineParser* parser)
0155 {
0156     Q_UNUSED(parser);
0157 }
0158 
0159 template <class ParserT>
0160 void setCustomArgs(ParserT* parser, QCommandLineParser* commandLineParser)
0161 {
0162     Q_UNUSED(parser);
0163     Q_UNUSED(commandLineParser);
0164 }
0165 
0166 /// call this after setting up @p aboutData in your @c main() function.
0167 template <class ParserT>
0168 int initAndRunParser(KAboutData& aboutData, int argc, char* argv[])
0169 {
0170     qout.setCodec("UTF-8");
0171     qerr.setCodec("UTF-8");
0172     qin.setCodec("UTF-8");
0173 
0174     QCoreApplication app(argc, argv);
0175 
0176     KAboutData::setApplicationData(aboutData);
0177 
0178     QCommandLineParser parser;
0179     aboutData.setupCommandLine(&parser);
0180 
0181     parser.addPositionalArgument("files",
0182                                  i18n(
0183                                      "files or - to read from STDIN, the latter is the default if nothing is provided"),
0184                                  "[FILE...]");
0185 
0186     parser.addOption(QCommandLineOption{QStringList{"a", "print-ast"}, i18n("print generated AST tree")});
0187     parser.addOption(QCommandLineOption{QStringList{"t", "print-tokens"}, i18n("print generated token stream")});
0188     parser.addOption(QCommandLineOption{QStringList{"c", "code"}, i18n("code to parse"), "code"});
0189     setupCustomArgs<ParserT>(&parser);
0190 
0191     parser.process(app);
0192     aboutData.processCommandLine(&parser);
0193 
0194     QStringList files = parser.positionalArguments();
0195     bool printAst = parser.isSet("print-ast");
0196     bool printTokens = parser.isSet("print-tokens");
0197 
0198     KDevelop::AutoTestShell::init();
0199     KDevelop::TestCore::initialize(KDevelop::Core::NoUi);
0200 
0201     KDevelop::DUChain::self()->disablePersistentStorage();
0202     KDevelop::CodeRepresentation::setDiskChangesForbidden(true);
0203 
0204     ParserT parserT(printAst, printTokens);
0205     setCustomArgs(&parserT, &parser);
0206 
0207     if (parser.isSet("code")) {
0208         parserT.parseCode(parser.value("code"));
0209     } else if (files.isEmpty()) {
0210         files << "-";
0211     }
0212 
0213     for (const QString& fileName : qAsConst(files)) {
0214         if (fileName == "-") {
0215 #ifndef Q_OS_WIN
0216             if (isatty(STDIN_FILENO)) {
0217                 qerr << "no STDIN given" << Qt::endl;
0218                 return 255;
0219             }
0220 #endif
0221             parserT.parseCode(qin.readAll().toUtf8());
0222         } else {
0223             parserT.parseFile(QFileInfo(fileName).absoluteFilePath());
0224         }
0225     }
0226 
0227     KDevelop::TestCore::shutdown();
0228 
0229     return 0;
0230 }
0231 }
0232 
0233 #endif // KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H