1 module osc.oscstring;
2 
3 ///
4 T addNullSuffix(T:string)(T str){
5     size_t nullCharacters = 4-str.length%4;
6     if(nullCharacters == 0)return str;
7     import std.range;
8     import std.conv;
9     import std.algorithm;
10     return str ~ ('\0'.repeat(nullCharacters).array).map!(c => cast(immutable(char))c).array;
11 }
12 
13 ///
14 T addNullSuffix(T:ubyte[])(T str){
15     return cast(T)addNullSuffix(cast(string)str);
16 }
17 
18 unittest{
19     assert("osc".addNullSuffix == "osc\0");
20     assert("data".addNullSuffix == "data\0\0\0\0");
21     assert(",iisff".addNullSuffix == ",iisff\0\0");
22     assert(",i".addNullSuffix == ",i\0\0");
23 }
24 
25 /++
26 +/
27 struct OscString(char P){
28     public{
29         ///
30         enum Prefix = P;
31         
32         ///
33         this(in int v){
34             import std.bitmanip;
35             ubyte[] buffer = [0, 0, 0, 0];
36             buffer.write!int(v, 0);
37             this(buffer);
38         }
39         
40         ///
41         this(in float v){
42             import std.bitmanip;
43             ubyte[] buffer = [0, 0, 0, 0];
44             buffer.write!float(v, 0);
45             this(buffer);
46         }
47         
48         ///
49         this(in string str)
50         in{
51             import std.algorithm;
52             assert(!str.canFind("\0"));
53             assert(str != "");
54         }out{
55             assert(_data.length%4 == 0);
56         }body{
57             import std.conv;
58             import std.algorithm;
59             import std.array;
60             ubyte[] arr = str.map!(c => c.to!char.to!ubyte).array;
61             this(arr);
62         }
63         unittest{
64             import core.exception, std.exception;
65             assertThrown!AssertError(OscString("\0string\0mixed\0null\0"));
66             assertThrown!AssertError(OscString(""));
67         }
68         
69         ///
70         this(in ubyte[] arr)
71         in{
72             import std.algorithm;
73             // assert(!arr.canFind(null));
74             assert(arr.length > 0);
75         }body{
76             if(Prefix != '\0'){
77                 import std.conv;
78                 _data = Prefix.to!ubyte ~ _data;
79             }
80             
81             foreach (ref c; arr) {
82                 _data ~= c;
83             }
84             _data = _data.addNullSuffix;
85         }
86         ///
87         string toString()const{
88             import std.conv:to;
89             import std.algorithm;
90             return _data.stripRight(0).map!(c => c.to!char).to!string;
91         }
92 
93         unittest{
94             ubyte[] buffer = [0x66, 0x6f, 0x6f];
95             import std.stdio;
96             import std.conv;
97             assert(OscString!('\0')(buffer).to!string == "foo");
98             assert(OscString!('\0')(buffer).size == 4);
99         }
100 
101         unittest{
102             auto oscString = OscString!('\0')("osc");
103             import std.stdio;
104             import std.conv;
105             assert(oscString.to!string == "osc");
106         }
107 
108         unittest{
109             import std.conv;
110             auto oscString = OscString!('\0')("data");
111             assert(oscString.to!string == "data");
112         }
113         
114         ///
115         ubyte[] opCast(T:ubyte[])()const{
116             return _data.dup;
117         }
118         
119         ///
120         T opCast(T:int)()const if(Prefix == '\0'){
121             import std.bitmanip;
122             return _data.peek!T();
123         }
124         
125         ///
126         T opCast(T:float)()const if(Prefix == '\0'){
127             import std.bitmanip;
128             return _data.peek!T();
129         }
130 
131         T opBinary(string op:"~", T)(in T rhs)const{
132             import std.algorithm;
133             import std.conv;
134             ubyte[] stripedLhsData = this._data.dup.stripRight(0);
135             ubyte[] stripedRhsData = rhs._data.dup.stripRight(0);
136             T result;
137             result._data = (stripedLhsData ~ stripedRhsData).addNullSuffix;
138             return result;
139         }
140         
141         unittest{
142             const oscStringLhs = OscString!('/')("foo");
143             const oscStringRhs = OscString!('/')("barbaz");
144             import std.stdio;
145             import std.conv;
146             assert((oscStringLhs ~ oscStringRhs).to!string == "/foo/barbaz");
147         }
148         // T convert(T)(in AddressPattern pattern){
149         //     string joinedString = pattern.map!(p => p.to!string).reduce!((a, b)=>a~b);
150         //     return OscString;
151         // }
152 
153         ///
154         bool isEmpty()const{
155             import std.algorithm;
156             import std.conv;
157             ubyte prefix = P.to!ubyte;
158             if(prefix != 0){
159                 return _data.length == 0;
160             }else{
161                 return _data.stripLeft(prefix).length == 0;
162             }
163         }
164         
165         ///
166         size_t size()const{
167             return _data.length;
168         }
169         
170         unittest{
171             import std.conv;
172             auto oscString = OscString!('\0')("data");
173             assert(oscString.size == 8);
174         }
175 
176     }//public
177 
178     private{
179         ubyte[] _data;
180     }//private
181 }//struct OscString
182         
183 
184 unittest{
185     const ubyte[] buffer = [0x00, 0x00, 0x6f, 0x00];
186     import std.conv;
187     assert(OscString!('\0')(buffer).to!int == 28416);
188 }
189 
190 unittest{
191     const ubyte[] buffer = [0x3f, 0x9d, 0xf3, 0xb6];
192     import std.conv;
193     import std.math;
194     assert(approxEqual(OscString!('\0')(buffer).to!float, 1.234));
195 }
196 
197 ///
198 OscString!('\0') OscString(T)(in T v){
199     return OscString!('\0')(v);
200 }
201 
202 ///
203 template isOscString(S){
204     enum bool isOscString = __traits(compiles, (){
205         S oscString = OscString!(S.Prefix)();
206     });
207 }
208 
209 unittest{
210     static assert(isOscString!(OscString!('a')));
211     static assert(isOscString!(OscString!('\n')));
212     static assert(!isOscString!(string));
213 }
214 
215 ///
216 string content(S)(in S oscString)if(isOscString!(S)){
217     import std.string;
218     import std.conv;
219     string str = oscString.to!string.replace("\0", "");
220     if(S.Prefix != '\0'){
221         return str[1..$];
222     }else{
223         return str;
224     }
225     // return oscString._data
226 }
227 
228 unittest{
229     import std.string;
230     assert(OscString!('\0')("data").content == "data");
231     assert(OscString!('/')("data").content == "data");
232 }