/*
  Copyright (C) 2000-2005 SKYRIX Software AG

  This file is part of SOPE.

  SOPE is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  SOPE 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 Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with SOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

#include "WODParser.h"
#include "common.h"

@implementation WODParser

static Class    StrClass    = Nil;
static Class    DictClass   = Nil;
static Class    NumberClass = Nil;
static NSNumber *yesNum     = nil;
static NSNumber *noNum      = nil;
static BOOL     useUTF8     = NO;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];

  StrClass    = [NSString class];
  DictClass   = [NSMutableDictionary class];
  NumberClass = [NSNumber      class];
  
  if (yesNum == nil) yesNum = [[NumberClass numberWithBool:YES] retain];
  if (noNum  == nil) noNum  = [[NumberClass numberWithBool:NO]  retain];
  
  useUTF8 = [ud boolForKey:@"WOParsersUseUTF8"];
}

- (id)initWithHandler:(id<WODParserHandler,NSObject>)_handler {
  ASSIGN(self->callback, _handler);
  return self;
}
- (void)dealloc {
  [self->callback release];
  [super dealloc];
}

/* callbacks */

- (id)associationWithValue:(id)_value {
  return [self->callback parser:self makeAssociationWithValue:_value];
}
- (id)associationWithKeyPath:(NSString *)_keyPath {
  return [self->callback parser:self makeAssociationWithKeyPath:_keyPath];
}

- (id)elementDefinitionForComponent:(NSString *)_cname
  associations:(id)_entry
  elementName:(NSString *)_elemName
{
  return [self->callback parser:self
                         makeDefinitionForComponentNamed:_cname
                         associations:_entry
                         elementName:_elemName];
}

/* parser */

static id _parseProperty(NSZone *_zone, const char *_buffer, unsigned *_idx,
                         unsigned _len, NSException **_exception,
                         BOOL _allowAssoc, id self);
static id _parseWodEntry(NSZone *_zone, const char *_buffer, unsigned *_idx,
                         unsigned _len, NSException **_exception,
                         NSString **_name, NSString **_class,
                         id self);

static NSString *_makeStringForBuffer(const unsigned char *_buf, unsigned _l) {
  // TODO: duplicate code with WOHTMLParser, but probably isn't worth a
  //       separate file
  NSString *r;
  NSData   *data;

  if (_l == 0)
    return @"";
  
  if (!useUTF8)
    return [[StrClass alloc] initWithCString:_buf length:_l];
  
  // Note: we cast the pointer because we are not going to modify _buf for the
  //       duration and we are never going to write the data - should work
  //       with any Foundation, but isn't strictly API compatible
  data = [[NSData alloc] initWithBytesNoCopy:(void *)_buf length:_l 
                         freeWhenDone:NO];
  r = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding];
  [data release];
  return r;
}

- (NSException *)parseDefinitionsFromBuffer:(const char *)_buffer
  length:(unsigned)_len
  mappings:(NSMutableDictionary *)_mappings
{
  NSException *exception     = nil;
  NSString    *elementName   = nil;
  NSString    *componentName = nil;
  unsigned    idx            = 0;
  id          entry          = nil;
  
  [_mappings removeAllObjects];
  
  while ((entry = _parseWodEntry(NULL, _buffer, &idx, _len, &exception,
                                 &elementName, &componentName,
                                 self))) {
    id def;
    
    if (exception) {
      RELEASE(entry);         entry         = nil;
      RELEASE(elementName);   elementName   = nil;
      RELEASE(componentName); componentName = nil;
      break;
    }

    if ([_mappings objectForKey:elementName] != nil)
      NSLog(@"WARNING: duplicate definition of element %@  !", elementName);

    def = [self elementDefinitionForComponent:componentName
                associations:entry
                elementName:elementName];
    
    RELEASE(componentName); componentName = nil;
    RELEASE(entry);         entry         = nil;

    if ((def != nil) && (elementName != nil))
      [_mappings setObject:def forKey:elementName];
#if 0
    NSLog(@"defined element %@ definition=%@", elementName, def);
#endif
    RELEASE(elementName); elementName = nil;
  }

  return exception;
}

/* parsing */

