1 module as.addons.str;
2 import core.memory;
3 import std.stdio;
4 import std.conv : emplace;
5 import as;
6 import as.utils;
7 
8 
9 /*
10     String Factory
11     The string factory can use the GC because the strings are allocated via refcount
12 */
13 private extern(C) {
14     
15     // String references
16     struct StrRef {
17         int refs;
18         string data;
19     }
20 
21     // Small type that allows moving strings on to the GC heap
22     struct StrPtr { string data; }
23 
24     /**
25         The cache of constant values
26     */
27     StrRef[string] constantCache;
28 
29     // FACTORY
30     void* getStringConstant(const(char)* data, uint length) {
31         string text = (cast(string)data[0..length]).idup;
32 
33         // Handle adding references to strings
34         if (text !in constantCache) constantCache[text] = StrRef(1, text);
35         else constantCache[text].refs++;
36 
37         return cast(void*)&constantCache[text].data;
38     }
39 
40     int releaseStringConstant(const(void)* str) {
41         if (str is null) return ReturnCodes.Error;
42 
43         string text = *cast(string*)str;
44 
45         // Handle releasing strings
46         if (text !in constantCache) return ReturnCodes.Error;
47         else if (--constantCache[text].refs <= 0) constantCache.remove(text);
48 
49         return ReturnCodes.Success;
50     }
51 
52     int getRawStringData(const(void)* istr, char* data, uint* length) {
53         if (istr is null) return ReturnCodes.Error;
54 
55         string str = *cast(string*)istr;
56 
57         // If the length is not null set the length
58         if (length !is null)
59             *length = cast(uint)str.length;
60 
61         // If the data pointer is not null fill the data buffer
62         if (data !is null) data[0..str.length] = str[0..str.length];
63 
64         // We're done
65         return ReturnCodes.Success;
66     }
67 }
68 
69 /**
70     An AngelScript compatible string
71 */
72 struct ASString {
73 private:
74     size_t length_;
75     const(char)* str_;
76 
77 public:
78 
79     /**
80         Creates an empty string with specified length
81     */
82     this(size_t length) {
83         this.length_ = length;
84         this.str_ = cast(const(char)*)cMalloc(length);
85     }
86 
87     /**
88         Creates an ASString based on a D string
89 
90         GC memory will be copied out
91     */
92     this(string str) {
93 
94         this.str_ = cast(const(char)*)cMalloc(str.length);
95         this.length_ = str.length;
96 
97         // Copy memory from GC string to non-GC memory
98         cMemCopy(this.str_, str.ptr, str.length);
99     }
100 
101     /**
102         Creates an ASString based on a D string
103 
104         GC memory will be copied out
105     */
106     this(ASString str) {
107         this.length_ = str.length_;
108         this.str_ = cast(const(char)*)cMalloc(str.length_);
109 
110         // Copy memory from GC string to non-GC memory
111         cMemCopy(this.str_, str.str_, str.length_);
112     }
113 
114     /**
115         Disposes this ASString
116     */
117     void dispose() {
118         cFree(str_);
119     }
120 
121     /**
122         Overrides string assignment
123     */
124     void opAssign(ref string str) {
125 
126         // Free old memory if need be
127         if (length_ > 0) cFree(str_);
128 
129         this.str_ = cast(const(char)*)cMalloc(str.length);
130         this.length_ = str.length;
131 
132         // Copy memory from GC string to non-GC memory
133         cMemCopy(this.str_, str.ptr, str.length);
134     }
135 
136     /**
137         Overrides string assignment
138     */
139     void opAssign(ASString str) {
140 
141         // Free old memory if need be
142         if (length_ > 0) {
143             cFree(str_);
144         }
145 
146         this.str_ = cast(const(char)*)cMalloc(str.length_);
147         this.length_ = str.length_;
148 
149         // Copy memory from GC string to non-GC memory
150         cMemCopy(this.str_, str.str_, str.length_);
151     }
152 
153     /**
154         Returns a new string based on this and an other string
155     */
156     ASString opBinary(string op = "~")(ref ASString rhs) {
157 
158         // Allocates the ASString on the stack
159         ASString newStr = ASString(this.length_+rhs.length_);
160 
161         // Copy the data from both strings in order
162         cMemCopy(newStr.str_, str_, length_);
163         cMemCopy(newStr.str_+length_, rhs.str_, rhs.length_);
164         return newStr;
165     }
166 
167     /**
168         Allows appending strings
169     */
170     ASString opOpAssign(string op="~=")(ref ASString rhs) {
171         auto nstr = (this~rhs);
172 
173         // Data is copied
174         this = nstr;
175 
176         // Destruct the temporary string
177         destruct(nstr);
178         return this;
179     }
180 
181     void resize(size_t length) {
182 
183         // Cache old data so we can copy later
184         auto oldData = this.str_;
185 
186         // Realloate
187         this.str_ = cast(const(char)*)cMalloc(length);
188 
189         // Copy the string data from the old string based on whether the old or new size is the smallest
190         cMemCopy(this.str_, oldData, length < this.length_ ? length : length_);
191 
192         // Free the memory for the old data
193         cFree(oldData);
194     }
195     
196     ASString substr(uint start, uint count) {
197         ASString ret;
198 
199         if (start < length_ && count != 0) {
200 
201             // -1 = To end of string
202             if (count < 0 || start+count > length_) {
203                 count = cast(uint)length_-start;
204             }
205 
206             ret.length_ = count;
207             ret.str_ = cast(const(char)*)cMalloc(count);
208             cMemCopy(ret.str_, &str_[start], count);
209         }
210         return ret;
211     }
212 
213     /**
214         Gets the length of the string
215     */
216     size_t length() {
217         return length_;
218     }
219 
220     /**
221         Gets whether the string is empty
222     */
223     bool isEmpty() {
224         return length_ == 0;
225     }
226 
227     /**
228         Reinterprets this as a D string
229 
230         Do note that ASStrings are *not* garbage collected memory.
231     */
232     string toString() const {
233         return reinterpret_cast!string(this);
234     }
235 }
236 
237 private {
238     void asStringConstruct(ASString* memory) {
239         emplace!ASString(memory);
240         memory.length_ = 0;
241     }
242 
243     void asStringCopyConstruct(ref ASString other, ASString* memory) {
244         emplace!ASString(memory);
245         *memory = other;
246     }
247 
248     void asStringDestruct(ASString* memory) {
249         destruct(memory);
250     }
251 
252     ASString* asStringAssign(ASString* in_, ASString* this_) {
253         *this_ = *in_;
254         return this_;
255     }
256 
257     ASString* asStringAddAssign(ASString* in_, ASString* this_) {
258         *this_ ~= *in_;
259         return this_;
260     }
261 
262     ASString* asStringAdd(ref ASString this_, ref ASString rhs) {
263         // Allocates the ASString on the heap
264         ASString* newStr = cCreate!ASString(this_.length_+rhs.length_);
265 
266         // Copy the data from both strings in order
267         cMemCopy(newStr.str_, this_.str_, this_.length_);
268         cMemCopy(newStr.str_+this_.length_, rhs.str_, rhs.length_);
269         return newStr;
270     }
271 
272     ASString* asSubstr(uint start, uint count, ref ASString this_) {
273         // Allocates the ASString on the heap
274         auto substr = this_.substr(start, count);
275         scope(exit) substr.dispose();
276 
277         return cCreate!ASString(substr);
278     }
279 }
280 
281 /**
282     Allocates a string on the GC
283 */
284 string* StringPtr(string text) {
285     StrPtr* strref = new StrPtr(text);
286     return &strref.data;
287 }
288 
289 void registerDStrings(ScriptEngine engine) {
290     engine.registerObjectType("string", ASString.sizeof, TypeFlags.Value | TypeFlags.CDAK);
291     engine.registerStringFactory("string", &getStringConstant, &releaseStringConstant, &getRawStringData);
292 
293     // Register constructor and destructor
294     engine.registerObjectBehaviour("string", Behaviours.Construct, "void f()", &asStringConstruct, CallConv.DDeclObjLast);
295     engine.registerObjectBehaviour("string", Behaviours.Construct, "void f(const string &in)", &asStringCopyConstruct, CallConv.DDeclObjLast);
296     engine.registerObjectBehaviour("string", Behaviours.Destruct, "void f()", &asStringDestruct, CallConv.DDeclObjLast);
297 
298     engine.registerObjectMethod("string", "string &opAssign(const string &in)", &asStringAssign, CallConv.DDeclObjLast);
299     engine.registerObjectMethod("string", "string &opAddAssign(const string &in) const", &asStringAddAssign, CallConv.DDeclObjLast);
300     engine.registerObjectMethod("string", "string &opAdd(const string &in) const", &asStringAdd, CallConv.DDeclObjFirst);
301 
302     engine.registerObjectMethod("string", "uint length() const", &ASString.length, CallConv.DDeclObjFirst);
303     engine.registerObjectMethod("string", "bool isEmpty() const", &ASString.isEmpty, CallConv.DDeclObjFirst);
304     engine.registerObjectMethod("string", "uint resize(uint size) const", &ASString.resize, CallConv.DDeclObjFirst);
305     engine.registerObjectMethod("string", "string &substr(uint start = 0, uint count = -1) const", &asSubstr, CallConv.DDeclObjLast);
306 
307     // Conversion functionality
308     import std.conv : text;
309     engine.registerGlobalFunction("string &intToString(int64 value)", (long value) { return StringPtr(value.text); });
310     engine.registerGlobalFunction("string &uintToString(uint64 value)", (ulong value) { return StringPtr(value.text); });
311     engine.registerGlobalFunction("string &floatToString(double value)", (double value) { return StringPtr(value.text); });
312 }