Commit 37e5621b authored by Nikita Sirgienko's avatar Nikita Sirgienko
Browse files

[Lua] Improve Lua parsing code. Also add support for expression queue.

BUG: 393579
FIXED-IN: 20.12
parent d38b4f36
Pipeline #35397 passed with stage
in 32 minutes and 46 seconds
......@@ -44,14 +44,12 @@ void LuaExpression::evaluate()
* set the status to computing
* decide what needs to be done if the user is trying to define a function etc
*/
setStatus(Cantor::Expression::Computing);
if (command().isEmpty()) {
setStatus(Cantor::Expression::Done);
return;
}
LuaSession* currentSession = dynamic_cast<LuaSession*>(session());
currentSession->runExpression(this);
session()->enqueueExpression(this);
}
void LuaExpression::parseError(QString &error)
......@@ -61,44 +59,12 @@ void LuaExpression::parseError(QString &error)
setStatus(Error);
}
void LuaExpression::parseOutput(QString &output)
void LuaExpression::parseOutput(const QString &output)
{
qDebug()<<"parsing the output " << output;
const QStringList& inputs = command().split(QLatin1Char('\n'));
const QStringList& outputs = output.split(QLatin1Char('\n'));
QString parsedOutput;
for (auto out : outputs) {
//remove lua's promt characters if available
if (out.startsWith(QLatin1String("> ")))
out.remove(0, 2);
else if (out.startsWith(QLatin1String(">> ")))
out.remove(0, 3);
//for multi-line inputs lua returs the inputs as part of the output
//-> remove the input lines from the output.
//since lua doesn't always seem to preserve the spaces, compare trimmed strings
out = out.trimmed();
bool found = false;
for (auto in : inputs) {
if (out == in.trimmed()) {
found = true;
break;
}
}
if (!found) {
if (!parsedOutput.isEmpty())
parsedOutput += QLatin1Char('\n');
parsedOutput += out;
}
}
qDebug() << "final output of the command " << command() << ": " << parsedOutput << endl;
if (!parsedOutput.isEmpty())
setResult(new Cantor::TextResult(parsedOutput));
if (!output.isEmpty())
setResult(new Cantor::TextResult(output));
setStatus(Cantor::Expression::Done);
}
......
......@@ -35,7 +35,7 @@ public:
void evaluate() override;
void interrupt() override;
void parseOutput(QString& output);
void parseOutput(const QString& output);
void parseError(QString& error);
};
......
......@@ -26,12 +26,18 @@
#include <settings.h>
#include "ui_settings.h"
#ifndef Q_OS_WIN
#include <signal.h>
#endif
#include <QProcess>
const QString LuaSession::LUA_PROMPT = QLatin1String("> ");
const QString LuaSession::LUA_SUBPROMPT = QLatin1String(">> ");
LuaSession::LuaSession( Cantor::Backend* backend) : Session(backend),
m_L(nullptr),
m_process(nullptr),
m_currentExpression(nullptr)
m_process(nullptr)
{
}
......@@ -80,7 +86,8 @@ void LuaSession::readIntroMessage()
m_output.append(QString::fromLocal8Bit(m_process->readLine()));
}
if(!m_output.isEmpty() && m_output.trimmed().endsWith(QLatin1String(">"))) {
const QString& output = m_output.join(QLatin1String("\n"));
if(!output.isEmpty() && output.trimmed().endsWith(QLatin1String(">"))) {
qDebug() << " reading the intro message " << m_output ;
m_output.clear();
......@@ -100,31 +107,90 @@ void LuaSession::readOutput()
// keep reading till the output ends with '>'.
// '>' marks the end of output for a particular command;
while(m_process->bytesAvailable()) {
m_output.append(QString::fromLocal8Bit(m_process->readLine()));
QString line = QString::fromLocal8Bit(m_process->readLine());
if (line.endsWith(QLatin1String("\n")))
line.chop(1);
m_output.append(line);
}
//merge outputs until lua's promt "> " appears, take care of the comment characters "-->"
if(m_currentExpression && !m_output.isEmpty() && m_output.endsWith(QLatin1String("> "))
&& !m_output.endsWith(QLatin1String("-> ")))
if (expressionQueue().size() > 0)
{
// we have our complete output
// clean the output and parse it and clear m_output;
m_currentExpression->parseOutput(m_output);
m_output.clear();
LuaExpression* expression = static_cast<LuaExpression*>(expressionQueue().first());
// How parsing works:
// For each line of input command, which for example we name as X
// lua prints output in this form (common form, 90% of output)
// X + "\n" + command_output + "\n" + "> " or ">> " + "\n"
// or (merged form, rare, only 10% of output
// X + "\n" + command_output + "\n" + ("> " or ">> " in beginning of next X)
// Sometimes empty lines also apears in command output
// In this realisation we iterate over input lines and output line and copy only output lines
int input_idx = 0;
int previous_output_idx = 0;
int output_idx = 0;
QString output;
qDebug() << "m_inputCommands" << m_inputCommands;
qDebug() << m_output;
while (output_idx < m_output.size() && input_idx < m_inputCommands.size())
{
const QString& line = m_output[output_idx];
bool previousLineIsPrompt = (output_idx >= 1 ? isPromptString(m_output[output_idx-1]) : true);
bool commonInputMatch = (line == m_inputCommands[input_idx] && previousLineIsPrompt);
bool mergedInputMatch = (line == LUA_PROMPT + m_inputCommands[input_idx] || line == LUA_SUBPROMPT + m_inputCommands[input_idx]);
if (commonInputMatch || mergedInputMatch)
{
if (previous_output_idx + 1 < output_idx)
{
for (int i = previous_output_idx+1; i < output_idx; i++)
{
const QString& copiedLine = m_output[i];
bool isLastLine = i == output_idx-1;
if (!(isLastLine && previousLineIsPrompt))
output += copiedLine + QLatin1String("\n");
}
}
previous_output_idx = output_idx;
input_idx++;
}
output_idx++;
}
if (input_idx == m_inputCommands.size() && m_output[m_output.size()-1] == LUA_PROMPT)
{
//We parse all output, so copy tail of the output and pass it to lua expression
for (int i = previous_output_idx+1; i < m_output.size()-1; i++)
{
const QString& copiedLine = m_output[i];
bool isLastLine = i == m_output.size()-1;
if (isLastLine)
output += copiedLine;
else
output += copiedLine + QLatin1String("\n");
}
expression->parseOutput(output);
m_output.clear();
}
}
}
bool LuaSession::isPromptString(const QString& s)
{
return s == LUA_PROMPT || s == LUA_SUBPROMPT;
}
void LuaSession::readError()
{
qDebug() << "readError";
QString error = QString::fromLocal8Bit(m_process->readAllStandardError());
if (!m_currentExpression || error.isEmpty())
if (expressionQueue().isEmpty() || error.isEmpty())
{
return;
}
m_currentExpression->parseError(error);
static_cast<LuaExpression*>(expressionQueue().first())->parseError(error);
}
void LuaSession::processStarted()
......@@ -149,9 +215,24 @@ void LuaSession::logout()
void LuaSession::interrupt()
{
if (status() == Cantor::Session::Running && m_currentExpression)
m_currentExpression->interrupt();
// Lua backend is synchronous, there is no way to currently interrupt an expression (in a clean way)
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();
}
changeStatus(Cantor::Session::Done);
}
......@@ -159,28 +240,30 @@ Cantor::Expression* LuaSession::evaluateExpression(const QString& cmd, Cantor::E
{
changeStatus(Cantor::Session::Running);
m_currentExpression = new LuaExpression(this, internal);
connect(m_currentExpression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(expressionFinished(Cantor::Expression::Status)));
m_currentExpression->setFinishingBehavior(behave);
m_currentExpression->setCommand(cmd);
m_currentExpression->evaluate();
LuaExpression* expression = new LuaExpression(this, internal);
expression->setFinishingBehavior(behave);
expression->setCommand(cmd);
expression->evaluate();
return m_currentExpression;
return expression;
}
void LuaSession::runExpression(LuaExpression *currentExpression)
void LuaSession::runFirstExpression()
{
/*
* get the current command
* format it and write to m_process
*/
QString command = currentExpression->command();
Cantor::Expression* expression = expressionQueue().first();
connect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(expressionFinished(Cantor::Expression::Status)));
QString command = expression->internalCommand();
command += QLatin1String("\n");
m_inputCommands = command.split(QLatin1String("\n"));
m_output.clear();
command += QLatin1String("\n");
qDebug() << "final command to be executed " << command << endl;
qDebug() << "m_inputCommands" << m_inputCommands;
m_process->write(command.toLocal8Bit());
expression->setStatus(Cantor::Expression::Computing);
}
Cantor::CompletionObject* LuaSession::completionFor(const QString& command, int index)
......@@ -190,15 +273,14 @@ Cantor::CompletionObject* LuaSession::completionFor(const QString& command, int
void LuaSession::expressionFinished(Cantor::Expression::Status status)
{
switch(status) {
case Cantor::Expression::Computing:
break;
switch(status)
{
case Cantor::Expression::Done:
case Cantor::Expression::Error:
case Cantor::Expression::Interrupted:
changeStatus(Cantor::Session::Done);
finishFirstExpression();
break;
default:
break;
}
}
......
......@@ -39,7 +39,7 @@ public:
void interrupt() override;
void runExpression(LuaExpression* currentExpression);
void runFirstExpression() override;
Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override;
Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override;
......@@ -55,11 +55,17 @@ public Q_SLOTS:
private Q_SLOTS:
void expressionFinished(Cantor::Expression::Status status);
private:
bool isPromptString(const QString& s);
private:
lua_State* m_L;
QProcess* m_process;
LuaExpression* m_currentExpression;
QString m_output;
QStringList m_inputCommands;
QStringList m_output;
static const QString LUA_PROMPT;
static const QString LUA_SUBPROMPT;
};
#endif /* _LUASESSION_H */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment