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 }