- (NSDictionary *)parseDeclarationData:(NSData *)_decl {
  NSMutableDictionary *defs;
  NSException *ex;
  
  if (![self->callback parser:self willParseDeclarationData:_decl])
    return nil;

  defs = [NSMutableDictionary dictionaryWithCapacity:100];
  
  ex = [self parseDefinitionsFromBuffer:[_decl bytes]
             length:[_decl length]
             mappings:defs];
  
  if (ex)
    [self->callback parser:self failedParsingDeclarationData:_decl exception:ex];
  else {
    [self->callback parser:self finishedParsingDeclarationData:_decl
                    declarations:defs];
  }
  
  return defs;
}


static int _numberOfLines(const char *_buffer, unsigned _lastIdx) {
  register unsigned pos, lineCount = 1;

  for (pos = 0; (pos < _lastIdx) && (_buffer[pos] != '\0'); pos++) {
    if (_buffer[pos] == '\n')
      lineCount++;
  }
  return lineCount;
}

static inline BOOL _isBreakChar(unsigned char _c) {
  switch (_c) {
    case ' ': case '\t': case '\n': case '\r':
    case '=':  case ';':  case ',':
    case '{': case '(':  case '"':  case '<':
    case '.': case ':':
    case ')': case '}':
      return YES;

    default:
      return NO;
  }
}
static inline BOOL _isIdChar(unsigned char _c) {
  return (_isBreakChar(_c) && (_c != '.')) ? NO : YES;
}

static inline int _valueOfHexChar(unsigned char _c) {
  switch (_c) {
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      return (_c - 48); // 0-9 (ascii-char)'0' - 48 => (int)0
      
    case 'A': case 'B': case 'C':
    case 'D': case 'E': case 'F':
      return (_c - 55); // A-F, A=10..F=15, 'A'=65..'F'=70
      
    case 'a': case 'b': case 'c':
    case 'd': case 'e': case 'f':
      return (_c - 87); // a-f, a=10..F=15, 'a'=97..'f'=102

    default:
      return -1;
  }
}
static inline BOOL _isHexDigit(unsigned char _c) {
  switch (_c) {
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
    case 'A': case 'B': case 'C':
    case 'D': case 'E': case 'F':
    case 'a': case 'b': case 'c':
    case 'd': case 'e': case 'f':
      return YES;

    default:
      return NO;
  }
}

static NSException *_makeException(NSException *_exception,
                                   const char *_buffer, unsigned _idx,
                                   unsigned _len, NSString *_text)
{
  NSMutableDictionary *ui = nil;
  NSException *exception = nil;
  int         numLines;
  BOOL        atEof;
  
  numLines   = _numberOfLines(_buffer, _idx);
  atEof      = (_idx >= _len) ? YES : NO;
  
  if (_exception)
    // error resulted from a previous error (exception already set)
    return _exception;

  if (atEof)
    _text = [@"Unexpected end: " stringByAppendingString:[_text stringValue]];
  else {
    _text = [StrClass stringWithFormat:@"Syntax error in line %i: %@",
                        numLines, _text];
  }

  // user info
  ui = [[exception userInfo] mutableCopy];
  if (ui == nil)
    ui = [[DictClass alloc] initWithCapacity:8];

  [ui setObject:[NumberClass numberWithInt:numLines] forKey:@"line"];
  [ui setObject:[NumberClass numberWithInt:_len]     forKey:@"size"];
  [ui setObject:[NumberClass numberWithInt:_idx]     forKey:@"position"];

  if (!atEof && (_idx > 0)) {
    register unsigned pos;
    const char *startPos, *endPos;

    for (pos = _idx; (pos >= 0) && (_buffer[pos] != '\n'); pos--)
      ;
    startPos = &(_buffer[pos + 1]);

    for (pos = _idx; ((pos < _len) && (_buffer[pos] != '\n')); pos++)
      ;
    endPos = &(_buffer[pos - 1]);
    
    if (startPos < endPos) {
      NSString *ll;

      ll = _makeStringForBuffer(startPos, endPos - startPos);
      [ui setObject:ll forKey:@"lastLine"];
      [ll release];
    }
    else {
      NSLog(@"%s: startPos=0x%08X endPos=0x%08X", __PRETTY_FUNCTION__,
            startPos, endPos);
    }
  }

  exception = [NSException exceptionWithName:@"SyntaxError"
                           reason:_text
                           userInfo:ui];
  [ui release]; ui = nil;

  return exception;
}

