blob: de1a0a30604df5722f9b09e43ca171b90c9e855d [file] [log] [blame]
// [The "BSD licence"]
// Copyright (c) 2006-2007 Kay Roepke 2010 Alan Condit
// 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 <ANTLR/antlr.h>
#import "ANTLRLexer.h"
@implementation ANTLRLexer
@synthesize input;
@synthesize ruleNestingLevel;
#pragma mark Initializer
- (id) initWithCharStream:(id<ANTLRCharStream>)anInput
{
self = [super initWithState:[[ANTLRRecognizerSharedState alloc] init]];
if ( self != nil ) {
input = [anInput retain];
if (state.token != nil)
[((ANTLRCommonToken *)state.token) setInput:anInput];
ruleNestingLevel = 0;
}
return self;
}
- (id) initWithCharStream:(id<ANTLRCharStream>)anInput State:(ANTLRRecognizerSharedState *)aState
{
self = [super initWithState:aState];
if ( self != nil ) {
input = [anInput retain];
if (state.token != nil)
[((ANTLRCommonToken *)state.token) setInput:anInput];
ruleNestingLevel = 0;
}
return self;
}
- (void) dealloc
{
if ( input ) [input release];
[super dealloc];
}
- (id) copyWithZone:(NSZone *)aZone
{
ANTLRLexer *copy;
copy = [[[self class] allocWithZone:aZone] init];
// copy = [super copyWithZone:aZone]; // allocation occurs here
if ( input != nil )
copy.input = input;
copy.ruleNestingLevel = ruleNestingLevel;
return copy;
}
- (void) reset
{
[super reset]; // reset all recognizer state variables
// wack Lexer state variables
if ( input != nil ) {
[input seek:0]; // rewind the input
}
if ( state == nil ) {
return; // no shared state work to do
}
state.token = nil;
state.type = ANTLRCommonToken.INVALID_TOKEN_TYPE;
state.channel = ANTLRCommonToken.DEFAULT_CHANNEL;
state.tokenStartCharIndex = -1;
state.tokenStartCharPositionInLine = -1;
state.tokenStartLine = -1;
state.text = nil;
}
// token stuff
#pragma mark Tokens
- (id<ANTLRToken>)getToken
{
return [state getToken];
}
- (void) setToken: (id<ANTLRToken>) aToken
{
if (state.token != aToken) {
[aToken retain];
state.token = aToken;
}
}
// this method may be overridden in the generated lexer if we generate a filtering lexer.
- (id<ANTLRToken>) nextToken
{
while (YES) {
[self setToken:nil];
state.channel = ANTLRCommonToken.DEFAULT_CHANNEL;
state.tokenStartCharIndex = input.index;
state.tokenStartCharPositionInLine = input.charPositionInLine;
state.tokenStartLine = input.line;
state.text = nil;
// [self setText:[self text]];
if ([input LA:1] == ANTLRCharStreamEOF) {
ANTLRCommonToken *eof = [ANTLRCommonToken newToken:input
Type:ANTLRTokenTypeEOF
Channel:ANTLRCommonToken.DEFAULT_CHANNEL
Start:input.index
Stop:input.index];
[eof setLine:input.line];
[eof setCharPositionInLine:input.charPositionInLine];
return eof;
}
@try {
[self mTokens];
// SEL aMethod = @selector(mTokens);
// [[self class] instancesRespondToSelector:aMethod];
if ( state.token == nil)
[self emit];
else if ( state.token == [ANTLRCommonToken skipToken] ) {
continue;
}
return state.token;
}
@catch (ANTLRNoViableAltException *nva) {
[self reportError:nva];
[self recover:nva];
}
@catch (ANTLRRecognitionException *e) {
[self reportError:e];
}
}
}
- (void) mTokens
{ // abstract, defined in generated source as a starting point for matching
[self doesNotRecognizeSelector:_cmd];
}
- (void) skip
{
state.token = [ANTLRCommonToken skipToken];
}
- (id<ANTLRCharStream>) input
{
return input;
}
- (void) setInput:(id<ANTLRCharStream>) anInput
{
if ( anInput != input ) {
if ( input ) [input release];
}
input = nil;
[self reset];
input = anInput;
[input retain];
}
/** Currently does not support multiple emits per nextToken invocation
* for efficiency reasons. Subclass and override this method and
* nextToken (to push tokens into a list and pull from that list rather
* than a single variable as this implementation does).
*/
- (void) emit:(id<ANTLRToken>)aToken
{
state.token = aToken;
}
/** The standard method called to automatically emit a token at the
* outermost lexical rule. The token object should point into the
* char buffer start..stop. If there is a text override in 'text',
* use that to set the token's text. Override this method to emit
* custom Token objects.
*
* If you are building trees, then you should also override
* Parser or TreeParser.getMissingSymbol().
*/
- (void) emit
{
id<ANTLRToken> aToken = [ANTLRCommonToken newToken:input
Type:state.type
Channel:state.channel
Start:state.tokenStartCharIndex
Stop:input.index-1];
[aToken setLine:state.tokenStartLine];
aToken.text = [self text];
[aToken setCharPositionInLine:state.tokenStartCharPositionInLine];
[aToken retain];
[self emit:aToken];
// [aToken release];
}
// matching
#pragma mark Matching
- (void) matchString:(NSString *)aString
{
unichar c;
unsigned int i = 0;
unsigned int stringLength = [aString length];
while ( i < stringLength ) {
c = [input LA:1];
if ( c != [aString characterAtIndex:i] ) {
if ([state getBacktracking] > 0) {
state.failed = YES;
return;
}
ANTLRMismatchedTokenException *mte = [ANTLRMismatchedTokenException newExceptionChar:[aString characterAtIndex:i] Stream:input];
mte.c = c;
[self recover:mte];
@throw mte;
}
i++;
[input consume];
state.failed = NO;
}
}
- (void) matchAny
{
[input consume];
}
- (void) matchChar:(unichar) aChar
{
// TODO: -LA: is returning an int because it sometimes is used in the generated parser to compare lookahead with a tokentype.
// try to change all those occurrences to -LT: if possible (i.e. if ANTLR can be made to generate LA only for lexer code)
unichar charLA;
charLA = [input LA:1];
if ( charLA != aChar) {
if ([state getBacktracking] > 0) {
state.failed = YES;
return;
}
ANTLRMismatchedTokenException *mte = [ANTLRMismatchedTokenException newExceptionChar:aChar Stream:input];
mte.c = charLA;
[self recover:mte];
@throw mte;
}
[input consume];
state.failed = NO;
}
- (void) matchRangeFromChar:(unichar)fromChar to:(unichar)toChar
{
unichar charLA = (unichar)[input LA:1];
if ( charLA < fromChar || charLA > toChar ) {
if ([state getBacktracking] > 0) {
state.failed = YES;
return;
}
ANTLRMismatchedRangeException *mre = [ANTLRMismatchedRangeException
newException:NSMakeRange((NSUInteger)fromChar,(NSUInteger)toChar)
stream:input];
mre.c = charLA;
[self recover:mre];
@throw mre;
}
[input consume];
state.failed = NO;
}
// info
#pragma mark Informational
- (NSUInteger) line
{
return input.line;
}
- (NSUInteger) charPositionInLine
{
return input.charPositionInLine;
}
- (NSInteger) index
{
return 0;
}
- (NSString *) text
{
if (state.text != nil) {
return state.text;
}
return [input substringWithRange:NSMakeRange(state.tokenStartCharIndex, input.index-state.tokenStartCharIndex)];
}
- (void) setText:(NSString *) theText
{
state.text = theText;
}
// error handling
- (void) reportError:(ANTLRRecognitionException *)e
{
/** TODO: not thought about recovery in lexer yet.
*
// if we've already reported an error and have not matched a token
// yet successfully, don't report any errors.
if ( errorRecovery ) {
//System.err.print("[SPURIOUS] ");
return;
}
errorRecovery = true;
*/
[self displayRecognitionError:[self getTokenNames] Exception:e];
}
- (NSString *)getErrorMessage:(ANTLRRecognitionException *)e TokenNames:(AMutableArray *)tokenNames
{
/* NSString *msg = [NSString stringWithFormat:@"Gotta fix getErrorMessage in ANTLRLexer.m--%@\n",
e.name];
*/
NSString *msg = nil;
if ( [e isKindOfClass:[ANTLRMismatchedTokenException class]] ) {
ANTLRMismatchedTokenException *mte = (ANTLRMismatchedTokenException *)e;
msg = [NSString stringWithFormat:@"mismatched character \"%@\" expecting \"%@\"",
[self getCharErrorDisplay:mte.c], [self getCharErrorDisplay:mte.expecting]];
}
else if ( [e isKindOfClass:[ANTLRNoViableAltException class]] ) {
ANTLRNoViableAltException *nvae = (ANTLRNoViableAltException *)e;
// for development, can add "decision=<<"+nvae.grammarDecisionDescription+">>"
// and "(decision="+nvae.decisionNumber+") and
// "state "+nvae.stateNumber
msg = [NSString stringWithFormat:@"no viable alternative at character \"%@\"",
[self getCharErrorDisplay:(nvae.c)]];
}
else if ( [e isKindOfClass:[ANTLREarlyExitException class]] ) {
ANTLREarlyExitException *eee = (ANTLREarlyExitException *)e;
// for development, can add "(decision="+eee.decisionNumber+")"
msg = [NSString stringWithFormat:@"required (...)+ loop did not match anything at character \"%@\"",
[self getCharErrorDisplay:(eee.c)]];
}
else if ( [e isKindOfClass:[ANTLRMismatchedNotSetException class]] ) {
ANTLRMismatchedNotSetException *mse = (ANTLRMismatchedNotSetException *)e;
msg = [NSString stringWithFormat:@"mismatched character \"%@\" expecting set \"%@\"",
[self getCharErrorDisplay:(mse.c)], mse.expecting];
}
else if ( [e isKindOfClass:[ANTLRMismatchedSetException class]] ) {
ANTLRMismatchedSetException *mse = (ANTLRMismatchedSetException *)e;
msg = [NSString stringWithFormat:@"mismatched character \"%@\" expecting set \"%@\"",
[self getCharErrorDisplay:(mse.c)], mse.expecting];
}
else if ( [e isKindOfClass:[ANTLRMismatchedRangeException class]] ) {
ANTLRMismatchedRangeException *mre = (ANTLRMismatchedRangeException *)e;
msg = [NSString stringWithFormat:@"mismatched character \"%@\" \"%@..%@\"",
[self getCharErrorDisplay:(mre.c)], [self getCharErrorDisplay:(mre.range.location)],
[self getCharErrorDisplay:(mre.range.location+mre.range.length-1)]];
}
else {
msg = [super getErrorMessage:e TokenNames:[self getTokenNames]];
}
return msg;
}
- (NSString *)getCharErrorDisplay:(NSInteger)c
{
NSString *s;
switch ( c ) {
case ANTLRTokenTypeEOF :
s = @"<EOF>";
break;
case '\n' :
s = @"\\n";
break;
case '\t' :
s = @"\\t";
break;
case '\r' :
s = @"\\r";
break;
default:
s = [NSString stringWithFormat:@"%c", (char)c];
break;
}
return s;
}
/** Lexers can normally match any char in it's vocabulary after matching
* a token, so do the easy thing and just kill a character and hope
* it all works out. You can instead use the rule invocation stack
* to do sophisticated error recovery if you are in a fragment rule.
*/
- (void)recover:(ANTLRRecognitionException *)re
{
//System.out.println("consuming char "+(char)input.LA(1)+" during recovery");
//re.printStackTrace();
[input consume];
}
- (void)traceIn:(NSString *)ruleName Index:(NSInteger)ruleIndex
{
NSString *inputSymbol = [NSString stringWithFormat:@"%c line=%d:%d\n", [input LT:1], input.line, input.charPositionInLine];
[super traceIn:ruleName Index:ruleIndex Object:inputSymbol];
}
- (void)traceOut:(NSString *)ruleName Index:(NSInteger)ruleIndex
{
NSString *inputSymbol = [NSString stringWithFormat:@"%c line=%d:%d\n", [input LT:1], input.line, input.charPositionInLine];
[super traceOut:ruleName Index:ruleIndex Object:inputSymbol];
}
@end