1 module bc..string;
2 
3 public import bc.string.ascii;
4 public import bc.string.conv;
5 public import bc.string.format;
6 public import bc.string.numeric;
7 public import bc.string.string;
8 
9 import core.stdc.stdio;
10 
11 version (CI_MAIN)
12 {
13     // workaround for dub not supporting unittests with betterC
14     version (D_BetterC)
15     {
16         extern(C) int main()
17         {
18             version (unittest)
19             {
20                 import std.meta : AliasSeq;
21 
22                 alias modules = AliasSeq!(bc..string.ascii, bc..string.conv, bc..string.format, bc..string.numeric, bc..string.string);
23                 static foreach (m; modules)
24                 {
25                     static foreach(u; __traits(getUnitTests, m)) {
26                         static if (__traits(getAttributes, u).length)
27                             printf("unittest %s:%d | '" ~ __traits(getAttributes, u)[0] ~ "'\n", __traits(getLocation, u)[0].ptr, __traits(getLocation, u)[1]);
28                         else
29                             printf("unittest %s:%d\n", __traits(getLocation, u)[0].ptr, __traits(getLocation, u)[1]);
30                         u();
31                     }
32                 }
33                 debug printf("All unit tests have been run successfully.\n");
34                 return 0;
35             }
36             else return rtTest();
37         }
38 
39         // workaround for linking error: `undefined reference to `_D4core8internal5array8equality__T8__equalsTaTaZQoFNaNbNiNeMxAaMxQeZb'`
40         bool equals(S1, S2)(S1 a, S2 b)
41         {
42             if (a.length != b.length) return false;
43             import core.stdc.string : strncmp;
44             return strncmp(a.ptr, b.ptr, a.length) == 0;
45         }
46     }
47     else
48     {
49         int main()
50         {
51             version (unittest) return 0; // run automagically
52             else return rtTest();
53         }
54 
55         bool equals(S1, S2)(S1 a, S2 b) { return a == b; }
56     }
57 
58     int rtTest()()
59     {
60         // just a compilation test - same as in the README to be sure it actually works..
61 
62         import bc.core.memory;
63         import bc.internal.utf : byCodeUnit;
64         import core.stdc.string : strlen;
65         import std.algorithm : filter;
66         import std.range : chunks;
67 
68         assert(nogcFormat!"abcd abcd".equals("abcd abcd"));
69         assert(nogcFormat!"123456789a".equals("123456789a"));
70         version (D_NoBoundsChecks) {}
71         else version (D_BetterC) {}
72         else
73         {
74             () @trusted
75             {
76                 import core.exception : RangeError;
77                 import std.exception : assertThrown;
78                 char[5] buf;
79                 version (assert) assertThrown!RangeError(buf.nogcFormatTo!"123412341234");
80             }();
81         }
82 
83         // literal escape
84         assert(nogcFormat!"123 %%".equals("123 %"));
85         assert(nogcFormat!"%%%%".equals("%%"));
86 
87         // %d
88         assert(nogcFormat!"%d"(1234).equals("1234"));
89         assert(nogcFormat!"%4d"(42).equals("  42"));
90         assert(nogcFormat!"%04d"(42).equals("0042"));
91         assert(nogcFormat!"%04d"(-42).equals("-042"));
92         assert(nogcFormat!"ab%dcd"(1234).equals("ab1234cd"));
93         assert(nogcFormat!"ab%d%d"(1234, 56).equals("ab123456"));
94 
95         // %x
96         assert(nogcFormat!"0x%x"(0x1234).equals("0x1234"));
97 
98         // %p
99         assert(nogcFormat!("%p")(0x1234).equals("0000000000001234"));
100 
101         // %s
102         assert(nogcFormat!"12345%s"("12345").equals("1234512345"));
103         assert(nogcFormat!"12345%s"(12345).equals("1234512345"));
104         enum Floop {XXX, YYY, ZZZ}
105         assert(nogcFormat!"12345%s"(Floop.YYY).equals("12345YYY"));
106         char[4] str = "foo\0";
107         assert(nogcFormat!"%s"(&str[0]).equals("foo"));
108 
109         version (D_BetterC) {} // can't import std.uuid in betterC
110         else
111         {
112             import std.uuid;
113             assert(nogcFormat!"%s"(
114                 parseUUID("22390768-cced-325f-8f0f-cfeaa19d0ccd"))
115                 .equals("22390768-cced-325f-8f0f-cfeaa19d0ccd"));
116         }
117 
118         // array format
119         version (D_BetterC)
120         {
121             int[] arr = (cast(int*)enforceMalloc(int.sizeof*10))[0..10];
122             foreach (i; 0..10) arr[i] = i;
123             scope (exit) pureFree(arr.ptr);
124         }
125         else auto arr = [0,1,2,3,4,5,6,7,8,9];
126 
127         assert(nogcFormat!"foo %(%d %)"(arr[1..4]).equals("foo 1 2 3"));
128         assert(nogcFormat!"foo %-(%d %)"(arr[1..4]).equals("foo 1 2 3"));
129         assert(nogcFormat!"foo %(-%d-%|, %)"(arr[1..4]).equals("foo -1-, -2-, -3-"));
130         assert(nogcFormat!"%(0x%02x %)"(arr[1..4]).equals("0x01 0x02 0x03"));
131         assert(nogcFormat!"%(%(%d %)\n%)"(arr[1..$].chunks(3)).equals("1 2 3\n4 5 6\n7 8 9"));
132 
133         // range format
134         auto r = arr.filter!(a => a < 5);
135         assert(nogcFormat!"%s"(r).equals("[0, 1, 2, 3, 4]"));
136 
137         // Arg num
138         assert(!__traits(compiles, nogcFormat!"abc"(5)));
139         assert(!__traits(compiles, nogcFormat!"%d"()));
140         assert(!__traits(compiles, nogcFormat!"%d a %d"(5)));
141 
142         // Format error
143         assert(!__traits(compiles, nogcFormat!"%"()));
144         assert(!__traits(compiles, nogcFormat!"abcd%d %"(15)));
145         assert(!__traits(compiles, nogcFormat!"%$"(1)));
146         assert(!__traits(compiles, nogcFormat!"%d"("hello")));
147         assert(!__traits(compiles, nogcFormat!"%x"("hello")));
148 
149         assert(nogcFormat!"Hello %s"(5).equals("Hello 5"));
150 
151         struct Foo { int x, y; }
152         assert(nogcFormat!("Hello %s")(Foo(1, 2)).equals("Hello Foo(x=1, y=2)"));
153 
154         version (D_BetterC)
155         {
156             struct Nullable(T) // can't be instanciated in betterC - fake just for the UT
157             {
158                 T get() { return T.init; }
159                 bool isNull() { return true; }
160                 void nullify() {}
161             }
162         }
163         else import std.typecons : Nullable;
164         struct Msg { Nullable!string foo; }
165         assert(nogcFormat!"%s"(Msg.init).equals("Msg(foo=null)"));
166 
167         RCString s = "abcd";
168         assert(nogcFormat!"%s"(s).equals("abcd"));
169 
170         version (D_BetterC) {}
171         else version (linux)
172         {
173             import bc.core.system.backtrace : TraceInfo;
174             StringZ buf;
175             buf.nogcFormatTo!"where am i: %s"(TraceInfo.current);
176             printf("%s\n", buf.ptr);
177         }
178 
179         assert(numDigits(0) == 1);
180         assert(numDigits(11) == 2);
181         assert(numDigits(-1) == 2);
182 
183         assert(numDigits(int.min) == 11);
184         assert(numDigits(int.max) == 10);
185         assert(numDigits(long.min) == 20);
186         assert(numDigits(long.max) == 19);
187         assert(numDigits(ulong.min) == 1);
188         assert(numDigits(ulong.max) == 20);
189 
190         string str2 = "abc";
191 
192         // Intended usage
193         assert(strlen(str2.tempCString()) == 3);
194 
195         // Correct usage
196         auto tmp = str2.tempCString();
197         assert(strlen(tmp) == 3); // or `tmp.ptr`, or `tmp.buffPtr`
198 
199         // Incorrect usage
200         auto pInvalid1 = str2.tempCString().ptr;
201         const char* pInvalid2 = str2.tempCString();
202 
203         RCString rcs;
204         rcs ~= "fo";
205         rcs ~= 'o';
206         rcs ~= "bar";
207         rcs ~= "baz".byCodeUnit.filter!(a => a == 'z');
208         assert(rcs.length == "foobarz".length);
209         assert(rcs.data.equals("foobarz"));
210         assert(rcs.equals("foobarz"));
211         assert(rcs.ptr == &rcs.data[0]);
212         assert((rcs.ptr["foobarz".length]) == 0);
213 
214         // construction from write like params
215         rcs = RCString.from("foo", 42, "bar");
216         assert(rcs.equals("foo42bar"));
217 
218         auto ss = String("Hello");
219         assert(ss[].equals("Hello"), s[]);
220         ss ~= " String";
221         assert(ss[].equals("Hello String"), ss[]);
222         auto ss2 = ss.clone();
223         assert(ss[].equals(ss2[]));
224         assert(ss.ptr != ss2.ptr);
225 
226         auto ss3 = ss.move();
227         assert(ss.length == 0);
228         assert(ss3[].equals("Hello String"));
229 
230         enum indented = `
231                 SELECT foo, bar, baz
232                     FROM foos
233                     WHERE id=ANY($1) AND type_id IN (
234                         SELECT id FROM bars WHERE owner=$2
235                     )`;
236 
237         enum dedented =
238                     "SELECT foo, bar, baz\n" ~
239                     "    FROM foos\n" ~
240                     "    WHERE id=ANY($1) AND type_id IN (\n" ~
241                     "        SELECT id FROM bars WHERE owner=$2\n" ~
242                     ")";
243 
244         // assert(dedent(indented) == dedented);
245 
246         printf("test run completed\n");
247         return 0;
248     }
249 }