//
// $Id: datatypes.m,v 1.4 1998/07/26 00:08:51 nygard Exp $
//

//
//  This file is a part of class-dump v2, a utility for examining the
//  Objective-C segment of Mach-O files.
//  Copyright (C) 1997  Steve Nygard
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//  You may contact the author by:
//     e-mail:  nygard@telusplanet.net
//

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#if NS_TARGET_MAJOR >= 4
#import <Foundation/Foundation.h>
#else
#import <foundation/NSString.h>
#import <foundation/NSUtilities.h>

@interface NSString (Foundation4Compatibility)
+ string;
@end

@implementation NSString (Foundation4Compatibility)
+ string { return [[self class] stringWithCapacity:1]; }
@end
#endif

#include "datatypes.h"
#include "gram.h"

NSString *string_indent_to_level (int level);
NSString *string_from_members (struct my_objc_type *t, int level);
NSString *string_from_simple_type (char c);
NSString *string_from_type (struct my_objc_type *t, NSString *inner, int expand, int level);

// Not ^ or b
static char *simple_types = "cislqCISLQfdv*#:%?rnNoORV";

static char *simple_type_names[] =
{
    "char",
    "int",
    "short",
    "long",
    "long long",
    "unsigned char",
    "unsigned int",
    "unsigned short",
    "unsigned long",
    "unsigned long long",
    "float",
    "double",
    "void",
    "STR",
    "Class",
    "SEL",
    "NXAtom",
    "UNKNOWN",
    "const",
    "in",
    "inout",
    "out",
    "bycopy",
    "byref",
    "oneway"
};

struct my_objc_type *allocated_types = NULL;
struct method_type *allocated_methods = NULL;

//======================================================================
// Type creation functions
//======================================================================

struct my_objc_type *create_empty_type (void)
{
    struct my_objc_type *tmp = malloc (sizeof (struct my_objc_type));

    assert (tmp != NULL);

    tmp->link = allocated_types;
    allocated_types = tmp;

    tmp->subtype = NULL;
    tmp->next = NULL;
    tmp->type = 0;
    tmp->var_name = NULL;
    tmp->type_name = NULL;

    return tmp;
}

struct my_objc_type *create_simple_type (int type)
{
    struct my_objc_type *t = create_empty_type ();

    extern int char_star_flag;

    if (char_star_flag == 1 && type == '*')
    {
        t->type = '^';
        t->subtype = create_simple_type ('c');
    }
    else
    {
        t->type = type;
    }

    return t;
}

struct my_objc_type *create_id_type (char *name)
{
    struct my_objc_type *t = create_empty_type ();

    t->type_name = name;

    if (name != NULL)
    {
        t->type = T_NAMED_OBJECT;
        return create_pointer_type (t);
    }
    else
    {
        t->type = '@';
    }

    return t;
}

struct my_objc_type *create_struct_type (char *name, struct my_objc_type *members)
{
    struct my_objc_type *t = create_empty_type ();

    t->type = '{';
    t->type_name = name;
    t->subtype = members;

    return t;
}

struct my_objc_type *create_union_type (struct my_objc_type *members, char *type_name)
{
    struct my_objc_type *t = create_empty_type ();

    t->type = '(';
    t->subtype = members;
    t->type_name = type_name;

    return t;
}

struct my_objc_type *create_bitfield_type (char *size)
{
    struct my_objc_type *t = create_empty_type ();

    t->type = 'b';
    t->bitfield_size = size;

    return t;
}

struct my_objc_type *create_array_type (char *count, struct my_objc_type *type)
{
    struct my_objc_type *t = create_empty_type ();

    t->type = '[';
    t->array_size = count;
    t->subtype = type;

    return t;
}

struct my_objc_type *create_pointer_type (struct my_objc_type *type)
{
    struct my_objc_type *t = create_empty_type ();

    t->type = '^';
    t->subtype = type;

    return t;
}

struct my_objc_type *create_modified_type (int modifier, struct my_objc_type *type)
{
    struct my_objc_type *t = create_empty_type ();

    t->type = modifier;
    t->subtype = type;

    return t;
}

//======================================================================
// Method creation functions
//======================================================================

struct method_type *create_method_type (struct my_objc_type *t, char *name)
{
    struct method_type *tmp = malloc (sizeof (struct method_type));

    assert (tmp != NULL);

    tmp->link = allocated_methods;
    allocated_methods = tmp;

    tmp->next = NULL;
    tmp->name = name;
    tmp->type = t;

