diff --git a/AppKit/CPTreeNode.j b/AppKit/CPTreeNode.j index 8d679da20..8c59f85b5 100644 --- a/AppKit/CPTreeNode.j +++ b/AppKit/CPTreeNode.j @@ -87,6 +87,40 @@ [_childNodes[count] sortWithSortDescriptors:sortDescriptors recursively:YES]; } +- (CPEnumerator)preOrder +{ + return [_CPTreeNodeEnumerator enumeratorWithTreeNode:self order:_CPTreeNodeEnumeratorPreOrder]; +} + +- (CPEnumerator)inOrder +{ + return [_CPTreeNodeEnumerator enumeratorWithTreeNode:self order:_CPTreeNodeEnumeratorInOrder]; +} + +- (CPEnumerator)postOrder +{ + return [_CPTreeNodeEnumerator enumeratorWithTreeNode:self order:_CPTreeNodeEnumeratorPostOrder]; +} + +- (CPEnumerator)levelOrder +{ + return [_CPTreeNodeEnumerator enumeratorWithTreeNode:self order:_CPTreeNodeEnumeratorLevelOrder]; +} + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + if (aState.state !== 0) + return 0; + + // FIXME: Change this to inorder when implemented? + var count = [[self preOrder] countByEnumeratingWithState:aState objects:objects count:aCount]; + + // Dangerous? + aState.state = YES; + + return count; +} + @end var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey", @@ -117,3 +151,166 @@ var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey", } @end + +var _CPTreeNodeEnumeratorPreOrder = 0, + _CPTreeNodeEnumeratorInOrder = 1, + _CPTreeNodeEnumeratorPostOrder = 2, + _CPTreeNodeEnumeratorLevelOrder = 3; + +@implementation _CPTreeNodeEnumerator : CPEnumerator +{ + int _order; + Object _state; + CPTreeNode _treeNode; +} + ++ (_CPTreeNodeEnumerator)enumeratorWithTreeNode:(CPTreeNode)aTreeNode order:(int)anOrder +{ + return [[self alloc] initWithTreeNode:aTreeNode order:anOrder]; +} + +- (id)initWithTreeNode:(CPTreeNode)aTreeNode order:(int)anOrder +{ + self = [super init]; + + if (self) + { + _order = anOrder + _state = [self _newState]; + _treeNode = aTreeNode; + } + + return self; +} + +- (id)_newState +{ + return { stack:[_treeNode], visited:[CPSet set] }; +} + +- (id)_preOrderFromState:(Object)state allObjects:(BOOL)shouldReturnAllObjects +{ + var stack = state.stack; + + if (stack.length <= 0) + return nil; + + if (shouldReturnAllObjects) + var allObjects = []; + + while (stack.length) + { + var currentNode = stack.pop(), + childNodes = currentNode._childNodes, + count = childNodes.length; + + while (count--) + stack.push(childNodes[count]); + + if (shouldReturnAllObjects) + allObjects.push(currentNode); + else + return currentNode; + } + + return allObjects; +} + +- (id)_inOrderFromState:(Object)state allObjects:(BOOL)shouldReturnAllObjects +{ + // Implement. +} + +- (id)_postOrderFromState:(Object)state allObjects:(BOOL)shouldReturnAllObjects +{ + var stack = state.stack; + + if (stack.length <= 0) + return nil; + + var visited = state.visited; + + if (shouldReturnAllObjects) + var allObjects = []; + + while (stack.length) + { + var currentNode = [stack lastObject], + childNodes = currentNode._childNodes, + originalCount = stack.length, + count = childNodes.length; + + while (count--) + { + var treeNode = childNodes[count]; + + if ([visited containsObject:treeNode]) + continue; + else + stack.push(treeNode); + } + + if (originalCount === stack.length) + { + [visited addObject:currentNode]; + + if (shouldReturnAllObjects) + allObjects.push(stack.pop()); + else + return stack.pop(); + } + } + + return allObjects; +} + +- (id)_levelOrderFromState:(Object)state allObjects:(BOOL)shouldReturnAllObjects +{ + // Implement. +} + +- (id)nextObject +{ + if (_order === _CPTreeNodeEnumeratorPreOrder) + return [self _preOrderFromState:[self _newState] allObjects:NO]; + + else if (_order === _CPTreeNodeEnumeratorInOrder) + return [self _inOrderFromState:[self _newState] allObjects:NO]; + + else if (_order === _CPTreeNodeEnumeratorPostOrder) + return [self _postOrderFromState:[self _newState] allObjects:NO]; + + //else if (_order === _CPTreeNodeEnumeratorLevelOrder) + return [self _levelOrderFromState:[self _newState] allObjects:NO]; + +} + +- (CPArray)allObjects +{ + if (_order === _CPTreeNodeEnumeratorPreOrder) + return [self _preOrderFromState:[self _newState] allObjects:YES]; + + else if (_order === _CPTreeNodeEnumeratorInOrder) + return [self _inOrderFromState:[self _newState] allObjects:YES]; + + else if (_order === _CPTreeNodeEnumeratorPostOrder) + return [self _postOrderFromState:[self _newState] allObjects:YES]; + + //else if (_order === _CPTreeNodeEnumeratorLevelOrder) + return [self _levelOrderFromState:[self _newState] allObjects:NO]; +} + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + if (aState.state !== 0) + return 0; + + var count = [[self allObjects] countByEnumeratingWithState:aState objects:objects count:aCount]; + + // Dangerous? + aState.state = YES; + + return count; +} + +@end diff --git a/Foundation/CPArray.j b/Foundation/CPArray.j index eeb8bad8b..ec071fe46 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,31 @@ @end +@implementation CPArray (CPFastEnumeration) + +- (int)countByEnumeratingWithState:(id)aState objects:(id)objects count:(id)aCount +{ + var count = [self count], + index = aState.state; + + if (index >= count) + return 0; + + var indexIndex = 0, + last = MIN(index + aCount, count); + + aCount = last - index; + + for (; index < last; ++index, ++indexIndex) + objects[indexIndex] = [self objectAtIndex:index]; + + aState.state = last; + + return aCount; +} + +@end + @implementation CPArray (CPCoding) - (id)initWithCoder:(CPCoder)aCoder diff --git a/Foundation/CPDictionary.j b/Foundation/CPDictionary.j index e6f83f56a..d4646bf43 100755 --- a/Foundation/CPDictionary.j +++ b/Foundation/CPDictionary.j @@ -546,6 +546,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 = new Array(count); + + while (index--) + 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..03e3a2d82 100644 --- a/Foundation/CPIndexSet.j +++ b/Foundation/CPIndexSet.j @@ -834,6 +834,32 @@ 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 = start + range.length, + indexes = new Array(range.length); + + while (start < end) + indexes[index++] = start++; + + aState.items = indexes; + ++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 fec6b2758..edc0924f5 100644 --- a/Objective-J/Preprocessor.js +++ b/Objective-J/Preprocessor.js @@ -321,6 +321,9 @@ Preprocessor.prototype.directive = function(tokens, aStringBuffer, allowedDirect else if (token === TOKEN_IMPORT) this._import(tokens); + else if (token === TOKEN_EACH) + this.each(tokens, buffer); + // @selector else if (token === TOKEN_SELECTOR) this.selector(tokens, buffer); @@ -329,6 +332,99 @@ Preprocessor.prototype.directive = function(tokens, aStringBuffer, allowedDirect 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"); + + // var $E = new objj_fastEnumerator(expression); + // for ([[var] arg1[, arg2[, ... argN]]]; + // $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, diff --git a/Objective-J/Runtime.js b/Objective-J/Runtime.js index f3ed81b90..3dd51bc20 100644 --- a/Objective-J/Runtime.js +++ b/Objective-J/Runtime.js @@ -530,3 +530,106 @@ GLOBAL(sel_registerName) = function(/*String*/ aName) { return aName; } + +var fastEnumerationSelector = sel_getUid("countByEnumeratingWithState:objects:count:"); + +GLOBAL(objj_fastEnumerator) = function(/*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() +{ + var object = this._target; + + // Nothing to iterate, don't iterate + if (!object) + return NO; + + var state = this._state, + index = state.assigneeCount; + + while (index--) + state["items" + index] = nil; + + this.i = 0; + + // We optimize the array case. + if (CPArray && object.isa === CPArray) + { + if (this.l) + return NO; + + this.o0 = object; + this.l = object.length; + } + + else + { + // Clear out all the old state. + state.items = nil; + state.itemsPtr = nil; + + this.o0 = []; + this.l = objj_msgSend(object, fastEnumerationSelector, state, this.o0, 16); + + // 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; + } + + // If this is the last iteration, set target to nil so that we don't call the + // fast enumeration method again. + return this.l > 0; +}