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 }