    return tmp;
}

//======================================================================
// Misc functions
//======================================================================

struct my_objc_type *reverse_types (struct my_objc_type *t)
{
    struct my_objc_type *head = NULL;
    struct my_objc_type *tmp;

    while (t != NULL)
    {
        tmp = t;
        t = t->next;
        tmp->next = head;
        head = tmp;
    }

    return head;
}

struct method_type *reverse_method_types (struct method_type *m)
{
    struct method_type *head = NULL;
    struct method_type *tmp;

    while (m != NULL)
    {
        tmp = m;
        m = m->next;
        tmp->next = head;
        head = tmp;
    }

    return head;
}

//======================================================================
// Display functions
//======================================================================

void indent_to_level (int level)
{
    int l;

    for (l = 0; l < level; l++)
        printf ("  ");
}

NSString *string_indent_to_level (int level)
{
    NSMutableString *str;
    int l;

    str = [NSMutableString string];
    for (l = 0; l < level; l++)
        [str appendString:@"  "];
    
    return str;
}

NSString *string_from_members (struct my_objc_type *t, int level)
{
    NSMutableString *str;

    str = [NSMutableString string];
    
    while (t != NULL)
    {
        [str appendString:string_indent_to_level (level)];
        [str appendString:string_from_type (t, nil, 1, level)];
        [str appendString:@";\n"];
        t = t->next;
    }

    return str;
}

NSString *string_from_simple_type (char c)
{
    char *ptr = strchr (simple_types, c);
    NSString *str = nil;

    if (ptr != NULL)
        str = [NSString stringWithFormat:@"%s", simple_type_names[ ptr - simple_types ]];
    else
        NSLog (@"Unknown simple type '%c'", c);

    return str;
}

NSString *string_from_type (struct my_objc_type *t, NSString *inner, int expand, int level)
{
    NSString *tmp;
    NSString *name, *type_name;

    if (t == NULL)
        return inner;

    if (inner == nil)
        inner = @"";

    //NSLog (@"sft: '%c', inner: '%@'", t->type, inner);

    switch (t->type)
    {
      case T_NAMED_OBJECT:
          if (t->var_name == NULL)
              name = @"";
          else
              name = [NSString stringWithFormat:@" %s", t->var_name];
          
          tmp = [NSString stringWithFormat:@"%s%@ %@", t->type_name, name, inner]; // We always have a pointer to this type
          break;
          
      case '@':
          if (t->var_name == NULL)
              name = @"";
          else
              name = [NSString stringWithFormat:@" %s", t->var_name];
          
          if ([inner length] > 0)
              tmp = [NSString stringWithFormat:@"id%@ %@", name, inner];
          else
              tmp = [NSString stringWithFormat:@"id%@%@", name, inner];
          break;

      case 'b':
          if (t->var_name == NULL)
              name = @"";
          else
              name = [NSString stringWithFormat:@"%s", t->var_name];

          tmp = [NSString stringWithFormat:@"int %@:%s%@", name, t->bitfield_size, inner];
          break;

      case '[':
          if (t->var_name == NULL)
              name = @"";
          else
              name = [NSString stringWithFormat:@"%s", t->var_name];

          tmp = [NSString stringWithFormat:@"%@%@[%s]", inner, name, t->array_size];
          tmp = string_from_type (t->subtype, tmp, expand, level);
          break;

      case '(':
          if (t->var_name == NULL)
              name = @"";
          else
              name = [NSString stringWithFormat:@"%s", t->var_name];

          if (t->type_name == NULL)
              type_name = @"";
          else
              type_name = [NSString stringWithFormat:@" %s", t->type_name];

          tmp = [NSString stringWithFormat:@"union%@", type_name];
          if (expand == 1 && t->subtype != NULL)
          {
              tmp = [NSString stringWithFormat:@"%@ {\n%@%@}", tmp, string_from_members (t->subtype, level + 1),
                              string_indent_to_level (level)];
          }

          if ([inner length] > 0 || [name length] > 0)
          {
              tmp = [NSString stringWithFormat:@"%@ %@%@", tmp, inner, name];
          }
          break;

      case '{':
          if (t->var_name == NULL)
              name = @"";
          else
              name = [NSString stringWithFormat:@"%s", t->var_name];

          if (t->type_name == NULL)
              type_name = @"";
          else
              type_name = [NSString stringWithFormat:@" %s", t->type_name];

          tmp = [NSString stringWithFormat:@"struct%@", type_name];
          if (expand == 1 && t->subtype != NULL)
          {
              tmp = [NSString stringWithFormat:@"%@ {\n%@%@}", tmp, string_from_members (t->subtype, level + 1),
                              string_indent_to_level (level)];
          }

          if ([inner length] > 0 || [name length] > 0)
          {
              tmp = [NSString stringWithFormat:@"%@ %@%@", tmp, inner, name];
          }
          break;

      case '^':
          if (t->var_name == NULL)
              name = @"";
          else
              name = [NSString stringWithFormat:@"%s", t->var_name];

          if (t->subtype != NULL && t->subtype->type == '[')
              tmp = [NSString stringWithFormat:@"(*%@%@)", inner, name];
          else
              tmp = [NSString stringWithFormat:@"*%@%@", name, inner];

          tmp = string_from_type (t->subtype, tmp, expand, level);
          break;

      case 'r':
      case 'n':
      case 'N':
      case 'o':
      case 'O':
      case 'R':
      case 'V':
          tmp = string_from_type (t->subtype, inner, expand, level);
          tmp = [NSString stringWithFormat:@"%@ %@", string_from_simple_type (t->type), tmp];
          break;

      default:
          if (t->var_name == NULL)
              name = @"";
          else
              name = [NSString stringWithFormat:@"%s", t->var_name];
          
          if ([name length] == 0 && [inner length] == 0)
              tmp = string_from_simple_type (t->type);
          else
              tmp = [NSString stringWithFormat:@"%@ %@%@", string_from_simple_type (t->type), name, inner];
          break;
    }

    return tmp;
}

