Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

added PubNub Cocoa Real-time API for Mac OSX Apps.

  • Loading branch information...
commit ad6fc330b4fe2d894e1f14584cd0933ffc273ff6 1 parent dd2fd53
@stephenlb stephenlb authored
Showing with 7,556 additions and 0 deletions.
  1. BIN  cocoa/.DS_Store
  2. +187 −0 cocoa/JSON/ByteStream.h
  3. +298 −0 cocoa/JSON/ByteStream.m
  4. +120 −0 cocoa/JSON/ByteStreamDelegate.h
  5. +391 −0 cocoa/JSON/ByteStreamDelegate.m
  6. +30 −0 cocoa/JSON/GNUmakefile
  7. +69 −0 cocoa/JSON/JSON.h
  8. +24 −0 cocoa/JSON/JSONExtensions.h
  9. +93 −0 cocoa/JSON/JSONPort.h
  10. +391 −0 cocoa/JSON/JSONPort.m
  11. +204 −0 cocoa/JSON/NSArray+JSON.m
  12. +248 −0 cocoa/JSON/NSDictionary+JSON.m
  13. +37 −0 cocoa/JSON/NSDictionary+JSONRPC.m
  14. +289 −0 cocoa/JSON/NSNumber+JSON.m
  15. +61 −0 cocoa/JSON/NSObject+JSON.h
  16. +106 −0 cocoa/JSON/NSObject+JSON.m
  17. +214 −0 cocoa/JSON/NSString+JSON.m
  18. +113 −0 cocoa/JSON/SBJsonParser.h
  19. +120 −0 cocoa/JSON/SBJsonParser.m
  20. +135 −0 cocoa/JSON/SBJsonStreamParser.h
  21. +264 −0 cocoa/JSON/SBJsonStreamParser.m
  22. +88 −0 cocoa/JSON/SBJsonStreamParserAdapter.h
  23. +171 −0 cocoa/JSON/SBJsonStreamParserAdapter.m
  24. +89 −0 cocoa/JSON/SBJsonStreamParserState.h
  25. +370 −0 cocoa/JSON/SBJsonStreamParserState.m
  26. +157 −0 cocoa/JSON/SBJsonStreamWriter.h
  27. +378 −0 cocoa/JSON/SBJsonStreamWriter.m
  28. +75 −0 cocoa/JSON/SBJsonStreamWriterState.h
  29. +132 −0 cocoa/JSON/SBJsonStreamWriterState.m
  30. +67 −0 cocoa/JSON/SBJsonTokeniser.h
  31. +482 −0 cocoa/JSON/SBJsonTokeniser.m
  32. +132 −0 cocoa/JSON/SBJsonWriter.h
  33. +102 −0 cocoa/JSON/SBJsonWriter.m
  34. +13 −0 cocoa/JSON/iohelpers.h
  35. +115 −0 cocoa/JSON/iohelpers.m
  36. +26 −0 cocoa/JSON/jsunicode.h
  37. +133 −0 cocoa/JSON/jsunicode.m
  38. +9 −0 cocoa/JSON/streamutils.h
  39. +24 −0 cocoa/JSON/streamutils.m
  40. +262 −0 cocoa/README.md
  41. +27 −0 cocoa/build-example
  42. +786 −0 cocoa/example.d
  43. +109 −0 cocoa/example.m
  44. +49 −0 cocoa/pubnub.h
  45. +223 −0 cocoa/pubnub.m
  46. +41 −0 cocoa/request.h
  47. +102 −0 cocoa/request.m
