1 /** 2 Lightweight Modular Staging library for D langauge. 3 4 Popularization of dependency injection framework somehow shaded 5 the bigger and more general technique - staging and staged computation. 6 7 This library ractifies that omission for the D language. 8 9 10 Built with love based on the ideas in the paper: 11 12 Lightweight Modular Staging: A Pragmatic Approach to 13 Runtime Code Generation and Compiled DSLs 14 by Tiark Rompf and Martin Odersky. 15 16 The above paper and many other good ones by Scala team at EPFL 17 are here http://infoscience.epfl.ch/record/150347/files/gpce63-rompf.pdf. 18 19 20 Synopsis 21 22 --- 23 auto stage = new BasicStage(); 24 int[] trace; // our primitive trace buffer 25 auto v1 = stage.slot!double("var1").map(delegate double(double x) { 26 trace ~= 1; 27 return x; 28 }); 29 auto v2 = stage.slot!double("var2").map(delegate double(double x) { 30 trace ~= 2; 31 return x; 32 }); 33 stage["var1"] = lift(1.5); 34 auto part = (v1 + v2).partial(stage); 35 stage["var2"] = lift(-0.5); 36 // first pass - both map functions called once 37 assert(part.eval(stage) == 1.0); 38 assert(trace == [1, 2]); 39 40 // second pass - only v2 is evaluated 41 assert(part.eval(stage) == 1.0); 42 assert(trace == [1, 2, 2]); 43 --- 44 45 */ 46 47 module lms; 48 49 class Box { 50 // replace value of this box with another, may throw if cannot do that 51 void replace(Box another) { 52 throw new LmsException("Internal error - cannot replace contents of this lifted value"); 53 } 54 } 55 56 /// Lift a simple constant 57 auto lift(T)(T value) { 58 static if(is(T : Box)) { 59 return value; 60 } 61 else 62 return new Constant!T(value); 63 } 64 65 template Unlift(T) 66 { 67 static if(is(T : Lift!V, V)) 68 { 69 alias Unlift = V; 70 } 71 else 72 alias Unlift = T; 73 } 74 75 /** 76 Stage is equivalent to DI container (or rather DI is simple late-binding + basic form of staging) 77 but the composition and execution of them is independent 78 and encapsulated by Lift!T interface 79 80 A user is expected to sub-class and define custom stages as needed. See also `BasicStage`. 81 */ 82 interface Stage { 83 /// Lift a placeholder - slot for concrete value to be filled in at a later stage 84 Slot!T slot(T)(string name) { 85 auto lifted = new Slot!T(name); 86 register(name, lifted); 87 return lifted; 88 } 89 90 /// Register existing slot at this _stage_ with name `name` 91 Slot!T slot(T)(string name, Slot!T value) { 92 register(name, value); 93 value.reset(); 94 return value; 95 } 96 97 /// Register existing slot at this _stage_ with original name 98 Slot!T slot(T)(Slot!T value) { 99 register(value.name, value); 100 value.reset(); 101 return value; 102 } 103 104 /// Try to evaluate (lower) lifted value using this stage 105 auto eval(T)(Lift!T value) { 106 return value.eval(this); 107 } 108 109 /// Do partial evaluation for lifted value, this folds all known-constant sub-tries and optimizes expressions 110 auto partial(U)(U value) 111 if (!is(U : Slot!T, T)) { 112 return value.partial(this); 113 } 114 115 ///ditto 116 auto partial(U)(U value) 117 if (is(U : Slot!T, T)) { 118 return cast(Lift!T)this[value]; 119 } 120 121 void register(T)(Slot!T slot) { 122 register(slot.name, typeid(T)); 123 } 124 125 /// This is an implementation hook - register must save name,typeinfo pair to check type matching later 126 void register(string name, Box box); 127 128 /// This is an implementation hook - bind must check that typeinfo matches and bind value to the lifted slot 129 Box opIndexAssign(Box value, string name); 130 131 /// Third implementation hook - lookup bound value for a given name 132 Box opIndex(string name); 133 } 134 135 /** 136 Simple stage that keeps slots as key-value pairs in built-in AA. 137 138 Could be used as is or as an example to build your own stage(s). 139 */ 140 class BasicStage : Stage { 141 override void register(string name, Box box) { 142 if (name in slots) throw new LmsNameConflict("This stage already has slot for '"~name~"' variable"); 143 slots[name] = box; 144 } 145 146 override Box opIndexAssign(Box lifted, string name) { 147 auto p = name in slots; 148 if (!p) throw new LmsNameResolution("This stage doesn't have '"~name~"' variable"); 149 slots[name].replace(lifted); 150 return lifted; 151 } 152 153 override Box opIndex(string name) { 154 auto p = name in slots; 155 if (!p) throw new LmsNameResolution("This stage doesn't have '"~name~"' variable"); 156 return slots[name]; 157 } 158 159 private Box[string] slots; 160 } 161 162 // Lifted value of type T 163 abstract class Lift(T) : Box { 164 // full evaluation, may fail if some variables are not defined at this stage 165 abstract T eval(Stage stage); 166 // partial evaluation given all of variables we know at this stage 167 abstract Lift!T partial(Stage stage); 168 // 169 final Lift!U map(U)(U delegate(T) mapFunc) { 170 return new Mapped!(T, U)(this, mapFunc); 171 } 172 // 173 final Lift!U flatMap(U)(Lift!U delegate(T) mapFunc) { 174 return new FlatMapped!(T, U)(this, mapFunc); 175 } 176 177 /// 178 auto opBinary(string op, U)(U rhs) { 179 return new Binary!(op, T, Unlift!U)(this, lift(rhs)); 180 } 181 } 182 183 /// Simpliest of all - just a constant, stays the same, regardless of _stage_ 184 class Constant(T) : Lift!T { 185 this(T value) { 186 this.value = value; 187 } 188 189 override T eval(Stage stage) { 190 return value; 191 } 192 193 override Lift!T partial(Stage stage) { 194 return this; 195 } 196 197 private T value; 198 } 199 200 class Binary(string op, T, U) : Lift!T { 201 this(Lift!T lhs, Lift!U rhs) { 202 left = lhs; 203 right = rhs; 204 } 205 206 override T eval(Stage stage) { 207 auto calculate(T x, U y){ 208 return mixin("x "~op~" y"); 209 } 210 auto vl = left.eval(stage); 211 auto vr = right.eval(stage); 212 return calculate(vl, vr); 213 } 214 215 override Lift!T partial(Stage stage) { 216 auto pl = left.partial(stage); 217 auto pr = right.partial(stage); 218 return new Binary!(op, T, U)(pl, pr); 219 } 220 221 private Lift!T left; 222 private Lift!U right; 223 } 224 225 /// Slot - a placeholder for value, that will be provided at a later _stage_ 226 class Slot(T) : Lift!T { 227 this(string name) { 228 _name = name; 229 reset(); 230 } 231 232 override void replace(Box another) { 233 expr = cast(Lift!T)another; 234 } 235 236 void reset() { 237 expr = lift(T.init).map(delegate T (T x){ 238 throw new LmsEvaluationFailed("slot "~_name~" has no bound value at this stage"); 239 }); 240 } 241 242 override T eval(Stage stage) { 243 return expr.eval(stage); 244 } 245 246 override Lift!T partial(Stage stage) { 247 return expr; 248 } 249 250 string name() { return _name; } 251 252 private string _name; 253 private Lift!T expr; 254 } 255 256 // Lifted map function call 257 private class Mapped(T, U) : Lift!U { 258 this(Lift!T arg, U delegate(T) func) { 259 this.liftedArg = arg; 260 this.func = func; 261 } 262 263 override U eval(Stage stage) { 264 return func(liftedArg.eval(stage)); 265 } 266 267 override Lift!U partial(Stage stage) { 268 import std.stdio : writeln; 269 auto v = liftedArg.partial(stage); 270 auto c = cast(Constant!T)v; 271 if (c) return lift(func(c.eval(stage))); 272 return v.map(func); 273 } 274 275 private Lift!T liftedArg; 276 private U delegate(T) func; 277 } 278 279 private class FlatMapped(T, U) : Lift!U { 280 this(Lift!T arg, Lift!U delegate(T) func) { 281 this.liftedArg = arg; 282 this.func = func; 283 } 284 285 override U eval(Stage stage) { 286 return func(liftedArg.eval(stage)).eval(stage); 287 } 288 289 override Lift!U partial(Stage stage) { 290 return liftedArg.partial(stage).flatMap((arg){ 291 return func(arg); 292 }); 293 } 294 295 private Lift!T liftedArg; 296 private Lift!U delegate(T) func; 297 } 298 299 class LmsException : Exception { 300 this(string message) { 301 super(message); 302 } 303 } 304 305 class LmsNameResolution : LmsException { 306 this(string message) { 307 super(message); 308 } 309 } 310 311 class LmsNameConflict : LmsNameResolution { 312 this(string message){ 313 super(message); 314 } 315 } 316 317 class LmsEvaluationFailed : LmsException { 318 this(string message){ 319 super(message); 320 } 321 } 322 323 version(unittest) { 324 void assertThrows(T)(lazy T expr) { 325 try { 326 expr; 327 } 328 catch(LmsException e) { 329 return; 330 } 331 assert(0, expr.stringof ~ " should throw but didn't!"); 332 } 333 } 334 335 /// 336 @("basics") 337 unittest { 338 auto stage = new BasicStage(); 339 auto value = lift(40) + 2; 340 assert(stage.eval(value) == 42); 341 } 342 343 /// 344 @("slots") 345 unittest { 346 auto stage = new BasicStage(); 347 auto slot = stage.slot!string("some.slot"); 348 assert(slot.name == "some.slot"); 349 auto expr = slot ~ ", world!"; 350 assertThrows(stage.eval(expr)); 351 352 stage["some.slot"] = lift("Hello"); 353 assert(stage.eval(expr) == "Hello, world!"); 354 355 auto laterStage = new BasicStage(); 356 laterStage.slot(slot); 357 assertThrows(laterStage.eval(slot)); 358 359 laterStage["some.slot"] = lift("Bye"); 360 361 assert(stage.eval(expr) == "Bye, world!"); 362 } 363 364 365 /// 366 @("partial evaluation") 367 unittest { 368 auto stage = new BasicStage(); 369 int[] trace; // our primitive trace buffer 370 auto v1 = stage.slot!double("var1").map(delegate double(double x) { 371 trace ~= 1; 372 return x; 373 }); 374 auto v2 = stage.slot!double("var2").map(delegate double(double x) { 375 trace ~= 2; 376 return x; 377 }); 378 stage["var1"] = lift(1.5); 379 auto part = (v1 + v2).partial(stage); 380 stage["var2"] = lift(-0.5); 381 // first pass - both map functions called once 382 assert(part.eval(stage) == 1.0); 383 assert(trace == [1, 2]); 384 385 // second pass - only v2 is evaluated 386 assert(part.eval(stage) == 1.0); 387 assert(trace == [1, 2, 2]); 388 } 389 390 /// 391 @("CTFE") 392 unittest { 393 enum result = () { 394 auto stage = new BasicStage(); 395 auto s = stage.slot!int("int"); 396 stage["int"] = lift(123); 397 auto s2 = lift(2); 398 // somehow fails.. to be fixed soon 399 auto s3 = s + s2; 400 return stage.eval(s3); 401 }(); 402 static assert(result == 125); 403 }