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 (isDynamicArray!Range && __traits(isZeroInit, T))
181     {
182         fill!0(range);
183     }
184     else
185     {
186         static immutable init = T.init;
187         for (; !range.empty; range.popFront())
188         {
189             copy((&init)[0 .. 1], (&range.front)[0 .. 1]);
190         }
191     }
192 }
193 
194 ///
195 @nogc nothrow pure @safe unittest
196 {
197     import std.algorithm.comparison : equal;
198 
199     int[2] actual = void;
200     const int[2] expected = [0, 0];
201 
202     initializeAll(actual[]);
203     assert(equal(actual[], expected[]));
204 }
205 
206 /**
207  * Destroys all elements in the $(D_PARAM range).
208  *
209  * This function has effect only if the element type of $(D_PARAM Range) has
210  * an elaborate destructor, i.e. it is a $(D_PSYMBOL struct) with an explicit
211  * or generated by the compiler destructor.
212  *
213  * Params:
214  *  Range = Input range type.
215  *  range = Input range.
216  */
217 void destroyAll(Range)(Range range)
218 if (isInputRange!Range && hasLvalueElements!Range)
219 {
220     tanya.memory.lifetime.destroyAllImpl!(Range, ElementType!Range)(range);
221 }
222 
223 ///
224 @nogc nothrow pure @trusted unittest
225 {
226     static struct WithDtor
227     {
228         private size_t* counter;
229         ~this() @nogc nothrow pure
230         {
231             if (this.counter !is null)
232             {
233                 ++(*this.counter);
234             }
235         }
236     }
237 
238     size_t counter;
239     WithDtor[2] withDtor = [WithDtor(&counter), WithDtor(&counter)];
240 
241     destroyAll(withDtor[]);
242 
243     assert(counter == 2);
244 }