| // [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 <string.h> |
| |
| 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<ANTLRToken>)t |
| { |
| [self sendToDebugger:[NSString stringWithFormat:@"consumeToken %@", [self escapeNewlines:[t description]]]]; |
| } |
| |
| - (void) consumeHiddenToken:(id<ANTLRToken>)t |
| { |
| [self sendToDebugger:[NSString stringWithFormat:@"consumeHiddenToken %@", [self escapeNewlines:[t description]]]]; |
| } |
| |
| - (void) LT:(NSInteger)i foundToken:(id<ANTLRToken>)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 |