diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 6c7b00d5b7..bd48a18ed4 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -246,6 +246,10 @@ Clojure: - .clj - .cljs +Cobol: + type: programming + primary_extension: .cob + CoffeeScript: type: programming ace_mode: coffee @@ -633,6 +637,9 @@ INI: - .prefs - .properties primary_extension: .ini + +INTERCAL: + type: programming IRC log: lexer: IRC logs @@ -717,6 +724,10 @@ LLVM: primary_extension: .ll extensions: - .ll + +LOLCODE: + type: programming + primary_extension: .lol Lasso: type: programming @@ -893,8 +904,14 @@ Objective-C: - obj-c - objc primary_extension: .m - extensions: - - .mm + +Objective-C++: + type: programming + color: "#4886FC" + aliases: + - obj-c++ + - objc++ + primary_extension: .mm Objective-J: type: programming @@ -1307,6 +1324,11 @@ Visual Basic: - .vb - .vba - .vbs + +Whitespace: + type: programming + color: "FFFFFF" + primary_extension: .ws XML: type: markup diff --git a/samples/Cobol/hello.cob b/samples/Cobol/hello.cob new file mode 100644 index 0000000000..35394bf046 --- /dev/null +++ b/samples/Cobol/hello.cob @@ -0,0 +1,5 @@ +IDENTIFICATION DIVISION. +PROGRAM-ID. HELLOWORLD. +PROCEDURE DIVISION. + DISPLAY 'HELLO WORLD'. + STOP RUN. diff --git a/samples/LOLCODE/test.lol b/samples/LOLCODE/test.lol new file mode 100644 index 0000000000..091526c198 --- /dev/null +++ b/samples/LOLCODE/test.lol @@ -0,0 +1,293 @@ +OBTW + A BF interpreter. +TLDR + +HAI 1.3 + I HAS A array ITZ A BUKKIT + I HAS A ptr ITZ 0 + I HAS A max ITZ 100 + + HOW IZ I convert YR char + char, WTF? + OMG "", FOUND YR 4 + OMG " ", FOUND YR 32 + OMG "!", FOUND YR 33 + OMG ":"", FOUND YR 34 + OMG "#", FOUND YR 35 + OMG "$", FOUND YR 36 + OMG "%", FOUND YR 37 + OMG "&", FOUND YR 38 + OMG "'", FOUND YR 39 + OMG "(", FOUND YR 40 + OMG ")", FOUND YR 41 + OMG "*", FOUND YR 42 + OMG "+", FOUND YR 43 + OMG ",", FOUND YR 44 + OMG "-", FOUND YR 45 + OMG ".", FOUND YR 46 + OMG "/", FOUND YR 47 + OMG "0", FOUND YR 48 + OMG "1", FOUND YR 49 + OMG "2", FOUND YR 50 + OMG "3", FOUND YR 51 + OMG "4", FOUND YR 52 + OMG "5", FOUND YR 53 + OMG "6", FOUND YR 54 + OMG "7", FOUND YR 55 + OMG "8", FOUND YR 56 + OMG "9", FOUND YR 57 + OMG "::", FOUND YR 58 + OMG ";", FOUND YR 59 + OMG "<", FOUND YR 60 + OMG "=", FOUND YR 61 + OMG ">", FOUND YR 62 + OMG "?", FOUND YR 63 + OMG "@", FOUND YR 64 + OMG "A", FOUND YR 65 + OMG "B", FOUND YR 66 + OMG "C", FOUND YR 67 + OMG "D", FOUND YR 68 + OMG "E", FOUND YR 69 + OMG "F", FOUND YR 70 + OMG "G", FOUND YR 71 + OMG "H", FOUND YR 72 + OMG "I", FOUND YR 73 + OMG "J", FOUND YR 74 + OMG "K", FOUND YR 75 + OMG "L", FOUND YR 76 + OMG "M", FOUND YR 77 + OMG "N", FOUND YR 78 + OMG "O", FOUND YR 79 + OMG "P", FOUND YR 80 + OMG "Q", FOUND YR 81 + OMG "R", FOUND YR 82 + OMG "S", FOUND YR 83 + OMG "T", FOUND YR 84 + OMG "U", FOUND YR 85 + OMG "V", FOUND YR 86 + OMG "W", FOUND YR 87 + OMG "X", FOUND YR 88 + OMG "Y", FOUND YR 89 + OMG "Z", FOUND YR 90 + OMG "[", FOUND YR 91 + OMG "]", FOUND YR 92 + OMG "\", FOUND YR 93 + OMG "^", FOUND YR 94 + OMG "_", FOUND YR 95 + OMG "`", FOUND YR 96 + OMG "a", FOUND YR 97 + OMG "b", FOUND YR 98 + OMG "c", FOUND YR 99 + OMG "d", FOUND YR 100 + OMG "e", FOUND YR 101 + OMG "f", FOUND YR 102 + OMG "g", FOUND YR 103 + OMG "h", FOUND YR 104 + OMG "i", FOUND YR 105 + OMG "j", FOUND YR 106 + OMG "k", FOUND YR 107 + OMG "l", FOUND YR 108 + OMG "m", FOUND YR 109 + OMG "n", FOUND YR 110 + OMG "o", FOUND YR 111 + OMG "p", FOUND YR 112 + OMG "q", FOUND YR 113 + OMG "r", FOUND YR 114 + OMG "s", FOUND YR 115 + OMG "t", FOUND YR 116 + OMG "u", FOUND YR 117 + OMG "v", FOUND YR 118 + OMG "w", FOUND YR 119 + OMG "x", FOUND YR 120 + OMG "y", FOUND YR 121 + OMG "z", FOUND YR 122 + OMG "{", FOUND YR 123 + OMG "|", FOUND YR 124 + OMG "}", FOUND YR 125 + OMG "~", FOUND YR 126 + OIC + IF U SAY SO + + HOW IZ I print YR char + char, WTF? + OMG 4, VISIBLE ""!, GTFO + OMG 32, VISIBLE " "!, GTFO + OMG 33, VISIBLE "!"!, GTFO + OMG 34, VISIBLE ":""!, GTFO + OMG 35, VISIBLE "#"!, GTFO + OMG 36, VISIBLE "$"!, GTFO + OMG 37, VISIBLE "%"!, GTFO + OMG 38, VISIBLE "&"!, GTFO + OMG 39, VISIBLE "'"!, GTFO + OMG 40, VISIBLE "("!, GTFO + OMG 41, VISIBLE ")"!, GTFO + OMG 42, VISIBLE "*"!, GTFO + OMG 43, VISIBLE "+"!, GTFO + OMG 44, VISIBLE ","!, GTFO + OMG 45, VISIBLE "-"!, GTFO + OMG 46, VISIBLE "."!, GTFO + OMG 47, VISIBLE "/"!, GTFO + OMG 48, VISIBLE "0"!, GTFO + OMG 49, VISIBLE "1"!, GTFO + OMG 50, VISIBLE "2"!, GTFO + OMG 51, VISIBLE "3"!, GTFO + OMG 52, VISIBLE "4"!, GTFO + OMG 53, VISIBLE "5"!, GTFO + OMG 54, VISIBLE "6"!, GTFO + OMG 55, VISIBLE "7"!, GTFO + OMG 56, VISIBLE "8"!, GTFO + OMG 57, VISIBLE "9"!, GTFO + OMG 58, VISIBLE "::"!, GTFO + OMG 59, VISIBLE ";"!, GTFO + OMG 60, VISIBLE "<"!, GTFO + OMG 61, VISIBLE "="!, GTFO + OMG 62, VISIBLE ">"!, GTFO + OMG 63, VISIBLE "?"!, GTFO + OMG 64, VISIBLE "@"!, GTFO + OMG 65, VISIBLE "A"!, GTFO + OMG 66, VISIBLE "B"!, GTFO + OMG 67, VISIBLE "C"!, GTFO + OMG 68, VISIBLE "D"!, GTFO + OMG 69, VISIBLE "E"!, GTFO + OMG 70, VISIBLE "F"!, GTFO + OMG 71, VISIBLE "G"!, GTFO + OMG 72, VISIBLE "H"!, GTFO + OMG 73, VISIBLE "I"!, GTFO + OMG 74, VISIBLE "J"!, GTFO + OMG 75, VISIBLE "K"!, GTFO + OMG 76, VISIBLE "L"!, GTFO + OMG 77, VISIBLE "M"!, GTFO + OMG 78, VISIBLE "N"!, GTFO + OMG 79, VISIBLE "O"!, GTFO + OMG 80, VISIBLE "P"!, GTFO + OMG 81, VISIBLE "Q"!, GTFO + OMG 82, VISIBLE "R"!, GTFO + OMG 83, VISIBLE "S"!, GTFO + OMG 84, VISIBLE "T"!, GTFO + OMG 85, VISIBLE "U"!, GTFO + OMG 86, VISIBLE "V"!, GTFO + OMG 87, VISIBLE "W"!, GTFO + OMG 88, VISIBLE "X"!, GTFO + OMG 89, VISIBLE "Y"!, GTFO + OMG 90, VISIBLE "Z"!, GTFO + OMG 91, VISIBLE "["!, GTFO + OMG 92, VISIBLE "\"!, GTFO + OMG 93, VISIBLE "]"!, GTFO + OMG 94, VISIBLE "^"!, GTFO + OMG 95, VISIBLE "_"!, GTFO + OMG 96, VISIBLE "`"!, GTFO + OMG 97, VISIBLE "a"!, GTFO + OMG 98, VISIBLE "b"!, GTFO + OMG 99, VISIBLE "c"!, GTFO + OMG 100, VISIBLE "d"!, GTFO + OMG 101, VISIBLE "e"!, GTFO + OMG 102, VISIBLE "f"!, GTFO + OMG 103, VISIBLE "g"!, GTFO + OMG 104, VISIBLE "h"!, GTFO + OMG 105, VISIBLE "i"!, GTFO + OMG 106, VISIBLE "j"!, GTFO + OMG 107, VISIBLE "k"!, GTFO + OMG 108, VISIBLE "l"!, GTFO + OMG 109, VISIBLE "m"!, GTFO + OMG 110, VISIBLE "n"!, GTFO + OMG 111, VISIBLE "o"!, GTFO + OMG 112, VISIBLE "p"!, GTFO + OMG 113, VISIBLE "q"!, GTFO + OMG 114, VISIBLE "r"!, GTFO + OMG 115, VISIBLE "s"!, GTFO + OMG 116, VISIBLE "t"!, GTFO + OMG 117, VISIBLE "u"!, GTFO + OMG 118, VISIBLE "v"!, GTFO + OMG 119, VISIBLE "w"!, GTFO + OMG 120, VISIBLE "x"!, GTFO + OMG 121, VISIBLE "y"!, GTFO + OMG 122, VISIBLE "z"!, GTFO + OMG 123, VISIBLE "{"!, GTFO + OMG 124, VISIBLE "|"!, GTFO + OMG 125, VISIBLE "}"!, GTFO + OMG 126, VISIBLE "~"!, GTFO + OIC + IF U SAY SO + + BTW Build up a slot name + HOW IZ I slot YR n + I HAS A name ITZ "x" + IM IN YR loop UPPIN YR i TIL BOTH SAEM i AN n + name R SMOOSH name AN "x" MKAY + IM OUTTA YR loop + FOUND YR name + IF U SAY SO + + BTW Initialize the array + IM IN YR init UPPIN YR ctr TIL BOTH SAEM ctr AN 100 + array HAS A SRS I IZ slot YR ctr MKAY ITZ 0 + IM OUTTA YR init + + HOW IZ I executin YR instruction + instruction, WTF? + OMG "" + FOUND YR WIN + OMG ">" + DIFFRINT ptr AN 100, O RLY? + YA RLY, ptr R SUM OF ptr AN 1 + OIC + FOUND YR FAIL + OMG "<" + DIFFRINT ptr AN 0, O RLY? + YA RLY, ptr R DIFF OF ptr AN 1 + OIC + FOUND YR FAIL + OMG "+" + array'Z SRS I IZ slot YR ptr MKAY R SUM OF... + array'Z SRS I IZ slot YR ptr MKAY AN 1 + FOUND YR FAIL + OMG "-" + array'Z SRS I IZ slot YR ptr MKAY R DIFF OF... + array'Z SRS I IZ slot YR ptr MKAY AN 1 + FOUND YR FAIL + OMG "," + I HAS A temp ITZ A YARN + GIMMEH temp + array'Z SRS I IZ slot YR ptr MKAY R... + I IZ convert YR temp MKAY + FOUND YR FAIL + OMG "." + I IZ print YR array'Z SRS I IZ slot YR ptr MKAY MKAY + FOUND YR FAIL + OMG "[" + BTW Fetch until ``]'' + I HAS A icache ITZ A BUKKIT + I HAS A size ITZ 0 + IM IN YR loop1 + I HAS A temp ITZ A YARN + GIMMEH temp + BOTH SAEM temp AN "]" + O RLY? + YA RLY, GTFO + OIC + icache HAS A SRS I IZ slot YR size MKAY ITZ temp + size R SUM OF size AN 1 + IM OUTTA YR loop1 + BTW Keep executing until done + IM IN YR loop2 UPPIN YR pc + pc R MOD OF pc AN size + I HAS A cond1 ITZ BOTH SAEM pc AN 0 + I HAS A cond2 ITZ BOTH SAEM array'Z SRS I IZ slot YR ptr MKAY AN 0 + BOTH OF cond1 AN cond2 + O RLY?, YA RLY, GTFO, OIC + I HAS A i ITZ icache'Z SRS I IZ slot YR pc MKAY + I IZ executin YR i MKAY + IM OUTTA YR loop2 + FOUND YR FAIL + OIC + IF U SAY SO + + IM IN YR main + I HAS A instruction ITZ A YARN + GIMMEH instruction + I IZ executin YR instruction MKAY + O RLY? + YA RLY, GTFO + OIC + IM OUTTA YR main +KTHXBYE diff --git a/samples/Objective-C++/App.mm b/samples/Objective-C++/App.mm new file mode 100644 index 0000000000..b2048457dd --- /dev/null +++ b/samples/Objective-C++/App.mm @@ -0,0 +1,73 @@ +#import "GLView.h" + +#import "App.h" + +const float TIMER_UPDATE_HZ = 1000; + +/* ------------------------------------------------ + Notes on Setting Up a Custom NSApplication class + ------------------------------------------------ + + 1) in MainMenu.nib the File's Owner custom class is set to App + 2) In Project's Application settings the Principal class is set to App +*/ + +@implementation App + +- (id) init +{ + self = [super init]; + + [self setDelegate: self]; // (to get NSApplication notifications) + + return self; +} + +- (void) sendEvent: (NSEvent*) theEvent +{ + // pass events up to the Sys class. If they don't handle it, send it up to NSApplication: + + if (!sys.HandleEvent(theEvent)) + [super sendEvent: theEvent]; +} + +- (void) applicationWillFinishLaunching: (NSNotification*) notification +{ +} + +- (void) applicationDidFinishLaunching: (NSNotification*) notification +{ + // Init the Sys class (which will start the event timer): + + sys.Init(TIMER_UPDATE_HZ, self, @selector(timerUpdate:)); +} + +- (void) applicationWillTerminate: (NSNotification*) notification +{ + sys.Reset(); +} + + +/* The timerUpdate: method is called by an NSTimer (instantiated in the Sys class) */ + +- (void) timerUpdate : (id) object +{ + sys.Process(); + + [glView timerUpdate]; +} + + +/* IBActions: In Interface Builder the UI controls were wired to target 'File's Owner', which is us */ + +- (IBAction) modeSelect: (id) sender +{ + [glView setMode: [sender intValue]]; +} + +- (IBAction) alphaSlider: (id) sender +{ + [glView setAlpha: [sender floatValue]]; +} + +@end \ No newline at end of file diff --git a/samples/Objective-C++/objsql.mm b/samples/Objective-C++/objsql.mm new file mode 100644 index 0000000000..f71ca8eb12 --- /dev/null +++ b/samples/Objective-C++/objsql.mm @@ -0,0 +1,1372 @@ +/* + * objsql.m - implementaion simple persistence layer using objcpp.h + * ======== + * + * Created by John Holdsworth on 01/04/2009. + * Copyright 2009 John Holdsworth. + * + * $Id: //depot/4.4/ObjCpp/objsql.mm#11 $ + * $DateTime: 2012/09/05 00:20:47 $ + * + * C++ classes to wrap up XCode classes for operator overload of + * useful operations such as access to NSArrays and NSDictionary + * by subscript or NSString operators such as + for concatenation. + * + * This works as the Apple Objective-C compiler supports source + * which mixes C++ with objective C. To enable this: for each + * source file which will include/import this header file, select + * it in Xcode and open it's "Info". To enable mixed compilation, + * for the file's "File Type" select: "sourcecode.cpp.objcpp". + * + * For bugs or ommisions please email objcpp@johnholdsworth.com + * + * Home page for updates and docs: http://objcpp.johnholdsworth.com + * + * You may make commercial use of this source in applications without + * charge but not sell it as source nor can you remove this notice from + * this source if you redistribute. You can make any changes you like + * to this code before redistribution but please annotate them below. + * + * If you find it useful please send a donation via paypal to account + * objcpp@johnholdsworth.com. Thanks. + * + * THIS CODE IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND EITHER + * EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + * WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS + * THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING + * ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT + * OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED + * TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED + * BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH + * ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + */ + +#import +#import + +#import "objsql.h" + +#if 0 +#ifdef OODEBUG +#define OODEBUG_SQL 1 +#endif +#endif + +OOOODatabase OODB; + +static NSString *kOOObject = @"__OOOBJECT__", *kOOInsert = @"__ISINSERT__", *kOOUpdate = @"__ISUPDATE__", *kOOExecSQL = @"__OOEXEC__"; + +#pragma mark OORecord abstract superclass for records + +@implementation OORecord + ++ (id)record OO_AUTORETURNS { + return OO_AUTORELEASE( [[self alloc] init] ); +} + ++ (id)insert OO_AUTORETURNS { + OORecord *record = [self record]; + [record insert]; + return record; +} + ++ (id)insertWithParent:(id)parent { + return [[OODatabase sharedInstance] copyJoinKeysFrom:parent to:[self insert]]; +} + +- (id)insert { [[OODatabase sharedInstance] insert:self]; return self; } +- (id)delete { [[OODatabase sharedInstance] delete:self]; return self; } + +- (void)update { [[OODatabase sharedInstance] update:self]; } +- (void)indate { [[OODatabase sharedInstance] indate:self]; } +- (void)upsert { [[OODatabase sharedInstance] upsert:self]; } + +- (int)commit { return [[OODatabase sharedInstance] commit]; } +- (int)rollback { return [[OODatabase sharedInstance] rollback]; } + +// to handle null values for ints, floats etc. +- (void)setNilValueForKey:(NSString *)key { + static OOReference zeroForNull; + if ( !zeroForNull ) + zeroForNull = [NSNumber numberWithInt:0]; + [self setValue:zeroForNull forKey:key]; +} + ++ (OOArray)select { + return [[OODatabase sharedInstance] select:nil intoClass:self joinFrom:nil]; +} + ++ (OOArray)select:(cOOString)sql { + return [[OODatabase sharedInstance] select:sql intoClass:self joinFrom:nil]; +} + ++ (OOArray)selectRecordsRelatedTo:(id)parent { + return [[OODatabase sharedInstance] select:nil intoClass:self joinFrom:parent]; +} + +- (OOArray)select { + return [[OODatabase sharedInstance] select:nil intoClass:[self class] joinFrom:self]; +} + +/** + import a flat file with column values separated by the delimiter specified into + the table associated with this class. + */ + ++ (int)importFrom:(OOFile &)file delimiter:(cOOString)delim { + OOArray rows = [OOMetaData import:file.string() intoClass:self delimiter:delim]; + [[OODatabase sharedInstance] insertArray:rows]; + return [OODatabase commit]; +} + +/** + Export a flat file with all rows in the table associated with the record subclass. + */ + ++ (BOOL)exportTo:(OOFile &)file delimiter:(cOOString)delim { + return file.save( [OOMetaData export:[self select] delimiter:delim] ); +} + +/** + populate a view with the string values taken from the ivars of the record + */ + +- (void)bindToView:(OOView *)view delegate:(id)delegate { + [OOMetaData bindRecord:self toView:view delegate:delegate]; +} + +/** + When the delegate method os sent use this method to update the record's values + */ + +- (void)updateFromView:(OOView *)view { + [OOMetaData updateRecord:self fromView:view]; +} + +/** + Default description is dictionary containing values of all ivars + */ + +- (NSString *)description { + OOMetaData *metaData = [OOMetaData metaDataForClass:[self class]]; + // hack required where record contains a field "description" to avoid recursion + OOStringArray ivars; ivars <<= *metaData->ivars; ivars -= "description"; + return [*[metaData encode:[self dictionaryWithValuesForKeys:ivars]] description]; +} + +@end + +#pragma mark OOAdaptor - all methods required by objsql to access a database + +/** + An internal class representing the interface to a particular database, in this case sqlite3. + */ + +@interface OOAdaptor : NSObject { + sqlite3 *db; + sqlite3_stmt *stmt; + struct _str_link { + struct _str_link *next; char str[1]; + } *strs; + OO_UNSAFE OODatabase *owner; +} + +- initPath:(cOOString)path database:(OODatabase *)database; +- (BOOL)prepare:(cOOString)sql; + +- (BOOL)bindCols:(cOOStringArray)columns values:(cOOValueDictionary)values startingAt:(int)pno bindNulls:(BOOL)bindNulls; +- (OOArray)bindResultsIntoInstancesOfClass:(Class)recordClass metaData:(OOMetaData *)metaData; +- (sqlite_int64)lastInsertRowID; + +@end + +@interface NSData(OOExtras) +- initWithDescription:(NSString *)description; +@end + +#pragma mark OODatabase is the low level interface to a particular database + +@implementation OODatabase + +static OOReference sharedInstance; + +/** + By default database file is "objsql.db" in the user/application's "Documents" directory + and a single shared OODatabase instance used for all db operations. + */ + ++ (OODatabase *)sharedInstance { + if ( !sharedInstance ) + [self sharedInstanceForPath:OODocument("objsql.db").path()]; + return sharedInstance; +} + +/** + Shared instance can be switched between any file paths. + */ + + + (OODatabase *)sharedInstanceForPath:(cOOString)path { + if ( !!sharedInstance ) + sharedInstance = OONil; + if ( !!path ) + OO_RELEASE( sharedInstance = [[OODatabase alloc] initPath:path] ); + return sharedInstance; +} + ++ (BOOL)exec:(cOOString)fmt, ... { + va_list argp; va_start(argp, fmt); + NSString *sql = [[NSString alloc] initWithFormat:fmt arguments:argp]; + va_end( argp ); + return [[self sharedInstance] exec:OO_AUTORELEASE( sql )]; +} + ++ (OOArray)select:(cOOString)select intoClass:(Class)recordClass joinFrom:(id)parent { + return [[self sharedInstance] select:select intoClass:recordClass joinFrom:parent]; +} ++ (OOArray)select:(cOOString)select intoClass:(Class)recordClass { + return [[self sharedInstance] select:select intoClass:recordClass joinFrom:nil]; +} ++ (OOArray)select:(cOOString)select { + return [[self sharedInstance] select:select intoClass:nil joinFrom:nil]; +} + ++ (int)insertArray:(const OOArray &)objects { return [[self sharedInstance] insertArray:objects]; } ++ (int)deleteArray:(const OOArray &)objects { return [[self sharedInstance] deleteArray:objects]; } + ++ (int)insert:(id)object { return [[self sharedInstance] insert:object]; } ++ (int)delete:(id)object { return [[self sharedInstance] delete:object]; } ++ (int)update:(id)object { return [[self sharedInstance] update:object]; } + ++ (int)indate:(id)object { return [[self sharedInstance] indate:object]; } ++ (int)upsert:(id)object { return [[self sharedInstance] upsert:object]; } + ++ (int)commit { return [[self sharedInstance] commit]; } ++ (int)rollback { return [[self sharedInstance] rollback]; } ++ (int)commitTransaction { return [[OODatabase sharedInstance] commitTransaction]; } + +/** + Designated initialiser for OODatabase instances. Generally only the shared instance is used + and the OODatabase class object is messaged instead. + */ + +- initPath:(cOOString)path { + if ( self = [super init] ) + OO_RELEASE( adaptor = [[OOAdaptor alloc] initPath:path database:self] ); + return self; +} + +/** + Automatically register all classes which are subclasses of a record abstract superclass (e.g. OORecord). + */ + +- (OOStringArray)registerSubclassesOf:(Class)recordSuperClass { + int numClasses = objc_getClassList( NULL, 0 ); + Class *classes = (Class *)malloc( sizeof *classes * numClasses ); + OOArray viewClasses; + OOStringArray classNames; + + // scan all registered classes for relevant subclasses + numClasses = objc_getClassList( classes, numClasses ); + for ( int c=0 ; cresults. + */ + +- (BOOL)exec:(cOOString)fmt, ... { + va_list argp; va_start(argp, fmt); + NSString *sql = [[NSString alloc] initWithFormat:fmt arguments:argp]; + va_end( argp ); + results = [self select:sql intoClass:NULL joinFrom:nil]; + OO_RELEASE( sql ); + return !errcode; +} + +/** + Return a single value from row 1, column one from sql sent to the database as a string. + */ + +- (OOString)stringForSql:(cOOString)fmt, ... { + va_list argp; va_start(argp, fmt); + NSString *sql = OO_AUTORELEASE( [[NSString alloc] initWithFormat:fmt arguments:argp] ); + va_end( argp ); + if( [self exec:"%@", sql] && results > 0 ) { + NSString *aColumnName = [[**results[0] allKeys] objectAtIndex:0]; + return [(NSNumber *)(*results[0])[aColumnName] stringValue]; + } + else + return nil; +} + +/** + Used to initialise new child records automatically from parent in relation. + */ + +- (id)copyJoinKeysFrom:(id)parent to:(id)newChild { + OOMetaData *parentMetaData = [self tableMetaDataForClass:[parent class]], + *childMetaData = [self tableMetaDataForClass:[newChild class]]; + OOStringArray commonColumns = [parentMetaData naturalJoinTo:childMetaData->columns]; //// + OOValueDictionary keyValues = [parentMetaData encode:[parent dictionaryWithValuesForKeys:commonColumns]]; + [newChild setValuesForKeysWithDictionary:[childMetaData decode:keyValues]]; + return newChild; +} + +/** + Build a where clause for the columns specified. + */ + +- (OOString)whereClauseFor:(cOOStringArray)columns values:(cOOValueDictionary)values qualifyNulls:(BOOL)qualifyNulls { + OOString out; + for ( int i=0 ; ijoinableColumns]; + joinValues = [parentMetaData encode:[parent dictionaryWithValuesForKeys:sharedColumns]]; + + sql += [self whereClauseFor:sharedColumns values:joinValues qualifyNulls:NO]; + } + + if ( [metaData->recordClass respondsToSelector:@selector(ooOrderBy)] ) + sql += OOFormat( @"\norder by %@", [metaData->recordClass ooOrderBy] ); + +#ifdef OODEBUG_SQL + NSLog( @"-[OOMetaData prepareSql:] %@\n%@", *sql, *joinValues ); +#endif + + if ( ![*adaptor prepare:sql] ) + return NO; + + return !parent || [*adaptor bindCols:sharedColumns values:joinValues startingAt:1 bindNulls:NO]; +} + +/** + Determine a list of the tables which have a natural join to the record passed in. + If the record is a specific instance of from a table this should determine if + there are any record which exist using the join. + */ + +- (OOArray)tablesRelatedByNaturalJoinFrom:(id)record { + OOMetaData *metaData = [record class] == [OOMetaData class] ? + record : [self tableMetaDataForClass:[record class]]; + + OOStringArray tablesWithNaturalJoin; + tablesWithNaturalJoin <<= metaData->tablesWithNaturalJoin; + + if ( record && record != metaData ) + for ( int i=0 ; i > tmpResults = [*adaptor bindResultsIntoInstancesOfClass:NULL metaData:nil]; + + if ( ![(*tmpResults[0])["result"] intValue] ) + ~tablesWithNaturalJoin[i--]; + } + + return tableMetaDataByClassName[+tablesWithNaturalJoin]; +} + +/** + Perform a select from a table on the database using either the sql specified + orselect all columns from the table associated with the record class passed in. + If a parent is passed in make a natural join from that record. + */ + +- (OOArray)select:(cOOString)select intoClass:(Class)recordClass joinFrom:(id)parent { + OOMetaData *metaData = [self tableMetaDataForClass:recordClass ? recordClass : [parent class]]; + OOString sql = !select ? + OOFormat( @"select %@\nfrom %@", *(metaData->outcols/", "), *metaData->tableName ) : *select; + + if ( ![self prepareSql:sql joinFrom:parent toTable:metaData] ) + return nil; + + return [*adaptor bindResultsIntoInstancesOfClass:recordClass metaData:metaData]; +} + +- (OOArray)select:(cOOString)select intoClass:(Class)recordClass { + return [self select:select intoClass:recordClass joinFrom:nil]; +} + +- (OOArray)select:(cOOString)select { + return [self select:select intoClass:nil joinFrom:nil]; +} + +/** + Returns sqlite3 row identifier for a record instance. + */ + +- (long long)rowIDForRecord:(id)record { + OOMetaData *metaData = [self tableMetaDataForClass:[record class]]; + OOString sql = OOFormat( @"select ROWID from %@", *metaData->tableName ); + OOArray > idResults = [self select:sql intoClass:nil joinFrom:record]; + return [*(*idResults[0])[@"rowid"] longLongValue]; +} + +/** + Returns sqlite3 row identifier for last inserted record. + */ + +- (long long)lastInsertRowID { + return [*adaptor lastInsertRowID]; +} + +/** + Insert an array of record objects into the database. This needs to be commited to take effect. + */ + +- (int)insertArray:(const OOArray &)objects { + int count = 0; + for ( id object in *objects ) + count = [self insert:object]; + return count; +} + +/** + Delete an array of record objects from the database. This needs to be commited to take effect. + */ + +- (int)deleteArray:(const OOArray &)objects { + int count = 0; + for ( id object in *objects ) + if ( ![object respondsToSelector:@selector(delete)] ) + count = [self delete:object]; + else { + [object delete]; + count++; + } + return count; +} + +/** + Insert the values of the record class instance at the time this method was called into the db. + (must be commited to take effect). Returns the total number of outstanding inserts/updates/deletes. + */ + +- (int)insert:(id)record { + return transaction += OOValueDictionary( kOOObject, record, kOOInsert, kCFNull, nil ); +} + +/** + Use the values of the record instance at the time this method is called in a where clause to + delete from the database when commit is called. Returns the total number of outstanding + inserts/updates/deletes. + */ + +- (int)delete:(id)record { + return transaction += OOValueDictionary( kOOObject, record, nil ); +} + +/** + Call this method if you intend to make changes to the record object and save them to the database. + This takes a snapshot of the previous values to use as a key for the update operation when "commit" + is called. Returns the total number of outstanding inserts/updates/deletes. + */ + +- (int)update:(id)record { + OOMetaData *metaData = [self tableMetaDataForClass:[record class]]; + OOValueDictionary oldValues = [metaData encode:[record dictionaryWithValuesForKeys:metaData->columns]]; + for ( NSString *key in *metaData->tocopy ) + OO_RELEASE( oldValues[key] = [oldValues[key] copy] ); + oldValues[kOOUpdate] = OONull; + oldValues[kOOObject] = record; + return transaction += oldValues; +} + +/** + Inserts a record into the database the deletes any previous record with the same key. + This ensures the record's rowid changes if this is used by child records. + */ + +- (int)indate:(id)record { + OOMetaData *metaData = [self tableMetaDataForClass:[record class]]; + OOString sql = OOFormat( @"select rowid from %@", *metaData->tableName ); + OOArray existing = [self select:sql intoClass:nil joinFrom:record]; + int count = [self insert:record]; + for ( NSDictionary *exist in *existing ) { + OOString sql = OOFormat( @"delete from %@ where rowid = %ld", *metaData->tableName, + (long)[[exist objectForKey:@"rowid"] longLongValue] ); + transaction += OOValueDictionary( kOOExecSQL, *sql, nil ); + } + return count; +} + +/** + Inserts a record into the database unless another record with the same key column values + exists in which case it will do an update of the previous record (preserving the ROWID.) + */ + +- (int)upsert:(id)record { + OOArray existing = [self select:nil intoClass:[record class] joinFrom:record]; + if ( existing > 1 ) + OOWarn( @"-[ODatabase upsert:] Duplicate record for upsert: %@", record ); + if ( existing > 0 ) { + [self update:existing[0]]; + (*transaction[-1])[kOOObject] = record; + return transaction; + } + else + return [self insert:record]; +} + +/** + Commit all pending inserts, updates and deletes to the database. Use commitTransaction to perform + this inside a database transaction. + */ + +- (int)commit { + int commited = 0; + + for ( int i=0 ; i object = *values[kOOObject]; values -= kOOObject; + BOOL isInsert = !!~values[kOOInsert], isUpdate = !!~values[kOOUpdate]; + + OOMetaData *metaData = [self tableMetaDataForClass:[object class]]; + OOValueDictionary newValues = [metaData encode:[object dictionaryWithValuesForKeys:metaData->columns]]; + OOStringArray changedCols; + + if ( isUpdate ) { + for ( NSString *name in *metaData->columns ) + if ( ![*newValues[name] isEqual:values[name]] ) + changedCols += name; + } + else + values = newValues; + + OOString sql = isInsert ? + OOFormat( @"insert into %@ (%@) values (", *metaData->tableName, *(metaData->columns/", ") ) : + OOFormat( isUpdate ? @"update %@ set" : @"delete from %@", *metaData->tableName ); + + int nchanged = changedCols; + if ( isUpdate && nchanged == 0 ) { + OOWarn( @"%s %@ (%@)", errmsg = (char *)"-[ODatabase commit:] Update of unchanged record", *object, *(lastSQL = sql) ); + continue; + } + + for ( int i=0 ; icolumns ; i++ ) + sql += i==0 ? quote : commaQuote; + sql += ")"; + } + else + sql += [self whereClauseFor:metaData->columns values:values qualifyNulls:YES]; + +#ifdef OODEBUG_SQL + NSLog( @"-[OODatabase commit]: %@ %@", *sql, *values ); +#endif + + if ( ![*adaptor prepare:sql] ) + continue; + + if ( isUpdate ) + [*adaptor bindCols:changedCols values:newValues startingAt:1 bindNulls:YES]; + [*adaptor bindCols:metaData->columns values:values startingAt:1+nchanged bindNulls:isInsert]; + + [*adaptor bindResultsIntoInstancesOfClass:nil metaData:metaData]; + commited += updateCount; + } + + transaction = nil; + return commited; +} + +/** + Commit all pending inserts, updates, deletes to the database inside a transaction. + */ + +- (int)commitTransaction { + [self exec:"BEGIN TRANSACTION"]; + int updated = [self commit]; + return [self exec:"COMMIT"] ? updated : 0; +} + +/** + Rollback any outstanding inserts, updates, or deletes. Please note updated values + are also rolled back inside the actual record in the application as well. + */ + +- (int)rollback { + for ( NSMutableDictionary *d in *transaction ) { + OODictionary values = d; + + if ( !!~values[kOOUpdate] ) { + OORef record = ~values[kOOObject]; + OOMetaData *metaData = [self tableMetaDataForClass:[*record class]]; + +#ifndef OO_ARC + for ( NSString *name in *metaData->boxed ) + OO_RELEASE( (id)[[*record valueForKey:name] pointerValue] ); +#endif + + [*record setValuesForKeysWithDictionary:[metaData decode:values]]; + } + } + return (int)[*~transaction count]; +} + +/** + Find/create an instance of the OOMetaData class which describes a record class and its associated table. + If the table does not exist it will be created along with indexes for columns/ivars which have + upper case names for use in joins. Details of the tables parameters can be controlled by using + methods in the OOTableCustomisation protool. If it does not exist a meta table class which + prepresents OOMetaData records themselves is also created from which the list of all registered + tables can be selected. + */ + +- (OOMetaData *)tableMetaDataForClass:(Class)recordClass { + if ( !recordClass || recordClass == [OOMetaData class] ) + return [OOMetaData metaDataForClass:[OOMetaData class]]; + + OOString className = class_getName( recordClass ); + OOMetaData *metaData = tableMetaDataByClassName[className]; + + if ( !metaData ) { + metaData = [OOMetaData metaDataForClass:recordClass]; + +#ifdef OODEBUG_SQL + NSLog(@"\n%@", *metaData->createTableSQL); +#endif + + if ( metaData->tableName[0] != '_' && + [self stringForSql:"select count(*) from sqlite_master where name = '%@'", + *metaData->tableName] == "0" ) + if ( [self exec:"%@", *metaData->createTableSQL] ) + for ( NSString *idx in *metaData->indexes ) + if ( ![self exec:idx] ) + OOWarn( @"-[OOMetaData tableMetaDataForClass:] Error creating index: %@", idx ); + + tableMetaDataByClassName[className] = metaData; + } + + return metaData; +} + +@end + +#pragma mark OOAdaptor - implements all access to a particular database + +@implementation OOAdaptor + +/** + Connect to/create sqlite3 database + */ + +- (OOAdaptor *)initPath:(cOOString)path database:(OODatabase *)database { + if ( self = [super init] ) { + owner = database; + OOFile( OOFile( path ).directory() ).mkdir(); + if ( (owner->errcode = sqlite3_open( path, &db )) != SQLITE_OK ) { + OOWarn( @"-[OOAdaptor initPath:database:] Error opening database at path: %@", *path ); + return nil; + } + } + return self; +} + +/** + Prepare a sql statement after which values can be bound and results returned. + */ + +- (BOOL)prepare:(cOOString)sql { + if ( (owner->errcode = sqlite3_prepare_v2( db, owner->lastSQL = sql, -1, &stmt, 0 )) != SQLITE_OK ) + OOWarn(@"-[OOAdaptor prepare:] Could not prepare sql: \"%@\" - %s", *owner->lastSQL, owner->errmsg = (char *)sqlite3_errmsg( db ) ); + return owner->errcode == SQLITE_OK; +} + +- (int)bindValue:(id)value asParameter:(int)pno { +#ifdef OODEBUG_BIND + NSLog( @"-[OOAdaptor bindValue:bindValue:] bind parameter #%d as: %@", pno, value ); +#endif + if ( !value || value == OONull ) + return sqlite3_bind_null( stmt, pno ); +#if OOSQL_THREAD_SAFE_BUT_USES_MORE_MEMORY + else if ( [value isKindOfClass:[NSString class]] ) + return sqlite3_bind_text( stmt, pno, [value UTF8String], -1, SQLITE_STATIC ); +#else + else if ( [value isKindOfClass:[NSString class]] ) { + int len = (int)[value lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + struct _str_link *str = (struct _str_link *)malloc( sizeof *str + len ); + str->next = strs; + strs = str; + [value getCString:str->str maxLength:len+1 encoding:NSUTF8StringEncoding]; + return sqlite3_bind_text( stmt, pno, str->str, len, SQLITE_STATIC ); + } +#endif + else if ( [value isKindOfClass:[NSData class]] ) + return sqlite3_bind_blob( stmt, pno, [value bytes], (int)[value length], SQLITE_STATIC ); + + const char *type = [value objCType]; + if ( type ) + switch ( type[0] ) { + case 'c': case 's': case 'i': case 'l': + case 'C': case 'S': case 'I': case 'L': + return sqlite3_bind_int( stmt, pno, [value intValue] ); + case 'q': case 'Q': + return sqlite3_bind_int64( stmt, pno, [value longLongValue] ); + case 'f': case 'd': + return sqlite3_bind_double( stmt, pno, [value doubleValue] ); + } + + OOWarn( @"-[OOAdaptor bindValue:bindValue:] Undefined type in bind of parameter #%d: %s, value: %@", pno, type, value ); + return -1; +} + +/** + Bind parameters from a prepared SQL statement. The "objCType" method is used to determine the type to bind. + */ + +- (BOOL)bindCols:(cOOStringArray)columns values:(cOOValueDictionary)values startingAt:(int)pno bindNulls:(BOOL)bindNulls { + int errcode; + for ( NSString *name in *columns ) + if ( bindNulls || *values[name] != OONull ) + if ( (errcode = [self bindValue:values[name] asParameter:pno++]) != SQLITE_OK ) + OOWarn( @"-[OOAdaptor bindCols:...] Bind failed column: %@ - %s (%d)", name, owner->errmsg = (char *)sqlite3_errmsg( db ), owner->errcode = errcode ); + return owner->errcode == SQLITE_OK; +} + +/** + Return a dictionary containing the values for a row returned by the database from a select. + These values need to be decoded using a classes metadata to set the ivar values later. + */ + +- (OOValueDictionary)valuesForNextRow { + int ncols = sqlite3_column_count( stmt ); + OOValueDictionary values; + + for ( int i=0 ; i)bindResultsIntoInstancesOfClass:(Class)recordClass metaData:(OOMetaData *)metaData { + OOArray out; + BOOL awakeFromDB = [recordClass instancesRespondToSelector:@selector(awakeFromDB)]; + + while( (owner->errcode = sqlite3_step( stmt )) == SQLITE_ROW ) { + OOValueDictionary values = [self valuesForNextRow]; + if ( recordClass ) { + id record = [[recordClass alloc] init]; + [record setValuesForKeysWithDictionary:[metaData decode:values]]; + + if ( awakeFromDB ) + [record awakeFromDB]; + + out += record; + OO_RELEASE( record ); + } + else + out += values; + } + + if ( owner->errcode != SQLITE_DONE ) + OOWarn(@"-[OOAdaptor bindResultsIntoInstancesOfClass:metaData:] Not done (bind) stmt: %@ - %s", *owner->lastSQL, owner->errmsg = (char *)sqlite3_errmsg( db ) ); + else { + owner->errcode = SQLITE_OK; + out.alloc(); + } + + while ( strs != NULL ) { + struct _str_link *next = strs->next; + free( strs ); + strs = next; + } + owner->updateCount = sqlite3_changes( db ); + sqlite3_finalize( stmt ); + return out; +} + +- (sqlite_int64)lastInsertRowID { + return sqlite3_last_insert_rowid( db ); +} + +- (void) dealloc { + sqlite3_close( db ); + OO_DEALLOC( super ); +} + +@end + +#pragma mark OOMetaData instances represent a table in the database and it's record class + +@implementation OOMetaData + +static OODictionary metaDataByClass; +static OOMetaData *tableOfTables; + ++ (NSString *)ooTableTitle { return @"Table MetaData"; } + ++ (OOMetaData *)metaDataForClass:(Class)recordClass OO_RETURNS { + if ( !tableOfTables ) + OO_RELEASE( tableOfTables = [[OOMetaData alloc] initClass:[OOMetaData class]] ); + OOMetaData *metaData = metaDataByClass[recordClass]; + if ( !metaData ) + OO_RELEASE( metaData = [[OOMetaData alloc] initClass:recordClass] ); + return metaData; +} + ++ (OOArray)selectRecordsRelatedTo:(id)record { + return [[OODatabase sharedInstance] tablesRelatedByNaturalJoinFrom:record]; +} + +- initClass:(Class)aClass { + if ( !(self = [super init]) ) + return self; + recordClass = aClass; + metaDataByClass[recordClass] = self; + recordClassName = class_getName( recordClass ); + tableTitle = [recordClass respondsToSelector:@selector(ooTableTitle)] ? + [recordClass ooTableTitle] : *recordClassName; + tableName = [recordClass respondsToSelector:@selector(ooTableName)] ? + [recordClass ooTableName] : *recordClassName; + + if ( aClass == [OOMetaData class] ) { + ivars = columns = outcols = boxed = unbox = + "tableTitle tableName recordClassName keyColumns ivars columns outcols"; + return self; + } + + createTableSQL = OOFormat( @"create table %@ (", *tableName ); + + OOArray hierarchy; + do + hierarchy += aClass; + while ( (aClass = [aClass superclass]) && aClass != [NSObject class] ); + + for ( int h=(int)hierarchy-1 ; h>=0 ; h-- ) { + aClass = (Class)hierarchy[h]; /// + Ivar *ivarInfo = class_copyIvarList( aClass, NULL ); + if ( !ivarInfo ) + continue; + + for ( int in=0 ; ivarInfo[in] ; in++ ) { + OOString columnName = ivar_getName( ivarInfo[in] ); + ivars += columnName; + + OOString type = types[columnName] = ivar_getTypeEncoding( ivarInfo[in] ), dbtype = ""; + + SEL columnSel = sel_getUid(columnName); + switch ( type[0] ) { + case 'c': case 's': case 'i': case 'l': + case 'C': case 'S': case 'I': case 'L': + case 'q': case 'Q': + dbtype = @"int"; + break; + case 'f': case 'd': + dbtype = @"real"; + break; + case '{': + static OOPattern isOORef( "=\"ref\"@\"NS" ); + if( !(type & isOORef) ) + OOWarn( @"-[OOMetaData initClass:] Invalid structure type for ivar %@ in class %@: %@", *columnName, *recordClassName, *type ); + boxed += columnName; + if ( ![recordClass instancesRespondToSelector:columnSel] ) { + unbox += columnName; + if ( [[recordClass superclass] instancesRespondToSelector:columnSel] ) + OOWarn( @"-[OOMetaData initClass:] Superclass of class %@ is providing method for column: %@", *recordClassName, *columnName ); + } + case '@': + static OOPattern isNSString( "NS(Mutable)?String\"" ), + isNSDate( "\"NSDate\"" ), isNSData( "NS(Mutable)?Data\"" ); + if ( type & isNSString ) + dbtype = @"text"; + else if ( type & isNSDate ) { + dbtype = @"real"; + dates += columnName; + } + else { + if ( !(type & isNSData) ) + archived += columnName; + blobs += columnName; + dbtype = @"blob"; + } + break; + default: + OOWarn( @"-[OOMetaData initClass:] Unknown data type '%@' in class %@", *type, *tableName ); + archived += columnName; + blobs += columnName; + dbtype = @"blob"; + break; + } + + if ( dbtype == @"text" ) + tocopy += columnName; + + if ( columnName == @"rowid" || columnName == @"ROWID" || + columnName == @"OID" || columnName == @"_ROWID_" ) { + outcols += columnName; + continue; + } + + createTableSQL += OOFormat(@"%s\n\t%@ %@ /* %@ */", + !columns?"":",", *columnName, *dbtype, *type ); + + if ( iswupper( columnName[columnName[0] != '_' ? 0 : 1] ) ) + indexes += OOFormat(@"create index %@_%@ on %@ (%@)\n", + *tableName, *columnName, + *tableName, *columnName); + + if ( class_getName( [aClass superclass] )[0] != '_' ) { + columns += columnName; + outcols += columnName; + joinableColumns += columnName; + } + } + + free( ivarInfo ); + } + + if ( [recordClass respondsToSelector:@selector(ooTableKey)] ) + createTableSQL += OOFormat( @",\n\tprimary key (%@)", + *(keyColumns = [recordClass ooTableKey]) ); + + if ( [recordClass respondsToSelector:@selector(ooConstraints)] ) + createTableSQL += OOFormat( @",\n\t%@", [recordClass ooConstraints] ); + + createTableSQL += "\n)\n"; + + if ( [recordClass respondsToSelector:@selector(ooTableSql)] ) { + createTableSQL = [recordClass ooTableSql]; + indexes = nil; + } + + tableOfTables->tablesWithNaturalJoin += recordClassName; + tablesWithNaturalJoin += recordClassName; + + for( Class other in [*metaDataByClass allKeys] ) { + OOMetaData *otherMetaData = metaDataByClass[other]; + if ( other == recordClass || otherMetaData == tableOfTables ) + continue; + + if ( [self naturalJoinTo:otherMetaData->joinableColumns] > 0 ) + tablesWithNaturalJoin += otherMetaData->recordClassName; + if ( [otherMetaData naturalJoinTo:joinableColumns] > 0 ) + otherMetaData->tablesWithNaturalJoin += recordClassName; + } + + return self; +} + +/** + Find the columns shared between two classes and that have upper case names (are indexed). + */ + +- (OOStringArray)naturalJoinTo:(cOOStringArray)to { + //NSLog( @"%@ -- %@", *columns, *to ); + OOStringArray commonColumns = columns & to; + for ( int i=0 ; i)import:(const OOArray > &)nodes intoClass:(Class)recordClass { + OOMetaData *metaData = [self metaDataForClass:recordClass]; + OOArray out; + + for ( NSMutableDictionary *dict in *nodes ) { + OOStringDictionary node = dict, values; + for ( NSString *ivar in *metaData->columns ) + values[ivar] = node[ivar]; + + id record = [[recordClass alloc] init]; + [record setValuesForKeysWithDictionary:[metaData decode:values]]; + out += record; + OO_RELEASE( record ); + } + + return out; +} + +/** + Convert a string taken from a flat file into record instances which can be inserted into the database. + */ + ++ (OOArray)import:(cOOString)string intoClass:(Class)recordClass delimiter:(cOOString)delim { + OOMetaData *metaData = [self metaDataForClass:recordClass]; + // remove escaped newlines then split by newline + OOStringArray lines = (string - @"\\\\\n") / @"\n"; + lines--; // pop last empty line + + OOArray out; + for ( int l=0 ; l values; + values[metaData->columns] = *lines[l] / delim; + + // empty columns are taken as null values + for ( NSString *key in *metaData->columns ) + if ( [*values[key] isEqualToString:@""] ) + values[key] = OONull; + + // convert description strings to NSData + for ( NSString *key in *metaData->blobs ) + OO_RELEASE( values[key] = (NSString *)[[NSData alloc] initWithDescription:values[key]] ); + + id record = [[recordClass alloc] init]; + [record setValuesForKeysWithDictionary:[metaData decode:values]]; + out += record; + OO_RELEASE( record ); + } + + return out; +} + +/** + Convert a set of records selected from the database into a string which can be saved to disk. + */ + ++ (OOString)export:(const OOArray &)array delimiter:(cOOString)delim { + OOMetaData *metaData = nil; + OOString out; + + for ( id record in *array ) { + if ( !metaData ) + metaData = [record isKindOfClass:[NSDictionary class]] ? + OONull : [self metaDataForClass:[record class]]; + + OODictionary values = metaData == OONull ? record : + *[metaData encode:[record dictionaryWithValuesForKeys:metaData->columns]]; + + OOStringArray line; + NSString *blank = @""; + for ( NSString *key in *metaData->columns ) + line += *values[key] != OONull ? [values[key] stringValue] : blank; + + out += line/delim+"\n"; + } + + return out; +} + +/** + Bind a record to a view containing elements which are to display values from the record. + The ivar number is selected by the subview's tag value and it's ".text" property if set to + the value returned record value "stringValue" for the ivar. Supports images stored as + NSData objects, UISwitches bound to boolean valuea and UITextField for alll other values. + */ + ++ (void)bindRecord:(id)record toView:(OOView *)view delegate:(id)delegate { +#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED + OOMetaData *metaData = [self metaDataForClass:[record class]]; + OOValueDictionary values = [metaData encode:[record dictionaryWithValuesForKeys:metaData->ivars]]; + + for ( int i=0 ; iivars ; i++ ) { + UILabel *label = (UILabel *)[view viewWithTag:1+i]; + id value = values[metaData->ivars[i]]; + + if ( [label isKindOfClass:[UIImageView class]] ) + ((UIImageView *)label).image = value != OONull ? [UIImage imageWithData:(NSData *)value] : nil; + else if ( [label isKindOfClass:[UISwitch class]] ) { + UISwitch *uiSwitch = (UISwitch *)label; + uiSwitch.on = value != OONull ? [value charValue] : 0; + if ( delegate ) + [uiSwitch addTarget:delegate action:@selector(valueChanged:) forControlEvents:UIControlEventValueChanged]; + } + else if ( [label isKindOfClass:[UIWebView class]] ) + [(UIWebView *)label loadHTMLString:value != OONull ? value : @"" baseURL:nil]; + else if ( label ) { + label.text = value != OONull ? [value stringValue] : @""; + if ( [label isKindOfClass:[UITextView class]] ) { + [(UITextView *)label setContentOffset:CGPointMake(0,0) animated:NO]; + [(UITextView *)label scrollRangeToVisible:NSMakeRange(0,0)]; + } + } + + if ( [label respondsToSelector:@selector(delegate)] ) + ((UITextField *)label).delegate = delegate; + label.hidden = NO; + + if ( (label = (UILabel *)[view viewWithTag:-1-i]) ) { + label.text = **metaData->ivars[i]; + label.hidden = NO; + } + } + + OOView *subView; + for ( int i=metaData->ivars ; (subView = [view viewWithTag:1+i]) ; i++ ) { + subView.hidden = YES; + if ( (subView = [view viewWithTag:-1-i]) ) + subView.hidden =YES; + } +#endif +} + +/** + When the delegate method fires this method should be called to update + the record with the modified value before updating the database. + */ + ++ (void)updateRecord:(id)record fromView:(OOView *)view { +#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED + if ( view.tag > 0 && [view respondsToSelector:@selector(text)] ) { + OOMetaData *metaData = [self metaDataForClass:[record class]]; + NSString *name = *metaData->ivars[view.tag-1]; + OOString type = metaData->types[name]; + id value = OO_RETAIN(((UITextField *)view).text ); + + if ( type[0] == '{' ) { +#ifdef OO_ARC + ooArcRetain( value ); +#endif + value = [[NSValue alloc] initWithBytes:&value objCType:@encode(id)]; +#ifndef OO_ARC + OO_RELEASE( (id)[[record valueForKey:name] pointerValue] ); +#endif + } + + [record setValue:value forKey:name]; + OO_RELEASE( value ); + } + for ( OOView *subview in [view subviews] ) + [self updateRecord:record fromView:subview]; +#endif +} + +@end + +@implementation OOView(OOExtras) + +- copyView { + NSData *archived = [NSKeyedArchiver archivedDataWithRootObject:self]; + OOView *copy = [NSKeyedUnarchiver unarchiveObjectWithData:archived]; +#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED + copy.frame = CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.height); +#else + copy.frame = NSMakeRect(0.0, 0.0, self.frame.size.width, self.frame.size.height); +#endif + return copy; +} + +@end + +@implementation NSData(OOExtras) + +static int unhex ( unsigned char ch ) { + return ch >= 'a' ? 10 + ch - 'a' : ch >= 'A' ? 10 + ch - 'A' : ch - '0'; +} + +- initWithDescription:(NSString *)description { + NSInteger len = [description length]/2, lin = [description lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + char *bytes = (char *)malloc( len ), *optr = bytes, *hex = (char *)malloc( lin+1 ); + [description getCString:hex maxLength:lin+1 encoding:NSUTF8StringEncoding]; + + for ( char *iptr = hex ; *iptr ; iptr+=2 ) { + if ( *iptr == '<' || *iptr == ' ' || *iptr == '>' ) + iptr--; + else + *optr++ = unhex( *iptr )*16 + unhex( *(iptr+1) ); + } + + free( hex ); + return [self initWithBytesNoCopy:bytes length:optr-bytes freeWhenDone:YES]; +} + +- (NSString *)stringValue { return [self description]; } + +@end + +@interface NSString(OOExtras) +@end +@implementation NSString(OOExtras) +- (char)charValue { return [self intValue]; } +- (char)shortValue { return [self intValue]; } +- (NSString *)stringValue { return self; } +@end + +@interface NSArray(OOExtras) +@end +@implementation NSArray(OOExtras) +- (NSString *)stringValue { + static OOReplace reformat( "/(\\s)\\s+|^\\(|\\)$|\"/$1/" ); + return &([self description] | reformat); +} +@end + +@interface NSDictionary(OOExtras) +@end +@implementation NSDictionary(OOExtras) +- (NSString *)stringValue { + static OOReplace reformat( "/(\\s)\\s+|^\\{|\\}$|\"/$1/" ); + return &([self description] | reformat); +} +@end + +#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED +@interface UISwitch(OOExtras) +@end +@implementation UISwitch(OOExtras) +- (NSString *)text { return self.on ? @"1" : @"0"; } +@end +#endif + diff --git a/samples/Whitespace/hworld.ws.ws b/samples/Whitespace/hworld.ws.ws new file mode 100644 index 0000000000..78e253d57a --- /dev/null +++ b/samples/Whitespace/hworld.ws.ws @@ -0,0 +1,110 @@ +Say hello. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +