The OpenD Programming Language

1 /++
2 $(H1 Small String)
3 
4 The module contains self-contained generic small string implementaton.
5 
6 $(LREF SmallString) supports ASDF - Json Serialisation Library.
7 
8 See also `include/mir/small_series.h` for a C++ version of this type.
9 Both C++ and D implementations have the same ABI and name mangling.
10 
11 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments
12 Authors: Ilia Ki
13 +/
14 module mir.small_string;
15 
16 import mir.serde: serdeScoped, serdeProxy;
17 
18 private static immutable errorMsg = "Cannot create SmallString: input string exceeds maximum allowed length.";
19 version(D_Exceptions)
20     private static immutable exception = new Exception(errorMsg);
21 
22 /++
23 Self-contained generic Small String implementaton.
24 +/
25 extern(C++, "mir")
26 @serdeScoped @serdeProxy!(const(char)[])
27 struct SmallString(uint maxLength)
28     if (maxLength)
29 {
30 
31     import core.stdc.string: memcmp, memcpy;
32     import std.traits: Unqual, isIterable;
33 
34     // maxLength bytes
35     char[maxLength] _data = '\0';
36 
37 extern(D) @safe pure @nogc:
38 
39     /// Constructor
40     this(typeof(null)) scope
41     {
42     }
43 
44     /// ditto
45     this(scope const(char)[] str) scope
46     {
47         this.opAssign(str);
48     }
49 
50     /// ditto
51     this(uint n)(auto ref scope const SmallString!n str) scope
52     {
53         this.opAssign(str);
54     }
55 
56     /// ditto
57     this(Range)(auto ref scope Range str) scope
58         if (isIterable!Range)
59     {
60         size_t i = 0;
61         foreach(char c; str)
62         {
63             if (i > _data.length)
64             {
65                 version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; }
66                 else assert(0, errorMsg);
67             }
68             _data[i++] = c;
69         }
70     }
71 
72     /// `=` operator
73     ref typeof(this) opAssign(typeof(null)) scope return
74     {
75         _data = '\0';
76         return this;
77     }
78 
79     /// ditto
80     ref typeof(this) opAssign(scope const(char)[] str) scope return @trusted
81     {
82         _data = '\0';
83         if (str.length > _data.sizeof)
84         {
85             version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; }
86             else assert(0, errorMsg);
87         }
88         if (__ctfe)
89             _data[0 .. str.length] =  str;
90         else
91             memcpy(_data.ptr, str.ptr, str.length);
92         return this;
93     }
94 
95     /// ditto
96     ref typeof(this) opAssign(uint n)(auto ref scope const SmallString!n rhs) scope return
97         if (n != maxLength)
98     {
99         static if (n < maxLength)
100         {
101             _data = '\0';
102             version (LDC)
103                 cast(char[n])(_data[0 .. n]) = rhs._data;
104             else
105                 _data[0 .. n] = rhs._data;
106         }
107         else
108         {
109             if (rhs._data[maxLength])
110             {
111                 version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; }
112                 else assert(0, errorMsg);
113             }
114             _data = cast(char[maxLength])(rhs._data[0 .. maxLength]);
115         }
116         return this;
117     }
118 
119     /// ditto
120     ref typeof(this) opAssign(uint n)(const SmallString!n rhs) scope return
121         if (n != maxLength)
122     {
123         static if (n < maxLength)
124         {
125             version (LDC)
126                 cast(char[n])(_data[0 .. n]) = rhs._data;
127             else
128                 _data[0 .. n] = rhs._data;
129             _data[n .. maxLength] = '\0';
130         }
131         else
132         {
133             if (rhs._data[maxLength])
134             {
135                 version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; }
136                 else assert(0, errorMsg);
137             }
138             _data = cast(char[maxLength])(rhs._data[0 .. maxLength]);
139         }
140         return this;
141     }
142 
143     /// ditto
144     void trustedAssign(scope const(char)[] str) return @trusted nothrow
145     {
146         _data = '\0';
147         if (__ctfe)
148             _data[0 .. str.length] =  str;
149         else
150             memcpy(_data.ptr, str.ptr, str.length);
151     }
152 
153     ///
154     ref typeof(this) append(char c) @trusted
155     {
156         auto length = opIndex.length;
157         if (length == maxLength)
158         {
159             version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; }
160             else assert(0, errorMsg);
161         }
162         _data[length] = c;
163         return this;
164     }
165 
166     ///
167     ref typeof(this) append(scope const(char)[] str) @trusted
168     {
169         auto length = opIndex.length;
170         if (length + str.length > maxLength)
171         {
172             version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; }
173             else assert(0, errorMsg);
174         }
175         if (__ctfe)
176             _data[length .. str.length + length] = str;
177         else
178             memcpy(_data.ptr + length, str.ptr, str.length);
179         return this;
180     }
181 
182     /// ditto
183     alias put = append;
184 
185     /// ditto
186     alias opOpAssign(string op : "~") = append;
187 
188     ///
189     SmallString concat(scope const(char)[] str) scope const
190     {
191         SmallString c = this;
192         c.append(str);
193         return c; 
194     }
195 
196     /// ditto
197     alias opBinary(string op : "~") = concat;
198 
199     ///
200     extern (D) size_t toHash() const nothrow @safe
201     {
202         return hashOf(this[]);
203     }
204 
205 scope nothrow:
206 
207     /++
208     Returns an scope common string.
209 
210     The method implement with `[]` operation.
211     +/
212     inout(char)[] opIndex() inout @trusted scope return
213     {
214         import mir.string: scanLeftAny;
215         return _data[0 .. $ - _data.scanLeftAny('\0').length];
216     }
217 
218     ///
219     ref inout(char) opIndex(size_t index) inout scope return
220     {
221         return opIndex[index];
222     }
223 
224     /++
225     Returns a common scope string.
226 
227     The method implement with `[i .. j]` operation.
228     +/
229     inout(char)[] opIndex(size_t[2] range) inout @trusted scope return
230     in (range[0] <= range[1])
231     in (range[1] <= this.length)
232     {
233         return opIndex()[range[0] .. range[1]];
234     }
235 
236 scope const:
237 
238     /// ditto
239     size_t[2] opSlice(size_t pos : 0)(size_t i, size_t j) {
240         return [i, j];
241     }
242 
243     ///
244     bool empty() @property
245     {
246         return _data[0] == 0;
247     }
248 
249     ///
250     size_t length() @property
251     {
252         return opIndex.length;
253     }
254 
255     /// ditto
256     alias opDollar(size_t pos : 0) = length;
257 
258     ///
259     alias toString = opIndex;
260 
261     /// Comparisons operator overloads
262     bool opEquals(ref scope const SmallString rhs)
263     {
264         return _data == rhs._data;
265     }
266 
267     /// ditto
268     bool opEquals(SmallString rhs)
269     {
270         return _data == rhs._data;
271     }
272 
273     /// ditto
274     bool opEquals(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs)
275         if (rhsMaxLength != maxLength)
276     {
277         return opIndex == rhs.opIndex;
278     }
279 
280     /// ditto
281     bool opEquals()(scope const(char)[] str)
282     {
283         return opIndex == str;
284     }
285 
286     /// ditto
287     int opCmp(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs)
288     {
289         return __cmp(opIndex, rhs.opIndex);
290     }
291 
292     /// ditto
293     int opCmp()(scope const(char)[] str)
294     {
295         return __cmp(opIndex, str);
296     }
297 }
298 
299 ///
300 @safe pure @nogc version(mir_test) unittest
301 {
302     SmallString!16 s16;
303     assert(s16.empty);
304 
305     auto s8 = SmallString!8("Hellow!!");
306     assert(s8 == "Hellow!!");
307     assert(s8[] == "Hellow!!");
308     assert(s8[0 .. $] == "Hellow!!");
309     assert(s8[1 .. 4] == "ell");
310 
311     s16 = s8;
312     assert(s16 == "Hellow!!");
313     s16[7] = '@';
314     s8 = null;
315     assert(s8.empty);
316     s8 = s16;
317     assert(s8 == "Hellow!@");
318 
319     auto s8_2 = s8;
320     assert(s8_2 == "Hellow!@");
321     assert(s8_2 == s8);
322 
323     assert(s8 < "Hey");
324     assert(s8 > "Hellow!");
325 
326     assert(s8.opCmp("Hey") < 0);
327     assert(s8.opCmp(s8) == 0);
328 }
329 
330 /// Concatenation
331 @safe pure @nogc version(mir_test) unittest
332 {
333     auto a = SmallString!16("asdf");
334     a ~= " ";
335     auto b = a ~ "qwerty";
336     static assert(is(typeof(b) == SmallString!16));
337     assert(b == "asdf qwerty");
338     b.put('!');
339     b.put("!");
340     assert(b == "asdf qwerty!!");
341 }
342 
343 @safe pure @nogc nothrow version(mir_test) unittest
344 {
345     import mir.conv: emplaceRef;
346     SmallString!32 a, b;
347     emplaceRef!(const SmallString!32)(a, cast(const)b);
348 }