File indexing completed on 2024-05-12 11:32:23

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
0004     SPDX-FileCopyrightText: 2023 Alexander Semke <alexander.semke@web.de>
0005 */
0006 #include "juliaserver.h"
0007 
0008 #include <julia_version.h>
0009 
0010 #include <iostream>
0011 #include <QFileInfo>
0012 #include <QDir>
0013 #include <QTemporaryFile>
0014 #include <QDebug>
0015 
0016 QStringList JuliaServer::INTERNAL_VARIABLES =
0017     QStringList() << QLatin1String("__originalSTDOUT__") << QLatin1String("__originalSTDERR__");
0018 
0019 JuliaServer::JuliaServer(QObject *parent) : QObject(parent), m_was_exception(false)
0020 {
0021 }
0022 
0023 JuliaServer::~JuliaServer()
0024 {
0025      /* strongly recommended: notify Julia that the
0026          program is about to terminate. this allows
0027          Julia time to cleanup pending write requests
0028          and run all finalizers
0029     */
0030     jl_atexit_hook(0);
0031 }
0032 
0033 int JuliaServer::login()
0034 {
0035     /* required: setup the julia context */
0036     jl_init();
0037 
0038     jl_eval_string("import REPL;");
0039 
0040     return 0;
0041 }
0042 
0043 void JuliaServer::runJuliaCommand(const QString &command)
0044 {
0045     // Redirect stdout, stderr to temporary files
0046     QTemporaryFile output, error;
0047     if (!output.open() || !error.open()) {
0048         qFatal("Unable to create temporary files for stdout/stderr");
0049         return;
0050     }
0051     jl_eval_string("const __originalSTDOUT__ = stdout");
0052     jl_eval_string("const __originalSTDERR__ = stderr");
0053     jl_eval_string(
0054         QString::fromLatin1("redirect_stdout(open(\"%1\", \"w\"))")
0055             .arg(output.fileName()).toLatin1().constData()
0056     );
0057     jl_eval_string(
0058         QString::fromLatin1("redirect_stderr(open(\"%1\", \"w\"))")
0059             .arg(error.fileName()).toLatin1().constData()
0060     );
0061 
0062     jl_module_t* jl_repl_module = (jl_module_t*)(jl_eval_string("REPL"));
0063     jl_function_t* jl_ends_func = jl_get_function(jl_repl_module, "ends_with_semicolon");
0064     bool isEndsWithSemicolon = jl_unbox_bool(jl_call1(jl_ends_func, jl_cstr_to_string(command.toStdString().c_str())));
0065 
0066     // Run command
0067     jl_value_t *val = static_cast<jl_value_t *>(
0068         jl_eval_string(command.toUtf8().constData())
0069     );
0070 
0071     if (jl_exception_occurred()) { // If exception occurred
0072         // Show it to user in stderr
0073 #if QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(1, 7, 0)
0074         jl_value_t *ex = jl_current_task->ptls->previous_exception;
0075 #elif QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(1, 1, 0)
0076         jl_value_t *ex = jl_get_ptls_states()->previous_exception;
0077 #else
0078         jl_value_t *ex = jl_exception_in_transit;
0079 #endif
0080         jl_printf(JL_STDERR, "error during run:\n");
0081         jl_function_t *showerror =
0082             jl_get_function(jl_base_module, "showerror");
0083         jl_value_t *bt = static_cast<jl_value_t *>(
0084             jl_eval_string("catch_backtrace()")
0085         );
0086         jl_value_t *err_stream = static_cast<jl_value_t *>(
0087             jl_eval_string("stderr")
0088         );
0089         jl_call3(showerror, err_stream, ex, bt);
0090         jl_exception_clear();
0091         m_was_exception = true;
0092     } else if (val && !isEndsWithSemicolon) { // no exception occurred
0093         // If last result is not nothing, show it
0094         jl_function_t *equality = jl_get_function(jl_base_module, "==");
0095         jl_value_t *nothing =
0096             static_cast<jl_value_t *>(jl_eval_string("nothing"));
0097         bool is_nothing = jl_unbox_bool(
0098             static_cast<jl_value_t *>(jl_call2(equality, nothing, val))
0099         );
0100         if (!is_nothing) {
0101             jl_value_t *out_display = static_cast<jl_value_t *>(jl_eval_string("TextDisplay(stdout)"));
0102             jl_function_t *display = jl_get_function(jl_base_module, "display");
0103             jl_call2(display, out_display, val);
0104         }
0105         m_was_exception = false;
0106     }
0107     // Clean up streams and files
0108     jl_eval_string("flush(stdout)");
0109     jl_eval_string("flush(stderr)");
0110     jl_eval_string("redirect_stdout(__originalSTDOUT__)");
0111     jl_eval_string("redirect_stderr(__originalSTDERR__)");
0112 
0113     // Clean up variables
0114     auto vars_to_remove = {
0115         "__originalSTDOUT__", "__originalSTDERR__"
0116     };
0117 
0118     for (const auto &var : vars_to_remove) {
0119         jl_eval_string(
0120             QString::fromLatin1("%1 = 0").arg(QLatin1String(var))
0121                 .toLatin1().constData()
0122         );
0123     }
0124 
0125     m_output = QString::fromUtf8(output.readAll());
0126     m_error = QString::fromUtf8(error.readAll());
0127 }
0128 
0129 QString JuliaServer::getError() const
0130 {
0131     return m_error;
0132 }
0133 
0134 QString JuliaServer::getOutput() const
0135 {
0136     return m_output;
0137 }
0138 
0139 bool JuliaServer::getWasException() const
0140 {
0141     return m_was_exception;
0142 }
0143 
0144 #if QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(1, 1, 0)
0145 #define JL_MAIN_MODULE jl_main_module
0146 #else
0147 #define JL_MAIN_MODULE jl_internal_main_module
0148 #endif
0149 
0150 void JuliaServer::parseModules(bool variableManagement)
0151 {
0152     parseJlModule(JL_MAIN_MODULE, variableManagement);
0153 }
0154 
0155 void JuliaServer::parseJlModule(jl_module_t* module, bool parseValue)
0156 {
0157     jl_function_t* jl_string_function = jl_get_function(jl_base_module, "string");
0158     jl_function_t* jl_sizeof_function = jl_get_function(jl_base_module, "sizeof");
0159 
0160     if (module != JL_MAIN_MODULE)
0161     {
0162         const QString& moduleName = fromJuliaString(jl_call1(jl_string_function, (jl_value_t*)(module->name)));
0163         if (parsedModules.contains(moduleName))
0164             return;
0165         else
0166             parsedModules.append(moduleName);
0167     }
0168 
0169     jl_function_t* jl_names_function = jl_get_function(jl_base_module, "names");
0170     jl_value_t* names = jl_call1(jl_names_function, (jl_value_t*)module);
0171     jl_value_t **data = (jl_value_t**)jl_array_data(names);
0172     for (size_t i = 0; i < jl_array_len(names); i++)
0173     {
0174         bool isBindingResolved = (bool)jl_binding_resolved_p(module, (jl_sym_t*)(data[i]));
0175         if (isBindingResolved)
0176         {
0177 
0178             const QString& name = fromJuliaString(jl_call1(jl_string_function, data[i]));
0179             jl_value_t* value = jl_get_binding_or_error(module, (jl_sym_t*)(data[i]))->value;
0180             jl_datatype_t* datetype = (jl_datatype_t*)jl_typeof(value);
0181             QString type = QString::fromUtf8(jl_typeof_str(value));
0182 
0183             // Module
0184             if (jl_is_module(value))
0185             {
0186                 if (module == JL_MAIN_MODULE && (jl_module_t*)value != JL_MAIN_MODULE)
0187                     parseJlModule((jl_module_t*)value, parseValue);
0188             }
0189             // Function
0190             else if (type.startsWith(QLatin1String("#")) || type == QLatin1String("Function"))
0191             {
0192                 if (!m_functions.contains(name))
0193                     m_functions.append(name);
0194             }
0195             // Variable
0196             else if (datetype != jl_datatype_type) // Not type
0197             {
0198                 if (module == JL_MAIN_MODULE && !INTERNAL_VARIABLES.contains(name))
0199                 {
0200                     const QString& size = fromJuliaString(jl_call1(jl_string_function, jl_call1(jl_sizeof_function, value)));
0201                     //const QString& type = fromJuliaString(jl_call1(jl_string_function, jl_call1(jl_typeof_function, value)));
0202                     if (parseValue)
0203                     {
0204                         const QString& valueString = fromJuliaString(jl_call1(jl_string_function, value));
0205                         if (m_variables.contains(name))
0206                         {
0207                             int i = m_variables.indexOf(name);
0208                             m_variableValues[i] = valueString;
0209                             m_variableSizes[i] = size;
0210                             m_variableTypes[i] = type;
0211                         }
0212                         else
0213                         {
0214                             m_variables.append(name);
0215                             m_variableValues.append(valueString);
0216                             m_variableSizes.append(size);
0217                             m_variableTypes.append(type);
0218                         }
0219                     }
0220                     else
0221                     {
0222                         if (m_variables.contains(name))
0223                         {
0224                             int i = m_variables.indexOf(name);
0225                             m_variableSizes[i] = size;
0226                             m_variableTypes[i] = type;
0227                         }
0228                         else
0229                         {
0230                             m_variables.append(name);
0231                             m_variableSizes.append(size);
0232                             m_variableTypes.append(type);
0233                         }
0234                     }
0235                 }
0236             }
0237         }
0238     }
0239 }
0240 
0241 QString JuliaServer::fromJuliaString(const jl_value_t* value)
0242 {
0243     return QString::fromUtf8(jl_string_data(value));
0244 }
0245 
0246 QStringList JuliaServer::variablesList()
0247 {
0248     return m_variables;
0249 }
0250 
0251 QStringList JuliaServer::variableValuesList()
0252 {
0253     return m_variableValues;
0254 }
0255 
0256 QStringList JuliaServer::variableSizesList()
0257 {
0258     return m_variableSizes;
0259 }
0260 
0261 QStringList JuliaServer::variableTypesList()
0262 {
0263     return m_variableTypes;
0264 }
0265 
0266 QStringList JuliaServer::functionsList()
0267 {
0268     return m_functions;
0269 }