void print_type (struct my_objc_type *t, int expand, int level)
{
    NSString *str;

    str = string_from_type (t, nil, expand, level);

    printf ("%s", [str cString]);
}

void print_method (char method_type, const char *method_name, struct method_type *m)
{
    extern int expand_structures_flag;
    int l;
    BOOL noMoreTypes;

    printf ("%c ", method_type);
    if (m == NULL)
    {
        printf ("%s; /* Error: No method types. */", method_name);
        return;
    }

    noMoreTypes = NO;

    if (!IS_ID (m->type))
    {
        printf ("(");
        print_type (m->type, expand_structures_flag, 0);
        printf (")");
    }

    for (l = 0; l < 3 && m != NULL; l++)
        m = m->next;

    while (*method_name != '\0')
    {
        while (*method_name != '\0' && *method_name != ':')
        {
            putchar (*method_name);
            method_name++;
        }

        if (*method_name == ':')
        {
            printf (":");
            if (m == NULL)
            {
                noMoreTypes = YES;
                method_name++;
            }
            else
            {
                if (!IS_ID (m->type))
                {
                    printf ("(");
                    print_type (m->type, expand_structures_flag, 0);
                    printf (")");
                }
                printf ("fp%s", m->name);
                method_name++;
                if (*method_name != '\0' && *method_name != ':')
                    printf (" ");
                m = m->next;
            }
        }
    }
    printf (";");

    if (noMoreTypes == YES)
    {
        printf (" /* Error: Ran out of types for this method. */");
    }
}

//======================================================================

void free_objc_type (struct my_objc_type *t)
{
    struct my_objc_type *tmp;

    while (t != NULL)
    {
        tmp = t;
        t = t->next;

        if (tmp->var_name != NULL)
            free (tmp->var_name);
        if (tmp->type_name != NULL)
            free (tmp->type_name);
        if (tmp->subtype != NULL)
            free_objc_type (tmp->subtype);
    }
}

void free_method_type (struct method_type *m)
{
    struct method_type *tmp;

    while (m != NULL)
    {
        tmp = m;
        m = m->next;

        if (tmp->name != NULL)
            free (tmp->name);
        free_objc_type (tmp->type);
    }
}

//======================================================================

void free_allocated_types (void)
{
    struct my_objc_type *tmp;

    while (allocated_types != NULL)
    {
        tmp = allocated_types;
        allocated_types = allocated_types->link;

        if (tmp->var_name != NULL)
            free (tmp->var_name);
        if (tmp->type_name != NULL)
            free (tmp->type_name);
    }
}

void free_allocated_methods (void)
{
    struct method_type *tmp;

    while (allocated_methods != NULL)
    {
        tmp = allocated_methods;
        allocated_methods = allocated_methods->link;

        if (tmp->name != NULL)
            free (tmp->name);
    }
}
