1 /**
2 struct-params-dlang - https://github.com/vporton/struct-params-dlang
3 
4 This file is part of struct-params-dlang.
5 
6 Licensed to the Apache Software Foundation (ASF) under one
7 or more contributor license agreements.  See the NOTICE file
8 distributed with this work for additional information
9 regarding copyright ownership.  The ASF licenses this file
10 to you under the Apache License, Version 2.0 (the
11 "License"); you may not use this file except in compliance
12 with the License.  You may obtain a copy of the License at
13 
14   http://www.apache.org/licenses/LICENSE-2.0
15 
16 Unless required by applicable law or agreed to in writing,
17 software distributed under the License is distributed on an
18 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 KIND, either express or implied.  See the License for the
20 specific language governing permissions and limitations
21 under the License.
22 */
23 
24 /**
25 Defines means to pass structs with default values that may be unspecified.
26 */
27 module struct_params;
28 
29 import std.meta;
30 import std.traits;
31 import std.typecons;
32 import std.range;
33 
34 private template FieldInfo(argT, string argName) {
35     alias T = argT;
36     alias name = argName;
37 }
38 
39 private alias processFields() = AliasSeq!();
40 
41 private alias processFields(T, string name, Fields...) =
42     AliasSeq!(FieldInfo!(T, name), processFields!(Fields));
43 
44 // Needs to be public not to break visibility rules in StructParams template mixin.
45 public string structParamsImplementation(string name, Fields...)() {
46     enum regularField(alias f) = fullyQualifiedName!(f.T) ~ ' ' ~ f.name ~ ';';
47     enum fieldWithDefault(alias f) = "Nullable!(" ~ fullyQualifiedName!(f.T) ~ ") " ~ f.name ~ ';';
48     enum funcField(alias f) = "Nullable!(" ~ fullyQualifiedName!(f.T) ~ " delegate()) " ~ f.name ~ ';';
49     alias fields = processFields!(Fields);
50     immutable string regularFields = cast(immutable string) [staticMap!(regularField, fields)].join('\n');
51     immutable string fieldsWithDefaults = cast(immutable string) [staticMap!(fieldWithDefault, fields)].join('\n');
52     immutable string funcFields = cast(immutable string) [staticMap!(funcField, fields)].join('\n');
53     return "struct " ~ name ~ " {\n" ~
54            "  struct Regular {\n" ~
55            "    " ~ regularFields ~ '\n' ~
56            "  }\n" ~
57            "  struct WithDefaults {\n" ~
58            "    " ~ fieldsWithDefaults ~ '\n' ~
59            "  }\n" ~
60            "  struct Func {\n" ~
61            "    " ~ funcFields ~ '\n' ~
62            "  }\n" ~
63            '}';
64 }
65 
66 /**
67 Example: `mixin StructParams!("S", int, "x", float, "y");` creates
68 ```
69 struct S {
70   struct Regular {
71     int x;
72     float y;
73   }
74   struct WithDefaults {
75     Nullable!int x;
76     Nullable!float y;
77   }
78   struct Func {
79     Nullable!(int delegate()) x;
80     Nullable!(float delegate()) y;
81   }
82 }
83 ```
84 These structures are intended to be used as arguments of `combine` function.
85 */
86 mixin template StructParams(string name, Fields...) {
87     import std.typecons : Nullable;
88     mixin(structParamsImplementation!(name, Fields)());
89 }
90 
91 /**
92 Creates a "combined" structure from `main` and `default_`. The combined structure contains member
93 values from `main` whenever `!isNull` for this value and otherwise values from `default_`.
94 
95 Example:
96 ```
97 mixin StructParams!("S", int, "x", float, "y");
98 immutable S.WithDefaults combinedMain = { x: 12 }; // y is default-initialized
99 immutable S.Regular combinedDefault = { x: 11, y: 3.0 };
100 immutable combined = combine(combinedMain, combinedDefault);
101 assert(combined.x == 12 && combined.y == 3.0);
102 ```
103 
104 Note that we cannot use struct literals like `S.Regular(x: 11, y: 3.0)` in the current version
105 (v2.084.1) of D, just because current version of D does not have this feature. See DIP71.
106 */
107 deprecated("Use the variant with both arguments *.WithDefaults")
108 S.Regular combine(S)(S.WithDefaults main, S.Regular default_) {
109     S.Regular result;
110     static foreach (m; FieldNameTuple!(S.Regular)) {
111         __traits(getMember, result, m) =
112             __traits(getMember, main, m).isNull ? __traits(getMember, default_, m)
113                                                 : __traits(getMember, main, m);
114     }
115     return result;
116 }
117 
118 /**
119 Creates a "combined" structure from `main` and `default_`. The combined structure contains member
120 values from `main` whenever `!isNull` for this value and otherwise values from `default_`.
121 Assertion error if both a member of `main` and of `default_` are null.
122 
123 Example:
124 ```
125 mixin StructParams!("S", int, "x", float, "y");
126 immutable S.WithDefaults combinedMain = { x: 12 }; // y is default-initialized
127 immutable S.WithDefaults combinedDefault = { x: 11, y: 3.0 };
128 immutable combined = combine(combinedMain, combinedDefault);
129 assert(combined.x == 12 && combined.y == 3.0);
130 ```
131 
132 Note that we cannot use struct literals like `S.Regular(x: 11, y: 3.0)` in the current version
133 (v2.084.1) of D, just because current version of D does not have this feature. See DIP71.
134 */
135 S.Regular combine(S)(S.WithDefaults main, S.WithDefaults default_) {
136     S.Regular result;
137     static foreach (m; FieldNameTuple!(S.Regular)) {
138         assert(!__traits(getMember, main, m).isNull || !__traits(getMember, default_, m).isNull);
139         __traits(getMember, result, m) =
140         __traits(getMember, main, m).isNull ? __traits(getMember, default_, m).get
141         : __traits(getMember, main, m).get;
142     }
143     return result;
144 }
145 
146 /**
147 Creates a "combined" structure from `main` and `default_`. The combined structure contains member
148 values from `main` whenever `!isNull` for this value and otherwise calculated values from `default_`.
149 Assertion error if both a member of `main` and of `default_` are null.
150 
151 Example:
152 ```
153 mixin StructParams!("S", int, "x", float, "y");
154 immutable S.WithDefaults combinedMain = { x: 12 }; // y is default-initialized
155 immutable S.Func combinedDefault = { x: ()=>11, y: ()=>3.0 };
156 immutable combined = combine(combinedMain, combinedDefault);
157 assert(combined.x == 12 && combined.y == 3.0);
158 ```
159 
160 Note that we cannot use struct literals like `S.Regular(x: 11, y: 3.0)` in the current version
161 (v2.084.1) of D, just because current version of D does not have this feature. See DIP71.
162 */
163 S.Regular combine(S)(S.WithDefaults main, S.Func default_) {
164     S.Regular result;
165     static foreach (m; FieldNameTuple!(S.Regular)) {
166         assert(!__traits(getMember, main, m).isNull || !__traits(getMember, default_, m).isNull);
167         __traits(getMember, result, m) =
168         __traits(getMember, main, m).isNull ? __traits(getMember, default_, m).get()()
169         : __traits(getMember, main, m).get;
170     }
171     return result;
172 }
173 
174 /**
175 Example:
176 
177 Consider function
178 ```
179 float f(int a, float b) {
180     return a + b;
181 }
182 ```
183 
184 Then we can call it like `callFunctionWithParamsStruct!f(combined)` where `combined` in
185 this example may be created by `combine` function. (`callFunctionWithParamsStruct` is intented
186 mainly to be used together with `combine` function.)
187 
188 The members from `combined` are passed to the function `f` in the same order as they are defined
189 in the struct.
190 */
191 ReturnType!f callFunctionWithParamsStruct(alias f, S)(S s) {
192     return f(s.tupleof);
193 }
194 
195 /**
196 Example:
197 
198 Consider:
199 ```
200 struct Test {
201     float f(int a, float b) {
202         return a + b;
203     }
204 }
205 Test t;
206 ```
207 
208 Then it can be called like `callMemberFunctionWithParamsStruct!(t, "f")(combined)`
209 (see `callFunctionWithParamsStruct` for the meaning of this).
210 
211 It is very unnatural to call member f by string name, but I have not found a better solution.
212 
213 Another variant would be to use
214 `callFunctionWithParamsStruct!((int a, float b) => t.f(a, b))(combined)`, but this way is
215 inconvenient as it requires specifying arguments explicitly.
216 */
217 ReturnType!(__traits(getMember, o, f))
218 callMemberFunctionWithParamsStruct(alias o, string f, S)(S s) {
219     return __traits(getMember, o, f)(s.tupleof);
220 }
221 
222 unittest {
223     mixin StructParams!("S", int, "x", float, "y");
224     immutable S.WithDefaults combinedMain = { x: 12 };
225     immutable S.Regular combinedDefault = { x: 11, y: 3.0 };
226     immutable combined = combine(combinedMain, combinedDefault);
227     assert(combined.x == 12 && combined.y == 3.0);
228     immutable S.WithDefaults combinedMain2 = { x: 12 };
229     immutable S.WithDefaults combinedDefault2 = { x: 11, y: 3.0 };
230     immutable combined2 = combine(combinedMain2, combinedDefault2);
231     assert(combined2.x == 12 && combined2.y == 3.0);
232     immutable S.WithDefaults combinedMain3 = { x: 12 };
233     immutable S.Func combinedDefault3 = { x: ()=>11, y: ()=>3.0 };
234     immutable combined3 = combine(combinedMain3, combinedDefault3);
235     assert(combined3.x == 12 && combined3.y == 3.0);
236 
237     float f(int a, float b) {
238         return a + b;
239     }
240     assert(callFunctionWithParamsStruct!f(combined) == combined.x + combined.y);
241 
242     struct Test {
243         float f(int a, float b) {
244             return a + b;
245         }
246     }
247     Test t;
248     assert(callMemberFunctionWithParamsStruct!(t, "f")(combined) == combined.x + combined.y);
249 }