//  This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
//  Copyright (C) 1997-1998, 2000-2001, 2004-2007  Steve Nygard

#import "CDTypeParser.h"

#include <assert.h>
#import <Foundation/Foundation.h>
#import "CDMethodType.h"
#import "CDType.h"
#import "CDTypeName.h"
#import "CDTypeLexer.h"
#import "NSString-Extensions.h"

NSString *CDSyntaxError = @"Syntax Error";
NSString *CDTypeParserErrorDomain = @"CDTypeParserErrorDomain";

NSString *CDTokenDescription(int token)
{
    if (token < 128)
        return [NSString stringWithFormat:@"%d(%c)", token, token];

    return [NSString stringWithFormat:@"%d", token];
}

@implementation CDTypeParser

- (id)initWithType:(NSString *)aType;
{
    NSMutableString *str;

    if ([super init] == nil)
        return nil;

    // Do some preprocessing first: Replace "<unnamed>::" with just "unnamed::".
    str = [aType mutableCopy];
    [str replaceOccurrencesOfString:@"<unnamed>::" withString:@"unnamed::" options:0 range:NSMakeRange(0, [aType length])];

    lexer = [[CDTypeLexer alloc] initWithString:str];
    lookahead = 0;

    [str release];

    return self;
}

- (void)dealloc;
{
    [lexer release];

    [super dealloc];
}

- (CDTypeLexer *)lexer;
{
    return lexer;
}

- (NSArray *)parseMethodType:(NSError **)error;
{
    NSArray *result;

    *error = nil;

    NS_DURING {
        lookahead = [lexer scanNextToken];
        result = [self _parseMethodType];
    } NS_HANDLER {
        NSDictionary *userInfo;
        int code;

        // Obviously I need to figure out a sane method of dealing with errors here.  This is not.
        if ([[localException name] isEqual:CDSyntaxError]) {
            code = CDTypeParserCodeSyntaxError;
            userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:@"Syntax Error", @"reason",
                                             [NSString stringWithFormat:@"Syntax Error, %@:\n\t     type: %@\n\tremaining: %@",
                                                       [localException reason], [lexer string], [lexer remainingString]], @"explanation",
                                             [lexer string], @"type",
                                             [lexer remainingString], @"remaining string",
                                             nil];
        } else {
            code = CDTypeParserCodeDefault;
            userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[localException reason], @"reason",
                                             [lexer string], @"type",
                                             [lexer remainingString], @"remaining string",
                                             nil];
        }
        *error = [NSError errorWithDomain:CDTypeParserErrorDomain code:code userInfo:userInfo];
        [userInfo release];

        result = nil;
    } NS_ENDHANDLER;

    return result;
}

- (CDType *)parseType:(NSError **)error;
{
    CDType *result;

    *error = nil;

    NS_DURING {
        lookahead = [lexer scanNextToken];
        result = [self _parseType];
    } NS_HANDLER {
        NSDictionary *userInfo;
        int code;

        // Obviously I need to figure out a sane method of dealing with errors here.  This is not.
        if ([[localException name] isEqual:CDSyntaxError]) {
            code = CDTypeParserCodeSyntaxError;
            userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:@"Syntax Error", @"reason",
                                             [NSString stringWithFormat:@"%@:\n\t     type: %@\n\tremaining: %@",
                                                       [localException reason], [lexer string], [lexer remainingString]], @"explanation",
                                             [lexer string], @"type",
                                             [lexer remainingString], @"remaining string",
                                             nil];
        } else {
            code = CDTypeParserCodeDefault;
            userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[localException reason], @"reason",
                                             [lexer string], @"type",
                                             [lexer remainingString], @"remaining string",
                                             nil];
        }
        *error = [NSError errorWithDomain:CDTypeParserErrorDomain code:code userInfo:userInfo];
        [userInfo release];

        result = nil;
    } NS_ENDHANDLER;

    return result;
}

@end

@implementation CDTypeParser (Private)

- (void)match:(int)token;
{
    [self match:token enterState:[lexer state]];
}

- (void)match:(int)token enterState:(int)newState;
{
    if (lookahead == token) {
        //NSLog(@"matched %@", CDTokenDescription(token));
        [lexer setState:newState];
        lookahead = [lexer scanNextToken];
    } else {
        [NSException raise:CDSyntaxError format:@"expected token %@, got %@",
                     CDTokenDescription(token),
                     CDTokenDescription(lookahead)];
    }
}

- (void)error:(NSString *)errorString;
{
    [NSException raise:CDSyntaxError format:@"%@", errorString];
}

- (NSArray *)_parseMethodType;
{
    NSMutableArray *methodTypes;
    CDMethodType *aMethodType;
    CDType *type;
    NSString *number;

    methodTypes = [NSMutableArray array];

    // Has to have at least one pair for the return type;
    // Probably needs at least two more, for object and selector
    // So it must be <type><number><type><number><type><number>.  Three pairs at a minimum.

    do {
        type = [self _parseType];
        number = [self parseNumber];

        aMethodType = [[CDMethodType alloc] initWithType:type offset:number];
        [methodTypes addObject:aMethodType];
        [aMethodType release];
    } while ([self isTokenInTypeStartSet:lookahead] == YES);

    return methodTypes;
}

