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 }