1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 /**
6  * Algorithms that modify its arguments.
7  *
8  * Copyright: Eugene Wissner 2017-2020.
9  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
10  *                  Mozilla Public License, v. 2.0).
11  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
12  * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/algorithm/mutation.d,
13  *                 tanya/algorithm/mutation.d)
14  */
15 module tanya.algorithm.mutation;
16 
17 static import tanya.memory.lifetime;
18 static import tanya.memory.op;
19 import tanya.meta.trait;
20 import tanya.meta.transform;
21 import tanya.range;
22 
23 /**
24  * Copies the $(D_PARAM source) range into the $(D_PARAM target) range.
25  *
26  * Params:
27  *  Source = Input range type.
28  *  Target = Output range type.
29  *  source = Source input range.
30  *  target = Target output range.
31  *
32  * Returns: $(D_PARAM target) range, whose front element is the one past the
33  *          last element copied.
34  *
35  * Precondition: $(D_PARAM target) should be large enough to accept all
36  *               $(D_PARAM source) elements.
37  */
38 Target copy(Source, Target)(Source source, Target target)
39 if (isInputRange!Source && isOutputRange!(Target, ElementType!Source))
40 in
41 {
42     static if (hasLength!Source && hasLength!Target)
43     {
44         assert(target.length >= source.length);
45     }
46 }
47 do
48 {
49     alias E = ElementType!Source;
50     static if (isDynamicArray!Source
51             && is(Unqual!E == ElementType!Target)
52             && !hasElaborateCopyConstructor!E
53             && !hasElaborateAssign!E
54             && !hasElaborateDestructor!E)
55     {
56         if (source.ptr < target.ptr
57          && (() @trusted => (target.ptr - source.ptr) < source.length)())
58         {
59             tanya.memory.op.copyBackward(source, target);
60         }
61         else if (source.ptr !is target.ptr)
62         {
63             tanya.memory.op.copy(source, target);
64         }
65         return target[source.length .. $];
66     }
67     else
68     {
69         for (; !source.empty; source.popFront())
70         {
71             put(target, source.front);
72         }
73         return target;
74     }
75 }
76 
77 ///
78 @nogc nothrow pure @safe unittest
79 {
80     import std.algorithm.comparison : equal;
81 
82     const int[2] source = [1, 2];
83     int[2] target = [3, 4];
84 
85     copy(source[], target[]);
86     assert(equal(source[], target[]));
87 }
88 
89 /**
90  * Fills $(D_PARAM range) with $(D_PARAM value).
91  *
92  * Params:
93  *  Range = Input range type.
94  *  Value = Filler type.
95  *  range = Input range.
96  *  value = Filler.
97  */
98 void fill(Range, Value)(Range range, auto ref Value value)
99 if (isInputRange!Range && isAssignable!(ElementType!Range, Value))
100 {
101     static if (!isDynamicArray!Range && is(typeof(range[] = value)))
102     {
103         range[] = value;
104     }
105     else
106     {
107         for (; !range.empty; range.popFront())
108         {
109             range.front = value;
110         }
111     }
112 }
113 
114 ///
115 @nogc nothrow pure @safe unittest
116 {
117     import std.algorithm.comparison : equal;
118 
119     int[6] actual;
120     const int[6] expected = [1, 1, 1, 1, 1, 1];
121 
122     fill(actual[], 1);
123     assert(equal(actual[], expected[]));
124 }
125 
126 /**
127  * Fills $(D_PARAM range) with $(D_PARAM value) assuming the elements of the
128  * $(D_PARAM range) aren't initialized.
129  *
130  * Params:
131  *  Range = Input range type.
132  *  Value = Initializer type.
133  *  range = Input range.
134  *  value = Initializer.
135  */
136 void uninitializedFill(Range, Value)(Range range, auto ref Value value)
137 if (isInputRange!Range && hasLvalueElements!Range
138  && isAssignable!(ElementType!Range, Value))
139 {
140     static if (hasElaborateDestructor!(ElementType!Range))
141     {
142         for (; !range.empty; range.popFront())
143         {
144             ElementType!Range* p = &range.front;
145             tanya.memory.lifetime.emplace!(ElementType!Range)(cast(void[]) (p[0 .. 1]), value);
146         }
147     }
148     else
149     {
150         fill(range, value);
151     }
152 }
153 
154 ///
155 @nogc nothrow pure @safe unittest
156 {
157     import std.algorithm.comparison : equal;
158 
159     int[6] actual = void;
160     const int[6] expected = [1, 1, 1, 1, 1, 1];
161 
162     uninitializedFill(actual[], 1);
163     assert(equal(actual[], expected[]));
164 }
165 
166 /**
167  * Initializes all elements of the $(D_PARAM range) assuming that they are
168  * uninitialized.
169  *
170  * Params:
171  *  Range = Input range type
172  *  range = Input range.
173  */
174 void initializeAll(Range)(Range range) @trusted
175 if (isInputRange!Range && hasLvalueElements!Range)
176 {
177     import tanya.memory.op : copy, fill;
178     alias T = ElementType!Range;
179 
180     static if (__VERSION__ >= 2083
181             && isDynamicArray!Range
182             && __traits(isZeroInit, T))
183     {
184         fill!0(range);
185     }
186     else
187     {
188         static immutable init = T.init;
189         for (; !range.empty; range.popFront())
190         {
191             copy((&init)[0 .. 1], (&range.front)[0 .. 1]);
192         }
193     }
194 }
195 
196 ///
197 @nogc nothrow pure @safe unittest
198 {
199     import std.algorithm.comparison : equal;
200 
201     int[2] actual = void;
202     const int[2] expected = [0, 0];
203 
204     initializeAll(actual[]);
205     assert(equal(actual[], expected[]));
206 }
207 
208 /**
209  * Destroys all elements in the $(D_PARAM range).
210  *
211  * This function has effect only if the element type of $(D_PARAM Range) has
212  * an elaborate destructor, i.e. it is a $(D_PSYMBOL struct) with an explicit
213  * or generated by the compiler destructor.
214  *
215  * Params:
216  *  Range = Input range type.
217  *  range = Input range.
218  */
219 void destroyAll(Range)(Range range)
220 if (isInputRange!Range && hasLvalueElements!Range)
221 {
222     tanya.memory.lifetime.destroyAllImpl!(Range, ElementType!Range)(range);
223 }
224 
225 ///
226 @nogc nothrow pure @trusted unittest
227 {
228     static struct WithDtor
229     {
230         private size_t* counter;
231         ~this() @nogc nothrow pure
232         {
233             if (this.counter !is null)
234             {
235                 ++(*this.counter);
236             }
237         }
238     }
239 
240     size_t counter;
241     WithDtor[2] withDtor = [WithDtor(&counter), WithDtor(&counter)];
242 
243     destroyAll(withDtor[]);
244 
245     assert(counter == 2);
246 }