1 /** 2 * Var-like helper for visitor. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.mapset.vars; 15 16 static if (__VERSION__ >= 2083): 17 18 import core.lifetime; 19 20 import std.meta; 21 import std.traits; 22 23 import ae.utils.mapset.mapset; 24 import ae.utils.mapset.visitor; 25 26 /// A wrapper around a MapSetVisitor which allows interacting with it 27 /// using a more conventional interface. 28 struct MapSetVars( 29 /// `MapSet` `DimName` parameter. 30 /// Must also provide `tempVarStart` and `tempVarEnd` 31 /// static fields for temporary variable allocation. 32 VarName_, 33 /// `MapSet` `DimValue` parameter. 34 Value_, 35 /// `MapSet` `nullValue` parameter. 36 Value_ nullValue = Value_.init, 37 ) 38 { 39 @disable this(this); // Do not copy! 40 41 alias VarName = VarName_; 42 alias Value = Value_; 43 44 alias Set = MapSet!(VarName, Value, nullValue); 45 alias Visitor = MapSetVisitor!(VarName, Value, nullValue); 46 47 Visitor visitor; 48 VarName varCounter; 49 50 private VarName allocateName() 51 { 52 assert(varCounter < VarName.tempVarEnd, "Too many temporary variables"); 53 return varCounter++; 54 } 55 56 private void deallocate(VarName name) 57 { 58 // best-effort de-bump-the-pointer 59 auto next = name; next++; 60 if (next > name && next == varCounter) 61 varCounter = name; 62 } 63 64 bool next() 65 { 66 varCounter = VarName.tempVarStart; 67 return visitor.next(); 68 } 69 70 Var get(VarName name) 71 { 72 return Var(&this, name); 73 } 74 75 alias opIndex = get; 76 77 private struct Dispatcher 78 { 79 private MapSetVars* vars; 80 81 Var opDispatch(string name)() 82 if (__traits(hasMember, VarName, name)) 83 { 84 return vars.get(__traits(getMember, VarName, name)); 85 } 86 87 Var opDispatch(string name, V)(auto ref V v) 88 if (__traits(hasMember, VarName, name) && is(typeof(vars.get(VarName.init) = v))) 89 { 90 return vars.get(__traits(getMember, VarName, name)) = v; 91 } 92 } 93 94 @property Dispatcher var() { return Dispatcher(&this); } 95 96 Var allocate(Value value = nullValue) 97 { 98 return Var(&this, allocateName(), value); 99 } 100 101 Var inject(Value[] values) 102 { 103 auto var = Var(&this, allocateName()); 104 visitor.inject(var.name, values); 105 return var; 106 } 107 108 private Var eval(size_t n)(Repeat!(n, Var) vars, scope Value delegate(Repeat!(n, Value) values) fun) 109 { 110 foreach (ref var; vars) 111 assert(var.vars is &this, "Cross-domain operation"); 112 VarName[n] inputs; 113 foreach (i, ref var; vars) 114 inputs[i] = var.name; 115 auto result = allocate(); 116 visitor.multiTransform(inputs, [result.name], 117 (scope Value[] inputValues, scope Value[] outputValues) 118 { 119 Repeat!(n, Value) values; 120 static foreach (i; 0 .. n) 121 values[i] = inputValues[i]; 122 outputValues[0] = fun(values); 123 } 124 ); 125 return result; 126 } 127 128 struct Var 129 { 130 // --- Lifetime 131 132 MapSetVars* vars; 133 VarName name; 134 135 private this(MapSetVars* vars, VarName name, Value value = nullValue) 136 { 137 this.vars = vars; 138 this.name = name; 139 if (value != nullValue) 140 this = value; 141 } 142 143 this(this) 144 { 145 auto newName = vars.allocateName(); 146 vars.visitor.copy(this.name, newName); 147 this.name = newName; 148 } 149 150 ~this() 151 { 152 // Must not be in GC! 153 if (vars && name >= VarName.tempVarStart && name < VarName.tempVarEnd) 154 { 155 vars.visitor.put(name, nullValue); 156 vars.deallocate(name); 157 } 158 } 159 160 // --- Transformation operations 161 162 private enum bool unaryIsInjective(string op) = op == "+" || op == "-" || op == "~"; 163 private enum bool binaryIsInjective(string op) = op == "+" || op == "-" || op == "^" || op == "~"; 164 165 Var opUnary(string op)() 166 if (is(typeof(mixin(op ~ ` Value.init`)) : Value)) 167 { 168 auto result = allocate(); 169 visitor.targetTransform!(unaryIsInjective!op)(this.name, result.name, 170 (ref const Value inputValue, out Value outputValue) 171 { 172 mixin(`outputValue = ` ~ op ~ ` inputValue;`); 173 } 174 ); 175 return result; 176 } 177 178 template opBinary(string op) 179 if (is(typeof(mixin(`Value.init ` ~ op ~ ` Value.init`)) : Value)) 180 { 181 Var opBinary(Var other) 182 { 183 assert(vars is other.vars, "Cross-domain operation"); 184 auto result = vars.allocate(); 185 vars.visitor.multiTransform([this.name, other.name], [result.name], 186 (scope Value[] inputValues, scope Value[] outputValues) 187 { 188 mixin(`outputValues[0] = inputValues[0] ` ~ op ~ ` inputValues[1];`); 189 } 190 ); 191 return result; 192 } 193 194 Var opBinary(Value other) 195 { 196 auto result = vars.allocate(); 197 vars.visitor.targetTransform!(binaryIsInjective!op)(this.name, result.name, 198 (ref const Value inputValue, out Value outputValue) 199 { 200 mixin(`outputValue = inputValue ` ~ op ~ ` other;`); 201 } 202 ); 203 return result; 204 } 205 } 206 207 Var opBinaryRight(string op)(Value other) 208 if (is(typeof(mixin(`Value.init ` ~ op ~ ` Value.init`)) : Value)) 209 { 210 auto result = vars.allocate(); 211 vars.visitor.targetTransform!(binaryIsInjective!op)(this.name, result.name, 212 (ref const Value inputValue, out Value outputValue) 213 { 214 mixin(`outputValue = other ` ~ op ~ ` inputValue;`); 215 } 216 ); 217 return result; 218 } 219 220 void opOpAssign(string op)(Value other) 221 if (is(typeof(mixin(`{ Value v; v ` ~ op ~ `= Value.init; }`)))) 222 { 223 static if (binaryIsInjective!op) 224 vars.visitor.injectiveTransform(name, (ref Value value) { mixin(`value ` ~ op ~ `= other;`); }); 225 else 226 vars.visitor. transform(name, (ref Value value) { mixin(`value ` ~ op ~ `= other;`); }); 227 } 228 229 alias opEquals = opBinary!"=="; 230 231 static if (Value.min < 0) 232 { 233 static struct CompareResult 234 { 235 Var var; 236 alias var this; 237 } 238 239 CompareResult /*opCmp*/cmp(Var other) 240 { 241 auto result = vars.allocate(); 242 vars.visitor.multiTransform([this.name, other.name], [result.name], 243 (scope Value[] inputValues, scope Value[] outputValues) 244 { 245 outputValues[0] = 246 inputValues[0] < inputValues[1] ? -1 : 247 inputValues[0] > inputValues[1] ? +1 : 248 0; 249 } 250 ); 251 return CompareResult(result); 252 } 253 254 CompareResult /*opCmp*/cmp(Value other) 255 { 256 auto result = vars.allocate(); 257 vars.visitor.multiTransform([this.name], [result.name], 258 (scope Value[] inputValues, scope Value[] outputValues) 259 { 260 outputValues[0] = 261 inputValues[0] < other ? -1 : 262 inputValues[0] > other ? +1 : 263 0; 264 } 265 ); 266 return CompareResult(result); 267 } 268 } 269 270 // D does not allow overloading comparison operators :( :( :( :( :( 271 alias lt = opBinary!"<"; 272 alias gt = opBinary!">"; 273 alias le = opBinary!"<="; 274 alias ge = opBinary!">="; 275 alias eq = opBinary!"=="; 276 alias ne = opBinary!"!="; 277 278 // Can't overload ! either 279 Var not() { return map(d => !d); } 280 281 /// Ternary. 282 Var choose(Value ifTrue, Value ifFalse) 283 { 284 return map(d => d ? ifTrue : ifFalse); 285 } 286 287 // --- Fundamental operations 288 289 Var opAssign(Var value) 290 { 291 if (!vars) 292 { 293 // Auto-allocate if uninitialized. 294 // Note, we can't do the same thing for the Value opAssign. 295 vars = value.vars; 296 name = vars.allocateName(); 297 } 298 299 vars.visitor.copy(value.name, this.name); 300 return this; 301 } 302 303 Var opAssign(Value value) 304 { 305 vars.visitor.put(this.name, value); 306 return this; 307 } 308 309 Value resolve() 310 { 311 return vars.visitor.get(this.name); 312 } 313 314 // Transform and copy one variable. 315 Var map(scope Value delegate(Value) fun) 316 { 317 auto result = vars.allocate(); 318 vars.visitor.targetTransform(this.name, result.name, 319 (ref const Value inputValue, out Value outputValue) 320 { 321 outputValue = fun(inputValue); 322 } 323 ); 324 return result; 325 } 326 } 327 } 328 329 /// An example. 330 /// Note that, unlike the similar Visitor test case, this iterates only once, 331 /// thanks to the opBinary support. 332 debug(ae_unittest) unittest 333 { 334 // Setup 335 336 enum VarName : uint { x, y, z, tempVarStart = 100, tempVarEnd = 200 } 337 MapSetVars!(VarName, int) v; 338 alias M = MapSetVars!(VarName, int).Set; 339 M m = v.Set.unitSet; 340 m = m.cartesianProduct(VarName.x, [1, 2, 3]); 341 m = m.cartesianProduct(VarName.y, [10, 20, 30]); 342 v.visitor = v.Visitor(m); 343 344 // The algorithm that runs on the mapset 345 346 void theAlgorithm() 347 { 348 v.var.z = v.var.x + v.var.y; 349 } 350 351 // The evaluation loop 352 353 M results; 354 size_t numIterations; 355 while (v.next()) 356 { 357 theAlgorithm(); 358 359 results = results.merge(v.visitor.currentSubset); 360 numIterations++; 361 } 362 363 assert(numIterations == 1); 364 assert(results.all(VarName.z) == [11, 12, 13, 21, 22, 23, 31, 32, 33]); 365 } 366 367 debug(ae_unittest) unittest 368 { 369 enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 } 370 MapSetVars!(VarName, int) v; 371 v.visitor = v.Visitor(v.Set.unitSet); 372 v.next(); 373 374 auto a = v.allocate(); 375 a = 5; 376 auto b = a; 377 a = 6; 378 assert(b.resolve == 5); 379 } 380 381 debug(ae_unittest) unittest 382 { 383 enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 } 384 MapSetVars!(VarName, int) v; 385 v.visitor = v.Visitor(v.Set.unitSet); 386 v.next(); 387 388 auto a = v.get(cast(VarName)0); 389 390 v.get(a.name) = 5; 391 assert(a.resolve == 5); 392 393 v.get(a.name) = v.allocate(7); 394 assert(a.resolve == 7); 395 } 396 397 debug(ae_unittest) unittest 398 { 399 enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 } 400 MapSetVars!(VarName, int) v; 401 v.visitor = v.Visitor(v.Set.unitSet); 402 v.next(); 403 404 auto a = v.get(cast(VarName)0); 405 v.Var b; 406 b = a; 407 } 408 409 /// Array indexing 410 Var at(T)(T[] array, Var index) 411 if (is(T : Value)) 412 { 413 return index.map(value => array[value]); 414 } 415 416 debug(ae_unittest) unittest 417 { 418 enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 } 419 MapSetVars!(VarName, int) v; 420 v.visitor = v.Visitor(v.Set.unitSet); 421 v.next(); 422 423 auto a = v.get(cast(VarName)0); 424 v.Var b; 425 b = a; 426 } 427 428 template varCall(alias fun) 429 // if (valueLike!(ReturnType!fun) && allSatisfy!(valueLike, Parameters!fun)) 430 { 431 template isValidVarArgs(VarArgs...) 432 { 433 alias Vars = __traits(parent, VarArgs[0]); 434 enum isVar(VarArg) = is(VarArg == Vars.Var); 435 enum isValidVarArgs = allSatisfy!(isVar, VarArgs); 436 } 437 438 // Var varCall(Repeat!(Parameters!fun.length, Var) vars) 439 VarArgs[0] varCall(VarArgs...)(VarArgs varArgs) 440 if (isValidVarArgs!VarArgs) 441 { 442 alias Vars = __traits(parent, VarArgs[0]); 443 alias Var = Vars.Var; 444 auto vars = varArgs[0].vars; 445 return vars.eval!(Parameters!fun.length)(varArgs, 446 (Repeat!(Parameters!fun.length, Vars.Value) values) 447 { 448 Parameters!fun params; 449 foreach (i, ref param; params) 450 param = cast(typeof(param)) values[i]; // Value -> Integer 451 return cast(Vars.Value)fun(params); 452 }); 453 } 454 } 455 456 debug(ae_unittest) unittest 457 { 458 static int fun(int i) { return i + 1; } 459 460 enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 } 461 MapSetVars!(VarName, int) v; 462 v.visitor = v.Visitor(v.Set.unitSet); 463 v.next(); 464 465 auto a = v.allocate(); 466 a = 5; 467 auto b = varCall!fun(a); 468 assert(b.resolve == 6); 469 }