1 // this code implements the ability to extend messages
2 module dprotobuf.pbextension;
3 import dprotobuf.pbchild;
4 import dprotobuf.pbgeneral;
5 import std..string;
6 import std.stdio;
7 
8 struct PBExtension {
9 	string name;
10 	PBChild[]children;
11 
12 	// string-modifying constructor
13 	static PBExtension opCall(ref ParserData pbstring)
14 	in {
15 		assert(pbstring.length);
16 	} body {
17 		// things we currently support in a message: messages, enums, and children(repeated, required, optional)
18 		// first things first, rip off "message"
19 		pbstring = pbstring["extend".length..pbstring.length];
20 		// now rip off the next set of whitespace
21 		pbstring = stripLWhite(pbstring);
22 		// get message name
23 		string name = stripValidChars(CClass.MultiIdentifier,pbstring);
24 		PBExtension exten;
25 		exten.name = name;
26 		// rip off whitespace
27 		pbstring = stripLWhite(pbstring);
28 		// make sure the next character is the opening {
29 		if (pbstring[0] != '{') {
30 			throw new PBParseException("Message Definition","Expected next character to be '{'. You may have a space in your message name: "~name, pbstring.line);
31 		}
32 		// rip off opening {
33 		pbstring = pbstring[1..pbstring.length];
34 		// prep for loop spinup by removing extraneous whitespace
35 		pbstring = stripLWhite(pbstring);
36 		// now we're ready to enter the loop and parse children
37 		while(pbstring[0] != '}') {
38 			// start parsing, we shouldn't have any whitespace here
39 			exten.children ~= PBChild(pbstring);
40 			// this needs to stay at the end
41 			pbstring = stripLWhite(pbstring);
42 		}
43 		// rip off the }
44 		pbstring = pbstring[1..pbstring.length];
45 		return exten;
46 	}
47 }
48 
49 unittest {
50 	auto instr =
51 ParserData("extend Foo {
52 	optional clunker blah = 1;
53 }
54 ");
55 	writefln("unittest ProtocolBuffer.pbextension");
56 	auto exten = PBExtension(instr);
57 	debug writefln("Checking PBExtension class correctness");
58 	assert(exten.name == "Foo","Class to be extended is incorrect");
59 	debug writefln("Checking PBExtension child correctness");
60 	assert(exten.children[0].name == "blah","Parsed child is incorrect");
61 	assert(exten.children[0].modifier == "optional","Parsed child is incorrect");
62 	assert(exten.children[0].type == "clunker","Parsed child is incorrect");
63 	assert(exten.children[0].index == 1,"Parsed child is incorrect");
64 	debug writefln("");
65 }
66 
67 import dprotobuf.pbmessage;
68 auto insertExtension(PBMessage pbmsg, PBExtension ext) {
69 	assert(pbmsg.name == ext.name, "Extensions apply to a specific message; " ~ pbmsg.name ~ " != " ~ ext.name);
70 	import std.conv;
71 	foreach(echild;ext.children) {
72 		bool extmatch = false;
73 		foreach(exten;pbmsg.exten_sets) {
74 			if (echild.index <= exten.max && echild.index >= exten.min) {
75 				extmatch = true;
76 				break;
77 			}
78 		}
79 		if (!extmatch) throw new Exception("The field number "~to!(string)(echild.index)~" for extension "~echild.name~" is not within a valid extension range for "~pbmsg.name);
80 	}
81 
82 	// now check each child vs each extension already applied to see if there are conflicts
83 	foreach(dchild; pbmsg.child_exten) foreach(echild; ext.children) {
84 		if (dchild.index == echild.index) throw new Exception("Extensions "~dchild.name~" and "~echild.name~" to "~pbmsg.name~" have identical index number "~to!(string)(dchild.index));
85 	}
86 	pbmsg.child_exten ~= ext.children;
87 
88 	return pbmsg;
89 }
90 
91 unittest {
92 	auto foo =
93 ParserData("message Foo {
94 	optional int de = 5;
95 	extensions 1 to 4;
96 }
97 ");
98 	auto extFoo =
99 ParserData("extend Foo {
100 	optional int blah = 1;
101 }
102 ");
103 
104 	auto Foo = PBMessage(foo);
105 	auto ExtFoo = PBExtension(extFoo);
106 
107 	auto m = insertExtension(Foo, ExtFoo);
108 }