1 /// Code generation module. 2 module pbd.codegen; 3 4 import std.meta : AliasSeq; 5 6 import pegged.grammar : ParseTree; 7 8 9 /// string dict converts Proto types to D types 10 /// See also: https://developers.google.com/protocol-buffers/docs/proto#scalar 11 enum Proto2DTypes = [ 12 "double": "double", 13 "float": "float", 14 "int32": "int", 15 "int64": "long", 16 "uint32": "uint", 17 "uint64": "ulong", 18 "sint32": "@ZigZag int", 19 "sint64": "@ZigZag long", 20 "fixed32": "uint", 21 "fixed64": "ulong", 22 "sfixed32": "int", 23 "sfixed64": "long", 24 "bool": "bool", 25 "string": "ProtoArray!char", 26 "bytes": "ProtoArray!byte", 27 ]; 28 29 struct ProtoTag 30 { 31 /// Tag number for encoding/decoding 32 int tag = 0; 33 } 34 35 /// Returns tag of a given member or 0 if not found. 36 int protoTagOf(T, string member)() 37 { 38 import std.traits : getUDAs; 39 enum udas = getUDAs!(__traits(getMember, T, member), ProtoTag); 40 static assert(udas.length == 1); 41 return udas[0].tag; 42 } 43 44 struct ZigZag {}; 45 struct Unpacked {}; 46 47 /// Returns true if member is zigzag encoded. 48 bool isZigZag(T, string member)() 49 { 50 import std.traits : hasUDA; 51 return hasUDA!(__traits(getMember, T, member), ZigZag); 52 } 53 54 /// Returns a tagged member name corresponding to a given tag. 55 string protoMemberOf(T)(int tag) 56 { 57 foreach (name; __traits(allMembers, T)) 58 { 59 alias m = __traits(getMember, T, name); 60 foreach (attr; __traits(getAttributes, m)) 61 { 62 static if (is(typeof(attr) == ProtoTag)) 63 { 64 if (attr.tag == tag) 65 { 66 return name; 67 } 68 } 69 } 70 } 71 assert(false, "@ProtoTag not found."); 72 } 73 74 /// 75 version (pbd_test) 76 unittest 77 { 78 struct Test 79 { 80 @ProtoTag(2) 81 int i; 82 @ZigZag 83 @ProtoTag(1) 84 int j; 85 } 86 87 static assert(protoTagOf!(Test, "i") == 2); 88 static assert(!isZigZag!(Test, "i")); 89 static assert(protoMemberOf!Test(2) == "i"); 90 static assert(isZigZag!(Test, "j")); 91 } 92 93 /// Generates D code from Protobuf IDL ParseTree (Proto result). 94 string toD(ParseTree p, int numIndent = 0, string indent = " ") 95 { 96 import std.range : repeat, join; 97 98 auto spaces = indent.repeat(numIndent).join; 99 switch (p.name) 100 { 101 case "Proto": 102 assert(p.children.length == 1, "Proto should have only one child."); 103 return "import pbd.codegen;\n" ~ toD(p.children[0], numIndent, indent); 104 case "Proto.Root": 105 string code; 106 foreach (child; p.children) 107 { 108 code ~= toD(child, numIndent, indent); 109 } 110 return code; 111 case "Proto.Syntax": 112 assert(p.matches[0] == "proto3", `only syntax = "proto3" is supported.`); 113 return ""; 114 case "Proto.Package": 115 // TODO(karita): support package? 116 return ""; 117 case "Proto.Option": 118 // TODO(karita): support option? 119 return ""; 120 case "Proto.Message": 121 string code = spaces ~ "struct " ~ p.matches[0] ~ " {\n"; 122 foreach (child; p.children) 123 { 124 code ~= toD(child, numIndent + 1, indent); 125 } 126 code ~= spaces ~ "}\n"; 127 return code; 128 case "Proto.SingleField": 129 // e.g., int32 a = 1; 130 auto type = Proto2DTypes[p.matches[0]]; 131 auto name = p.matches[1]; 132 auto tag = "@ProtoTag(" ~ p.matches[2] ~ ") "; 133 // [packed = true]; 134 auto packed = p.matches.length == 3 || 135 (p.matches[3] == "packed" && p.matches[4] == "true") 136 ? "" : "@Unpacked "; 137 return spaces ~ packed ~ tag ~ type ~ " " ~ name ~ ";\n"; 138 default: 139 assert(false, p.name ~ " unsupported"); 140 } 141 } 142 143 /// 144 version (pbd_test) 145 @nogc nothrow pure @safe 146 unittest 147 { 148 import std.stdio; 149 import pbd.parse : Proto; 150 151 enum exampleProto = ` 152 syntax = "proto3"; 153 154 package tensorflow; 155 156 message Foo { 157 int32 aa = 1; 158 sint32 bb = 2; 159 } 160 `; 161 162 // example of generated code 163 import pbd.codegen; 164 struct ExpectedFoo { 165 @ProtoTag(1) int aa; 166 @ZigZag @ProtoTag(2) int bb; 167 } 168 169 enum tree = Proto(exampleProto); 170 enum code = tree.toD(0, " "); 171 mixin(code); 172 173 static assert(is(typeof(Foo.aa) == int)); 174 static assert(protoTagOf!(Foo, "aa") == 1); 175 static assert(!isZigZag!(Foo, "aa")); 176 177 static assert(is(typeof(Foo.bb) == int)); 178 static assert(protoTagOf!(Foo, "bb") == 2); 179 static assert(isZigZag!(Foo, "bb")); 180 // writeln("generated:\n", code); 181 } 182 183 184 /// Custom array type for pb decoding 185 struct ProtoArray(T) 186 { 187 import std.container.array : Array; 188 189 Array!T base; 190 alias base this; 191 192 /// Converts to string for debugging 193 string toString() const 194 { 195 import std.conv : text; 196 return this.base[].text; 197 } 198 199 static if (is(T == char)) 200 { 201 /// Constructs from string 202 this(string rhs) 203 { 204 this.base.clear(); 205 this.base.reserve(rhs.length); 206 foreach (x; rhs) 207 { 208 this.base.insertBack(x); 209 } 210 } 211 212 /// Compares to non-string types 213 bool opEquals(Rhs)(auto ref Rhs rhs) 214 { 215 return this.base == rhs; 216 } 217 218 /// Compares to string 219 @nogc nothrow pure bool opEquals(Rhs : string)(Rhs rhs) 220 { 221 // if (this.length != rhs.length) return false; 222 // foreach (i; 0 .. this.base.length) 223 // { 224 // if (this.base[i] != rhs[i]) return false; 225 // } 226 // return true; 227 return (&this[0])[0 .. this.length] == rhs; 228 } 229 } 230 } 231 232 alias pstring = ProtoArray!char; 233 234 version (pbd_test) 235 @nogc nothrow pure 236 unittest 237 { 238 pstring cs = "abc"; 239 assert(cs == "abc"); 240 } 241 242 auto ProtoToD(string path)() 243 { 244 import pbd.parse : Proto; 245 return Proto(import(path)).toD; 246 }