diff --git a/Makefile b/Makefile index cd5905da..c161dae0 100644 --- a/Makefile +++ b/Makefile @@ -15,21 +15,20 @@ VPATH = app app/en.lproj json oniguruma oniguruma/enc universalchardet lemon \ NO_ARC_OBJC_SRCS = \ NSObject+SPInvocationGrabbing.m \ - Nu.m \ - $(JSON_OBJC_SRCS) + Nu.m JSON_OBJC_SRCS = \ - NSObject+JSON.m \ SBJsonParser.m \ SBJsonStreamParser.m \ SBJsonStreamParserAdapter.m \ SBJsonStreamParserAccumulator.m \ - SBJsonStreamParserTokeniser.m \ - SBJsonStreamParserWriterAccumulator.m \ + SBJsonStreamParserAdapter.m \ SBJsonStreamParserState.m \ SBJsonStreamWriter.m \ + SBJsonStreamWriterAccumulator.m \ SBJsonStreamWriterState.m \ SBJsonTokeniser.m \ + SBJsonUTF8Stream.m \ SBJsonWriter.m OBJC_SRCS = \ @@ -145,6 +144,7 @@ OBJC_SRCS = \ ViWindow.m \ ViWindowController.m \ ViWordCompletion.m \ + $(JSON_OBJC_SRCS) \ main.m OBJCXX_SRCS = \ diff --git a/app/ViAppController.m b/app/ViAppController.m index 5c379606..118bd954 100644 --- a/app/ViAppController.m +++ b/app/ViAppController.m @@ -37,7 +37,7 @@ #import "ViPreferencePaneAdvanced.h" #import "TMFileURLProtocol.h" #import "TxmtURLProtocol.h" -#import "JSON.h" +#import "SBJson.h" #import "ViError.h" #import "ViCommandMenuItemView.h" #import "ViEventManager.h" @@ -649,7 +649,9 @@ - (NSString *)eval:(NSString *)script if ([result isKindOfClass:[NSNull class]]) return nil; - return [result JSONRepresentation]; + + SBJsonWriter *writer = [[SBJsonWriter alloc] init]; + return [writer stringWithObject:result]; } - (NSError *)openURL:(NSString *)pathOrURL andWait:(BOOL)waitFlag backChannel:(NSString *)channelName diff --git a/app/ViPreferencePaneBundles.m b/app/ViPreferencePaneBundles.m index 61217b52..ae3ce4a9 100644 --- a/app/ViPreferencePaneBundles.m +++ b/app/ViPreferencePaneBundles.m @@ -25,7 +25,7 @@ #import "ViPreferencePaneBundles.h" #import "ViBundleStore.h" -#import "JSON.h" +#import "SBJson.h" #include "logging.h" @implementation repoUserTransformer @@ -167,15 +167,20 @@ - (void)loadBundlesFromRepo:(NSString *)username return; NSData *jsonData = [NSData dataWithContentsOfFile:path]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - NSArray *arry = [jsonString JSONValue]; + + SBJsonParser *parser = [[SBJsonParser alloc] init]; + NSArray *arry = [parser objectWithString:jsonString]; if (![arry isKindOfClass:[NSArray class]]) { - INFO(@"%s", "failed to parse JSON"); + INFO(@"%s: %@", "failed to parse JSON, error was ", parser.error); return; } [_repositories addObjectsFromArray: arry]; } else { NSString *path = [self repoPathForUser:username readonly:NO]; - [[_repoJson JSONRepresentation] writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL]; + + SBJsonWriter *writer = [[SBJsonWriter alloc] init]; + NSString *json =[writer stringWithObject:_repoJson]; + [json writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL]; [_repositories addObjectsFromArray:_repoJson]; } @@ -457,7 +462,9 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection NSString *jsonString = [[NSString alloc] initWithData:_userData encoding:NSUTF8StringEncoding]; _userData = nil; - NSDictionary *dict = [jsonString JSONValue]; + + SBJsonParser *parser = [[SBJsonParser alloc] init]; + NSDictionary *dict = [parser objectWithString:jsonString]; if (![dict isKindOfClass:[NSDictionary class]]) { [self cancelProgressSheet:nil]; [progressDescription setStringValue:[NSString stringWithFormat:@"Failed to parse data for user %@.", username]]; @@ -492,7 +499,9 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection if (connection == _repoConnection) { NSString *jsonString = [[NSString alloc] initWithData:_repoData encoding:NSUTF8StringEncoding]; - NSArray *repoJson = [jsonString JSONValue]; + + SBJsonParser *parser = [[SBJsonParser alloc] init]; + NSArray *repoJson = [parser objectWithString:jsonString]; [_repoJson addObjectsFromArray:repoJson]; NSDictionary *repoUser = [_processQueue lastObject]; diff --git a/json/JSON.h b/json/JSON.h deleted file mode 100644 index 89f15934..00000000 --- a/json/JSON.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - 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 Project home page. - @li Online version of the API documentation. - -*/ - - -// 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 -#import -#import -#import -#import -#import - -#endif diff --git a/json/NSObject+JSON.h b/json/NSObject+JSON.h deleted file mode 100644 index 4a9e7603..00000000 --- a/json/NSObject+JSON.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - 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 - -#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 - - diff --git a/json/NSObject+JSON.m b/json/NSObject+JSON.m deleted file mode 100644 index 3fc264fb..00000000 --- a/json/NSObject+JSON.m +++ /dev/null @@ -1,58 +0,0 @@ -/* - 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 "NSObject+JSON.h" -#import "SBJsonWriter.h" -#import "SBJsonParser.h" - -@implementation NSObject (NSObject_SBJsonWriting) - -- (NSString *)JSONRepresentation { - SBJsonWriter *jsonWriter = [SBJsonWriter new]; - NSString *json = [jsonWriter stringWithObject:self]; - if (!json) - NSLog(@"-JSONRepresentation failed. Error is: %@", jsonWriter.error); - return json; -} - -@end - - - -@implementation NSString (NSString_SBJsonParsing) - -- (id)JSONValue { - SBJsonParser *jsonParser = [SBJsonParser new]; - id repr = [jsonParser objectWithString:self]; - if (!repr) - NSLog(@"-JSONValue failed. Error is: %@", jsonParser.error); - return repr; -} - -@end diff --git a/json/SBJson.h b/json/SBJson.h index 58b9acb7..0a8e5632 100644 --- a/json/SBJson.h +++ b/json/SBJson.h @@ -32,5 +32,3 @@ #import "SBJsonStreamParser.h" #import "SBJsonStreamParserAdapter.h" #import "SBJsonStreamWriter.h" -#import "SBJsonStreamTokeniser.h" - diff --git a/json/SBJsonParser.h b/json/SBJsonParser.h index ddfff030..69c81c8f 100644 --- a/json/SBJsonParser.h +++ b/json/SBJsonParser.h @@ -71,15 +71,31 @@ - (id)objectWithData:(NSData*)data; /** - Parse string and return the represented dictionary or array. + Return the object represented by the given string - Calls objectWithData: internally. + This method converts its input to an NSData object containing UTF8 and calls -objectWithData: with it. - @param string An NSString containing JSON text. + @return The NSArray or NSDictionary represented by the object, or nil if an error occured. + */ +- (id)objectWithString:(NSString *)repr; + +/** + Return the object represented by the given string + + This method calls objectWithString: internally. If an error occurs, and if error + is not nil, it creates an NSError object and returns this through its second argument. + + @param jsonText the json string to parse + @param error pointer to an NSError object to populate on error @return The NSArray or NSDictionary represented by the object, or nil if an error occured. + + @warning Deprecated in Version 3.2; will be removed in 4.0 + */ -- (id)objectWithString:(NSString *)string; + +- (id)objectWithString:(NSString*)jsonText + error:(NSError**)error __attribute__ ((deprecated)); @end diff --git a/json/SBJsonParser.m b/json/SBJsonParser.m index 3b77508d..729e896c 100644 --- a/json/SBJsonParser.m +++ b/json/SBJsonParser.m @@ -27,9 +27,9 @@ 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. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonParser.h" #import "SBJsonStreamParser.h" @@ -84,8 +84,21 @@ - (id)objectWithData:(NSData *)data { return nil; } -- (id)objectWithString:(NSString *)string { - return [self objectWithData:[string dataUsingEncoding:NSUTF8StringEncoding]]; +- (id)objectWithString:(NSString *)repr { + return [self objectWithData:[repr dataUsingEncoding:NSUTF8StringEncoding]]; +} + +- (id)objectWithString:(NSString*)repr error:(NSError**)error_ { + id tmp = [self objectWithString:repr]; + if (tmp) + return tmp; + + if (error_) { + NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:error, NSLocalizedDescriptionKey, nil]; + *error_ = [NSError errorWithDomain:@"org.brautaset.SBJsonParser.ErrorDomain" code:0 userInfo:ui]; + } + + return nil; } @end diff --git a/json/SBJsonStreamParser.h b/json/SBJsonStreamParser.h index 930df492..619cd55b 100644 --- a/json/SBJsonStreamParser.h +++ b/json/SBJsonStreamParser.h @@ -32,6 +32,7 @@ #import +@class SBJsonTokeniser; @class SBJsonStreamParser; @class SBJsonStreamParserState; @@ -99,7 +100,8 @@ typedef enum { - object -> NSMutableDictionary - true -> NSNumber's -numberWithBool:YES - false -> NSNumber's -numberWithBool:NO - - number -> NSNumber + - integer up to 19 digits -> NSNumber's -numberWithLongLong: + - all other numbers -> NSDecimalNumber Since Objective-C doesn't have a dedicated class for boolean values, these turns into NSNumber instances. However, since these are @@ -107,15 +109,18 @@ typedef enum { properly. In other words, they won't silently suddenly become 0 or 1; they'll be represented as 'true' and 'false' again. - Integers are parsed into either a `long long` or `unsigned long long` - type if they fit, else a `double` is used. All real & exponential numbers - are represented using a `double`. Previous versions of this library used - an NSDecimalNumber in some cases, but this is no longer the case. - + As an optimisation integers up to 19 digits in length (the max length + for signed long long integers) turn into NSNumber instances, while + complex ones turn into NSDecimalNumber instances. We can thus avoid any + loss of precision as JSON allows ridiculously large numbers. + See also SBJsonStreamParserAdapter for more information. */ -@interface SBJsonStreamParser : NSObject +@interface SBJsonStreamParser : NSObject { +@private + SBJsonTokeniser *tokeniser; +} @property (nonatomic, unsafe_unretained) SBJsonStreamParserState *state; // Private @property (nonatomic, readonly, strong) NSMutableArray *stateStack; // Private diff --git a/json/SBJsonStreamParser.m b/json/SBJsonStreamParser.m index 7086ed0e..57d5016b 100644 --- a/json/SBJsonStreamParser.m +++ b/json/SBJsonStreamParser.m @@ -30,19 +30,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonStreamParser.h" -#import "SBJsonStreamTokeniser.h" +#import "SBJsonTokeniser.h" #import "SBJsonStreamParserState.h" -#define SBStringIsSurrogateHighCharacter(character) ((character >= 0xD800UL) && (character <= 0xDBFFUL)) - -@implementation SBJsonStreamParser { - SBJsonStreamTokeniser *tokeniser; -} +@implementation SBJsonStreamParser @synthesize supportMultipleDocuments; @synthesize error; @@ -59,7 +55,7 @@ - (id)init { maxDepth = 32u; stateStack = [[NSMutableArray alloc] initWithCapacity:maxDepth]; state = [SBJsonStreamParserStateStart sharedInstance]; - tokeniser = [[SBJsonStreamTokeniser alloc] init]; + tokeniser = [[SBJsonTokeniser alloc] init]; } return self; } @@ -69,25 +65,24 @@ - (id)init { - (NSString*)tokenName:(sbjson_token_t)token { switch (token) { - case sbjson_token_array_open: + case sbjson_token_array_start: return @"start of array"; break; - case sbjson_token_array_close: + case sbjson_token_array_end: return @"end of array"; break; - case sbjson_token_integer: - case sbjson_token_real: + case sbjson_token_number: return @"number"; break; - case sbjson_token_string: - case sbjson_token_encoded: + case sbjson_token_string: return @"string"; break; - case sbjson_token_bool: + case sbjson_token_true: + case sbjson_token_false: return @"boolean"; break; @@ -95,19 +90,19 @@ - (NSString*)tokenName:(sbjson_token_t)token { return @"null"; break; - case sbjson_token_entry_sep: + case sbjson_token_keyval_separator: return @"key-value separator"; break; - case sbjson_token_value_sep: + case sbjson_token_separator: return @"value separator"; break; - case sbjson_token_object_open: + case sbjson_token_object_start: return @"start of object"; break; - case sbjson_token_object_close: + case sbjson_token_object_end: return @"end of object"; break; @@ -177,10 +172,8 @@ - (SBJsonStreamParserStatus)parse:(NSData *)data_ { if ([state isError]) return SBJsonStreamParserError; - char *token; - NSUInteger token_len; - sbjson_token_t tok = [tokeniser getToken:&token length:&token_len]; - + NSObject *token; + sbjson_token_t tok = [tokeniser getToken:&token]; switch (tok) { case sbjson_token_eof: return [state parserShouldReturn:self]; @@ -200,78 +193,55 @@ - (SBJsonStreamParserStatus)parse:(NSData *)data_ { } switch (tok) { - case sbjson_token_object_open: + case sbjson_token_object_start: [self handleObjectStart]; break; - case sbjson_token_object_close: + case sbjson_token_object_end: [self handleObjectEnd: tok]; break; - case sbjson_token_array_open: + case sbjson_token_array_start: [self handleArrayStart]; break; - case sbjson_token_array_close: + case sbjson_token_array_end: [self handleArrayEnd: tok]; break; - case sbjson_token_value_sep: - case sbjson_token_entry_sep: + case sbjson_token_separator: + case sbjson_token_keyval_separator: [state parser:self shouldTransitionTo:tok]; break; - case sbjson_token_bool: - [delegate parser:self foundBoolean:token[0] == 't']; + case sbjson_token_true: + [delegate parser:self foundBoolean:YES]; [state parser:self shouldTransitionTo:tok]; break; - - case sbjson_token_null: - [delegate parserFoundNull:self]; + case sbjson_token_false: + [delegate parser:self foundBoolean:NO]; [state parser:self shouldTransitionTo:tok]; break; - - case sbjson_token_integer: { - const int UNSIGNED_LONG_LONG_MAX_DIGITS = 20; - if (token_len <= UNSIGNED_LONG_LONG_MAX_DIGITS) { - if (*token == '-') - [delegate parser:self foundNumber: @(strtoll(token, NULL, 10))]; - else - [delegate parser:self foundNumber: @(strtoull(token, NULL, 10))]; - - [state parser:self shouldTransitionTo:tok]; - break; - } - } - // FALLTHROUGH - - case sbjson_token_real: { - [delegate parser:self foundNumber: @(strtod(token, NULL))]; + + case sbjson_token_null: + [delegate parserFoundNull:self]; [state parser:self shouldTransitionTo:tok]; break; - } - - case sbjson_token_string: { - NSString *string = [[NSString alloc] initWithBytes:token length:token_len encoding:NSUTF8StringEncoding]; - if ([state needKey]) - [delegate parser:self foundObjectKey:string]; - else - [delegate parser:self foundString:string]; + + case sbjson_token_number: + [delegate parser:self foundNumber:(NSNumber*)token]; [state parser:self shouldTransitionTo:tok]; break; - } - - case sbjson_token_encoded: { - NSString *string = [self decodeStringToken:token length:token_len]; + + case sbjson_token_string: if ([state needKey]) - [delegate parser:self foundObjectKey:string]; + [delegate parser:self foundObjectKey:(NSString*)token]; else - [delegate parser:self foundString:string]; + [delegate parser:self foundString:(NSString*)token]; [state parser:self shouldTransitionTo:tok]; break; - } - + default: break; } @@ -282,57 +252,4 @@ - (SBJsonStreamParserStatus)parse:(NSData *)data_ { } } -- (unichar)decodeHexQuad:(char *)quad { - unichar ch = 0; - for (NSUInteger i = 0; i < 4; i++) { - int c = quad[i]; - ch *= 16; - switch (c) { - case '0' ... '9': ch += c - '0'; break; - case 'a' ... 'f': ch += 10 + c - 'a'; break; - case 'A' ... 'F': ch += 10 + c - 'A'; break; - default: @throw @"FUT FUT FUT"; - } - } - return ch; -} - -- (NSString*)decodeStringToken:(char*)bytes length:(NSUInteger)len { - NSMutableString *string = [NSMutableString stringWithCapacity:len]; - - for (NSUInteger i = 0; i < len;) { - switch (bytes[i]) { - case '\\': { - switch (bytes[++i]) { - case '"': [string appendString:@"\""]; i++; break; - case '/': [string appendString:@"/"]; i++; break; - case '\\': [string appendString:@"\\"]; i++; break; - case 'b': [string appendString:@"\b"]; i++; break; - case 'f': [string appendString:@"\f"]; i++; break; - case 'n': [string appendString:@"\n"]; i++; break; - case 'r': [string appendString:@"\r"]; i++; break; - case 't': [string appendString:@"\t"]; i++; break; - case 'u': { - unichar hi = [self decodeHexQuad:bytes + i + 1]; - i += 5; - if (SBStringIsSurrogateHighCharacter(hi)) { - // Skip past \u that we know is there.. - unichar lo = [self decodeHexQuad:bytes + i + 2]; - i += 6; - [string appendFormat:@"%C%C", hi, lo]; - } else { - [string appendFormat:@"%C", hi]; - } - break; - } - default: @throw @"FUT FUT FUT"; - } - break; - } - default: [string appendFormat:@"%c", bytes[i++]]; break; - } - } - return string; -} - @end diff --git a/json/SBJsonStreamParserAccumulator.m b/json/SBJsonStreamParserAccumulator.m index d126a376..82d8fe80 100644 --- a/json/SBJsonStreamParserAccumulator.m +++ b/json/SBJsonStreamParserAccumulator.m @@ -27,9 +27,9 @@ 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. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonStreamParserAccumulator.h" diff --git a/json/SBJsonStreamParserAdapter.m b/json/SBJsonStreamParserAdapter.m index d45566ca..7259a060 100644 --- a/json/SBJsonStreamParserAdapter.m +++ b/json/SBJsonStreamParserAdapter.m @@ -30,9 +30,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonStreamParserAdapter.h" diff --git a/json/SBJsonStreamParserState.h b/json/SBJsonStreamParserState.h index 35b3f248..ea893cb3 100644 --- a/json/SBJsonStreamParserState.h +++ b/json/SBJsonStreamParserState.h @@ -32,7 +32,7 @@ #import -#import "SBJsonStreamTokeniser.h" +#import "SBJsonTokeniser.h" #import "SBJsonStreamParser.h" @interface SBJsonStreamParserState : NSObject diff --git a/json/SBJsonStreamParserState.m b/json/SBJsonStreamParserState.m index a186410c..a59e7dc2 100644 --- a/json/SBJsonStreamParserState.m +++ b/json/SBJsonStreamParserState.m @@ -30,9 +30,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonStreamParserState.h" @@ -82,23 +82,23 @@ @implementation SBJsonStreamParserStateStart SINGLETON - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - return token == sbjson_token_array_open || token == sbjson_token_object_open; + return token == sbjson_token_array_start || token == sbjson_token_object_start; } - (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { SBJsonStreamParserState *state = nil; switch (tok) { - case sbjson_token_array_open: + case sbjson_token_array_start: state = [SBJsonStreamParserStateArrayStart sharedInstance]; break; - case sbjson_token_object_open: + case sbjson_token_object_start: state = [SBJsonStreamParserStateObjectStart sharedInstance]; break; - case sbjson_token_array_close: - case sbjson_token_object_close: + case sbjson_token_array_end: + case sbjson_token_object_end: if (parser.supportMultipleDocuments) state = parser.state; else @@ -163,9 +163,8 @@ - (NSString*)name { return @"at beginning of object"; } - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { switch (token) { - case sbjson_token_object_close: + case sbjson_token_object_end: case sbjson_token_string: - case sbjson_token_encoded: return YES; break; default: @@ -193,7 +192,7 @@ @implementation SBJsonStreamParserStateObjectGotKey - (NSString*)name { return @"after object key"; } - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - return token == sbjson_token_entry_sep; + return token == sbjson_token_keyval_separator; } - (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { @@ -212,14 +211,13 @@ - (NSString*)name { return @"as object value"; } - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { switch (token) { - case sbjson_token_object_open: - case sbjson_token_array_open: - case sbjson_token_bool: + case sbjson_token_object_start: + case sbjson_token_array_start: + case sbjson_token_true: + case sbjson_token_false: case sbjson_token_null: - case sbjson_token_integer: - case sbjson_token_real: - case sbjson_token_string: - case sbjson_token_encoded: + case sbjson_token_number: + case sbjson_token_string: return YES; break; @@ -245,8 +243,8 @@ - (NSString*)name { return @"after object value"; } - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { switch (token) { - case sbjson_token_object_close: - case sbjson_token_value_sep: + case sbjson_token_object_end: + case sbjson_token_separator: return YES; break; default: @@ -271,7 +269,7 @@ @implementation SBJsonStreamParserStateObjectNeedKey - (NSString*)name { return @"in place of object key"; } - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - return sbjson_token_string == token || sbjson_token_encoded == token; + return sbjson_token_string == token; } - (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { @@ -294,9 +292,9 @@ - (NSString*)name { return @"at array start"; } - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { switch (token) { - case sbjson_token_object_close: - case sbjson_token_entry_sep: - case sbjson_token_value_sep: + case sbjson_token_object_end: + case sbjson_token_keyval_separator: + case sbjson_token_separator: return NO; break; @@ -322,11 +320,11 @@ - (NSString*)name { return @"after array value"; } - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - return token == sbjson_token_array_close || token == sbjson_token_value_sep; + return token == sbjson_token_array_end || token == sbjson_token_separator; } - (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - if (tok == sbjson_token_value_sep) + if (tok == sbjson_token_separator) parser.state = [SBJsonStreamParserStateArrayNeedValue sharedInstance]; } @@ -343,10 +341,10 @@ - (NSString*)name { return @"as array value"; } - (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { switch (token) { - case sbjson_token_array_close: - case sbjson_token_entry_sep: - case sbjson_token_object_close: - case sbjson_token_value_sep: + case sbjson_token_array_end: + case sbjson_token_keyval_separator: + case sbjson_token_object_end: + case sbjson_token_separator: return NO; break; diff --git a/json/SBJsonStreamWriter.m b/json/SBJsonStreamWriter.m index c4893430..7302cc05 100644 --- a/json/SBJsonStreamWriter.m +++ b/json/SBJsonStreamWriter.m @@ -30,13 +30,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonStreamWriter.h" #import "SBJsonStreamWriterState.h" +static NSNumber *kNotANumber; static NSNumber *kTrue; static NSNumber *kFalse; static NSNumber *kPositiveInfinity; @@ -54,6 +55,7 @@ @implementation SBJsonStreamWriter @synthesize sortKeysComparator; + (void)initialize { + kNotANumber = [NSDecimalNumber notANumber]; kPositiveInfinity = [NSNumber numberWithDouble:+HUGE_VAL]; kNegativeInfinity = [NSNumber numberWithDouble:-HUGE_VAL]; kTrue = [NSNumber numberWithBool:YES]; @@ -338,7 +340,7 @@ - (BOOL)writeNumber:(NSNumber*)number { self.error = @"-Infinity is not a valid number in JSON"; return NO; - } else if (isnan([number doubleValue])) { + } else if ([kNotANumber isEqualToNumber:number]) { self.error = @"NaN is not a valid number in JSON"; return NO; } @@ -354,10 +356,15 @@ - (BOOL)writeNumber:(NSNumber*)number { case 'C': case 'I': case 'S': case 'L': case 'Q': len = snprintf(num, sizeof num, "%llu", [number unsignedLongLongValue]); break; - case 'f': case 'd': default: { - len = snprintf(num, sizeof num, "%.17g", [number doubleValue]); + case 'f': case 'd': default: + if ([number isKindOfClass:[NSDecimalNumber class]]) { + char const *utf8 = [[number stringValue] UTF8String]; + [delegate writer:self appendBytes:utf8 length: strlen(utf8)]; + [state transitionState:self]; + return YES; + } + len = snprintf(num, sizeof num, "%.17g", [number doubleValue]); break; - } } [delegate writer:self appendBytes:num length: len]; [state transitionState:self]; diff --git a/json/SBJsonStreamWriterAccumulator.m b/json/SBJsonStreamWriterAccumulator.m index 4afd1cca..d78c3176 100644 --- a/json/SBJsonStreamWriterAccumulator.m +++ b/json/SBJsonStreamWriterAccumulator.m @@ -27,9 +27,9 @@ 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. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonStreamWriterAccumulator.h" diff --git a/json/SBJsonStreamWriterState.m b/json/SBJsonStreamWriterState.m index 714b87db..a87b447d 100644 --- a/json/SBJsonStreamWriterState.m +++ b/json/SBJsonStreamWriterState.m @@ -30,9 +30,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonStreamWriterState.h" #import "SBJsonStreamWriter.h" diff --git a/json/SBJsonTokeniser.h b/json/SBJsonTokeniser.h index f3610932..e484a948 100644 --- a/json/SBJsonTokeniser.h +++ b/json/SBJsonTokeniser.h @@ -6,16 +6,16 @@ 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. + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. - Neither the name of the 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. + 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 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 @@ -33,38 +33,35 @@ #import typedef enum { - sbjson_token_eof, - sbjson_token_error, - sbjson_token_object_start, - sbjson_token_key_value_separator, - sbjson_token_object_end, - sbjson_token_array_start, - sbjson_token_array_end, - sbjson_token_separator, - sbjson_token_string, - sbjson_token_string_encoded, - sbjson_token_integer, - sbjson_token_double, - sbjson_token_true, - sbjson_token_false, - sbjson_token_null, + sbjson_token_error = -1, + sbjson_token_eof, + + sbjson_token_array_start, + sbjson_token_array_end, + + sbjson_token_object_start, + sbjson_token_object_end, + + sbjson_token_separator, + sbjson_token_keyval_separator, + + sbjson_token_number, + sbjson_token_string, + sbjson_token_true, + sbjson_token_false, + sbjson_token_null, + } sbjson_token_t; -@interface SBJsonTokeniser : NSObject { - NSUInteger tokenStart, tokenLength; - NSMutableData *buf; - const char *bufbytes; - NSUInteger bufbytesLength; - NSString *error; - NSCharacterSet *illegalCharacterSet; -} +@class SBJsonUTF8Stream; + +@interface SBJsonTokeniser : NSObject -@property(copy, readonly) NSString *error; +@property (strong) SBJsonUTF8Stream *stream; +@property (copy) NSString *error; -- (void)appendData:(NSData*)data; +- (void)appendData:(NSData*)data_; -- (sbjson_token_t)next; -- (BOOL)getToken:(const char **)utf8 length:(NSUInteger*)length; -- (NSString*)getDecodedStringToken; +- (sbjson_token_t)getToken:(NSObject**)token; @end diff --git a/json/SBJsonTokeniser.m b/json/SBJsonTokeniser.m index ee6236f5..9b68d2e6 100644 --- a/json/SBJsonTokeniser.m +++ b/json/SBJsonTokeniser.m @@ -1,22 +1,21 @@ /* - Copyright (c) 2010, Stig Brautaset. - All rights reserved. - + Copyright (c) 2010-2011, 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 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. - + + 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 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 @@ -30,480 +29,449 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + #import "SBJsonTokeniser.h" +#import "SBJsonUTF8Stream.h" +#define SBStringIsIllegalSurrogateHighCharacter(character) (((character) >= 0xD800UL) && ((character) <= 0xDFFFUL)) +#define SBStringIsSurrogateLowCharacter(character) ((character >= 0xDC00UL) && (character <= 0xDFFFUL)) +#define SBStringIsSurrogateHighCharacter(character) ((character >= 0xD800UL) && (character <= 0xDBFFUL)) -#define isDigit(x) (*x >= '0' && *x <= '9') -#define skipDigits(x) while (isDigit(x)) x++ +static int const DECIMAL_MAX_PRECISION = 38; +static int const DECIMAL_EXPONENT_MAX = 127; +static short const DECIMAL_EXPONENT_MIN = -128; +static int const LONG_LONG_DIGITS = 19; -#define SBStringIsIllegalSurrogateHighCharacter(x) (((x) >= 0xd800) && ((x) <= 0xdfff)) +static NSCharacterSet *kDecimalDigitCharacterSet; +@implementation SBJsonTokeniser -@interface SBJsonTokeniser () +@synthesize error = _error; +@synthesize stream = _stream; -@property (copy) NSString *error; ++ (void)initialize { + kDecimalDigitCharacterSet = [NSCharacterSet decimalDigitCharacterSet]; +} -- (void)skipWhitespace; +- (id)init { + self = [super init]; + if (self) { + _stream = [[SBJsonUTF8Stream alloc] init]; -- (sbjson_token_t)match:(const char *)utf8 ofLength:(NSUInteger)len andReturn:(sbjson_token_t)tok; -- (sbjson_token_t)matchString; -- (sbjson_token_t)matchNumber; + } -- (int)parseUnicodeEscape:(const char *)bytes index:(NSUInteger *)anIndex; -- (NSString*)decodeUnicodeEscape:(const char *)bytes index:(NSUInteger *)anIndex; + return self; +} -@end -@implementation SBJsonTokeniser +- (void)appendData:(NSData *)data_ { + [_stream appendData:data_]; +} -@synthesize error; -#pragma mark Housekeeping +- (sbjson_token_t)match:(const char *)pattern length:(NSUInteger)len retval:(sbjson_token_t)token { + if (![_stream haveRemainingCharacters:len]) + return sbjson_token_eof; -- (id)init { - self = [super init]; - if (self) { - tokenStart = tokenLength = 0; - buf = [[NSMutableData alloc] initWithCapacity:4096]; - illegalCharacterSet = [[NSCharacterSet illegalCharacterSet] copy]; - } - return self; -} + if ([_stream skipCharacters:pattern length:len]) + return token; -- (void)dealloc { - self.error = nil; - - [super dealloc]; + self.error = [NSString stringWithFormat:@"Expected '%s' after initial '%.1s'", pattern, pattern]; + return sbjson_token_error; } -#pragma mark Methods - -- (void)appendData:(NSData *)data { - - // Remove previous NUL char - if (buf.length) - buf.length = buf.length - 1; - - if (tokenStart) { - // Remove stuff in the front of the offset - [buf replaceBytesInRange:NSMakeRange(0, tokenStart) withBytes:"" length:0]; - tokenStart = 0; - } - - [buf appendData:data]; - - // Append NUL byte to simplify logic - [buf appendBytes:"\0" length:1]; - - bufbytes = [buf bytes]; - bufbytesLength = [buf length]; +- (BOOL)decodeEscape:(unichar)ch into:(unichar*)decoded { + switch (ch) { + case '\\': + case '/': + case '"': + *decoded = ch; + break; + + case 'b': + *decoded = '\b'; + break; + + case 'n': + *decoded = '\n'; + break; + + case 'r': + *decoded = '\r'; + break; + + case 't': + *decoded = '\t'; + break; + + case 'f': + *decoded = '\f'; + break; + + default: + self.error = @"Illegal escape character"; + return NO; + break; + } + return YES; } -- (BOOL)getToken:(const char **)utf8 length:(NSUInteger *)len { - if (!tokenLength) - return NO; - - *len = tokenLength; - *utf8 = bufbytes + tokenStart; - return YES; -} +- (BOOL)decodeHexQuad:(unichar*)quad { + unichar c, tmp = 0; -- (NSString*)getDecodedStringToken { - NSUInteger len; - const char *bytes; - [self getToken:&bytes length:&len]; - - len -= 1; - - NSMutableData *data = [NSMutableData dataWithCapacity:len * 1.1]; - - char c; - NSUInteger i = 1; -again: while (i < len) { - switch (c = bytes[i++]) { - case '\\': - switch (c = bytes[i++]) { - case '\\': - case '/': - case '"': - break; - - case 'b': - c = '\b'; - break; - - case 'n': - c = '\n'; - break; - - case 'r': - c = '\r'; - break; - - case 't': - c = '\t'; - break; - - case 'f': - c = '\f'; - break; - - case 'u': { - NSString *s = [self decodeUnicodeEscape:bytes index:&i]; - NSAssert(s, @"Illegal unicode escape"); - [data appendData:[s dataUsingEncoding:NSUTF8StringEncoding]]; - goto again; - break; - } - - default: - NSAssert(NO, @"Should never get here"); - break; - } - break; - - case 0 ... 0x1F: - self.error = @"Unescaped control chars"; - return nil; - break; - - default: - break; - } - [data appendBytes:&c length:1]; - } - - return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; -} + for (int i = 0; i < 4; i++) { + (void)[_stream getNextUnichar:&c]; + tmp *= 16; + switch (c) { + case '0' ... '9': + tmp += c - '0'; + break; + case 'a' ... 'f': + tmp += 10 + c - 'a'; + break; -- (sbjson_token_t)next { - tokenStart += tokenLength; - tokenLength = 0; - - [self skipWhitespace]; - - switch (*(bufbytes + tokenStart)) { - case '\0': - return sbjson_token_eof; - break; - - case '[': - tokenLength = 1; - return sbjson_token_array_start; - break; - - case ']': - tokenLength = 1; - return sbjson_token_array_end; - break; - - case '{': - tokenLength = 1; - return sbjson_token_object_start; - break; - - case ':': - tokenLength = 1; - return sbjson_token_key_value_separator; - break; - - case '}': - tokenLength = 1; - return sbjson_token_object_end; - break; - - case ',': - tokenLength = 1; - return sbjson_token_separator; - break; - - case 'n': - return [self match:"null" ofLength:4 andReturn:sbjson_token_null]; - break; - - case 't': - return [self match:"true" ofLength:4 andReturn:sbjson_token_true]; - break; - - case 'f': - return [self match:"false" ofLength:5 andReturn:sbjson_token_false]; - break; - - case '"': - return [self matchString]; - break; - - case '-': - case '0' ... '9': - return [self matchNumber]; - break; - - case '+': - self.error = [NSString stringWithFormat:@"Leading + is illegal in numbers at offset %lu", tokenStart]; - return sbjson_token_error; - break; - } - - self.error = [NSString stringWithFormat:@"Unrecognised leading character at offset %lu", tokenStart]; - return sbjson_token_error; -} + case 'A' ... 'F': + tmp += 10 + c - 'A'; + break; -#pragma mark Private methods - -- (void)skipWhitespace { - while (tokenStart < bufbytesLength) { - switch (bufbytes[tokenStart]) { - case ' ': - case '\t': - case '\n': - case '\r': - case '\f': - case '\v': - tokenStart++; - break; - default: - return; - break; - } - } + default: + return NO; + } + } + *quad = tmp; + return YES; } -- (sbjson_token_t)match:(const char *)utf8 ofLength:(NSUInteger)len andReturn:(sbjson_token_t)tok { - if (buf.length - tokenStart - 1 < len) - return sbjson_token_eof; - - if (strncmp(bufbytes + tokenStart, utf8, len)) { - NSString *format = [NSString stringWithFormat:@"Expected '%%s' but found '%%.%lus'.", len]; - self.error = [NSString stringWithFormat:format, utf8, bufbytes + tokenStart]; - return sbjson_token_error; - } - - tokenLength = len; - return tok; +- (sbjson_token_t)getStringToken:(NSObject**)token { + NSMutableString *acc = nil; + + for (;;) { + [_stream skip]; + + unichar ch; + { + NSMutableString *string = nil; + + if (![_stream getStringFragment:&string]) + return sbjson_token_eof; + + if (!string) { + self.error = @"Broken Unicode encoding"; + return sbjson_token_error; + } + + if (![_stream getUnichar:&ch]) + return sbjson_token_eof; + + if (acc) { + [acc appendString:string]; + + } else if (ch == '"') { + *token = [string copy]; + [_stream skip]; + return sbjson_token_string; + + } else { + acc = [string mutableCopy]; + } + } + + + switch (ch) { + case 0 ... 0x1F: + self.error = [NSString stringWithFormat:@"Unescaped control character [0x%0.2X]", (int)ch]; + return sbjson_token_error; + break; + + case '"': + *token = acc; + [_stream skip]; + return sbjson_token_string; + break; + + case '\\': + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + + if (ch == 'u') { + if (![_stream haveRemainingCharacters:5]) + return sbjson_token_eof; + + unichar hi; + if (![self decodeHexQuad:&hi]) { + self.error = @"Invalid hex quad"; + return sbjson_token_error; + } + + if (SBStringIsSurrogateHighCharacter(hi)) { + unichar lo; + + if (![_stream haveRemainingCharacters:6]) + return sbjson_token_eof; + + (void)[_stream getNextUnichar:&ch]; + (void)[_stream getNextUnichar:&lo]; + if (ch != '\\' || lo != 'u' || ![self decodeHexQuad:&lo]) { + self.error = @"Missing low character in surrogate pair"; + return sbjson_token_error; + } + + if (!SBStringIsSurrogateLowCharacter(lo)) { + self.error = @"Invalid low character in surrogate pair"; + return sbjson_token_error; + } + + [acc appendFormat:@"%C%C", hi, lo]; + } else if (SBStringIsIllegalSurrogateHighCharacter(hi)) { + self.error = @"Invalid high character in surrogate pair"; + return sbjson_token_error; + } else { + [acc appendFormat:@"%C", hi]; + } + + + } else { + unichar decoded; + if (![self decodeEscape:ch into:&decoded]) + return sbjson_token_error; + [acc appendFormat:@"%C", decoded]; + } + + break; + + default: { + self.error = [NSString stringWithFormat:@"Invalid UTF-8: '%x'", (int)ch]; + return sbjson_token_error; + break; + } + } + } + return sbjson_token_eof; } +- (sbjson_token_t)getNumberToken:(NSObject**)token { -- (int)decodeHexQuad:(const char *)hexQuad { - char c; - int ret = 0; - int i; - for (i = 0; i < 4; i++) { - ret *= 16; - switch (c = hexQuad[i]) { - case '\0': - return -2; - break; - - case '0' ... '9': - ret += c - '0'; - break; - - case 'a' ... 'f': - ret += 10 + c - 'a'; - break; - - case 'A' ... 'F': - ret += 10 + c - 'A'; - break; - - default: - self.error = @"XXX illegal digit in hex char"; - return -1; - break; - } + NSUInteger numberStart = _stream.index; + + unichar ch; + if (![_stream getUnichar:&ch]) + return sbjson_token_eof; + + BOOL isNegative = NO; + if (ch == '-') { + isNegative = YES; + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; } - return ret; -} -- (int)parseUnicodeEscape:(const char *)bytes index:(NSUInteger *)anIndex { - int hi = [self decodeHexQuad:bytes + *anIndex]; - if (hi == -2) return -2; // EOF - if (hi < 0) { - self.error = @"Missing hex quad"; - return -1; - } - *anIndex += 4; - - if (CFStringIsSurrogateHighCharacter(hi)) { - int lo = -1; - if (bytes[(*anIndex)++] == '\\' && bytes[(*anIndex)++] == 'u') - lo = [self decodeHexQuad:bytes + *anIndex]; - - if (lo < 0) { - self.error = @"Missing low character in surrogate pair"; - return -1; - } - *anIndex += 4; - - if (!CFStringIsSurrogateLowCharacter(lo)) { - self.error = @"Invalid low surrogate char"; - return -1; - } - } else if (SBStringIsIllegalSurrogateHighCharacter(hi)) { - self.error = @"Invalid high character in surrogate pair"; - return -1; - } - - - return hi; -} + unsigned long long mantissa = 0; + int mantissa_length = 0; + + if (ch == '0') { + mantissa_length++; + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + + if ([kDecimalDigitCharacterSet characterIsMember:ch]) { + self.error = @"Leading zero is illegal in number"; + return sbjson_token_error; + } + } -- (NSString*)decodeUnicodeEscape:(const char *)bytes index:(NSUInteger *)anIndex { - int hi_int = [self decodeHexQuad:bytes + *anIndex]; - if (hi_int < 0) { - self.error = @"Missing hex quad"; - return nil; - } - unichar hi = (unichar)hi_int; - *anIndex += 4; - - if (CFStringIsSurrogateHighCharacter(hi)) { // high surrogate char? - int lo = -1; - if (bytes[(*anIndex)++] == '\\' && bytes[(*anIndex)++] == 'u') - lo = [self decodeHexQuad:bytes + *anIndex]; - - if (lo < 0) { - self.error = @"Missing low character in surrogate pair"; - return nil; - } - *anIndex += 4; - - if (!CFStringIsSurrogateLowCharacter(lo)) { - self.error = @"Invalid low surrogate char"; - return nil; - } - - unichar pair[2] = {hi, lo}; - return [NSString stringWithCharacters:pair length:2]; - - } else if (SBStringIsIllegalSurrogateHighCharacter(hi)) { - self.error = @"Invalid high character in surrogate pair"; - return nil; - } - - return [NSString stringWithCharacters:&hi length:1]; -} + while ([kDecimalDigitCharacterSet characterIsMember:ch]) { + mantissa *= 10; + mantissa += (ch - '0'); + mantissa_length++; + + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + } + + short exponent = 0; + BOOL isFloat = NO; + + if (ch == '.') { + isFloat = YES; + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + + while ([kDecimalDigitCharacterSet characterIsMember:ch]) { + mantissa *= 10; + mantissa += (ch - '0'); + mantissa_length++; + exponent--; + + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + } + + if (!exponent) { + self.error = @"No digits after decimal point"; + return sbjson_token_error; + } + } + + BOOL hasExponent = NO; + if (ch == 'e' || ch == 'E') { + hasExponent = YES; + + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + + BOOL expIsNegative = NO; + if (ch == '-') { + expIsNegative = YES; + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + + } else if (ch == '+') { + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + } + + short explicit_exponent = 0; + short explicit_exponent_length = 0; + while ([kDecimalDigitCharacterSet characterIsMember:ch]) { + explicit_exponent *= 10; + explicit_exponent += (ch - '0'); + explicit_exponent_length++; + + if (![_stream getNextUnichar:&ch]) + return sbjson_token_eof; + } + + if (explicit_exponent_length == 0) { + self.error = @"No digits in exponent"; + return sbjson_token_error; + } + + if (expIsNegative) + exponent -= explicit_exponent; + else + exponent += explicit_exponent; + } + + if (!mantissa_length && isNegative) { + self.error = @"No digits after initial minus"; + return sbjson_token_error; + + } else if (mantissa_length > DECIMAL_MAX_PRECISION) { + self.error = @"Precision is too high"; + return sbjson_token_error; + + } else if (exponent > DECIMAL_EXPONENT_MAX || exponent < DECIMAL_EXPONENT_MIN) { + self.error = @"Exponent out of range"; + return sbjson_token_error; + } + + if (mantissa_length <= LONG_LONG_DIGITS) { + if (!isFloat && !hasExponent) { + *token = [NSNumber numberWithLongLong: isNegative ? -mantissa : mantissa]; + } else if (mantissa == 0) { + *token = [NSNumber numberWithFloat:-0.0f]; + } else { + *token = [NSDecimalNumber decimalNumberWithMantissa:mantissa + exponent:exponent + isNegative:isNegative]; + } + + } else { + NSString *number = [_stream stringWithRange:NSMakeRange(numberStart, _stream.index - numberStart)]; + *token = [NSDecimalNumber decimalNumberWithString:number]; + } -- (sbjson_token_t)matchString { - sbjson_token_t ret = sbjson_token_string; - - const char *bytes = bufbytes + tokenStart; - NSUInteger idx = 1; - NSUInteger maxIdx = buf.length - 2 - tokenStart; - - while (idx <= maxIdx) { - switch (bytes[idx++]) { - case 0 ... 0x1F: - self.error = [NSString stringWithFormat:@"Unescaped control char 0x%0.2X", (int)bytes[idx-1]]; - return sbjson_token_error; - break; - - case '\\': - ret = sbjson_token_string_encoded; - - if (idx >= maxIdx) - return sbjson_token_eof; - - switch (bytes[idx++]) { - case 'b': - case 't': - case 'n': - case 'r': - case 'f': - case 'v': - case '"': - case '\\': - case '/': - // Valid escape sequence - break; - - case 'u': { - int ch = [self parseUnicodeEscape:bytes index:&idx]; - if (ch == -2) - return sbjson_token_eof; - if (ch == -1) - return sbjson_token_error; - break; - } - default: - self.error = [NSString stringWithFormat:@"Broken escape character at index %lu in token starting at offset %lu", idx-1, tokenStart]; - return sbjson_token_error; - break; - } - break; - - case '"': - tokenLength = idx; - return ret; - break; - - default: - // any other character - break; - } - } - - return sbjson_token_eof; + return sbjson_token_number; } -- (sbjson_token_t)matchNumber { - - sbjson_token_t ret = sbjson_token_integer; - const char *c = bufbytes + tokenStart; - - if (*c == '-') { - c++; - if (!isDigit(c)) { - self.error = @"No digits after initial minus"; - return sbjson_token_error; - } - } - - if (*c == '0') { - c++; - if (isDigit(c)) { - self.error = [NSString stringWithFormat:@"Leading zero is illegal in number at offset %lu", tokenStart]; - return sbjson_token_error; - } - } - - skipDigits(c); - - - if (*c == '.') { - ret = sbjson_token_double; - c++; - - if (!isDigit(c) && *c) { - self.error = [NSString stringWithFormat:@"No digits after decimal point at offset %lu", tokenStart]; - return sbjson_token_error; - } - - skipDigits(c); - } - - if (*c == 'e' || *c == 'E') { - ret = sbjson_token_double; - c++; - - if (*c == '-' || *c == '+') - c++; - - if (!isDigit(c) && *c) { - self.error = [NSString stringWithFormat:@"No digits after exponent mark at offset %lu", tokenStart]; - return sbjson_token_error; - } - - skipDigits(c); - } - - if (!*c) - return sbjson_token_eof; - - tokenLength = c - (bufbytes + tokenStart); - return ret; +- (sbjson_token_t)getToken:(NSObject **)token { + + [_stream skipWhitespace]; + + unichar ch; + if (![_stream getUnichar:&ch]) + return sbjson_token_eof; + + NSUInteger oldIndexLocation = _stream.index; + sbjson_token_t tok; + + switch (ch) { + case '[': + tok = sbjson_token_array_start; + [_stream skip]; + break; + + case ']': + tok = sbjson_token_array_end; + [_stream skip]; + break; + + case '{': + tok = sbjson_token_object_start; + [_stream skip]; + break; + + case ':': + tok = sbjson_token_keyval_separator; + [_stream skip]; + break; + + case '}': + tok = sbjson_token_object_end; + [_stream skip]; + break; + + case ',': + tok = sbjson_token_separator; + [_stream skip]; + break; + + case 'n': + tok = [self match:"null" length:4 retval:sbjson_token_null]; + break; + + case 't': + tok = [self match:"true" length:4 retval:sbjson_token_true]; + break; + + case 'f': + tok = [self match:"false" length:5 retval:sbjson_token_false]; + break; + + case '"': + tok = [self getStringToken:token]; + break; + + case '0' ... '9': + case '-': + tok = [self getNumberToken:token]; + break; + + case '+': + self.error = @"Leading + is illegal in number"; + tok = sbjson_token_error; + break; + + default: + self.error = [NSString stringWithFormat:@"Illegal start of token [%c]", ch]; + tok = sbjson_token_error; + break; + } + + if (tok == sbjson_token_eof) { + // We ran out of bytes in the middle of a token. + // We don't know how to restart in mid-flight, so + // rewind to the start of the token for next attempt. + // Hopefully we'll have more data then. + _stream.index = oldIndexLocation; + } + + return tok; } + @end diff --git a/json/SBJsonUTF8Stream.h b/json/SBJsonUTF8Stream.h new file mode 100644 index 00000000..a26f0326 --- /dev/null +++ b/json/SBJsonUTF8Stream.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2011, 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 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 + HOLDER 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 + + +@interface SBJsonUTF8Stream : NSObject { +@private + const char *_bytes; + NSMutableData *_data; + NSUInteger _length; +} + +@property (assign) NSUInteger index; + +- (void)appendData:(NSData*)data_; + +- (BOOL)haveRemainingCharacters:(NSUInteger)chars; + +- (void)skip; +- (void)skipWhitespace; +- (BOOL)skipCharacters:(const char *)chars length:(NSUInteger)len; + +- (BOOL)getUnichar:(unichar*)ch; +- (BOOL)getNextUnichar:(unichar*)ch; +- (BOOL)getStringFragment:(NSString**)string; + +- (NSString*)stringWithRange:(NSRange)range; + +@end diff --git a/json/SBJsonUTF8Stream.m b/json/SBJsonUTF8Stream.m new file mode 100644 index 00000000..8185ee1a --- /dev/null +++ b/json/SBJsonUTF8Stream.m @@ -0,0 +1,145 @@ +/* + Copyright (c) 2011, 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 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 + HOLDER 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. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonUTF8Stream.h" + + +@implementation SBJsonUTF8Stream + +@synthesize index = _index; + +- (id)init { + self = [super init]; + if (self) { + _data = [[NSMutableData alloc] initWithCapacity:4096u]; + } + return self; +} + + +- (void)appendData:(NSData *)data_ { + + if (_index) { + // Discard data we've already parsed + [_data replaceBytesInRange:NSMakeRange(0, _index) withBytes:"" length:0]; + + // Reset index to point to current position + _index = 0; + } + + [_data appendData:data_]; + + // This is an optimisation. + _bytes = (const char*)[_data bytes]; + _length = [_data length]; +} + + +- (BOOL)getUnichar:(unichar*)ch { + if (_index < _length) { + *ch = (unichar)_bytes[_index]; + return YES; + } + return NO; +} + +- (BOOL)getNextUnichar:(unichar*)ch { + if (++_index < _length) { + *ch = (unichar)_bytes[_index]; + return YES; + } + return NO; +} + +- (BOOL)getStringFragment:(NSString **)string { + NSUInteger start = _index; + while (_index < _length) { + switch (_bytes[_index]) { + case '"': + case '\\': + case 0 ... 0x1f: + *string = [[NSString alloc] initWithBytes:(_bytes + start) + length:(_index - start) + encoding:NSUTF8StringEncoding]; + return YES; + break; + default: + _index++; + break; + } + } + return NO; +} + +- (void)skip { + _index++; +} + +- (void)skipWhitespace { + while (_index < _length) { + switch (_bytes[_index]) { + case ' ': + case '\t': + case '\r': + case '\n': + _index++; + break; + default: + return; + break; + } + } +} + +- (BOOL)haveRemainingCharacters:(NSUInteger)chars { + return [_data length] - _index >= chars; +} + +- (BOOL)skipCharacters:(const char *)chars length:(NSUInteger)len { + const void *bytes = ((const char*)[_data bytes]) + _index; + if (!memcmp(bytes, chars, len)) { + _index += len; + return YES; + } + return NO; +} + +- (NSString*)stringWithRange:(NSRange)range { + return [[NSString alloc] initWithBytes:_bytes + range.location length:range.length encoding:NSUTF8StringEncoding]; + +} + + +@end diff --git a/json/SBJsonWriter.h b/json/SBJsonWriter.h index aaf6b0b4..8b0a059e 100644 --- a/json/SBJsonWriter.h +++ b/json/SBJsonWriter.h @@ -82,7 +82,7 @@ @property (copy) NSComparator sortKeysComparator; /** - Generates string with JSON representation for the given object. + Return JSON representation for the given object. Returns a string containing JSON representation of the passed in value, or nil on error. If nil is returned and error is not NULL, *error can be interrogated to find the cause of the error. @@ -92,7 +92,7 @@ - (NSString*)stringWithObject:(id)value; /** - Generates JSON representation for the given object. + Return JSON representation for the given object. Returns an NSData object containing JSON represented as UTF8 text, or nil on error. @@ -100,4 +100,20 @@ */ - (NSData*)dataWithObject:(id)value; +/** + Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and error is not NULL, *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error pointer to object to be populated with NSError on failure + + @warning Deprecated in Version 3.2; will be removed in 4.0 + + */ +- (NSString*)stringWithObject:(id)value + error:(NSError**)error __attribute__ ((deprecated)); + + @end diff --git a/json/SBJsonWriter.m b/json/SBJsonWriter.m index 10b289b7..105acb93 100644 --- a/json/SBJsonWriter.m +++ b/json/SBJsonWriter.m @@ -27,9 +27,9 @@ 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. */ -//#if !__has_feature(objc_arc) -//#error "This source file must be compiled with ARC enabled!" -//#endif +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif #import "SBJsonWriter.h" #import "SBJsonStreamWriter.h" @@ -64,6 +64,19 @@ - (NSString*)stringWithObject:(id)value { if (data) return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; return nil; +} + +- (NSString*)stringWithObject:(id)value error:(NSError**)error_ { + NSString *tmp = [self stringWithObject:value]; + if (tmp) + return tmp; + + if (error_) { + NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:error, NSLocalizedDescriptionKey, nil]; + *error_ = [NSError errorWithDomain:@"org.brautaset.SBJsonWriter.ErrorDomain" code:0 userInfo:ui]; + } + + return nil; } - (NSData*)dataWithObject:(id)object { diff --git a/util/vico.m b/util/vico.m index e9210166..1ea94c70 100644 --- a/util/vico.m +++ b/util/vico.m @@ -32,7 +32,7 @@ #include #import "ViAppController.h" -#import "JSON.h" +#import "SBJson.h" BOOL keepRunning = YES; int returnCode = 0; @@ -85,6 +85,11 @@ - (void)log:(NSString *)message printf(" -w wait for document to close\n"); } +id jsonValueFor(NSString *json) { + SBJsonParser *parser = [[SBJsonParser alloc] init]; + return [parser objectWithString:json]; +} + int main(int argc, char **argv) { @@ -128,7 +133,7 @@ - (void)log:(NSString *)message } else { if ((json = [NSString stringWithUTF8String:optarg]) == nil) errx(1, "parameters not proper UTF8"); - if ((params = [json JSONValue]) == nil) + if ((params = jsonValueFor(json)) == nil) errx(1, "parameters not proper JSON"); if (![params isKindOfClass:[NSDictionary class]]) errx(1, "parameters not a JSON object"); @@ -230,7 +235,9 @@ - (void)log:(NSString *)message errx(2, "stdin: read failure"); if ((json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]) == nil) errx(1, "parameters not proper UTF8"); - if ((params = [json JSONValue]) == nil) + + SBJsonParser *parser = [[SBJsonParser alloc] init]; + if ((params = [parser objectWithString:json]) == nil) errx(1, "parameters not proper JSON"); if (![params isKindOfClass:[NSDictionary class]]) errx(1, "parameters not a JSON object"); @@ -306,7 +313,8 @@ - (void)log:(NSString *)message ; if (returnObject != nil) { - NSString *returnJSON = [returnObject JSONRepresentation]; + SBJsonWriter *writer = [[SBJsonWriter alloc] init]; + NSString *returnJSON = [writer stringWithObject:returnObject]; printf("%s\n", [returnJSON UTF8String]); } } diff --git a/vico.xcodeproj/project.pbxproj b/vico.xcodeproj/project.pbxproj index 6051a32f..95af9843 100644 --- a/vico.xcodeproj/project.pbxproj +++ b/vico.xcodeproj/project.pbxproj @@ -9,9 +9,10 @@ /* Begin PBXBuildFile section */ 1730CE0817CF058D00A1DA2C /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1730CE0717CF058D00A1DA2C /* AddressBook.framework */; }; 1730CE0B17CF05D100A1DA2C /* GenerateFormData.m in Sources */ = {isa = PBXBuildFile; fileRef = 1730CE0A17CF05D100A1DA2C /* GenerateFormData.m */; }; - 17D8A10317C842B7000C25C6 /* SBJsonStreamParserAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A0FE17C842B7000C25C6 /* SBJsonStreamParserAccumulator.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 17D8A10417C842B7000C25C6 /* SBJsonStreamTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A10017C842B7000C25C6 /* SBJsonStreamTokeniser.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 17D8A10517C842B7000C25C6 /* SBJsonStreamWriterAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A10217C842B7000C25C6 /* SBJsonStreamWriterAccumulator.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 1730CE1117D1282900A1DA2C /* SBJsonUTF8Stream.m in Sources */ = {isa = PBXBuildFile; fileRef = 1730CE0F17D1282800A1DA2C /* SBJsonUTF8Stream.m */; }; + 17D8A10317C842B7000C25C6 /* SBJsonStreamParserAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A0FE17C842B7000C25C6 /* SBJsonStreamParserAccumulator.m */; }; + 17D8A10417C842B7000C25C6 /* SBJsonStreamTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A10017C842B7000C25C6 /* SBJsonStreamTokeniser.m */; }; + 17D8A10517C842B7000C25C6 /* SBJsonStreamWriterAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A10217C842B7000C25C6 /* SBJsonStreamWriterAccumulator.m */; }; 17D8A10717C846F1000C25C6 /* SBJsonStreamParserAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A0FE17C842B7000C25C6 /* SBJsonStreamParserAccumulator.m */; }; 17D8A10817C846F1000C25C6 /* SBJsonStreamTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A10017C842B7000C25C6 /* SBJsonStreamTokeniser.m */; }; 17D8A10917C846F1000C25C6 /* SBJsonStreamWriterAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D8A10217C842B7000C25C6 /* SBJsonStreamWriterAccumulator.m */; }; @@ -218,16 +219,14 @@ 9A4346E01331257A002B04DE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A4346DF1331257A002B04DE /* Foundation.framework */; }; 9A4346EB13312710002B04DE /* vico.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346EA13312710002B04DE /* vico.m */; }; 9A4346EF13312861002B04DE /* vicotool in Copy Executables */ = {isa = PBXBuildFile; fileRef = 9A4346DD1331257A002B04DE /* vicotool */; }; - 9A4347041331383C002B04DE /* NSObject+JSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F31331383C002B04DE /* NSObject+JSON.m */; }; - 9A4347051331383C002B04DE /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F51331383C002B04DE /* SBJsonParser.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 9A4347061331383C002B04DE /* SBJsonStreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F71331383C002B04DE /* SBJsonStreamParser.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 9A4347071331383C002B04DE /* SBJsonStreamParserAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F91331383C002B04DE /* SBJsonStreamParserAdapter.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 9A4347081331383C002B04DE /* SBJsonStreamParserState.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346FB1331383C002B04DE /* SBJsonStreamParserState.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 9A4347091331383C002B04DE /* SBJsonStreamWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346FD1331383C002B04DE /* SBJsonStreamWriter.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 9A43470A1331383C002B04DE /* SBJsonStreamWriterState.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346FF1331383C002B04DE /* SBJsonStreamWriterState.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 9A43470B1331383C002B04DE /* SBJsonTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4347011331383C002B04DE /* SBJsonTokeniser.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 9A43470C1331383C002B04DE /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4347031331383C002B04DE /* SBJsonWriter.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 9A43470D13313854002B04DE /* NSObject+JSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F31331383C002B04DE /* NSObject+JSON.m */; }; + 9A4347051331383C002B04DE /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F51331383C002B04DE /* SBJsonParser.m */; }; + 9A4347061331383C002B04DE /* SBJsonStreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F71331383C002B04DE /* SBJsonStreamParser.m */; }; + 9A4347071331383C002B04DE /* SBJsonStreamParserAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F91331383C002B04DE /* SBJsonStreamParserAdapter.m */; }; + 9A4347081331383C002B04DE /* SBJsonStreamParserState.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346FB1331383C002B04DE /* SBJsonStreamParserState.m */; }; + 9A4347091331383C002B04DE /* SBJsonStreamWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346FD1331383C002B04DE /* SBJsonStreamWriter.m */; }; + 9A43470A1331383C002B04DE /* SBJsonStreamWriterState.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346FF1331383C002B04DE /* SBJsonStreamWriterState.m */; }; + 9A43470B1331383C002B04DE /* SBJsonTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4347011331383C002B04DE /* SBJsonTokeniser.m */; }; + 9A43470C1331383C002B04DE /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4347031331383C002B04DE /* SBJsonWriter.m */; }; 9A43470E13313854002B04DE /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F51331383C002B04DE /* SBJsonParser.m */; }; 9A43470F13313854002B04DE /* SBJsonStreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F71331383C002B04DE /* SBJsonStreamParser.m */; }; 9A43471013313854002B04DE /* SBJsonStreamParserAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A4346F91331383C002B04DE /* SBJsonStreamParserAdapter.m */; }; @@ -404,6 +403,8 @@ 1730CE0717CF058D00A1DA2C /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; 1730CE0917CF05D100A1DA2C /* GenerateFormData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GenerateFormData.h; sourceTree = ""; }; 1730CE0A17CF05D100A1DA2C /* GenerateFormData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GenerateFormData.m; sourceTree = ""; }; + 1730CE0E17D1282800A1DA2C /* SBJsonUTF8Stream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBJsonUTF8Stream.h; path = json/SBJsonUTF8Stream.h; sourceTree = SOURCE_ROOT; }; + 1730CE0F17D1282800A1DA2C /* SBJsonUTF8Stream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SBJsonUTF8Stream.m; path = json/SBJsonUTF8Stream.m; sourceTree = SOURCE_ROOT; }; 17D8A0FC17C842B7000C25C6 /* SBJson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBJson.h; path = json/SBJson.h; sourceTree = SOURCE_ROOT; }; 17D8A0FD17C842B7000C25C6 /* SBJsonStreamParserAccumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBJsonStreamParserAccumulator.h; path = json/SBJsonStreamParserAccumulator.h; sourceTree = SOURCE_ROOT; }; 17D8A0FE17C842B7000C25C6 /* SBJsonStreamParserAccumulator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SBJsonStreamParserAccumulator.m; path = json/SBJsonStreamParserAccumulator.m; sourceTree = SOURCE_ROOT; }; @@ -478,8 +479,6 @@ 9A2EF2CB13310AD70023C5B7 /* TestViTextStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestViTextStorage.m; sourceTree = ""; }; 9A2EF2CC13310AD70023C5B7 /* TestViTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestViTextView.m; sourceTree = ""; }; 9A2EF2D513310AF90023C5B7 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - 9A2EF2D713310C320023C5B7 /* ber.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ber.c; sourceTree = ""; }; - 9A2EF2D813310C320023C5B7 /* ber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ber.h; sourceTree = ""; }; 9A2EF2DA13310C320023C5B7 /* ExCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExCommand.h; sourceTree = ""; }; 9A2EF2DB13310C320023C5B7 /* ExCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExCommand.m; sourceTree = ""; }; 9A2EF2E013310C320023C5B7 /* logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = logging.h; sourceTree = ""; }; @@ -512,8 +511,6 @@ 9A2EF2FD13310C3A0023C5B7 /* PSMTabDragAssistant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSMTabDragAssistant.h; sourceTree = ""; }; 9A2EF2FE13310C3A0023C5B7 /* PSMTabDragAssistant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSMTabDragAssistant.m; sourceTree = ""; }; 9A2EF2FF13310C3B0023C5B7 /* PSMTabStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSMTabStyle.h; sourceTree = ""; }; - 9A2EF30013310C3B0023C5B7 /* receipt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = receipt.c; sourceTree = ""; }; - 9A2EF30113310C3C0023C5B7 /* receipt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = receipt.h; sourceTree = ""; }; 9A2EF30213310C3C0023C5B7 /* scope_selector_parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scope_selector_parser.h; sourceTree = ""; }; 9A2EF30313310C3D0023C5B7 /* scope_selector.lemon */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = scope_selector.lemon; sourceTree = ""; }; 9A2EF30413310C3D0023C5B7 /* SFTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SFTPConnection.h; sourceTree = ""; }; @@ -747,9 +744,6 @@ 9A4346DF1331257A002B04DE /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 9A4346E51331257E002B04DE /* vico-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vico-Prefix.pch"; sourceTree = ""; }; 9A4346EA13312710002B04DE /* vico.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = vico.m; path = util/vico.m; sourceTree = SOURCE_ROOT; }; - 9A4346F11331383C002B04DE /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSON.h; path = json/JSON.h; sourceTree = SOURCE_ROOT; }; - 9A4346F21331383C002B04DE /* NSObject+JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject+JSON.h"; path = "json/NSObject+JSON.h"; sourceTree = SOURCE_ROOT; }; - 9A4346F31331383C002B04DE /* NSObject+JSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+JSON.m"; path = "json/NSObject+JSON.m"; sourceTree = SOURCE_ROOT; }; 9A4346F41331383C002B04DE /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBJsonParser.h; path = json/SBJsonParser.h; sourceTree = SOURCE_ROOT; }; 9A4346F51331383C002B04DE /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SBJsonParser.m; path = json/SBJsonParser.m; sourceTree = SOURCE_ROOT; }; 9A4346F61331383C002B04DE /* SBJsonStreamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBJsonStreamParser.h; path = json/SBJsonStreamParser.h; sourceTree = SOURCE_ROOT; }; @@ -825,7 +819,6 @@ 9AAD5D611395736900FDFFA6 /* ViSyntaxCompletion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViSyntaxCompletion.h; sourceTree = ""; }; 9AAD5D621395736900FDFFA6 /* ViSyntaxCompletion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViSyntaxCompletion.m; sourceTree = ""; }; 9AAF73E1135C931600D75E1E /* Credits.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Credits.txt; path = ../Credits.txt; sourceTree = ""; }; - 9AAF73F3135F3BDF00D75E1E /* Vico.help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Vico.help; path = help/Vico.help; sourceTree = ""; }; 9AAF73F913654C8500D75E1E /* ViFileURLHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViFileURLHandler.h; sourceTree = ""; }; 9AAF73FA13654C8500D75E1E /* ViFileURLHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViFileURLHandler.m; sourceTree = ""; }; 9AAF73FB13654C8500D75E1E /* ViURLManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViURLManager.h; sourceTree = ""; }; @@ -966,7 +959,6 @@ 1730CE0717CF058D00A1DA2C /* AddressBook.framework */, 17D8A11617C852FF000C25C6 /* Sparkle.framework */, 9AAD5D5B1393C33A00FDFFA6 /* Carbon.framework */, - 9AAF73F3135F3BDF00D75E1E /* Vico.help */, 9A2606861353104A002C86FB /* libcrypto.0.9.8.dylib */, 9A43471D13313DA0002B04DE /* ApplicationServices.framework */, 9A43471B13313B0A002B04DE /* AppKit.framework */, @@ -1349,8 +1341,6 @@ 9AB10BA3133F72E600D81D33 /* ViCommand.m */, 9AB10BA4133F72E600D81D33 /* ViMap.h */, 9AB10BA5133F72E600D81D33 /* ViMap.m */, - 9A2EF2D713310C320023C5B7 /* ber.c */, - 9A2EF2D813310C320023C5B7 /* ber.h */, 9A2EF2DA13310C320023C5B7 /* ExCommand.h */, 9A2EF2DB13310C320023C5B7 /* ExCommand.m */, 9A2EF2E013310C320023C5B7 /* logging.h */, @@ -1383,8 +1373,6 @@ 9A2EF2FD13310C3A0023C5B7 /* PSMTabDragAssistant.h */, 9A2EF2FE13310C3A0023C5B7 /* PSMTabDragAssistant.m */, 9A2EF2FF13310C3B0023C5B7 /* PSMTabStyle.h */, - 9A2EF30013310C3B0023C5B7 /* receipt.c */, - 9A2EF30113310C3C0023C5B7 /* receipt.h */, 9A2EF30213310C3C0023C5B7 /* scope_selector_parser.h */, 9A2EF30313310C3D0023C5B7 /* scope_selector.lemon */, 9A2EF30413310C3D0023C5B7 /* SFTPConnection.h */, @@ -1536,6 +1524,8 @@ 9A4346F01331381B002B04DE /* json */ = { isa = PBXGroup; children = ( + 1730CE0E17D1282800A1DA2C /* SBJsonUTF8Stream.h */, + 1730CE0F17D1282800A1DA2C /* SBJsonUTF8Stream.m */, 17D8A0FC17C842B7000C25C6 /* SBJson.h */, 17D8A0FD17C842B7000C25C6 /* SBJsonStreamParserAccumulator.h */, 17D8A0FE17C842B7000C25C6 /* SBJsonStreamParserAccumulator.m */, @@ -1543,9 +1533,6 @@ 17D8A10017C842B7000C25C6 /* SBJsonStreamTokeniser.m */, 17D8A10117C842B7000C25C6 /* SBJsonStreamWriterAccumulator.h */, 17D8A10217C842B7000C25C6 /* SBJsonStreamWriterAccumulator.m */, - 9A4346F11331383C002B04DE /* JSON.h */, - 9A4346F21331383C002B04DE /* NSObject+JSON.h */, - 9A4346F31331383C002B04DE /* NSObject+JSON.m */, 9A4346F41331383C002B04DE /* SBJsonParser.h */, 9A4346F51331383C002B04DE /* SBJsonParser.m */, 9A4346F61331383C002B04DE /* SBJsonStreamParser.h */, @@ -2002,7 +1989,6 @@ 9A2EF4E01331152A0023C5B7 /* utf16_le.c in Sources */, 9A2EF4E11331152A0023C5B7 /* utf32_be.c in Sources */, 9A2EF4E21331152A0023C5B7 /* utf32_le.c in Sources */, - 9A4347041331383C002B04DE /* NSObject+JSON.m in Sources */, 9A4347051331383C002B04DE /* SBJsonParser.m in Sources */, 9A4347061331383C002B04DE /* SBJsonStreamParser.m in Sources */, 9A4347071331383C002B04DE /* SBJsonStreamParserAdapter.m in Sources */, @@ -2068,6 +2054,7 @@ 17D8A12917CF0354000C25C6 /* SFBCrashReporterWindowController.m in Sources */, 17D8A12A17CF0354000C25C6 /* SFBSystemInformation.m in Sources */, 1730CE0B17CF05D100A1DA2C /* GenerateFormData.m in Sources */, + 1730CE1117D1282900A1DA2C /* SBJsonUTF8Stream.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2098,7 +2085,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9A43470D13313854002B04DE /* NSObject+JSON.m in Sources */, 9A43470E13313854002B04DE /* SBJsonParser.m in Sources */, 9A43470F13313854002B04DE /* SBJsonStreamParser.m in Sources */, 9A43471013313854002B04DE /* SBJsonStreamParserAdapter.m in Sources */,