diff --git a/src/backends/CMakeLists.txt b/src/backends/CMakeLists.txt --- a/src/backends/CMakeLists.txt +++ b/src/backends/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(maxima) add_subdirectory(octave) add_subdirectory(scilab) +add_subdirectory(julia) if(NOT WIN32) add_subdirectory(sage) diff --git a/src/backends/julia/CMakeLists.txt b/src/backends/julia/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/backends/julia/CMakeLists.txt @@ -0,0 +1,16 @@ +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}) diff --git a/src/backends/julia/juliabackend.h b/src/backends/julia/juliabackend.h new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliabackend.h @@ -0,0 +1,44 @@ +/* + 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 + */ +#pragma once + +#include "backend.h" + +class JuliaBackend: public Cantor::Backend +{ + Q_OBJECT +public: + explicit JuliaBackend( + QObject *parent = 0, + const QList &args = QList()); + + 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; +}; diff --git a/src/backends/julia/juliabackend.cpp b/src/backends/julia/juliabackend.cpp new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliabackend.cpp @@ -0,0 +1,95 @@ +/* + 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 + */ +#include "juliabackend.h" + +#include + +#include "juliasession.h" +#include "ui_settings.h" +#include "settings.h" + +JuliaBackend::JuliaBackend(QObject *parent, const QList &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( + "

Julia 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.