static BOOL _skipComments(const char *_buffer, unsigned *_idx, unsigned _len,
                          NSException **_exception)
{
  register unsigned pos = *_idx;
  BOOL lookAgain;

  if (pos >= _len)
    return NO;

  //NSLog(@"start at '%c' (%i)", _buffer[pos], pos);
  
  do { // until all comments are filtered ..
    lookAgain = NO;
    
    if ((_buffer[pos] == '/') && (pos + 1 < _len)) {
      if (_buffer[pos + 1] == '/') { // single line comments
        pos += 2; // skip '//'

        // search for '\n' ..
        while ((pos < _len) && (_buffer[pos] != '\n'))
          pos++;

        if ((pos < _len) && (_buffer[pos] == '\n')) {
          pos++; // skip newline, otherwise EOF was reached
          lookAgain = YES;
        }
      }
      else if (_buffer[pos + 1] == '*') { /* multiline comments */
        BOOL commentIsClosed = NO;
      
        pos += 2; // skip '/*'

        do { // search for '*/'
          while ((pos < _len) && (_buffer[pos] != '*'))
            pos++;

          if (pos < _len) { // found '*'
            if ((pos + 1) < _len) {
              if (_buffer[pos + 1] == '/') { // found '*/'
                commentIsClosed = YES;
                pos += 2; // skip '*/'
                lookAgain = YES;
                break; // leave loop
              }
            }
          }
        }
        while (pos < _len);

        if (!commentIsClosed) {
          // EOF found, comment wasn't closed
          *_exception =
            _makeException(*_exception, _buffer, *_idx, _len,
                           @"comment was not closed (expected '*/')");
          return NO;
        }
      }
    }
    else if (isspace((int)_buffer[pos])) {
      pos++;
      lookAgain = YES;
    }
  }
  while (lookAgain && (pos < _len));
  
  // store position ..
  *_idx = pos;
  //NSLog(@"end at '%c' (%i)", _buffer[pos], pos);

  return (pos < _len);
}

static NSString *_parseIdentifier(NSZone *_zone,
                                  const char *_buffer, unsigned *_idx,
                                  unsigned _len, NSException **_exception)
{
  register unsigned pos = *_idx;
  register unsigned len = 0;
  unsigned startPos = pos;

  // skip comments and spaces
  if (!_skipComments(_buffer, _idx, _len, _exception)) {
    // EOF reached during comment-skipping
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                        @"did not find an id (expected 'a-zA-Z0-9') !");
    return nil;
  }
  
  // loop until break char
  while (_isIdChar(_buffer[pos]) && (pos < _len)) {
    pos++;
    len++;
  }

  if (len == 0) { // wasn't a string ..
    *_exception =
      _makeException(*_exception, _buffer, *_idx, _len,
                     @"did not find an id (expected 'a-zA-Z0-9') !");
    return nil;
  }
  else {
    *_idx = pos;
    return _makeStringForBuffer(&(_buffer[startPos]), len);
  }
}
static NSString *_parseKeyPath(NSZone *_zone,
                               const char *_buffer, unsigned *_idx,
                               unsigned _len, NSException **_exception,
                               id self)
{
  NSMutableString *keypath   = nil;
  NSString        *component = nil;
  
  if (!_skipComments(_buffer, _idx, _len, _exception)) {
    // EOF reached during comment-skipping
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find keypath (expected id)");
    return nil;
  }

  component = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
  if (component == nil) {
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find keypath (expected id)");
    return nil;
  }
  if (_buffer[*_idx] != '.') // single id-keypath
    return component;

  keypath = [[NSMutableString allocWithZone:_zone] init];
  [keypath appendString:component];
  
  while ((_buffer[*_idx] == '.') && (component != nil)) {
    *_idx += 1; // skip '.'
    [keypath appendString:@"."];

    RELEASE(component); component = nil;
    component = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);

    if (component == nil) {
      RELEASE(keypath); keypath = nil;
      *_exception =
        _makeException(*_exception, _buffer, *_idx, _len,
                       @"expected component after '.' in keypath !");
      break;
    }

    [keypath appendString:component];
  }
  RELEASE(component); component = nil;

  return keypath;
}

