File indexing completed on 2024-05-19 11:21:30

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com>
0004     SPDX-FileCopyrightText: 2010 Oleksiy Protas <elfy.ua@gmail.com>
0005     SPDX-FileCopyrightText: 2023 by Alexander Semke (alexander.semke@web.de)
0006 */
0007 
0008 // TODO: setStatus in syntax and completions, to be or not to be?
0009 // on the one hand comme il faut, on another, causes flickering in UI
0010 
0011 #include "rserver.h"
0012 #include "radaptor.h"
0013 #include "rcallbacks.h"
0014 #include "settings.h"
0015 
0016 #include <QApplication>
0017 #include <QDesktopWidget>
0018 #include <QDir>
0019 #include <QUrl>
0020 
0021 #include <KIO/DeleteJob>
0022 #include <KLocalizedString>
0023 
0024 #ifdef Q_OS_WIN
0025 #include <process.h>
0026 #else
0027 #include <unistd.h>
0028 #endif
0029 
0030 //R includes
0031 #include <R.h>
0032 #include <Rembedded.h>
0033 #include <Rversion.h>
0034 #include <Rdefines.h>
0035 #define R_INTERFACE_PTRS
0036 #include <R_ext/Parse.h>
0037 
0038 const QChar RServer::recordSep(30);
0039 const QChar RServer::unitSep(31);
0040 
0041 
0042 RServer::RServer() : m_isInitialized(false),m_isCompletionAvailable(false)
0043 {
0044     new RAdaptor(this);
0045 
0046     m_tmpDir = QDir::tempPath() + QString::fromLatin1("/cantor_rserver-%1").arg(getpid());
0047     QDir dir;
0048     dir.mkdir(m_tmpDir);
0049     qDebug()<<"RServer: "<<"storing plots at "<<m_tmpDir;
0050 
0051     initR();
0052     m_status=RServer::Idle;
0053     m_isInitialized=true;
0054 }
0055 
0056 RServer::~RServer()
0057 {
0058     //delete the directory with old plots
0059     KIO::del(QUrl(m_tmpDir));
0060 }
0061 
0062 void RServer::initR()
0063 {
0064     //Setup environment variables
0065     // generated as littler.h via from svn/littler/littler.R
0066     #include "renvvars.h"
0067 
0068     for (int i = 0; R_VARS[i] != nullptr; i+= 2)
0069         qputenv(R_VARS[i], R_VARS[i+1]);
0070 
0071     //R_SignalHandlers = 0;               // Don't let R set up its own signal handlers
0072 
0073     const char *R_argv[] = {"Cantor",  "--no-save",  "--no-readline",  "",  ""}; //--gui=none
0074     const char *R_argv_opt[] = {"--vanilla",  "--slave"};
0075     int R_argc = (sizeof(R_argv) - sizeof(R_argv_opt) ) / sizeof(R_argv[0]);
0076 
0077     Rf_initEmbeddedR(R_argc,  (char**) R_argv);
0078 
0079     R_ReplDLLinit();            // this is to populate the repl console buffers
0080 
0081     setupCallbacks(this);
0082 
0083     autoload();
0084 
0085     // Set gui editor for R
0086     runCommand(QLatin1String("options(editor = 'cantor_scripteditor') \n"),true);
0087 
0088     //Setting up some settings dependent stuff
0089     if(RServerSettings::self()->integratePlots())
0090     {
0091         qDebug()<<"RServer: "<<"integrating plots";
0092         newPlotDevice();
0093     }
0094 
0095     //Loading automatic run scripts
0096     foreach (const QString& path, RServerSettings::self()->autorunScripts())
0097     {
0098         int errorOccurred=0;
0099         if (QFile::exists(path))
0100             R_tryEval(lang2(install("source"),mkString(path.toUtf8().data())),nullptr,&errorOccurred);
0101         // TODO: error handling
0102         else
0103         {
0104             qDebug()<<"RServer: "<<(QLatin1String("Script ")+path+QLatin1String(" not found")); // FIXME: or should we throw a messagebox
0105         }
0106     }
0107 
0108     qDebug()<<"RServer: "<<"done initializing";
0109 }
0110 
0111 //Code from the RInside library
0112 void RServer::autoload()
0113 {
0114     #include "rautoloads.h"
0115 
0116     /* Autoload default packages and names from autoloads.h
0117      *
0118      * This function behaves in almost every way like
0119      * R's autoload:
0120      * function (name, package, reset = FALSE, ...)
0121      * {
0122      *     if (!reset && exists(name, envir = .GlobalEnv, inherits = FALSE))
0123      *        stop("an object with that name already exists")
0124      *     m <- match.call()
0125      *     m[[1]] <- as.name("list")
0126      *     newcall <- eval(m, parent.frame())
0127      *     newcall <- as.call(c(as.name("autoloader"), newcall))
0128      *     newcall$reset <- NULL
0129      *     if (is.na(match(package, .Autoloaded)))
0130      *        assign(".Autoloaded", c(package, .Autoloaded), env = .AutoloadEnv)
0131      *     do.call("delayedAssign", list(name, newcall, .GlobalEnv,
0132      *                                                         .AutoloadEnv))
0133      *     invisible()
0134      * }
0135      *
0136      * What's missing is the updating of the string vector .Autoloaded with
0137      * the list of packages, which by my code analysis is useless and only
0138      * for informational purposes.
0139      *
0140      */
0141     //void autoloads(void){
0142 
0143     SEXP da, dacall, al, alcall, AutoloadEnv, name, package;
0144     int i,j, idx=0, errorOccurred, ptct;
0145 
0146     /* delayedAssign call*/
0147     PROTECT(da = Rf_findFun(Rf_install("delayedAssign"), R_GlobalEnv));
0148     PROTECT(AutoloadEnv = Rf_findVar(Rf_install(".AutoloadEnv"), R_GlobalEnv));
0149     if (AutoloadEnv == R_NilValue){
0150         qDebug()<<"RServer: "<<"Cannot find .AutoloadEnv";
0151         //exit(1);
0152     }
0153     PROTECT(dacall = allocVector(LANGSXP,5));
0154     SETCAR(dacall,da);
0155     /* SETCAR(CDR(dacall),name); */          /* arg1: assigned in loop */
0156     /* SETCAR(CDR(CDR(dacall)),alcall); */  /* arg2: assigned in loop */
0157     SETCAR(CDR(CDR(CDR(dacall))),R_GlobalEnv); /* arg3 */
0158     SETCAR(CDR(CDR(CDR(CDR(dacall)))),AutoloadEnv); /* arg3 */
0159 
0160     /* autoloader call */
0161     PROTECT(al = Rf_findFun(Rf_install("autoloader"), R_GlobalEnv));
0162     PROTECT(alcall = allocVector(LANGSXP,3));
0163     SET_TAG(alcall, R_NilValue); /* just like do_ascall() does */
0164     SETCAR(alcall,al);
0165     /* SETCAR(CDR(alcall),name); */          /* arg1: assigned in loop */
0166     /* SETCAR(CDR(CDR(alcall)),package); */  /* arg2: assigned in loop */
0167 
0168     ptct = 5;
0169     for(i = 0; i < packc; ++i){
0170         idx += (i != 0)? packobjc[i-1] : 0;
0171         for (j = 0; j < packobjc[i]; ++j){
0172             /*printf("autload(%s,%s)\n",packobj[idx+j],pack[i]);*/
0173 
0174             PROTECT(name = NEW_CHARACTER(1));
0175             PROTECT(package = NEW_CHARACTER(1));
0176             SET_STRING_ELT(name, 0, COPY_TO_USER_STRING(packobj[idx+j]));
0177             SET_STRING_ELT(package, 0, COPY_TO_USER_STRING(pack[i]));
0178 
0179             /* Set up autoloader call */
0180             PROTECT(alcall = allocVector(LANGSXP,3));
0181             SET_TAG(alcall, R_NilValue); /* just like do_ascall() does */
0182             SETCAR(alcall,al);
0183             SETCAR(CDR(alcall),name);
0184             SETCAR(CDR(CDR(alcall)),package);
0185 
0186             /* Setup delayedAssign call */
0187             SETCAR(CDR(dacall),name);
0188             SETCAR(CDR(CDR(dacall)),alcall);
0189 
0190             R_tryEval(dacall,R_GlobalEnv,&errorOccurred);
0191             if (errorOccurred){
0192                 qDebug()<<"RServer: "<<"Error calling delayedAssign!";
0193                 //exit(1);
0194             }
0195 
0196             ptct += 3;
0197         }
0198     }
0199     UNPROTECT(ptct);
0200 
0201     /* Initialize the completion libraries if needed, adapted from sys-std.c of R */
0202     // TODO: should we do this or init on demand?
0203     // if (completion is needed) // TODO: discuss how to pass parameter
0204     {
0205         /* First check if namespace is loaded */
0206         if  (findVarInFrame(R_NamespaceRegistry,install("utils"))==R_UnboundValue)
0207         { /* Then try to load it */
0208             SEXP cmdSexp, cmdexpr;
0209             ParseStatus status;
0210             int i;
0211             const char *p="try(loadNamespace('rcompgen'), silent=TRUE)";
0212 
0213             PROTECT(cmdSexp=mkString(p));
0214             cmdexpr=PROTECT(R_ParseVector(cmdSexp,-1,&status,R_NilValue));
0215             if(status==PARSE_OK)
0216             {
0217                 for(i=0;i<length(cmdexpr);++i)
0218                     eval(VECTOR_ELT(cmdexpr,i),R_GlobalEnv);
0219             }
0220             UNPROTECT(2);
0221             /* Completion is available if the namespace is correctly loaded */
0222             m_isCompletionAvailable= (findVarInFrame(R_NamespaceRegistry,install("utils"))!=R_UnboundValue);
0223         }
0224     }
0225 }
0226 
0227 void RServer::endR()
0228 {
0229    Rf_endEmbeddedR(0);
0230 }
0231 
0232 void RServer::addFileToOutput(const QString& file)
0233 {
0234     m_expressionFiles.append(file);
0235 }
0236 
0237 void RServer::runCommand(const QString& cmd, bool internal)
0238 {
0239     m_expressionFiles.clear();
0240     qDebug()<<"RServer: "<<"running " << (internal ? "internal " : "") << "command "<<cmd;
0241 
0242     // Handle some internal command, like variable model update, etc.
0243     if (internal)
0244     {
0245         const QLatin1String completionCommandPrefix("%completion ");
0246         if (cmd == QLatin1String("%model update"))
0247         {
0248             listSymbols();
0249             return;
0250         }
0251         else if (cmd.startsWith(completionCommandPrefix))
0252         {
0253 
0254             QString arg = cmd;
0255             arg.remove(0, completionCommandPrefix.size());
0256             qDebug() << "arg" << arg;
0257             completeCommand(arg);
0258             return;
0259         }
0260     }
0261 
0262     Expression* expr=new Expression;
0263     expr->cmd=cmd;
0264     expr->hasOtherResults=false;
0265 
0266     setStatus(RServer::Busy);
0267 
0268     setCurrentExpression(expr);
0269 
0270     expr->std_buffer.clear();
0271     expr->err_buffer.clear();
0272 
0273     ReturnCode returnCode=RServer::SuccessCode;
0274     QString returnText;
0275     QStringList neededFiles;
0276 
0277     //Code to evaluate an R function (taken from RInside library)
0278     ParseStatus status;
0279     SEXP cmdSexp,  cmdexpr = R_NilValue;
0280     SEXP result = nullptr;
0281     int i,  errorOccurred;
0282     QByteArray memBuf;
0283 
0284     memBuf.append(cmd.toUtf8());
0285 
0286     PROTECT(cmdSexp = allocVector(STRSXP,  1));
0287     SET_STRING_ELT(cmdSexp,  0,  mkChar((char*)memBuf.data()));
0288 
0289     cmdexpr = PROTECT(R_ParseVector(cmdSexp,  -1,  &status,  R_NilValue));
0290     switch (status)
0291     {
0292         case PARSE_OK:
0293             qDebug()<<"RServer: "<<"PARSING "<<cmd<<" went OK";
0294             /* Loop is needed here as EXPSEXP might be of length > 1 */
0295             for (i = 0; i < length(cmdexpr); ++i) {
0296 
0297                 result = R_tryEval(VECTOR_ELT(cmdexpr,  i), nullptr, &errorOccurred);
0298                 if (errorOccurred)
0299                 {
0300                     qDebug()<<"RServer: "<<"Error occurred.";
0301                     break;
0302                 }
0303                 // TODO: multiple results
0304             }
0305             memBuf.clear();
0306             break;
0307         case PARSE_INCOMPLETE:
0308             /* need to read another line */
0309             qDebug()<<"RServer: "<<"parse incomplete..";
0310             break;
0311         case PARSE_NULL:
0312             qDebug()<<"RServer: "<<"ParseStatus is null: "<<status;
0313             break;
0314         case PARSE_ERROR:
0315             qDebug()<<"RServer: "<<"Parse Error: "<<cmd;
0316             break;
0317         case PARSE_EOF:
0318             qDebug()<<"RServer: "<<"ParseStatus is eof: "<<status;
0319             break;
0320         default:
0321             qDebug()<<"RServer: "<<"Parse status is not documented: "<<status;
0322             break;
0323     }
0324     UNPROTECT(2);
0325 
0326     if(status==PARSE_OK)
0327     {
0328         qDebug()<<"RServer: "<<"done running";
0329 
0330         qDebug()<<"RServer: "<<"result: " << result << " std: "<<expr->std_buffer<<" err: "<<expr->err_buffer;
0331         //if the command didn't print anything on its own, print the result
0332         //but only, if result exists, because comment expression don't create result
0333 
0334 
0335         //TODO: handle some known result types like lists, matrices separately
0336         //      to make the output look better, by using html (tables etc.)
0337         if(result && expr->std_buffer.isEmpty()&&expr->err_buffer.isEmpty())
0338         {
0339             qDebug()<<"RServer: "<<"printing result...";
0340             SEXP count=PROTECT(R_tryEval(lang2(install("length"),result),nullptr,&errorOccurred)); // TODO: error checks
0341             if (*INTEGER(count)==0)
0342                 qDebug()<<"RServer: " << "no result, so show nothing";
0343             else
0344                 Rf_PrintValue(result);
0345             UNPROTECT(1);
0346         }
0347 
0348 
0349         setCurrentExpression(nullptr); //is this save?
0350 
0351         if(!expr->err_buffer.isEmpty())
0352         {
0353             returnCode=RServer::ErrorCode;
0354             returnText=expr->err_buffer;
0355         }
0356         else
0357         {
0358             returnCode=RServer::SuccessCode;
0359             returnText=expr->std_buffer;
0360 
0361         }
0362     }else
0363     {
0364         returnCode=RServer::ErrorCode;
0365         returnText=i18n("Error Parsing Command");
0366     }
0367 
0368     if(internal)
0369     {
0370         qDebug()<<"RServer: "<<"internal result: "<<returnCode<<" :: "<<returnText;
0371         return;
0372     }
0373 
0374     QFileInfo f(m_curPlotFile);
0375     qDebug()<<"RServer: "<<"file: "<<m_curPlotFile<<" exists: "<<f.exists()<<" size: "<<f.size();
0376     if(f.exists())
0377     {
0378         expr->hasOtherResults=true;
0379         newPlotDevice();
0380         neededFiles<<f.filePath();
0381     }
0382 
0383     qDebug()<<"RServer: " << "files: " << neededFiles+m_expressionFiles;
0384     emit expressionFinished(returnCode, returnText, neededFiles+m_expressionFiles);
0385 
0386     setStatus(Idle);
0387 }
0388 
0389 void RServer::completeCommand(const QString& cmd)
0390 {
0391     setStatus(RServer::Busy);
0392 
0393     // TODO: is static okay? guess RServer is a singletone, but ...
0394     // TODO: error handling?
0395     // TODO: investigate encoding problem
0396     // TODO: propage the flexibility of token selection upward
0397     // TODO: what if install() fails? investigate
0398     // TODO: investigate why errors break the whole foodchain of RServer callbacks in here
0399     static SEXP comp_env=R_FindNamespace(mkString("utils"));
0400     static SEXP tokenizer_func=install(".guessTokenFromLine");
0401     static SEXP linebuffer_func=install(".assignLinebuffer");
0402     static SEXP buffer_end_func=install(".assignEnd");
0403     static SEXP complete_func=install(".completeToken");
0404     static SEXP retrieve_func=install(".retrieveCompletions");
0405 
0406     /* Setting buffer parameters */
0407     int errorOccurred=0; // TODO: error cheks, too lazy to do it now
0408     R_tryEval(lang2(linebuffer_func,mkString(cmd.toUtf8().data())),comp_env,&errorOccurred);
0409     R_tryEval(lang2(buffer_end_func,ScalarInteger(cmd.size())),comp_env,&errorOccurred);
0410 
0411     /* Passing the tokenizing work to professionals */
0412     SEXP token=PROTECT(R_tryEval(lang1(tokenizer_func),comp_env,&errorOccurred));
0413 
0414     /* Doing the actual stuff */
0415     R_tryEval(lang1(complete_func),comp_env,&errorOccurred);
0416     SEXP completions=PROTECT(R_tryEval(lang1(retrieve_func),comp_env,&errorOccurred));
0417 
0418     /* Populating the list of completions */
0419     QStringList completionOptions;
0420     for (int i=0;i<length(completions);i++)
0421         completionOptions<<QLatin1String(translateCharUTF8(STRING_ELT(completions,i)));
0422     QString qToken=QLatin1String(translateCharUTF8(STRING_ELT(token,0)));
0423     UNPROTECT(2);
0424 
0425     const QString output = qToken + unitSep + completionOptions.join(recordSep);
0426     emit expressionFinished(RServer::SuccessCode, output, QStringList());
0427     setStatus(RServer::Idle);
0428 }
0429 
0430 // FIXME: This scheme is somewhat placeholder, I honestly don't like it too much
0431 // I am not sure whether or not asking the server with each keypress if what he typed was
0432 // acceptable or not is a good idea. I'll leave it under investigation, let it be this way just for now
0433 // ~Landswellsong
0434 
0435 void RServer::listSymbols()
0436 {
0437     setStatus(RServer::Busy);
0438 
0439     QStringList vars, values, funcs, constants;
0440     int errorOccurred; // TODO: error checks
0441 
0442     /* Obtaining a list of user namespace objects */
0443     SEXP usr=PROTECT(R_tryEval(lang1(install("ls")),nullptr,&errorOccurred));
0444     for (int i=0;i<length(usr);i++)
0445     {
0446         SEXP object = STRING_ELT(usr,i);
0447         const QString& name = QString::fromUtf8(translateCharUTF8(object));
0448         SEXP value = findVar(installChar(object), R_GlobalEnv);
0449 
0450         if (Rf_isFunction(value))
0451             funcs << name;
0452         else if (RServerSettings::variableManagement())
0453         {
0454             int convertStatus;
0455             SEXP valueAsString = PROTECT(R_tryEval(lang2(install("toString"),value),nullptr,&convertStatus));
0456             if (convertStatus == 0)
0457             {
0458                 vars << name;
0459                 values << QString::fromUtf8(translateCharUTF8(asChar(valueAsString)));
0460             }
0461         }
0462         else
0463             vars << name;
0464     }
0465     UNPROTECT(1);
0466 
0467     /* Obtaining a list of active packages */
0468     SEXP packages=PROTECT(R_tryEval(lang1(install("search")),nullptr,&errorOccurred));
0469     //int i=1; // HACK to prevent scalability issues
0470     for (int i=1;i<length(packages);i++) // Package #0 is user environment, so starting with 1
0471     {
0472         QString packageName = QString::fromUtf8(translateCharUTF8(STRING_ELT(packages,i)));
0473 
0474         if (!m_parsedNamespaces.contains(packageName))
0475         {
0476             CachedParsedNamespace cache;
0477 
0478             //char pos[32];
0479             //sprintf(pos,"%d",i+1);
0480             SEXP f=PROTECT(R_tryEval(lang2(install("ls"),ScalarInteger(i+1)),nullptr,&errorOccurred));
0481             for (int j=0;j<length(f);j++)
0482             {
0483                 SEXP object = STRING_ELT(f,j);
0484                 const QString& name = QString::fromUtf8(translateCharUTF8(object));
0485                 SEXP value = installChar(object);
0486                 int errorOccurred2 = 2;
0487                 //TODO error handling
0488                 //FIXME without this unused typeof evaling - server crash on certain symbols
0489                 SEXP test = PROTECT(R_tryEval(lang2(install("typeof"), value),nullptr,&errorOccurred2));
0490                 Q_UNUSED(test);
0491 
0492                 SEXP resultIs = PROTECT(R_tryEval(lang2(install("is.function"), value),nullptr, &errorOccurred2));
0493                 if (QString::fromUtf8(translateCharUTF8(asChar(resultIs))) == QLatin1String("TRUE"))
0494                     cache.functions << name;
0495                 else
0496                     cache.constants << name;
0497             }
0498             UNPROTECT(1);
0499 
0500             m_parsedNamespaces[packageName] = cache;
0501         }
0502 
0503         funcs += m_parsedNamespaces[packageName].functions;
0504         constants += m_parsedNamespaces[packageName].constants;
0505     }
0506     UNPROTECT(1);
0507 
0508     const QString output = vars.join(recordSep) + unitSep + values.join(recordSep) + unitSep + funcs.join(recordSep) + unitSep + constants.join(recordSep);
0509     emit expressionFinished(RServer::SuccessCode, output, QStringList());
0510     setStatus(Idle);
0511 }
0512 
0513 void RServer::setStatus(Status status)
0514 {
0515     if(m_status!=status)
0516     {
0517         m_status=status;
0518         if(m_isInitialized)
0519             emit statusChanged(status);
0520     }
0521 }
0522 
0523 QString RServer::requestInput(const QString& prompt)
0524 {
0525     emit inputRequested(prompt);
0526 
0527     //Wait until the input arrives over dbus
0528     QEventLoop loop;
0529     connect(this, SIGNAL(requestAnswered()), &loop, SLOT(quit()));
0530     loop.exec();
0531 
0532     return m_requestCache;
0533 }
0534 
0535 void RServer::answerRequest(const QString& answer)
0536 {
0537     m_requestCache=answer;
0538     emit requestAnswered();
0539 }
0540 
0541 void RServer::newPlotDevice()
0542 {
0543     static int deviceNum = 0;
0544 
0545     QString extension;
0546     QString command;
0547     int w = RServerSettings::self()->plotWidth();
0548     int h = RServerSettings::self()->plotHeight();
0549     auto format = RServerSettings::self()->inlinePlotFormat();
0550 
0551     if (format == 0 || format == 1) // PDF and SVG
0552     {
0553         // convert the size from cm to inches
0554         w =  w / 2.54;
0555         h = h / 2.54;
0556 
0557         if (format == 0)
0558         {
0559             // TODO: pdf produces corrupted output!
0560             command = QLatin1String("pdf(\"%1\", width = %2, height = %3)");
0561             extension = QLatin1String("pdf");
0562         }
0563         else
0564         {
0565             command = QLatin1String("svg(\"%1\", width = %2, height = %3)");
0566             extension = QLatin1String("svg");
0567         }
0568     }
0569     else // PNG
0570     {
0571         // convert the size from cm to pixels with the current desktop resolution
0572         w = w / 2.54 * QApplication::desktop()->physicalDpiX();
0573         h = h / 2.54 * QApplication::desktop()->physicalDpiX();
0574         command = QLatin1String("png(\"%1\", width = %2, height = %3)");
0575         extension = QLatin1String("png");
0576     }
0577 
0578     m_curPlotFile = QString::fromLatin1("%1/Rplot%2.%3").arg(m_tmpDir, QString::number(deviceNum++), extension);
0579     if(m_isInitialized)
0580         runCommand(QLatin1String("dev.off()"), true);
0581 
0582     runCommand(command.arg(m_curPlotFile, QString::number(w), QString::number(h)), true);
0583 }