sageexpression.cpp 9.49 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
    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 <alexanderrieder@gmail.com>
 */

#include "sageexpression.h"

#include "sagesession.h"
#include "textresult.h"
#include "imageresult.h"
26
#include "animationresult.h"
27
28
#include "helpresult.h"

29
#include <QDebug>
30
#include <KLocalizedString>
Filipe Saraiva's avatar
Filipe Saraiva committed
31
#include <QMimeDatabase>
32
#include <QRegularExpression>
33

34
SageExpression::SageExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal),
35
    m_isHelpRequest(false),
Nikita Sirgienko's avatar
Nikita Sirgienko committed
36
37
    m_promptCount(0),
    m_syntaxError(false)
38
39
40
41
42
{
}

void SageExpression::evaluate()
{
Alexander Rieder's avatar
Alexander Rieder committed
43
    m_imagePath.clear();
44
45

    m_isHelpRequest=false;
Alexander Rieder's avatar
Alexander Rieder committed
46

47
48
49
50
51
    //check if this is a ?command or help command
    if( command().startsWith(QLatin1Char('?'))
        || command().endsWith(QLatin1Char('?'))
        || command().contains(QLatin1String("help("))
    )
52
53
        m_isHelpRequest=true;

54
55
56
    //coun't how many newlines are in the command,
    //as sage will output one "sage: " or "....:" for
    //each.
57
    m_promptCount=command().count(QLatin1Char('\n'))+2;
58

59
    session()->enqueueExpression(this);
60
61
62
63
}

void SageExpression::interrupt()
{
64
    qDebug()<<"interrupting";
65
66

    setStatus(Cantor::Expression::Interrupted);
67
68
69
70
}

void SageExpression::parseOutput(const QString& text)
{
Nikita Sirgienko's avatar
Nikita Sirgienko committed
71
72
73
74
75
76
77
    if (m_syntaxError)
    {
        setErrorMessage(i18n("Syntax Error"));
        setStatus(Cantor::Expression::Error);
        return;
    }

Alexander Rieder's avatar
Alexander Rieder committed
78
    QString output=text;
79
    //remove carriage returns, we only use \n internally
80
    output.remove(QLatin1Char('\r'));
Alexander Rieder's avatar
Alexander Rieder committed
81
    //replace appearing backspaces, as they mess the whole output up
82
83
84
    //with QRegularExpression/PCRE to make \b match a backspace put it inside []
    //see https://perldoc.perl.org/perlrecharclass.html#Bracketed-Character-Classes
    output.remove(QRegularExpression(QStringLiteral(".[\b]")));
Alexander Rieder's avatar
Alexander Rieder committed
85
    //replace Escape sequences (only tested with `ls` command)
86
    const QChar ESC(0x1b);
87
    output.remove(QRegularExpression(QString(ESC)+QLatin1String("\\][^\a]*\a")));
88

89
    const QString promptRegexpBase(QLatin1String("(^|\\n)%1"));
90
91
92
93
    const QRegularExpression promptRegexp(promptRegexpBase.arg(
                QRegularExpression::escape(QLatin1String(SageSession::SagePrompt))));
    const QRegularExpression altPromptRegexp(promptRegexpBase.arg(
                QRegularExpression::escape(QLatin1String(SageSession::SageAlternativePrompt))));
94

95
    bool endsWithAlternativePrompt=output.endsWith(QLatin1String(SageSession::SageAlternativePrompt));
96

97
98
99
    //remove all prompts. we do this in a loop, because after we removed the first prompt,
    //there could be a second one, that isn't matched by promptRegexp in the first run, because
    //it originally isn't at the beginning of a line.
100
101
    int index=-1, index2=-1;
    while ( (index=output.indexOf(promptRegexp)) != -1 || (index2=output.indexOf(altPromptRegexp)) != -1 )
102
    {
103
        qDebug()<<"got prompt"<<index<<"  "<<index2;
104
105
106
107
108
109
110
        if(index!=-1)
        {
            m_promptCount--;

            //remove this prompt, the if is needed, because, if the prompt is on the
            //beginning of the string, index points to the "s", if it is within the string
            //index points to the newline
111
            if(output[index]==QLatin1Char('\n'))
112
113
114
115
                output.remove(index+1, SageSession::SagePrompt.length());
            else
                output.remove(index, SageSession::SagePrompt.length());
        }
116

117
118
119
120
121
        if(index2!=-1)
        {
            m_promptCount--;

            //see comment above, for the reason for this "if"
122
            if(output[index2]==QLatin1Char('\n'))
123
124
125
126
127
128
129
                output.remove(index2+1, SageSession::SageAlternativePrompt.length());
            else
                output.remove(index2, SageSession::SageAlternativePrompt.length());
        }

        //reset the indices
        index=index2=-1;
130
    }
131

132
    m_outputCache+=output;
133
134

    if(m_promptCount<=0)
135
    {
136
        if(m_promptCount<0)
137
            qDebug()<<"got too many prompts";
138

139
        //if the output ends with an AlternativePrompt, this means that
Yuri Chornoivan's avatar
Yuri Chornoivan committed
140
        //Sage is expecting additional input, although m_promptCount==0
141
142
143
        //indicates that all information has been passed to sage.
        //This means that the user has entered an invalid command.
        //interrupt it and show an error message
144
        if(endsWithAlternativePrompt)
145
        {
146
147
            // Exit from sage additional input mode
            static_cast<SageSession*>(session())->sendInputToProcess(QLatin1String("\x03"));
Nikita Sirgienko's avatar
Nikita Sirgienko committed
148
149
150
151
152
            m_syntaxError = true;
        }
        else
        {
            evalFinished();
153
        }
154
155
156
157
158
159
    }

}