static NSString *_parseQString(NSZone *_zone,
                               const char *_buffer, unsigned *_idx,
                               unsigned _len, NSException **_exception,
                               id self)
{
  // skip comments and spaces
  if (!_skipComments(_buffer, _idx, _len, _exception)) {
    // EOF reached during comment-skipping
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                        @"did not find a quoted string (expected '\"') !");
    return nil;
  }

  if (_buffer[*_idx] != '"') { // it's not a quoted string that's follows
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find quoted string (expected '\"')");
    return nil;
  }
  else { // a quoted string
    register unsigned pos = *_idx;
    register unsigned len = 0;
    unsigned startPos = pos + 1;
    BOOL     containsEscaped = NO;
    
    pos++; // skip starting quote

    // loop until closing quote
    while ((_buffer[pos] != '"') && (pos < _len)) {
      if (_buffer[pos] == '\\') {
        containsEscaped = YES;
        pos++; // skip following char
        if (pos == _len) {
          *_exception =
            _makeException(*_exception, _buffer, *_idx, _len,
                           @"escape in quoted string not finished !");
          return nil;
        }
      }
      pos++;
      len++;
    }

    if (pos == _len) { // syntax error, quote not closed
      *_idx = pos;
      *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                   @"quoted string not closed (expected '\"')");
      return nil;
    }

    pos++;       // skip closing quote
    *_idx = pos; // store pointer
    pos = 0;
    
    if (len == 0) { // empty string
      return @"";
    }
    else if (containsEscaped) {
      register unsigned pos2;
      id   ostr = nil;
      unsigned char *str;
      
      NSCAssert(len > 0, @"invalid length ..");
      str = malloc(len + 3);
      
      for (pos = startPos, pos2 = 0; _buffer[pos] != '"'; pos++, pos2++) {
        //NSLog(@"char=%c pos=%i pos2=%i", _buffer[pos], pos2);
        if (_buffer[pos] == '\\') {
          pos++;
          switch (_buffer[pos]) {
            case 'a':  str[pos2] = '\a'; break;
            case 'b':  str[pos2] = '\b'; break;
            case 'f':  str[pos2] = '\f'; break;
            case 'n':  str[pos2] = '\n'; break;
            case 't':  str[pos2] = '\t'; break;
            case 'v':  str[pos2] = '\v'; break;
            case '\\': str[pos2] = '\\'; break;
            
            default:
              str[pos2] = _buffer[pos];
              break;
          }
        }
        else {
          str[pos2] = _buffer[pos];
        }
      }
      str[pos2] = '\0';
      NSCAssert(pos2 == len, @"invalid unescape ..");
      
      ostr = _makeStringForBuffer(str, pos2);
      if (str) free(str); str = NULL;

      return ostr;
    }
    else {
      NSCAssert(len > 0, @"invalid length ..");
      return _makeStringForBuffer(&(_buffer[startPos]), len);
    }
  }
}

static NSData *_parseData(NSZone *_zone, const char *_buffer,
                          unsigned *_idx, unsigned _len,
                          NSException **_exception,
                          id self)
{
  if (!_skipComments(_buffer, _idx, _len, _exception)) {
    // EOF reached during comment-skipping
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find a data (expected '<') !");
    return nil;
  }

  if (_buffer[*_idx] != '<') { // it's not a data that's follows
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find a data (expected '<') !");
    return nil;
  }
  else {
    register      unsigned pos = *_idx + 1;
    register      unsigned len = 0;
    unsigned      endPos = 0;
    NSMutableData *data  = nil;
    
    *_idx += 1; // skip '<'

    if (!_skipComments(_buffer, _idx, _len, _exception)) {
      *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                   @"data was not closed (expected '>') ..");
      return nil; // EOF
    }

    if (_buffer[*_idx] == '>') { // empty data
      *_idx += 1; // skip '>'
      return [[NSData allocWithZone:_zone] init];
    }

    // count significant chars
    while ((_buffer[pos] != '>') && (pos < _len)) {
      if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t'))
        ;
      else if (_isHexDigit(_buffer[pos]))
        len++;
      else {
        *_idx = pos;
        *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                     @"invalid char in data property");
        return nil;
      }
      pos++;
    }
    if (pos == _len) {
      *_idx = pos;
      *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                   @"data was not closed (expected '>')");
      return nil; // EOF
    }
    endPos = pos; // store position of closing '>'

    // if odd, then add one byte for trailing nibble
    len = (len % 2 == 1) ? len / 2 + 1 : len / 2;
    data = [[NSMutableData allocWithZone:_zone] initWithLength:len];

    /* now copy bytes ... */
    {
      register unsigned i;
      register int pending = -1;
      char *buf = [data mutableBytes];
      
      for (pos = *_idx, i = 0; (pos < endPos) && (i < len); pos++) {
        int value = _valueOfHexChar(_buffer[pos]);

        if (value != -1) {
          if (pending == -1)
            pending = value;
          else {
            value = value * 16 + pending;
            pending = -1;

            buf[i] = value;
            i++;
          }
        }
      }
      if (pending != -1) { // was odd, now add the trailer ..
        NSCAssert(i < len, @"invalid length ..");
        buf[i] = pending * 16;
      }
    }
    
    // update global position
    *_idx = endPos + 1; // endPos + 1 (*endPos == '>', 1 => skips '>')

    return data;
  }
}

