diff --git a/core/Indexable.savi b/core/Indexable.savi index 1c8b8c9b..721321c2 100644 --- a/core/Indexable.savi +++ b/core/Indexable.savi @@ -1,4 +1,6 @@ +:: An collection of elements of type A that can be accessed via numeric index. :trait box Indexable(A) + :fun size USize :fun "[]!"(index USize) (@->A)'aliased //-- @@ -9,6 +11,16 @@ stride USize = 1 ) None :yields ((@->A)'aliased, USize) + index = from + to = to.at_most(@size) + stride = stride.at_least(1) + while index < to ( + try ( + value = @[index]! + yield (--value, index) + ) + index = try (index +! stride | return) + ) :fun each( from USize = 0 @@ -95,6 +107,15 @@ stride USize = 1 ) None :yields ((@->A)'aliased, USize) + index = from.at_most(try (@size -! 1 | return)) + stride = stride.at_least(1) + while index >= to ( + try ( + value = @[index]! + yield (--value, index) + ) + index = try (index -! stride | return) + ) :fun reverse_each( from = USize.max_value @@ -129,6 +150,18 @@ //-- + :fun first! @->(A'aliased) + @[0]! + + :fun last! @->(A'aliased) + @reverse_each_with_index -> (value, index | + return value + None // TODO: this should not be needed + ) + error! + + //-- + :fun select( from USize = 0 to = USize.max_value diff --git a/spec/core/Indexable.Spec.savi b/spec/core/Indexable.Spec.savi index 295ad815..f1e2418a 100644 --- a/spec/core/Indexable.Spec.savi +++ b/spec/core/Indexable.Spec.savi @@ -1,121 +1,122 @@ +// A little custom class to showcase the minimum trait implementation. +// All other Indexable methods will be based on this minimal implementation. +:class _ASCIILettersExample + :is Indexable(String) + + :fun size USize: 26 + :fun "[]!"(index USize) + error! if index >= @size + "\((index + 'a').format.printable_ascii)" + +:class _ArrayWrapperExample(T val) // TODO: val constraint should not be required + :is Indexable(T) + :let array Array(T) + :new (@array) + + :fun size: @array.size + :fun "[]!"(index USize): @array[index]! + :class Savi.Indexable.Spec :is Spec :const describes: "Indexable" :it "yields each element" array Array(String) = [] - ["foo", "bar", "baz"].each -> (string | array << string) - assert: array == ["foo", "bar", "baz"] + _ASCIILettersExample.new.each -> (string | array << string) + assert: array == [ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m" + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" + ] :it "yields each element of a subslice" array Array(String) = [] - ["a", "b", "c", "d", "e", "f"].each(1, 5) -> (string | array << string) + _ASCIILettersExample.new.each(1, 5) -> (string | array << string) assert: array == ["b", "c", "d", "e"] :it "yields each element along with the index" array_a Array(String) = [] array_b Array(USize) = [] - ["foo", "bar", "baz"].each_with_index -> (string, index | + _ASCIILettersExample.new.each_with_index -> (string, index | array_a << string array_b << index ) - assert: array_a == ["foo", "bar", "baz"] - assert: array_b == [0, 1, 2] + assert: array_a == [ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m" + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" + ] + assert: array_b == [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 + ] :it "yields each element, in reverse" array Array(String) = [] - ["foo", "bar", "baz"].reverse_each -> (string | array << string) - assert: array == ["baz", "bar", "foo"] + _ASCIILettersExample.new.reverse_each -> (string | array << string) + assert: array == [ + "z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p", "o", "n" + "m", "l", "k", "j", "i", "h", "g", "f", "e", "d", "c", "b", "a" + ] :it "yields each element, in reverse, along with the index" array_a Array(String) = [] array_b Array(USize) = [] - ["foo", "bar", "baz"].reverse_each_with_index -> (string, index | + _ASCIILettersExample.new.reverse_each_with_index -> (string, index | array_a << string array_b << index ) - assert: array_b == [2, 1, 0] - assert: - array_a == ["baz", "bar", "foo"] - - :it "yields each element, stopping early if the criteria is met" - array Array(String) = [] - early_stop = ["foo", "bar", "baz"].each_until -> (string | - array << string - string == "bar" - ) - assert: early_stop - assert: array == ["foo", "bar"] - - array.clear - early_stop = ["foo", "bar", "baz"].each_until -> (string | - array << string - string == "bard" - ) - assert: early_stop.is_false - assert: array == ["foo", "bar", "baz"] - - :it "yields each element of a subslice, stopping early if the criteria is met" - array Array(String) = [] - early_stop = ["a", "b", "c", "d", "e", "f"].each_until(1, 5) -> (string | - array << string - string == "d" - ) - assert: early_stop - assert: array == ["b", "c", "d"] - - array.clear - early_stop = ["a", "b", "c", "d", "e", "f"].each_until(1, 5) -> (string | - array << string - string == "z" - ) - assert: early_stop.is_false - assert: array == ["b", "c", "d", "e"] + assert: array_a == [ + "z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p", "o", "n" + "m", "l", "k", "j", "i", "h", "g", "f", "e", "d", "c", "b", "a" + ] + assert: array_b == [ + 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, + 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + ] :it "returns True if any element meets the criteria" - array Array(U8) = [11, 22, 33, 44, 36, 27, 18] + array = _ArrayWrapperExample(U8).new([11, 22, 33, 44, 36, 27, 18]) assert: array.has_any -> (num | num > 30) assert: array.has_any -> (num | num > 50).is_false :it "returns True if all elements meet the criteria" - array Array(U8) = [11, 22, 33, 44, 36, 27, 18] + array = _ArrayWrapperExample(U8).new([11, 22, 33, 44, 36, 27, 18]) assert: array.has_all -> (num | num > 10) assert: array.has_all -> (num | num > 30).is_false :it "finds the first element that meets the criteria" - array Array(U8) = [11, 22, 33, 44, 36, 27, 18] + array = _ArrayWrapperExample(U8).new([11, 22, 33, 44, 36, 27, 18]) assert: array.find! -> (num | num > 30) == 33 assert error: array.find! -> (num | num > 50) :it "finds the first index that meets the criteria" - array Array(U8) = [11, 22, 33, 44, 36, 27, 18] + array = _ArrayWrapperExample(U8).new([11, 22, 33, 44, 36, 27, 18]) assert: array.find_index! -> (num | num > 30) == 2 assert error: array.find_index! -> (num | num > 50) :it "finds, starting from the end, the first element that meets the criteria" - array Array(U8) = [11, 22, 33, 44, 36, 27, 18] + array = _ArrayWrapperExample(U8).new([11, 22, 33, 44, 36, 27, 18]) assert: array.reverse_find! -> (num | num > 30) == 36 assert error: array.reverse_find! -> (num | num > 50) :it "finds, starting from the end, the first index that meets the criteria" - array Array(U8) = [11, 22, 33, 44, 36, 27, 18] + array = _ArrayWrapperExample(U8).new([11, 22, 33, 44, 36, 27, 18]) assert: array.reverse_find_index! -> (num | num > 30) == 4 assert error: array.reverse_find_index! -> (num | num > 50) :it "selects those elements that meet the criteria" - array Array(U8) = [11, 22, 33, 44, 36, 27, 18] + array = _ArrayWrapperExample(U8).new([11, 22, 33, 44, 36, 27, 18]) selected = array.select -> (num | num < 30) assert: selected == [11, 22, 27, 18] :it "rejects those elements that do not meet the criteria" - array Array(U8) = [1, 2, 3, 4, 5] + array = _ArrayWrapperExample(U8).new([1, 2, 3, 4, 5]) odds = array.reject -> (num | num % 2 == 0) assert: odds == [1, 3, 5] :it "rejects nothing from an empty array" - array Array(U8) = [] - assert: array.reject -> (num | num % 2 == 0) == array + array = _ArrayWrapperExample(U8).new([]) + assert: array.reject -> (num | num % 2 == 0) == [] :it "rejects nothing if criteria is always false" - array Array(U8) = [1, 2, 3] - assert: array.reject -> (num | False) == array + array = _ArrayWrapperExample(U8).new([1, 2, 3]) + assert: array.reject -> (num | False) == [1, 2, 3] diff --git a/src/savi/compiler/populate.cr b/src/savi/compiler/populate.cr index ffbb1fb2..57fb6ac0 100644 --- a/src/savi/compiler/populate.cr +++ b/src/savi/compiler/populate.cr @@ -205,11 +205,17 @@ class Savi::Compiler::Populate visitor = self params = f.params.try(&.accept(ctx, visitor)) ret = f.ret.try(&.accept(ctx, visitor)) + error_out = f.error_out.try(&.accept(ctx, visitor)) + yield_out = f.yield_out.try(&.accept(ctx, visitor)) + yield_in = f.yield_in.try(&.accept(ctx, visitor)) body = f.body.try(&.accept(ctx, visitor)) f = f.dup f.params = params f.ret = ret + f.error_out = error_out + f.yield_out = yield_out + f.yield_in = yield_in f.body = body f }