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 }