static NSDictionary *_parseDict(NSZone *_zone,
                                const char *_buffer, unsigned *_idx,
                                unsigned _len, NSException **_exception,
                                id self)
{
  if (!_skipComments(_buffer, _idx, _len, _exception)) {
    // EOF reached during comment-skipping
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find dictionary (expected '{')");
    return nil;
  }
  
  if (_buffer[*_idx] != '{') { // it's not a dict that's follows
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find dictionary (expected '{')");
    return nil;
  }
  else {
    NSMutableDictionary *result = nil;
    id   key     = nil;
    id   value   = nil;
    BOOL didFail = NO;
    
    *_idx += 1; // skip '{'

    if (!_skipComments(_buffer, _idx, _len, _exception)) {
      *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                   @"dictionary was not closed (expected '}')");
      return nil; // EOF
    }

    if (_buffer[*_idx] == '}') { // an empty dictionary
      *_idx += 1; // skip the '}'
      return [[DictClass allocWithZone:_zone] init];
    }

    result = [[DictClass allocWithZone:_zone] init];
    do {
      key   = nil;
      value = nil;
      
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception =
          _makeException(*_exception, _buffer, *_idx, _len,
                         @"dictionary was not closed (expected '}')");
        didFail = YES;
        break; // unexpected EOF
      }

      if (_buffer[*_idx] == '}') { // dictionary closed
        *_idx += 1; // skip the '}'
        break;
      }
      
      // read key property
      key = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
      if (key == nil) { // syntax error
        if (*_exception == nil) {
          *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                       @"got nil-key in dictionary ..");
        }
        didFail = YES;
        break;
      }

      /* The following parses:  (comment|space)* '=' (comment|space)* */
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                     @"expected '=' after key in dictionary");
        didFail = YES;
        break; // unexpected EOF
      }
      // no we need a '=' assignment
      if (_buffer[*_idx] != '=') {
        *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                     @"expected '=' after key in dictionary");
        didFail = YES;
        break;
      }
      *_idx += 1; // skip '='
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception =
          _makeException(*_exception, _buffer, *_idx, _len,
                         @"expected value after key '=' in dictionary");
        didFail = YES;
        break; // unexpected EOF
      }

      // read value property
      value = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
#if 1
      if (*_exception) {
        didFail = YES;
        break;
      }
#else
      if (value == nil) { // syntax error
        if (*_exception == nil) {
          *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                       @"got nil-value in dictionary");
        }
        didFail = YES;
        break;
      }
#endif
      
      if ((key != nil) && (value != nil))
        [result setObject:value forKey:key];
      
      // release key and value
      RELEASE(key);   key   = nil;
      RELEASE(value); value = nil;

      // read trailing ';' if available
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception =
          _makeException(*_exception, _buffer, *_idx, _len,
                         @"dictionary was not closed (expected '}')");
        didFail = YES;
        break; // unexpected EOF
      }
      if (_buffer[*_idx] == ';') {
        *_idx += 1; // skip ';'
      }
      else { // no ';' at end of pair, only allowed at end of dictionary
        if (!_skipComments(_buffer, _idx, _len, _exception)) {
          *_exception =
            _makeException(*_exception, _buffer, *_idx, _len,
                           @"dictionary was not closed (expected '}')");
          didFail = YES;
          break; // unexpected EOF
        }

        if (_buffer[*_idx] != '}') { // dictionary wasn't closed
          *_exception =
            _makeException(*_exception, _buffer, *_idx, _len,
                           @"key-value pair without ';' at the end");
          didFail = YES;
          break;
        }
      }
    }
    while ((*_idx < _len) && (result != nil) && !didFail);

    RELEASE(key);    key    = nil;
    RELEASE(value);  value  = nil;
    if (didFail) {
      [result release]; result = nil;
      return nil;
    }
    else
      return result;
  }
}

