diff --git a/Foundation/CPArray.j b/Foundation/CPArray.j index eeb8bad8b..f3dd32481 100755 --- a/Foundation/CPArray.j +++ b/Foundation/CPArray.j @@ -55,6 +55,11 @@ return [_array objectAtIndex:_index]; } +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + return [_array countByEnumeratingWithState:aState objects:objects count:aCount]; +} + @end /* @ignore */ @@ -1206,6 +1211,23 @@ @end +@implementation CPArray (CPFastEnumeration) + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + var count = [self count]; + + if (aState.state >= count) + return 0; + + aState.items = self; + aState.state = count; + + return count; +} + +@end + @implementation CPArray (CPCoding) - (id)initWithCoder:(CPCoder)aCoder diff --git a/Foundation/CPDictionary.j b/Foundation/CPDictionary.j index fc162b2c7..105b2c711 100755 --- a/Foundation/CPDictionary.j +++ b/Foundation/CPDictionary.j @@ -532,6 +532,31 @@ @end +@implementation CPDictionary (CPFastEnumeration) + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + var count = [self count]; + + if (aState.state >= count) + return 0; + + var keys = [self allKeys], + index = count, + objects = []; + + while (count--) + objects[index] = [self objectForKey:keys[index]]; + + aState.items0 = objects; + aState.items1 = keys; + aState.state = count; + + return count; +} + +@end + /*! @class CPMutableDictionary @ingroup compatability diff --git a/Foundation/CPEnumerator.j b/Foundation/CPEnumerator.j index 51d25b92a..21084487f 100755 --- a/Foundation/CPEnumerator.j +++ b/Foundation/CPEnumerator.j @@ -56,4 +56,11 @@ return []; } + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + // This is pretty terrible, but what do you expect. + return [[self allObjects] countByEnumeratingWithState:aState objects:objects count:aCount]; +} + @end diff --git a/Foundation/CPIndexSet.j b/Foundation/CPIndexSet.j index 03b427d34..02d6424c2 100644 --- a/Foundation/CPIndexSet.j +++ b/Foundation/CPIndexSet.j @@ -834,6 +834,30 @@ var CPIndexSetCountKey = @"CPIndexSetCountKey", @end +@implementation CPIndexSet (CPFastEnumeration) + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + var rangeIndex = aState.state; + + if (aState.state >= _ranges.length) + return 0; + + var range = _ranges[rangeIndex], + index = 0, + start = range.location, + end = CPMaxRange(range); + + for (; start < end; ++start, ++index) + objects[index] = start; + + ++aState.state; + + return range.length; +} + +@end + /*! @class CPMutableIndexSet @ingroup compatability diff --git a/Foundation/CPNull.j b/Foundation/CPNull.j index 43ba1cbc9..cd67e9e1e 100644 --- a/Foundation/CPNull.j +++ b/Foundation/CPNull.j @@ -58,3 +58,12 @@ var CPNullSharedNull = nil; } @end + +@implementation CPNull (CPFastEnumeration) + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + return 0; +} + +@end diff --git a/Foundation/CPSet.j b/Foundation/CPSet.j index 7026a376e..fd28da504 100644 --- a/Foundation/CPSet.j +++ b/Foundation/CPSet.j @@ -499,6 +499,22 @@ var CPSetObjectsKey = @"CPSetObjectsKey"; @end +@implementation CPSet (CPFastEnumeration) + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + var count = [self count]; + + if (aState.state >= count) + return 0; + + count = [[self allObjects] countByEnumeratingWithState:aState objects:objects count:aCount]; + + return count; +} + +@end + /*! @class CPMutableSet @ingroup compatability diff --git a/Objective-J/Preprocessor.js b/Objective-J/Preprocessor.js index eec0358c8..8e3a457c5 100644 --- a/Objective-J/Preprocessor.js +++ b/Objective-J/Preprocessor.js @@ -29,10 +29,13 @@ var TOKEN_ACCESSORS = "accessors", TOKEN_FUNCTION = "function", TOKEN_IMPLEMENTATION = "implementation", TOKEN_IMPORT = "import", + TOKEN_EACH = "each", TOKEN_NEW = "new", TOKEN_SELECTOR = "selector", TOKEN_SUPER = "super", - + TOKEN_VAR = "var", + TOKEN_IN = "in", + TOKEN_EQUAL = '=', TOKEN_PLUS = '+', TOKEN_MINUS = '-', @@ -52,7 +55,7 @@ var TOKEN_ACCESSORS = "accessors", TOKEN_QUESTION_MARK = '?', TOKEN_OPEN_PARENTHESIS = '(', TOKEN_CLOSE_PARENTHESIS = ')', - + TOKEN_WHITESPACE = /^(?:(?:\s+$)|(?:\/(?:\/|\*)))/, TOKEN_NUMBER = /^[+-]?\d+(([.]\d+)*([eE][+-]?\d+))?$/, TOKEN_IDENTIFIER = /^[a-zA-Z_$](\w|$)*$/; @@ -290,7 +293,7 @@ Preprocessor.prototype.directive = function(tokens, aStringBuffer, allowedDirect // Currently we simply swallow forward declarations and only provide them to allow // compatibility with Objective-C files. - else if (token == TOKEN_CLASS) + else if (token === TOKEN_CLASS) { tokens.skip_whitespace(); @@ -298,24 +301,119 @@ Preprocessor.prototype.directive = function(tokens, aStringBuffer, allowedDirect } // @implementation Class implementations - else if (token == TOKEN_IMPLEMENTATION) + else if (token === TOKEN_IMPLEMENTATION) this.implementation(tokens, buffer); // @import - else if (token == TOKEN_IMPORT) + else if (token === TOKEN_IMPORT) this._import(tokens); + else if (token === TOKEN_EACH) + this.each(tokens, buffer); + // @selector - else if (token == TOKEN_SELECTOR) + else if (token === TOKEN_SELECTOR) this.selector(tokens, buffer); - else if (token == TOKEN_ACCESSORS) + else if (token === TOKEN_ACCESSORS) return this.accessors(tokens); if (!aStringBuffer) return buffer; } +var fastEnumeratorCount = 0; + +Preprocessor.prototype.each = function(tokens, /*StringBuffer*/ aStringBuffer) +{ + var token = tokens.skip_whitespace(); + + // If we reach an open parenthesis, we are declaring a category. + if (token !== TOKEN_OPEN_PARENTHESIS) + throw new SyntaxError(this.error_message("*** Expecting (, found: \"" + token + "\".")); + + var identifiers = [], + isVared = NO; + + do + { + token = tokens.skip_whitespace(); + + if (identifiers.length === 0 && token === TOKEN_VAR) + { + isVared = YES; + + token = tokens.skip_whitespace(); + } + + if (!TOKEN_IDENTIFIER.test(token)) + throw new SyntaxError(this.error_message("*** Expecting identifier, found: \"" + token + "\".")); + + identifiers.push(token); + + token = tokens.skip_whitespace(); + + if (token !== TOKEN_COMMA && token !== TOKEN_IN) + throw new SyntaxError(this.error_message("*** Expecting \",\", found: \"" + token + "\".")); + + } while (token && token === TOKEN_COMMA); + + if (token !== TOKEN_IN) + throw new SyntaxError(this.error_message("*** Expecting \"in\", found: \"" + token + "\".")); + + var generatedFastEnumeratorName = "$OBJJ_GENERATED_FAST_ENUMERATOR_" + fastEnumeratorCount++; + + CONCAT(aStringBuffer, "var "); + CONCAT(aStringBuffer, generatedFastEnumeratorName); + CONCAT(aStringBuffer, " = new objj_fastEnumerator("); + + this.preprocess(tokens, aStringBuffer, TOKEN_CLOSE_PARENTHESIS, TOKEN_OPEN_PARENTHESIS); + + CONCAT(aStringBuffer, ", "); + CONCAT(aStringBuffer, identifiers.length); + CONCAT(aStringBuffer, ");\n"); + + // for ([var] arg1[, arg2[, ... argN]], $E = new objj_fastEnumerator(expression); + // $E.i < $E.l || $E.e() && ((arg1 = $E.o0[$E.i][, $E.o1[$E.i][, ... $E.oN[$E.i]]]) || YES); + // ++$E.i) + + CONCAT(aStringBuffer, "for ("); + + if (isVared) + { + CONCAT(aStringBuffer, "var "); + CONCAT(aStringBuffer, identifiers.join(", ")); + } + + CONCAT(aStringBuffer, ";("); + CONCAT(aStringBuffer, generatedFastEnumeratorName); + CONCAT(aStringBuffer, ".i < "); + CONCAT(aStringBuffer, generatedFastEnumeratorName); + CONCAT(aStringBuffer, ".l || "); + CONCAT(aStringBuffer, generatedFastEnumeratorName); + CONCAT(aStringBuffer, ".e()) && (("); + + // Man don't you wish we had fast enumeration here!! + for (var index = 0, count = identifiers.length; index < count; ++index) + { + CONCAT(aStringBuffer, identifiers[index]); + CONCAT(aStringBuffer, " = "); + CONCAT(aStringBuffer, generatedFastEnumeratorName); + CONCAT(aStringBuffer, ".o"); + CONCAT(aStringBuffer, index); + CONCAT(aStringBuffer, "["); + CONCAT(aStringBuffer, generatedFastEnumeratorName); + CONCAT(aStringBuffer, ".i]"); + + if (index + 1 < count) + CONCAT(aStringBuffer, ", "); + } + + CONCAT(aStringBuffer, ") || YES); ++"); + CONCAT(aStringBuffer, generatedFastEnumeratorName); + CONCAT(aStringBuffer, ".i)"); +} + Preprocessor.prototype.implementation = function(tokens, /*StringBuffer*/ aStringBuffer) { var buffer = aStringBuffer, @@ -651,7 +749,7 @@ Preprocessor.prototype.preprocess = function(tokens, /*StringBuffer*/ aStringBuf closures = [0, 0, 0]; } - while ((token = tokens.next()) && ((token != terminator) || count)) + while ((token = tokens.next()) && ((token !== terminator) || count)) { if (tuple) { @@ -762,12 +860,12 @@ Preprocessor.prototype.preprocess = function(tokens, /*StringBuffer*/ aStringBuf } if (instigator) - { - if (token == instigator) + { + if (token === instigator) ++count; - - else if (token == terminator) - --count; + + else if (token === terminator) + --count; } // Safari can't handle function declarations of the form function [name]([arguments]) { } @@ -778,13 +876,16 @@ Preprocessor.prototype.preprocess = function(tokens, /*StringBuffer*/ aStringBuf var accumulator = ""; // Following the function identifier we can either have an open parenthesis or an identifier: - while((token = tokens.next()) && token != TOKEN_OPEN_PARENTHESIS && !(/^\w/).test(token)) + while((token = tokens.next()) && token !== TOKEN_OPEN_PARENTHESIS && !(/^\w/).test(token)) accumulator += token; - + // If the next token is an open parenthesis, we have a standard function and we don't have to // change it: if (token === TOKEN_OPEN_PARENTHESIS) { + if (instigator === TOKEN_OPEN_PARENTHESIS) + ++count; + CONCAT(buffer, "function" + accumulator + '('); if (tuple) diff --git a/Objective-J/Runtime.js b/Objective-J/Runtime.js index 90d8bf699..644c02358 100644 --- a/Objective-J/Runtime.js +++ b/Objective-J/Runtime.js @@ -534,6 +534,91 @@ function sel_registerName(aName) return aName; } +var fastEnumerationSelector = sel_getUid("countByEnumeratingWithState:objects:count:"); + +function objj_fastEnumerator(/*Object*/ anObject, /*Integer*/ anAssigneeCount) +{ + // If this object doesn't respond to countByEnumeratingWithState:objects:count: + // (which is obviously the case for non-Objective-J objects), then just iterate + // this one object. + if (anObject && (!anObject.isa || !class_getInstanceMethod(anObject.isa, fastEnumerationSelector))) + this._target = [anObject]; + + // Else, use it's implementation. + else + this._target = anObject; + + this._state = { state:0, assigneeCount:anAssigneeCount }; + this._index = 0; + + // Nothing to iterate in this case. + if (!anObject) + { + this.i = 0; + this.l = 0; + } + else + this.e(); +} + +objj_fastEnumerator.prototype.e = function() +{ + // Nothing to iterate, don't iterate + if (!this._target) + return NO; + + var state = this._state, + index = state.assigneeCount; + + // Clear out all the old state. + state.items = nil; + state.itemsPtr = nil; + + while (index--) + state["items" + index] = nil; + + this.o0 = []; + this.i = 0; + this.l = objj_msgSend(this._target, fastEnumerationSelector, state, this.o0, 10); + + // We're flexible on this. + this.o0 = state.items || state.itemsPtr || state.items0 || this.o0; + + // We allow the user to not explictly return anything in countByEnumeratingWithState:objects:count: + if (this.l === undefined) + this.l = this.o0.length; + + var assigneeCount = state.assigneeCount; + + index = assigneeCount - 1; + + // Handle all items from [1 .. assigneeCount - 1] + while (index-- > 1) + this["o" + index] = state["items" + index] || []; + + var lastAssigneeIndex = assigneeCount - 1; + + // Autogenerate the indexes if this was left blank. + if (lastAssigneeIndex > 0) + + if (state["items" + lastAssigneeIndex]) + this["o" + lastAssigneeIndex] = state["items" + lastAssigneeIndex]; + + else + { + var count = this.l, + indexIndex = 0, + indexes = new Array(count) + + for (; indexIndex < count; ++indexIndex, ++this._index) + indexes[indexIndex] = this._index; + + this["o" + lastAssigneeIndex] = indexes; + } + + return this.l > 0; +} + // Exports and Globals exports.objj_ivar = objj_ivar; @@ -586,5 +671,7 @@ exports.sel_getUid = sel_getUid; exports.sel_isEqual = sel_isEqual; exports.sel_registerName = sel_registerName; +exports.objj_fastEnumerator = objj_fastEnumerator; + exports.objj_generateObjectUID = generateObjectUID; exports._objj_generateObjectHash = generateObjectUID;