1 /** Pass metadata alongside the data stream.
2  *
3  *  Authors: $(LINK2 https://github.com/epi, Adrian Matoga)
4  *  Copyright: © 2016 Adrian Matoga
5  *  License: $(LINK2 http://www.boost.org/users/license.html, BSL-1.0).
6  */
7 module flod.metadata;
8 
9 import std.algorithm : isSorted;
10 import std.meta : AliasSeq, Filter, allSatisfy, anySatisfy, staticMap;
11 import std.typecons : Tuple, tuple;
12 
13 import flod.traits;
14 version(unittest) import flod.meta : Id;
15 
16 package enum TagOp { get, set }
17 
18 package struct TagAttribute(T, string k, TagOp o) {
19 	alias Type = T;
20 	enum string key = k;
21 	enum TagOp op = o;
22 }
23 
24 // workaround for #17143
25 private struct expandTuple(a...)
26 {
27 	enum value = tuple(a).expand;
28 }
29 
30 private enum tagSetterImpl() = expandTuple!().value;
31 private enum tagGetterImpl() = expandTuple!().value;
32 private enum tagSetterImpl(T, string k, Z...) =
33 	expandTuple!(TagAttribute!(T, k, TagOp.set)(), tagSetterImpl!Z).value;
34 private enum tagGetterImpl(T, string k, Z...) =
35 	expandTuple!(TagAttribute!(T, k, TagOp.get)(), tagGetterImpl!Z).value;
36 
37 /// This attribute declares the stage as setter for tag `key` of type `Type`.
38 enum tagSetter(Type, string key, next...) = tagSetterImpl!(Type, key, next);
39 
40 /// This attribute declares the stage as getter for tag `key` of type `Type`.
41 enum tagGetter(Type, string key, next...) = tagGetterImpl!(Type, key, next);
42 
43 private enum isTagAttribute(S...) = is(typeof(S[0]) : TagAttribute!_a, _a...);
44 package enum getTagAttributes(S...) = expandTuple!(Filter!(isTagAttribute, __traits(getAttributes, S[0]))).value;
45 
46 unittest {
47 	static struct Bar {}
48 	enum x = getTagAttributes!Bar;
49 	static assert(x.length == 0);
50 }
51 
52 unittest {
53 	@tagSetter!(uint, "foo")
54 	@tagSetter!(string, "bar")
55 	@tagGetter!(double, "baz", string, "quux")
56 	@sink!uint(Method.push)
57 	static struct Foo {}
58 	enum x = getTagAttributes!Foo;
59 	static assert(x.length == 4);
60 	static assert(x[0] == TagAttribute!(uint, "foo", TagOp.set)());
61 	static assert(x[1] == TagAttribute!(string, "bar", TagOp.set)());
62 	static assert(x[2] == TagAttribute!(double, "baz", TagOp.get)());
63 	static assert(x[3] == TagAttribute!(string, "quux", TagOp.get)());
64 }
65 
66 /// Bundles stage index and its TagAttributes
67 private template TagAttributeTuple(size_t i, ta...)
68 	if (allSatisfy!(isTagAttribute, ta))
69 {
70 	enum size_t index = i;
71 	enum tagAttributes = expandTuple!(ta).value;
72 
73 	static if (tagAttributes.length) {
74 		enum front = tagAttributes[0];
75 		alias removeFront = TagAttributeTuple!(index, tagAttributes[1 .. $]);
76 	} else {
77 		alias removeFront = TagAttributeTuple!(index);
78 	}
79 }
80 
81 /** Extracts tag attributes from a sequence of stages.
82 Params:
83 i = index of first stage in StageSeq
84 StageSeq = sequence of stages
85 */
86 package template FilterTagAttributes(size_t i, StageSeq...)
87 	if (allSatisfy!(isStage, StageSeq))
88 {
89 	static if (StageSeq.length) {
90 		alias tags = getTagAttributes!(StageSeq[0]);
91 		static if (tags.length)
92 			alias FilterTagAttributes = AliasSeq!(TagAttributeTuple!(i, getTagAttributes!(StageSeq[0])),
93 				.FilterTagAttributes!(i + 1, StageSeq[1 .. $]));
94 		else
95 			alias FilterTagAttributes = .FilterTagAttributes!(i + 1, StageSeq[1 .. $]);
96 	} else {
97 		alias FilterTagAttributes = AliasSeq!();
98 	}
99 }
100 
101 /// Bundles tag value type, key, and indexes of all setters
102 private template TagSpec(T, string k, size_t[] s, size_t[] g)
103 	if (isSorted(s) && isSorted(g))
104 {
105 	alias Type = T;
106 	enum string key = k;
107 	enum size_t[] setters = s;
108 	enum size_t[] getters = g;
109 	// returns array of getter indexes that should be notified by setter at i
110 	enum gettersAt(size_t index) = (size_t i){
111 		import std.range : assumeSorted, array;
112 		import std.stdio;
113 		size_t nextSetter = (setters ~ (size_t.max - 1)).assumeSorted.upperBound(i)[0];
114 		return getters.assumeSorted.upperBound(i).lowerBound(nextSetter + 1).array();
115 	}(index);
116 }
117 
118 unittest {
119 	alias ts = TagSpec!(double, "foo", [ 1, 4, 5, 9, 15 ], [ 2, 4, 8, 9, 10, 11, 16 ]);
120 	static assert(ts.gettersAt!1 == [ 2, 4 ]);
121 	static assert(ts.gettersAt!4 == []);
122 	static assert(ts.gettersAt!5 == [ 8, 9 ]);
123 	static assert(ts.gettersAt!9 == [ 10, 11 ]);
124 	static assert(ts.gettersAt!15 == [ 16 ]);
125 }
126 
127 private enum isTagSpec(S...) = is(S[0].Type) && is(typeof(S[0].key) == string)
128 	&& is(typeof(S[0].setters) == size_t[]) && is(typeof(S[0].getters) == size_t[]);
129 
130 unittest {
131 	static assert( isTagSpec!(TagSpec!(double, "foo", [ 1, 4, 5 ], [ 2 ])));
132 	static assert(!isTagSpec!());
133 	static assert(!isTagSpec!2);
134 	static assert(!isTagSpec!(Id!int));
135 }
136 
137 private template hasKey(string k) {
138 	enum bool hasKey(alias S) = S.key == k;
139 }
140 
141 private template TagSpecByKey(string k, tagSpecs...)
142 	if (allSatisfy!(isTagSpec, tagSpecs))
143 {
144 	alias TagSpecByKey = Filter!(hasKey!k, tagSpecs);
145 }
146 
147 unittest {
148 	alias specs = AliasSeq!(
149 		TagSpec!(uint, "foo", [ 1, 2, 15 ], [ 4, 6, 20 ]),
150 		TagSpec!(uint, "bar", [ 5 ], []),
151 		TagSpec!(uint, "baz", [ 7, 42 ], [ 8, 42, 43 ]));
152 	alias bar = TagSpecByKey!("bar", specs);
153 	static assert(is(Id!bar == Id!(specs[1])));
154 }
155 
156 private template RemoveTagSpecByKey(string k, tagSpecs...)
157 	if (allSatisfy!(isTagSpec, tagSpecs))
158 {
159 	static if (tagSpecs.length == 0)
160 		alias RemoveTagSpecByKey = AliasSeq!();
161 	else static if (tagSpecs[0].key == k)
162 		alias RemoveTagSpecByKey = RemoveTagSpecByKey!(k, tagSpecs[1 .. $]);
163 	else
164 		alias RemoveTagSpecByKey = AliasSeq!(tagSpecs[0], RemoveTagSpecByKey!(k, tagSpecs[1 .. $]));
165 }
166 
167 unittest {
168 	alias specs = AliasSeq!(
169 		TagSpec!(uint, "foo", [ 1, 2, 15 ], [ 4, 6, 20 ]),
170 		TagSpec!(uint, "bar", [ 5 ], []),
171 		TagSpec!(uint, "baz", [ 7, 42 ], [ 8, 42, 43 ]));
172 	alias nobar = RemoveTagSpecByKey!("bar", specs);
173 	static assert(is(Id!nobar == Id!(specs[0], specs[2])));
174 }
175 
176 template MergeTagSpecs(alias NewSpec, tagSpecs...)
177 	if (allSatisfy!(isTagSpec, NewSpec, tagSpecs))
178 {
179 	alias MergeTagSpecs = AliasSeq!(NewSpec, RemoveTagSpecByKey!(NewSpec.key, tagSpecs));
180 }
181 
182 /** Transposes a sequence of (index, tag_attributes...) tuples into a sequence of TagSpecs
183 */
184 private template TagSpecSeq(TagAttributeTupleSeq...) {
185 	static if (TagAttributeTupleSeq.length == 0)
186 		alias TagSpecSeq = AliasSeq!();
187 	else static if (TagAttributeTupleSeq[0].tagAttributes.length == 0)
188 		alias TagSpecSeq = .TagSpecSeq!(TagAttributeTupleSeq[1 .. $]);
189 	else {
190 		alias AttrTuple = TagAttributeTupleSeq[0];
191 		enum index = AttrTuple.index;
192 		alias Type = AttrTuple.front.Type;
193 		enum key = AttrTuple.front.key;
194 		enum op = AttrTuple.front.op;
195 		alias Tail = .TagSpecSeq!(AliasSeq!(AttrTuple.removeFront, TagAttributeTupleSeq[1 .. $]));
196 		alias Spec = TagSpecByKey!(key, Tail);
197 
198 		static assert(allSatisfy!(isTagSpec, Spec, Tail));
199 		static assert(Spec.length == 0 || is(Spec[0].Type == Type), "Conflicting types for tag `" ~ key ~ "`: "
200 			~ str!(Spec[0].Type) ~ " and " ~ str!Type);
201 
202 		static if (op == TagOp.set) {
203 			static if (Spec.length == 0)
204 				alias TagSpecSeq = AliasSeq!(TagSpec!(Type, key, [ index ], []), Tail);
205 			else static if (Spec.length == 1)
206 				alias TagSpecSeq = MergeTagSpecs!(
207 					TagSpec!(Type, key, [ index ] ~ Spec[0].setters, Spec[0].getters), Tail);
208 		} else static if (op == TagOp.get) {
209 			static if (Spec.length == 0)
210 				alias TagSpecSeq = AliasSeq!(TagSpec!(Type, key, [], [ index ]), Tail);
211 			else static if (Spec.length == 1)
212 				alias TagSpecSeq = MergeTagSpecs!(
213 					TagSpec!(Type, key, Spec[0].setters, [ index ] ~ Spec[0].getters), Tail);
214 		}
215 	}
216 }
217 
218 unittest {
219 	alias stageTags = AliasSeq!(
220 		TagAttributeTuple!(3, tagSetter!(uint, "foo")),
221 		TagAttributeTuple!(4, tagSetter!(string, "bar")),
222 		TagAttributeTuple!(5, tagGetter!(uint, "foo"), tagSetter!(uint, "foo")),
223 		TagAttributeTuple!(6, tagGetter!(uint, "foo", string, "bar")));
224 	alias specs = TagSpecSeq!stageTags;
225 	static assert(specs.length == 2);
226 	static assert(is(Id!specs == Id!(
227 		TagSpec!(uint, "foo", [ 3, 5 ], [ 5, 6 ]),
228 		TagSpec!(string, "bar", [ 4 ], [ 6 ]))));
229 }
230 
231 /// Structure that holds values of a single tag for all subranges of a pipeline.
232 private struct Tag(alias Spec) {
233 	alias T = Spec.Type;
234 	enum size_t length = Spec.setters.length;
235 	T[length] store;
236 
237 	template storeIndex(size_t stageIndex, size_t left = 0, size_t right = length) {
238 		static if (right - left == 1) {
239 			enum storeIndex = left;
240 		} else {
241 			enum size_t m = (left + right) / 2;
242 			static if (stageIndex < Spec.setters[m])
243 				enum storeIndex = storeIndex!(stageIndex, left, m);
244 			else
245 				enum storeIndex = storeIndex!(stageIndex, m, right);
246 		}
247 	}
248 
249 	void set(size_t index)(T value)
250 	{
251 		enum si = storeIndex!index;
252 		static assert(Spec.setters[si] == index);
253 		store[si] = value;
254 	}
255 
256 	T get(size_t index)()
257 	{
258 		import std.conv : to;
259 		static assert(index > Spec.setters[0], "There is no setter for tag " ~ name
260 			~ " before stage #" ~ index.to!string);
261 		enum si = storeIndex!(index - 1);
262 		return store[si];
263 	}
264 }
265 
266 unittest {
267 	Tag!(TagSpec!(string, "sometag", [ 7, 13, 19, 32 ], [])) tag;
268 	tag.set!7("foo");
269 	tag.set!13("bar");
270 	tag.set!19("baz");
271 	tag.set!32("quux");
272 	static assert(!__traits(compiles, tag.set!5("fail")));
273 	static assert(!__traits(compiles, tag.set!14("fail")));
274 	static assert(!__traits(compiles, tag.get!0));
275 	static assert(!__traits(compiles, tag.get!7));
276 	assert(tag.get!8 == "foo");
277 	assert(tag.get!13 == "foo");
278 	assert(tag.get!14 == "bar");
279 	assert(tag.get!18 == "bar");
280 	assert(tag.get!19 == "bar");
281 	assert(tag.get!20 == "baz");
282 	assert(tag.get!32 == "baz");
283 	assert(tag.get!33 == "quux");
284 }
285 
286 private string escape(string s)
287 {
288 	assert(__ctfe);
289 	import std.array : appender;
290 	import std.format : formattedWrite;
291 	import std.ascii : isAlpha, isAlphaNum;
292 	auto app = appender!string();
293 	app.put("_");
294 	if (s.length == 0)
295 		return app.data;
296 	if (s[0].isAlpha)
297 		app.put(s[0]);
298 	else
299 		app.formattedWrite("_%02X", s[0]);
300 	for (;;) {
301 		s = s[1 .. $];
302 		if (s.length == 0)
303 			break;
304 		if (s[0].isAlphaNum)
305 			app.put(s[0]);
306 		else
307 			app.formattedWrite("_%02X", s[0]);
308 	}
309 	return app.data;
310 }
311 
312 unittest {
313 	static assert("".escape == "_");
314 	static assert("bar".escape == "_bar");
315 	static assert("5foo".escape == "__35foo");
316 	static assert("_foo.bar".escape == "__5Ffoo_2Ebar");
317 }
318 
319 /// Structure that holds values of all tags for all subranges in a pipeline.
320 private struct TagTuple(tagSpecs...)
321 	if (allSatisfy!(isTagSpec, tagSpecs))
322 {
323 	alias specMap(alias spec) = AliasSeq!(Tag!spec, spec.key.escape);
324 	alias Tup = Tuple!(staticMap!(specMap, tagSpecs));
325 	Tup tags;
326 
327 	template ValueType(string key) {
328 		static if (is(TagSpecByKey!(key, tagSpecs)[0].Type T))
329 			alias ValueType = T;
330 		else
331 			alias ValueType = void;
332 	}
333 
334 	ValueType!k get(string k, size_t i)()
335 	{
336 		import std.conv : to;
337 		mixin("return tags." ~ k.escape ~ ".get!(" ~ i.to!string ~ ");");
338 	}
339 
340 	void set(string k, size_t i)(ValueType!k value)
341 	{
342 		import std.conv : to;
343 		mixin("tags." ~ k.escape ~ ".set!(" ~ i.to!string ~ ")(value);");
344 	}
345 }
346 
347 unittest {
348 	alias specs = AliasSeq!(
349 		TagSpec!(uint, "foo.bar", [ 3, 5, 17 ], [ 4 ]),
350 		TagSpec!(string, "bar.baz", [ 4, 5, 13 ], [ 5, 8 ]));
351 	alias T = TagTuple!specs;
352 	static assert(is(T.ValueType!"foo.bar" == uint));
353 	static assert(is(T.ValueType!"bar.baz" == string));
354 	T tup;
355 	tup.set!("foo.bar", 3)(42);
356 	assert(tup.get!("foo.bar", 4) == 42);
357 	tup.set!("bar.baz", 4)("bar");
358 }
359 
360 template hasOp(TagOp op) {
361 	enum hasOp(alias Attr) = Attr.op == op;
362 }
363 
364 ///
365 package struct Metadata(TagAttributeTuples...) {
366 private:
367 	template test(size_t i, TagOp op) {
368 		enum test(alias S) = S.index == i && anySatisfy!(hasOp!op, S.tagAttributes);
369 	}
370 
371 	enum bool isGetter(size_t index) = anySatisfy!(test!(index, TagOp.get), TagAttributeTuples);
372 	enum bool isSetter(size_t index) = anySatisfy!(test!(index, TagOp.set), TagAttributeTuples);
373 
374 	alias TagSpecs = TagSpecSeq!TagAttributeTuples;
375 	alias Tup = TagTuple!TagSpecs;
376 	Tup tup;
377 
378 public:
379 	///
380 	alias ValueType(string key) = Tup.ValueType!key;
381 
382 	enum size_t[] getters(string key, size_t index) = TagSpecByKey!(key, TagSpecs)[0].gettersAt!index;
383 	void set(string key, size_t index)(ValueType!key value) { tup.set!(key, index)(value); }
384 	ValueType!key get(string key, size_t index)() { return tup.get!(key, index); }
385 }
386 
387 version(unittest) {
388 private:
389 	import flod.traits;
390 	@source!ubyte(Method.pull)
391 	@tagSetter!(string, "foo", int, "bar")
392 	struct TestSource(alias Context, A...) {
393 		mixin Context!A;
394 
395 		size_t pull(ubyte[] buf) {
396 			tag!"foo" = "source";
397 			tag!"bar" = 31337;
398 			return 0;
399 		}
400 	}
401 
402 	@filter(Method.peek, Method.pull)
403 	@tagGetter!(string, "foo") @tagSetter!(string, "foo")
404 	struct TestFilter1(alias Context, A...) {
405 		mixin Context!A;
406 
407 		size_t pull(ubyte[] buf) { return source.peek(buf.length).length; }
408 
409 		void onChange(string key)()
410 		{
411 			static if (key == "foo")
412 				tag!"foo" = tag!"foo" ~ ".filter1";
413 			// TestFilter1 not declared @tagSetter!(int, "bar")
414 			static assert(!__traits(compiles, tag!"bar" = 31337));
415 		}
416 	}
417 
418 	@sink!ubyte(Method.push)
419 	@tagGetter!(string, "foo", int, "bar")
420 	struct TestSink(alias Context, A...) {
421 		mixin Context!A;
422 
423 		string* _foo;
424 		int* _bar;
425 
426 		this(string* foo, int* bar)
427 		{
428 			_foo = foo;
429 			_bar = bar;
430 		}
431 
432 		size_t push(const(ubyte)[] buf) { return buf.length; }
433 
434 		void onChange(string key)()
435 		{
436 			mixin(`*_` ~ key ~ ` = tag!"` ~ key ~ `";`);
437 		}
438 	}
439 }
440 
441 unittest {
442 	import flod.pipeline : pipe;
443 	string foo;
444 	int bar;
445 	pipe!TestSource.pipe!TestFilter1.pipe!TestFilter1.pipe!TestSink(&foo, &bar);
446 	assert(foo == "source.filter1.filter1");
447 	assert(bar == 31337);
448 }