static NSArray *_parseArray(NSZone *_zone, const char *_buffer, unsigned *_idx,
                            unsigned _len, NSException **_exception,
                            id self)
{
  if (!_skipComments(_buffer, _idx, _len, _exception)) {
    // EOF reached during comment-skipping
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find array (expected '(')");
    return nil;
  }

  if (_buffer[*_idx] != '(') { // it's not an array that's follows
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"did not find array (expected '(')");
    return nil;
  }
  else {
    NSMutableArray *result = nil;
    id element = nil;

    *_idx += 1; // skip '('

    if (!_skipComments(_buffer, _idx, _len, _exception)) {
      *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                   @"array was not closed (expected ')')");
      return nil; // EOF
    }

    if (_buffer[*_idx] == ')') { // an empty array
      *_idx += 1; // skip the ')'
      return [[NSArray allocWithZone:_zone] init];
    }
    
    result = [[NSMutableArray allocWithZone:_zone] init];
    do {
      element = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
      if (element == nil) {
        *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                     @"expected element in array");
        [result release]; result = nil;
        break;
      }
      [result addObject:element];
      [element release]; element = nil;
      
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception =
          _makeException(*_exception, _buffer, *_idx, _len,
                         @"array was not closed (expected ')' or ',')");
        [result release];
        result = nil;
        break;
      }
      
      if (_buffer[*_idx] == ')') { // closed array
        *_idx += 1; // skip ')'
        break;
      }
      else if (_buffer[*_idx] == ',') { // next element
        *_idx += 1; // skip ','
        
        if (!_skipComments(_buffer, _idx, _len, _exception)) {
          *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                       @"array was not closed (expected ')')");
          [result release];
          result = nil;
          break;
        }
        if (_buffer[*_idx] == ')') { // closed array, like this '(1,2,)'
          *_idx += 1; // skip ')'
          break;
        }
      }
      else { // syntax error
        *_exception =
          _makeException(*_exception, _buffer, *_idx, _len,
                         @"expected ')' or ',' after array element");
        [result release]; result = nil;
        break;
      }
    }
    while ((*_idx < _len) && (result != nil));
    
    [element release]; element = nil;

    return result;
  }
}

static NSNumber *_parseDigitPath(NSString *digitPath) {
  NSRange  r;

  r = [digitPath rangeOfString:@"."];
  return r.length > 0 
    ? [NumberClass numberWithDouble:[digitPath doubleValue]]
    : [NumberClass numberWithInt:[digitPath intValue]];
}

