Basic vending, will probably require R-devel soon to get external functions
[RExecServer.git] / RInterpreter.m
blobbfda54a0cefbfcec69b8aabdcf0a1fba14595dc3
1 //
2 //  RInterpreter.m
3 //  RExecServer
4 //
5 //  Created by Byron Ellis on 6/25/07.
6 //  Copyright 2007 __MyCompanyName__. All rights reserved.
7 //
9 #import "RInterpreter.h"
10 #import "RDevice.h"
11 #import "DeviceWindowController.h"
12 #import "NSData+RSerialize.h"
14 #define R_INTERFACE_PTRS 1
15 #define CSTACK_DEFNS     1
17 #include <Rinternals.h>
18 #include <Rinterface.h>
19 #include <R_ext/Utils.h>
20 #include <Rgraphics.h>
21 #include <R_ext/GraphicsDevice.h>
22 #include <R_ext/eventloop.h>
23 #include <R_ext/Rdynload.h>
24 #include <Rversion.h>
26 #include <pthread.h>
27 #include <signal.h>
30 #undef Boolean
32 @interface RInterpreter (Private)
33 - (void)configure;
34 - (void)readyToEvaluate;
35 - (Rboolean)openDevice:(NewDevDesc *)dev withDisplay:(char *)display width:(double)width height:(double)height
36         pointsize:(double)ps family:(char*)family antialias:(Rboolean)antialias autorefresh:(Rboolean)autorefreash 
37         quartzpos:(int)quartzpos background:(int)bg;
38 - (void)registerInterface;
39 @end
41 @implementation RInterpreter
43 + (RInterpreter*)sharedInterpreter {
44         static RInterpreter *interp = nil;
45         if(nil == interp) interp = [[RInterpreter alloc] init];
46         return interp;
49 - (id)init {
50         if(nil == [super init]) return nil;
51         
52         //Some configuration defaults
53         bufferSize           = 2048;
54         buffer               = [[NSMutableAttributedString alloc] init];
55         suppressOutput       = NO;
56         ptr_R_OldReadConsole = NULL;
57         ptr_R_OldWriteConsole= NULL;
58         ptr_R_OldFlushConsole= NULL;
59         allowTerminal        = NO;
60         vend                 = YES;
61         waiting              = YES;
62         delegate             = nil;
64         deviceList = [[NSMutableArray alloc] init];
65         evalLock   = [[NSLock alloc] init];
66         
67         _argc = 0;
68         _argv = NULL;
71         //Set some environment variables to their defaults if they are not presently set.
72         setenv("R_HOME",[[[NSBundle bundleWithIdentifier:@"org.r-project.R-framework"] resourcePath] UTF8String],0);
73         setenv("LANG",[[NSString stringWithFormat:@"%@.UTF-8",[[NSLocale currentLocale] localeIdentifier]] UTF8String],0);
74         return self;
79 - (BOOL)isConfigured { return configured; }
81 - (void)setArgv:(char**)argv argc:(int)argc {
82         _argc = argc;
83         _argv = argv;
86 - (id)delegate { return delegate; }
87 - (void)setDelegate:(id)aDelegate { delegate = aDelegate; }
89 - (long)bufferSize   { return bufferSize; }
90 - (void)setBufferSize:(long)aSize {
91         bufferSize = aSize;
93 - (BOOL)allowTerminal { return allowTerminal; }
94 - (void)setAllowTerminal:(BOOL)aBool { allowTerminal = aBool; }
96 - (BOOL)vend { return vend; }
97 - (void)setVend:(BOOL)aBool { vend = aBool; }
99 - (NSString*)homePath { return (NULL == getenv("R_HOME")) ? nil : [NSString stringWithUTF8String:getenv("R_HOME")]; }
100 - (void)setHomePath:(NSString*)aPath {
101         setenv("R_HOME",[aPath UTF8String],1);
104 - (NSString*)localeIdentifier { return (NULL == getenv("LANG")) ? nil : [NSString stringWithUTF8String:getenv("LANG")]; }
105 - (void)setLocaleIdentifier:(NSString*)aLocale {
106         setenv("LANG",[aLocale UTF8String],1);
109 - (void)_run {
110         [evalLock lock];
111         setup_Rmainloop();
112         [self registerInterface];
113         run_Rmainloop();
116 - (void)run {
117         if(NO == [self isConfigured]) [self configure];
118         if(YES == [self vend]) {
119                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
120                 if(NULL != getenv("RVENDNAME")) {
121                         vendName = [[NSString alloc] initWithUTF8String:getenv("RVENDNAME")];
122                         NSConnection *conn = [NSConnection defaultConnection];
123                         [conn setRootObject:self];
124                         if(NO == [conn registerName:vendName]) {
125                                 NSLog(@"Unable to register server as %@");
126                                 [vendName release];
127                                 vendName = nil;
128                         }
129                 } else {
130                         int vend_num = 0;
131                         NSConnection *conn = [NSConnection defaultConnection];
132                         [conn setRootObject:self];
133                         while(vend_num < 17) {
134                                 vendName = [[NSString alloc] initWithFormat:@"R Execution Server %d",++vend_num];
135                                 if(YES == [conn registerName:vendName]) {
136                                         break;
137                                 } else
138                                         [vendName release];
139                         }
140                         if(vend_num == 11) {
141                                 NSLog(@"Unable to register server. Presently, a maximum of 16 local execution servers are allowed per machine");
142                                 vendName = nil;
143                         }
144                 }
145                 [pool release];
146         
147                 //If we need a console to do our thing then wait on a connection
148                 //to be made via awakeConsole.
149                 waiting = YES;
150                 if(NO == [self allowTerminal]) {
151                         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
152                         [NSApp run];
153                         [pool release];
154                 }
155         }
156         waiting = NO;
157         [self _run];
161 - (void)awakeConsole {
162         if(YES == waiting) {
163                 //Post a stop event so that we fall out of the wait loop at the 
164                 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined 
165                                                                                 location:NSMakePoint(0,0)
166                                                                                 modifierFlags:0
167                                                                                 timestamp:0 
168                                                                                 windowNumber:0 
169                                                                                 context:nil 
170                                                                                 subtype:1337 
171                                                                                 data1:0 
172                                                                                 data2:0] atStart:NO];   
173         }
176 - (void)evaluateInput:(NSString*)aString {
177         if(YES == allowTerminal) {
178                 //Right now, do nothing. In future we may 
179         } else {
180                 readerBufferUsed = [aString length];
181                 memcpy(readerBuffer,[aString UTF8String],sizeof(unsigned char*)*readerBufferUsed);
182                 readerBuffer[readerBufferUsed] = '\0';
183                 [self readyToEvaluate];
184         }
187 - (NSArray*)devices { return deviceList; }
189 - (NSString*)serverName { return vendName; }
191 - (NSData*)serializeObjectWithName:(NSString*)anName {
192         const char *name = [anName UTF8String];
193         SEXP  obj  = findVar(install(name),R_GlobalEnv);
194         if(obj != R_UnboundValue) {
195                 PROTECT(obj);
196                 NSMutableData *data = [[NSMutableData alloc] init];
197                 [data serialize:obj];
198                 UNPROTECT(1);
199                 return [data autorelease];
200         } else { 
201                 return nil;
202         }
204 - (BOOL)deserializeObject:(NSData*)anObject withName:(NSString*)aName replace:(BOOL)shouldReplace {
205         if(anObject == nil) return NO;
206         
207         const char *name = [aName UTF8String];
208         SEXP  cur  = findVar(install(name),R_GlobalEnv);
209         if(cur != R_UnboundValue && NO == shouldReplace) return NO;
210         Rf_setVar(install(name),[anObject unserialize],R_GlobalEnv);
211         return NO;
214 - (void)copyObjectWithName:(NSString*)aName toServer:(NSString*)aServer {
215         id theProxy = [[NSConnection rootProxyForConnectionWithRegisteredName:aServer host:nil] retain];
216         [theProxy deserializeObject:[self serializeObjectWithName:aName] withName:aName replace:YES];
217         [theProxy release];
220 @end
223 #pragma mark Function Prototypes
224 void RInterp_Suicide(char*);
225 void RInterp_ShowMessage(char*);
226 void RInterp_FlushConsole();
227 void RInterp_WritePrompt(char*);
228 int  RInterp_ReadConsole(char*,unsigned char*,int,int);
229 void RInterp_ResetConsole();
230 void RInterp_WriteConsole(char*,int);
231 void RInterp_ClearerrConsole();
232 void RInterp_Busy();
233 void RInterp_CleanUp(SA_TYPE,int,int);
234 int  RInterp_ShowFiles(int,char**,char**,char*,Rboolean,char*);
235 int  RInterp_ChooseFile(int,char*,int);
236 int  RInterp_EditFile(char*);
237 void RInterp_System(char*);
238 void RInterp_ProcessEvents();
239 Rboolean RInterp_Device(NewDevDesc*,char*,double,double,double,char*,Rboolean,Rboolean,int,int);
240 void RInterp_DeviceParams(double*,double*,double*,char*,Rboolean*,Rboolean*,int*);
241 int  RInterp_CustomPrint(char *,SEXP);
243 void *RInterp_TerminalReader(void*data);
245 extern void     (*ptr_R_ProcessEvents)(void);
246 extern void     (*ptr_CocoaSystem)(char*);
247 extern Rboolean (*ptr_CocoaInnerQuartzDevice)(NewDevDesc*,char*,double,double,double,char*,Rboolean,Rboolean,int,int);
248 extern void     (*ptr_CocoaGetQuartzParameters)(double*,double*,double*,char*,Rboolean*,Rboolean*,int*);
249 extern int      (*ptr_Raqua_CustomPrint)(char *, SEXP);
251 extern void Rstd_WriteConsole(char*,int);
253 @implementation RInterpreter (Private)
255 - (void)_dummy { }
257 - (void)configure {
258         char *argv_orig[] = {"R","--gui=cocoa","--no-save","--no-restore-data"};
259         char **argv;
260         int   argc = 0;
261         
262         if(_argc > 0) {
263                 int i,j,has_gui=0,has_g=0;
264                 
265                 argc=_argc;
266                 for(i=1;i<_argc;i++) {
267                         if(strncmp(_argv[i],"-g",2)==0) 
268                                 has_g = 1; 
269                         else if(strncmp(_argv[i],"--gui",5)==0)
270                                 has_gui = 1;
271                 }
272                 if(has_g || has_gui)
273                         printf("warning: Execution server will ignore GUI settings.\n");
274                 if(has_g) argc -= 1; else if(!has_g && !has_gui) argc++;
275                 argv = malloc(sizeof(char*)*argc);
276                 
277                 j=0;
278                 for(i=0;i<_argc;i++) {
279                         if(strncmp(_argv[i],"-g",2)==0) {
280                                 i++;
281                         } else if(strncmp(_argv[i],"--gui",5)==0) {
282                         } else {
283                                 argv[j++] = _argv[i];
284                         }
285                 }
286                 argv[j++] = "--gui=cocoa";
287         } else {
288                 argc = 4;
289                 argv = argv_orig;
290         }
291         Rf_initialize_R(argc,argv);
293         if(YES == allowTerminal) {
294                 ptr_R_OldReadConsole = ptr_R_ReadConsole;
295                 ptr_R_OldWriteConsole= Rstd_WriteConsole;
296                 ptr_R_OldFlushConsole= ptr_R_FlushConsole;
297                 ptr_R_OldShowFiles   = ptr_R_ShowFiles;
298         }
299         R_Outputfile  = NULL;
300         R_Consolefile = NULL;
303 #ifdef R_USING_TRAMPOLINE
304 //On systems supporting libffi we can generate a direct trampoline
305 //function that dispatches to the R level. This is coming later.
306 #else
307     ptr_R_Suicide                = RInterp_Suicide;
308     ptr_R_ShowMessage            = RInterp_ShowMessage;
309     ptr_R_ReadConsole            = RInterp_ReadConsole;
310     ptr_R_WriteConsole           = RInterp_WriteConsole;
311         ptr_R_WriteConsoleEx         = NULL;
312     ptr_R_ShowFiles              = RInterp_ShowFiles;
313     ptr_R_EditFile               = RInterp_EditFile;
314     ptr_R_ChooseFile             = RInterp_ChooseFile;
315     ptr_Raqua_CustomPrint        = RInterp_CustomPrint;
316         ptr_R_ProcessEvents          = RInterp_ProcessEvents;
317     ptr_CocoaInnerQuartzDevice   = RInterp_Device;
318     ptr_CocoaGetQuartzParameters = RInterp_DeviceParams;
319     ptr_CocoaSystem              = RInterp_System;
320 #endif
321         
322         //Put ourselves into a multithreaded state
323         //[NSThread detachNewThreadSelector:@selector(_dummy) toTarget:self withObject:nil];
326 - (void)writeConsole:(char*)output length:(int)aLength {
327         //Write out to the console if we're using a terminal
328         if(YES == allowTerminal && ptr_R_OldWriteConsole) 
329                 ptr_R_OldWriteConsole(output,aLength);
330         
333 - (int)readConsoleWithPrompt:(char*)aPrompt buffer:(unsigned char*)aBuffer length:(int)aLength history:(int)useHistory {
334         //Put those there for something that wants to write a string
335         readerPrompt = aPrompt;
336         readerBuffer = aBuffer;
337         readerBufferLength = aLength;
338         readerAddToHistory = useHistory;
339         
340         //Flush any drawing that may have happened since the last time we were here.
341         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
342         NSEnumerator *e = [deviceList objectEnumerator];
343         RDevice *dev;
344         while((dev = (RDevice*)[e nextObject]) != nil) {
345                 [dev flushDrawing];
346         }
347         [pool release];
348         [evalLock unlock];
349         if(YES == allowTerminal) {
350                 R_PolledEvents = RInterp_ProcessEvents;
351                 R_wait_usec    = 100000;
352                 readerBufferUsed = ptr_R_OldReadConsole(readerPrompt,readerBuffer,readerBufferLength,readerAddToHistory);
353         } else {
354                 [NSApp run];
355         }
356         [evalLock lock];
357         return readerBufferUsed;
360 - (void)removeDevice:(NSNotification*)aNotify {
361         [deviceList removeObject:[aNotify object]];
363         
364 - (Rboolean)openDevice:(NewDevDesc *)dev withDisplay:(char *)display width:(double)width height:(double)height
365         pointsize:(double)ps family:(char*)family antialias:(Rboolean)antialias autorefresh:(Rboolean)autorefreash 
366         quartzpos:(int)quartzpos background:(int)bg {
367         NSString *aDisplay = [NSString stringWithUTF8String:display];
368         NSArray  *bits     = [[aDisplay componentsSeparatedByString:@":"] retain];
369         Class deviceClass = nil;
370         deviceClass = [RDevice deviceForDisplay:([bits count] < 2) ? @"" : [bits objectAtIndex:1]];
372         //Can't create device.
373         if(nil == deviceClass) return 0;
374         RDevice *rd = [[deviceClass alloc] initWithDevice:dev size:NSMakeSize(width,height) pointSize:ps 
375                 display:[bits objectAtIndex:0]
376                 target:[bits count] < 2 ? nil : [bits objectAtIndex:1] background:bg antialias:antialias];
377         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeDevice:) name:@"RDeviceClosed" object:rd];
378         [deviceList addObject:rd];
379         if(YES == [rd canDrawInView] && YES == [self allowTerminal]) {
380                 //Load the device display nib
381                 DeviceWindowController *devCtrl = [[DeviceWindowController alloc] initWithWindowNibName:@"Device"];
382                 [devCtrl setDevice:rd];
383                 [[devCtrl window] makeKeyAndOrderFront:self];
384         }
385         [rd finishOpening];
386         [rd release];
387         return TRUE;
390 - (void)flushConsole {
391         if(YES == allowTerminal && ptr_R_OldFlushConsole) ptr_R_OldFlushConsole();
394 - (void)showMessage:(char*)aMsg {
395         [self writeConsole:aMsg length:strlen(aMsg)];
398 - (void)suicide:(char*)aMsg {
399         [self writeConsole:aMsg length:strlen(aMsg)];
402 - (void)resetConsole {
405 - (void)clearErrorConsole {
408 - (void)busy {
411 - (void)system:(char*)cmd {
412         system(cmd);
415 - (int)editFile:(char*)aFileName {
416         if(YES == allowTerminal) {
417                 NSLog(@"Editing file: %s",aFileName);
418                 return 0;
419         } else {
420                 return 0;
421         }
424 - (int)showFiles:(int)nfiles file:(char**)file headers:(char**)headers wtitle:(char*)wtitle del:(Rboolean)del pager:(char*)pager {
425         if(YES == allowTerminal && ptr_R_OldShowFiles) {
426                 return ptr_R_OldShowFiles(nfiles,file,headers,wtitle,del,pager);
427         }
428         return 0;
431 - (int)customPrintType:(char*)aType list:(SEXP)aList {
432         NSLog(@"This is hosed for right now. Might need support from R itself.");
433         return 0;
436 - (void)readyToEvaluate {
437         [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined 
438                                                                         location:NSMakePoint(0,0)
439                                                                          modifierFlags:0
440                                                                          timestamp:0 
441                                                                          windowNumber:0 
442                                                                          context:nil 
443                                                                          subtype:1337 
444                                                                          data1:0 
445                                                                          data2:0] atStart:NO];  
448 SEXP RES_ServerName() {
449         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
450         const char *str = [[[RInterpreter sharedInterpreter] serverName] UTF8String];
451         SEXP ret;
452         PROTECT(ret = allocVector(STRSXP,1));
453         SET_STRING_ELT(ret,0,mkChar(str));
454         UNPROTECT(1);
455         [pool release];
456         return ret;
459 SEXP RES_CopyObject(SEXP name,SEXP server) {
460         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
461         NSString *aStr = [[NSString alloc] initWithUTF8String:CHAR(STRING_ELT(name,0))];
462         NSString *bStr = [[NSString alloc] initWithUTF8String:CHAR(STRING_ELT(server,0))];
463         [[RInterpreter sharedInterpreter] copyObjectWithName:aStr toServer:bStr];
464         [aStr release];
465         [bStr release];
466         [pool release];
467         return R_NilValue;
470 static const R_CallMethodDef R_CallDef[] = {
471         {"RES_ServerName",(DL_FUNC)RES_ServerName,0},
472         {"RES_CopyObject",(DL_FUNC)RES_CopyObject,2},
473         NULL
476 extern void
477 R_addCallRoutine(DllInfo *info, const R_CallMethodDef * const croutine,void*sym);
479 - (void)registerInterface {
480 #if R_VERSION >= R_Version(2,6,0) 
481         R_registerRoutines(R_getEmbeddingDllInfo(),NULL,R_CallDef,NULL,NULL);
482 #endif
485 @end
487 #ifdef R_USING_TRAMPOLINE
488 #else
489 void RInterp_Suicide(char*aMsg) { [[RInterpreter sharedInterpreter] suicide:aMsg]; }
490 void RInterp_ShowMessage(char*aMsg) { [[RInterpreter sharedInterpreter] showMessage:aMsg]; }
491 void RInterp_FlushConsole() { [[RInterpreter sharedInterpreter] flushConsole]; }
492 int  RInterp_ReadConsole(char*prompt,unsigned char*buffer,int length,int history) {
493         return [[RInterpreter sharedInterpreter] readConsoleWithPrompt:prompt buffer:buffer length:length history:history];
495 void RInterp_ResetConsole() { [[RInterpreter sharedInterpreter] resetConsole]; }
496 void RInterp_WriteConsole(char*buffer,int length) { [[RInterpreter sharedInterpreter] writeConsole:buffer length:length]; }
497 void RInterp_ClearerrConsole() { [[RInterpreter sharedInterpreter] clearErrorConsole]; }
498 void RInterp_Busy() { [[RInterpreter sharedInterpreter] busy]; }
499 void RInterp_CleanUp(SA_TYPE type,int a,int b) { }
500 int  RInterp_ShowFiles(int a,char**b,char**c,char*d,Rboolean e,char*f) { 
501         return [[RInterpreter sharedInterpreter] showFiles:a file:b headers:c wtitle:d del:e pager:f];
503 int  RInterp_ChooseFile(int a,char*b,int c) { return 0; }
504 int  RInterp_EditFile(char*a) { return [[RInterpreter sharedInterpreter] editFile:a]; }
505 void RInterp_System(char*a) { [[RInterpreter sharedInterpreter] system:a]; }
506 void RInterp_ProcessEvents() { 
507         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
508         [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined 
509                                                                         location:NSMakePoint(0,0)
510                                                                          modifierFlags:0
511                                                                          timestamp:0 
512                                                                          windowNumber:0 
513                                                                          context:nil 
514                                                                          subtype:1337 
515                                                                          data1:0 
516                                                                          data2:0] atStart:NO];
517         [NSApp run];
518         [pool release];
520 Rboolean RInterp_Device(NewDevDesc *dev,
521         char *display,double width,double height,
522         double ps,char *family,Rboolean antialias,Rboolean autorefresh,int quartzpos,int bg) {
523         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
524         Rboolean ret = [[RInterpreter sharedInterpreter] openDevice:dev
525          withDisplay:display width:width height:height pointsize:ps family:family
526           antialias:antialias autorefresh:autorefresh quartzpos:quartzpos background:bg];
527         [pool release];
528         return ret;
530 void RInterp_DeviceParams(double*a,double*b,double*c,char*d,Rboolean*e,Rboolean*f,int*g) { }
531 int  RInterp_CustomPrint(char *a,SEXP b) { return [[RInterpreter sharedInterpreter] customPrintType:a list:b]; }
532 #endif