diff --git a/src/backends/maxima/maximaexpression.cpp b/src/backends/maxima/maximaexpression.cpp index 46955153..d07239a8 100644 --- a/src/backends/maxima/maximaexpression.cpp +++ b/src/backends/maxima/maximaexpression.cpp @@ -1,635 +1,634 @@ /* 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::MaximaExpression( Cantor::Session* session ) : Cantor::Expression(session), m_tempFile(nullptr), m_isHelpRequest(false), m_isPlot(false), m_gotErrorContent(false) { } void MaximaExpression::evaluate() { setStatus(Cantor::Expression::Computing); //until we get the real output Id from maxima, set it to invalid setId(-1); m_isHelpRequest=false; m_isPlot=false; m_gotErrorContent=false; if(m_tempFile) m_tempFile->deleteLater(); m_tempFile=nullptr; //check if this is a ?command if(command().startsWith(QLatin1Char('?'))||command().startsWith(QLatin1String("describe("))||command().startsWith(QLatin1String("example("))) m_isHelpRequest=true; if(command().contains(QRegExp(QLatin1String("(?:plot2d|plot3d)\\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(); disconnect(&m_fileWatch, &KDirWatch::dirty, this, &MaximaExpression::imageChanged); m_fileWatch.addFile(m_tempFile->fileName()); connect(&m_fileWatch, &KDirWatch::dirty, this, &MaximaExpression::imageChanged); } 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; } dynamic_cast(session())->appendExpressionToQueue(this); } void MaximaExpression::interrupt() { - qDebug()<<"interrupting"; dynamic_cast(session())->interrupt(this); 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)\\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); } 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')); } //The maxima backend is modified, so that it outputs //xml-style tags around outputs, input prompts etc. //the following are some simple helper functions to faciliate parsing inline void skipWhitespaces(int* idx, const QString& txt) { for(;*idx < txt.size() && (txt[*idx]).isSpace();++(*idx)); } QStringRef readXmlOpeningTag(int* idx, const QString& txt, bool* isComplete=nullptr) { qDebug()<<"trying to read an opening tag"; if (*idx >= txt.size()) return QStringRef(); skipWhitespaces(idx, txt); if(isComplete) *isComplete=false; if(txt[*idx]!=QLatin1Char('<')) { qDebug()<<"This is NOT AN OPENING TAG."<')) { if(isComplete) *isComplete=true; break; }else length++; } return QStringRef(&txt, startIndex, length); } QStringRef readXmlTagContent(int* idx, const QString& txt, const QStringRef& name, bool* isComplete=nullptr) { bool readingClosingTag=false; int contentStartIdx=*idx; int contentLength=0; int currentTagStartIdx=-1; int currentTagLength=0; if(isComplete) *isComplete=false; while(*idx0&&txt[(*idx)-1]==QLatin1Char('<')) { //remove the opening < contentLength--; currentTagStartIdx=*idx+1; currentTagLength=0; readingClosingTag=true; } else if(readingClosingTag) { if(c==QLatin1Char('>')) { const QStringRef currentTagName(&txt, currentTagStartIdx, currentTagLength); if(currentTagName==name) { //eat up the closing > ++(*idx); if(isComplete) (*isComplete)=true; break; } readingClosingTag=false; }else currentTagLength++; } else contentLength++; ++(*idx); } if(contentStartIdx+contentLength>txt.size()) { qDebug()<<"something is wrong with the content-length "<"), idx); int idx2=out.indexOf(QLatin1String(""), idx); idx1=(idx1==-1) ? out.size():idx1; idx2=(idx2==-1) ? out.size():idx2; int newIdx=qMin(idx1, idx2); if(newIdx>idx) { const QString& err=out.mid(idx, newIdx-idx); if(!err.isEmpty()) m_gotErrorContent=true; errorBuffer+=err; qDebug()<<"the unmatched part of the output is: "<0) { textBuffer.append(QLatin1String("\n")); latexBuffer.append(QLatin1String("\n")); } result=parseResult(&idx, out, textBuffer, latexBuffer); numResults++; qDebug()<<"got "< with their html code, so they won't be confused as html tags m_errorBuffer.replace( QLatin1Char('<') , QLatin1String("<")); m_errorBuffer.replace( QLatin1Char('>') , QLatin1String(">")); if(command().startsWith(QLatin1String(":lisp"))||command().startsWith(QLatin1String(":lisp-quiet"))) { if(result) { if(result->type()==Cantor::TextResult::Type) m_errorBuffer.prepend(dynamic_cast(result)->plain()+QLatin1String("\n")); else if(result->type()==Cantor::LatexResult::Type) m_errorBuffer.prepend(dynamic_cast(result)->plain()+QLatin1String("\n")); } Cantor::TextResult* result=new Cantor::TextResult(m_errorBuffer); setResult(result); setStatus(Cantor::Expression::Done); }else if(m_isHelpRequest) //Help Messages are also provided in the errorBuffer. { Cantor::HelpResult* result=new Cantor::HelpResult(m_errorBuffer); setResult(result); setStatus(Cantor::Expression::Done); }else { if(result) { qDebug()<<"result: "<toHtml(); if(result->type()==Cantor::TextResult::Type) m_errorBuffer.prepend(dynamic_cast(result)->plain()+QLatin1String("\n")); else if(result->type()==Cantor::LatexResult::Type) m_errorBuffer.prepend(dynamic_cast(result)->plain()+QLatin1String("\n")); } qDebug()<<"errorBuffer: "< with their html code, so they won't be confused as html tags text.replace( QLatin1Char('<') , QLatin1String("<")); text.replace( QLatin1Char('>') , QLatin1String(">")); QRegExp outputPromptRegexp=QRegExp(QLatin1Char('^')+MaximaSession::MaximaOutputPrompt.pattern()); int idxOfPrompt=outputPromptRegexp.indexIn(text); text.remove(idxOfPrompt, outputPromptRegexp.matchedLength()); //find the number if this output in the MaximaOutputPrompt QString prompt=outputPromptRegexp.cap(0).trimmed(); bool ok; QString idString=prompt.mid(3, prompt.length()-4); int id=idString.toInt(&ok); if(ok) setId(id); else setId(-1); qDebug()<<"prompt: "< element wasn't read completely, there //is no point in trying to render it. Use text for //incomplete results. if(!isLatexComplete ||(latexBuffer.trimmed().isEmpty()&&latex.isEmpty()) ||m_isHelpRequest||isInternal()) { qDebug()<<"using text"; result=new Cantor::TextResult(textBuffer); }else { qDebug()<<"using latex"; //strip away the latex code for the label. //it is contained in an \mbox{} call int i; int pcount=0; for(i=latex.indexOf(QLatin1String("\\mbox{"))+5;isetFormat(Cantor::TextResult::LatexFormat); } } return result; } void MaximaExpression::parseError(const QString& out) { m_errorBuffer.append(out); } void MaximaExpression::imageChanged() { qDebug()<<"the temp image has changed"; if(m_tempFile->size()>0) { #ifdef WITH_EPS setResult( new Cantor::EpsResult( QUrl::fromLocalFile(m_tempFile->fileName()) ) ); #else setResult( new Cantor::ImageResult( QUrl::fromLocalFile(m_tempFile->fileName()) ) ); #endif setStatus(Cantor::Expression::Done); } } QString MaximaExpression::additionalLatexHeaders() { return QString(); } diff --git a/src/backends/maxima/maximasession.cpp b/src/backends/maxima/maximasession.cpp index 72ae7bdd..1da35b34 100644 --- a/src/backends/maxima/maximasession.cpp +++ b/src/backends/maxima/maximasession.cpp @@ -1,450 +1,450 @@ /* 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 "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 #ifdef Q_OS_WIN #include #else #include #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 static QString initCmd=QLatin1String(":lisp($load \"%1\")"); MaximaSession::MaximaSession( Cantor::Backend* backend ) : Session(backend), m_process(nullptr), m_variableModel(new MaximaVariableModel(this)), m_initState(MaximaSession::NotInitialized), m_justRestarted(false) { } MaximaSession::~MaximaSession() { } void MaximaSession::login() { qDebug()<<"login"; emit loginStarted(); if (m_process) m_process->deleteLater(); #ifndef Q_OS_WIN m_process=new KPtyProcess(this); m_process->setPtyChannels(KPtyProcess::StdinChannel|KPtyProcess::StdoutChannel); m_process->pty()->setEcho(false); connect(m_process->pty(), SIGNAL(readyRead()), this, SLOT(readStdOut())); #else m_process=new KProcess(this); m_process->setOutputChannelMode(KProcess::SeparateChannels); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdOut())); #endif m_process->setProgram(MaximaSettings::self()->path().toLocalFile()); m_process->start(); connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(restartMaxima())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); const QString initFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/maximabackend/cantor-initmaxima.lisp")); write(initCmd.arg(initFile)); Cantor::Expression* expr=evaluateExpression(QLatin1String("print(____END_OF_INIT____);"), Cantor::Expression::DeleteOnFinish); expr->setInternal(true); //check if we actually landed in the queue and there wasn't some //error beforehand instead if(m_expressionQueue.contains(dynamic_cast(expr))) { //move this expression to the front m_expressionQueue.prepend(m_expressionQueue.takeLast()); } //reset the typesetting state setTypesettingEnabled(isTypesettingEnabled()); m_initState=MaximaSession::Initializing; if(!MaximaSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = MaximaSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, MaximaExpression::DeleteOnFinish); } runFirstExpression(); emit loginDone(); } void MaximaSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(restartMaxima())); if(status()==Cantor::Session::Done) { write(QLatin1String("quit();\n")); #ifdef Q_OS_WIN //Give maxima time to clean up qDebug()<<"waiting for maxima to finish"; m_process->waitForFinished(); #endif } else { m_expressionQueue.clear(); } //if it is still running, kill just kill it if(m_process->state()!=QProcess::NotRunning) { m_process->kill(); } qDebug()<<"done logging out"; delete m_process; m_process=nullptr; qDebug()<<"destroyed maxima"; m_expressionQueue.clear(); } Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) { qDebug() << "evaluating: " << cmd; MaximaExpression* expr = new MaximaExpression(this); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void MaximaSession::appendExpressionToQueue(MaximaExpression* expr) { m_expressionQueue.append(expr); qDebug()<<"queue: "<readAllStandardError()); if(m_expressionQueue.size()>0) { MaximaExpression* expr=m_expressionQueue.first(); expr->parseError(out); } } void MaximaSession::readStdOut() { if (!m_process) return; #ifndef Q_OS_WIN QString out=QLatin1String(m_process->pty()->readAll()); #else QString out=QLatin1String(m_process->readAllStandardOutput()); #endif out.remove(QLatin1Char('\r')); qDebug() << "std out: " << out; m_cache+=out; bool parsingSuccessful=true; if(m_expressionQueue.isEmpty()) { qDebug()<<"got output without active expression. dropping: "<parseOutput(m_cache); else parsingSuccessful=false; if(parsingSuccessful) { qDebug()<<"parsing successful. dropping "<setInternal(true); //TODO: what for? connect(e, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SIGNAL(ready())); } void MaximaSession::reportProcessError(QProcess::ProcessError e) { qDebug()<<"process error"<command().contains( QLatin1String("____END_OF_INIT____"))) { qDebug()<<"initialized"; m_expressionQueue.removeFirst(); m_initState=MaximaSession::Initialized; m_cache.clear(); runFirstExpression(); //QTimer::singleShot(0, this, SLOT(killLabels())); killLabels(); changeStatus(Cantor::Session::Done); return; } if(status!=Cantor::Expression::Computing) //The session is ready for the next command { qDebug()<<"expression finished"; disconnect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); qDebug()<<"running next command"; m_expressionQueue.removeFirst(); if(m_expressionQueue.isEmpty()) { //if we are done with all the commands in the queue, //use the opportunity to update the variablemodel (if the last command wasn't already an update, as infinite loops aren't fun) QRegExp exp=QRegExp(QRegExp::escape(MaximaVariableModel::inspectCommand).arg(QLatin1String("(values|functions)"))); QRegExp exp2=QRegExp(QRegExp::escape(MaximaVariableModel::variableInspectCommand).arg(QLatin1String("(values|functions)"))); if(MaximaSettings::variableManagement()&&!exp.exactMatch(expression->command())&&!exp2.exactMatch(expression->command())) { m_variableModel->checkForNewFunctions(); m_variableModel->checkForNewVariables(); }else { changeStatus(Cantor::Session::Done); } }else { runFirstExpression(); } } } void MaximaSession::runFirstExpression() { if(m_initState==MaximaSession::NotInitialized) { qDebug()<<"not ready to run expression"; return; } qDebug()<<"running next expression"; if (!m_process) return; if(!m_expressionQueue.isEmpty()) { MaximaExpression* expr=m_expressionQueue.first(); QString command=expr->internalCommand(); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); if(command.isEmpty()) { qDebug()<<"empty command"; expr->forceDone(); } else { m_cache.clear(); write(command + QLatin1Char('\n')); } } } void MaximaSession::interrupt() { if(!m_expressionQueue.isEmpty()) m_expressionQueue.first()->interrupt(); m_expressionQueue.clear(); changeStatus(Cantor::Session::Done); } void MaximaSession::interrupt(MaximaExpression* expr) { Q_ASSERT(!m_expressionQueue.isEmpty()); if(expr==m_expressionQueue.first()) { + qDebug()<<"interrupting " << expr->command(); disconnect(expr, nullptr, this, nullptr); - m_process->kill(); #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else //TODO: interrupt the process on windows #endif qDebug()<<"done interrupting"; }else { m_expressionQueue.removeAll(expr); } } void MaximaSession::sendInputToProcess(const QString& input) { qDebug()<<"WARNING: use this method only if you know what you're doing. Use evaluateExpression to run commands"; qDebug()<<"running "<setInternal(true); Cantor::Session::setTypesettingEnabled(enable); } Cantor::CompletionObject* MaximaSession::completionFor(const QString& command, int index) { return new MaximaCompletionObject(command, index, this); } Cantor::SyntaxHelpObject* MaximaSession::syntaxHelpFor(const QString& command) { return new MaximaSyntaxHelpObject(command, this); } QSyntaxHighlighter* MaximaSession::syntaxHighlighter(QObject* parent) { return new MaximaHighlighter(parent, this); } QAbstractItemModel* MaximaSession::variableModel() { return m_variableModel; } void MaximaSession::write(const QString& exp) { qDebug()<<"sending expression to maxima process: " << exp << endl; #ifndef Q_OS_WIN m_process->pty()->write(exp.toUtf8()); #else m_process->write(exp.toUtf8()); #endif }