File indexing completed on 2024-09-15 09:10:09
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-or-later 0003 SPDX-FileCopyrightText: 2009-2012 Alexander Rieder <alexanderrieder@gmail.com> 0004 SPDX-FileCopyrightText: 2017-2022 Alexander Semke (alexander.semke@web.de) 0005 */ 0006 0007 #include "maximasession.h" 0008 #include "maximaexpression.h" 0009 #include "maximacompletionobject.h" 0010 #include "maximasyntaxhelpobject.h" 0011 #include "maximahighlighter.h" 0012 #include "maximavariablemodel.h" 0013 #include "result.h" 0014 #include "settings.h" 0015 0016 #include <QDebug> 0017 #include <QTimer> 0018 #include <QStandardPaths> 0019 0020 #include <KMessageBox> 0021 #include <KLocalizedString> 0022 0023 #ifndef Q_OS_WIN 0024 #include <signal.h> 0025 #endif 0026 0027 0028 //NOTE: the \\s in the expressions is needed, because Maxima seems to sometimes insert newlines/spaces between the letters 0029 //maybe this is caused by some behaviour if the Prompt is split into multiple "readStdout" calls 0030 //the Expressions are encapsulated in () to allow capturing for the text 0031 const QRegularExpression MaximaSession::MaximaOutputPrompt = 0032 QRegularExpression(QStringLiteral("(\\(\\s*%\\s*o\\s*[0-9\\s]*\\))")); //Text, maxima outputs, before any output 0033 const QRegularExpression MaximaSession::MaximaInputPrompt = 0034 QRegularExpression(QStringLiteral("(\\(\\s*%\\s*i\\s*[0-9\\s]*\\))")); 0035 0036 0037 MaximaSession::MaximaSession( Cantor::Backend* backend ) : Session(backend) 0038 { 0039 setVariableModel(new MaximaVariableModel(this)); 0040 } 0041 0042 void MaximaSession::login() 0043 { 0044 qDebug()<<"login"; 0045 0046 if (m_process) 0047 return; //TODO: why do we call login() again?!? 0048 0049 emit loginStarted(); 0050 QStringList arguments; 0051 arguments << QLatin1String("--quiet"); //Suppress Maxima start-up message 0052 const QString initFile = locateCantorFile(QLatin1String("maximabackend/cantor-initmaxima.lisp")); 0053 arguments << QLatin1String("--init-lisp=") + initFile; //Set the name of the Lisp initialization file 0054 0055 m_process = new QProcess(this); 0056 m_process->start(MaximaSettings::self()->path().toLocalFile(), arguments); 0057 m_process->waitForStarted(); 0058 0059 // Wait until first maxima prompt 0060 QString input; 0061 while (!input.contains(QLatin1String("</cantor-prompt>"))) 0062 { 0063 m_process->waitForReadyRead(); 0064 input += QString::fromLatin1(m_process->readAllStandardOutput()); 0065 qDebug() << input; 0066 } 0067 0068 connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(restartMaxima())); 0069 connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdOut())); 0070 connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); 0071 connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); 0072 0073 //enable latex typesetting if needed 0074 const QString& val = QLatin1String((isTypesettingEnabled() ? "t":"nil")); 0075 evaluateExpression(QString::fromLatin1(":lisp(setf $display2d %1)").arg(val), Cantor::Expression::DeleteOnFinish, true); 0076 0077 //auto-run scripts 0078 if(!MaximaSettings::self()->autorunScripts().isEmpty()){ 0079 QString autorunScripts = MaximaSettings::self()->autorunScripts().join(QLatin1String(";")); 0080 autorunScripts.append(QLatin1String(";kill(labels)")); // Reset labels after running autorun scripts 0081 evaluateExpression(autorunScripts, MaximaExpression::DeleteOnFinish, true); 0082 updateVariables(); 0083 } 0084 0085 changeStatus(Session::Done); 0086 emit loginDone(); 0087 qDebug()<<"login done"; 0088 } 0089 0090 void MaximaSession::logout() 0091 { 0092 qDebug()<<"logout"; 0093 0094 if(!m_process) 0095 return; 0096 0097 disconnect(m_process, nullptr, this, nullptr); 0098 0099 if (status() == Cantor::Session::Running) 0100 interrupt(); 0101 0102 write(QLatin1String("quit();\n")); 0103 qDebug()<<"waiting for maxima to finish"; 0104 0105 if(!m_process->waitForFinished(1000)) 0106 { 0107 m_process->kill(); 0108 qDebug()<<"maxima still running, process kill enforced"; 0109 } 0110 m_process->deleteLater(); 0111 m_process = nullptr; 0112 0113 Session::logout(); 0114 0115 qDebug()<<"logout done"; 0116 } 0117 0118 MaximaSession::Mode MaximaSession::mode() const { 0119 return m_mode; 0120 } 0121 0122 void MaximaSession::setMode(MaximaSession::Mode mode) 0123 { 0124 m_mode = mode; 0125 } 0126 0127 Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) 0128 { 0129 qDebug() << "evaluating: " << cmd; 0130 auto* expr = new MaximaExpression(this, internal); 0131 expr->setFinishingBehavior(behave); 0132 expr->setCommand(cmd); 0133 expr->evaluate(); 0134 0135 return expr; 0136 } 0137 0138 void MaximaSession::readStdErr() 0139 { 0140 qDebug()<<"reading stdErr"; 0141 if (!m_process) 0142 return; 0143 QString out = QString::fromLocal8Bit(m_process->readAllStandardError()); 0144 0145 if(expressionQueue().size()>0) 0146 { 0147 auto* expr = expressionQueue().first(); 0148 expr->parseError(out); 0149 } 0150 } 0151 0152 void MaximaSession::readStdOut() 0153 { 0154 QString out = QString::fromLocal8Bit(m_process->readAllStandardOutput()); 0155 m_cache += out; 0156 0157 //collect the multi-line output until Maxima has finished the calculation and returns a new promt 0158 if ( !out.contains(QLatin1String("</cantor-prompt>")) ) 0159 return; 0160 0161 if(expressionQueue().isEmpty()) 0162 { 0163 //queue is empty, interrupt was called, nothing to do here 0164 qDebug()<<m_cache; 0165 m_cache.clear(); 0166 return; 0167 } 0168 0169 auto* expr = expressionQueue().first(); 0170 if (!expr) 0171 return; //should never happen 0172 0173 qDebug()<<"output: " << m_cache; 0174 expr->parseOutput(m_cache); 0175 m_cache.clear(); 0176 } 0177 0178 void MaximaSession::reportProcessError(QProcess::ProcessError e) 0179 { 0180 qDebug()<<"process error"<<e; 0181 if(e==QProcess::FailedToStart) 0182 { 0183 changeStatus(Cantor::Session::Done); 0184 emit error(i18n("Failed to start Maxima")); 0185 } 0186 } 0187 0188 void MaximaSession::runFirstExpression() 0189 { 0190 qDebug()<<"running next expression"; 0191 if (!m_process) 0192 return; 0193 0194 if(!expressionQueue().isEmpty()) 0195 { 0196 auto* expr = expressionQueue().first(); 0197 const auto& command = expr->internalCommand(); 0198 connect(expr, &Cantor::Expression::statusChanged, this, &Session::currentExpressionStatusChanged); 0199 0200 if(command.isEmpty()) 0201 { 0202 qDebug()<<"empty command"; 0203 static_cast<MaximaExpression*>(expr)->forceDone(); 0204 } 0205 else 0206 { 0207 expr->setStatus(Cantor::Expression::Computing); 0208 m_cache.clear(); 0209 write(command + QLatin1Char('\n')); 0210 } 0211 } 0212 } 0213 0214 void MaximaSession::interrupt() 0215 { 0216 if(!expressionQueue().isEmpty()) 0217 { 0218 qDebug()<<"interrupting " << expressionQueue().first()->command(); 0219 if(m_process && m_process->state() != QProcess::NotRunning) 0220 { 0221 #ifndef Q_OS_WIN 0222 const int pid = m_process->processId(); 0223 kill(pid, SIGINT); 0224 #else 0225 ; //TODO: interrupt the process on windows 0226 #endif 0227 } 0228 for (auto* expression : expressionQueue()) 0229 expression->setStatus(Cantor::Expression::Interrupted); 0230 0231 expressionQueue().clear(); 0232 0233 qDebug()<<"done interrupting"; 0234 } 0235 0236 changeStatus(Cantor::Session::Done); 0237 m_cache.clear(); 0238 } 0239 0240 void MaximaSession::sendInputToProcess(const QString& input) 0241 { 0242 write(input); 0243 } 0244 0245 void MaximaSession::restartMaxima() 0246 { 0247 qDebug()<<"restarting maxima cooldown: " << m_justRestarted; 0248 0249 if(!m_justRestarted) 0250 { 0251 emit error(i18n("Maxima crashed. restarting...")); 0252 //remove the command that caused maxima to crash (to avoid infinite loops) 0253 if(!expressionQueue().isEmpty()) 0254 expressionQueue().removeFirst(); 0255 0256 m_justRestarted=true; 0257 QTimer::singleShot(1000, this, SLOT(restartsCooledDown())); 0258 0259 disconnect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(restartMaxima())); 0260 login(); 0261 }else 0262 { 0263 if(!expressionQueue().isEmpty()) 0264 expressionQueue().removeFirst(); 0265 KMessageBox::error(nullptr, i18n("Maxima crashed twice within a short time. Stopping to try starting"), i18n("Error - Cantor")); 0266 } 0267 } 0268 0269 void MaximaSession::restartsCooledDown() 0270 { 0271 qDebug()<<"maxima restart cooldown"; 0272 m_justRestarted=false; 0273 } 0274 0275 void MaximaSession::setTypesettingEnabled(bool enable) 0276 { 0277 if (m_process) 0278 { 0279 //we use the lisp command to set the variable, as those commands 0280 //don't mess with the labels and history 0281 const QString& val = QLatin1String((enable ? "t":"nil")); 0282 evaluateExpression(QString::fromLatin1(":lisp(setf $display2d %1)").arg(val), Cantor::Expression::DeleteOnFinish, true); 0283 } 0284 0285 Cantor::Session::setTypesettingEnabled(enable); 0286 } 0287 0288 Cantor::CompletionObject* MaximaSession::completionFor(const QString& command, int index) 0289 { 0290 return new MaximaCompletionObject(command, index, this); 0291 } 0292 0293 Cantor::SyntaxHelpObject* MaximaSession::syntaxHelpFor(const QString& command) 0294 { 0295 return new MaximaSyntaxHelpObject(command, this); 0296 } 0297 0298 QSyntaxHighlighter* MaximaSession::syntaxHighlighter(QObject* parent) 0299 { 0300 return new MaximaHighlighter(parent, this); 0301 } 0302 0303 void MaximaSession::write(const QString& exp) { 0304 qDebug()<<"################################## EXPRESSION START ###############################################"; 0305 qDebug()<<"sending expression to maxima process: " << exp; 0306 m_process->write(exp.toUtf8()); 0307 }