" + ); +} + +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(); +) + +#include "juliabackend.moc" diff --git a/src/backends/julia/juliabackend.json b/src/backends/julia/juliabackend.json new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliabackend.json @@ -0,0 +1,12 @@ +{ + "KPlugin": { + "Dependencies": [], + "Description": "Julia backend for Cantor", + "Id": "Julia", + "Name": "Julia", + "ServiceTypes": [ + "Cantor/Backend" + ], + "Website": "http://julialang.org/" + } +} diff --git a/src/backends/julia/juliabackend.kcfg b/src/backends/julia/juliabackend.kcfg new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliabackend.kcfg @@ -0,0 +1,16 @@ + + + + QStandardPaths + + + + + QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("julia"))) + + + + diff --git a/src/backends/julia/juliaexpression.h b/src/backends/julia/juliaexpression.h new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliaexpression.h @@ -0,0 +1,34 @@ +/* + 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 + */ +#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(); +}; diff --git a/src/backends/julia/juliaexpression.cpp b/src/backends/julia/juliaexpression.cpp new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliaexpression.cpp @@ -0,0 +1,57 @@ +/* + 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 + */ +#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(session())->runExpression(this); +} + +void JuliaExpression::finalize() +{ + auto juliaSession = dynamic_cast(session()); + setErrorMessage( + juliaSession->getError() + .replace(QLatin1String("\n"), QLatin1String("
")) + ); + 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" diff --git a/src/backends/julia/juliaserver/CMakeLists.txt b/src/backends/julia/juliaserver/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliaserver/CMakeLists.txt @@ -0,0 +1,14 @@ +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}) diff --git a/src/backends/julia/juliaserver/juliaserver.h b/src/backends/julia/juliaserver/juliaserver.h new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliaserver/juliaserver.h @@ -0,0 +1,44 @@ +/* + * 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 +#include + +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; +}; diff --git a/src/backends/julia/juliaserver/juliaserver.cpp b/src/backends/julia/juliaserver/juliaserver.cpp new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliaserver/juliaserver.cpp @@ -0,0 +1,117 @@ +/* + * 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 + */ +#include "juliaserver.h" + +#include + +#include +#include +#include +#include +#include + +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_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_eval_string("catch_backtrace()") + ); + jl_value_t *err_stream = static_cast( + 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_eval_string("nothing")); + bool is_nothing = jl_unbox_bool( + static_cast(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" diff --git a/src/backends/julia/juliaserver/main.cpp b/src/backends/julia/juliaserver/main.cpp new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliaserver/main.cpp @@ -0,0 +1,57 @@ +/* + * 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 + */ + +#include +#include +#include +#include +#include + +#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(); +} diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliasession.h @@ -0,0 +1,69 @@ +/* + 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 + */ +#pragma once + +#include "session.h" + +class JuliaExpression; +class KProcess; +class QDBusInterface; + +class JuliaSession: public Cantor::Session +{ + Q_OBJECT +public: + JuliaSession(Cantor::Backend *backend); + virtual ~JuliaSession() {} + + virtual void login() override; + virtual void logout() override; + + virtual void interrupt() override; + + virtual Cantor::Expression *evaluateExpression( + const QString &command, + Cantor::Expression::FinishingBehavior behave) override; + + virtual Cantor::CompletionObject *completionFor( + const QString &cmd, + int index = -1) override; + +private Q_SLOTS: + void onResultReady(); + +private: + KProcess *m_process; + QDBusInterface *m_interface; + + QList m_runningExpressions; + JuliaExpression *m_currentExpression; + + friend JuliaExpression; + + void runExpression(JuliaExpression *expression); + + void runJuliaCommand(const QString &command) const; + void runJuliaCommandAsync(const QString &command); + + QString getStringFromServer(const QString &method); + QString getOutput(); + QString getError(); + bool getWasException(); +}; diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliasession.cpp @@ -0,0 +1,191 @@ +/* + 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 + */ +#include "juliasession.h" + +#include +#include +#include +#include +#include + +#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; + } + + m_interface->call( + QString::fromLatin1("login"), + JuliaSettings::self()->replPath().path() + ); + + emit ready(); +} + +void JuliaSession::logout() +{ + m_process->terminate(); + changeStatus(Cantor::Session::Done); +} + +void JuliaSession::interrupt() +{ + if (m_process->pid()) { + m_process->kill(); + } + + for (Cantor::Expression *e : m_runningExpressions) { + e->interrupt(); + } + + m_runningExpressions.clear(); + changeStatus(Cantor::Session::Done); +} + +Cantor::Expression *JuliaSession::evaluateExpression( + const QString &cmd, + Cantor::Expression::FinishingBehavior behave) +{ + JuliaExpression *expr = new JuliaExpression(this); + + changeStatus(Cantor::Session::Running); + + expr->setFinishingBehavior(behave); + expr->setCommand(cmd); + expr->evaluate(); + + return expr; +} + +Cantor::CompletionObject *JuliaSession::completionFor( + const QString &command, + int index) +{ + Q_UNUSED(command); + Q_UNUSED(index); + return nullptr; +} + +void JuliaSession::runJuliaCommand(const QString &command) const +{ + m_interface->call(QLatin1String("runJuliaCommand"), command); +} + +void JuliaSession::runJuliaCommandAsync(const QString &command) +{ + m_interface->callWithCallback( + QLatin1String("runJuliaCommand"), + {command}, + this, + SLOT(onResultReady()) + ); +} + +void JuliaSession::onResultReady() +{ + m_currentExpression->finalize(); + m_runningExpressions.removeAll(m_currentExpression); + + changeStatus(Cantor::Session::Done); +} + +void JuliaSession::runExpression(JuliaExpression *expr) +{ + m_runningExpressions.append(expr); + m_currentExpression = expr; + runJuliaCommandAsync(expr->command()); +} + +QString JuliaSession::getStringFromServer(const QString &method) +{ + const QDBusReply &reply = m_interface->call(method); + return (reply.isValid() ? reply.value() : reply.error().message()); +} + +QString JuliaSession::getOutput() +{ + return getStringFromServer(QLatin1String("getOutput")); +} + +QString JuliaSession::getError() +{ + return getStringFromServer(QLatin1String("getError")); +} + +bool JuliaSession::getWasException() +{ + const QDBusReply &reply = + m_interface->call(QLatin1String("getWasException")); + return reply.isValid() and reply.value(); +} + +#include "juliasession.moc" diff --git a/src/backends/julia/settings.kcfgc b/src/backends/julia/settings.kcfgc new file mode 100644 --- /dev/null +++ b/src/backends/julia/settings.kcfgc @@ -0,0 +1,3 @@ +File=juliabackend.kcfg +ClassName=JuliaSettings +Singleton=true diff --git a/src/backends/julia/settings.ui b/src/backends/julia/settings.ui new file mode 100644 --- /dev/null +++ b/src/backends/julia/settings.ui @@ -0,0 +1,31 @@ + + + JuliaSettingsBase + + + + + + + + Path to Julia REPL: + + + + + + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + +