diff --git a/README b/README index 5e166b3b..8a549418 100644 --- a/README +++ b/README @@ -1,43 +1,44 @@ Cantor -=-=-=-=-=-=-=-=-=-=-=-=-= Cantor is a KDE Application aimed to provide a nice Interface for doing Mathematics and Scientific Computing. It doesn't implement its own Computation Logic, but instead is built around different Backends. Available Backends -=-=-=-=-=-=-=-=-=-=-=-=-= - KAlgebra for Calculation and Plotting: http://edu.kde.org/kalgebra/ - Lua Programming Language: http://lua.org/ - Maxima Computer Algebra System: http://maxima.sourceforge.net/ - Octave for Numerical Computation: https://gnu.org/software/octave/ - Python 2 Programming Language: http://python.org/ - Python 3 Programming Language: http://python.org/ - Qalculate Desktop Calculator: http://qalculate.sourceforge.net/ - R Project for Statistical Computing: http://r-project.org/ - Sage Mathematics Software: http://sagemath.org/ - Scilab for Numerical Computation: http://scilab.org/ +- Julia programming language: http://julialang.org/ How To Build and Install Cantor -=-=-=-=-=-=-=-=-=-=-=-=-= To build and install Cantor, follow the steps below: ``` cd cantor mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install -DCMAKE_BUILD_TYPE=debugfull make make install or su -c 'make install' ``` If `-DCMAKE_INSTALL_PREFIX` is not used, Cantor will be installed in root directory. If you provide some `/path/to/install`, you must to configure the [`QT_PLUGIN_PATH`](http://doc.qt.io/qt-5/deployment-plugins.html#the-plugin-directory) environment variable in order to Cantor loads the plugins correctly. To uninstall the project: ```make uninstall or su -c 'make uninstall'``` diff --git a/README.md b/README.md index fe907774..dc9f5b51 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,44 @@ ## Cantor Cantor is a KDE Application aimed to provide a nice Interface for doing Mathematics and Scientific Computing. It doesn't implement its own Computation Logic, but instead is built around different Backends. ## Available Backends - KAlgebra for Calculation and Plotting: http://edu.kde.org/kalgebra/ - Lua Programming Language: http://lua.org/ - Maxima Computer Algebra System: http://maxima.sourceforge.net/ - Octave for Numerical Computation: https://gnu.org/software/octave/ - Python 2 Programming Language: http://python.org/ - Python 3 Programming Language: http://python.org/ - Qalculate Desktop Calculator: http://qalculate.sourceforge.net/ - R Project for Statistical Computing: http://r-project.org/ - Sage Mathematics Software: http://sagemath.org/ - Scilab for Numerical Computation: http://scilab.org/ +- Julia programming language: http://julialang.org/ ## How To Build and Install Cantor To build and install Cantor, follow the steps below: ``` cd cantor mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install -DCMAKE_BUILD_TYPE=debugfull make make install or su -c 'make install' ``` If `-DCMAKE_INSTALL_PREFIX` is not used, Cantor will be installed in root directory. If you provide some `/path/to/install`, you must to configure the [`QT_PLUGIN_PATH`](http://doc.qt.io/qt-5/deployment-plugins.html#the-plugin-directory) environment variable in order to Cantor loads the plugins correctly. To uninstall the project: ```make uninstall or su -c 'make uninstall'``` diff --git a/src/backends/julia/juliabackend.h b/src/backends/julia/juliabackend.h index e2089935..b79d3e69 100644 --- a/src/backends/julia/juliabackend.h +++ b/src/backends/julia/juliabackend.h @@ -1,44 +1,86 @@ /* 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" +/** + * Backend for Julia language + * + * @see http://julialang.org/ + * @see JuliaServer + */ class JuliaBackend: public Cantor::Backend { Q_OBJECT public: + /** + * Constructs julia backend + * + * @param parent QObject parent. Defaults to nullptr. + * @param args Additional arguments for the backend. + * Defaults to QList(). + */ explicit JuliaBackend( QObject *parent = 0, const QList &args = QList()); virtual ~JuliaBackend() {} + /** + * @see Cantor::Backend::id + */ virtual QString id() const override; + + /** + * @see Cantor::Backend::createSession + */ virtual Cantor::Session *createSession() override; + /** + * @see Cantor::Backend::capabilities + */ virtual Cantor::Backend::Capabilities capabilities() const override; + + /** + * @see Cantor::Backend::description + */ virtual QString description() const override; + + /** + * @see Cantor::Backend::helpUrl + */ virtual QUrl helpUrl() const override; + + /** + * @see Cantor::Backend::requirementsFullfilled + */ virtual bool requirementsFullfilled() const override; + /** + * @see Cantor::Backend::settingsWidget + */ virtual QWidget *settingsWidget(QWidget *parent) const override; + + /** + * @see Cantor::Backend::config + */ virtual KConfigSkeleton *config() const override; }; diff --git a/src/backends/julia/juliacompletionobject.h b/src/backends/julia/juliacompletionobject.h index 0a3ec6cd..e993e6a6 100644 --- a/src/backends/julia/juliacompletionobject.h +++ b/src/backends/julia/juliacompletionobject.h @@ -1,39 +1,62 @@ /* * 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 "completionobject.h" class JuliaSession; +/** + * Implements code completion for Julia language + * + * Uses Julia's Base.REPL.REPLCompletions.completions command to get + * context-aware completions like in native Julia REPL + */ class JuliaCompletionObject : public Cantor::CompletionObject { public: + /** + * Constructs JuliaCompletionObject + * + * @param cmd command piece to generate completion + * @param index index of cursor in commmand + * @param session current session + */ JuliaCompletionObject(const QString &cmd, int index, JuliaSession *session); ~JuliaCompletionObject(); protected: + /** + * @see Cantor::CompletionObject::mayIdentifierContain + */ virtual bool mayIdentifierContain(QChar c) const; virtual bool mayIdentifierBeginWith(QChar c) const; + /** + * @see Cantor::CompletionObject::mayIdentifierBeginWith + */ + protected Q_SLOTS: + /** + * @see Cantor::CompletionObject::fetchCompletions + */ void fetchCompletions(); }; diff --git a/src/backends/julia/juliaexpression.cpp b/src/backends/julia/juliaexpression.cpp index 6746a6db..27cdd85c 100644 --- a/src/backends/julia/juliaexpression.cpp +++ b/src/backends/julia/juliaexpression.cpp @@ -1,102 +1,105 @@ /* 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 #include #include "settings.h" #include "juliasession.h" #include "juliakeywords.h" #include "textresult.h" #include "imageresult.h" JuliaExpression::JuliaExpression(Cantor::Session *session) : Cantor::Expression(session) { } void JuliaExpression::evaluate() { setStatus(Cantor::Expression::Computing); auto juliaSession = dynamic_cast(session()); + // Plots integration m_plot_filename.clear(); if (juliaSession->integratePlots() and checkPlotShowingCommands()) { + // Simply add plot saving command to the end of execution QStringList inlinePlotFormats; inlinePlotFormats << QLatin1String("svg"); inlinePlotFormats << QLatin1String("eps"); inlinePlotFormats << QLatin1String("png"); auto inlinePlotFormat = inlinePlotFormats[JuliaSettings::inlinePlotFormat()]; m_plot_filename = QDir::tempPath() + QString::fromLatin1("/cantor-julia-export-%1.%2") .arg(QUuid::createUuid().toString()).arg(inlinePlotFormat); QString saveFigCommand = QString::fromLatin1("\nGR.savefig(\"%1\")\n").arg(m_plot_filename); setCommand(command().append(saveFigCommand)); } juliaSession->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 { if (not m_plot_filename.isEmpty() and QFileInfo(m_plot_filename).exists()) { + // If we have plot in result, show it setResult( new Cantor::ImageResult(QUrl::fromLocalFile(m_plot_filename))); QDir().remove(m_plot_filename); } else { setResult(new Cantor::TextResult(juliaSession->getOutput())); } setStatus(Cantor::Expression::Done); } } void JuliaExpression::interrupt() { setStatus(Cantor::Expression::Interrupted); } bool JuliaExpression::checkPlotShowingCommands() { for (auto showingCommand : JuliaKeywords::instance()->plotShowingCommands()) { if (command().contains(showingCommand + QLatin1String("("))) { return true; } } return false; } #include "juliaexpression.moc" diff --git a/src/backends/julia/juliaexpression.h b/src/backends/julia/juliaexpression.h index 344c585f..fd5eb23c 100644 --- a/src/backends/julia/juliaexpression.h +++ b/src/backends/julia/juliaexpression.h @@ -1,39 +1,68 @@ /* 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" +/** + * Expression of Julia language + */ class JuliaExpression: public Cantor::Expression { Q_OBJECT public: + /** + * Creates new JuliaExpression + * + * @param session session to bound expression to + */ JuliaExpression(Cantor::Session *session); virtual ~JuliaExpression() {}; + /** + * @see Cantor::Expression::evaluate + */ virtual void evaluate() override; + + /** + * @see Cantor::Expression::interrupt + */ virtual void interrupt() override; + + /** + * Call this function from session when JuliaServer ends evaluation of + * this expression. + * + * This checks inline plots, exceptions and set appropriate result + */ void finalize(); private: + /// If not empty, it's a filename of plot image file expression is awaiting + /// to get QString m_plot_filename; + + /** + * @return bool indicator if current expression contains command that + * shows plot + */ bool checkPlotShowingCommands(); }; diff --git a/src/backends/julia/juliaextensions.h b/src/backends/julia/juliaextensions.h index cb4486ad..21e2e09d 100644 --- a/src/backends/julia/juliaextensions.h +++ b/src/backends/julia/juliaextensions.h @@ -1,105 +1,204 @@ /* 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 #define JULIA_EXT_CDTOR_DECL(name) Julia##name##Extension(QObject *parent); \ ~Julia##name##Extension(); +/** + * Implementation of linear algebra wizards for Julia + */ class JuliaLinearAlgebraExtension: public Cantor::LinearAlgebraExtension { public: JULIA_EXT_CDTOR_DECL(LinearAlgebra) + /** + * @see Cantor::LinearAlgebraExtension::createVector + */ virtual QString createVector( const QStringList &entries, VectorType type) override; + /** + * @see Cantor::LinearAlgebraExtension::nullVector + */ virtual QString nullVector(int size, VectorType type) override; + /** + * @see Cantor::LinearAlgebraExtension::createMatrix + */ virtual QString createMatrix( const Cantor::LinearAlgebraExtension::Matrix &matrix) override; + /** + * @see Cantor::LinearAlgebraExtension::identityMatrix + */ virtual QString identityMatrix(int size) override; + + /** + * @see Cantor::LinearAlgebraExtension::nullMatrix + */ virtual QString nullMatrix(int rows, int columns) override; + + /** + * @see Cantor::LinearAlgebraExtension::rank + */ virtual QString rank(const QString &matrix) override; + + /** + * @see Cantor::LinearAlgebraExtension::invertMatrix + */ virtual QString invertMatrix(const QString &matrix) override; + + /** + * @see Cantor::LinearAlgebraExtension::charPoly + */ virtual QString charPoly(const QString &matrix) override; + + /** + * @see Cantor::LinearAlgebraExtension::eigenVectors + */ virtual QString eigenVectors(const QString &matrix) override; + + /** + * @see Cantor::LinearAlgebraExtension::eigenValues + */ virtual QString eigenValues(const QString &matrix) override; }; +/** + * Implementation of packaging wizards for Julia + */ class JuliaPackagingExtension: public Cantor::PackagingExtension { public: JULIA_EXT_CDTOR_DECL(Packaging) + /** + * @see Cantor::PackagingExtension::importPackage + */ virtual QString importPackage(const QString &module) override; }; +/** + * Implementation of plot wizards for Julia + * + * Plotting is based on GR package + */ class JuliaPlotExtension: public Cantor::PlotExtension { public: JULIA_EXT_CDTOR_DECL(Plot) + /** + * @see Cantor::PlotExtension::plotFunction2d + */ virtual QString plotFunction2d( const QString &function, const QString &variable, const QString &left, const QString &right) override; + /** + * @see Cantor::PlotExtension::plotFunction3d + */ virtual QString plotFunction3d( const QString &function, VariableParameter var1, VariableParameter var2) override; }; +/** + * Implementation of script wizard for Julia + */ class JuliaScriptExtension: public Cantor::ScriptExtension { public: JULIA_EXT_CDTOR_DECL(Script) + /** + * @see Cantor::ScriptExtension::scriptFileFilter + */ virtual QString scriptFileFilter() override; + + /** + * @see Cantor::ScriptExtension::highlightingMode + */ virtual QString highlightingMode() override; + + /** + * @see Cantor::ScriptExtension::runExternalScript + */ virtual QString runExternalScript(const QString &path) override; }; +/** + * Julia variable management extension + * + * Based on JLD package for loading/saving variables + */ class JuliaVariableManagementExtension: public Cantor::VariableManagementExtension { public: JULIA_EXT_CDTOR_DECL(VariableManagement) + // There is no way to completely delete object from scope: + // http://docs.julialang.org/en/release-0.4/manual/faq/#how-do-i-delete-an-object-in-memory + // So we are saving special marker to variable to mark it as removed static const QString REMOVED_VARIABLE_MARKER; + /** + * @see Cantor::VariableManagementExtension::addVariable + */ virtual QString addVariable( const QString &name, const QString &value) override; + /** + * @see Cantor::VariableManagementExtension::setValue + */ virtual QString setValue( const QString &name, const QString &value) override; + /** + * @see Cantor::VariableManagementExtension::removeVariable + */ virtual QString removeVariable(const QString &name) override; + + /** + * @see Cantor::VariableManagementExtension::saveVariables + */ virtual QString saveVariables(const QString &fileName) override; + + /** + * @see Cantor::VariableManagementExtension::loadVariables + */ virtual QString loadVariables(const QString &fileName) override; + + /** + * @see Cantor::VariableManagementExtension::clearVariables + */ virtual QString clearVariables() override; }; diff --git a/src/backends/julia/juliahighlighter.cpp b/src/backends/julia/juliahighlighter.cpp index 26a7ec72..47953616 100644 --- a/src/backends/julia/juliahighlighter.cpp +++ b/src/backends/julia/juliahighlighter.cpp @@ -1,162 +1,183 @@ /* * 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 "juliahighlighter.h" #include "juliakeywords.h" #include #include JuliaHighlighter::JuliaHighlighter(QObject *parent) : Cantor::DefaultHighlighter(parent) { addKeywords(JuliaKeywords::instance()->keywords()); addVariables(JuliaKeywords::instance()->variables()); addFunctions(JuliaKeywords::instance()->functions()); } void JuliaHighlighter::highlightBlock(const QString &text) { if (skipHighlighting(text)) { return; } // Do some backend independent highlighting (brackets etc.) DefaultHighlighter::highlightBlock(text); + // Now we are about to make corrent strings and comments highlighting + // + // Main idea: as soon as string starts comment or anything else cant start + // until current string ends. The same with comment, except '#' comment + // that ends by newline + // + // To pass information to next block, we are using next states const int IN_MULTILINE_COMMENT = 1; const int IN_CHARACTER = 2; const int IN_SINGLE_QUOTE_STRING = 4; const int IN_TRIPLE_QUOTE_STRING = 8; + // Markers of scopes start, ends QRegExp multiLineCommentStart(QLatin1String("#=")); QRegExp multiLineCommentEnd(QLatin1String("=#")); QRegExp characterStartEnd(QLatin1String("'")); QRegExp singleQuoteStringStartEnd(QLatin1String("\"")); QRegExp tripleQuoteStringStartEnd(QLatin1String("\"\"\"")); QRegExp singleLineCommentStart(QLatin1String("#(?!=)")); + // Get current state int state = previousBlockState(); if (state == -1) { state = 0; } + // This 4 arrays establish matching between state, start marker, end marker + // and format to apply QList flags = { IN_TRIPLE_QUOTE_STRING, IN_SINGLE_QUOTE_STRING, IN_CHARACTER, IN_MULTILINE_COMMENT }; QList regexps_starts = { tripleQuoteStringStartEnd, singleQuoteStringStartEnd, characterStartEnd, multiLineCommentStart }; QList regexps_ends = { tripleQuoteStringStartEnd, singleQuoteStringStartEnd, characterStartEnd, multiLineCommentEnd }; QList formats = { stringFormat(), stringFormat(), stringFormat(), commentFormat() }; - int pos = 0; + int pos = 0; // current position in block while (pos < text.length()) { // Trying to close current environments bool triggered = false; for (int i = 0; i < flags.size() and not triggered; i++) { int flag = flags[i]; QRegExp ®exp = regexps_ends[i]; QTextCharFormat &format = formats[i]; - if (state & flag) { + if (state & flag) { // Found current state + // find where end marker is int new_pos = regexp.indexIn(text, pos); int length; if (new_pos == -1) { + // not in this block, highlight till the end length = text.length() - pos; } else { + // highlight untill the marker and modify state length = new_pos - pos + regexp.matchedLength(); state -= flag; } + // Apply format to the found area setFormat(pos, length, format); pos = pos + length; triggered = true; } } - if (triggered) { + if (triggered) { // We have done something move to next iteration continue; } - QRegExp *minRegexp = nullptr; - int minPos = INT_MAX; - int minIdx = -1; + // Now we should found the scope that start the closest to current + // position + QRegExp *minRegexp = nullptr; // closest marker + int minPos = INT_MAX; // closest pos + int minIdx = -1; // closest scope index for (int i = 0; i < regexps_starts.size(); i++) { QRegExp ®exp = regexps_starts[i]; int newPos = regexp.indexIn(text, pos); if (newPos != -1) { minPos = qMin(minPos, newPos); minRegexp = ®exp; minIdx = i; } } + // Check where single line comment starts int singleLineCommentStartPos = singleLineCommentStart.indexIn(text, pos); if (singleLineCommentStartPos != -1 and singleLineCommentStartPos < minPos) { + // single line comment starts earlier setFormat(pos, text.length() - pos, commentFormat()); break; } else if (minRegexp) { + // We are going to another scope state += flags[minIdx]; pos = minPos + minRegexp->matchedLength(); setFormat(minPos, minRegexp->matchedLength(), formats[minIdx]); - } else { + } else { // There is nothing to highlight break; } } setCurrentBlockState(state); } void JuliaHighlighter::updateHighlight() { + // Remove rules for outdated variables and functions for (const auto &var : JuliaKeywords::instance()->removedVariables()) { removeRule(var); } - for (const auto &func : JuliaKeywords::instance()->removedFunctions()) { removeRule(func); } + // Add actual variables and function addVariables(JuliaKeywords::instance()->variables()); addFunctions(JuliaKeywords::instance()->functions()); rehighlight(); } QString JuliaHighlighter::nonSeparatingCharacters() const { return QLatin1String("[\\w\\u00A1-\\uFFFF!]"); } diff --git a/src/backends/julia/juliahighlighter.h b/src/backends/julia/juliahighlighter.h index a112732c..6fb54ee6 100644 --- a/src/backends/julia/juliahighlighter.h +++ b/src/backends/julia/juliahighlighter.h @@ -1,38 +1,60 @@ /* * 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 "defaulthighlighter.h" +/** + * Implementation of JuliaHighlighter + * + * Takes into account loaded symbols from scope and predefined keywords. + * There is no common regexps that bound to fail with such syntax-overloaded + * languages as Julia + */ class JuliaHighlighter: public Cantor::DefaultHighlighter { Q_OBJECT public: + /** + * Constructs JuliaHighlighter + * + * @param parent QObject parent + */ JuliaHighlighter(QObject *parent); virtual ~JuliaHighlighter() {} public Q_SLOTS: + /** + * Call this to update highlighter to the current state of keywords storage + */ void updateHighlight(); protected: + /** + * @see Cantor::DefaultHighlighter::highlightBlock + */ virtual void highlightBlock(const QString &text) override; + + /** + * @see Cantor::DefaultHighlighter::nonSeparatingCharacters + */ virtual QString nonSeparatingCharacters() const override; }; diff --git a/src/backends/julia/juliakeywords.h b/src/backends/julia/juliakeywords.h index 3ea16a0b..d58ddd68 100644 --- a/src/backends/julia/juliakeywords.h +++ b/src/backends/julia/juliakeywords.h @@ -1,57 +1,108 @@ /* * 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 +/** + * Keywords storage for Julia session + * + * Class is implemented with singleton pattern + */ class JuliaKeywords { public: + /** + * @return singleton instance pointer + */ static JuliaKeywords *instance(); + /** + * @return list of Julia language predefined keywords + */ const QStringList &keywords() const { return m_keywords; } + + /** + * @return list of predefined commands, that are capable to show plot + */ const QStringList &plotShowingCommands() const { return m_plotShowingCommands; } + /** + * @return list of known variable names + */ const QStringList &variables() const { return m_variables; } + + /** + * @return list of variables removed during the last clearVariables() call + */ const QStringList &removedVariables() const { return m_removedVariables; } + + /** + * Clears all known variables + */ void clearVariables(); + + /** + * Add new variable to the known list + * + * @param variable name of the variable to add + */ void addVariable(const QString &variable); + /** + * @return list of known function names + */ const QStringList &functions() const { return m_functions; } + + /** + * @return list of functions removed during the last clearFunctions() call + */ const QStringList &removedFunctions() const { return m_removedFunctions; } + + /** + * Clears all known functions + */ void clearFunctions(); + + /** + * Add new function to the known list + * + * @param function name of the function to add + */ void addFunction(const QString &function); private: - QStringList m_keywords; - QStringList m_plotShowingCommands; - QStringList m_variables; - QStringList m_removedVariables; - QStringList m_functions; - QStringList m_removedFunctions; + QStringList m_keywords; //< list of predefined keywords + QStringList m_plotShowingCommands; //< list of predefined plot showing cmds + QStringList m_variables; //< list of variables known at the moment + QStringList m_removedVariables; //< list of variables removed during cleaning + QStringList m_functions; //< list of known function at the moment + QStringList m_removedFunctions; //< list of functions removed during cleaning + // We are hidding constructor and destructor for singleton JuliaKeywords() {} ~JuliaKeywords() {} + /// Do first load of predefined stuff from keywords.xml void loadFromFile(); }; diff --git a/src/backends/julia/juliaserver/juliaserver.cpp b/src/backends/julia/juliaserver/juliaserver.cpp index 736bc2c5..c9beb4cc 100644 --- a/src/backends/julia/juliaserver/juliaserver.cpp +++ b/src/backends/julia/juliaserver/juliaserver.cpp @@ -1,127 +1,134 @@ /* * 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) { + // Redirect stdout, stderr to temprorary files 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() ); + // Run command jl_value_t *val = static_cast( jl_eval_string(command.toLatin1().constData()) ); - if (jl_exception_occurred()) { + + if (jl_exception_occurred()) { // If exception occured + // Show it to user in stderr 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) { + } else if (val) { // no exception occured + // If last result is not nothing, show it 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; } + // Clean up streams and files jl_eval_string("flush(STDOUT)"); jl_eval_string("flush(STDERR)"); jl_eval_string("redirect_stdout(__originalSTDOUT__)"); jl_eval_string("redirect_stderr(__originalSTDERR__)"); + // Clean up variables auto vars_to_remove = { "__originalSTDOUT__", "__originalSTDERR__" }; for (const auto &var : vars_to_remove) { jl_eval_string( QString::fromLatin1("%1 = 0").arg(QLatin1String(var)) .toLatin1().constData() ); } 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/juliaserver.h b/src/backends/julia/juliaserver/juliaserver.h index 0cb16e29..be76e6d0 100644 --- a/src/backends/julia/juliaserver/juliaserver.h +++ b/src/backends/julia/juliaserver/juliaserver.h @@ -1,44 +1,76 @@ /* * 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 +/** + * Implementation of command execution server with DBus interface for Julia + * language. + * + * Uses Julia embedding + * http://docs.julialang.org/en/release-0.4/manual/embedding/ to get results. + */ class JuliaServer: public QObject { Q_OBJECT public: JuliaServer(QObject *parent = nullptr); virtual ~JuliaServer(); public Q_SLOTS: + /** + * Initializer for JuliaServer. Call this first before using it + * + * @param path path to julia executable + */ Q_SCRIPTABLE void login(const QString &path) const; + + /** + * Runs a piece of julia code. After this returns use getOutput, getError, + * getWasException methods to retrieve execution result. + * + * @param command maybe multiline piece of julia code to run + */ Q_SCRIPTABLE void runJuliaCommand(const QString &command); + + /** + * @return stdout output of the last command execution + */ Q_SCRIPTABLE QString getOutput() const; + + /** + * @return stderr output of the last command exection + */ Q_SCRIPTABLE QString getError() const; + + /** + * @return indicator that exception was triggered during last command + * execution + */ Q_SCRIPTABLE bool getWasException() const; private: - QString m_error; - QString m_output; - bool m_was_exception; + QString m_error; //< Stores last stderr output + QString m_output; //< Stores last stdout output + bool m_was_exception; //< Stores indicator of exception }; diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp index 7958280f..d4fcebf5 100644 --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -1,339 +1,352 @@ /* 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 "defaultvariablemodel.h" #include "juliaexpression.h" #include "settings.h" #include "juliahighlighter.h" #include "juliakeywords.h" #include "juliaextensions.h" #include "juliabackend.h" #include "juliacompletionobject.h" JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) , m_process(nullptr) , m_interface(nullptr) , m_currentExpression(nullptr) , m_variableModel(new Cantor::DefaultVariableModel(this)) { } 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() ); listVariables(); // Plots integration if (integratePlots()) { runJuliaCommand( QLatin1String("import GR; ENV[\"GKS_WSTYPE\"] = \"nul\"") ); } emit ready(); } void JuliaSession::logout() { m_process->terminate(); JuliaKeywords::instance()->clearVariables(); JuliaKeywords::instance()->clearFunctions(); 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) { return new JuliaCompletionObject(command, index, this); } QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject *parent) { JuliaHighlighter *highlighter = new JuliaHighlighter(parent); QObject::connect( this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight()) ); return highlighter; } 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); listVariables(); 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(); } void JuliaSession::listVariables() { QStringList ignoredVariables; ignoredVariables // These are tech variables of juliaserver << QLatin1String("__originalSTDOUT__") << QLatin1String("__originalSTDERR__"); + // Wrapping removed marker to quotes auto rem_marker = QString::fromLatin1("\"%1\"") .arg(JuliaVariableManagementExtension::REMOVED_VARIABLE_MARKER); + // Clear current symbols JuliaKeywords::instance()->clearVariables(); JuliaKeywords::instance()->clearFunctions(); - QStringList processed_modules; - QStringList modules_to_process; - modules_to_process << QLatin1String("__GLOBAL__"); + QStringList processed_modules; // modules we have processed + QStringList modules_to_process; // modules in queue + modules_to_process << QLatin1String("__GLOBAL__"); // starting from global while (modules_to_process.size() > 0) { + // Get from queue auto module = modules_to_process.front(); modules_to_process.pop_front(); if (processed_modules.contains(module)) { continue; } processed_modules << module; + // Get whos() output, maybe from cache QString whos_output; if (module == QLatin1String("__GLOBAL__")) { runJuliaCommand(QLatin1String("whos()")); whos_output = getOutput(); } else { auto it = m_whos_cache.find(module); if (it == m_whos_cache.end()) { runJuliaCommand(QString::fromLatin1("whos(%1)").arg(module)); whos_output = getOutput(); m_whos_cache[module] = whos_output; } else { whos_output = it.value(); } } + // In this lists we will collect symbols to apply `show` to them + // in one DBus call QStringList batchCommands; QStringList batchTypes; QStringList batchNames; for (auto line : whos_output.split(QLatin1String("\n"))) { QString name = line.simplified().split(QLatin1String(" ")).first().simplified(); - if (name.isEmpty()) { + if (name.isEmpty()) { // some empty line continue; } QString type = line.simplified().split(QLatin1String(" ")).last().simplified(); if (ignoredVariables.contains(name)) { + // Ignored variable continue; } if (type == QLatin1String("Module")) { + // Found module, place in queue modules_to_process.append(name); continue; } if (type == QLatin1String("Function")) { + // Found function JuliaKeywords::instance()->addFunction(name); continue; } if (module != QLatin1String("__GLOBAL__")) { continue; // Don't add variables not included on global scope } + // Add to batch batchCommands << QString::fromLatin1("show(%1);").arg(name); batchTypes << type; batchNames << name; } if (batchCommands.isEmpty()) { - continue; + continue; // nothing to do } + // Run batched command runJuliaCommand( batchCommands.join(QLatin1String("print(\"__CANTOR_DELIM__\");")) ); auto values = getOutput().split(QLatin1String("__CANTOR_DELIM__")); for (int i = 0; i < values.size(); i++) { auto value = values[i].simplified(); auto type = batchTypes[i]; auto name = batchNames[i]; if (type == QLatin1String("ASCIIString")) { if (value == rem_marker) { + // This is removed variable m_variableModel->removeVariable(name); continue; } } + // Register variable m_variableModel->addVariable(name, value); JuliaKeywords::instance()->addVariable(name); } } emit updateHighlighter(); } QAbstractItemModel *JuliaSession::variableModel() { return m_variableModel; } bool JuliaSession::integratePlots() { return JuliaSettings::integratePlots(); } #include "juliasession.moc" diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h index 9f5a7879..d101956c 100644 --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -1,90 +1,173 @@ /* 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 #include "session.h" class JuliaExpression; class JuliaCompletionObject; class KProcess; class QDBusInterface; namespace Cantor { class DefaultVariableModel; } +/** + * Implements a Cantor session for the Julia backend + * + * It communicates through DBus interface with JuliaServer + */ class JuliaSession: public Cantor::Session { Q_OBJECT public: + /** + * Constructs session + * + * @param backend owning backend + */ JuliaSession(Cantor::Backend *backend); virtual ~JuliaSession() {} + /** + * @see Cantor::Session::login + */ virtual void login() override; + + /** + * @see Cantor::Session::logout + */ virtual void logout() override; + /** + * @see Cantor::Session::interrupt + */ virtual void interrupt() override; + /** + * @see Cantor::Session::evaluateExpression + */ virtual Cantor::Expression *evaluateExpression( const QString &command, Cantor::Expression::FinishingBehavior behave) override; + /** + * @see Cantor::Session::completionFor + */ virtual Cantor::CompletionObject *completionFor( const QString &cmd, int index = -1) override; + /** + * @see Cantor::Session::syntaxHighlighter + */ virtual QSyntaxHighlighter *syntaxHighlighter(QObject *parent); + + /** + * @see Cantor::Session::variableModel + */ virtual QAbstractItemModel *variableModel() override; + /** + * @return indicator if config says to integrate plots into worksheet + */ bool integratePlots(); Q_SIGNALS: + /** + * Emit this to update syntax highlighter + */ void updateHighlighter(); private Q_SLOTS: + /** + * Called when async call to JuliaServer is finished + */ void onResultReady(); private: - KProcess *m_process; - QDBusInterface *m_interface; + KProcess *m_process; //< process to run JuliaServer inside + QDBusInterface *m_interface; //< interface to JuliaServer + /// Expressions running at the moment QList m_runningExpressions; - JuliaExpression *m_currentExpression; + JuliaExpression *m_currentExpression; //< current expression + /// Variable management model Cantor::DefaultVariableModel *m_variableModel; + /// Cache to speedup modules whos calls QMap m_whos_cache; friend JuliaExpression; friend JuliaCompletionObject; + /** + * Runs Julia expression + * + * @param expression expression to run + */ void runExpression(JuliaExpression *expression); + /** + * Runs Julia piece of code in synchronous mode + * + * @param command command to execute + */ void runJuliaCommand(const QString &command) const; + + /** + * Runs Julia piece of code in asynchronous mode. When finished + * onResultReady is called + * + * @param command command to execute + */ void runJuliaCommandAsync(const QString &command); + /** + * Helper method to get QString returning function result + * + * @param method DBus method to call + * @return result of the method + */ QString getStringFromServer(const QString &method); + + /** + * @return stdout of the last executed command + */ QString getOutput(); + + /** + * @return stderr of the last executed command + */ QString getError(); + + /** + * @return indicator of exception occured during the last command execution + */ bool getWasException(); + /** + * Updates variable model by querying all modules in scope with whos command + */ void listVariables(); }; diff --git a/src/backends/julia/scripts/variables_loader.jl b/src/backends/julia/scripts/variables_loader.jl index a649e062..6daf31eb 100644 --- a/src/backends/julia/scripts/variables_loader.jl +++ b/src/backends/julia/scripts/variables_loader.jl @@ -1,6 +1,8 @@ -# Variable loading script +# Variable loading script. +# +# Install JLD script with `Pkg.add(JLD)` to use it import JLD for (var_name, value) in JLD.load("%1") s = symbol(var_name) @eval (($s) = ($value)) end diff --git a/src/backends/julia/scripts/variables_saver.jl b/src/backends/julia/scripts/variables_saver.jl index b5762b0c..a97143cc 100644 --- a/src/backends/julia/scripts/variables_saver.jl +++ b/src/backends/julia/scripts/variables_saver.jl @@ -1,14 +1,16 @@ # Variable saving script +# +# Install JLD script with `Pkg.add(JLD)` to use it import JLD JLD.jldopen("%1", "w") do file for name in names(Main)[4:end] if name == :__originalSTDOUT__ || name == :__originalSTDERR__ continue end var_info = summary(eval(name)) if var_info == "Function" || var_info == "Module" continue end JLD.write(file, repr(name)[2:end], eval(name)) end end