void SageExpression::parseError(const QString& text)
{
160
    qDebug()<<"error";
Nikita Sirgienko's avatar
Nikita Sirgienko committed
161
    setErrorMessage(text);
Alexander Rieder's avatar
Alexander Rieder committed
162
    setStatus(Cantor::Expression::Error);
163
164
165
166
}

void SageExpression::addFileResult( const QString& path )
{
167
  QUrl url = QUrl::fromLocalFile(path);
Filipe Saraiva's avatar
Filipe Saraiva committed
168
169
170
171
  QMimeDatabase db;
  QMimeType type = db.mimeTypeForUrl(url);

  if(m_imagePath.isEmpty()||type.name().contains(QLatin1String("image"))||path.endsWith(QLatin1String(".png"))||path.endsWith(QLatin1String(".gif")))
172
  {
173
      m_imagePath=path;
174
175
176
177
178
  }
}

void SageExpression::evalFinished()
{
179
180
181
    qDebug()<<"evaluation finished";
    qDebug()<<m_outputCache;

182
    //check if our image path contains a valid image that we can try to show
183
    const bool hasImage=!m_imagePath.isNull();
184

185
    if (!m_outputCache.isEmpty())
186
    {
Alexander Rieder's avatar
Alexander Rieder committed
187
        QString stripped=m_outputCache;
188
        const bool isHtml=stripped.contains(QLatin1String("<html>"));
189
        const bool isLatex=m_outputCache.contains(QLatin1String("\\newcommand{\\Bold}")); //Check if it's latex stuff
190
        if(isLatex) //It's latex stuff so encapsulate it into an eqnarray environment
Alexander Rieder's avatar
Alexander Rieder committed
191
        {
192
193
            stripped.prepend(QLatin1String("\\begin{eqnarray*}"));
            stripped.append(QLatin1String("\\end{eqnarray*}"));
Alexander Rieder's avatar
Alexander Rieder committed
194
195
196
197
198
        }

        //strip html tags
        if(isHtml)
        {
199
            stripped.remove( QRegularExpression( QStringLiteral("<[a-zA-Z\\/][^>]*>") ) );
Alexander Rieder's avatar
Alexander Rieder committed
200
        }
201

202
        if (stripped.endsWith(QLatin1Char('\n')))
Alexander Rieder's avatar
Alexander Rieder committed
203
            stripped.chop(1);
204

Alexander Rieder's avatar
Alexander Rieder committed
205
        if (m_isHelpRequest)
206
        {
207
            stripped = stripped.toHtmlEscaped();
208
            stripped.replace(QLatin1Char(' '), QLatin1String("&nbsp;"));
209
            stripped.replace(QLatin1Char('\n'), QLatin1String("<br/>\n"));
210

211
            //make things quoted in `` `` bold
212
            stripped.replace(QRegularExpression(QStringLiteral("``([^`]*)``")), QStringLiteral("<b>\\1</b>"));
213

214
            addResult(new Cantor::HelpResult(stripped, true));
215
216
217
        }
        else
        {
218
219
220
221
            Cantor::TextResult* result=new Cantor::TextResult(stripped);
            if(isLatex)
                result->setFormat(Cantor::TextResult::LatexFormat);
            addResult(result);
222
223
        }
    }
224
225

    if (hasImage)
226
    {
Filipe Saraiva's avatar
Filipe Saraiva committed
227
228
229
    QMimeDatabase db;
    QMimeType type = db.mimeTypeForUrl(QUrl::fromLocalFile(m_imagePath));
        if(type.inherits(QLatin1String("image/gif")))
230
            addResult( new Cantor::AnimationResult(QUrl::fromLocalFile(m_imagePath ),i18n("Result of %1" , command() ) ) );
231
        else
232
            addResult( new Cantor::ImageResult( QUrl::fromLocalFile(m_imagePath ),i18n("Result of %1" , command() ) ) );
233
    }
Alexander Rieder's avatar
Alexander Rieder committed
234
    setStatus(Cantor::Expression::Done);
235
236
}

