/* HeaderParser: parse useful info in an objective-C header file -*-objc-*-

   Copyright (C) 2000, 2001 Free Software Foundation, Inc.

   Author:  Nicola Pero <nicola@brainstorm.co.uk>
   Date: August 2000, July 2001
   
   This file is part of JIGS, the GNUstep Java Interface.
   
   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. */

#include "WCHeaderParser.h"
#include "WCLibrary.h"

static NSCharacterSet *whiteSpace = nil;
static NSCharacterSet *openBracket = nil;
static NSCharacterSet *closeBracket = nil;
static NSCharacterSet *plusMinusOrEndClass = nil;
static NSCharacterSet *argOrEndMethod = nil;
static NSCharacterSet *endMethod = nil;
static NSCharacterSet *whiteSpaceOrEndMethod = nil;
static NSCharacterSet *whiteSpaceOrAtSymbol = nil;
static NSCharacterSet *whiteSpaceOrBeginEnum = nil;
static NSCharacterSet *whiteSpaceOrOpenParenthesis = nil;
static NSCharacterSet *whiteSpaceOrPointerSymbol = nil;
static NSCharacterSet *endEnumBodyKey = nil;
static NSCharacterSet *endEnumBodyValue = nil;
static NSCharacterSet *atSymbol = nil;
static NSCharacterSet *endClassName = nil;
static NSCharacterSet *subclassSymbol = nil;
static NSCharacterSet *pointerSymbol = nil;

