aboutsummaryrefslogtreecommitdiffstats
path: root/LispREPL.m
diff options
context:
space:
mode:
Diffstat (limited to 'LispREPL.m')
-rw-r--r--LispREPL.m318
1 files changed, 318 insertions, 0 deletions
diff --git a/LispREPL.m b/LispREPL.m
new file mode 100644
index 0000000..0033ad7
--- /dev/null
+++ b/LispREPL.m
@@ -0,0 +1,318 @@
+//
+// LispREPL.m
+// Moxie
+//
+// Created by Brian Cully on Sat Aug 14 2004.
+// Copyright (c) 2004 Brian Cully. All rights reserved.
+//
+
+#import "LispREPL.h"
+#import "LispREPLController.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+enum repl_lock_condition { NO_DATA, HAS_DATA };
+
+@implementation LispREPL
+void
+sig_pipe(int signo)
+{
+ NSLog(@"WARNING: SIGPIPE caught.");
+}
+
+void
+sig_child(int signo)
+{
+ int status;
+
+ if (wait(&status) == -1) {
+ NSLog(@"WARNING: Couldn't clean up child: %s.\n", strerror(errno));
+ return;
+ }
+}
+
++ (LispREPL *)sharedREPL
+{
+ static LispREPL *sharedREPL;
+
+ if (sharedREPL == nil)
+ sharedREPL = [[self alloc] init];
+ return sharedREPL;
+}
+
+- (int)initLispProcess;
+{
+ NSString *helperPath;
+ NSString *resLocation, *pluginLocation, *frameworkLocation;
+ int pin[2], pout[2], res[2];
+ pid_t pid;
+ struct sigaction sa, ocsa, opsa;
+
+ helperPath = [[NSBundle mainBundle] pathForAuxiliaryExecutable: @"startlisp"];
+ if (helperPath == nil) {
+ NSLog(@"Couldn't find helper application in resources!");
+ return -1;
+ }
+
+ resLocation = [[NSBundle mainBundle] resourcePath];
+ pluginLocation = [[NSBundle mainBundle] builtInPlugInsPath];
+ frameworkLocation = [[NSBundle mainBundle] privateFrameworksPath];
+
+ if (pipe(pin) == -1 || pipe(pout) == -1 || pipe(res) == -1) {
+ NSLog(@"Couldn't create pipes for REPL!");
+ return -1;
+ }
+
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sig_child;
+ sigaction(SIGCHLD, &sa, &ocsa);
+ sa.sa_handler = sig_pipe;
+ sigaction(SIGPIPE, &sa, &opsa);
+
+ setsid();
+ pid = fork();
+ if (pid == -1) {
+ NSLog(@"ERROR: Couldn't fork off lisp process: %s.", strerror(errno));
+ return -1;
+ } else if (pid == 0) {
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGCHLD, &sa, NULL);
+ sigaction(SIGPIPE, &sa, NULL);
+
+ close(pin[1]); close(pout[0]); close(res[0]);
+ if (dup2(pin[0], STDIN_FILENO) == -1 ||
+ dup2(pout[1], STDOUT_FILENO) == -1 ||
+ dup2(pout[1], STDERR_FILENO) == -1 ||
+ dup2(res[1], STDERR_FILENO+1) == -1) {
+ NSLog(@"ERROR: Couldn't setup standard I/O pipes: %s.", strerror(errno));
+ exit(1);
+ }
+ execl([helperPath UTF8String], [[helperPath lastPathComponent] UTF8String],
+ [resLocation UTF8String], [pluginLocation UTF8String], [frameworkLocation UTF8String], NULL);
+ exit(1);
+ }
+
+ close(pin[0]); close(pout[1]); close(res[1]);
+ theStdinWriter = [[NSFileHandle alloc] initWithFileDescriptor: pin[1]
+ closeOnDealloc: YES];
+ theStdoutReader = [[NSFileHandle alloc] initWithFileDescriptor: pout[0]
+ closeOnDealloc: YES];
+ theResultReader = [[NSFileHandle alloc] initWithFileDescriptor: res[0]
+ closeOnDealloc: YES];
+
+ return 0;
+}
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ [self initLispProcess];
+
+ // Set up the result reader thread.
+ theREPLResults = [[NSMutableArray alloc] init];
+ theCommandHandlers = [[NSMutableDictionary alloc] init];
+ theDispatcherLock = [[NSConditionLock alloc] initWithCondition: NO_DATA];
+ theLispIsLoaded = NO;
+ [NSThread detachNewThreadSelector: @selector(readREPLResults:)
+ toTarget: self
+ withObject: [self resultReader]];
+ theDispatcherTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1
+ target: self
+ selector: @selector(dispatchREPLResults:)
+ userInfo: nil
+ repeats: YES];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ // Gotta kill the reader thread here.
+ [theDispatcherLock release];
+ [theDispatcherTimer release];
+
+ // Then close file handles.
+ [[self stdinWriter] release];
+ [[self stdoutReader] release];
+ [[self resultReader] release];
+
+ // Then release our data.
+ [theREPLResults release];
+ [theCommandHandlers release];
+ [super dealloc];
+}
+
+- (BOOL)isLoaded
+{
+ return theLispIsLoaded;
+}
+
+- (NSArray *)handlerInfoForCommand: (id)command
+{
+ NSArray *handlerInfo;
+
+ handlerInfo = [[self commandHandlers] objectForKey: [command isKindOfClass: [NSString class]] ? command : [command stringValue]];
+ if (handlerInfo && [handlerInfo count] == 2)
+ return handlerInfo;
+ return nil;
+}
+
+- (void)setHandlerInfo: (NSArray *)handlerInfo forCommand: (NSString *)command
+{
+ if ([handlerInfo count] == 2)
+ [[self commandHandlers] setObject: handlerInfo forKey: command];
+ else if (handlerInfo == nil)
+ [[self commandHandlers] removeObjectForKey: command];
+}
+
+- (void)addCommand: (NSString *)command
+ handler: (id)object
+ selector: (SEL)handler
+{
+ [self setHandlerInfo: [NSArray arrayWithObjects: object, NSStringFromSelector(handler), nil]
+ forCommand: command];
+}
+
+- (void)removeCommand: (NSString *)command
+{
+ [self setHandlerInfo: nil forCommand: command];
+}
+
+- (void)runCommand: (NSString *)command withObjects: (id)objects
+{
+ id handler;
+ SEL selector;
+
+ handler = [self handlerForCommand: command];
+ if (handler) {
+ selector = [self selectorForCommand: command];
+
+ NS_DURING
+ [handler performSelector: selector withObject: objects];
+ NS_HANDLER
+ NSLog(@"WARNING: Got exception: %@ (%@) while evaluating %@:%@",
+ [localException name], [localException reason], handler, NSStringFromSelector(selector));
+ NS_ENDHANDLER
+ }
+}
+
+- (BOOL)commandHasHandler: (NSString *)command
+{
+ if ([self handlerInfoForCommand: command])
+ return YES;
+ return NO;
+}
+
+- (id)handlerForCommand: (NSString *)command
+{
+ return [[self handlerInfoForCommand: command] objectAtIndex: 0];
+}
+
+- (SEL)selectorForCommand: (NSString *)command
+{
+ return NSSelectorFromString([[self handlerInfoForCommand: command] objectAtIndex: 1]);
+}
+
+- (void)eval: (id)aForm
+{
+ [[self stdinWriter] writeData: [[NSString stringWithFormat: @"%@\n", aForm]
+ dataUsingEncoding: NSUTF8StringEncoding]];
+}
+
+/*
+ * Read and parse result forms from the REPL. Once parsed into arrays, dispatch them via
+ * the main thread by adding them to a worker queue.
+ */
+- (void)readREPLResults: (NSFileHandle *)resultsHandle
+{
+ NS_DURING
+ while (1) {
+ NSAutoreleasePool *rp;
+ id results;
+
+ rp = [[NSAutoreleasePool alloc] init];
+ results = [resultsHandle readLispForm];
+ [theDispatcherLock lock];
+ [theREPLResults addObject: results];
+ [theDispatcherLock unlock];
+ [rp release];
+ }
+ NS_HANDLER
+ [self performSelectorOnMainThread: @selector(replDiedSelector:)
+ withObject: self
+ waitUntilDone: NO];
+ NS_ENDHANDLER
+}
+
+- (void)replDiedSelector: (id)sender
+{
+ [[LispREPLController sharedController] showWindow: self];
+ NSRunCriticalAlertPanel(@"REPL died", @"The REPL sub-process has died, and Moxie cannot continue.",
+ @"OK", @"Retry", @"DEBUG");
+// [NSApp stop: self];
+}
+
+/*
+ * Wait for updates on the queue and dispatch results to the appropriate callback
+ * method on the values, if they're there, and call regular callbacks on the rest of
+ * the elements.
+ */
+- (void)dispatchREPLResults: (NSNotification *)resultsNotification
+{
+ [theDispatcherLock lock];
+ while ([theREPLResults count] > 0) {
+ id result;
+
+ if (theLispIsLoaded == NO) {
+ NSNotificationCenter *defaultCenter;
+ theLispIsLoaded = YES;
+
+ defaultCenter = [NSNotificationCenter defaultCenter];
+ [defaultCenter postNotificationName: LispFinishedLoadingNotification
+ object: self];
+ }
+ result = [theREPLResults objectAtIndex: 0];
+ if ([result isKindOfClass: [NSArray class]]) {
+ NSString *command;
+ NSArray *argArray;
+
+ command = [result objectAtIndex: 0];
+ argArray = [result subarrayWithRange: NSMakeRange(1, [result count] - 1)];
+ [self runCommand: command withObjects: argArray];
+ } else {
+ NSLog(@"ERROR: Couldn't dispatch results: %@", result);
+ }
+ [theREPLResults removeObjectAtIndex: 0];
+ }
+ [theDispatcherLock unlock];
+}
+@end
+
+@implementation LispREPL (Accessors)
+- (NSMutableDictionary *)commandHandlers
+{
+ return theCommandHandlers;
+}
+
+- (NSFileHandle *)stdinWriter
+{
+ return theStdinWriter;
+}
+
+- (NSFileHandle *)stdoutReader
+{
+ return theStdoutReader;
+}
+
+- (NSFileHandle *)resultReader
+{
+ return theResultReader;
+}
+@end \ No newline at end of file