diff --git a/import/dproto/attributes.d b/import/dproto/attributes.d index 3307fab..799b2b0 100644 --- a/import/dproto/attributes.d +++ b/import/dproto/attributes.d @@ -15,13 +15,21 @@ import std.typecons : Nullable; struct ProtoField { string wireType; + string wireKeyType; ubyte fieldNumber; @disable this(); this(string w, ubyte f) { wireType = w; + wireKeyType = ""; + fieldNumber = f; + } + this(string w, string k, ubyte f) { + wireType = w; + wireKeyType = k; fieldNumber = f; } @nogc auto header() { + auto mt = wireKeyType != "" ? "map".msgType : wireType.msgType; return (wireType.msgType | (fieldNumber << 3)); } } diff --git a/import/dproto/intermediate.d b/import/dproto/intermediate.d index b61839e..e60bea1 100644 --- a/import/dproto/intermediate.d +++ b/import/dproto/intermediate.d @@ -177,15 +177,17 @@ struct Field { REPEATED, REQUIRED } - this(Requirement labelEnum, string type, string name, uint tag, Options options) { + this(Requirement labelEnum, string type, string keyType, string name, uint tag, Options options) { this.requirement = labelEnum; this.type = type; + this.keyType = keyType; this.name = name; this.id = tag; this.options = options; } Requirement requirement; string type; + string keyType; string name; uint id; Options options; @@ -197,6 +199,10 @@ struct Field { return options["default"]; } + const bool isMap() { + return keyType != null; + } + const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) { switch(fmt.spec) { @@ -231,7 +237,11 @@ struct Field { } } sink("@(dproto.attributes.ProtoField"); - sink.formattedWrite(`("%s", %s)`, type, id); + if (isMap) { + sink.formattedWrite(`("%s", "%s", %s)`, type, keyType, id); + } else { + sink.formattedWrite(`("%s", %s)`, type, id); + } sink(")\n"); bool wrap_with_nullable = @@ -245,6 +255,9 @@ struct Field { if(type.isBuiltinType) { typestr = format(`BuffType!"%s"`, type); } + if (isMap) { + typestr = format(`%s[BuffType!"%s"]`, typestr, keyType); + } sink(typestr); diff --git a/import/dproto/parse.d b/import/dproto/parse.d index e3a7cf5..67ced54 100644 --- a/import/dproto/parse.d +++ b/import/dproto/parse.d @@ -177,7 +177,26 @@ ProtoPackage ParseProtoSchema(const string name_, string data_) case "repeated": { static if( hasMember!(Context, "fields") ) { string type = readSymbolName(context); - auto newfield = readField(label, type, context); + auto newfield = readField(context, label, type); + unexpected(context.fields.all!(a => a.id != newfield.id)(), + "Repeated field ID"); + context.fields ~= newfield; + return; + } else { + throw new DProtoSyntaxException("Fields must be nested"); + } + } + case "map": { + static if( hasMember!(Context, "fields") ) { + unexpected(readChar() == '<', "Expected '<'"); + string keyType = readSymbolName(context); + if (!isBuiltinType(keyType)) { + throw new DProtoSyntaxException("Key type must be builtin type, not '" ~ keyType ~ "'"); + } + unexpected(readChar() == ',', "Expected ','"); + auto valueType = readSymbolName(context); + unexpected(readChar() == '>', "Expected '>'"); + auto newfield = readField(context, "optional", valueType, keyType); unexpected(context.fields.all!(a => a.id != newfield.id)(), "Repeated field ID"); context.fields ~= newfield; @@ -186,7 +205,6 @@ ProtoPackage ParseProtoSchema(const string name_, string data_) throw new DProtoSyntaxException("Fields must be nested"); } } - case "map": case "oneof": { throw new DProtoSyntaxException("'" ~ label ~ "' not yet implemented"); } @@ -215,7 +233,7 @@ ProtoPackage ParseProtoSchema(const string name_, string data_) else static if (hasMember!(Context, "fields")) { string type = reservedName(context, label); - auto newfield = readField("optional", type, context); + auto newfield = readField(context, "optional", type); unexpected(context.fields.all!(a => a.id != newfield.id)(), "Repeated field ID"); context.fields ~= newfield; @@ -332,7 +350,7 @@ ProtoPackage ParseProtoSchema(const string name_, string data_) } /** Reads a field declaration and returns it. */ - Field readField(Context)(string label, string type, Context context) { + Field readField(Context)(Context context, string label, string type, string keyType = "") { Field.Requirement labelEnum = label.toUpper().to!(Field.Requirement)(); string name = readSymbolName(context); unexpected(readChar() == '=', "Expected '='"); @@ -351,7 +369,7 @@ ProtoPackage ParseProtoSchema(const string name_, string data_) if (labelEnum != Field.Requirement.REPEATED && options.get("packed", "false") != "false") { throw new DProtoSyntaxException("[packed = true] can only be specified for repeated primitive fields"); } - return Field(labelEnum, type, name, tag, options); + return Field(labelEnum, type, keyType, name, tag, options); } throw new DProtoSyntaxException("Expected ';'"); } diff --git a/import/dproto/unittests.d b/import/dproto/unittests.d index df5c730..60caba2 100644 --- a/import/dproto/unittests.d +++ b/import/dproto/unittests.d @@ -440,6 +440,14 @@ unittest } } +unittest +{ + mixin ProtocolBufferFromString!" + message Person { + map telephone = 1; + }"; +} + unittest { mixin ProtocolBufferFromString!"