// Plain object types can be:
//     @                     - plain id type
//     @"NSObject"           - NSObject *
//     @"<MyProtocol>"       - id <MyProtocol>
// But these can also be part of a structure, with the field name in quotes before the type:
//     "foo"i"bar"i                - int foo, int bar
//     "foo"@"bar"i                - id foo, int bar
//     "foo"@"Foo""bar"i           - Foo *foo, int bar
// So this is where we need to be careful.
//
// I'm going to make a simplifying assumption:  Either the structure/union has member names,
// or is doesn't, it can't have some names and be missing others.
// The two key tests are:
//     {my_struct3="field1"@"field2"i}
//     {my_struct4="field1"@"NSObject""field2"i}

- (CDType *)_parseType;
{
    return [self _parseTypeInStruct:NO];
}

- (CDType *)_parseTypeInStruct:(BOOL)isInStruct;
{
    CDType *result;

    if (lookahead == 'r'
        || lookahead == 'n'
        || lookahead == 'N'
        || lookahead == 'o'
        || lookahead == 'O'
        || lookahead == 'R'
        || lookahead == 'V') { // modifiers
        int modifier;
        CDType *unmodifiedType;
        modifier = lookahead;
        [self match:modifier];

        if ([self isTokenInTypeStartSet:lookahead] == YES)
            unmodifiedType = [self _parseTypeInStruct:isInStruct];
        else
            unmodifiedType = nil;
        result = [[CDType alloc] initModifier:modifier type:unmodifiedType];
    } else if (lookahead == '^') { // pointer
        CDType *type;

        [self match:'^'];
        type = [self _parseTypeInStruct:isInStruct];
        result = [[CDType alloc] initPointerType:type];
    } else if (lookahead == 'b') { // bitfield
        NSString *number;

        [self match:'b'];
        number = [self parseNumber];
        result = [[CDType alloc] initBitfieldType:number];
    } else if (lookahead == '@') { // id
        [self match:'@'];
#if 0
        if (lookahead == TK_QUOTED_STRING) {
            NSLog(@"%s, quoted string ahead, shouldCheckFieldNames: %d, end: %d",
                  _cmd, shouldCheckFieldNames, [[lexer scanner] isAtEnd]);
            if ([[lexer scanner] isAtEnd] == NO)
                NSLog(@"next character: %d (%c), isInTypeStartSet: %d", [lexer peekChar], [lexer peekChar], [self isTokenInTypeStartSet:[lexer peekChar]]);
        }
#endif
        if (lookahead == TK_QUOTED_STRING && (isInStruct == NO || [[lexer lexText] isFirstLetterUppercase] == YES || [self isTokenInTypeStartSet:[lexer peekChar]] == NO)) {
            NSString *str;
            CDTypeName *typeName;

            str = [lexer lexText];
            if ([str hasPrefix:@"<"] == YES && [str hasSuffix:@">"] == YES) {
                str = [str substringWithRange:NSMakeRange(1, [str length] - 2)];
                result = [[CDType alloc] initIDTypeWithProtocols:[str componentsSeparatedByString:@","]];
            } else {
                typeName = [[CDTypeName alloc] init];
                [typeName setName:str];
                result = [[CDType alloc] initIDType:typeName];
                [typeName release];
            }

            [self match:TK_QUOTED_STRING];
        } else {
            result = [[CDType alloc] initIDType:nil];
        }
    } else if (lookahead == '{') { // structure
        CDTypeName *typeName;
        NSArray *optionalMembers;
        CDTypeLexerState savedState;

        savedState = [lexer state];
        [self match:'{' enterState:CDTypeLexerStateIdentifier];
        typeName = [self parseTypeName];
        optionalMembers = [self parseOptionalMembers];
        [self match:'}' enterState:savedState];

        result = [[CDType alloc] initStructType:typeName members:optionalMembers];
    } else if (lookahead == '(') { // union
        CDTypeLexerState savedState;

        savedState = [lexer state];
        [self match:'(' enterState:CDTypeLexerStateIdentifier];
        if (lookahead == TK_IDENTIFIER) {
            CDTypeName *typeName;
            NSArray *optionalMembers;

            typeName = [self parseTypeName];
            optionalMembers = [self parseOptionalMembers];
            [self match:')' enterState:savedState];

            result = [[CDType alloc] initUnionType:typeName members:optionalMembers];
        } else {
            NSArray *unionTypes;

            unionTypes = [self parseUnionTypes];
            [self match:')' enterState:savedState];

            result = [[CDType alloc] initUnionType:nil members:unionTypes];
        }
    } else if (lookahead == '[') { // array
        NSString *number;
        CDType *type;

        [self match:'['];
        number = [self parseNumber];
        type = [self _parseType];
        [self match:']'];

        result = [[CDType alloc] initArrayType:type count:number];
    } else if ([self isTokenInSimpleTypeSet:lookahead] == YES) { // simple type
        int simpleType;

        simpleType = lookahead;
        [self match:simpleType];
        result = [[CDType alloc] initSimpleType:simpleType];
    } else {
        result = nil;
        [NSException raise:CDSyntaxError format:@"expected (many things), got %@", CDTokenDescription(lookahead)];
    }

    return [result autorelease];
}