static inline void skipParenthesisBlockIfAny (NSScanner *scanner)
{
  /* Skip spaces */
  [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
  
  /* Look for the closing bracket now */
  if ([scanner scanCharactersFromSet: openBracket intoString: NULL] == YES)
    {
      [scanner scanUpToCharactersFromSet: closeBracket  intoString: NULL];
      [scanner scanCharactersFromSet: closeBracket intoString: NULL];
    }
}

static inline void skipObjcType (NSScanner *scanner)
{
  skipParenthesisBlockIfAny(scanner);
}


static inline NSArray * getPropertyAttributes (NSScanner * scanner)
{
  NSString * attributesList = @"";

  /* Skip spaces */
  [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
  
  /* Look for the closing bracket now */
  if ([scanner scanCharactersFromSet: openBracket intoString: NULL] == YES)
    {
      [scanner scanUpToCharactersFromSet: closeBracket  intoString: &attributesList];
      [scanner scanCharactersFromSet: closeBracket intoString: NULL];
    }

	NSArray * attributes = [attributesList componentsSeparatedByString:@","];
	NSMutableArray * trimmed = [ NSMutableArray arrayWithCapacity: [attributes count] ];
	int i, count = [attributes count];
	for (i = 0; i < count; i++)
    {
			[ trimmed addObject: [ [attributes objectAtIndex: i] stringByTrimmingSpaces ] ];
		}
  return trimmed;
}

static inline NSString * getPropertyType (NSScanner *scanner)
{
  /* Skip spaces */
  [scanner scanCharactersFromSet: whiteSpace intoString: NULL];

  /* Capture the property type, which may be a pointer */
  NSString * type;
  [scanner scanUpToCharactersFromSet: whiteSpaceOrPointerSymbol  intoString: &type];
  if ([scanner scanCharactersFromSet: pointerSymbol intoString: NULL] == YES)
    {
      [scanner scanCharactersFromSet: pointerSymbol intoString: NULL];
      return [type stringByAppendingFormat: @"*"];
    }

  return type;
}


/* We remove any space character from the string */
static NSString *trimString (NSString *string)
{
  NSString *result;
  int i, count = [string length];
  unichar original[count];
  unichar trimmed[count];
  int charsCopied = 0;

  [string getCharacters: original];
  
  for (i = 0; i < count; i++)
    {
      unichar c = original[i];
      
      if (c != ' '  &&  c != '\t'  &&  c != '\n'  &&  c != '\r')
	{
	  trimmed[charsCopied] = c;
	  charsCopied++;
	}
    }
  
  result = [[NSString alloc] initWithCharacters: trimmed  length: charsCopied];
  AUTORELEASE (result);
  
  return result;    
}

@interface WCHeaderParser (Internals)
- (void) parseObjectiveC;
- (void) parseC;
- (NSDictionary *) parseEnumBodyUsingScanner: (NSScanner *)scanner;
- (void) recordEnumeration: (NSString *)enumIdentifier
	    withDictionary: (NSDictionary *)enumDictionary;
@end

@implementation WCHeaderParser
+ (void) initialize
{
  if (self == [WCHeaderParser class])
    {
      NSMutableCharacterSet *tmpSet;

      whiteSpace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
      openBracket = [NSCharacterSet characterSetWithCharactersInString: @"("];
      closeBracket = [NSCharacterSet characterSetWithCharactersInString: @")"];

      plusMinusOrEndClass = [NSCharacterSet 
                              characterSetWithCharactersInString: @"+-@"];
      endClassName = [NSCharacterSet 
                              characterSetWithCharactersInString: @"+-@;"];
      argOrEndMethod = [NSCharacterSet characterSetWithCharactersInString: @":;"];
      endMethod = [NSCharacterSet characterSetWithCharactersInString: @";"];
      endEnumBodyKey = [NSCharacterSet characterSetWithCharactersInString: 
					 @"=,}"];
      endEnumBodyValue = [NSCharacterSet characterSetWithCharactersInString: 
					   @",}"];
      atSymbol = [NSCharacterSet characterSetWithCharactersInString: @"@"];
      subclassSymbol = [NSCharacterSet characterSetWithCharactersInString: @":"];
      pointerSymbol = [NSCharacterSet characterSetWithCharactersInString: @"*"];

      tmpSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
      [tmpSet addCharactersInString: @";"];
      whiteSpaceOrEndMethod = [tmpSet copy];
      RELEASE (tmpSet);

      tmpSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
      [tmpSet addCharactersInString: @"@"];
      whiteSpaceOrAtSymbol = [tmpSet copy];
      RELEASE (tmpSet);

      tmpSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
      [tmpSet addCharactersInString: @"{"];
      whiteSpaceOrBeginEnum = [tmpSet copy];
      RELEASE (tmpSet);

      tmpSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
      [tmpSet addCharactersInString: @"(<{+-;:@"];
      endClassName = [tmpSet copy];
      RELEASE (tmpSet);

      tmpSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
      [tmpSet addCharactersInString: @"("];
      whiteSpaceOrOpenParenthesis = [tmpSet copy];
      RELEASE (tmpSet);

      tmpSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
      [tmpSet addCharactersInString: @"*"];
      whiteSpaceOrPointerSymbol = [tmpSet copy];
      RELEASE (tmpSet);

      RETAIN (whiteSpace);
      RETAIN (openBracket);
      RETAIN (closeBracket);
      RETAIN (plusMinusOrEndClass);
      RETAIN (endClassName);
      RETAIN (argOrEndMethod);
      RETAIN (endMethod);
      RETAIN (endEnumBodyKey);
      RETAIN (endEnumBodyValue);
      RETAIN (atSymbol);
      RETAIN (subclassSymbol);
      RETAIN (pointerSymbol);
    }
}

/* This facility is used by WCClass.  It's here because it is a
   separate facility and it makes more sense to put it here (in a
   parser class) than elsewhere. */
+ (NSString *) methodNameFromMethodDeclaration: (NSString *)declaration
{
  NSScanner *scanner = [NSScanner scannerWithString: declaration];
  NSString *string;
  NSMutableString *methodName;
  int argument;

  /* Skip spaces */
  [scanner scanCharactersFromSet: whiteSpace intoString: NULL];

  /* Read the +/- */
  [scanner scanCharactersFromSet: plusMinusOrEndClass intoString: &string];

  if (([string isEqualToString: @"+"] == NO) 
      && ([string isEqualToString: @"-"] == NO))
    {
      [NSException raise: @"WCHeaderParserException"
		   format: @"A method declaration must begin with either + or -, while `%@' does not", 
		   declaration];
    }

  /* Skip return type */
  skipObjcType (scanner);
  methodName = [NSMutableString new];
  argument = 0;
  while (1)
    {
      if ([scanner isAtEnd])
	{
	  break;
	}
      
      /* Skip spaces */
      [scanner scanCharactersFromSet: whiteSpace intoString: NULL];

      if ([scanner isAtEnd])
	{
	  break;
	}
      
      /* Read method name component */
      [scanner scanUpToCharactersFromSet: argOrEndMethod  
	       intoString: &string];
      
      [methodName appendString: [string stringByTrimmingSpaces]];
      
      [scanner scanCharactersFromSet: argOrEndMethod intoString: &string];
      if ((argument == 0) && [string isEqualToString: @";"])
	{
	  break;
	}
      else if ([string isEqualToString: @":"])
	{
	  [methodName appendString: @":"];
	}
      else /* Something's wrong ! */
	{
	  [NSException raise: @"WCHeaderParserException"
		       format: @"Error parsing method declaration `%@'", 
		       declaration];
	}
      
      /* Skip argument type */
      skipObjcType (scanner);
      
      /* Skip spaces */
      [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
      
      /* Skip argument name */
      [scanner scanUpToCharactersFromSet: whiteSpaceOrEndMethod 
	       intoString: &string];
      
      /* Skip spaces */
      [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
      
      /* Now check if next character it is a ; */
      if ([scanner scanCharactersFromSet: endMethod 
		   intoString: &string] == YES)
	{
	  break;
	}
      argument++;
    }

  return methodName;
}

+ (id) newWithHeaderFile: (NSString *)fileName
{
  return [[self alloc] initWithHeaderFile: fileName];
}

- (id) initWithHeaderFile: (NSString *)fileName
{
  NSString *string;

  string = [NSString stringWithContentsOfFile: fileName];
  if (string == nil)
    {
      return nil;
    }

  ASSIGN (header, string);

  classMethods = [NSMutableDictionary new];
  instanceMethods = [NSMutableDictionary new];
  enumerations = [NSMutableDictionary new];
  unresolvedTypedefs = [NSMutableDictionary new];

  [self parseObjectiveC];
  [self parseC];

  return self;
}

/* The following methods are internals - are called by init */
- (void) parseObjectiveC
{
  NSString *string;
  NSScanner *scanner;
  NSMutableString *methodName;
  int startIndex;
  BOOL isClassMethod = NO;

  if ([WCLibrary verboseOutput] == YES)
    {
      printf ("Parsing header file for Objective-C classes and methods...\n");
    }

  scanner = [NSScanner scannerWithString: header];

  /* Loop on the class interfaces */
  while (1)
    {
      if ([scanner isAtEnd] == YES)
	{
	  break;
	}

      /* Scan up to next class interface declaration */
      [scanner scanUpToCharactersFromSet: atSymbol intoString: NULL];
      
      [scanner scanUpToCharactersFromSet: whiteSpace intoString: &string];
      
      if (([string isEqualToString: @"@interface"] == NO) 
	  && ([string isEqualToString: @"@protocol"] == NO))
	{ 
	  continue;
	}
     
      if ([WCLibrary verboseOutput] == YES)
        {
          fflush (stdout);
        }

      NSString *classname;
      [scanner scanUpToCharactersFromSet: endClassName 
               intoString: &classname];

      if ([WCLibrary verboseOutput] == YES)
        {
          printf("Found class : %s\n", [classname cString]);
        }

      /* Now check if next character it is a ; */
      if ([scanner scanCharactersFromSet: endMethod 
                   intoString: &string] == YES)
        {
          // With stumbled on a forward declaration. Skip it. It has no method we can use.
          continue;
        }

      /* Loop on the list of methods */
      while (1) 
        {
          int argument;
          
          /* Look for method declaration */
          [scanner scanUpToCharactersFromSet: plusMinusOrEndClass 
                   intoString: NULL];
          /* Save the starting index */
          startIndex = [scanner scanLocation];
          /* Read the +/- */
          [scanner scanCharactersFromSet: plusMinusOrEndClass 
                   intoString: &string];
          /* @end */
          if ([string isEqualToString: @"@"] == YES)
            {
              [scanner scanUpToCharactersFromSet: whiteSpaceOrOpenParenthesis 
                       intoString: &string];
               if ([string isEqualToString: @"property"] == YES)
                {
                  NSArray * attributes = getPropertyAttributes(scanner);
                  NSString * propertyType = getPropertyType(scanner);

                  NSString * propertyName;
                  [scanner scanUpToString: @";"
                    intoString: &propertyName];

                  // Create getter
                  NSMutableString * accessorName = [NSString stringWithFormat: @"-(%@) %@;", propertyType, propertyName];

                  [instanceMethods setObject: accessorName  forKey: propertyName];

                  if ([WCLibrary verboseOutput] == YES)
                    {
                      printf(" %s for property %s\n", [accessorName cString], [propertyName cString]);
                    }

                  // Create setter for property that can be assigned
                  if (! [attributes containsObject: @"readonly"])
                    {
                      NSString * capital = [[propertyName substringToIndex:1] uppercaseString];
                      NSString * rest = [propertyName substringFromIndex:1];
                      NSString * methodName = [NSString stringWithFormat: @"set%@%@:", capital, rest];
                      NSString * accessorName = [NSString stringWithFormat: @"-(void) %@(%@) %@;", methodName, propertyType, propertyName];

                      [instanceMethods setObject: accessorName  forKey: methodName];

                      if ([WCLibrary verboseOutput] == YES)
                        {
                          printf(" %s for property %s\n", [accessorName cString], [propertyName cString]);
                        }
                    }

                  continue;
                }
              else if (([string isEqualToString: @"end"] == YES))
                {
                  methodName = nil;
                  if ([WCLibrary verboseOutput] == YES)
                    {
                      printf("\n@%s - end of class\n", [string cString]);
                    }
                  break;
                }
              else
                {
                  continue;
                }
            }
          if ([string isEqualToString: @"+"] == YES)
            {
              isClassMethod = YES;
            }
          else if ([string isEqualToString: @"-"] == YES)
            {
              isClassMethod = NO;
            }
          else
            {
              [NSException raise: @"WCHeaderParserException"
                           format: @"Could not parse the header file"];
            }
          /* Skip return type */
          skipObjcType (scanner);
          methodName = [NSMutableString new];
          argument = 0;
          while (1)
            {
              /* Skip spaces */
              [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
              
              /* Read method name component */
              [scanner scanUpToCharactersFromSet: argOrEndMethod  
                       intoString: &string];

              [methodName appendString: [string stringByTrimmingSpaces]];
              
              [scanner scanCharactersFromSet: argOrEndMethod 
                       intoString: &string];
              if (argument == 0)
                {
                  if ([string isEqualToString: @";"])
                    {
                      break;
                    }
                }
              
              if ([string isEqualToString: @":"])
                {
                  [methodName appendString: @":"];
                }
              else
                {
                  methodName = nil;
                  break;
                }

              /* Skip argument type */
              skipObjcType (scanner);
      
	      /* Skip spaces */
	      [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
      
	      /* Skip argument name */
	      [scanner scanUpToCharactersFromSet: whiteSpaceOrEndMethod 
		       intoString: &string];

	      /* Skip spaces */
	      [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
      
              /* Now check if next character it is a ; */
              if ([scanner scanCharactersFromSet: endMethod 
                           intoString: &string] == YES)
                {
                  if ([string isEqualToString: @";"])
                    {
                      break;
                    }
                  else
                    {

                      [NSException raise: @"WCHeaderParserException"
                                   format: @"Error while parsing the header file : found '%@' instead of ';'", string];
                    }
                }
              argument++;
            }
          if (methodName != nil)
            {
              NSRange methodDeclarationRange;
              int endIndex = [scanner scanLocation];
              
              methodDeclarationRange = NSMakeRange (startIndex, 
                                                    (endIndex - startIndex));
              
              
              string = [header substringWithRange: methodDeclarationRange];
              
              if (isClassMethod == YES)
                {
                  [classMethods setObject: string  forKey: methodName];
                }
              else
                {
                  [instanceMethods setObject: string  forKey: methodName];
                }

              if ([WCLibrary verboseOutput] == YES)
                {
                  printf(" %s Signature for JIGS files : %s", [string cString], [methodName cString]);
                }
            }
        }
    }

  if ([WCLibrary verboseOutput] == YES)
    {
      printf ("\n");
    }
}

- (void) parseC
{
  /* scopeDepth is the number of pending open '{' (or '(') that we
     encountered.  We want to ignore anything which is not in the
     top-most scope, which means we ignore anything found when
     (scopeDepth > 0) [except of course the closing } or ) which
     reduces scopeDepth]
     
     We also need special code to ignore anything inside string
     literals, and preprocessor comments (lines beginning with #),
     even if we try to avoid preprocessor comments completely by
     passing -P to the preprocessor. */
  int scopeDepth = 0;
  int length = [header length];
  /* we parse most of the header accessing the unichars directly */
  unichar *buffer = malloc (sizeof (unichar) * length);
  /* location is the next character we will read */
  int location;
  /* When we find something interesting, then we use a scanner to do 
     the high-level parsing of the enum declarations */
  NSScanner *scanner;
  
  if ([WCLibrary verboseOutput] == YES)
    {
      printf ("Parsing header file for C enumerations...\n");
    }
  
  /* Get the header file as a unicode buffer of chars */
  [header getCharacters: buffer  range: NSMakeRange (0, length)];
  location = 0;
  
  /* And prepare a reusable scanner for when we find something
     interesting in the header. */
  scanner = [NSScanner scannerWithString: header];
  /* FIXME the following one */
  [scanner setCharactersToBeSkipped: nil];
  
#define checkThatNextCharIs(X) \
if (!(location < length  &&  buffer[location] == X)) {break;} \
else {location++;}

#define skipSpaces \
[scanner scanCharactersFromSet: whiteSpace intoString: NULL]; \
location = [scanner scanLocation]; \
if ([scanner isAtEnd]) break;

  /* Ignore first line if it is a preprocessor comment */
  if (buffer[0] == '#')
    {
      while (location < length  &&  buffer[location] != '\n')
	{
	  location++;
	}
    }
  
  /* Loop on the chars - we parse the stuff quickly to spot out the
     relevant bits.  Then we use higher-level NSScanner stuff to
     analyze the relevant bits when we find them. */
  while (location < length)
    {
      unichar c = buffer[location];

      location++;
      switch (c)
	{
	case '\n':
	  {
	    if (location < length  &&  buffer[location] == '#')
	      {
		/* Line info comment from the preprocessor - Ignore to end
		   of line */
		while (location < length  &&  buffer[location] != '\n')
		  {
		    location++;
		  }
	      }
	    break;
	  }
	case '"':
	  {
	    /* It's important that we recognize strings because we
	       don't want to be confused by things like `enum' inside
	       a string, or more commonly, open or close brackets
	       inside a string */
	    BOOL inString = YES;

	    while (inString  &&  location < length)
	      {
		switch (buffer[location])
		  {
		  case '"':
		    {
		      inString = NO;
		      break;
		    }
		  case '\\':
		    {
		      /* Ignore next char */
		      location++;
		      break;
		    }
		  }
		location++;
	      }
	    break;
	  }
	case '{':
	case '(':
	  {
	    scopeDepth++;
	    break;
	  }
	case '}':
	case ')':
	  {
	    scopeDepth--;
	    if (scopeDepth < 0)
	      {
		printf ("Error in parsing C code - found more closing brackets than opening ones!\n"); 
		exit (1);
	      }
	    break;
	  }
	case 'e':
	  {
	    /* Ignore if not at the topmost scope */
	    if (scopeDepth != 0)
	      {
		break;
	      }
	    checkThatNextCharIs ('n');
	    checkThatNextCharIs ('u');
	    checkThatNextCharIs ('m');

	    /* We now check that next char is some sort of whitespace */
	    if (!(location < length  &&  (buffer[location] == ' '
					  || buffer[location] == '\t'
					  || buffer[location] == '\n'
					  || buffer[location] == '\r')))
	      {
		break;
	      }
	    /* Yes - it is an enum at level 0 !! - process it ! 

	       NB: Here we *only* process things like 

	       enum _NSRectEdge
	       {
	         NSMinXEdge,
	         NSMinYEdge,
	         NSMaxXEdge,
	         NSMaxYEdge
	       };

	       Anything involving a typedef is instead processes when
	       `typedef enum' is encountered.  */
	    {
	      NSString *enumIdentifier;
	      NSDictionary *enumDictionary;
	      
	      /* Now that we have spotted something interesting (which
		 is going to be quite a rare thing) we sort of enter
		 into lossy high-level code programming style -
		 prepare the scanner */
	      [scanner setScanLocation: location];
	      
	      skipSpaces;
	      
	      /* TODO: Skip __attribute__((xxx)) if any */
	      
	      /* Check what follows - if it is a '{', then no luck
		 - quit to the main loop. */
	      if (location < length  &&  buffer[location] == '{')
		{
		  break;
		}
	      
	      /* It is an identifier - read it */
	      [scanner scanUpToCharactersFromSet: whiteSpace  
		       intoString: &enumIdentifier];
	      location = [scanner scanLocation];
	      if ([scanner isAtEnd])
		{
		  break;
		}
	      
	      skipSpaces;
	      
	      if (location < length  &&  buffer[location] != '{')
		{
		  /* Ack !  It is just a variable or a function returning 
		     an enum - not a declaration :-( */
		  break;
		}
	      
	      enumDictionary = [self parseEnumBodyUsingScanner: scanner];
	      
	      /* Now, save the enumDictionary for the enumIdentifier
		 in our enumeration dictionary.  Save it with full
		 correct format `enum _NSRectEdge' to prevent
		 confusion. */
	      enumIdentifier = [NSString stringWithFormat: @"enum %@", 
					 enumIdentifier];
	      [self recordEnumeration: enumIdentifier
		    withDictionary: enumDictionary];
	      location = [scanner scanLocation];
	    }
	    break;
	  }
	case 't':
	  {
	    BOOL foundSpaces = NO;

	    if (scopeDepth != 0)
	      {
		break;
	      }
	    /* Might be the beginning of a `typedef enum'
	       declaration */
	    checkThatNextCharIs ('y');
	    checkThatNextCharIs ('p');
	    checkThatNextCharIs ('e');
	    checkThatNextCharIs ('d');
	    checkThatNextCharIs ('e');
	    checkThatNextCharIs ('f');
	    
	    /* Skip spaces - but there *must* be some */
	    while (location < length && (buffer[location] == ' '
					 || buffer[location] == '\t'
					 || buffer[location] == '\r'
					 || buffer[location] == '\n'))
	      {
		foundSpaces = YES;
		location++;
	      }
	    
	    if (!foundSpaces)
	      {
		break;
	      }

	    checkThatNextCharIs ('e');
	    checkThatNextCharIs ('n');
	    checkThatNextCharIs ('u');
	    checkThatNextCharIs ('m');

	    /* Skip spaces - but there *must* be some */
	    foundSpaces = NO;
	    while (location < length && (buffer[location] == ' '
					 || buffer[location] == '\t'
					 || buffer[location] == '\r'
					 || buffer[location] == '\n'))
	      {
		foundSpaces = YES;
		location++;
	      }
	    
	    if (!foundSpaces)
	      {
		break;
	      }
	    
	    /* We got it :-) now let's process that */
	    {
	      /* Eg, in `typedef enum _NSRectEdge { xxx, yyy } NSRectEdge;'
		 enumIdentifier is `enum _NSRectEdge', while
		 typedefIdentifier is `NSRect' */
	      NSString *enumIdentifier = nil;
	      NSString *typedefIdentifier = nil;
	      NSDictionary *enumDictionary = nil;

	      /* Now that we have spotted something interesting (which
		 is going to be quite a rare thing) we sort of enter
		 into lossy high-level code programming style -
		 prepare the scanner */
	      [scanner setScanLocation: location];
	      
	      skipSpaces;
	      
	      /* TODO: Skip __attribute__((xxx)) if any */
	      
	      /* If now we have a '{', then the enum has no identifier */
	      if (location < length  &&  buffer[location] == '{')
		{
		  enumDictionary = [self parseEnumBodyUsingScanner: scanner];
		}
	      else
		{
		  /* It is an enum identifier - read it */
		  [scanner scanUpToCharactersFromSet: whiteSpaceOrBeginEnum
			   intoString: &enumIdentifier];
		  location = [scanner scanLocation];
		  if ([scanner isAtEnd])
		    {
		      break;
		    }

		  enumIdentifier = [NSString stringWithFormat: @"enum %@", 
					     enumIdentifier];
		  
		  skipSpaces;
		  
		  if (location < length  &&  buffer[location] == '{')
		    {
		      enumDictionary = [self parseEnumBodyUsingScanner:
					       scanner];
		    }
		}

	      location = [scanner scanLocation];

	      /* Now read the typedefIdentifier (which must always be
                 there) */
	      skipSpaces;
	      
	      [scanner scanUpToCharactersFromSet: whiteSpaceOrEndMethod
		       intoString: &typedefIdentifier];
	      
	      /* Now, we look at what we parsed to decide how to store it */
	      
	      if (enumDictionary != nil)
		{
		  [self recordEnumeration: typedefIdentifier
			withDictionary: enumDictionary];
		  
		  if (enumIdentifier != nil)
		    {
		      [self recordEnumeration: enumIdentifier
			    withDictionary: enumDictionary];
		    }
		}
	      else
		{
		  if (enumIdentifier == nil)
		    {
		      printf ("Error - inconsistent typedef for %s\n",
			      [typedefIdentifier lossyCString]);
		    }
		  else
		    {
		      [unresolvedTypedefs setObject: enumIdentifier
					  forKey: typedefIdentifier];
		    }
		}
	      location = [scanner scanLocation];
	    }
	    break;
	  }
	}
    }
  
  free (buffer);
}

/* The following method parses the core of the enum (the part between 
   the {}), returning a dictionary of variableName = variableValue.
   Eg, in the definition 

   `typedef enum _NSComparisonResult 
    {
      NSOrderedAscending = -1, NSOrderedSame, NSOrderedDescending
    } 
    NSComparisonResult;'

    the method would be called to parse

    {
      NSOrderedAscending = -1, NSOrderedSame, NSOrderedDescending
    } 

    and it would return the dictionary

    {
      "NSOrderedAscending" = "-1";
      "NSOrderedSame" = "0";
      "NSOrderedAscending" = "1";
    }
*/
- (NSDictionary *) parseEnumBodyUsingScanner: (NSScanner *)scanner
{
#undef skipSpaces
#define skipSpaces \
[scanner scanCharactersFromSet: whiteSpace intoString: NULL]; \
if ([scanner isAtEnd]) break;

  NSMutableDictionary *enumDictionary;
  //NSString *string;
  NSString *key, *value;
  BOOL done = NO;
  /* Last enum value read, needed to compute next if not specified */
  int lastValue = -1;                   
  NSString *string = [scanner string];
  NSString *tmp;

  enumDictionary = [NSMutableDictionary new];

  /* Skip { */
  [scanner scanString: @"{"  intoString: NULL];
  
  /* We're inside the enum now :-) - loop on declarations */
  while (!done)
    {
      skipSpaces;
      
      /* It is an identifier - read it into the key */
      if (![scanner scanUpToCharactersFromSet: endEnumBodyKey  
		    intoString: &key])
	  {
	      /* Ops - no identifier - should mean it's finished -
                 usually it happens if your last enum entry has got 
		 a comma after it, as in 

		 enum hello { nicola, ettore, };

		 we just try to sneak out */
	      [scanner setScanLocation: ([scanner scanLocation] + 1)];
	      break;
	  }
      if ([scanner isAtEnd])
	{
	  break;
	}

      key = trimString (key);

      skipSpaces;
      
      switch ([string characterAtIndex: [scanner scanLocation]])
	{
	case '=':
	  {
	    /* Wow - this one has got a value */
	    [scanner setScanLocation: ([scanner scanLocation] + 1)];
	    skipSpaces;
	    [scanner scanUpToCharactersFromSet: endEnumBodyValue  
		     intoString: &value];
	    value = trimString (value);
	    if ([scanner isAtEnd])
	      {
		break;
	      }
	    /* Warning - if you have put an expression to compute as
	       value, the following is likely to return 0. */
	    lastValue = [value intValue];
	    
	    [scanner scanCharactersFromSet: endEnumBodyValue
		     intoString: &tmp];
	    if ([tmp isEqualToString: @"}"])
	      {
		done = YES;
	      }
	    break;
	  }
	case '}':
	  {
	    /* End of enum body - mark it as finished, then fall back
               on the code for ',' which computes the value */
	    done = YES;
	  }
	case ',':
	  {
	    /* No value - compute it */
	    [scanner setScanLocation: ([scanner scanLocation] + 1)];
	    lastValue++;
	    value = [NSString stringWithFormat: @"%d", lastValue];
	    break;
	  }

	}
      [enumDictionary setObject: value  forKey: key];
    }
  return enumDictionary;
}

- (void) recordEnumeration: (NSString *)enumIdentifier
	    withDictionary: (NSDictionary *)enumDictionary
{
  NSArray *aliasesToResolv;
  int i, count;

  if ([enumerations objectForKey: enumIdentifier] != nil)
    {
      printf ("WARNING: Found multiple declarations for enumeration %s\n", 
	      [enumIdentifier lossyCString]);
      return;
    }

  if ([WCLibrary verboseOutput] == YES)
    {
      printf ("Recorded enumeration %s\n", [enumIdentifier lossyCString]);
    }
  
  [enumerations setObject: enumDictionary  forKey: enumIdentifier];
  /* Now look on the unresolvedTypedefs dictionary to see if we have
     pending unresolvedTypedefs referring to this enumeration.

     For example, we might be recording now the _NSRectEdge
     enumeration, and the unresolvedTypedefs might be telling us that
     NSRectEdge is a typedef for _NSRectEdge.  In that case, we add
     NSRectEdge to enumerations with the same enumDictionary as
     _NSRectEdge, and remove NSRectEdge from the
     unresolvedTypedefs. */
  aliasesToResolv = [unresolvedTypedefs allKeysForObject: enumIdentifier];
  count = [aliasesToResolv count];
  
  for (i = 0; i < count; i++)
    {
      if ([WCLibrary verboseOutput] == YES)
	{
	  printf ("Recorded enumeration %s\n", 
		  [[aliasesToResolv objectAtIndex: i] lossyCString]);
	}
      [enumerations setObject: enumDictionary
		    forKey: [aliasesToResolv objectAtIndex: i]];
    }

  [unresolvedTypedefs removeObjectsForKeys: aliasesToResolv]; 
}

- (NSString *) declarationOfMethod: (NSString *)objcMethodName
		     isClassMethod: (BOOL)flag
{
  NSString *returnString = nil;

  if (flag == YES)
    {
      returnString = [classMethods objectForKey: objcMethodName];
    }
  else
    {
      returnString = [instanceMethods objectForKey: objcMethodName];
    }
  
  if (returnString == nil)
    {
      [NSException raise: @"WCHeaderParserException"
		   format: @"Could not find method %@ in the header file", 
		   objcMethodName];
    }
  if ([WCLibrary verboseOutput] == YES)
    {
      printf ("Found method %s\n", [returnString cString]);  
    }

  return returnString;
}

- (NSString *) getSuperclassOfClass: (NSString *)objcClassName
{
  NSScanner *scanner;
  NSString *string;
  
  scanner = [NSScanner scannerWithString: header];
  
  /* Loop on the class interfaces */
  while (1)
    {
      if ([scanner isAtEnd] == YES)
	{
	  break;
	}
      
      /* Scan up to next class interface declaration */
      [scanner scanUpToString: @"@interface" intoString: NULL];
      [scanner scanString: @"@interface" intoString: NULL];

      /* Skip spaces */
      [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
      
      /* Read up to the end of class name */
      [scanner scanUpToCharactersFromSet: endClassName intoString: &string];
      
      string = [string stringByTrimmingSpaces];

      if ([string isEqualToString: objcClassName] == NO)
	{
	  continue;
	}

      /* Skip whitespaces */
      [scanner scanCharactersFromSet: whiteSpace intoString: NULL];

      if ([scanner scanCharactersFromSet: subclassSymbol 
		   intoString: &string] == YES)
	{
	  if ([string isEqualToString: @":"])
	    {
	      /* Skip whitespaces */
	      [scanner scanCharactersFromSet: whiteSpace intoString: NULL];
	      /* Read up to the end of superclass name */
	      [scanner scanUpToCharactersFromSet: endClassName 
		       intoString: &string];	      
	      string = [string stringByTrimmingSpaces];
	      if ([string isEqualToString: @""] == NO)
		{
		  if ([WCLibrary verboseOutput] == YES)
		    {
		      printf ("%s inherits from %s\n", [objcClassName cString], 
			      [string cString]);
		    }
		  return string;
		}
	    }
	}
    }
  NSLog (@"Could not find superclass of class %@", objcClassName);
  NSLog (@"Assuming it is NSObject");
  return @"NSObject";
}

- (NSArray *) enumerationTypes
{
  return [enumerations allKeys];
}

- (NSDictionary *) dictionaryForEnumeration: (NSString *)enumeration
{
  return [enumerations objectForKey: enumeration];
}
@end