237
238
239
240
void SageExpression::onProcessError(const QString& msg)
{
    QString errMsg=i18n("%1\nThe last output was: \n %2", msg, m_outputCache.trimmed());
    setErrorMessage(errMsg);
Alexander Rieder's avatar
Alexander Rieder committed
241
    setStatus(Cantor::Expression::Error);
242
}
243

244
245
QString SageExpression::additionalLatexHeaders()
{
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
    //The LaTeX sage needs the amsmath package and some specific macros.
    //So include them in the header.
    //More about the macros requirement in bug #312738
    return QLatin1String("\\usepackage{amsmath}\n"                   \
                         "\\newcommand{\\ZZ}{\\Bold{Z}}\n"           \
                         "\\newcommand{\\NN}{\\Bold{N}}\n"           \
                         "\\newcommand{\\RR}{\\Bold{R}}\n"           \
                         "\\newcommand{\\CC}{\\Bold{C}}\n"           \
                         "\\newcommand{\\QQ}{\\Bold{Q}}\n"           \
                         "\\newcommand{\\QQbar}{\\overline{\\QQ}}\n" \
                         "\\newcommand{\\GF}[1]{\\Bold{F}_{#1}}\n"   \
                         "\\newcommand{\\Zp}[1]{\\ZZ_{#1}}\n"        \
                         "\\newcommand{\\Qp}[1]{\\QQ_{#1}}\n"        \
                         "\\newcommand{\\Zmod}[1]{\\ZZ/#1\\ZZ}\n"    \
                         "\\newcommand{\\CDF}{\\Bold{C}}\n"          \
                         "\\newcommand{\\CIF}{\\Bold{C}}\n"          \
                         "\\newcommand{\\CLF}{\\Bold{C}}\n"          \
                         "\\newcommand{\\RDF}{\\Bold{R}}\n"          \
                         "\\newcommand{\\RIF}{\\Bold{I} \\Bold{R}}\n"\
                         "\\newcommand{\\RLF}{\\Bold{R}}\n"          \
                         "\\newcommand{\\CFF}{\\Bold{CFF}}\n");
267
268
}