1 /** 2 * Authors: Tomoya Tanjo 3 * Copyright: © 2021 Tomoya Tanjo 4 * License: Apache-2.0 5 */ 6 module salad.canonicalizer; 7 8 /// 9 mixin template genCanonicalizeBody(Base, FieldCanonicalizer...) 10 { 11 import dyaml : Node; 12 13 import salad.context : LoadingContext; 14 import salad.meta : genIdentifier, genToString, id, isConstantMember; 15 16 import std.algorithm : endsWith; 17 import std.format : format; 18 import std.meta : AliasSeq, Stride; 19 import std.traits : isCallable, FieldNameTuple, Fields, hasUDA, Parameters, ReturnType; 20 21 alias FTypes = Fields!Base; 22 alias FNames = FieldNameTuple!Base; 23 24 static assert(FieldCanonicalizer.length % 2 == 0); 25 static if (FieldCanonicalizer.length == 0) 26 { 27 alias ConvFuns = AliasSeq!(); 28 } 29 else 30 { 31 alias ConvFuns = Stride!(2, FieldCanonicalizer[1..$]); 32 } 33 34 static auto findIndex(string name) 35 { 36 import std.algorithm : find; 37 import std.range : enumerate; 38 39 auto rng = (cast(string[])[Stride!(2, FieldCanonicalizer)]).enumerate.find!(e => e.value~"_" == name); 40 return rng.empty ? -1 : rng.front.index; 41 } 42 43 static foreach(idx, fname; FNames) 44 { 45 static assert(fname.endsWith("_"), 46 format!"Field name should end with `_` (%s.%s)"(Base.stringof, fname)); 47 static if (isConstantMember!(Base, fname)) 48 { 49 mixin("immutable string "~fname~" = \""~mixin("(new Base)."~fname)~"\";"); 50 } 51 else static if (findIndex(fname) != -1) 52 { 53 static assert(isCallable!(ConvFuns[findIndex(fname)]), 54 format!"Convert function for `%s` is not callable"(fname)); 55 static assert(Parameters!(ConvFuns[findIndex(fname)]).length == 1, 56 format!"Convert function for `%s` should have only one parameter"(fname)); 57 static assert(is(Parameters!(ConvFuns[findIndex(fname)])[0] == FTypes[idx]), 58 format!"A parameter of convert function for `%s` expects %s but actual: %s"( 59 fname, FTypes[idx], Parameters!(ConvFuns[findIndex(fname)])[0] 60 )); 61 static if (hasUDA!(__traits(getMember, Base, fname), id)) 62 { 63 mixin("@id "~ReturnType!(ConvFuns[findIndex(fname)]).stringof ~ " " ~ fname ~ ";"); 64 } 65 else 66 { 67 mixin(ReturnType!(ConvFuns[findIndex(fname)]).stringof ~ " " ~ fname ~ ";"); 68 } 69 } 70 else 71 { 72 static if (hasUDA!(__traits(getMember, Base, fname), id)) 73 { 74 mixin("@id "~FTypes[idx].stringof~" "~fname~";"); 75 } 76 else 77 { 78 mixin(FTypes[idx].stringof~" "~fname~";"); 79 } 80 } 81 } 82 83 this() {} 84 85 this(Base base) 86 { 87 canonicalize(base); 88 } 89 90 this(in Node node, in LoadingContext context = LoadingContext.init) 91 { 92 auto base = new Base(node, context); 93 canonicalize(base); 94 } 95 96 mixin genToString; 97 mixin genIdentifier; 98 99 final void canonicalize(Base base) 100 { 101 static foreach(fname; FNames) 102 { 103 static if (findIndex(fname) != -1) 104 { 105 { 106 alias conv = ConvFuns[findIndex(fname)]; 107 __traits(getMember, this, fname) = conv(__traits(getMember, base, fname)); 108 } 109 } 110 else static if (!isConstantMember!(Base, fname)) 111 { 112 __traits(getMember, this, fname) = __traits(getMember, base, fname); 113 } 114 } 115 } 116 } 117 118 unittest 119 { 120 import salad.context : LoadingContext; 121 import std.conv : to; 122 import dyaml : Node, Loader; 123 124 static class C 125 { 126 int foo_; 127 string str_; 128 129 this(Node node, in LoadingContext context = LoadingContext.init) 130 { 131 foo_ = node["foo"].as!int; 132 str_ = node["str"].as!string; 133 } 134 } 135 136 static class Foo 137 { 138 mixin genCanonicalizeBody!( 139 C, 140 "foo", (int i) => i.to!string, 141 "str", (string s) => 0, 142 ); 143 } 144 145 enum ymlStr = q"EOS 146 foo: 10 147 str: "string" 148 EOS"; 149 150 auto foo = Loader.fromString(ymlStr).load.as!Foo; 151 assert(foo.foo_ == "10"); 152 assert(foo.str_ == 0); 153 } 154 155 unittest 156 { 157 import salad.context : LoadingContext; 158 import std.conv : to; 159 import dyaml : Node, Loader; 160 161 static class C 162 { 163 immutable class_ = "File"; 164 int foo_; 165 166 this() {} 167 this(Node node, in LoadingContext context = LoadingContext.init) 168 { 169 foo_ = node["foo"].as!int; 170 } 171 } 172 173 static class Foo 174 { 175 mixin genCanonicalizeBody!(C, "foo", (int i) => i.to!string); 176 } 177 178 enum ymlStr = q"EOS 179 foo: 10 180 EOS"; 181 182 auto foo = Loader.fromString(ymlStr).load.as!Foo; 183 assert(foo.foo_ == "10"); 184 assert(foo.class_ == "File"); 185 }