1 module mutils.container.string_builder;
2 
3 import std.meta;
4 import std.stdio;
5 import std.traits;
6 
7 import mutils.container.string_intern;
8 import mutils.container.string_tmp;
9 import mutils.container.vector;
10 import mutils.conv : num2str;
11 import mutils.safe_union;
12 
13 struct StringBuilder {
14 
15     alias ElementData = SafeUnion!(true, char, string, const(char)[], StringIntern, long, double);
16     static struct Element {
17         ElementData data;
18     }
19 
20     Vector!Element elements;
21 
22     this(Args...)(Args args) {
23         elements.reserve(args.length);
24         foreach (i, ref arg; args) {
25             addNewElement(arg, elements);
26         }
27     }
28 
29     void reserve(size_t size) {
30         elements.reserve(size);
31     }
32 
33     StringBuilder opBinaryRight(string op, T)(T lhs) {
34         static assert(op == "~", "Only concatenation operator is supported");
35         StringBuilder newBuilder;
36         newBuilder.elements.reserve(elements.length + 1);
37         addNewElement(lhs, newBuilder.elements);
38         newBuilder.elements ~= elements[];
39         return newBuilder;
40     }
41 
42     StringBuilder opBinary(string op, T)(T lhs) {
43         static assert(op == "~", "Only concatenation operator is supported");
44         StringBuilder newBuilder;
45         newBuilder.elements.reserve(elements.length + 1);
46         newBuilder.elements ~= elements[];
47         addNewElement(lhs, newBuilder.elements);
48         return newBuilder;
49     }
50 
51     private void addNewElement(T)(T rhs, ref Vector!Element arrToAdd) {
52         static assert(isProperType!T, "Concatenation of given type not supported");
53 
54         static if (is(T == char[])) {
55             auto rhsValue = cast(const(char)[]) rhs;
56         } else static if (isIntegral!T) {
57             auto rhsValue = cast(long) rhs;
58         } else static if (isFloatingPoint!T) {
59             auto rhsValue = cast(double) rhs;
60         } else {
61             auto rhsValue = rhs;
62         }
63 
64         Element element = Element(ElementData(rhsValue));
65         arrToAdd ~= element;
66     }
67 
68     size_t getRequiredSize() {
69         size_t size;
70         foreach (ref e; elements) {
71             switch (e.data.currentType) {
72             case ElementData.getEnum!char:
73                 size += 1;
74                 break;
75             case ElementData.getEnum!string:
76                 string str = *e.data.get!string;
77                 size += str.length;
78                 break;
79             case ElementData.getEnum!(const(char)[]):
80                 const(char)[] str = *e.data.get!(const(char)[]);
81                 size += str.length;
82                 break;
83             case ElementData.getEnum!StringIntern:
84                 StringIntern str = *e.data.get!StringIntern;
85                 size += str.length;
86                 break;
87             case ElementData.getEnum!long:
88                 char[64] buff;
89                 long num = *e.data.get!long;
90                 string str = num2str(num, buff[]);
91                 size += str.length;
92                 break;
93             case ElementData.getEnum!double:
94                 char[64] buff;
95                 double num = *e.data.get!double;
96                 string str = num2str(num, buff[]);
97                 size += str.length;
98                 break;
99             default:
100                 break;
101             }
102         }
103         return size + 1;
104     }
105 
106     StringTmp getStringTmp(char[] buffer = null) {
107         size_t charsAdded;
108 
109         size_t requiredSize = getRequiredSize();
110         if (buffer.length < requiredSize) {
111             buffer = StringTmp.allocateStr(requiredSize);
112         }
113 
114         foreach (ref e; elements) {
115             switch (e.data.currentType) {
116             case ElementData.getEnum!char:
117                 buffer[charsAdded] = *e.data.get!char;
118                 charsAdded++;
119                 break;
120             case ElementData.getEnum!string:
121                 string str = *e.data.get!string;
122                 buffer[charsAdded .. charsAdded + str.length] = str;
123                 charsAdded += str.length;
124                 break;
125             case ElementData.getEnum!(const(char)[]):
126                 const(char)[] str = *e.data.get!(const(char)[]);
127                 buffer[charsAdded .. charsAdded + str.length] = str;
128                 charsAdded += str.length;
129                 break;
130             case ElementData.getEnum!StringIntern:
131                 StringIntern str = *e.data.get!StringIntern;
132                 buffer[charsAdded .. charsAdded + str.length] = str.str();
133                 charsAdded += str.length;
134                 break;
135             case ElementData.getEnum!long:
136                 char[64] buff;
137                 long num = *e.data.get!long;
138                 string str = num2str(num, buff[]);
139                 buffer[charsAdded .. charsAdded + str.length] = str;
140                 charsAdded += str.length;
141                 break;
142             case ElementData.getEnum!double:
143                 char[64] buff;
144                 double num = *e.data.get!double;
145                 string str = num2str(num, buff[]);
146                 buffer[charsAdded .. charsAdded + str.length] = str;
147                 charsAdded += str.length;
148                 break;
149             default:
150                 break;
151             }
152         }
153         buffer[requiredSize - 1] = '\0';
154         if (buffer.length < requiredSize) {
155             return StringTmp(buffer, true);
156         }
157         return StringTmp(buffer[0 .. requiredSize], false);
158     }
159 
160     static bool isProperType(T)() {
161         enum index = is(T == char[]) || isNumeric!T || staticIndexOf!(T, ElementData.FromTypes);
162         return index != -1;
163     }
164 
165 }
166 
167 unittest {
168     char[4] chars = ['h', 'a', 's', ' '];
169     char[4] chars2 = ['h', 'a', 'v', 'e'];
170     char[256] buffer;
171     StringBuilder str = StringBuilder("String ") ~ chars[] ~ 'n' ~ 'o' ~ StringIntern(
172             " power ") ~ 2 ~ " be m" ~ 8.0f;
173 
174     auto tmpStr = str.getStringTmp(buffer);
175     assert(tmpStr.cstr == "String has no power 2 be m8\0");
176     assert(StringBuilder("You ", chars2[], " failed ", 10, " times.")
177             .getStringTmp(buffer).str == "You have failed 10 times.");
178 }