Commit 7e6af394 authored by Ivan Lakhtanov's avatar Ivan Lakhtanov
Browse files

Impelemented command execution with DBUS

Used Julia embedding. Features: execution, syntax errors,
exceptions, multiline input, printing of the last result.

Julia path is detected automatically.

BUGS and FIXMES:
* Workaround: julia redirect commands user pipes/sockets with
  limited buffer to store data. So on large output writing pipe
  end blocks, and julia server execution is blocked. It's
  better to reimplement this by using concurrent threads to
  run julia command and read from pipe. Now using files as they
  provide unlimited buffer.

Differential Revision: https://phabricator.kde.org/D2006
parent 0d185884
......@@ -14,6 +14,7 @@ add_subdirectory(null)
add_subdirectory(maxima)
add_subdirectory(octave)
add_subdirectory(scilab)
add_subdirectory(julia)
if(NOT WIN32)
add_subdirectory(sage)
......
add_subdirectory(juliaserver)
set(JuliaBackend_SRCS
juliabackend.cpp
juliasession.cpp
juliaexpression.cpp
)
kconfig_add_kcfg_files(JuliaBackend_SRCS settings.kcfgc)
ki18n_wrap_ui(JuliaBackend_SRCS settings.ui)
add_backend(juliabackend ${JuliaBackend_SRCS})
target_link_libraries(cantor_juliabackend Qt5::DBus)
install(FILES juliabackend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
/*
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) 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
*/
#include "juliabackend.h"
#include <klocalizedstring.h>
#include "juliasession.h"
#include "ui_settings.h"
#include "settings.h"
JuliaBackend::JuliaBackend(QObject *parent, const QList<QVariant> &args)
: Cantor::Backend(parent, args)
{
setEnabled(true);
}
QString JuliaBackend::id() const
{
return QLatin1String("julia");
}
Cantor::Session *JuliaBackend::createSession()
{
return new JuliaSession(this);
}
Cantor::Backend::Capabilities JuliaBackend::capabilities() const
{
return Cantor::Backend::Nothing;
}
QString JuliaBackend::description() const
{
return i18n(
"<p><b>Julia</b> is a high-level, high-performance dynamic programming "
"language for technical computing, with syntax that is familiar to "
"users of other technical computing environments. It provides a "
"sophisticated compiler, distributed parallel execution, numerical "
"accuracy, and an extensive mathematical function library.</p>"
);
}
QUrl JuliaBackend::helpUrl() const
{
return QUrl(i18nc(
"The url to the documentation of Julia, please check if there is a"
" translated version and use the correct url",
"http://docs.julialang.org/en/latest/"
));
}
bool JuliaBackend::requirementsFullfilled() const
{
return QFileInfo(
JuliaSettings::self()->replPath().toLocalFile()
).isExecutable();
}
QWidget *JuliaBackend::settingsWidget(QWidget *parent) const
{
QWidget *widget = new QWidget(parent);
Ui::JuliaSettingsBase s;
s.setupUi(widget);
return widget;
}
KConfigSkeleton *JuliaBackend::config() const
{
return JuliaSettings::self();
}
K_PLUGIN_FACTORY_WITH_JSON(
juliabackend,
"juliabackend.json",
registerPlugin<JuliaBackend>();
)
#include "juliabackend.moc"
/*
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) 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
*/
#pragma once
#include "backend.h"
class JuliaBackend: public Cantor::Backend
{
Q_OBJECT
public:
explicit JuliaBackend(
QObject *parent = 0,
const QList<QVariant> &args = QList<QVariant>());
virtual ~JuliaBackend() {}
virtual QString id() const override;
virtual Cantor::Session *createSession() override;
virtual Cantor::Backend::Capabilities capabilities() const override;
virtual QString description() const override;
virtual QUrl helpUrl() const override;
virtual bool requirementsFullfilled() const override;
virtual QWidget *settingsWidget(QWidget *parent) const override;
virtual KConfigSkeleton *config() const override;
};
{
"KPlugin": {
"Dependencies": [],
"Description": "Julia backend for Cantor",
"Id": "Julia",
"Name": "Julia",
"ServiceTypes": [
"Cantor/Backend"
],
"Website": "http://julialang.org/"
}
}
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name="cantorrc"/>
<include>QStandardPaths</include>
<group name="JuliaBackend">
<entry name="replPath" type="Url">
<label>Path to the Julia native REPL</label>
<default code="true">
QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("julia")))
</default>
</entry>
</group>
</kcfg>
/*
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) 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
*/
#include "juliaexpression.h"
#include "juliasession.h"
#include "textresult.h"
JuliaExpression::JuliaExpression(Cantor::Session *session)
: Cantor::Expression(session)
{
}
void JuliaExpression::evaluate()
{
setStatus(Cantor::Expression::Computing);
dynamic_cast<JuliaSession *>(session())->runExpression(this);
}
void JuliaExpression::finalize()
{
auto juliaSession = dynamic_cast<JuliaSession *>(session());
setErrorMessage(
juliaSession->getError()
.replace(QLatin1String("\n"), QLatin1String("<br>"))
);
if (juliaSession->getWasException()) {
setResult(new Cantor::TextResult(juliaSession->getOutput()));
setStatus(Cantor::Expression::Error);
} else {
setResult(new Cantor::TextResult(juliaSession->getOutput()));
setStatus(Cantor::Expression::Done);
}
}
void JuliaExpression::interrupt()
{
setStatus(Cantor::Expression::Interrupted);
}
#include "juliaexpression.moc"
/*
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) 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
*/
#pragma once
#include "expression.h"
class JuliaExpression: public Cantor::Expression
{
Q_OBJECT
public:
JuliaExpression(Cantor::Session *session);
virtual ~JuliaExpression() {};
virtual void evaluate() override;
virtual void interrupt() override;
void finalize();
};
set(JuliaServer_SRCS
juliaserver.cpp
main.cpp
)
add_executable(cantor_juliaserver ${JuliaServer_SRCS})
target_link_libraries(cantor_juliaserver
julia
Qt5::Widgets
Qt5::DBus
)
install(TARGETS cantor_juliaserver ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
/*
* 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) 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
*/
#include "juliaserver.h"
#include <julia/julia.h>
#include <iostream>
#include <QFileInfo>
#include <QDir>
#include <QTemporaryFile>
#include <QDebug>
JuliaServer::JuliaServer(QObject *parent)
: QObject(parent)
{
}
JuliaServer::~JuliaServer()
{
jl_atexit_hook(0);
}
void JuliaServer::login(const QString &path) const
{
QString dir_path = QFileInfo(path).dir().absolutePath();
jl_init(dir_path.toLatin1().constData());
}
void JuliaServer::runJuliaCommand(const QString &command)
{
QTemporaryFile output, error;
if (not output.open() or not error.open()) {
qFatal("Unable to create temprorary files for stdout/stderr");
return;
}
jl_eval_string("const originalSTDOUT = STDOUT");
jl_eval_string("const originalSTDERR = STDERR");
jl_eval_string(
QString::fromLatin1("redirect_stdout(open(\"%1\", \"w\"))")
.arg(output.fileName()).toLatin1().constData()
);
jl_eval_string(
QString::fromLatin1("redirect_stderr(open(\"%1\", \"w\"))")
.arg(error.fileName()).toLatin1().constData()
);
jl_value_t *val = static_cast<jl_value_t *>(
jl_eval_string(command.toLatin1().constData())
);
if (jl_exception_occurred()) {
jl_value_t *ex = jl_exception_in_transit;
jl_printf(JL_STDERR, "error during run:\n");
jl_function_t *showerror =
jl_get_function(jl_base_module, "showerror");
jl_value_t *bt = static_cast<jl_value_t *>(
jl_eval_string("catch_backtrace()")
);
jl_value_t *err_stream = static_cast<jl_value_t *>(
jl_eval_string("STDERR")
);
jl_call3(showerror, err_stream, ex, bt);
jl_exception_clear();
m_was_exception = true;
} else if (val) {
jl_function_t *equality = jl_get_function(jl_base_module, "==");
jl_value_t *nothing =
static_cast<jl_value_t *>(jl_eval_string("nothing"));
bool is_nothing = jl_unbox_bool(
static_cast<jl_value_t *>(jl_call2(equality, nothing, val))
);
if (not is_nothing) {
jl_static_show(JL_STDOUT, val);
}
m_was_exception = false;
}
jl_eval_string("flush(STDOUT)");
jl_eval_string("flush(STDERR)");
jl_eval_string("redirect_stdout(originalSTDOUT)");
jl_eval_string("redirect_stderr(originalSTDERR)");
m_output = QString::fromUtf8(output.readAll());
m_error = QString::fromUtf8(error.readAll());
}
QString JuliaServer::getError() const
{
return m_error;
}
QString JuliaServer::getOutput() const
{
return m_output;
}
bool JuliaServer::getWasException() const
{
return m_was_exception;
}
#include "juliaserver.moc"
/*
* 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) 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com
*/
#pragma once
#include <QObject>
#include <QString>
class JuliaServer: public QObject
{
Q_OBJECT
public:
JuliaServer(QObject *parent = nullptr);
virtual ~JuliaServer();
public Q_SLOTS:
Q_SCRIPTABLE void login(const QString &path) const;
Q_SCRIPTABLE void runJuliaCommand(const QString &command);
Q_SCRIPTABLE QString getOutput() const;
Q_SCRIPTABLE QString getError() const;
Q_SCRIPTABLE bool getWasException() const;
private:
QString m_error;
QString m_output;
bool m_was_exception;
};
/*
* 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) 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
*/
#include <QApplication>
#include <QDBusConnection>
#include <QDBusError>
#include <QDebug>
#include <QTextStream>
#include "juliaserver.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
if (not QDBusConnection::sessionBus().isConnected()) {
qWarning() << "Can't connect to the D-Bus session bus.\n"
"To start it, run: eval `dbus-launch --auto-syntax`";
return 1;
}
const QString &serviceName =
QString::fromLatin1("org.kde.Cantor.Julia-%1").arg(app.applicationPid());
if (not QDBusConnection::sessionBus().registerService(serviceName)) {
qWarning() << QDBusConnection::sessionBus().lastError().message();
return 2;
}
JuliaServer server;
QDBusConnection::sessionBus().registerObject(
QLatin1String("/"),
&server,
QDBusConnection::ExportAllSlots
);
QTextStream(stdout) << "ready" << endl;
return app.exec();
}
/*
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) 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
*/
#include "juliasession.h"
#include <KProcess>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
#include <QStandardPaths>
#include "juliaexpression.h"
#include "settings.h"
JuliaSession::JuliaSession(Cantor::Backend *backend)
: Session(backend)
, m_process(nullptr)
, m_interface(nullptr)
, m_currentExpression(nullptr)
{
}
void JuliaSession::login()
{
if (m_process) {
m_process->deleteLater();
}
m_process = new KProcess(this);
m_process->setOutputChannelMode(KProcess::SeparateChannels);
(*m_process)
<< QStandardPaths::findExecutable(QLatin1String("cantor_juliaserver"));
m_process->start();
m_process->waitForStarted();
m_process->waitForReadyRead();
QTextStream stream(m_process->readAllStandardOutput());
QString readyStatus = QLatin1String("ready");
while (m_process->state() == QProcess::Running) {
const QString &rl = stream.readLine();
if (rl == readyStatus) {
break;
}
}
if (!QDBusConnection::sessionBus().isConnected()) {
qWarning() << "Can't connect to the D-Bus session bus.\n"
"To start it, run: eval `dbus-launch --auto-syntax`";
return;
}
const QString &serviceName =
QString::fromLatin1("org.kde.Cantor.Julia-%1").arg(m_process->pid());
m_interface = new QDBusInterface(
serviceName,
QString::fromLatin1("/"),
QString(),
QDBusConnection::sessionBus()
);
if (not m_interface->isValid()) {
qWarning() << QDBusConnection::sessionBus().lastError().message();
return;