From f95c3ca46aad916464bf848a4293f8f16fa57413 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Mon, 15 Feb 2010 21:46:50 -0800 Subject: [PATCH 1/4] Adds fast enumeration to Cappuccino. Currently support for Arrays, dictionaries, single objects, and nil. Closes #472. Reviewed by me. --- Foundation/CPArray.j | 28 ++++++++ Foundation/CPDictionary.j | 25 +++++++ Objective-J/Preprocessor.js | 131 +++++++++++++++++++++++++++++++----- Objective-J/Runtime.js | 61 +++++++++++++++++ 4 files changed, 230 insertions(+), 15 deletions(-) diff --git a/Foundation/CPArray.j b/Foundation/CPArray.j index eeb8bad8b..de693148a 100755 --- a/Foundation/CPArray.j +++ b/Foundation/CPArray.j @@ -1206,6 +1206,34 @@ @end +@implementation CPArray (FastEnumeration) + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + var count = [self count]; + + if (aState.state >= count) + return 0; + + aState.items0 = self; + aState.state = count; + + if (aState.assigneeCount > 1) + { + var index = 0, + indexes = []; + + for (; index < count; ++index) + indexes[index] = index; + + aState.items1 = indexes; + } + + return count; +} + +@end + @implementation CPArray (CPCoding) - (id)initWithCoder:(CPCoder)aCoder diff --git a/Foundation/CPDictionary.j b/Foundation/CPDictionary.j index fc162b2c7..f4378fb0b 100755 --- a/Foundation/CPDictionary.j +++ b/Foundation/CPDictionary.j @@ -532,6 +532,31 @@ @end +@implementation CPDictionary (FastEnumeration) + +- (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/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..c87783b74 100644 --- a/Objective-J/Runtime.js +++ b/Objective-J/Runtime.js @@ -534,6 +534,65 @@ 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 }; + + // 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; + + this.items = nil; + this.itemsPtr = nil; + + while (index--) + state["items" + index] = nil; + + // This is safer, but possibly slower. + this.o0 = []; + this.i = 0; + this.l = objj_msgSend(this._target, fastEnumerationSelector, state, this.o0, 10); + + this.o0 = state.items || state.itemsPtr || state.items0 || this.o0; + + index = state.assigneeCount; + + while (index--) + this["o" + index] = state["items" + index] || []; + + if (this.l === undefined) + this.l = this.o0.length; + + return this.l > 0; +} + // Exports and Globals exports.objj_ivar = objj_ivar; @@ -586,5 +645,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; From 92d28e468e31fc7dff79f26c7cc4a7feef5031c1 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Mon, 15 Feb 2010 22:34:50 -0800 Subject: [PATCH 2/4] Autogenerate the index parameter for Fast Enumeration, and add Fast Enumeration support to CPIndexSet. Reviewed by me. --- Foundation/CPArray.j | 13 +------------ Foundation/CPIndexSet.j | 24 +++++++++++++++++++++++ Objective-J/Runtime.js | 42 +++++++++++++++++++++++++++++++++-------- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/Foundation/CPArray.j b/Foundation/CPArray.j index de693148a..e070cae94 100755 --- a/Foundation/CPArray.j +++ b/Foundation/CPArray.j @@ -1215,20 +1215,9 @@ if (aState.state >= count) return 0; - aState.items0 = self; + aState.items = self; aState.state = count; - if (aState.assigneeCount > 1) - { - var index = 0, - indexes = []; - - for (; index < count; ++index) - indexes[index] = index; - - aState.items1 = indexes; - } - return count; } 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/Objective-J/Runtime.js b/Objective-J/Runtime.js index c87783b74..644c02358 100644 --- a/Objective-J/Runtime.js +++ b/Objective-J/Runtime.js @@ -549,6 +549,7 @@ function objj_fastEnumerator(/*Object*/ anObject, /*Integer*/ anAssigneeCount) this._target = anObject; this._state = { state:0, assigneeCount:anAssigneeCount }; + this._index = 0; // Nothing to iterate in this case. if (!anObject) @@ -569,27 +570,52 @@ objj_fastEnumerator.prototype.e = function() var state = this._state, index = state.assigneeCount; - this.items = nil; - this.itemsPtr = nil; + // Clear out all the old state. + state.items = nil; + state.itemsPtr = nil; while (index--) state["items" + index] = nil; - // This is safer, but possibly slower. 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; - index = state.assigneeCount; - - while (index--) - this["o" + index] = state["items" + index] || []; - + // 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; } From db949728dd793d094c1260b96ed847dd15aecd6e Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Mon, 15 Feb 2010 22:44:52 -0800 Subject: [PATCH 3/4] Add CPFastEnumeration support to CPSet and CPNull. Reviewed by me. --- Foundation/CPArray.j | 2 +- Foundation/CPDictionary.j | 2 +- Foundation/CPNull.j | 9 +++++++++ Foundation/CPSet.j | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Foundation/CPArray.j b/Foundation/CPArray.j index e070cae94..32dca638c 100755 --- a/Foundation/CPArray.j +++ b/Foundation/CPArray.j @@ -1206,7 +1206,7 @@ @end -@implementation CPArray (FastEnumeration) +@implementation CPArray (CPFastEnumeration) - (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount { diff --git a/Foundation/CPDictionary.j b/Foundation/CPDictionary.j index f4378fb0b..105b2c711 100755 --- a/Foundation/CPDictionary.j +++ b/Foundation/CPDictionary.j @@ -532,7 +532,7 @@ @end -@implementation CPDictionary (FastEnumeration) +@implementation CPDictionary (CPFastEnumeration) - (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount { 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 From 89291990822d302ee7df6b0867948bc7fd0a9c05 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Mon, 15 Feb 2010 22:48:28 -0800 Subject: [PATCH 4/4] Add CPFastEnumeration support for CPEnumerator. Reviewed by me. --- Foundation/CPArray.j | 5 +++++ Foundation/CPEnumerator.j | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/Foundation/CPArray.j b/Foundation/CPArray.j index 32dca638c..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 */ 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