static id _parseProperty(NSZone *_zone, const char *_buffer, unsigned *_idx,
                         unsigned _len,
                         NSException **_exception, BOOL _allowAssoc,
                         id self)
{
  BOOL valueProperty = YES;
  id   result = nil;
  
  if (!_skipComments(_buffer, _idx, _len, _exception))
    return nil; // EOF

  switch (_buffer[*_idx]) {
    case '"': // quoted string
      NSCAssert(result == nil, @"result is already set ..");
      result = _parseQString(_zone, _buffer, _idx, _len, _exception, self);
      break;

    case '{': // dictionary
      NSCAssert(result == nil, @"result is already set ..");
      result = _parseDict(_zone, _buffer, _idx, _len, _exception, self);
      break;

    case '(': // array
      NSCAssert(result == nil, @"result is already set ..");
      result = _parseArray(_zone, _buffer, _idx, _len, _exception, self);
      break;

    case '<': // data
      NSCAssert(result == nil, @"result is already set ..");
      result = _parseData(_zone, _buffer, _idx, _len, _exception, self);
      break;
      
    default:
      NSCAssert(result == nil, @"result is already set ..");
      
      if (isdigit((int)_buffer[*_idx]) || (_buffer[*_idx] == '-')) {
        id digitPath;
        NSCAssert(result == nil, @"result is already set ..");
        
        digitPath = _parseKeyPath(_zone, _buffer, _idx, _len, _exception,self);
        result = [_parseDigitPath(digitPath) retain];
        [digitPath release]; digitPath = nil;
        valueProperty = YES;
      }
      else if (_isIdChar(_buffer[*_idx])) {
        valueProperty = NO;
	
        if ((_buffer[*_idx] == 'Y') || (_buffer[*_idx] == 'N')) {
          // parse YES and NO
          if ((*_idx + 4) < _len) {
            if (strncmp(&(_buffer[*_idx]), "YES", 3) == 0 &&
		_isBreakChar(_buffer[*_idx + 3])) {
              result = [yesNum retain];
              valueProperty = YES;
              *_idx += 3; // skip the YES
            }
          }
          if (((*_idx + 3) < _len) && !valueProperty) {
            if (strncmp(&(_buffer[*_idx]), "NO", 2) == 0 &&
		_isBreakChar(_buffer[*_idx + 2])) {
              result = [noNum retain];
              valueProperty = YES;
              *_idx += 2; // skip the NO
            }
          }
        }
        else if ((_buffer[*_idx] == 't') || (_buffer[*_idx] == 'f')) {
          // parse true and false
          if ((*_idx + 5) < _len) {
            if (strncmp(&(_buffer[*_idx]), "true", 4) == 0 &&
		_isBreakChar(_buffer[*_idx + 4])) {
              result = [yesNum retain];
              valueProperty = YES;
              *_idx += 4; // skip the true
            }
          }
          if (((*_idx + 6) < _len) && !valueProperty) {
            if (strncmp(&(_buffer[*_idx]), "false", 5) == 0 &&
		_isBreakChar(_buffer[*_idx + 5])) {
              result = [noNum retain];
              valueProperty = YES;
              *_idx += 5; // skip the false
            }
          }
        }
        
        if (!valueProperty) {
          NSCAssert(result == nil, @"result already set ..");
          result = _parseKeyPath(_zone, _buffer, _idx, _len, _exception, self);
        }
      }
      else {
        NSCAssert(result == nil, @"result already set ..");
        
        *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                     @"invalid char");
      }
      break;
  }

  if (*_exception)
    return nil;
  
  if (result == nil) {
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"error in property value");
  }

  NSCAssert(result, @"missing property value ..");

  if (_allowAssoc) {
    id old = result;
    
    result = valueProperty
      ? [self associationWithValue:result]
      : [self associationWithKeyPath:result];
    
#if 0
    NSCAssert(result, @"got no association for property ..");
#endif
    [old release]; old = nil;
    
    return [result retain];
  }
  
  /* result is already retained */
  return result;
}

static NSDictionary *_parseWodConfig(NSZone *_zone, const char *_buffer,
                                     unsigned *_idx, unsigned _len,
                                     NSException **_exception,
                                     id self)
{
  if (!_skipComments(_buffer, _idx, _len, _exception)) {
    // EOF reached during comment-skipping
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                     @"did not find element configuration (expected '{')");
    return nil;
  }
  
  if (_buffer[*_idx] != '{') { // it's not a dict that's follows
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                     @"did not find element configuration (expected '{')");
    return nil;
  }
  else { // found '{'
    NSMutableDictionary *result = nil;
    NSString *key     = nil;
    id       value    = nil;
    BOOL     didFail  = NO;
    
    *_idx += 1; // skip '{'

    if (!_skipComments(_buffer, _idx, _len, _exception)) {
      *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                       @"element configuration was not closed (expected '}')");
      return nil; // EOF
    }

    if (_buffer[*_idx] == '}') { // an empty configuration
      *_idx += 1; // skip the '}'
      return [[DictClass allocWithZone:_zone] init];
    }

    result = [[DictClass allocWithZone:_zone] init];
    do {
      key   = nil;
      value = nil;
      
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception =
          _makeException(*_exception, _buffer, *_idx, _len,
                         @"dictionary was not closed (expected '}')");
        didFail = YES;
        break; // unexpected EOF
      }

      if (_buffer[*_idx] == '}') { // dictionary closed
        *_idx += 1; // skip the '}'
        break;
      }
      
      // read key property
      key = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
      if (key == nil) { // syntax error
        *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                         @"expected identifier in element configuration ..");
        didFail = YES;
        break;
      }

      /* The following parses:  (comment|space)* '=' (comment|space)* */
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                         @"expected '=' after id in element configuration");
        didFail = YES;
        break; // unexpected EOF
      }
      // no we need a '=' assignment
      if (_buffer[*_idx] != '=') {
        *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                         @"expected '=' after id in element configuration");
        didFail = YES;
        break;
      }
      *_idx += 1; // skip '='
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception =
          _makeException(*_exception, _buffer, *_idx, _len,
                         @"expected value after id '=' in "
                         @"element configuration");
        didFail = YES;
        break; // unexpected EOF
      }

      // read value property
      value = _parseProperty(_zone, _buffer, _idx, _len, _exception, YES, self);
