diff --git a/src/backends/octave/octaveexpression.cpp b/src/backends/octave/octaveexpression.cpp index 5327be57..5a325dd4 100644 --- a/src/backends/octave/octaveexpression.cpp +++ b/src/backends/octave/octaveexpression.cpp @@ -1,223 +1,224 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "octaveexpression.h" #include "octavesession.h" #include "defaultvariablemodel.h" #include "textresult.h" #include #include #include #include +#include #include #include static const QLatin1String printCommandBegin("cantor_print('"); static const QLatin1String printCommandEnd("');"); static const QStringList plotCommands({ QLatin1String("plot"), QLatin1String("semilogx"), QLatin1String("semilogy"), QLatin1String("loglog"), QLatin1String("polar"), QLatin1String("contour"), QLatin1String("bar"), QLatin1String("stairs"), QLatin1String("errorbar"), QLatin1String("sombrero"), QLatin1String("hist"), QLatin1String("fplot"), QLatin1String("imshow"), QLatin1String("stem"), QLatin1String("stem3"), QLatin1String("scatter"), QLatin1String("pareto"), QLatin1String("rose"), QLatin1String("pie"), QLatin1String("quiver"), QLatin1String("compass"), QLatin1String("feather"), QLatin1String("pcolor"), QLatin1String("area"), QLatin1String("fill"), QLatin1String("comet"), QLatin1String("plotmatrix"), /* 3d-plots */ QLatin1String("plot3"), QLatin1String("mesh"), QLatin1String("meshc"), QLatin1String("meshz"), QLatin1String("surf"), QLatin1String("surfc"), QLatin1String("surfl"), QLatin1String("surfnorm"), QLatin1String("isosurface"), QLatin1String("isonormals"), QLatin1String("isocaps"), /* 3d-plots defined by a function */ QLatin1String("ezplot3"), QLatin1String("ezmesh"), QLatin1String("ezmeshc"), QLatin1String("ezsurf"), QLatin1String("ezsurfc"), QLatin1String("cantor_plot2d"), QLatin1String("cantor_plot3d")}); OctaveExpression::OctaveExpression(Cantor::Session* session, bool internal): Expression(session, internal) { } OctaveExpression::~OctaveExpression() { if(m_tempFile) { delete m_tempFile; m_tempFile = nullptr; } } void OctaveExpression::interrupt() { qDebug() << "interrupt"; setStatus(Interrupted); } void OctaveExpression::evaluate() { if(m_tempFile) { delete m_tempFile; m_tempFile = nullptr; } qDebug() << "evaluate"; QString cmd = command(); - QStringList cmdWords = cmd.split(QRegExp(QLatin1String("\\b")), QString::SkipEmptyParts); + QStringList cmdWords = cmd.split(QRegularExpression(QStringLiteral("\\b")), QString::SkipEmptyParts); if (!cmdWords.contains(QLatin1String("help")) && !cmdWords.contains(QLatin1String("completion_matches"))) { for (const QString& plotCmd : plotCommands) { if (cmdWords.contains(plotCmd)) { qDebug() << "Executing a plot command"; #ifdef WITH_EPS QLatin1String ext(".eps"); #else QLatin1String ext(".png"); #endif m_tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_octave-XXXXXX")+ext); m_tempFile->open(); qDebug() << "plot temp file" << m_tempFile->fileName(); QFileSystemWatcher* watcher = fileWatcher(); if (!watcher->files().isEmpty()) watcher->removePaths(watcher->files()); watcher->addPath(m_tempFile->fileName()); connect(watcher, &QFileSystemWatcher::fileChanged, this, &OctaveExpression::imageChanged, Qt::UniqueConnection); m_plotPending = true; break; } } } m_finished = false; session()->enqueueExpression(this); } QString OctaveExpression::internalCommand() { QString cmd = command(); if (m_plotPending) { if (!cmd.endsWith(QLatin1Char(';')) && !cmd.endsWith(QLatin1Char(','))) cmd += QLatin1Char(','); cmd += printCommandBegin + m_tempFile->fileName() + printCommandEnd; } // We need remove all comments here, because below we merge all strings to one long string // Otherwise, all code after line with comment will be commented out after merging // So, this small state machine remove all comments // FIXME better implementation QString tmp; // 0 - command mode, 1 - string mode for ', 2 - string mode for ", 3 - comment mode int status = 0; for (int i = 0; i < cmd.size(); i++) { const char ch = cmd[i].toLatin1(); if (status == 0 && (ch == '#' || ch == '%')) status = 3; else if (status == 0 && ch == '\'') status = 1; else if (status == 0 && ch == '"') status = 2; else if (status == 1 && ch == '\'') status = 0; else if (status == 2 && ch == '"') status = 0; else if (status == 3 && ch == '\n') status = 0; if (status != 3) tmp += cmd[i]; } cmd = tmp; cmd.replace(QLatin1String(";\n"), QLatin1String(";")); cmd.replace(QLatin1Char('\n'), QLatin1Char(',')); cmd += QLatin1Char('\n'); return cmd; } void OctaveExpression::parseOutput(const QString& output) { qDebug() << "parseOutput: " << output; if (!output.trimmed().isEmpty()) { // TODO: what about help in comment? printf with '... help ...'? // This must be corrected. if (command().contains(QLatin1String("help"))) { addResult(new Cantor::HelpResult(output)); } else { addResult(new Cantor::TextResult(output)); } } m_finished = true; if (!m_plotPending) setStatus(Done); } void OctaveExpression::parseError(const QString& error) { if (error.startsWith(QLatin1String("warning: "))) { // It's warning, so add as result addResult(new Cantor::TextResult(error)); } else { setErrorMessage(error); setStatus(Error); } } void OctaveExpression::imageChanged() { if(m_tempFile->size() <= 0) return; OctavePlotResult* newResult = new OctavePlotResult(QUrl::fromLocalFile(m_tempFile->fileName())); bool found = false; for (int i = 0; i < results().size(); i++) if (results()[i]->type() == newResult->type()) { replaceResult(i, newResult); found = true; } if (!found) addResult(newResult); m_plotPending = false; if (m_finished && status() != Expression::Done) { setStatus(Done); } } diff --git a/src/backends/octave/octavesession.cpp b/src/backends/octave/octavesession.cpp index 2c0e7923..d1f75873 100644 --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -1,340 +1,343 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "octavesession.h" #include "octaveexpression.h" #include "octavecompletionobject.h" #include "octavesyntaxhelpobject.h" #include "octavehighlighter.h" #include "result.h" #include "textresult.h" #include "settings.h" #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include "octavevariablemodel.h" -const QRegExp OctaveSession::PROMPT_UNCHANGEABLE_COMMAND = QRegExp(QLatin1String("(,|;)+")); +const QRegularExpression OctaveSession::PROMPT_UNCHANGEABLE_COMMAND = QRegularExpression(QStringLiteral("^(?:,|;)+$")); OctaveSession::OctaveSession ( Cantor::Backend* backend ) : Session ( backend), m_process(nullptr), -m_prompt(QLatin1String("CANTOR_OCTAVE_BACKEND_PROMPT:([0-9]+)> ")), -m_subprompt(QLatin1String("CANTOR_OCTAVE_BACKEND_SUBPROMPT:([0-9]+)> ")), +m_prompt(QStringLiteral("CANTOR_OCTAVE_BACKEND_PROMPT:([0-9]+)> ")), +m_subprompt(QStringLiteral("CANTOR_OCTAVE_BACKEND_SUBPROMPT:([0-9]+)> ")), m_previousPromptNumber(1), m_syntaxError(false) { setVariableModel(new OctaveVariableModel(this)); } OctaveSession::~OctaveSession() { if (m_process) { m_process->kill(); m_process->deleteLater(); m_process = nullptr; } } void OctaveSession::login() { qDebug() << "login"; if (m_process) return; emit loginStarted(); m_process = new KProcess ( this ); QStringList args; args << QLatin1String("--silent"); args << QLatin1String("--interactive"); args << QLatin1String("--persist"); // Setting prompt and subprompt args << QLatin1String("--eval"); args << QLatin1String("PS1('CANTOR_OCTAVE_BACKEND_PROMPT:\\#> ');"); args << QLatin1String("--eval"); args << QLatin1String("PS2('CANTOR_OCTAVE_BACKEND_SUBPROMPT:\\#> ');"); // Add the cantor script directory to octave script search path const QStringList& scriptDirs = locateAllCantorFiles(QLatin1String("octavebackend"), QStandardPaths::LocateDirectory); if (scriptDirs.isEmpty()) qCritical() << "Octave script directory not found, needed for integrated plots"; else { for (const QString& dir : scriptDirs) args << QLatin1String("--eval") << QString::fromLatin1("addpath \"%1\";").arg(dir); } // Do not show extra text in help commands args << QLatin1String("--eval"); args << QLatin1String("suppress_verbose_help_message(1);"); if (OctaveSettings::integratePlots()) { // Do not show the popup when plotting, rather only print to a file args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"off\");"); args << QLatin1String("--eval"); args << QLatin1String("graphics_toolkit gnuplot;"); } else { args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"on\");"); } m_process->setProgram ( OctaveSettings::path().toLocalFile(), args ); qDebug() << "starting " << m_process->program(); m_process->setOutputChannelMode ( KProcess::SeparateChannels ); m_process->start(); m_process->waitForStarted(); connect ( m_process, SIGNAL (readyReadStandardOutput()), SLOT (readOutput()) ); connect ( m_process, SIGNAL (readyReadStandardError()), SLOT (readError()) ); connect ( m_process, SIGNAL (error(QProcess::ProcessError)), SLOT (processError()) ); if(!OctaveSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = OctaveSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, OctaveExpression::DeleteOnFinish, true); updateVariables(); } changeStatus(Cantor::Session::Done); emit loginDone(); qDebug()<<"login done"; } void OctaveSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); if(status() == Cantor::Session::Running) interrupt(); m_process->write("exit\n"); qDebug()<<"send exit command to octave"; if(!m_process->waitForFinished(1000)) { m_process->kill(); qDebug()<<"octave still running, process kill enforced"; } m_process->deleteLater(); m_process = nullptr; expressionQueue().clear(); m_output.clear(); m_previousPromptNumber = 1; Session::logout(); qDebug()<<"logout done"; } void OctaveSession::interrupt() { qDebug() << expressionQueue().size(); if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process && m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); // Cleanup inner state and call octave prompt printing // If we move this code for interruption to Session, we need add function for // cleaning before setting Done status m_output.clear(); m_process->write("\n"); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } void OctaveSession::processError() { qDebug() << "processError"; emit error(m_process->errorString()); } Cantor::Expression* OctaveSession::evaluateExpression ( const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior, bool internal ) { qDebug() << "evaluating: " << command; OctaveExpression* expression = new OctaveExpression ( this, internal); expression->setCommand ( command ); expression->setFinishingBehavior ( finishingBehavior ); expression->evaluate(); return expression; } void OctaveSession::runFirstExpression() { OctaveExpression* expression = static_cast(expressionQueue().first()); connect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); QString command = expression->internalCommand(); expression->setStatus(Cantor::Expression::Computing); if (isDoNothingCommand(command)) expression->setStatus(Cantor::Expression::Done); else { m_process->write ( command.toLocal8Bit() ); } } void OctaveSession::readError() { qDebug() << "readError"; QString error = QString::fromLocal8Bit(m_process->readAllStandardError()); if (!expressionQueue().isEmpty() && !error.isEmpty()) { OctaveExpression* const exp = static_cast(expressionQueue().first()); if (m_syntaxError) { m_syntaxError = false; exp->parseError(i18n("Syntax Error")); } else exp->parseError(error); m_output.clear(); } } void OctaveSession::readOutput() { qDebug() << "readOutput"; while (m_process->bytesAvailable() > 0) { QString line = QString::fromLocal8Bit(m_process->readLine()); qDebug()<<"start parsing " << " " << line; - if (line.contains(m_prompt)) + QRegularExpressionMatch match = m_prompt.match(line); + if (match.hasMatch()) { - const int promptNumber = m_prompt.cap(1).toInt(); + const int promptNumber = match.captured(1).toInt(); // Add all text before prompt, if exists - m_output += QStringRef(&line, 0, line.indexOf(m_prompt)).toString(); + m_output += QStringRef(&line, 0, match.capturedStart(0)).toString(); if (!expressionQueue().isEmpty()) { const QString& command = expressionQueue().first()->command(); if (m_previousPromptNumber + 1 == promptNumber || isSpecialOctaveCommand(command)) { if (!expressionQueue().isEmpty()) { readError(); static_cast(expressionQueue().first())->parseOutput(m_output); } } else { // Error command don't increase octave prompt number (usually, but not always) readError(); } } m_previousPromptNumber = promptNumber; m_output.clear(); } - else if (line.contains(m_subprompt) && m_subprompt.cap(1).toInt() == m_previousPromptNumber) + else if ((match = m_subprompt.match(line)).hasMatch() + && match.captured(1).toInt() == m_previousPromptNumber) { // User don't write finished octave statement (for example, write 'a = [1,2, ' only), so // octave print subprompt and waits input finish. m_syntaxError = true; qDebug() << "subprompt catch"; m_process->write(")]'\"\n"); // force exit from subprompt m_output.clear(); } else m_output += line; } } void OctaveSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { qDebug() << "currentExpressionStatusChanged" << status << expressionQueue().first()->command(); switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: finishFirstExpression(); break; default: break; } } Cantor::CompletionObject* OctaveSession::completionFor ( const QString& cmd, int index ) { return new OctaveCompletionObject ( cmd, index, this ); } Cantor::SyntaxHelpObject* OctaveSession::syntaxHelpFor ( const QString& cmd ) { return new OctaveSyntaxHelpObject ( cmd, this ); } QSyntaxHighlighter* OctaveSession::syntaxHighlighter ( QObject* parent ) { return new OctaveHighlighter ( parent, this ); } void OctaveSession::runSpecificCommands() { m_process->write("figure(1,'visible','off')"); } bool OctaveSession::isDoNothingCommand(const QString& command) { - return PROMPT_UNCHANGEABLE_COMMAND.exactMatch(command) || command.isEmpty() || command == QLatin1String("\n"); + return PROMPT_UNCHANGEABLE_COMMAND.match(command).hasMatch() + || command.isEmpty() || command == QLatin1String("\n"); } bool OctaveSession::isSpecialOctaveCommand(const QString& command) { return command.contains(QLatin1String("completion_matches")); } diff --git a/src/backends/octave/octavesession.h b/src/backends/octave/octavesession.h index bfa397d1..d76eba6e 100644 --- a/src/backends/octave/octavesession.h +++ b/src/backends/octave/octavesession.h @@ -1,80 +1,80 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OCTAVESESSION_H #define OCTAVESESSION_H #include #include #include -#include +#include #include namespace Cantor { class DefaultVariableModel; } class KDirWatch; class OctaveExpression; class KProcess; class OctaveSession : public Cantor::Session { Q_OBJECT public: explicit OctaveSession(Cantor::Backend* backend); ~OctaveSession() override; void interrupt() override; Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void logout() override; void login() override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; void runFirstExpression() override; private: - const static QRegExp PROMPT_UNCHANGEABLE_COMMAND; + const static QRegularExpression PROMPT_UNCHANGEABLE_COMMAND; private: KProcess* m_process; QTextStream m_stream; - QRegExp m_prompt; - QRegExp m_subprompt; + QRegularExpression m_prompt; + QRegularExpression m_subprompt; int m_previousPromptNumber; bool m_syntaxError; QString m_output; private: void readFromOctave(QByteArray data); bool isDoNothingCommand(const QString& command); bool isSpecialOctaveCommand(const QString& command); private Q_SLOTS: void readOutput(); void readError(); void currentExpressionStatusChanged(Cantor::Expression::Status status); void processError(); void runSpecificCommands(); }; #endif // OCTAVESESSION_H