diff --git a/ListTalk/classes/Iterator.h b/ListTalk/classes/Iterator.h index a07ba65..f08914e 100644 --- a/ListTalk/classes/Iterator.h +++ b/ListTalk/classes/Iterator.h @@ -16,6 +16,7 @@ LT_DECLARE_CLASS(LT_Iterator); LT_DECLARE_CLASS(LT_EmptyIterator); LT_DECLARE_CLASS(LT_ListIterator); LT_DECLARE_CLASS(LT_MapIterator); +LT_DECLARE_CLASS(LT_FilterIterator); LT_Value LT_Iterator_this(LT_Value iterator); LT_Value LT_Iterator_hasThis(LT_Value iterator); @@ -24,6 +25,7 @@ LT_Value LT_Iterator_next(LT_Value iterator); LT_EmptyIterator* LT_EmptyIterator_instance(void); LT_ListIterator* LT_ListIterator_new(LT_Value list); LT_MapIterator* LT_MapIterator_new(LT_Value iterator, LT_Value callable); +LT_FilterIterator* LT_FilterIterator_new(LT_Value iterator, LT_Value callable); LT__END_DECLS diff --git a/ListTalk/classes/List.h b/ListTalk/classes/List.h index 4e0f7a5..5e18a4b 100644 --- a/ListTalk/classes/List.h +++ b/ListTalk/classes/List.h @@ -31,6 +31,7 @@ void LT_List_debugPrintOn(LT_Value value, FILE* stream); int LT_List_proper_p(LT_Value value); LT_Value LT_List_at(LT_Value list, size_t index); LT_Value LT_List_map(LT_Value callable, LT_Value list); +LT_Value LT_List_filter(LT_Value callable, LT_Value list); LT_Value LT_List_map_many( LT_Value callable, size_t list_count, diff --git a/src/classes/Iterator.c b/src/classes/Iterator.c index 98fc498..1873324 100644 --- a/src/classes/Iterator.c +++ b/src/classes/Iterator.c @@ -29,10 +29,38 @@ struct LT_MapIterator_s { LT_Value callable; }; +struct LT_FilterIterator_s { + LT_Object base; + LT_Value iterator; + LT_Value callable; +}; + static LT_EmptyIterator empty_iterator_instance = { .base = {.klass = <_EmptyIterator_class}, }; +static LT_Value iterator_apply1(LT_Value callable, LT_Value value){ + return LT_apply( + callable, + LT_cons(value, LT_NIL), + LT_NIL, + LT_NIL, + NULL + ); +} + +static LT_Value iterator_apply2(LT_Value callable, + LT_Value left, + LT_Value right){ + return LT_apply( + callable, + LT_cons(left, LT_cons(right, LT_NIL)), + LT_NIL, + LT_NIL, + NULL + ); +} + LT_DEFINE_SUBCLASS_RESPONSIBILITY_METHOD_0( iterator_method_this, "Iterator>>this", @@ -73,11 +101,188 @@ LT_DEFINE_PRIMITIVE( return LT_ListBuilder_value(builder); } +LT_DEFINE_PRIMITIVE( + iterator_method_as_iterator, + "Iterator>>asIterator", + "(self)", + "Return receiver." +){ + LT_Value cursor = arguments; + LT_Value self; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_ARG_END(cursor); + return self; +} + +LT_DEFINE_PRIMITIVE( + iterator_method_map, + "Iterator>>map:", + "(self callable)", + "Return a lazy iterator mapping callable over receiver." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value callable; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)LT_MapIterator_new(self, callable); +} + +LT_DEFINE_PRIMITIVE( + iterator_method_filter, + "Iterator>>filter:", + "(self callable)", + "Return a lazy iterator selecting values for which callable is truthy." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value callable; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)LT_FilterIterator_new(self, callable); +} + +LT_DEFINE_PRIMITIVE( + iterator_method_for_each, + "Iterator>>forEach:", + "(self callable)", + "Consume receiver, applying callable to each value, and return nil." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value callable; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + while (LT_Value_truthy_p(LT_Iterator_hasThis(self))){ + (void)iterator_apply1(callable, LT_Iterator_this(self)); + (void)LT_Iterator_next(self); + } + return LT_NIL; +} + +LT_DEFINE_PRIMITIVE( + iterator_method_any, + "Iterator>>any:", + "(self callable)", + "Consume receiver until callable returns truthy or it is exhausted." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value callable; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + while (LT_Value_truthy_p(LT_Iterator_hasThis(self))){ + LT_Value value = LT_Iterator_this(self); + (void)LT_Iterator_next(self); + if (LT_Value_truthy_p(iterator_apply1(callable, value))){ + return LT_TRUE; + } + } + return LT_FALSE; +} + +LT_DEFINE_PRIMITIVE( + iterator_method_every, + "Iterator>>every:", + "(self callable)", + "Consume receiver until callable returns falsey or it is exhausted." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value callable; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + while (LT_Value_truthy_p(LT_Iterator_hasThis(self))){ + LT_Value value = LT_Iterator_this(self); + (void)LT_Iterator_next(self); + if (!LT_Value_truthy_p(iterator_apply1(callable, value))){ + return LT_FALSE; + } + } + return LT_TRUE; +} + +LT_DEFINE_PRIMITIVE( + iterator_method_inject_into, + "Iterator>>inject:into:", + "(self initial callable)", + "Consume receiver, folding values from the left." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value accumulator; + LT_Value callable; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, accumulator); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + while (LT_Value_truthy_p(LT_Iterator_hasThis(self))){ + accumulator = iterator_apply2(callable, accumulator, LT_Iterator_this(self)); + (void)LT_Iterator_next(self); + } + return accumulator; +} + +LT_DEFINE_PRIMITIVE( + iterator_method_reduce, + "Iterator>>reduce:", + "(self callable)", + "Consume receiver, reducing values from the left." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value callable; + LT_Value accumulator; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + if (!LT_Value_truthy_p(LT_Iterator_hasThis(self))){ + LT_error("Iterator reduce: requires at least one value"); + } + accumulator = LT_Iterator_this(self); + (void)LT_Iterator_next(self); + while (LT_Value_truthy_p(LT_Iterator_hasThis(self))){ + accumulator = iterator_apply2(callable, accumulator, LT_Iterator_this(self)); + (void)LT_Iterator_next(self); + } + return accumulator; +} + static LT_Method_Descriptor Iterator_methods[] = { {"this", &iterator_method_this}, {"hasThis?", &iterator_method_has_this}, {"next!", &iterator_method_next}, {"asList", &iterator_method_as_list}, + {"asIterator", &iterator_method_as_iterator}, + {"map:", &iterator_method_map}, + {"filter:", &iterator_method_filter}, + {"forEach:", &iterator_method_for_each}, + {"do:", &iterator_method_for_each}, + {"any:", &iterator_method_any}, + {"every:", &iterator_method_every}, + {"inject:into:", &iterator_method_inject_into}, + {"reduce:", &iterator_method_reduce}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; @@ -378,6 +583,122 @@ LT_DEFINE_CLASS(LT_MapIterator) { .class_methods = MapIterator_class_methods, }; +static void filter_iterator_advance_to_match(LT_FilterIterator* iterator){ + while (LT_Value_truthy_p(LT_Iterator_hasThis(iterator->iterator))){ + if (LT_Value_truthy_p(iterator_apply1( + iterator->callable, + LT_Iterator_this(iterator->iterator) + ))){ + return; + } + (void)LT_Iterator_next(iterator->iterator); + } +} + +static void FilterIterator_debugPrintOn(LT_Value obj, FILE* stream){ + LT_FilterIterator* iterator = LT_FilterIterator_from_value(obj); + + fputs("#>filter:with:", + "(self iterator callable)", + "Return a lazy iterator filtering iterator with callable." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value iterator; + LT_Value callable; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, iterator); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + if (self != (LT_Value)(uintptr_t)<_FilterIterator_class){ + LT_error("filter:with: class method is only supported on FilterIterator"); + } + return (LT_Value)(uintptr_t)LT_FilterIterator_new(iterator, callable); +} + +LT_DEFINE_PRIMITIVE( + filter_iterator_method_this, + "FilterIterator>>this", + "(self)", + "Return the current matching value." +){ + LT_Value cursor = arguments; + LT_FilterIterator* iterator; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, iterator, LT_FilterIterator*, LT_FilterIterator_from_value); + LT_ARG_END(cursor); + return LT_Iterator_this(iterator->iterator); +} + +LT_DEFINE_PRIMITIVE( + filter_iterator_method_has_this, + "FilterIterator>>hasThis?", + "(self)", + "Return true when a matching value remains." +){ + LT_Value cursor = arguments; + LT_FilterIterator* iterator; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, iterator, LT_FilterIterator*, LT_FilterIterator_from_value); + LT_ARG_END(cursor); + return LT_Iterator_hasThis(iterator->iterator); +} + +LT_DEFINE_PRIMITIVE( + filter_iterator_method_next, + "FilterIterator>>next!", + "(self)", + "Advance to the next matching value and return receiver." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_FilterIterator* iterator; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_ARG_END(cursor); + iterator = LT_FilterIterator_from_value(self); + (void)LT_Iterator_next(iterator->iterator); + filter_iterator_advance_to_match(iterator); + return self; +} + +static LT_Method_Descriptor FilterIterator_methods[] = { + {"this", &filter_iterator_method_this}, + {"hasThis?", &filter_iterator_method_has_this}, + {"next!", &filter_iterator_method_next}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + +static LT_Method_Descriptor FilterIterator_class_methods[] = { + {"filter:with:", &filter_iterator_class_method_filter_with}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + +LT_DEFINE_CLASS(LT_FilterIterator) { + .superclass = <_Iterator_class, + .metaclass_superclass = <_Class_class, + .name = "FilterIterator", + .documentation = "Lazy iterator selecting values accepted by a callable.", + .instance_size = sizeof(LT_FilterIterator), + .debugPrintOn = FilterIterator_debugPrintOn, + .methods = FilterIterator_methods, + .class_methods = FilterIterator_class_methods, +}; + LT_Value LT_Iterator_this(LT_Value iterator){ return LT_SEND(iterator, "this"); } @@ -415,3 +736,12 @@ LT_MapIterator* LT_MapIterator_new(LT_Value iterator, LT_Value callable){ map_iterator->callable = callable; return map_iterator; } + +LT_FilterIterator* LT_FilterIterator_new(LT_Value iterator, LT_Value callable){ + LT_FilterIterator* result = LT_Class_ALLOC(LT_FilterIterator); + + result->iterator = iterator; + result->callable = callable; + filter_iterator_advance_to_match(result); + return result; +} diff --git a/src/classes/List.c b/src/classes/List.c index 4b46c66..953a978 100644 --- a/src/classes/List.c +++ b/src/classes/List.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -231,6 +232,30 @@ LT_Value LT_List_map(LT_Value callable, LT_Value list){ return LT_List_map_many(callable, 1, &list); } +LT_Value LT_List_filter(LT_Value callable, LT_Value list){ + LT_ListBuilder* builder = LT_ListBuilder_new(); + + while (LT_Pair_p(list)){ + LT_Value value = LT_car(list); + LT_Value result = LT_apply( + callable, + LT_cons(value, LT_NIL), + LT_NIL, + LT_NIL, + NULL + ); + + if (LT_Value_truthy_p(result)){ + LT_ListBuilder_append(builder, value); + } + list = LT_cdr(list); + } + if (list != LT_NIL){ + LT_error("filter expects proper list"); + } + return LT_ListBuilder_value(builder); +} + static LT_Value* list_cursors_new(size_t list_count, const LT_Value* lists){ LT_Value* cursors; size_t i; @@ -720,6 +745,23 @@ LT_DEFINE_PRIMITIVE( return LT_NIL; } +LT_DEFINE_PRIMITIVE( + list_method_filter, + "List>>filter:", + "(self callable)", + "Return elements for which callable returns truthy." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value callable; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, callable); + LT_ARG_END(cursor); + return LT_List_filter(callable, self); +} + LT_DEFINE_PRIMITIVE( list_method_any, "List>>any:", @@ -852,10 +894,36 @@ LT_DEFINE_PRIMITIVE( return (LT_Value)(uintptr_t)LT_ListIterator_new(self); } +LT_DEFINE_PRIMITIVE( + list_method_as_vector, + "List>>asVector", + "(self)", + "Return list elements as a vector." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Vector* vector; + size_t length; + size_t i; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_ARG_END(cursor); + + length = list_length(self); + vector = LT_Vector_new(length); + for (i = 0; i < length; i++){ + LT_Vector_atPut(vector, i, LT_car(self)); + self = LT_cdr(self); + } + return (LT_Value)(uintptr_t)vector; +} + static LT_Method_Descriptor List_methods[] = { {"length", &list_method_length}, {"at:", &list_method_at}, {"map:", &list_method_map}, + {"filter:", &list_method_filter}, {"forEach:", &list_method_for_each}, {"any:", &list_method_any}, {"every:", &list_method_every}, @@ -864,6 +932,7 @@ static LT_Method_Descriptor List_methods[] = { {"sort", &list_method_sort}, {"sortUsing:", &list_method_sort_using}, {"asList", &list_method_as_list}, + {"asVector", &list_method_as_vector}, {"asIterator", &list_method_as_iterator}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; diff --git a/src/classes/Set.c b/src/classes/Set.c index 28ec82c..351ced0 100644 --- a/src/classes/Set.c +++ b/src/classes/Set.c @@ -641,6 +641,39 @@ LT_DEFINE_PRIMITIVE( return LT_Set_remove(set_from_value(self), value) ? LT_TRUE : LT_FALSE; } +LT_DEFINE_PRIMITIVE( + set_method_size, + "Set>>size", + "(self)", + "Return set size." +){ + LT_Value cursor = arguments; + LT_Set* set; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, set, LT_Set*, set_from_value); + LT_ARG_END(cursor); + return LT_Number_smallinteger_from_size( + LT_Set_size(set), + "Set size does not fit fixnum" + ); +} + +LT_DEFINE_PRIMITIVE( + set_method_empty_p, + "Set>>empty?", + "(self)", + "Return true when set has no elements." +){ + LT_Value cursor = arguments; + LT_Set* set; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, set, LT_Set*, set_from_value); + LT_ARG_END(cursor); + return LT_Set_size(set) == 0 ? LT_TRUE : LT_FALSE; +} + LT_DEFINE_PRIMITIVE( set_method_as_list, "Set>>asList", @@ -814,6 +847,8 @@ LT_DEFINE_PRIMITIVE( } static LT_Method_Descriptor Set_methods[] = { + {"size", &set_method_size}, + {"empty?", &set_method_empty_p}, {"put:", &set_method_put}, {"contains?:", &set_method_contains}, {"remove:", &set_method_remove}, diff --git a/src/classes/Vector.c b/src/classes/Vector.c index ab23ec1..ee938a2 100644 --- a/src/classes/Vector.c +++ b/src/classes/Vector.c @@ -346,6 +346,21 @@ LT_DEFINE_PRIMITIVE( return LT_ListBuilder_value(builder); } +LT_DEFINE_PRIMITIVE( + vector_method_as_vector, + "Vector>>asVector", + "(self)", + "Return receiver." +){ + LT_Value cursor = arguments; + LT_Value self; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_ARG_END(cursor); + return self; +} + LT_DEFINE_PRIMITIVE( vector_method_as_iterator, "Vector>>asIterator", @@ -383,6 +398,7 @@ static LT_Method_Descriptor Vector_methods[] = { {"sort", &vector_method_sort}, {"sortUsing:", &vector_method_sort_using}, {"asList", &vector_method_as_list}, + {"asVector", &vector_method_as_vector}, {"asIterator", &vector_method_as_iterator}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; diff --git a/src/vm/base_env/base_env.c b/src/vm/base_env/base_env.c index 41d58b5..5471c61 100644 --- a/src/vm/base_env/base_env.c +++ b/src/vm/base_env/base_env.c @@ -129,6 +129,7 @@ static const struct LT_NativeClassBinding native_class_bindings[] = { {"Iterator", <_Iterator_class}, {"EmptyIterator", <_EmptyIterator_class}, {"MapIterator", <_MapIterator_class}, + {"FilterIterator", <_FilterIterator_class}, {"List", <_List_class}, {"ListIterator", <_ListIterator_class}, {"ImmutableList", <_ImmutableList_class}, diff --git a/tests/eval-collections.lt b/tests/eval-collections.lt index 129459e..02e8acf 100644 --- a/tests/eval-collections.lt +++ b/tests/eval-collections.lt @@ -25,6 +25,14 @@ (check (not (list? '(1 . 2))) "list? false for dotted pair") (check (equal? ['(1 2 3) map: (lambda (x) (+ x 2))] '(3 4 5)) "List>>map: maps callable across receiver") +(check (equal? ['(1 2 3 4) filter: (lambda (x) (> x 2))] '(3 4)) + "List>>filter: keeps matching elements in order") +(check (equal? ['(1 2 3) filter: (lambda (x) x)] '(1 2 3)) + "List>>filter: uses normal truthiness") +(check (null? ['() filter: (lambda (x) #true)]) + "List>>filter: handles an empty list") +(check-raises "List>>filter: rejects improper lists" + ['(1 . 2) filter: (lambda (x) #true)]) (check (let ((seen '())) (and (null? ['(1 2 3) forEach: (lambda (x) (set! seen (cons x seen)))]) (equal? seen '(3 2 1)))) @@ -48,6 +56,11 @@ (check (= ['(1 2 3) reduce: +] 6) "List>>reduce: reduces receiver from the left") (check (= ['(1 2 3) inject: 10 into: +] 16) "List>>inject:into: folds receiver from the left") +(check (equal? ['(1 2 3) asVector] #(1 2 3)) + "List>>asVector converts elements") +(check (= [['() asVector] length] 0) + "List>>asVector handles an empty list") +(check-raises "List>>asVector rejects improper lists" ['(1 . 2) asVector]) ) (define-test "Set" @@ -55,9 +68,13 @@ (check (eq? (type-of [Set new]) Set) "Set>>new returns set") (let ((s [Set new])) + (check (= [s size] 0) "Set>>size returns zero for an empty set") + (check [s empty?] "Set>>empty? returns true for an empty set") [s put: 1] [s put: 2] [s put: 1] + (check (= [s size] 2) "Set>>size counts unique elements") + (check (not [s empty?]) "Set>>empty? returns false for a non-empty set") (check [s contains?: 1] "Set>>contains?: finds inserted item") (check (not [s contains?: 3]) "Set>>contains?: rejects absent item") (check [s remove: 1] "Set>>remove: reports removed item") @@ -104,6 +121,7 @@ [s put: a] [s put: b] [s put: a] + (check (= [s size] 2) "IdentitySet inherits Set>>size") (check (eq? (type-of s) IdentitySet) "IdentitySet>>new returns identity set") (check [s contains?: a] "IdentitySet>>contains?: finds identical item") (check [s contains?: b] "IdentitySet>>contains?: preserves distinct equal item") @@ -287,6 +305,17 @@ with: (lambda (x) (+ x 10))] asList] '(11 12 13)) "MapIterator maps wrapped iterator lazily") +(let ((iterator [#(1 2 3) asIterator])) + (check (eq? [iterator asIterator] iterator) + "Iterator>>asIterator returns receiver")) +(check (equal? [[[#(1 2 3) asIterator] map: (lambda (x) (+ x 10))] asList] + '(11 12 13)) + "Iterator>>map: returns a lazy mapped iterator") +(let ((v (vector 1)) + (iterator #false)) + (set! iterator [[v asIterator] map: (lambda (x) (+ x 1))]) + [v at: 0 put: 41] + (check (= [iterator this] 42) "Iterator>>map: maps lazily")) (let ((v (vector 1))) (let ((iterator [MapIterator map: [v asIterator] with: (lambda (x) (+ x 1))])) [v at: 0 put: 41] @@ -296,6 +325,40 @@ with: (lambda (x) (set! calls (+ calls 1)) x)])) (check (null? [iterator asList]) "MapIterator handles empty wrapped iterator") (check (= calls 0) "MapIterator does not call function without current value")) +(check (equal? [[[#(1 2 3 4) asIterator] filter: (lambda (x) (> x 2))] asList] + '(3 4)) + "Iterator>>filter: lazily selects matching values") +(check (null? [[['() asIterator] filter: (lambda (x) #true)] asList]) + "Iterator>>filter: handles an empty iterator") +(let ((seen '()) + (iterator [#(1 2 3) asIterator])) + (check (null? [iterator forEach: (lambda (x) (set! seen (cons x seen)))]) + "Iterator>>forEach: returns nil") + (check (equal? seen '(3 2 1)) "Iterator>>forEach: visits every value") + (check (not [iterator hasThis?]) "Iterator>>forEach: consumes receiver")) +(let ((seen '())) + [['(1 2 3) asIterator] do: (lambda (x) (set! seen (cons x seen)))] + (check (equal? seen '(3 2 1)) "Iterator>>do: aliases forEach:")) +(let ((iterator [#(1 2 3 4) asIterator])) + (check [iterator any: (lambda (x) (= x 2))] + "Iterator>>any: finds a matching value") + (check (= [iterator this] 3) "Iterator>>any: short-circuits after the match")) +(check (not [['() asIterator] any: (lambda (x) #true)]) + "Iterator>>any: is false for an empty iterator") +(let ((iterator [#(1 2 3 4) asIterator])) + (check (not [iterator every: (lambda (x) (< x 3))]) + "Iterator>>every: detects a falsey result") + (check (= [iterator this] 4) "Iterator>>every: short-circuits after the miss")) +(check [['() asIterator] every: (lambda (x) #false)] + "Iterator>>every: is true for an empty iterator") +(check (= [[#(1 2 3) asIterator] inject: 10 into: +] 16) + "Iterator>>inject:into: folds from the left") +(check (= [['() asIterator] inject: 10 into: +] 10) + "Iterator>>inject:into: returns its initial value for an empty iterator") +(check (= [[#(10 1 2) asIterator] reduce: (lambda (a b) (- a b))] 7) + "Iterator>>reduce: reduces from the left") +(check-raises "Iterator>>reduce: rejects an empty iterator" + [['() asIterator] reduce: +]) ) (define-test "Strings" diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index 7bf871e..e7a71b3 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -922,6 +922,8 @@ (check (eq? [#(1 2 3) at: 1] 2) "Vector>>at: dispatches correctly") (check (equal? ['(1 2 3) asList] '(1 2 3)) "List>>asList returns receiver") (check (equal? [#(1 2 3) asList] '(1 2 3)) "Vector>>asList converts elements") +(let ((vector #(1 2 3))) + (check (eq? [vector asVector] vector) "Vector>>asVector returns receiver")) (let ((v #(1 2 3))) [v at: 1 put: 9] (check (eq? [v at: 1] 9) "Vector>>at:put: mutates element"))