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  * Range adapters transform some data structures into ranges.
7  *
8  * Copyright: Eugene Wissner 2018-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/range/adapter.d,
13  *                 tanya/range/adapter.d)
14  */
15 module tanya.range.adapter;
16 
17 import tanya.algorithm.mutation;
18 import tanya.memory.lifetime;
19 import tanya.meta.trait;
20 import tanya.range;
21 
22 private mixin template InserterCtor()
23 {
24     private Container* container;
25 
26     private this(return scope ref Container container) @trusted
27     {
28         this.container = &container;
29     }
30 }
31 
32 /**
33  * If $(D_PARAM container) is a container with `insertBack`-support,
34  * $(D_PSYMBOL backInserter) returns an output range that puts the elements
35  * into the container with `insertBack`.
36  *
37  * The resulting output range supports all types `insertBack` supports.
38  *
39  * The range keeps a reference to the container passed to it, it doesn't use
40  * any other storage. So there is no method to get the written data out of the
41  * range - the container passed to $(D_PSYMBOL backInserter) contains that data
42  * and can be used directly after all operations on the output range are
43  * completed. It also means that the result range is not allowed to outlive its
44  * container.
45  *
46  * Params:
47  *  Container = Container type.
48  *  container = Container used as an output range.
49  *
50  * Returns: `insertBack`-based output range.
51  */
52 auto backInserter(Container)(return scope ref Container container)
53 if (hasMember!(Container, "insertBack"))
54 {
55     static struct Inserter
56     {
57         void opCall(T)(auto ref T data)
58         {
59             this.container.insertBack(forward!data);
60         }
61 
62         mixin InserterCtor;
63     }
64     return Inserter(container);
65 }
66 
67 ///
68 @nogc nothrow pure @safe unittest
69 {
70     static struct Container
71     {
72         int element;
73 
74         void insertBack(int element)
75         {
76             this.element = element;
77         }
78     }
79     Container container;
80     backInserter(container)(5);
81 
82     assert(container.element == 5);
83 }
84 
85 /**
86  * If $(D_PARAM container) is a container with `insertFront`-support,
87  * $(D_PSYMBOL frontInserter) returns an output range that puts the elements
88  * into the container with `insertFront`.
89  *
90  * The resulting output range supports all types `insertFront` supports.
91  *
92  * The range keeps a reference to the container passed to it, it doesn't use
93  * any other storage. So there is no method to get the written data out of the
94  * range - the container passed to $(D_PSYMBOL frontInserter) contains that data
95  * and can be used directly after all operations on the output range are
96  * completed. It also means that the result range is not allowed to outlive its
97  * container.
98  *
99  * Params:
100  *  Container = Container type.
101  *  container = Container used as an output range.
102  *
103  * Returns: `insertFront`-based output range.
104  */
105 auto frontInserter(Container)(return scope ref Container container)
106 if (hasMember!(Container, "insertFront"))
107 {
108     static struct Inserter
109     {
110         void opCall(T)(auto ref T data)
111         {
112             this.container.insertFront(forward!data);
113         }
114 
115         mixin InserterCtor;
116     }
117     return Inserter(container);
118 }
119 
120 ///
121 @nogc nothrow pure @safe unittest
122 {
123     static struct Container
124     {
125         int element;
126 
127         void insertFront(int element)
128         {
129             this.element = element;
130         }
131     }
132     Container container;
133     frontInserter(container)(5);
134 
135     assert(container.element == 5);
136 }
137 
138 /**
139  * $(D_PSYMBOL arrayInserter) makes an output range out of an array.
140  *
141  * The returned output range accepts single values as well as input ranges that
142  * can be copied into the target array.
143  *
144  * Params:
145  *  Array = Array type.
146  *  array = Array.
147  *
148  * Returns: An output range writing into $(D_PARAM array).
149  */
150 auto arrayInserter(Array)(return scope ref Array array)
151 if (isArray!Array)
152 {
153     static if (is(Array ArrayT : ArrayT[size], size_t size))
154     {
155         alias E = ArrayT;
156     }
157     else
158     {
159         alias E = ElementType!Array;
160     }
161 
162     static struct ArrayInserter
163     {
164         private E[] data;
165 
166         private this(return scope ref Array data) @trusted
167         {
168             this.data = data[];
169         }
170 
171         void opCall(T)(auto ref T data)
172         if (is(T : E))
173         in
174         {
175             assert(!this.data.empty);
176         }
177         do
178         {
179             put(this.data, data);
180         }
181 
182         void opCall(R)(auto ref R data)
183         if (isInputRange!R && isOutputRange!(E[], ElementType!R))
184         {
185             this.data = copy(data, this.data);
186         }
187     }
188     return ArrayInserter(array);
189 }
190 
191 ///
192 @nogc nothrow pure @safe unittest
193 {
194     int[1] array;
195 
196     arrayInserter(array)(5);
197     assert(array[0] == 5);
198 }
199 
200 ///
201 @nogc nothrow pure @safe unittest
202 {
203     char[1] array;
204     alias Actual = typeof(arrayInserter(array));
205 
206     static assert(isOutputRange!(Actual, char));
207     static assert(isOutputRange!(Actual, char[]));
208 }