// [The "BSD licence"] // Copyright (c) 2006-2007 Kay Roepke // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. The name of the author may not be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "ANTLRDebugEventProxy.h" #import "ANTLRToken+DebuggerSupport.h" #include static NSData *newlineData = nil; static unsigned lengthOfUTF8Ack = 0; @implementation ANTLRDebugEventProxy + (void) initialize { if (!newlineData) newlineData = [@"\n" dataUsingEncoding:NSUTF8StringEncoding]; if (!lengthOfUTF8Ack) lengthOfUTF8Ack = [[@"ack\n" dataUsingEncoding:NSUTF8StringEncoding] length]; } - (id) init { return [self initWithGrammarName:nil debuggerPort:DEFAULT_DEBUGGER_PORT]; } - (id) initWithGrammarName:(NSString *)aGrammarName debuggerPort:(NSInteger)aPort { self = [super init]; if (self) { serverSocket = -1; [self setGrammarName:aGrammarName]; if (aPort == -1) aPort = DEFAULT_DEBUGGER_PORT; [self setDebuggerPort:aPort]; } return self; } - (void) dealloc { if (serverSocket != -1) shutdown(serverSocket,SHUT_RDWR); serverSocket = -1; [debuggerFH release]; [self setGrammarName:nil]; [super dealloc]; } - (void) waitForDebuggerConnection { if (serverSocket == -1) { serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); NSAssert1(serverSocket != -1, @"Failed to create debugger socket. %s", strerror(errno)); int yes = 1; setsockopt(serverSocket, SOL_SOCKET, SO_KEEPALIVE|SO_REUSEPORT|SO_REUSEADDR|TCP_NODELAY, (void *)&yes, sizeof(NSInteger)); struct sockaddr_in server_addr; bzero(&server_addr, sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons([self debuggerPort]); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); NSAssert1( bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) != -1, @"bind(2) failed. %s", strerror(errno)); NSAssert1(listen(serverSocket,50) == 0, @"listen(2) failed. %s", strerror(errno)); NSLog(@"ANTLR waiting for debugger attach (grammar %@)", [self grammarName]); debuggerSocket = accept(serverSocket, &debugger_sockaddr, &debugger_socklen); NSAssert1( debuggerSocket != -1, @"accept(2) failed. %s", strerror(errno)); debuggerFH = [[NSFileHandle alloc] initWithFileDescriptor:debuggerSocket]; [self sendToDebugger:[NSString stringWithFormat:@"ANTLR %d", ANTLRDebugProtocolVersion] waitForResponse:NO]; [self sendToDebugger:[NSString stringWithFormat:@"grammar \"%@", [self grammarName]] waitForResponse:NO]; } } - (void) waitForAck { NSString *response; @try { NSData *newLine = [debuggerFH readDataOfLength:lengthOfUTF8Ack]; response = [[NSString alloc] initWithData:newLine encoding:NSUTF8StringEncoding]; if (![response isEqualToString:@"ack\n"]) @throw [NSException exceptionWithName:@"ANTLRDebugEventProxy" reason:@"illegal response from debugger" userInfo:nil]; } @catch (NSException *e) { NSLog(@"socket died or debugger misbehaved: %@ read <%@>", e, response); } @finally { [response release]; } } - (void) sendToDebugger:(NSString *)message { [self sendToDebugger:message waitForResponse:YES]; } - (void) sendToDebugger:(NSString *)message waitForResponse:(BOOL)wait { if (! debuggerFH ) return; [debuggerFH writeData:[message dataUsingEncoding:NSUTF8StringEncoding]]; [debuggerFH writeData:newlineData]; if (wait) [self waitForAck]; } - (NSInteger) serverSocket { return serverSocket; } - (void) setServerSocket: (NSInteger) aServerSocket { serverSocket = aServerSocket; } - (NSInteger) debuggerSocket { return debuggerSocket; } - (void) setDebuggerSocket: (NSInteger) aDebuggerSocket { debuggerSocket = aDebuggerSocket; } - (NSString *) grammarName { return grammarName; } - (void) setGrammarName: (NSString *) aGrammarName { if (grammarName != aGrammarName) { [aGrammarName retain]; [grammarName release]; grammarName = aGrammarName; } } - (NSInteger) debuggerPort { return debuggerPort; } - (void) setDebuggerPort: (NSInteger) aDebuggerPort { debuggerPort = aDebuggerPort; } - (NSString *) escapeNewlines:(NSString *)aString { NSMutableString *escapedText; if (aString) { escapedText = [NSMutableString stringWithString:aString]; NSRange wholeString = NSMakeRange(0,[escapedText length]); [escapedText replaceOccurrencesOfString:@"%" withString:@"%25" options:0 range:wholeString]; [escapedText replaceOccurrencesOfString:@"\n" withString:@"%0A" options:0 range:wholeString]; [escapedText replaceOccurrencesOfString:@"\r" withString:@"%0D" options:0 range:wholeString]; } else { escapedText = [NSMutableString stringWithString:@""]; } return escapedText; } #pragma mark - #pragma mark DebugEventListener Protocol - (void) enterRule:(NSString *)ruleName { [self sendToDebugger:[NSString stringWithFormat:@"enterRule %@", ruleName]]; } - (void) enterAlt:(NSInteger)alt { [self sendToDebugger:[NSString stringWithFormat:@"enterAlt %d", alt]]; } - (void) exitRule:(NSString *)ruleName { [self sendToDebugger:[NSString stringWithFormat:@"exitRule %@", ruleName]]; } - (void) enterSubRule:(NSInteger)decisionNumber { [self sendToDebugger:[NSString stringWithFormat:@"enterSubRule %d", decisionNumber]]; } - (void) exitSubRule:(NSInteger)decisionNumber { [self sendToDebugger:[NSString stringWithFormat:@"exitSubRule %d", decisionNumber]]; } - (void) enterDecision:(NSInteger)decisionNumber { [self sendToDebugger:[NSString stringWithFormat:@"enterDecision %d", decisionNumber]]; } - (void) exitDecision:(NSInteger)decisionNumber { [self sendToDebugger:[NSString stringWithFormat:@"exitDecision %d", decisionNumber]]; } - (void) consumeToken:(id)t { [self sendToDebugger:[NSString stringWithFormat:@"consumeToken %@", [self escapeNewlines:[t description]]]]; } - (void) consumeHiddenToken:(id)t { [self sendToDebugger:[NSString stringWithFormat:@"consumeHiddenToken %@", [self escapeNewlines:[t description]]]]; } - (void) LT:(NSInteger)i foundToken:(id)t { [self sendToDebugger:[NSString stringWithFormat:@"LT %d %@", i, [self escapeNewlines:[t description]]]]; } - (void) mark:(NSInteger)marker { [self sendToDebugger:[NSString stringWithFormat:@"mark %d", marker]]; } - (void) rewind:(NSInteger)marker { [self sendToDebugger:[NSString stringWithFormat:@"rewind %d", marker]]; } - (void) rewind { [self sendToDebugger:@"rewind"]; } - (void) beginBacktrack:(NSInteger)level { [self sendToDebugger:[NSString stringWithFormat:@"beginBacktrack %d", level]]; } - (void) endBacktrack:(NSInteger)level wasSuccessful:(BOOL)successful { [self sendToDebugger:[NSString stringWithFormat:@"endBacktrack %d %d", level, successful ? 1 : 0]]; } - (void) locationLine:(NSInteger)line column:(NSInteger)pos { [self sendToDebugger:[NSString stringWithFormat:@"location %d %d", line, pos]]; } - (void) recognitionException:(ANTLRRecognitionException *)e { #warning TODO: recognition exceptions // these must use the names of the corresponding Java exception classes, because ANTLRWorks recreates the exception // objects on the Java side. // Write categories for Objective-C exceptions to provide those names } - (void) beginResync { [self sendToDebugger:@"beginResync"]; } - (void) endResync { [self sendToDebugger:@"endResync"]; } - (void) semanticPredicate:(NSString *)predicate matched:(BOOL)result { [self sendToDebugger:[NSString stringWithFormat:@"semanticPredicate %d %@", result?1:0, [self escapeNewlines:predicate]]]; } - (void) commence { // no need to send event } - (void) terminate { [self sendToDebugger:@"terminate"]; @try { [debuggerFH closeFile]; } @finally { #warning TODO: make socket handling robust. too lazy now... shutdown(serverSocket,SHUT_RDWR); serverSocket = -1; } } #pragma mark Tree Parsing - (void) consumeNode:(unsigned)nodeHash ofType:(NSInteger)type text:(NSString *)text { [self sendToDebugger:[NSString stringWithFormat:@"consumeNode %u %d %@", nodeHash, type, [self escapeNewlines:text] ]]; } - (void) LT:(NSInteger)i foundNode:(unsigned)nodeHash ofType:(NSInteger)type text:(NSString *)text { [self sendToDebugger:[NSString stringWithFormat:@"LN %d %u %d %@", i, nodeHash, type, [self escapeNewlines:text] ]]; } #pragma mark AST Events - (void) createNilNode:(unsigned)hash { [self sendToDebugger:[NSString stringWithFormat:@"nilNode %u", hash]]; } - (void) createNode:(unsigned)hash text:(NSString *)text type:(NSInteger)type { [self sendToDebugger:[NSString stringWithFormat:@"createNodeFromToken %u %d %@", hash, type, [self escapeNewlines:text] ]]; } - (void) createNode:(unsigned)hash fromTokenAtIndex:(NSInteger)tokenIndex { [self sendToDebugger:[NSString stringWithFormat:@"createNode %u %d", hash, tokenIndex]]; } - (void) becomeRoot:(unsigned)newRootHash old:(unsigned)oldRootHash { [self sendToDebugger:[NSString stringWithFormat:@"becomeRoot %u %u", newRootHash, oldRootHash]]; } - (void) addChild:(unsigned)childHash toTree:(unsigned)treeHash { [self sendToDebugger:[NSString stringWithFormat:@"addChild %u %u", treeHash, childHash]]; } - (void) setTokenBoundariesForTree:(unsigned)nodeHash From:(NSInteger)tokenStartIndex To:(NSInteger)tokenStopIndex { [self sendToDebugger:[NSString stringWithFormat:@"setTokenBoundaries %u %d %d", nodeHash, tokenStartIndex, tokenStopIndex]]; } @end