// This seems to be used in method types -- no names
- (NSArray *)parseUnionTypes;
{
    NSMutableArray *members;

    members = [NSMutableArray array];

    while ([self isTokenInTypeSet:lookahead] == YES) {
        CDType *aType;

        aType = [self _parseType];
        //[aType setVariableName:@"___"];
        [members addObject:aType];
    }

    return members;
}

- (NSArray *)parseOptionalMembers;
{
    NSArray *result;

    if (lookahead == '=') {
        [self match:'='];
        result = [self parseMemberList];
    } else
        result = nil;

    return result;
}

- (NSArray *)parseMemberList;
{
    NSMutableArray *result;

    result = [NSMutableArray array];

    while (lookahead == TK_QUOTED_STRING || [self isTokenInTypeSet:lookahead] == YES)
        [result addObject:[self parseMember]];

    return result;
}

- (CDType *)parseMember;
{
    CDType *result;

    //NSLog(@" > %s", _cmd);

    if (lookahead == TK_QUOTED_STRING) {
        NSString *identifier = nil;

        while (lookahead == TK_QUOTED_STRING) {
            if (identifier == nil)
                identifier = [lexer lexText];
            else {
                // TextMate 1.5.4 has structures like... "storage""stack"{etc} -- two quoted strings next to each other.
                identifier = [NSString stringWithFormat:@"%@__%@", identifier, [lexer lexText]];
            }
            [self match:TK_QUOTED_STRING];
        }

        result = [self _parseTypeInStruct:YES];
        [result setVariableName:identifier];
    } else {
        result = [self _parseTypeInStruct:YES];
    }

    //NSLog(@"<  %s", _cmd);
    return result;
}

- (CDTypeName *)parseTypeName;
{
    CDTypeName *typeName;

    typeName = [[[CDTypeName alloc] init] autorelease];
    [typeName setName:[self parseIdentifier]];

    if (lookahead == '<') {
        CDTypeLexerState savedState;

        savedState = [lexer state];
        [self match:'<' enterState:CDTypeLexerStateTemplateTypes];
        [typeName addTemplateType:[self parseTypeName]];
        while (lookahead == ',') {
            [self match:','];
            [typeName addTemplateType:[self parseTypeName]];
        }

        [self match:'>' enterState:savedState];
    }
#if 0
    // This breaks a bunch of the unit tests... need to figure out what's up with that first.
    // We'll treat "?" as no name, returning nil here instead of testing the type name for this later.
    if ([[typeName name] isEqualToString:@"?"] && [typeName isTemplateType] == NO)
        typeName = nil;
#endif

    return typeName;
}

- (NSString *)parseIdentifier;
{
    NSString *result = nil;

    if (lookahead == TK_IDENTIFIER) {
        result = [lexer lexText];
        [self match:TK_IDENTIFIER];
    }

    return result;
}

- (NSString *)parseNumber;
{
    if (lookahead == TK_NUMBER) {
        NSString *result;

        result = [lexer lexText];
        [self match:TK_NUMBER];
        return result;
    }

    return nil;
}

- (BOOL)isTokenInModifierSet:(int)aToken;
{
    if (aToken == 'r'
        || aToken == 'n'
        || aToken == 'N'
        || aToken == 'o'
        || aToken == 'O'
        || aToken == 'R'
        || aToken == 'V')
        return YES;

    return NO;
}

- (BOOL)isTokenInSimpleTypeSet:(int)aToken;
{
    if (aToken == 'c'
        || aToken == 'i'
        || aToken == 's'
        || aToken == 'l'
        || aToken == 'q'
        || aToken == 'C'
        || aToken == 'I'
        || aToken == 'S'
        || aToken == 'L'
        || aToken == 'Q'
        || aToken == 'f'
        || aToken == 'd'
        || aToken == 'B'
        || aToken == 'v'
        || aToken == '*'
        || aToken == '#'
        || aToken == ':'
        || aToken == '%'
        || aToken == '?')
        return YES;

    return NO;
}

- (BOOL)isTokenInTypeSet:(int)aToken;
{
    if ([self isTokenInModifierSet:aToken] == YES
        || [self isTokenInSimpleTypeSet:aToken] == YES
        || aToken == '^'
        || aToken == 'b'
        || aToken == '@'
        || aToken == '{'
        || aToken == '('
        || aToken == '[')
        return YES;

    return NO;
}

- (BOOL)isTokenInTypeStartSet:(int)aToken;
{
    if (aToken == 'r'
        || aToken == 'n'
        || aToken == 'N'
        || aToken == 'o'
        || aToken == 'O'
        || aToken == 'R'
        || aToken == 'V'
        || aToken == '^'
        || aToken == 'b'
        || aToken == '@'
        || aToken == '{'
        || aToken == '('
        || aToken == '['
        || [self isTokenInSimpleTypeSet:aToken] == YES)
        return YES;

    return NO;
}

@end