View
BIN  cocoa/.DS_Store
Binary file not shown
View
187 cocoa/JSON/ByteStream.h
@@ -0,0 +1,187 @@
+#ifndef _INCLUDED_ByteStream_h
+#define _INCLUDED_ByteStream_h
+
+// ByteIStream, ByteOStream - bufferred input and output classes
+// emulating stream of bytes. They provide subset of FILE stream
+// functionality from standard C library.
+
+#include <Foundation/NSObject.h>
+
+//-----------------------------------------------------------------------------
+// Delegate protocols.
+//-----------------------------------------------------------------------------
+
+@protocol ByteISDelegate
+// Get new data from the data source. Return first new character or -1 if
+// operation failed or end of file encountered. Do not advance the position.
+- (int) underflow;
+
+// Return YES if the stream is at EOF
+- (BOOL) eof;
+
+// Close data source. Returns 0 on success or -1 on error
+- (int) close;
+@end
+
+@protocol ByteOSDelegate
+// Flush the buffer into the data sink. Return 0 on success and -1 on failure.
+- (int) flush;
+
+// Move some or all data to the data sink, then put ch into buffer.
+// Return that character on success or -1 if data move failed.
+// Current implementations try to move all data, like flush does.
+- (int) overflow: (unsigned char) ch;
+
+// Flush data and close data sink. Return 0 on success or -1 on error
+- (int) close;
+@end
+
+
+//-----------------------------------------------------------------------------
+// ByteStreamBuffer - superclass for input and output streams
+//-----------------------------------------------------------------------------
+
+@interface ByteStreamBuffer : NSObject
+{
+@public
+ unsigned char * _buf; // buffer
+ unsigned _capa; // size of buffer, 0 means _buf is not owned
+ unsigned _pos; // current input or output position
+}
+
+- (unsigned char *) buffer;
+- (unsigned) bufferCapacity;
+
+- (int) close;
+
+@end
+
+//-----------------------------------------------------------------------------
+// Input byte stream.
+//-----------------------------------------------------------------------------
+
+@class NSInputStream;
+
+@interface ByteIStream: ByteStreamBuffer
+{
+@public
+ id <ByteISDelegate, NSObject> _del;
+ unsigned _len; // length of data within buffer
+}
+
+// Memory stream
+- (id) initWithBuffer: (unsigned char *) buffer size: (int) len;
+- (id) initWithBuffer: (unsigned char *) buffer size: (int) len
+ makeCopy: (BOOL) copy;
+
+// File descriptor stream
+- (id) initWithFD: (int) fd;
+- (id) initWithFileAtPath: (NSString *) path;
+- (id) initWithFileAtPath: (NSString *) path flags: (int) flags;
+
+// NSStream stream
+- (id) initWithNSIStream: (NSInputStream *) s;
+
+- (int) close;
+
+- (unsigned) bufferLength;
+
+@end
+
+//-----------------------------------------------------------------------------
+// Output byte stream.
+//-----------------------------------------------------------------------------
+
+@class NSOutputStream;
+
+@interface ByteOStream: ByteStreamBuffer
+{
+@public
+ id <ByteOSDelegate, NSObject> _del;
+}
+
+// Memory stream
+- (id) init;
+- (id) initWithCapacity: (int) capa;
+
+// File descriptor stream
+- (id) initWithFD: (int) fd;
+- (id) initToFileAtPath: (NSString *) path;
+- (id) initToFileAtPath: (NSString *) path flags: (int) flags;
+
+// NSStream based stream
+- (id) initWithNSOStream: (NSOutputStream *) s;
+
+- (int) close;
+
+- (unsigned) bufferLength;
+
+@end
+
+
+//-----------------------------------------------------------------------------
+// Input functions.
+//-----------------------------------------------------------------------------
+
+
+inline static BOOL str_eof( ByteIStream * s )
+{
+ return [s->_del eof];
+}
+
+// Returns next byte and does not advance the current position,
+// returns -1 on eof or error.
+inline static int str_peekc( ByteIStream * s )
+{
+ return (s->_pos < s->_len) ? s->_buf[s->_pos] : [s->_del underflow];
+}
+
+// Return next byte and advance the current position,
+// returns -1 on eof or error.
+inline static int str_getc( ByteIStream * s )
+{
+ int ch = str_peekc( s );
+ if ( ch != -1 )
+ ++s->_pos;
+ return ch;
+}
+
+// Move current position back and inserts given byte at it.
+// Currently guaranteed to work only after str_getc().
+inline static int str_ungetc( unsigned char c, ByteIStream * s )
+{
+ return (s->_pos > 0) ? (s->_buf[--s->_pos] = c) : -1;
+}
+
+// Reads 'count' bytes from stream into buffer. Returns number of bytes read
+// which can be less than 'count' if EOF is reached.
+unsigned str_getbytes( ByteIStream * s, char * buffer, unsigned count );
+
+
+//-----------------------------------------------------------------------------
+// Output functions.
+//-----------------------------------------------------------------------------
+
+// Put character c into stream s. Returns that character or -1 on error.
+inline static int str_putc( unsigned char c, ByteOStream * s )
+{
+ return (s->_pos < s->_capa) ?
+ (s->_buf[s->_pos++] = c) : [s->_del overflow: c];
+}
+
+// Flushes the buffer into the data sink. Returns 0 on success, -1 on failure.
+inline static int str_flush( ByteOStream * s )
+{
+ return [s->_del flush];
+}
+
+// Writes string into stream, returns number of bytes written or -1 on error.
+int str_puts( const char * str, ByteOStream * s );
+
+inline static int str_close( ByteStreamBuffer * s )
+{
+ return [s close];
+}
+
+
+#endif // _INCLUDED_ByteStream_h
View
298 cocoa/JSON/ByteStream.m
@@ -0,0 +1,298 @@
+#include "ByteStream.h"
+#include <fcntl.h>
+#include <Foundation/NSString.h>
+#include "ByteStreamDelegate.h"
+
+#define DEFAULT_BUFFER_SIZE 4096
+//#define DEFAULT_BUFFER_SIZE 64
+#define MINIMAL_BUFFER_SIZE 4
+
+#define max( a, b ) ((a) < (b) ? (b) : (a))
+
+
+// Reads 'count' bytes from stream into buffer. Returns number of bytes read
+// which can be less than 'count' if EOF is reached.
+unsigned str_getbytes( ByteIStream * s, char * buffer, unsigned count )
+{
+ unsigned i = 0;
+ int ch;
+
+ // Order of evaluation is important.
+ // Condition (i < count) comes first to ensure no more than 'count' reads
+ while ( i < count && (ch = str_getc( s )) != -1 )
+ buffer[i++] = ch;
+ return i;
+}
+
+// Writes string into stream, returns number of bytes written or -1 on error.
+int str_puts( const char * str, ByteOStream * s )
+{
+ int i = -1;
+ while ( str[++i] )
+ if ( str_putc( str[i], s ) == -1 )
+ return -1;
+
+ return i+1;
+}
+
+
+//-----------------------------------------------------------------------------
+// ByteStreamBuffer
+//-----------------------------------------------------------------------------
+
+@interface ByteStreamBuffer (Protected)
+- (id) initWithCapacity: (unsigned) capa;
+@end
+
+@implementation ByteStreamBuffer
+
+- (id) initWithCapacity: (unsigned) capa
+{
+ if ( (self = [super init]) == nil )
+ return 0;
+
+ _capa = max( capa, MINIMAL_BUFFER_SIZE);
+ _buf = malloc( _capa );
+ if ( ! _buf )
+ {
+ [self release];
+ return 0;
+ }
+ _pos = 0;
+ return self;
+}
+
+- (void) dealloc
+{
+ if ( _capa ) // we own _buf
+ free( _buf );
+
+ [super dealloc];
+}
+
+- (unsigned char *) buffer
+{
+ return _buf;
+}
+
+- (unsigned) bufferCapacity
+{
+ return _capa;
+}
+
+- (int) close
+{
+ return 0; // always succeeds
+}
+
+@end
+
+//-----------------------------------------------------------------------------
+// ByteIStream
+//-----------------------------------------------------------------------------
+
+@implementation ByteIStream
+
+// Memory stream
+- (id) initWithBuffer: (unsigned char *) buf size: (int) len
+{
+ return [self initWithBuffer: buf size: len makeCopy: NO];
+}
+
+// Memory stream
+- (id) initWithBuffer: (unsigned char *) buf size: (int) len
+ makeCopy: (BOOL) copy
+{
+ if ( copy )
+ {
+ if ( (self = [super initWithCapacity: len ]) == nil )
+ return 0;
+
+ memcpy( _buf, buf, len );
+ _len = len;
+ }
+ else
+ {
+ if ( (self = [super init]) == nil )
+ return 0;
+
+ _buf = buf;
+ _capa = 0; // do not own
+ _len = len;
+ _pos = 0;
+ }
+
+ _del = [[ByteISDelegateMem alloc] initWithIStream: self];
+ if ( ! _del )
+ {
+ [self release];
+ return 0;
+ }
+
+ return self;
+}
+
+// File descriptor stream
+- (id) initWithFD: (int) fd
+{
+ if ( (self = [super initWithCapacity: DEFAULT_BUFFER_SIZE ]) == nil )
+ return 0;
+ _len = 0;
+
+ _del = [[ByteISDelegateFD alloc] initWithIStream: self fd: fd];
+ if ( ! _del )
+ {
+ [ self release];
+ return 0;
+ }
+
+ return self;
+}
+
+- (id) initWithFileAtPath: (NSString *) path
+{
+ return [self initWithFileAtPath: path flags: 0];
+}
+
+- (id) initWithFileAtPath: (NSString *) path flags: (int) flags
+{
+ int fd = open( [path cString], flags | O_RDONLY );
+ if ( fd == -1 )
+ {
+ [self release];
+ return 0;
+ }
+
+ return [self initWithFD: fd];
+}
+
+
+// NSStream stream
+- (id) initWithNSIStream: (NSInputStream *) s
+{
+ if ( (self = [super initWithCapacity: DEFAULT_BUFFER_SIZE ]) == nil )
+ return 0;
+ _len = 0;
+
+ _del = [[ByteISDelegateStream alloc] initWithIStream: self NSIStream: s];
+ if ( ! _del )
+ {
+ [ self release];
+ return 0;
+ }
+
+ return self;
+}
+
+- (int) close
+{
+ return [_del close];
+}
+
+- (void) dealloc
+{
+ [_del release];
+ [super dealloc];
+}
+
+- (unsigned) bufferLength
+{
+ return _len;
+}
+
+@end
+
+//-----------------------------------------------------------------------------
+// ByteOStream
+//-----------------------------------------------------------------------------
+
+@implementation ByteOStream
+
+// Memory based stream
+- (id) init
+{
+ return [self initWithCapacity: DEFAULT_BUFFER_SIZE];
+}
+
+// Memory based stream
+- (id) initWithCapacity: (int) size
+{
+ if ( (self = [super initWithCapacity: DEFAULT_BUFFER_SIZE ]) == nil )
+ return 0;
+
+ _del = [[ByteOSDelegateMem alloc] initWithOStream: self];
+ if ( ! _del )
+ {
+ [ self release];
+ return 0;
+ }
+
+ return self;
+}
+
+// File descriptor stream
+- (id) initWithFD: (int) fd
+{
+ if ( (self = [super initWithCapacity: DEFAULT_BUFFER_SIZE ]) == nil )
+ return 0;
+
+ _del = [[ByteOSDelegateFD alloc] initWithOStream: self fd: fd];
+ if ( ! _del )
+ {
+ [ self release];
+ return 0;
+ }
+
+ return self;
+}
+
+- (id) initToFileAtPath: (NSString *) path
+{
+ return [self initToFileAtPath: path flags: 0];
+}
+
+- (id) initToFileAtPath: (NSString *) path flags: (int) flags
+{
+ int fd = open( [path cString], flags | O_WRONLY, 0777 );
+ if ( fd == -1 )
+ {
+ [self release];
+ return 0;
+ }
+
+ return [self initWithFD: fd];
+}
+
+// NSStream based stream
+- (id) initWithNSOStream: (NSOutputStream *) s
+{
+ if ( (self = [super initWithCapacity: DEFAULT_BUFFER_SIZE ]) == nil )
+ return 0;
+
+ _del = [[ByteOSDelegateStream alloc] initWithOStream: self NSOStream: s];
+ if ( ! _del )
+ {
+ [ self release];
+ return 0;
+ }
+
+ return self;
+}
+
+- (int) close
+{
+ return [_del close];
+}
+
+- (void) dealloc
+{
+ [_del release];
+ [super dealloc];
+}
+
+- (unsigned) bufferLength
+{
+ return _pos;
+}
+
+@end
View
120 cocoa/JSON/ByteStreamDelegate.h
@@ -0,0 +1,120 @@
+#ifndef _INCLUDED_ByteStreamDelegate_h
+#define _INCLUDED_ByteStreamDelegate_h
+
+#include "ByteStream.h"
+
+@class NSInputStream;
+@class NSOutputStream;
+
+//-----------------------------------------------------------------------------
+// Input stream delegate - memory.
+//-----------------------------------------------------------------------------
+
+@interface ByteISDelegateMem : NSObject <ByteISDelegate>
+{
+ ByteIStream * _s;
+}
+
+- (id) initWithIStream: (ByteIStream *) s;
+- (void) dealloc;
+
+- (int) underflow;
+- (BOOL) eof;
+- (int) close;
+@end
+
+//-----------------------------------------------------------------------------
+// Output stream delegate - memory
+//-----------------------------------------------------------------------------
+
+@interface ByteOSDelegateMem : NSObject <ByteOSDelegate>
+{
+ ByteOStream * _s;
+}
+
+- (id) initWithOStream: (ByteOStream *) s;
+- (void) dealloc;
+
+- (int) flush;
+- (int) overflow: (unsigned char) ch;
+- (int) close;
+@end
+
+//-----------------------------------------------------------------------------
+// Input stream delegate - file descriptor
+//-----------------------------------------------------------------------------
+
+@interface ByteISDelegateFD : NSObject <ByteISDelegate>
+{
+ ByteIStream * _s;
+ int _fd;
+ unsigned _flags;
+}
+
+- (id) initWithIStream: (ByteIStream *) s fd: (int) fd;
+- (void) dealloc;
+
+- (int) underflow;
+- (BOOL) eof;
+- (int) close;
+@end
+
+//-----------------------------------------------------------------------------
+// Output stream delegate - file descriptor
+//-----------------------------------------------------------------------------
+
+@interface ByteOSDelegateFD : NSObject <ByteOSDelegate>
+{
+ ByteOStream * _s;
+ int _fd;
+ unsigned _flags;
+}
+
+- (id) initWithOStream: (ByteOStream *) s fd: (int) fd;
+- (void) dealloc;
+
+- (int) flush;
+- (int) overflow: (unsigned char) ch;
+- (int) close;
+@end
+
+//-----------------------------------------------------------------------------
+// Input stream delegate - NSInputStream
+//-----------------------------------------------------------------------------
+
+@interface ByteISDelegateStream : NSObject <ByteISDelegate>
+{
+ ByteIStream * _s;
+ NSInputStream * _iStream;
+ unsigned _flags;
+}
+
+- (id) initWithIStream: (ByteIStream *) s NSIStream: (NSInputStream *) iStream;
+- (void) dealloc;
+
+- (int) underflow;
+- (BOOL) eof;
+- (int) close;
+@end
+
+
+//-----------------------------------------------------------------------------
+// Output stream delegate - NSOutputStream
+//-----------------------------------------------------------------------------
+
+@interface ByteOSDelegateStream : NSObject <ByteOSDelegate>
+{
+ ByteOStream * _s;
+ NSOutputStream * _oStream;
+ unsigned _flags;
+}
+
+- (id) initWithOStream: (ByteOStream *) s NSOStream: (NSOutputStream *) oStream;
+- (void) dealloc;
+
+- (int) flush;
+- (int) overflow: (unsigned char) ch;
+- (int) close;
+@end
+
+#endif // _INCLUDED_ByteStreamDelegate_h
View
391 cocoa/JSON/ByteStreamDelegate.m
@@ -0,0 +1,391 @@
+#include "ByteStreamDelegate.h"
+#include "iohelpers.h"
+#include <Foundation/NSStream.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+
+//#include <Foundation/NSString.h> // Debug
+
+#define F_DEL_EOF 0x1
+#define F_DEL_BAD 0x2
+
+//-----------------------------------------------------------------------------
+// Memory
+//-----------------------------------------------------------------------------
+
+@implementation ByteISDelegateMem
+
+- (id) initWithIStream: (ByteIStream *) s
+{
+ if ( (self = [super init]) != nil )
+ {
+ _s = s;
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [super dealloc];
+}
+
+- (BOOL) eof
+{
+ return _s->_pos >= _s->_len;
+}
+
+- (int) underflow
+{
+ return -1; // fail
+}
+
+- (int) close
+{
+ return 0; // success
+}
+
+@end
+
+@implementation ByteOSDelegateMem
+
+- (id) initWithOStream: (ByteOStream *) s
+{
+ if ( (self = [super init]) != nil )
+ {
+ _s = s;
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [super dealloc];
+}
+
+- (int) close
+{
+ return 0; // success
+}
+
+- (int) flush
+{
+ return 0; // success
+}
+
+- (int) overflow: (unsigned char) ch
+{
+ // double the buffer capacity
+ if ( ! _s->_capa )
+ _s->_capa = 2;
+
+ _s->_capa *= 2;
+ if ( (_s->_buf = realloc( _s->_buf, _s->_capa )) == 0 )
+ return -1;
+ return _s->_buf[ _s->_pos++ ] = ch;
+}
+
+@end
+
+//-----------------------------------------------------------------------------
+// File descriptor
+//-----------------------------------------------------------------------------
+
+@implementation ByteISDelegateFD
+
+- (id) initWithIStream: (ByteIStream *) s fd: (int) fd
+{
+ if ( (self = [super init]) != nil )
+ {
+ _s = s;
+ _fd = fd;
+ _flags = 0;
+ }
+
+ return self;
+}
+
+- (void) dealloc
+{
+ [self close];
+ [super dealloc];
+}
+
+- (BOOL) eof
+{
+ return _flags & F_DEL_EOF;
+}
+
+- (int) close
+{
+ int ret = close(_fd);
+ if ( ret == -1 )
+ if ( errno == EBADF )
+ ret = errno = 0;
+ return ret;
+}
+
+- (int) underflow
+{
+ if ( _flags & F_DEL_EOF || _flags & F_DEL_BAD )
+ return -1;
+
+ // Reset parent's position
+ _s->_pos = 0;
+ _s->_len = 0; // if unchanged, next getc() will cause underflow
+
+ // the read() call blocks
+ int nread = read( _fd, _s->_buf, _s->_capa );
+ if ( nread == -1 )
+ {
+ _flags |= F_DEL_BAD;
+ return -1;
+ }
+ else if (nread == 0)
+ {
+ _flags |= F_DEL_EOF;
+ return -1;
+ }
+ else
+ {
+ _s->_len = nread;
+ }
+
+ return _s->_buf[0];
+}
+@end
+
+@implementation ByteOSDelegateFD
+
+- (id) initWithOStream: (ByteOStream *) s fd: (int) fd
+{
+ if ( (self = [super init]) != nil )
+ {
+ _s = s;
+ _fd = fd;
+ _flags = 0;
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [self close];
+ [super dealloc];
+}
+
+- (int) close
+{
+ int flush_status = [self flush];
+ int close_status = close(_fd);
+ if ( close_status == -1 )
+ if ( errno == EBADF )
+ close_status = errno = 0;
+
+ return flush_status ? flush_status : close_status;
+}
+
+- (int) flush
+{
+ if ( _flags & F_DEL_BAD )
+ return -1;
+
+ // Write characters from position 0 up to _pos.
+ if ( _s->_pos > 0 )
+ {
+ // Blocking write
+ ssize_t nwritten = writeall( _fd, _s->_buf, _s->_pos );
+ if ( nwritten != _s->_pos )
+ {
+ _flags |= F_DEL_BAD;
+ _s->_pos = _s->_capa; // next putc() will cause overflow
+ return -1;
+ }
+ _s->_pos = 0;
+ }
+ return 0;
+}
+
+- (int) overflow: (unsigned char) ch
+{
+ // Inefficient, should we get an IMP pointer?
+ if ( [self flush] == -1 )
+ return -1;
+
+ // Place input character at position 0 and return that character.
+ return _s->_buf[ _s->_pos++ ] = ch;
+}
+
+@end
+
+//-----------------------------------------------------------------------------
+// NSStream
+//-----------------------------------------------------------------------------
+
+static struct timespec delay = { 0, 100000 }; // 100 microseconds
+
+@implementation ByteISDelegateStream
+
+- (id) initWithIStream: (ByteIStream *) s NSIStream: (NSInputStream *) iStream
+{
+ if ( (self = [super init]) != nil )
+ {
+ _s = s;
+ _iStream = iStream;
+ [_iStream retain];
+ _flags = 0;
+
+ // Open the stream if not yet opened.
+ if ( _iStream && [_iStream streamStatus] == NSStreamStatusNotOpen )
+ {
+ [_iStream open];
+
+ // BUG: polling instead of arranging to get notification
+ int cnt = 0;
+ while ( [_iStream streamStatus] != NSStreamStatusOpen && cnt++ < 10)
+ nanosleep( &delay, 0 );
+ if ( [_iStream streamStatus] != NSStreamStatusOpen )
+ {
+ // open failed
+ [self release];
+ return nil;
+ }
+ }
+ }
+
+ return self;
+}
+
+- (void) dealloc
+{
+ [self close];
+ [_iStream release];
+ [super dealloc];
+}
+
+- (BOOL) eof
+{
+ return _flags & F_DEL_EOF;
+}
+
+- (int) close
+{
+ NSStreamStatus status = [_iStream streamStatus];
+ if ( status != NSStreamStatusNotOpen && status != NSStreamStatusClosed )
+ [_iStream close];
+ return 0;
+}
+
+- (int) underflow
+{
+ if ( _flags & F_DEL_EOF || _flags & F_DEL_BAD )
+ return -1;
+
+ // Reset parent's position
+ _s->_pos = 0;
+ _s->_len = 0; // if unchanged, next getc() will cause underflow
+
+ // the read call blocks
+ int nread = stream_blocking_read( _iStream, _s->_buf, _s->_capa );
+ if ( nread == -1 )
+ {
+ _flags |= F_DEL_BAD;
+ return -1;
+ }
+ else if (nread == 0)
+ {
+ _flags |= F_DEL_EOF;
+ return -1;
+ }
+ else
+ {
+ _s->_len = nread;
+ }
+
+ return _s->_buf[0];
+}
+
+@end
+
+@implementation ByteOSDelegateStream
+
+- (id) initWithOStream: (ByteOStream *) s NSOStream: (NSOutputStream *) oStream
+{
+ if ( (self = [super init]) != nil )
+ {
+ _s = s;
+ _oStream = oStream;
+ [_oStream retain];
+ _flags = 0;
+
+ // Open the stream if not yet opened
+ if ([_oStream streamStatus] == NSStreamStatusNotOpen )
+ {
+ [_oStream open];
+
+ // BUG: polling instead of arranging to get notification
+ int cnt = 0;
+ while ( [_oStream streamStatus] != NSStreamStatusOpen && cnt++ < 10)
+ nanosleep( &delay, 0 );
+ if ( [_oStream streamStatus] != NSStreamStatusOpen )
+ {
+ // open failed
+ [self release];
+ return nil;
+ }
+ }
+ }
+
+ return self;
+}
+
+- (void) dealloc
+{
+ [self close];
+ [_oStream release];
+ [super dealloc];
+}
+
+- (int) close
+{
+ int flush_status = [self flush];
+
+ NSStreamStatus status = [_oStream streamStatus];
+ if ( status != NSStreamStatusNotOpen && status != NSStreamStatusClosed )
+ [_oStream close];
+ return flush_status;
+}
+
+- (int) flush
+{
+ if ( _flags & F_DEL_BAD )
+ return -1;
+
+ // Write characters from position 0 up to _pos.
+ if ( _s->_pos > 0 )
+ {
+ // Blocking write
+ int nwritten = stream_writeall( _oStream, _s->_buf, _s->_pos );
+ if ( nwritten != _s->_pos )
+ {
+ _flags |= F_DEL_BAD;
+ _s->_pos = _s->_capa; // next putc() will cause overflow
+ return -1;
+ }
+ _s->_pos = 0;
+ }
+ return 0;
+}
+
+- (int) overflow: (unsigned char) ch
+{
+ // Inefficient, should we get an IMP pointer?
+ if ( [self flush] == -1 )
+ return -1;
+
+ // Place input character at position 0 and return that character.
+ return _s->_buf[ _s->_pos++ ] = ch;
+}
+
+@end
View
30 cocoa/JSON/GNUmakefile
@@ -0,0 +1,30 @@
+include $(GNUSTEP_MAKEFILES)/common.make
+
+LIBRARY_NAME = libjsonwire-oc
+MAJOR_VERSION = 0
+MINOR_VERSION = 1
+
+libjsonwire-oc_INTERFACE_VERSION=$(MAJOR_VERSION).$(MINOR_VERSION)
+
+libjsonwire-oc_OBJC_FILES = \
+ iohelpers.m \
+ ByteStream.m \
+ ByteStreamDelegate.m \
+ streamutils.m \
+ jsunicode.m \
+ NSObject+JSON.m \
+ NSArray+JSON.m \
+ NSDictionary+JSON.m \
+ NSNumber+JSON.m \
+ NSString+JSON.m \
+ NSDictionary+JSONRPC.m \
+ JSONPort.m \
+
+libjsonwire-oc_HEADER_FILES = \
+ ByteStream.h \
+ JSONExtensions.h \
+ JSONPort.h \
+
+libjsonwire-oc_HEADER_FILES_INSTALL_DIR = JSONWire
+
+include $(GNUSTEP_MAKEFILES)/library.make
View
69 cocoa/JSON/JSON.h
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2009-2010 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ @mainpage A strict JSON parser and generator for Objective-C
+
+ JSON (JavaScript Object Notation) is a lightweight data-interchange
+ format. This framework provides two apis for parsing and generating
+ JSON. One standard object-based and a higher level api consisting of
+ categories added to existing Objective-C classes.
+
+ This framework does its best to be as strict as possible, both in what it accepts and what it generates. For example, it does not support trailing commas in arrays or objects. Nor does it support embedded comments, or anything else not in the JSON specification. This is considered a feature.
+
+ @section Links
+
+ @li <a href="http://stig.github.com/json-framework">Project home page</a>.
+ @li Online version of the <a href="http://stig.github.com/json-framework/api">API documentation</a>.
+
+*/
+
+
+// This setting of 1 is best if you copy the source into your project.
+// The build transforms the 1 to a 0 when building the framework and static lib.
+
+#if 1
+
+#import "SBJsonParser.h"
+#import "SBJsonWriter.h"
+#import "SBJsonStreamWriter.h"
+#import "SBJsonStreamParser.h"
+#import "SBJsonStreamParserAdapter.h"
+#import "NSObject+JSON.h"
+
+#else
+
+#import <JSON/SBJsonParser.h>
+#import <JSON/SBJsonWriter.h>
+#import <JSON/SBJsonStreamParser.h>
+#import <JSON/SBJsonStreamParserAdapter.h>
+#import <JSON/SBJsonStreamWriter.h>
+#import <JSON/NSObject+JSON.h>
+
+#endif
View
24 cocoa/JSON/JSONExtensions.h
@@ -0,0 +1,24 @@
+#ifndef _INCLUDED_JSONExtensions_h
+#define _INCLUDED_JSONExtensions_h
+
+// JSON extensions to Objective C
+
+#include <Foundation/NSObject.h>
+#include <Foundation/NSString.h>
+
+@class ByteIStream;
+@class ByteOStream;
+
+@interface NSObject (JSON)
+- (id) initFromJSONStream: (ByteIStream *) str error: (NSString **) err;
+- (BOOL) writeToJSONStream: (ByteOStream *) str prefix: (NSString *) prefix;
+- (BOOL) writeToJSONStream: (ByteOStream *) str;
+@end
+
+@interface NSString (JSON)
++ (BOOL) preferUtf8ForWritingJSON;
++ (void) setPreferUtf8ForWritingJSON: (BOOL) val;
+@end
+
+
+#endif // _INCLUDED_JSONExtensions_h
View
93 cocoa/JSON/JSONPort.h
@@ -0,0 +1,93 @@
+#ifndef _INCLUDED_JSONPort_h
+#define _INCLUDED_JSONPort_h
+
+// JSONPort - end point of JSON_RPC connection.
+
+#include <Foundation/NSObject.h>
+#include <Foundation/NSDictionary.h>
+
+@class NSArray;
+@class NSHost;
+@class NSString;
+@class NSInputStream;
+@class NSOutputStream;
+@class ByteIStream;
+@class ByteOStream;
+
+@interface NSDictionary (JSONRPC)
+- (NSString *) method;
+- (NSArray *) params;
+- (id) rid;
+- (id) result;
+- (id) error;
+@end
+
+
+@interface JSONPort : NSObject
+{
+ ByteIStream * _is;
+ ByteOStream * _os;
+ NSString * _error;
+
+ Class _NullClass;
+ id _null;
+}
+
+/* Those two methods seems to call accept(), therefore they apply to
+ * cliesnt side sockets.
+ */
+
+- (id) initWithHost: (NSHost *) host port: (int) port;
+- (id) initWithLocalSocket: (NSString *) path;
+
+/* These init method works for both client and server side sockets. */
+
+- (id) initWithInputStream: (NSInputStream *) iStream
+ outputStream: (NSOutputStream *) oStream;
+- (id) initWithInputFD: (int) fdin outputFD: (int) fdout;
+- (id) initWithDuplexFD: (int) fd;
+
+
+/**
+ * Send method request or notification (requestID is nil).
+ * Does not wait for response. On failure returns NO and sets the error.
+ */
+- (BOOL) sendMethod: (NSString *) method params: (NSArray *) params
+ rid: (id) rid;
+
+/**
+ * Convenience method for -sendMethod:params:rid: with null rid.
+ * Null rid means the caller does not expect response.
+ */
+- (BOOL) sendNotification: (NSString *) method params: (NSArray *) params;
+
+/**
+ * These methods return NO on failure and set the error.
+ */
+- (BOOL) sendResult: (id) result rid: (id) requestID;
+- (BOOL) sendError: (id) error rid: (id) requestID;
+
+/**
+ * On success returns retained dictionary that is a valid request
+ * or response. On error returns 0 and sets the error.
+ */
+- (NSDictionary *) getRetainedMessage;
+
+/**
+ * Makes blocking method call: sends method and waits for response.
+ * Returns retained dictionary that contains response or 0 on error.
+ */
+- (id) makeMethodCall: (NSString *) method params: (NSArray *) params;
+
+/**
+ * Creates retained request ID object. Override this method to create RIDs
+ * that suit your purpouse, RID cannot be null os NSNull. Current implementation
+ * returns NSNumber initialized with 1.
+ */
+- (id) createRetainedRid;
+
+- (NSString *) lastError;
+
+@end
+
+#endif // _INCLUDED_JSONPort_h
View
391 cocoa/JSON/JSONPort.m
@@ -0,0 +1,391 @@
+#include "JSONPort.h"
+#include <Foundation/NSStream.h>
+#include <Foundation/NSDictionary.h>
+#include <Foundation/NSString.h>
+#include <Foundation/NSValue.h>
+#include <Foundation/NSNull.h>
+
+#include "ByteStream.h"
+#include "JSONExtensions.h"
+
+
+@interface JSONPort (PrivateMethods)
+
+- (void) closeConnection;
+
+- (BOOL) sendMessage: (NSDictionary *) msg;
+
+@end
+
+
+#define ALLOC_REQUEST( met, par, rid ) \
+ [[NSDictionary alloc] initWithObjectsAndKeys: \
+ (met), @"method", (par), @"params", (rid), @"id", 0]
+
+#define ALLOC_RESPONSE( res, err, rid ) \
+ [[NSDictionary alloc] initWithObjectsAndKeys: \
+ (res), @"result", (err), @"error", (rid), @"id", 0]
+
+#define SET_ERROR( fmt, args... ) \
+ do { \
+ [_error release]; \
+ _error = [[NSString alloc] initWithFormat: fmt , ##args]; \
+ } while (0)
+
+#define SET_ERROR_AND_FAIL( fmt, args... ) \
+ do { \
+ [_error release]; \
+ _error = [[NSString alloc] initWithFormat: fmt , ##args]; \
+ return 0; \
+ } while (0)
+
+
+@implementation JSONPort
+
+- (id) initWithHost: (NSHost *) host port: (int) port
+{
+ NSInputStream * is;
+ NSOutputStream * os;
+ [NSStream getStreamsToHost: host port: port
+ inputStream: &is outputStream: &os ];
+
+ return [self initWithInputStream: is outputStream: os];
+}
+
+- (id) initWithLocalSocket: (NSString *) path
+{
+ NSInputStream * is;
+ NSOutputStream * os;
+ [NSStream getLocalStreamsToPath: path
+ inputStream: &is outputStream: &os ];
+
+ return [self initWithInputStream: is outputStream: os];
+}
+
+- (id) initWithInputStream: (NSInputStream *) iStream
+ outputStream: (NSOutputStream *) oStream
+{
+ if ( (self = [super init]) == 0 )
+ return 0;
+
+ _is = 0;
+ _os = 0;
+ _error = 0;
+
+ _NullClass = [NSNull class];
+ _null = [NSNull null];
+
+ if ( ! (iStream && oStream) )
+ {
+ [self release];
+ return 0;
+ }
+
+ _is = [[ByteIStream alloc] initWithNSIStream: iStream ];
+ if ( ! _is )
+ {
+ [self release];
+ return 0;
+ }
+
+ _os = [[ByteOStream alloc] initWithNSOStream: oStream ];
+ if ( ! _os )
+ {
+ [self release];
+ return 0;
+ }
+
+ return self;
+}
+
+- (id) initWithDuplexFD: (int) fd
+{
+ return [self initWithInputFD: fd outputFD: fd];
+}
+
+- (id) initWithInputFD: (int) fdin outputFD: (int) fdout
+{
+ if ( (self = [super init]) == 0 )
+ return 0;
+
+ _is = 0;
+ _os = 0;
+ _error = 0;
+
+ _NullClass = [NSNull class];
+ _null = [NSNull null];
+
+ _is = [[ByteIStream alloc] initWithFD: fdin ];
+ if ( ! _is )
+ {
+ [self release];
+ return 0;
+ }
+
+ _os = [[ByteOStream alloc] initWithFD: fdout ];
+ if ( ! _os )
+ {
+ [self release];
+ return 0;
+ }
+
+ return self;
+}
+
+- (void) dealloc
+{
+ [_is release];
+ [_os release];
+
+ [_error release];
+
+ [super dealloc];
+}
+
+- (NSString *) lastError
+{
+ return _error;
+}
+
+- (void) closeConnection
+{
+ [_os release];
+ [_is release];
+ _is = nil;
+ _os = nil;
+}
+
+- (NSDictionary *) getRetainedMessage
+{
+ if ( ! (_is && _os) )
+ SET_ERROR_AND_FAIL( @"No connection" );
+
+ NSString * parseErr = 0;
+ NSMutableDictionary * msg =
+ [[NSDictionary alloc] initFromJSONStream: _is error: &parseErr];
+
+ if ( ! msg )
+ {
+ if ( parseErr )
+ {
+ SET_ERROR( @"Cannot parse message: %@. Closing connection.",
+ parseErr );
+
+ [self sendError: _error rid: _null];
+ }
+ else
+ {
+ // parseErr == 0 means end of input stream.
+ [_error release];
+ _error = 0;
+ }
+
+ [self closeConnection];
+ return 0;
+ }
+
+ // Check that the message has the right format.
+ // Here we do not do rigorous check and allow for missing keys.
+ // We treat them as the ones with null values.
+
+ NSString * error = 0;
+
+ id rid = [msg rid];
+ id met = [msg method];
+ id res = [msg result];
+ id err = [msg error];
+
+ if ( met )
+ {
+ // request
+
+ id pms = [msg params];
+
+ if ( res )
+ error = @"both \"method\" and \"result\" keys exit";
+ else if (err)
+ error = @"both \"method\" and \"error\" keys exist";
+ else if ( ! [met isKindOfClass: [NSString class]] )
+ error = @"method is not a string";
+ else if ( [met isEqualToString: @""] )
+ error = @"method is empty string";
+ else if ( pms && ! [pms isKindOfClass: [NSArray class]] )
+ error = @"params is not an array or null";
+
+ }
+ else
+ {
+ // response
+
+ if ( !rid )
+ error = @"response without \"id\"";
+ else if ( !res && !err )
+ error = @"both result and error set to null";
+ // if both are not null, assume that error takes priority
+ }
+
+ if ( error )
+ {
+ SET_ERROR( @"Protocol error: %@.", error );
+ [msg release];
+ msg = 0;
+
+ // Send the error back to the client if the client expects
+ // the response, i.e. if met is set and rid is non-null.
+ // Do we really need this ??
+
+ if ( met && rid )
+ {
+ NSString * protocolError = _error;
+ [protocolError retain];
+
+ BOOL sendStatus = [self sendError: _error rid: rid];
+ NSString * sendError = _error;
+ [sendError retain];
+
+ if ( sendStatus )
+ SET_ERROR( @"%@ Error response sent back.", protocolError );
+ else
+ SET_ERROR( @"%@ An attempt to send the error back failed: %@",
+ protocolError, sendError);
+
+ [sendError release];
+ [protocolError release];
+ }
+ }
+
+ return msg;
+}
+
+- (BOOL) sendMessage: (NSDictionary *) msg
+{
+ if ( ! _os )
+ SET_ERROR_AND_FAIL( @"sendMessage: No connection" );
+
+ BOOL status = [msg writeToJSONStream: _os];
+
+ if ( status )
+ status = (str_flush( _os ) == 0);
+
+ if ( ! status )
+ SET_ERROR( @"sendMessage: writeToJSONStream failed");
+
+ return status;
+}
+
+- (BOOL) sendResult: (id) result rid: (id) rid
+{
+ if ( ! result )
+ SET_ERROR_AND_FAIL( @"sendResult: result is required" );
+ if ( ! rid )
+ SET_ERROR_AND_FAIL( @"sendResult: requestID is required" );
+ if ( [rid isKindOfClass: _NullClass] )
+ SET_ERROR_AND_FAIL( @"sendResult: requiestID cannot be NSNull" );
+
+ NSDictionary * msg = ALLOC_RESPONSE( result, _null, rid );
+ if ( ! msg )
+ SET_ERROR_AND_FAIL( @"sendResult: cannot allocate memory" );
+
+ BOOL status = [self sendMessage: msg];
+
+ [msg release];
+ return status;
+}
+
+- (BOOL) sendError: (id) error rid: (id) rid
+{
+ if ( ! error )
+ SET_ERROR_AND_FAIL( @"sendError: error is required" );
+ if ( ! rid )
+ SET_ERROR_AND_FAIL( @"sendError: requestID is required" );
+ if ( [rid isKindOfClass: _NullClass] )
+ SET_ERROR_AND_FAIL( @"sendError: requiestID cannot be NSNull" );
+
+ NSDictionary * msg = ALLOC_RESPONSE( _null, error, rid );
+ if ( ! msg )
+ SET_ERROR_AND_FAIL( @"sendError: cannot allocate memory" );
+
+ BOOL status = [self sendMessage: msg];
+
+ [msg release];
+ return status;
+}
+
+- (BOOL) sendNotification: (NSString *) method params: (NSArray *) params
+{
+ return [self sendMethod: method params: params rid: _null];
+}
+
+- (BOOL) sendMethod: (NSString *) method params: (NSArray *) params
+ rid: (id) rid
+{
+ if ( ! method )
+ SET_ERROR_AND_FAIL( @"sendMethod: method is required" );
+
+ NSDictionary * msg =
+ ALLOC_REQUEST( method, params ? (id)params : _null, rid ? rid : _null);
+ if ( ! msg )
+ SET_ERROR_AND_FAIL( @"sendMethod: cannot allocate memory" );
+
+ BOOL status = [self sendMessage: msg];
+
+ [msg release];
+ return status;
+}
+
+
+/**
+ * Makes blocking method call: sends method and waits for response.
+ * Returns retained dictionary that contains response or 0 on error.
+ */
+- (id) makeMethodCall: (NSString *) method params: (NSArray *) params
+{
+ id rid = [self createRetainedRid];
+ if ( ! rid || [rid isKindOfClass: _NullClass] )
+ SET_ERROR_AND_FAIL( @"makeMethodCall: request ID cannot be null" );
+
+ BOOL status = [self sendMethod: method params: params rid: rid];
+
+ NSDictionary * response = 0;
+
+ if ( status == NO )
+ goto communicationFailure;
+
+ // Get response back from the server.
+ // BUG: we need to implement timeout.
+
+ response = [self getRetainedMessage];
+ if ( ! response )
+ {
+ if ( ! _error )
+ SET_ERROR( @"unexpected end of stream while waiting for response" );
+ goto communicationFailure;
+ }
+
+ if ( ! [response objectForKey: @"result"] )
+ {
+ SET_ERROR( @"wrong message type: request instead of response" );
+ goto communicationFailure;
+ }
+
+ if ( ! [rid isEqual: [response rid]] )
+ {
+ SET_ERROR( @"response with wrong ID" );
+ goto communicationFailure;
+ }
+
+ [rid release];
+ return response;
+
+communicationFailure:
+ [response release];
+ [rid release];
+ return 0;
+}
+
+- (id) createRetainedRid
+{
+ return [[NSNumber alloc] initWithInt: 1];
+}
+
+@end // JSONPort
View
204 cocoa/JSON/NSArray+JSON.m
@@ -0,0 +1,204 @@
+#include "JSONExtensions.h"
+#include <Foundation/NSArray.h>
+#include <Foundation/NSString.h>
+#include <ctype.h>
+#include "ByteStream.h"
+#include "streamutils.h"
+
+static const char * prefixAdd = " ";
+
+@implementation NSArray (JSON)
+
+- (id) initFromJSONStream: (ByteIStream *) str error: (NSString **) err
+{
+ [self release];
+ return [[NSMutableArray alloc] initFromJSONStream: str error: err];
+}
+
+- (BOOL) writeToJSONStream: (ByteOStream *) str prefix: (NSString *) prefix
+{
+ if ( str_putc( '[', str ) == -1 )
+ return NO;
+
+ NSString * newprefix = 0;
+ const char * pref = 0;
+ if ( prefix )
+ {
+ newprefix = [[NSString alloc] initWithFormat: @"%@%s",
+ prefix, prefixAdd];
+ pref = [newprefix cString];
+ str_putc( '\n', str );
+ }
+
+ unsigned count = [self count];
+ unsigned i;
+ for ( i = 0; i < count; ++i )
+ {
+ if ( i )
+ {
+ if ( str_putc( ',', str ) == -1 )
+ goto failure;
+
+ // Put '\n' after every key-value pair
+ if ( prefix && str_putc( '\n', str ) == -1 )
+ goto failure;
+ }
+
+ // Put prefix before every element
+ if ( pref && str_puts( pref, str ) == -1 )
+ goto failure;
+
+ if ([[self objectAtIndex: i] writeToJSONStream: str
+ prefix: newprefix] == NO)
+ goto failure;
+ }
+
+ // Put newline and prefix before ']' if not an empty array
+ if ( prefix && i )
+ {
+ if ( str_putc( '\n', str ) == -1 )
+ goto failure;
+ if ( str_puts( [prefix cString], str ) == -1 )
+ goto failure;
+ }
+
+ if ( str_putc( ']', str ) == -1 )
+ goto failure;
+
+ [newprefix release];
+ return YES;
+
+ failure:
+ [newprefix release];
+ return NO;
+}
+
+@end
+
+@implementation NSMutableArray (JSON)
+
+/*
+ State machine diagram.
+
+ ----------------------
+ | ^
+ | |
+ v |
+ [Start]-( [ )->[BeforeO]-(obj)->[AfterO]-( , )->[AfterC]-(obj)
+ ^ |
+ |<--->(space) |<--->(space)
+ | |
+ | -----( ] )->[Success]
+ |
+ -----( ] )->[Success]
+
+*/
+
+- (id) initFromJSONStream: (ByteIStream *) str error: (NSString **) err
+{
+ enum State { Start, BeforeObj, AfterObj, AfterComma, Success, Fail };
+
+ NSString * F_NOOPENBRACKET = @"JSON array: expected '[', got '%c'";
+ NSString * F_BADDELIM = @"JSON array: unexpected delimiter '%c'";
+ NSString * F_EOF = @"JSON array: unexpected EOF";
+
+ self = [self initWithCapacity: 0];
+
+ id obj = 0;
+
+ enum State state = Start;
+
+ // Skip leading blanks
+ int ch = peek_nonblank_char( str );
+
+ // Return 0 if the stream ended
+ if ( ch == -1 && str_eof( str ) )
+ {
+ [self release];
+ return 0;
+ }
+
+ // Do the parsing
+ while ( (ch = str_getc(str)) != -1 )
+ {
+ switch ( state )
+ {
+ case Start:
+ state = ( ch == '[' ) ? BeforeObj : Fail;
+ if ( state == Fail && err )
+ *err = [NSString stringWithFormat: F_NOOPENBRACKET, ch];
+ break;
+ case BeforeObj:
+ if ( ch == ']' )
+ state = Success;
+ else if ( isspace( ch ) )
+ ;
+ else
+ {
+ str_ungetc( ch, str );
+ obj = [[NSObject alloc] initFromJSONStream: str
+ error: err];
+ state = obj ? AfterObj : Fail;
+ }
+ break;
+ case AfterObj:
+ if ( ch == ',' )
+ {
+ [self addObject: obj];
+ [obj release];
+ obj = 0;
+ state = AfterComma;
+ }
+ else if ( ch == ']' )
+ state = Success;
+ else if ( isspace( ch ) )
+ ;
+ else
+ {
+ if ( err )
+ *err = [NSString stringWithFormat: F_BADDELIM, ch];
+ state = Fail;
+ }
+ break;
+ case AfterComma:
+ str_ungetc( ch, str );
+ obj = [[NSObject alloc] initFromJSONStream: str error: err];
+ state = obj ? AfterObj : Fail;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (state)
+ {
+ case Fail:
+ str_ungetc( ch, str );
+ goto fail;
+ break;
+ case Success:
+ goto success;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // We should not get here
+ if ( err )
+ *err = [NSString stringWithFormat: F_EOF];
+
+ fail:
+ [self release];
+ return 0;
+
+ success:
+ if (obj)
+ {
+ [self addObject: obj];
+ [obj release];
+ }
+ return self;
+}
+
+@end
View
248 cocoa/JSON/NSDictionary+JSON.m
@@ -0,0 +1,248 @@
+#include "JSONExtensions.h"
+#include <Foundation/NSDictionary.h>
+#include <Foundation/NSString.h>
+#include <Foundation/NSNull.h>
+#include <Foundation/NSEnumerator.h>
+#include "ByteStream.h"
+#include "streamutils.h"
+
+static const char * prefixAdd = " ";
+
+@implementation NSDictionary (JSON)
+- (id) initFromJSONStream: (ByteIStream *) str error: (NSString **) err
+{
+ [self release];
+ return [[NSMutableDictionary alloc] initFromJSONStream: str error: err];
+}
+
+- (BOOL) writeToJSONStream: (ByteOStream *) str prefix: (NSString *) prefix
+{
+ NSString * newprefix = 0;
+ const char * pref = 0;
+ if ( prefix )
+ {
+ newprefix = [[NSString alloc] initWithFormat: @"%@%s",
+ prefix, prefixAdd];
+ pref = [newprefix cString];
+ }
+
+ // open brace goes without prefix
+ if ( str_putc( '{', str ) == -1 )
+ goto failure;
+
+ if ( pref && str_putc( '\n', str ) == -1 )
+ goto failure;
+
+ NSEnumerator *enumerator = [self keyEnumerator];
+
+ id key;
+ id value;
+
+ unsigned i = 0;
+
+ while ((key = [enumerator nextObject]))
+ {
+ if (i++)
+ {
+ if (str_putc( ',', str ) == -1)
+ goto failure;
+
+ // Put '\n' after every key-value pair
+ if ( prefix && str_putc( '\n', str ) == -1 )
+ goto failure;
+ }
+
+ // In JSON key must be a string
+ if ([key isKindOfClass: [NSString class]] == NO)
+ goto failure;
+
+ // Put prefix before every key
+ if ( pref && str_puts( pref, str ) == -1 )
+ goto failure;
+
+ if ([key writeToJSONStream: str ] == NO)
+ goto failure;
+
+ if (str_putc( ':', str ) == -1)
+ goto failure;
+
+ value = [self objectForKey: key];
+ id obj = value ? value : (id)[NSNull null];
+ if ([obj writeToJSONStream: str prefix: newprefix ] == NO)
+ goto failure;
+ }
+
+ // Put newline and prefix before '}' if not an empty dictionary
+ if ( prefix && i )
+ {
+ if ( str_putc( '\n', str ) == -1 )
+ goto failure;
+ if ( str_puts( [prefix cString], str ) == -1 )
+ goto failure;
+ }
+
+ if ( str_putc( '}', str ) == -1 )
+ goto failure;
+
+ [newprefix release];
+ return YES;
+
+ failure:
+ [newprefix release];
+ return NO;
+}
+
+@end
+
+@implementation NSMutableDictionary (JSON)
+
+/*
+
+ State machine diagram.
+ ------------------------------------------
+ | ^
+ v |
+ [Start]-({)->[BKey]-(str)->[AKey]-(:)->[BVal]-(obj)->[AVal]-(,)->[AC]-(str)
+ | | |
+ | | |
+ |<->(space) <-->(space) |<-->(space)
+ | |
+ ---(})->[Success] ----(})->[Success]
+
+*/
+
+- (id) initFromJSONStream: (ByteIStream *) str error: (NSString **) err
+{
+ enum State {
+ Start, BeforeKey, AfterKey, BeforeVal, AfterVal, AfterComma,
+ Success, Fail
+ };
+
+ NSString * F_NOOPENBRACE = @"JSON dict: expected '{', got '%c'";
+ NSString * F_BADKEYDELIM = @"JSON dict: unexpected key delimiter '%c'";
+ NSString * F_BADVALDELIM =
+ @"JSON dict: unexpected value delimiter '%c', key=%@";
+ NSString * F_EOF = @"JSON dict: unexpected EOF";
+
+ self = [self initWithCapacity: 0];
+
+ NSString * key = 0;
+ id obj = 0;
+
+ enum State state = Start;
+
+ // Skip leading blanks
+ int ch = peek_nonblank_char( str );
+
+ // Return 0 if the stream ended
+ if ( ch == -1 && str_eof( str ) )
+ {
+ [self release];
+ return 0;
+ }
+
+ // Do the parsing
+ while ( (ch = str_getc(str)) != -1 )
+ {
+ switch ( state )
+ {
+ case Start:
+ state = ( ch == '{' ) ? BeforeKey : Fail;
+ if ( state == Fail && err )
+ *err = [NSString stringWithFormat: F_NOOPENBRACE, ch];
+ break;
+ case BeforeKey:
+ if ( ch == '}' )
+ state = Success;
+ else if ( isspace( ch ) )
+ ;
+ else
+ {
+ str_ungetc( ch, str );
+ key = [[NSString alloc] initFromJSONStream: str
+ error: err];
+ state = key ? AfterKey : Fail;
+ }
+ break;
+ case AfterKey:
+ if ( isspace(ch) )
+ ;
+ else if ( ch == ':' )
+ state = BeforeVal;
+ else
+ {
+ if ( err )
+ *err = [NSString stringWithFormat: F_BADKEYDELIM, ch];
+ state = Fail;
+ }
+ break;
+ case BeforeVal:
+ str_ungetc( ch, str );
+
+ obj = [[NSObject alloc] initFromJSONStream: str error: err];
+ state = obj ? AfterVal : Fail;
+ break;
+ case AfterVal:
+ if ( ch == ',' )
+ {
+ [self setObject: obj forKey: key];
+ [obj release];
+ [key release];
+ state = AfterComma;
+ }
+ else if ( ch == '}' )
+ state = Success;
+ else if ( isspace(ch ) )
+ ;
+ else
+ {
+ if ( err )
+ *err = [NSString stringWithFormat: F_BADVALDELIM, ch, key];
+ state = Fail;
+ }
+ break;
+ case AfterComma:
+ str_ungetc( ch, str );
+
+ key = [[NSString alloc] initFromJSONStream: str error: err];
+ state = key ? AfterKey : Fail;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (state)
+ {
+ case Fail:
+ str_ungetc( ch, str );
+ goto fail;
+ break;
+ case Success:
+ goto success;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // We should not get here
+ if ( err )
+ *err = [NSString stringWithFormat: F_EOF];
+
+ fail:
+ [self release];
+ return 0;
+
+ success:
+ if ( key && obj )
+ {
+ [self setObject: obj forKey: key];
+ [obj release];
+ [key release];
+ }
+
+ return self;
+}
+
+@end
View
37 cocoa/JSON/NSDictionary+JSONRPC.m
@@ -0,0 +1,37 @@
+#include "JSONPort.h"
+#include <Foundation/NSString.h>
+#include <Foundation/NSNull.h>
+
+@implementation NSDictionary (JSONRPC)
+
+- (NSString *) method
+{
+ return (NSString *)[self objectForKey: @"method"];
+}
+
+- (NSArray *) params
+{
+ id par = [self objectForKey: @"params"];
+ return (!par || [par isKindOfClass: [NSNull class]]) ? 0 : (NSArray *)par;
+}
+
+- (id) rid
+{
+ id rid = [self objectForKey: @"id"];
+ return (!rid || [rid isKindOfClass: [NSNull class]]) ? nil : rid;
+}
+
+- (id) result
+{
+ id res = [self objectForKey: @"result"];
+ return (!res || [res isKindOfClass: [NSNull class]]) ? nil : res;
+}
+
+- (id) error
+{
+ id err = [self objectForKey: @"error"];
+ return (!err || [err isKindOfClass: [NSNull class]]) ? nil : err;
+}
+
+@end // NSDictionary (JSONRPC)
+
View
289 cocoa/JSON/NSNumber+JSON.m
@@ -0,0 +1,289 @@
+#include "JSONExtensions.h"
+#include <Foundation/NSValue.h>
+#include <Foundation/NSString.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include "ByteStream.h"
+#include "streamutils.h"
+
+@implementation NSNumber (JSON)
+
+/*
+ I implement the parsing of JSON number to make an exersize in state machine
+ coding. It would be much simpler to read the token into a buffer and call
+ strtod() after that.
+
+ State machine diagram for JSON number parsing.
+
+ I allow 2 extensions over http://json.org:
+ 1. '+' is allowed in front of the number,
+ 2. Unsigned hexadecimal numbers are allowed in the form 0<x|X><hex-digit>.
+
+ -> (0)-->[UnsignedZero]
+
+ -> (1-9)---------------------
+ |
+ v
+ -> (+-)-->[Sign]-(1-9)->[SignedNum]-(.)->[Frac]-(0-9)->[InFrac]-(eE)-->[Exp]
+ | | |
+ ----(0)->[S0] |<---->(0-9) |<->(0-9)
+ | |
+ |------(eE)->[Exp] -(delim)->[Success]
+ |
+ ------(delim)->[Success]
+
+
+ [Exp]-(+-)->[SignedExp]-(0-9)->[InExp]-(delim)->[Success]
+ | |
+ ---(0-9)->[InExp] <-->(0-9)
+
+
+
+ [S0]-(.)->[Frac]
+ |
+ |---(eE)->[Exp]
+ |
+ ---(delim)->[Success]
+
+
+ [UnsignedZero]-(xX)->[Hex]-(hex-digit)->[InHex]-(delim)->[Success]
+ | |
+ |-------(.)->[Frac] <-->(hex-digit)
+ |
+ |-------(eE)->[Exp]
+ |
+ -------(delim)->[Success]
+
+*/
+
+- (id) initFromJSONStream: (ByteIStream *) str error: (NSString **) err
+{
+ enum State {
+ Start, Sign, UnsignedZero, SignedZero, SignedNum, Frac, InFrac, Exp,
+ SignedExp, InExp, Hex, InHex, Success, Fail
+ };
+
+ char buffer[64];
+ int capa = sizeof(buffer)-1;
+ int i = 0;
+
+ BOOL fraction = NO; // number has '.'
+ BOOL exponent = NO; // number has 'e'
+
+ enum State state = Start;
+
+ // Skip leading blanks
+ int ch = peek_nonblank_char( str );
+
+ // Return 0 if the stream ended
+ if ( ch == -1 && str_eof( str ) )
+ {
+ [self release];
+ return 0;
+ }
+
+ // Do the parsing
+ while ( (ch = str_getc(str)) != -1 && i < capa )
+ {
+ switch (state)
+ {
+ case Start:
+ switch (ch)
+ {
+ case '+': case '-': state = Sign; break;
+ case '0': state = UnsignedZero; break;
+ case '1' ... '9': state = SignedNum; break;
+ default: state = Fail; break;
+ }
+ break;
+ case Sign:
+ switch (ch)
+ {
+ case '0': state = SignedZero; break;
+ case '1' ... '9': state = SignedNum; break;
+ default: state = Fail; break;
+ }
+ break;
+ case SignedZero:
+ switch (ch)
+ {
+ case '.': state = Frac; break;
+ case 'e': case 'E': state = Exp; break;
+ default: state = Success; break;
+ }
+ break;
+ case UnsignedZero:
+ switch (ch)
+ {
+ case '.': state = Frac; break;
+ case 'e': case 'E': state = Exp; break;
+ case 'x': case 'X': state = Hex; break;
+ default: state = Success; break;
+ }
+ break;
+ case SignedNum:
+ switch (ch)
+ {
+ case '0' ... '9': state = SignedNum; break;
+ case '.': state = Frac; break;
+ case 'e': case 'E': state = Exp; break;
+ default: state = Success; break;
+ }
+ break;
+ case Frac:
+ fraction = YES;
+ switch (ch)
+ {
+ case '0' ... '9': state = InFrac; break;
+ default: state = Fail; break;
+ }
+ break;
+ case InFrac:
+ switch (ch)
+ {
+ case '0' ... '9': state = InFrac; break;
+ case 'e': case 'E': state = Exp; break;
+ default: state = Success; break;
+ }
+ break;
+ case Exp:
+ exponent = YES;
+ switch (ch)
+ {
+ case '+': case '-': state = SignedExp; break;
+ case '0' ... '9': state = InExp; break;
+ default: state = Fail; break;
+ }
+ break;
+ case SignedExp:
+ switch (ch)
+ {
+ case '0' ... '9': state = InExp; break;
+ default: state = Fail; break;
+ }
+ break;
+ case InExp:
+ switch (ch)
+ {
+ case '0' ... '9': state = InExp; break;
+ default: state = Success; break;
+ }
+ break;
+ case Hex:
+ switch (ch)
+ {
+ case '0' ... '9':
+ case 'a' ... 'f':
+ case 'A' ... 'F': state = InHex; break;
+ default: state = Fail; break;
+ }
+ break;
+ case InHex:
+ switch (ch)
+ {
+ case '0' ... '9':
+ case 'a' ... 'f':
+ case 'A' ... 'F': state = InHex; break;
+ default: state = Success; break;
+ }
+ break;
+ case Success: // fall through
+ break;
+ case Fail:
+ break;
+ }
+
+ if (state == Success || state == Fail)
+ {
+ str_ungetc( ch, str ); // TODO: process error in ungetc()
+ goto endparse;
+ }
+
+ // add valid character to buffer
+ buffer[i++] = ch;
+ }
+
+ endparse:
+ if ( state != Success )
+ {
+ if ( err )
+ {
+ if ( state == Fail )
+ *err = [NSString stringWithFormat:
+ @"number: unexpected character '%c'", ch];
+ else if ( ch == -1 )
+ *err = [NSString stringWithFormat: @"number: unexpected EOF"];
+ else if ( i == capa )
+ *err = [NSString stringWithFormat:
+ @"number: can only hold %d characters", capa];
+ else
+ *err = [NSString stringWithFormat: @"number: internal error"];
+ }
+ goto failure;
+ }
+
+ // Convert buffer to a number and create NSNumber
+
+ buffer[i] = 0;
+
+ char * endp = 0;
+ errno = 0;
+ double dresult;
+ long long lresult;
+ if ( fraction || exponent )
+ dresult = strtod( buffer, &endp );
+ else
+ lresult = strtoll( buffer, &endp, 0 );
+
+ if ( errno == ERANGE )
+ {
+ if ( err )
+ *err = [NSString stringWithFormat:
+ @"number \"%s\" is out of range", buffer];
+ goto failure;
+ }
+ else if ( *endp != '\0' )
+ {
+ if ( err )
+ *err = [NSString stringWithFormat:
+ @"number \"%s\" is inconsistent with libc",
+ buffer];
+ goto failure;
+ }
+
+ // Select the proper type among integer ones to save memory.
+
+ if (fraction || exponent)
+ return [self initWithDouble: dresult];
+ else if ( SHRT_MIN <= lresult && lresult <= SHRT_MAX )
+ return [self initWithShort: lresult];
+ else if ( INT_MIN <= lresult && lresult <= INT_MAX )
+ return [self initWithInt: lresult];
+ else if ( LONG_MIN <= lresult && lresult <= LONG_MAX )
+ return [self initWithLong: lresult];
+ else
+ return [self initWithLongLong: lresult];
+
+ failure:
+ [self release];
+ return 0;
+}
+
+- (BOOL) writeToJSONStream: (ByteOStream *) str prefix: (NSString *) unused
+{
+ const char * p;
+
+ // BOOL is typdef'ed to unsigned char. This code will work only
+ // if there is no unsigned chars among JSON scalar types.
+ if ( ! strcmp( [self objCType], @encode(BOOL) ) )
+ p = [self boolValue] ? "true" : "false";
+ else
+ p = [[self description] cString];
+
+ int ret = str_puts( p, str );
+ return (ret == -1) ? NO : YES;
+}
+
+@end
View
61 cocoa/JSON/NSObject+JSON.h
@@ -0,0 +1,61 @@
+/*
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+#pragma mark JSON Writing
+
+/// Adds JSON generation to NSArray
+@interface NSArray (NSArray_SBJsonWriting)
+
+/// Returns a string containing the receiver encoded in JSON.
+- (NSString *)JSONRepresentation;
+
+@end
+
+
+/// Adds JSON generation to NSArray
+@interface NSDictionary (NSDictionary_SBJsonWriting)
+
+/// Returns a string containing the receiver encoded in JSON.
+- (NSString *)JSONRepresentation;
+
+@end
+
+#pragma mark JSON Parsing
+
+/// Adds JSON parsing methods to NSString
+@interface NSString (NSString_SBJsonParsing)
+
+/// Returns the NSDictionary or NSArray represented by the receiver's JSON representation, or nil on error
+- (id)JSONValue;
+
+@end
+
+
View
106 cocoa/JSON/NSObject+JSON.m
@@ -0,0 +1,106 @@
+#include "JSONExtensions.h"
+#include <Foundation/NSObject.h>
+#include <Foundation/NSValue.h>
+#include <Foundation/NSString.h>
+#include <Foundation/NSNull.h>
+#include <Foundation/NSArray.h>
+#include <Foundation/NSDictionary.h>
+#include <ctype.h> // isdigit()
+#include "ByteStream.h"
+#include "streamutils.h"
+
+@implementation NSObject (JSON)
+- (id) initFromJSONStream: (ByteIStream *) str error: (NSString **) err
+{
+ id result = 0;
+
+ // Skip leading blanks
+ int c = peek_nonblank_char( str );
+
+ // Return 0 if the stream ended
+ if ( c == -1 && str_eof( str ) )
+ {
+ [self release];
+ return 0;
+ }
+
+ switch (c)
+ {
+ case '{':
+ result = [[NSMutableDictionary alloc] initFromJSONStream: str error: err];
+ break;
+ case '[':
+ result = [[NSMutableArray alloc] initFromJSONStream: str error: err];
+ break;
+ case '"':
+ result = [[NSString alloc] initFromJSONStream: str error: err];
+ break;
+ case 't': // fall through
+ case 'f':
+ case 'n':
+ {
+ char buffer[8];
+ memset( buffer, 0, sizeof(buffer) );
+
+ str_getbytes( str, buffer, (c=='f') ? 5 : 4 );
+ if ( strcmp( buffer, "true" ) == 0 )
+ result = [[NSNumber alloc] initWithBool: YES];
+ else if ( strcmp( buffer, "false" ) == 0 )
+ result = [[NSNumber alloc] initWithBool: NO];
+ else if ( strcmp( buffer, "null" ) == 0 )
+ result = [[NSNull alloc] init]; // [NSNull null];
+ else if ( err )
+ *err = [NSString stringWithFormat:
+ @"unrecognized word %s", buffer];
+ }
+ break;
+ default:
+ {
+ if ( isdigit(c) || c == '+' || c == '-' )
+ result = [[NSNumber alloc] initFromJSONStream: str error: err];
+ else if ( err )
+ *err = [NSString stringWithFormat:
+ @"unexpected first character '%c'", c];
+ }
+ break;
+ }
+
+ [self release];
+ return result;
+}
+
+- (BOOL) writeToJSONStream: (ByteOStream *) str prefix: (NSString *) prefix
+{
+ // If the method is not overriden, this object
+ // cannot be represented in JSON
+
+ if ( [self isKindOfClass: [NSDictionary class]] == NO )
+ {
+ // We should always get here since the method -writeToJSONStream
+ // should be implemented for NSDictionary.
+ // The "if" is an extra protection against infinite recursion.
+ NSMutableDictionary * dict = [NSMutableDictionary dictionary];
+ [dict setObject: [self className] forKey: @"class"];
+ [dict setObject: @"unknown JSON representation" forKey: @"error"];
+ return [dict writeToJSONStream: str prefix: prefix];
+ }
+
+ return NO; // should never get here
+}
+
+- (BOOL) writeToJSONStream: (ByteOStream *) str
+{
+ return [self writeToJSONStream: str prefix: 0];
+}
+
+@end
+
+@implementation NSNull (JSON)
+
+- (BOOL) writeToJSONStream: (ByteOStream *) str prefix: (NSString *) unused
+{
+ int ret = str_puts( "null", str );
+ return (ret == -1) ? NO : YES;
+}
+
+@end
View
214 cocoa/JSON/NSString+JSON.m
@@ -0,0 +1,214 @@
+#include "JSONExtensions.h"
+#include <Foundation/NSString.h>
+#include <Foundation/NSZone.h>
+#include "ByteStream.h"
+#include "streamutils.h"
+#include "jsunicode.h"
+
+static BOOL s_preferUtf8ForWrite = YES;
+
+@implementation NSString (JSON)
+
++ (BOOL) preferUtf8ForWritingJSON
+{
+ return s_preferUtf8ForWrite;
+}
+
++ (void) setPreferUtf8ForWritingJSON: (BOOL) val
+{
+ s_preferUtf8ForWrite = val;
+}
+
+- (id) initFromJSONStream: (ByteIStream *) str error: (NSString **) err
+{
+ int capa = 32;
+ unsigned char * buffer = (unsigned char *)NSZoneMalloc([self zone], capa);
+ if ( ! buffer )
+ {
+ if ( err )
+ *err = [NSString stringWithFormat: @"NSZoneMalloc() failed"];
+ goto failure;
+ }
+
+
+ int i = 0; // position for writing in buffer
+
+ int uChar4Hex = -1; // differs from -1 if we have 4hex representation
+ int charLength = 1; // may differ from 1 if we have 4hex representation
+
+ // First character must be '"'
+ int ch = peek_nonblank_char( str );
+
+ // Return 0 if the stream ended
+ if ( ch == -1 && str_eof( str ) )
+ {
+ [self release];
+ return 0;
+ }
+
+ if ( ch != '"' )
+ {
+ if ( err )
+ *err = [NSString stringWithFormat: @"first char is not \""];
+ goto failure;
+ }
+
+ str_getc( str ); // eat leading '"'
+
+ BOOL escape = NO;
+ BOOL escaped = NO;
+ while ( (ch = str_getc( str )) != -1 )
+ {
+ escape = (ch == '\\' && escape == NO);
+ if ( escape )
+ {
+ escaped = YES;
+ continue;
+ }
+
+ if ( !escaped && ch == '"' )
+ {
+ // unescaped '"' ends the string
+ goto success;
+ }
+
+ if ( escaped )
+ {
+ escaped = NO;
+ switch ( ch )
+ {
+ case 'n': ch = '\n'; break;
+ case 't': ch = '\t'; break;
+ case 'r': ch = '\r'; break;
+ case 'b': ch = '\b'; break;
+ case 'f': ch = '\f'; break;
+ case 'u':
+ {
+ uChar4Hex = get4hex( str );
+ if ( uChar4Hex == -1 )
+ {
+ if ( err )
+ [NSString stringWithFormat: @"can't read 4 hex"];
+ goto failure;
+ }
+ charLength = utf8length( uChar4Hex );
+ }