diff --git a/src/backends/maxima/maximaexpression.cpp b/src/backends/maxima/maximaexpression.cpp index d608d3c9..a66c9b62 100644 --- a/src/backends/maxima/maximaexpression.cpp +++ b/src/backends/maxima/maximaexpression.cpp @@ -1,410 +1,444 @@ /* 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. --- Copyright (C) 2009-2012 Alexander Rieder */ #include "maximaexpression.h" #include #include "maximasession.h" #include "textresult.h" #include "epsresult.h" #include "imageresult.h" #include "helpresult.h" #include "latexresult.h" #include "settings.h" #include #include #include #include #include #include #include #include // MaximaExpression use real id from Maxima as expression id, so we don't know id before executing MaximaExpression::MaximaExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal, -1), m_tempFile(nullptr), m_isHelpRequest(false), + m_isHelpRequestAdditional(false), m_isPlot(false), m_plotResult(nullptr), m_plotResultIndex(-1), m_gotErrorContent(false) { } MaximaExpression::~MaximaExpression() { if(m_tempFile) delete m_tempFile; } void MaximaExpression::evaluate() { m_isHelpRequest=false; m_gotErrorContent=false; if(m_tempFile) { delete m_tempFile; m_tempFile = nullptr; m_isPlot = false; m_plotResult = nullptr; m_plotResultIndex = -1; } //check if this is a ?command if(command().startsWith(QLatin1String("??")) || command().startsWith(QLatin1String("describe(")) || command().startsWith(QLatin1String("example(")) || command().startsWith(QLatin1String(":lisp(cl-info::info-exact"))) m_isHelpRequest=true; if(command().contains(QRegExp(QLatin1String("(?:plot2d|plot3d|contour_plot)\\s*\\([^\\)]"))) && MaximaSettings::self()->integratePlots() && !command().contains(QLatin1String("ps_file"))) { m_isPlot=true; #ifdef WITH_EPS m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_maxima-XXXXXX.eps" )); #else m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_maxima-XXXXXX.png")); #endif m_tempFile->open(); m_fileWatch.removePaths(m_fileWatch.files()); m_fileWatch.addPath(m_tempFile->fileName()); connect(&m_fileWatch, &QFileSystemWatcher::fileChanged, this, &MaximaExpression::imageChanged, Qt::UniqueConnection); } const QString& cmd=command(); bool isComment = true; int commentLevel = 0; bool inString = false; for (int i = 0; i < cmd.size(); ++i) { if (cmd[i] == QLatin1Char('\\')) { ++i; // skip the next character if (commentLevel == 0 && !inString) { isComment = false; } } else if (cmd[i] == QLatin1Char('"') && commentLevel == 0) { inString = !inString; isComment = false; } else if (cmd.mid(i,2) == QLatin1String("/*") && !inString) { ++commentLevel; ++i; } else if (cmd.mid(i,2) == QLatin1String("*/") && !inString) { if (commentLevel == 0) { qDebug() << "Comments mismatched!"; setErrorMessage(i18n("Error: Too many */")); setStatus(Cantor::Expression::Error); return; } ++i; --commentLevel; } else if (isComment && commentLevel == 0 && !cmd[i].isSpace()) { isComment = false; } } if (commentLevel > 0) { qDebug() << "Comments mismatched!"; setErrorMessage(i18n("Error: Too many /*")); setStatus(Cantor::Expression::Error); return; } if (inString) { qDebug() << "String not closed"; setErrorMessage(i18n("Error: expected \" before ;")); setStatus(Cantor::Expression::Error); return; } if(isComment) { setStatus(Cantor::Expression::Done); return; } session()->enqueueExpression(this); } void MaximaExpression::interrupt() { qDebug()<<"interrupting"; setStatus(Cantor::Expression::Interrupted); } QString MaximaExpression::internalCommand() { QString cmd=command(); if(m_isPlot) { if(!m_tempFile) { qDebug()<<"plotting without tempFile"; return QString(); } QString fileName = m_tempFile->fileName(); #ifdef WITH_EPS const QString psParam=QLatin1String("[gnuplot_ps_term_command, \"set size 1.0, 1.0; set term postscript eps color solid \"]"); const QString plotParameters = QLatin1String("[ps_file, \"")+ fileName+QLatin1String("\"],")+psParam; #else const QString plotParameters = QLatin1String("[gnuplot_term, \"png size 500,340\"], [gnuplot_out_file, \"")+fileName+QLatin1String("\"]"); #endif cmd.replace(QRegExp(QLatin1String("((plot2d|plot3d|contour_plot)\\s*\\(.*)\\)([;\n]|$)")), QLatin1String("\\1, ")+plotParameters+QLatin1String(");")); } if (!cmd.endsWith(QLatin1Char('$'))) { if (!cmd.endsWith(QLatin1String(";"))) cmd+=QLatin1Char(';'); } //replace all newlines with spaces, as maxima isn't sensitive about //whitespaces, and without newlines the whole command //is executed at once, without outputting an input //prompt after each line cmd.replace(QLatin1Char('\n'), QLatin1Char(' ')); //lisp-quiet doesn't print a prompt after the command //is completed, which causes the parsing to hang. //replace the command with the non-quiet version cmd.replace(QRegExp(QLatin1String("^:lisp-quiet")), QLatin1String(":lisp")); return cmd; } void MaximaExpression::forceDone() { qDebug()<<"forcing Expression state to DONE"; setResult(nullptr); setStatus(Cantor::Expression::Done); } /*! example output for the simple expression '5+5': latex mode - "\n(%o1) 10\n\\mbox{\\tt\\red(\\mathrm{\\%o1}) \\black}10\n(%i2) \n" text mode - "\n(%o1) 10\n\n(%i2) \n" */ bool MaximaExpression::parseOutput(QString& out) { const int promptStart = out.indexOf(QLatin1String("")); const int promptEnd = out.indexOf(QLatin1String("")); const QString prompt = out.mid(promptStart + 15, promptEnd - promptStart - 15).simplified(); //check whether the result is part of the promt - this is the case when additional input is required from the user if (prompt.contains(QLatin1String(""))) { //text part of the output const int textContentStart = prompt.indexOf(QLatin1String("")); const int textContentEnd = prompt.indexOf(QLatin1String("")); QString textContent = prompt.mid(textContentStart + 13, textContentEnd - textContentStart - 13).trimmed(); qDebug()<<"asking for additional input for " << textContent; emit needsAdditionalInformation(textContent); return true; } qDebug()<<"new input label: " << prompt; QString errorContent; //parse the results int resultStart = out.indexOf(QLatin1String("")); if (resultStart != -1) errorContent += out.mid(0, resultStart); + while (resultStart != -1) { int resultEnd = out.indexOf(QLatin1String(""), resultStart + 15); const QString resultContent = out.mid(resultStart + 15, resultEnd - resultStart - 15); parseResult(resultContent); //search for the next openning tag after the current closing tag resultStart = out.indexOf(QLatin1String(""), resultEnd + 16); } //parse the error message, the part outside of the tags int lastResultEnd = out.lastIndexOf(QLatin1String("")); if (lastResultEnd != -1) lastResultEnd += 16; else lastResultEnd = 0; errorContent += out.mid(lastResultEnd, promptStart - lastResultEnd).trimmed(); if (errorContent.isEmpty()) { // For plots we set Done status in imageChanged if (!m_isPlot || m_plotResult) setStatus(Cantor::Expression::Done); } else { qDebug() << "error content: " << errorContent; + if (out.contains(QLatin1String("cantor-value-separator"))) { //when fetching variables, in addition to the actual result with variable names and values, - //Maxima also write out the names of the variables to the error buffer. + //Maxima also writes out the names of the variables to the error buffer. //we don't interpret this as an error. setStatus(Cantor::Expression::Done); } - else if(m_isHelpRequest) //help messages are also part of the error output + else if(m_isHelpRequest || m_isHelpRequestAdditional) //help messages are also part of the error output { + //we've got help result, but maybe additional input is required -> check this + const int index = MaximaSession::MaximaInputPrompt.indexIn(prompt.trimmed()); + if (index == -1) { + // No input label found in the prompt -> additional info is required + qDebug()<<"asking for additional input for the help request" << prompt; + m_isHelpRequestAdditional = true; + emit needsAdditionalInformation(prompt); + } + + //set the help result Cantor::HelpResult* result = new Cantor::HelpResult(errorContent); - addResult(result); - setStatus(Cantor::Expression::Done); + setResult(result); + + //if a new input prompt was found, no further input is expected and we're done + if (index != -1) { + m_isHelpRequestAdditional = false; + setStatus(Cantor::Expression::Done); + } } else { errorContent = errorContent.replace(QLatin1String("\n\n"), QLatin1String("
")); errorContent = errorContent.replace(QLatin1String("\n"), QLatin1String("
")); setErrorMessage(errorContent); setStatus(Cantor::Expression::Error); } } return true; } void MaximaExpression::parseResult(const QString& resultContent) { + //in case we asked for additional input for the help request, + //no need to process the result - we're not done yet and maxima is waiting for further input + if (m_isHelpRequestAdditional) + return; + qDebug()<<"result content: " << resultContent; //text part of the output const int textContentStart = resultContent.indexOf(QLatin1String("")); const int textContentEnd = resultContent.indexOf(QLatin1String("")); QString textContent = resultContent.mid(textContentStart + 13, textContentEnd - textContentStart - 13).trimmed(); qDebug()<<"text content: " << textContent; //output label can be a part of the text content -> determine it const QRegExp regex = QRegExp(MaximaSession::MaximaOutputPrompt.pattern()); const int index = regex.indexIn(textContent); QString outputLabel; if (index != -1) // No match, so output don't contain output label outputLabel = textContent.mid(index, regex.matchedLength()).trimmed(); qDebug()<<"output label: " << outputLabel; //extract the expression id bool ok; QString idString = outputLabel.mid(3, outputLabel.length()-4); int id = idString.toInt(&ok); if (ok) setId(id); qDebug()<<"expression id: " << this->id(); //remove the output label from the text content textContent = textContent.remove(outputLabel).trimmed(); //determine the actual result Cantor::Result* result = nullptr; const int latexContentStart = resultContent.indexOf(QLatin1String("")); //Handle system maxima output for plotting commands if (m_isPlot && textContent.endsWith(QString::fromLatin1("\"%1\"]").arg(m_tempFile->fileName()))) { m_plotResultIndex = results().size(); // Gnuplot could generate plot before we parse text output from maxima and after // If we already have plot result, just add it // Else set info message, and replace it by real result in imageChanged function later if (m_plotResult) result = m_plotResult; else result = new Cantor::TextResult(i18n("Waiting for the plot result")); } else if (latexContentStart != -1) { //latex output is available const int latexContentEnd = resultContent.indexOf(QLatin1String("")); QString latexContent = resultContent.mid(latexContentStart + 14, latexContentEnd - latexContentStart - 14).trimmed(); qDebug()<<"latex content: " << latexContent; Cantor::TextResult* textResult; //replace the \mbox{} environment, if available, by the eqnarray environment if (latexContent.indexOf(QLatin1String("\\mbox{")) != -1) { int i; int pcount=0; for(i = latexContent.indexOf(QLatin1String("\\mbox{"))+5; i < latexContent.size(); ++i) { if(latexContent[i]==QLatin1Char('{')) pcount++; else if(latexContent[i]==QLatin1Char('}')) pcount--; if(pcount==0) break; } QString modifiedLatexContent = latexContent.mid(i+1); if(modifiedLatexContent.trimmed().isEmpty()) { //empty content in the \mbox{} environment (e.g. for print() outputs), use the latex string outside of the \mbox{} environment modifiedLatexContent = latexContent.left(latexContent.indexOf(QLatin1String("\\mbox{"))); } modifiedLatexContent.prepend(QLatin1String("\\begin{eqnarray*}")); modifiedLatexContent.append(QLatin1String("\\end{eqnarray*}")); textResult = new Cantor::TextResult(modifiedLatexContent, textContent); qDebug()<<"modified latex content: " << modifiedLatexContent; } else { //no \mbox{} available, use what we've got. textResult = new Cantor::TextResult(latexContent, textContent); } textResult->setFormat(Cantor::TextResult::LatexFormat); result = textResult; } else { //no latex output is availabe, the actual result is part of the textContent string result = new Cantor::TextResult(textContent); } addResult(result); } void MaximaExpression::parseError(const QString& out) { m_errorBuffer.append(out); } +void MaximaExpression::addInformation(const QString& information) +{ + qDebug()<<"adding information"; + QString inf=information; + if(!inf.endsWith(QLatin1Char(';'))) + inf+=QLatin1Char(';'); + Cantor::Expression::addInformation(inf); + + dynamic_cast(session())->sendInputToProcess(inf+QLatin1Char('\n')); +} + void MaximaExpression::imageChanged() { if(m_tempFile->size()>0) { #ifdef WITH_EPS m_plotResult = new Cantor::EpsResult( QUrl::fromLocalFile(m_tempFile->fileName()) ); #else m_plotResult = new Cantor::ImageResult( QUrl::fromLocalFile(m_tempFile->fileName()) ); #endif // Check, that we already parse maxima output for this plot, and if not, keep it up to this moment // If it's true, replace text info result by real plot and set status as Done if (m_plotResultIndex != -1) { replaceResult(m_plotResultIndex, m_plotResult); setStatus(Cantor::Expression::Done); } } } diff --git a/src/backends/maxima/maximaexpression.h b/src/backends/maxima/maximaexpression.h index 6cdab348..b33b792d 100644 --- a/src/backends/maxima/maximaexpression.h +++ b/src/backends/maxima/maximaexpression.h @@ -1,69 +1,72 @@ /* 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. --- Copyright (C) 2009-2012 Alexander Rieder */ #ifndef _MAXIMAEXPRESSION_H #define _MAXIMAEXPRESSION_H #include "expression.h" #include #include class QTemporaryFile; class MaximaExpression : public Cantor::Expression { Q_OBJECT public: explicit MaximaExpression(Cantor::Session*, bool internal = false); ~MaximaExpression() override; void evaluate() override; void interrupt() override; //returns the command that should be send to //the Maxima process, it's different from the //command() for example to allow plot embedding QString internalCommand(); //Forces the status of this Expression to done void forceDone(); //reads from @param out until a prompt indicates that a new expression has started bool parseOutput(QString&); void parseError(const QString&); + void addInformation(const QString&) override; + private Q_SLOTS: void imageChanged(); private: void parseResult(const QString&); QTemporaryFile *m_tempFile; QFileSystemWatcher m_fileWatch; bool m_isHelpRequest; + bool m_isHelpRequestAdditional; bool m_isPlot; Cantor::Result* m_plotResult; int m_plotResultIndex; QString m_errorBuffer; bool m_gotErrorContent; }; #endif /* _MAXIMAEXPRESSION_H */ diff --git a/src/backends/maxima/maximasession.cpp b/src/backends/maxima/maximasession.cpp index b822e5d4..e5379d21 100644 --- a/src/backends/maxima/maximasession.cpp +++ b/src/backends/maxima/maximasession.cpp @@ -1,347 +1,348 @@ /* 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. --- Copyright (C) 2009-2012 Alexander Rieder Copyright (C) 2017-2018 Alexander Semke (alexander.semke@web.de) */ #include "maximasession.h" #include "maximaexpression.h" #include "maximacompletionobject.h" #include "maximasyntaxhelpobject.h" #include "maximahighlighter.h" #include "maximavariablemodel.h" #include "result.h" #include "settings.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif //NOTE: the \\s in the expressions is needed, because Maxima seems to sometimes insert newlines/spaces between the letters //maybe this is caused by some behaviour if the Prompt is split into multiple "readStdout" calls //the Expressions are encapsulated in () to allow capturing for the text const QRegExp MaximaSession::MaximaOutputPrompt=QRegExp(QLatin1String("(\\(\\s*%\\s*o\\s*[0-9\\s]*\\))")); //Text, maxima outputs, before any output +const QRegExp MaximaSession::MaximaInputPrompt = QRegExp(QLatin1String("(\\(\\s*%\\s*i\\s*[0-9\\s]*\\))")); MaximaSession::MaximaSession( Cantor::Backend* backend ) : Session(backend), m_process(nullptr), m_variableModel(new MaximaVariableModel(this)), m_justRestarted(false) { } void MaximaSession::login() { qDebug()<<"login"; if (m_process) return; //TODO: why do we call login() again?!? emit loginStarted(); QStringList arguments; arguments << QLatin1String("--quiet"); //Suppress Maxima start-up message const QString initFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/maximabackend/cantor-initmaxima.lisp")); arguments << QLatin1String("--init-lisp=") + initFile; //Set the name of the Lisp initialization file m_process = new QProcess(this); m_process->start(MaximaSettings::self()->path().toLocalFile(), arguments); m_process->waitForStarted(); m_process->waitForReadyRead(); qDebug()<readAllStandardOutput(); connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(restartMaxima())); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); if(!MaximaSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = MaximaSettings::self()->autorunScripts().join(QLatin1String(";")); autorunScripts.append(QLatin1String(";kill(labels)")); // Reset labels after running autorun scripts evaluateExpression(autorunScripts, MaximaExpression::DeleteOnFinish, true); } changeStatus(Session::Done); emit loginDone(); qDebug()<<"login done"; } void MaximaSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); // if(status()==Cantor::Session::Running) //TODO: terminate the running expressions first write(QLatin1String("quit();\n")); qDebug()<<"waiting for maxima to finish"; m_process->waitForFinished(); qDebug()<<"maxima exit finished"; if(m_process->state() != QProcess::NotRunning) { m_process->kill(); qDebug()<<"maxima still running, process kill enforced"; } expressionQueue().clear(); delete m_process; m_process = nullptr; changeStatus(Status::Disable); qDebug()<<"logout done"; } Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; MaximaExpression* expr = new MaximaExpression(this, internal); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void MaximaSession::readStdErr() { qDebug()<<"reading stdErr"; if (!m_process) return; QString out=QLatin1String(m_process->readAllStandardError()); if(expressionQueue().size()>0) { MaximaExpression* expr = static_cast(expressionQueue().first()); expr->parseError(out); } } void MaximaSession::readStdOut() { QString out = QLatin1String(m_process->readAllStandardOutput()); m_cache += out; //collect the multi-line output until Maxima has finished the calculation and returns a new promt if ( !out.contains(QLatin1String("")) ) return; if(expressionQueue().isEmpty()) { //queue is empty, interrupt was called, nothing to do here qDebug()<(expressionQueue().first()); if (!expr) return; //should never happen qDebug()<<"output: " << m_cache; expr->parseOutput(m_cache); m_cache.clear(); } void MaximaSession::killLabels() { Cantor::Expression* e=evaluateExpression(QLatin1String("kill(labels);"), Cantor::Expression::DeleteOnFinish, true); //TODO: what for? connect(e, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SIGNAL(ready())); } void MaximaSession::reportProcessError(QProcess::ProcessError e) { qDebug()<<"process error"<command())&&!exp2.exactMatch(expression->command())) { m_variableModel->checkForNewFunctions(); m_variableModel->checkForNewVariables(); }else { changeStatus(Cantor::Session::Done); } }else { runFirstExpression(); } } } void MaximaSession::runFirstExpression() { qDebug()<<"running next expression"; if (!m_process) return; if(!expressionQueue().isEmpty()) { MaximaExpression* expr = static_cast(expressionQueue().first()); QString command=expr->internalCommand(); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); expr->setStatus(Cantor::Expression::Computing); if(command.isEmpty()) { qDebug()<<"empty command"; expr->forceDone(); } else { m_cache.clear(); write(command + QLatin1Char('\n')); } } } void MaximaSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(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 } expressionQueue().first()->interrupt(); expressionQueue().removeFirst(); foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Done); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); m_cache.clear(); } void MaximaSession::sendInputToProcess(const QString& input) { write(input); } void MaximaSession::restartMaxima() { qDebug()<<"restarting maxima cooldown: "<write(exp.toUtf8()); } diff --git a/src/backends/maxima/maximasession.h b/src/backends/maxima/maximasession.h index 8d4b17ee..4547092a 100644 --- a/src/backends/maxima/maximasession.h +++ b/src/backends/maxima/maximasession.h @@ -1,77 +1,78 @@ /* Tims 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. --- Copyright (C) 2009-2012 Alexander Rieder */ #ifndef _MAXIMASESSION_H #define _MAXIMASESSION_H #include "session.h" #include "expression.h" #include class MaximaExpression; class MaximaVariableModel; class MaximaSession : public Cantor::Session { Q_OBJECT public: static const QRegExp MaximaOutputPrompt; + static const QRegExp MaximaInputPrompt; explicit MaximaSession( Cantor::Backend* backend); void login() override; void logout() override; Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void interrupt() override; void sendInputToProcess(const QString& input); void setTypesettingEnabled(bool enable) override; Cantor::CompletionObject* completionFor(const QString& command, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& command) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; QAbstractItemModel* variableModel() override; void runFirstExpression() override; public Q_SLOTS: void readStdOut(); void readStdErr(); private Q_SLOTS: void currentExpressionChangedStatus(Cantor::Expression::Status status); void restartMaxima(); void restartsCooledDown(); void killLabels(); void reportProcessError(QProcess::ProcessError error); private: void write(const QString&); QProcess* m_process; QString m_cache; MaximaVariableModel* m_variableModel; bool m_justRestarted; }; #endif /* _MAXIMASESSION_H */ diff --git a/src/backends/maxima/testmaxima.cpp b/src/backends/maxima/testmaxima.cpp index 354cfabd..e117e9c5 100644 --- a/src/backends/maxima/testmaxima.cpp +++ b/src/backends/maxima/testmaxima.cpp @@ -1,278 +1,296 @@ /* 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. --- Copyright (C) 2009 Alexander Rieder */ #include "testmaxima.h" #include "session.h" #include "backend.h" #include "expression.h" #include "result.h" #include "imageresult.h" #include "epsresult.h" #include "syntaxhelpobject.h" #include #include QString TestMaxima::backendName() { return QLatin1String("maxima"); } void TestMaxima::testSimpleCommand() { Cantor::Expression* e=evalExp( QLatin1String("2+2") ); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( cleanOutput( e->result()->toHtml() ), QLatin1String("4") ); } void TestMaxima::testMultilineCommand() { Cantor::Expression* e = evalExp( QLatin1String("2+2;3+3") ); QVERIFY(e != nullptr); QVERIFY(e->results().size() == 2); QString result = e->results().at(0)->toHtml(); QCOMPARE(result, QLatin1String("4")); result = e->results().at(1)->toHtml(); QCOMPARE(result, QLatin1String("6")); } //WARNING: for this test to work, Integration of Plots must be enabled //and CantorLib must be compiled with EPS-support void TestMaxima::testPlot() { if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull()) { QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle); } Cantor::Expression* e=evalExp( QLatin1String("plot2d(sin(x), [x, -10,10])") ); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); if(e->result()->type()!= Cantor::EpsResult::Type) { waitForSignal(e, SIGNAL(gotResult())); } #ifdef WITH_EPS QCOMPARE( e->result()->type(), (int)Cantor::EpsResult::Type ); #else QCOMPARE( e->result()->type(), (int)Cantor::ImageResult::Type ); #endif QVERIFY( !e->result()->data().isNull() ); QVERIFY( e->errorMessage().isNull() ); } void TestMaxima::testPlotWithAnotherTextResults() { if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull()) { QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle); } Cantor::Expression* e=evalExp( QLatin1String( "2*2; \n" "plot2d(sin(x), [x, -10,10]); \n" "4*4;" )); QVERIFY( e!=nullptr ); QVERIFY( e->errorMessage().isNull() ); QCOMPARE(e->results().size(), 3); QCOMPARE(e->results()[0]->toHtml(), QLatin1String("4")); #ifdef WITH_EPS QCOMPARE( e->results()[1]->type(), (int)Cantor::EpsResult::Type ); #else QCOMPARE( e->results()[1]->type(), (int)Cantor::ImageResult::Type ); #endif QVERIFY( !e->results()[1]->data().isNull() ); QCOMPARE(e->results()[2]->toHtml(), QLatin1String("16")); } void TestMaxima::testInvalidSyntax() { Cantor::Expression* e=evalExp( QLatin1String("2+2*(") ); QVERIFY( e!=nullptr ); QVERIFY( e->status()==Cantor::Expression::Error ); } void TestMaxima::testExprNumbering() { Cantor::Expression* e=evalExp( QLatin1String("kill(labels)") ); //first reset the labels e=evalExp( QLatin1String("2+2") ); QVERIFY( e!=nullptr ); int id=e->id(); QCOMPARE( id, 1 ); e=evalExp( QString::fromLatin1("%o%1+1" ).arg( id ) ); QVERIFY( e != nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( cleanOutput( e->result()->toHtml() ), QLatin1String( "5" ) ); } void TestMaxima::testCommandQueue() { //only wait for the last Expression to return, so the queue gets //actually filled Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("0+1")); Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("1+1")); Cantor::Expression* e3=evalExp(QLatin1String("1+2")); QVERIFY(e1!=nullptr); QVERIFY(e2!=nullptr); QVERIFY(e3!=nullptr); QVERIFY(e1->result()); QVERIFY(e2->result()); QVERIFY(e3->result()); QCOMPARE(cleanOutput(e1->result()->toHtml()), QLatin1String("1")); QCOMPARE(cleanOutput(e2->result()->toHtml()), QLatin1String("2")); QCOMPARE(cleanOutput(e3->result()->toHtml()), QLatin1String("3")); } void TestMaxima::testSimpleExpressionWithComment() { Cantor::Expression* e=evalExp(QLatin1String("/*this is a comment*/2+2")); QVERIFY(e!=nullptr); QVERIFY(e->result()!=nullptr); QCOMPARE(cleanOutput(e->result()->toHtml()), QLatin1String("4")); } void TestMaxima::testCommentExpression() { Cantor::Expression* e=evalExp(QLatin1String("/*this is a comment*/")); QVERIFY(e!=nullptr); QVERIFY(e->result()==nullptr||e->result()->toHtml().isEmpty()); } void TestMaxima::testNestedComment() { Cantor::Expression* e=evalExp(QLatin1String("/*/*this is still a comment*/*/2+2/*still/*a*/comment*//**/")); QVERIFY(e!=nullptr); QVERIFY(e->result()!=nullptr); QCOMPARE(cleanOutput(e->result()->toHtml()), QLatin1String("4")); } void TestMaxima::testUnmatchedComment() { Cantor::Expression* e=evalExp(QLatin1String("/*this comment doesn't end here!")); QVERIFY(e!=nullptr); QVERIFY(e->result()==nullptr); QVERIFY(e->status()==Cantor::Expression::Error); } void TestMaxima::testInvalidAssignment() { Cantor::Expression* e=evalExp(QLatin1String("0:a")); QVERIFY(e!=nullptr); //QVERIFY(e->result()==0); //QVERIFY(e->status()==Cantor::Expression::Error); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); //make sure we didn't screw up the session Cantor::Expression* e2=evalExp(QLatin1String("2+2")); QVERIFY(e2!=nullptr); QVERIFY(e2->result()!=nullptr); QCOMPARE(cleanOutput(e2->result()->toHtml()), QLatin1String("4")); } void TestMaxima::testInformationRequest() { Cantor::Expression* e=session()->evaluateExpression(QLatin1String("integrate(x^n,x)")); QVERIFY(e!=nullptr); waitForSignal(e, SIGNAL(needsAdditionalInformation(QString))); e->addInformation(QLatin1String("N")); waitForSignal(e, SIGNAL(statusChanged(Cantor::Expression::Status))); QVERIFY(e->result()!=nullptr); QCOMPARE(cleanOutput(e->result()->toHtml()), QLatin1String("x^(n+1)/(n+1)")); } void TestMaxima::testSyntaxHelp() { Cantor::SyntaxHelpObject* help = session()->syntaxHelpFor(QLatin1String("simplify_sum")); help->fetchSyntaxHelp(); waitForSignal(help, SIGNAL(done())); qWarning()<toHtml(); QVERIFY(help->toHtml().contains(QLatin1String("simplify_sum"))); } +void TestMaxima::testHelpRequest() +{ + //execute "??print" + Cantor::Expression* e = session()->evaluateExpression(QLatin1String("??print")); + QVERIFY(e != nullptr); + + //help result will be shown, but maxima still expects further input + waitForSignal(e, SIGNAL(needsAdditionalInformation(QString))); + QVERIFY(e->status() != Cantor::Expression::Done); + + //ask for help for the first flag of the print command + e->addInformation(QLatin1String("0")); + + //no further input is required, we're done + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + QVERIFY(e->status() == Cantor::Expression::Done); +} + void TestMaxima::testVariableModel() { QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); Cantor::Expression* e1=evalExp(QLatin1String("a: 15")); Cantor::Expression* e2=evalExp(QLatin1String("a: 15; b: \"Hello, world!\"")); Cantor::Expression* e3=evalExp(QLatin1String("l: [1,2,3]")); QVERIFY(e1!=nullptr); QVERIFY(e2!=nullptr); QVERIFY(e3!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(3, model->rowCount()); QVariant name = model->index(0,0).data(); QCOMPARE(name.toString(),QLatin1String("a")); QVariant value = model->index(0,1).data(); QCOMPARE(value.toString(),QLatin1String("15")); QVariant name1 = model->index(1,0).data(); QCOMPARE(name1.toString(),QLatin1String("b")); QVariant value1 = model->index(1,1).data(); QCOMPARE(value1.toString(),QLatin1String("\"Hello, world!\"")); QVariant name2 = model->index(2,0).data(); QCOMPARE(name2.toString(),QLatin1String("l")); QVariant value2 = model->index(2,1).data(); QCOMPARE(value2.toString(),QLatin1String("[1,2,3]")); } QTEST_MAIN( TestMaxima ) diff --git a/src/backends/maxima/testmaxima.h b/src/backends/maxima/testmaxima.h index a4aa53fb..f71c6a6d 100644 --- a/src/backends/maxima/testmaxima.h +++ b/src/backends/maxima/testmaxima.h @@ -1,68 +1,68 @@ /* 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. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _TESTMAXIMA_H #define _TESTMAXIMA_H #include "backendtest.h" /** This class test some of the basic functions of the maxima backend The different tests represent some general expressions, as well as expressions, that are known to have caused problems in earlier versions **/ class TestMaxima : public BackendTest { Q_OBJECT private Q_SLOTS: //tests evaluating a simple command void testSimpleCommand(); //tests a command, containing more than 1 line void testMultilineCommand(); //tests if the command queue works correcly void testCommandQueue(); //tests doing a plot void testPlot(); void testPlotWithAnotherTextResults(); //tests a syntax error (not closing bracket) void testInvalidSyntax(); //tests if the expression numbering works void testExprNumbering(); //some tests to see if comments are working correctly void testSimpleExpressionWithComment(); void testCommentExpression(); void testNestedComment(); void testUnmatchedComment(); void testInvalidAssignment(); void testInformationRequest(); - + void testHelpRequest(); void testSyntaxHelp(); void testVariableModel(); private: QString backendName() override; }; #endif /* _TESTMAXIMA_H */