5 // Created by Byron Ellis on 6/25/07.
6 // Copyright 2007 __MyCompanyName__. All rights reserved.
9 #import "RInterpreter.h"
11 #import "DeviceWindowController.h"
12 #import "NSData+RSerialize.h"
13 #import "TerminalDelegate.h"
15 #define R_INTERFACE_PTRS 1
16 #define CSTACK_DEFNS 1
18 #include <Rinternals.h>
19 #include <Rinterface.h>
20 #include <R_ext/Utils.h>
21 #include <Rgraphics.h>
22 #include <R_ext/GraphicsDevice.h>
23 #include <R_ext/eventloop.h>
24 #include <R_ext/Rdynload.h>
34 @interface RInterpreter (Private)
36 - (void)readyToEvaluate;
37 - (Rboolean)openDevice:(NewDevDesc *)dev withDisplay:(char *)display width:(double)width height:(double)height
38 pointsize:(double)ps family:(char*)family antialias:(Rboolean)antialias autorefresh:(Rboolean)autorefreash
39 quartzpos:(int)quartzpos background:(int)bg;
40 - (void)registerInterface;
43 @implementation RInterpreter
45 + (RInterpreter*)sharedInterpreter {
46 static RInterpreter *interp = nil;
47 if(nil == interp) interp = [[RInterpreter alloc] init];
52 if(nil == [super init]) return nil;
54 //Some configuration defaults
56 buffer = [[NSMutableAttributedString alloc] init];
65 deviceList = [[NSMutableArray alloc] init];
66 evalLock = [[NSLock alloc] init];
71 outputTag = [[NSDictionary alloc] initWithObjectsAndKeys:@"ROutput",@"RTextType",nil];
72 promptTag = [[NSDictionary alloc] initWithObjectsAndKeys:@"RInput",@"RTextType",nil];
73 errorTag = [[NSDictionary alloc] initWithObjectsAndKeys:@"RError",@"RTextType",nil];
75 //Set some environment variables to their defaults if they are not presently set.
76 setenv("R_HOME",[[[NSBundle bundleWithIdentifier:@"org.r-project.R-framework"] resourcePath] UTF8String],0);
77 setenv("LANG",[[NSString stringWithFormat:@"%@.UTF-8",[[NSLocale currentLocale] localeIdentifier]] UTF8String],0);
83 - (BOOL)isConfigured { return configured; }
85 - (void)setArgv:(char**)argv argc:(int)argc {
90 - (id)delegate { return delegate; }
91 - (void)setDelegate:(id)aDelegate { delegate = aDelegate; }
93 - (long)bufferSize { return bufferSize; }
94 - (void)setBufferSize:(long)aSize {
97 - (BOOL)allowTerminal { return allowTerminal; }
98 - (void)setAllowTerminal:(BOOL)aBool { allowTerminal = aBool; }
100 - (BOOL)vend { return vend; }
101 - (void)setVend:(BOOL)aBool { vend = aBool; }
103 - (NSString*)homePath { return (NULL == getenv("R_HOME")) ? nil : [NSString stringWithUTF8String:getenv("R_HOME")]; }
104 - (void)setHomePath:(NSString*)aPath {
105 setenv("R_HOME",[aPath UTF8String],1);
108 - (NSString*)localeIdentifier { return (NULL == getenv("LANG")) ? nil : [NSString stringWithUTF8String:getenv("LANG")]; }
109 - (void)setLocaleIdentifier:(NSString*)aLocale {
110 setenv("LANG",[aLocale UTF8String],1);
114 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
116 if(nil != delegate) [delegate didBeginEvaluationForInterpreter:self];
118 [self registerInterface];
124 if(NO == [self isConfigured]) [self configure];
125 if(YES == [self vend]) {
126 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
127 if(NULL != getenv("RVENDNAME")) {
128 vendName = [[NSString alloc] initWithUTF8String:getenv("RVENDNAME")];
129 NSConnection *conn = [NSConnection defaultConnection];
130 [conn setRootObject:self];
131 if(NO == [conn registerName:vendName]) {
132 NSLog(@"Unable to register server as %@");
138 NSConnection *conn = [NSConnection defaultConnection];
139 [conn setRootObject:self];
140 while(vend_num < 17) {
141 vendName = [[NSString alloc] initWithFormat:@"R Execution Server %d",++vend_num];
142 if(YES == [conn registerName:vendName]) {
148 NSLog(@"Unable to register server. Presently, a maximum of 16 local execution servers are allowed per machine");
154 //If we don't have a delegate yet, enter the event loop. The delegate (when it connects)
155 //should post a stop message via awakeConsole.
156 if(nil == delegate) {
157 NSLog(@"Waiting for a delegate");
159 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
160 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"RExecServerStartedVending" object:vendName];
164 NSLog(@"Finished vending with delegate %@",[delegate description]);
171 - (void)awakeConsole {
173 NSLog(@"Awake Console");
174 //Post a stop event so that we fall out of the wait loop at the
175 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
176 location:NSMakePoint(0,0)
183 data2:0] atStart:NO];
187 - (void)evaluateInput:(NSString*)aString {
188 readerBufferUsed = [aString length];
189 if(readerBufferUsed > readerBufferLength) readerBufferUsed = readerBufferLength;
190 memcpy(readerBuffer,[aString UTF8String],sizeof(unsigned char)*readerBufferUsed);
191 [self readyToEvaluate];
194 - (NSArray*)devices { return deviceList; }
196 - (NSString*)serverName { return vendName; }
198 - (NSData*)serializeObjectWithName:(NSString*)anName error:(NSError**)anError {
199 const char *name = [anName UTF8String];
200 SEXP obj = findVar(install(name),R_GlobalEnv);
201 if(obj != R_UnboundValue) {
203 NSMutableData *data = [[NSMutableData alloc] init];
204 [data serialize:obj];
206 return [data autorelease];
211 - (BOOL)deserializeObject:(NSData*)anObject withName:(NSString*)aName replace:(BOOL)shouldReplace error:(NSError**)error {
213 if(anObject == nil) {
214 *error = [NSError errorWithDomain:@"org.r-project.RExecServer" code:1 userInfo:nil];
219 const char *name = [aName UTF8String];
220 SEXP cur = findVar(install(name),R_GlobalEnv);
221 if(cur != R_UnboundValue && NO == shouldReplace) {
222 *error = [NSError errorWithDomain:@"org.r-project.RExecServer" code:1 userInfo:nil];
225 Rf_setVar(install(name),[anObject unserialize],R_GlobalEnv);
229 - (void)copyObjectWithName:(NSString*)aName toServer:(NSString*)aServer error:(NSError**)error {
230 id theProxy = [[NSConnection rootProxyForConnectionWithRegisteredName:aServer host:nil] retain];
232 NSData *serialized = [self serializeObjectWithName:aName error:error];
234 [theProxy deserializeObject:serialized withName:aName replace:YES error:error];
241 #pragma mark Function Prototypes
242 void RInterp_Suicide(char*);
243 void RInterp_ShowMessage(char*);
244 void RInterp_FlushConsole();
245 void RInterp_WritePrompt(char*);
246 int RInterp_ReadConsole(char*,unsigned char*,int,int);
247 void RInterp_ResetConsole();
248 void RInterp_WriteConsole(char*,int);
249 void RInterp_ClearerrConsole();
251 void RInterp_CleanUp(SA_TYPE,int,int);
252 int RInterp_ShowFiles(int,char**,char**,char*,Rboolean,char*);
253 int RInterp_ChooseFile(int,char*,int);
254 int RInterp_EditFile(char*);
255 void RInterp_System(char*);
256 void RInterp_ProcessEvents();
257 Rboolean RInterp_Device(NewDevDesc*,char*,double,double,double,char*,Rboolean,Rboolean,int,int);
258 void RInterp_DeviceParams(double*,double*,double*,char*,Rboolean*,Rboolean*,int*);
259 int RInterp_CustomPrint(char *,SEXP);
260 SEXP RInterp_do_selectlist(SEXP,SEXP,SEXP,SEXP);
264 extern void (*ptr_R_ProcessEvents)(void);
265 extern void (*ptr_CocoaSystem)(char*);
266 extern Rboolean (*ptr_CocoaInnerQuartzDevice)(NewDevDesc*,char*,double,double,double,char*,Rboolean,Rboolean,int,int);
267 extern void (*ptr_CocoaGetQuartzParameters)(double*,double*,double*,char*,Rboolean*,Rboolean*,int*);
268 extern int (*ptr_Raqua_CustomPrint)(char *, SEXP);
270 extern DL_FUNC ptr_do_wsbrowser,
271 ptr_do_dataentry, ptr_do_browsepkgs, ptr_do_datamanger,
272 ptr_do_packagemanger, ptr_do_flushconsole, ptr_do_hsbrowser,
277 extern void Rstd_WriteConsole(char*,int);
279 @implementation RInterpreter (Private)
284 char *argv_orig[] = {"R","--gui=cocoa","--no-save","--no-restore-data"};
289 int i,j,has_gui=0,has_g=0;
292 for(i=1;i<_argc;i++) {
293 if(strncmp(_argv[i],"-g",2)==0)
295 else if(strncmp(_argv[i],"--gui",5)==0)
299 printf("warning: Execution server will ignore GUI settings.\n");
300 if(has_g) argc -= 1; else if(!has_g && !has_gui) argc++;
301 argv = malloc(sizeof(char*)*argc);
304 for(i=0;i<_argc;i++) {
305 if(strncmp(_argv[i],"-g",2)==0) {
307 } else if(strncmp(_argv[i],"--gui",5)==0) {
309 argv[j++] = _argv[i];
312 argv[j++] = "--gui=cocoa";
317 Rf_initialize_R(argc,argv);
318 //R_GUIType = "RExecServer"; //We are NOT the Aqua GUI, but not being the aqua gui is even more annoying.
319 if(YES == allowTerminal) {
320 NSLog(@"Installing terminal delegate");
321 TerminalDelegate *tempDel = [[TerminalDelegate alloc] init];
322 [tempDel setReaderFunction:ptr_R_ReadConsole];
323 [tempDel setWriterFunction:Rstd_WriteConsole];
324 [tempDel setFlushFunction:ptr_R_FlushConsole];
325 [self setDelegate:tempDel];
328 R_Consolefile = NULL;
330 #ifdef R_USING_TRAMPOLINE
331 //On systems supporting libffi we can generate a direct trampoline
332 //function that dispatches to the R level. This is coming later.
334 ptr_R_Suicide = RInterp_Suicide;
335 ptr_R_ShowMessage = RInterp_ShowMessage;
336 ptr_R_ReadConsole = RInterp_ReadConsole;
337 ptr_R_WriteConsole = RInterp_WriteConsole;
338 ptr_R_WriteConsoleEx = NULL;
339 ptr_R_ShowFiles = RInterp_ShowFiles;
340 ptr_R_EditFile = RInterp_EditFile;
341 ptr_R_ChooseFile = RInterp_ChooseFile;
342 ptr_Raqua_CustomPrint = RInterp_CustomPrint;
343 ptr_R_ProcessEvents = RInterp_ProcessEvents;
344 ptr_CocoaInnerQuartzDevice = RInterp_Device;
345 ptr_CocoaGetQuartzParameters = RInterp_DeviceParams;
346 ptr_CocoaSystem = RInterp_System;
349 //Put ourselves into a multithreaded state
350 //[NSThread detachNewThreadSelector:@selector(_dummy) toTarget:self withObject:nil];
353 - (void)writeConsole:(char*)output length:(int)aLength {
354 char c = output[aLength];
355 output[aLength] = '\0';
356 if(nil != delegate) [delegate appendString:[NSString stringWithUTF8String:output] ofType:0 forInterpreter:self];
360 - (int)readConsoleWithPrompt:(char*)aPrompt buffer:(unsigned char*)aBuffer length:(int)aLength history:(int)useHistory {
361 //Put those there for something that wants to write a string
362 readerPrompt = aPrompt;
363 readerBuffer = aBuffer;
364 readerBufferLength = aLength;
365 readerAddToHistory = useHistory;
367 memset(readerBuffer,0,sizeof(unsigned char)*aLength);
369 //Flush any drawing that may have happened since the last time we were here.
370 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
371 if(nil != delegate) [delegate didFinishEvaluationForInterpreter:self];
372 NSEnumerator *e = [deviceList objectEnumerator];
374 while((dev = (RDevice*)[e nextObject]) != nil) {
379 pool = [[NSAutoreleasePool alloc] init];
380 if(nil != delegate) [delegate appendString:[NSString stringWithUTF8String:aPrompt] ofType:2 forInterpreter:self];
381 if(nil != delegate) [delegate didBeginWaitingForInputWithMaximumLength:readerBufferLength addToHistory:(useHistory == 1) ? YES : NO forInterpreter:self];
383 if(nil != delegate) [delegate didGetInputForInterpreter:self];
385 if(nil != delegate) [delegate didBeginEvaluationForInterpreter:self];
387 return readerBufferUsed;
390 - (void)removeDevice:(NSNotification*)aNotify {
391 if(nil != delegate) [delegate didCloseDevice:[aNotify object] forInterpreter:self];
392 [deviceList removeObject:[aNotify object]];
395 - (Rboolean)openDevice:(NewDevDesc *)dev withDisplay:(char *)display width:(double)width height:(double)height
396 pointsize:(double)ps family:(char*)family antialias:(Rboolean)antialias autorefresh:(Rboolean)autorefreash
397 quartzpos:(int)quartzpos background:(int)bg {
398 NSString *aDisplay = [NSString stringWithUTF8String:display];
399 NSArray *bits = [[aDisplay componentsSeparatedByString:@":"] retain];
400 Class deviceClass = nil;
401 deviceClass = [RDevice deviceForDisplay:([bits count] < 2) ? @"" : [bits objectAtIndex:1]];
403 //Can't create device.
404 if(nil == deviceClass) return 0;
405 RDevice *rd = [[deviceClass alloc] initWithDevice:dev size:NSMakeSize(width,height) pointSize:ps
406 display:[bits objectAtIndex:0]
407 target:[bits count] < 2 ? nil : [bits objectAtIndex:1] background:bg antialias:antialias];
408 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeDevice:) name:@"RDeviceClosed" object:rd];
409 [deviceList addObject:rd];
410 if(nil != delegate) [delegate didOpenDevice:rd forInterpreter:self];
416 - (void)flushConsole {
419 - (void)showMessage:(char*)aMsg {
420 [self writeConsole:aMsg length:strlen(aMsg)];
423 - (void)suicide:(char*)aMsg {
424 [self writeConsole:aMsg length:strlen(aMsg)];
427 - (void)resetConsole {
430 - (void)clearErrorConsole {
436 - (void)system:(char*)cmd {
440 - (int)editFile:(char*)aFileName {
441 if(YES == allowTerminal) {
442 NSLog(@"Editing file: %s",aFileName);
449 - (int)showFiles:(int)nfiles file:(char**)file headers:(char**)headers wtitle:(char*)wtitle del:(Rboolean)del pager:(char*)pager {
453 - (int)customPrintType:(char*)aType list:(SEXP)aList {
454 NSLog(@"This is hosed for right now. Might need support from R itself.");
458 - (SEXP)selectListWithCall:(SEXP)call op:(SEXP)op args:(SEXP)args rho:(SEXP)rho {
462 - (void)readyToEvaluate {
463 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
464 location:NSMakePoint(0,0)
471 data2:0] atStart:NO];
474 SEXP RES_ServerName() {
475 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
476 const char *str = [[[RInterpreter sharedInterpreter] serverName] UTF8String];
478 PROTECT(ret = allocVector(STRSXP,1));
479 SET_STRING_ELT(ret,0,mkChar(str));
485 SEXP RES_CopyObject(SEXP name,SEXP server) {
486 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
487 NSString *aStr = [[NSString alloc] initWithUTF8String:CHAR(STRING_ELT(name,0))];
488 NSString *bStr = [[NSString alloc] initWithUTF8String:CHAR(STRING_ELT(server,0))];
489 NSError *error = nil;
490 [[RInterpreter sharedInterpreter] copyObjectWithName:aStr toServer:bStr error:&error];
495 Rf_error("Problem copying object");
499 static const R_CallMethodDef R_CallDef[] = {
500 {"RES_ServerName",(DL_FUNC)RES_ServerName,0},
501 {"RES_CopyObject",(DL_FUNC)RES_CopyObject,2},
506 R_addCallRoutine(DllInfo *info, const R_CallMethodDef * const croutine,void*sym);
508 - (void)registerInterface {
509 R_registerRoutines(R_getEmbeddingDllInfo(),NULL,R_CallDef,NULL,NULL);
514 #ifdef R_USING_TRAMPOLINE
516 void RInterp_Suicide(char*aMsg) {
517 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
518 [[RInterpreter sharedInterpreter] suicide:aMsg];
521 void RInterp_ShowMessage(char*aMsg) {
522 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
523 [[RInterpreter sharedInterpreter] showMessage:aMsg];
526 void RInterp_FlushConsole() {
527 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
528 [[RInterpreter sharedInterpreter] flushConsole];
531 int RInterp_ReadConsole(char*prompt,unsigned char*buffer,int length,int history) {
532 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
533 int ret = [[RInterpreter sharedInterpreter] readConsoleWithPrompt:prompt buffer:buffer length:length history:history];
537 void RInterp_ResetConsole() { [[RInterpreter sharedInterpreter] resetConsole]; }
538 void RInterp_WriteConsole(char*buffer,int length) {
539 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
540 [[RInterpreter sharedInterpreter] writeConsole:buffer length:length];
543 void RInterp_ClearerrConsole() { [[RInterpreter sharedInterpreter] clearErrorConsole]; }
544 void RInterp_Busy() { [[RInterpreter sharedInterpreter] busy]; }
545 void RInterp_CleanUp(SA_TYPE type,int a,int b) { }
546 int RInterp_ShowFiles(int a,char**b,char**c,char*d,Rboolean e,char*f) {
547 return [[RInterpreter sharedInterpreter] showFiles:a file:b headers:c wtitle:d del:e pager:f];
549 int RInterp_ChooseFile(int a,char*b,int c) { return 0; }
550 int RInterp_EditFile(char*a) { return [[RInterpreter sharedInterpreter] editFile:a]; }
551 void RInterp_System(char*a) { [[RInterpreter sharedInterpreter] system:a]; }
552 void RInterp_ProcessEvents() {
553 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
554 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
555 location:NSMakePoint(0,0)
562 data2:0] atStart:NO];
566 Rboolean RInterp_Device(NewDevDesc *dev,
567 char *display,double width,double height,
568 double ps,char *family,Rboolean antialias,Rboolean autorefresh,int quartzpos,int bg) {
569 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
570 Rboolean ret = [[RInterpreter sharedInterpreter] openDevice:dev
571 withDisplay:display width:width height:height pointsize:ps family:family
572 antialias:antialias autorefresh:autorefresh quartzpos:quartzpos background:bg];
576 void RInterp_DeviceParams(double*a,double*b,double*c,char*d,Rboolean*e,Rboolean*f,int*g) { }
577 int RInterp_CustomPrint(char *a,SEXP b) { return [[RInterpreter sharedInterpreter] customPrintType:a list:b]; }
579 SEXP RInterp_do_selectlist(SEXP call,SEXP op,SEXP args,SEXP rho) {
580 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
581 SEXP ret = [[RInterpreter sharedInterpreter] selectListWithCall:call op:op args:args rho:rho];