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 }