#if 1
      if (*_exception) {
        didFail = YES;
        break;
      }
#else
      if (value == nil) { // syntax error
        if (*_exception == nil) {
          *_exception =
            _makeException(*_exception, _buffer, *_idx, _len,
                           @"got nil-value in element configuration");
        }
        didFail = YES;
        break;
      }
      NSCAssert(key,   @"invalid key ..");
      NSCAssert(value, @"invalid value ..");
#endif

      if ((value != nil) && (key != nil))
        [result setObject:value forKey:key];

      // release key and value
      RELEASE(key);   key   = nil;
      RELEASE(value); value = nil;

      // read trailing ';' if available
      if (!_skipComments(_buffer, _idx, _len, _exception)) {
        *_exception =
          _makeException(*_exception, _buffer, *_idx, _len,
                         @"element configuration was not "
                         @"closed (expected '}')");
        didFail = YES;
        break; // unexpected EOF
      }
      if (_buffer[*_idx] == ';') {
        *_idx += 1; // skip ';'
      }
      else { // no ';' at end of pair, only allowed at end of dictionary
        if (!_skipComments(_buffer, _idx, _len, _exception)) {
          *_exception =
            _makeException(*_exception, _buffer, *_idx, _len,
                           @"element configuration was not "
                           @"closed (expected '}')");
          didFail = YES;
          break; // unexpected EOF
        }

        if (_buffer[*_idx] != '}') { // config wasn't closed
          *_exception =
            _makeException(*_exception, _buffer, *_idx, _len,
                           @"key-value pair without ';' at the end");
          didFail = YES;
          break;
        }
      }
    }
    while ((*_idx < _len) && (result != nil) && !didFail);

    [key   release]; key    = nil;
    [value release]; value  = nil;
    
    if (didFail) {
      [result release]; result = nil;
      return nil;
    }
    else
      return result;
  }
}

static id _parseWodEntry(NSZone *_zone, const char *_buffer, unsigned *_idx,
                         unsigned _len, NSException **_exception,
                         NSString **_name, NSString **_class, id self)
{
  NSString     *elementName   = nil;
  NSString     *componentName = nil;
  NSDictionary *config        = nil;

  *_name  = nil;
  *_class = nil;
  
  if (!_skipComments(_buffer, _idx, _len, _exception))
    return nil; // EOF

  // Element name
  elementName = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
  if (elementName == nil) {
    *_exception = _makeException(nil, _buffer, *_idx, _len,
                                 @"expected element name");
    goto failed;
  }

  if (!_skipComments(_buffer, _idx, _len, _exception))
    goto failed;

  // Element/Component separator
  if (_buffer[*_idx] == ':') {
    *_idx += 1; // skip ':'
  }
  else {
    *_exception = _makeException(*_exception, _buffer, *_idx, _len,
                                 @"expected ':' after element name");
    goto failed;
  }

  if (!_skipComments(_buffer, _idx, _len, _exception))
    goto failed;

  // Component Name
  componentName = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
  if (componentName == nil) {
    *_exception = _makeException(nil, _buffer, *_idx, _len,
                                 @"expected component name");
    goto failed;
  }

  if (!_skipComments(_buffer, _idx, _len, _exception))
    goto failed;

  // Configuration
  config = _parseWodConfig(_zone, _buffer, _idx, _len, _exception, self);
  if (config == nil)
    goto failed;
  
  //NSLog(@"%@ : %@ %@", elementName, componentName, config);

  // read trailing ';' if available
  if (_skipComments(_buffer, _idx, _len, _exception)) {
    if (_buffer[*_idx] == ';') {
      *_idx += 1; // skip ';'
    }
  }

  *_name  = elementName;
  *_class = componentName;
  return config;
  
 failed:
#if 0
  NSLog(@"failed at %@:%@ ..", elementName, componentName);
#endif
  [elementName   release]; elementName   = nil;
  [componentName release]; componentName = nil;
  [config        release]; config        = nil;
  return nil;
}

@end /* WODParser */
