Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Compare file prefix only.
[simgrid.git] / src / xbt / xbt_str.cpp
1 /* xbt_str.cpp - various helping functions to deal with strings               */
2
3 /* Copyright (c) 2007-2014. The SimGrid Team.
4  * All rights reserved.                                                     */
5
6 /* This program is free software; you can redistribute it and/or modify it
7  * under the terms of the license (GNU LGPL) which comes with this package. */
8
9 #include <xbt/ex.hpp>
10 #include "xbt/misc.h"
11 #include "xbt/sysdep.h"
12 #include "xbt/str.h"            /* headers of these functions */
13
14 /**  @brief Strip whitespace (or other characters) from the end of a string.
15  *
16  * Strips the whitespaces from the end of s.
17  * By default (when char_list=nullptr), these characters get stripped:
18  *
19  *  - " "    (ASCII 32  (0x20))  space.
20  *  - "\t"    (ASCII 9  (0x09))  tab.
21  *  - "\n"    (ASCII 10  (0x0A))  line feed.
22  *  - "\r"    (ASCII 13  (0x0D))  carriage return.
23  *  - "\0"    (ASCII 0  (0x00))  nullptr.
24  *  - "\x0B"  (ASCII 11  (0x0B))  vertical tab.
25  *
26  * @param s The string to strip. Modified in place.
27  * @param char_list A string which contains the characters you want to strip.
28  */
29 void xbt_str_rtrim(char *s, const char *char_list)
30 {
31   char *cur = s;
32   const char *__char_list = " \t\n\r\x0B";
33   char white_char[256] = { 1, 0 };
34
35   if (not s)
36     return;
37
38   if (not char_list) {
39     while (*__char_list) {
40       white_char[(unsigned char) *__char_list++] = 1;
41     }
42   } else {
43     while (*char_list) {
44       white_char[(unsigned char) *char_list++] = 1;
45     }
46   }
47
48   while (*cur)
49     ++cur;
50
51   while ((cur >= s) && white_char[(unsigned char) *cur])
52     --cur;
53
54   *++cur = '\0';
55 }
56
57 /**  @brief Strip whitespace (or other characters) from the beginning of a string.
58  *
59  * Strips the whitespaces from the beginning of s.
60  * By default (when char_list=nullptr), these characters get stripped:
61  *
62  *  - " "    (ASCII 32  (0x20))  space.
63  *  - "\t"    (ASCII 9  (0x09))  tab.
64  *  - "\n"    (ASCII 10  (0x0A))  line feed.
65  *  - "\r"    (ASCII 13  (0x0D))  carriage return.
66  *  - "\0"    (ASCII 0  (0x00))  nullptr.
67  *  - "\x0B"  (ASCII 11  (0x0B))  vertical tab.
68  *
69  * @param s The string to strip. Modified in place.
70  * @param char_list A string which contains the characters you want to strip.
71  */
72 void xbt_str_ltrim(char *s, const char *char_list)
73 {
74   char *cur = s;
75   const char *__char_list = " \t\n\r\x0B";
76   char white_char[256] = { 1, 0 };
77
78   if (not s)
79     return;
80
81   if (not char_list) {
82     while (*__char_list) {
83       white_char[(unsigned char) *__char_list++] = 1;
84     }
85   } else {
86     while (*char_list) {
87       white_char[(unsigned char) *char_list++] = 1;
88     }
89   }
90
91   while (*cur && white_char[(unsigned char) *cur])
92     ++cur;
93
94   memmove(s, cur, strlen(cur) + 1);
95 }
96
97 /**  @brief Strip whitespace (or other characters) from the end and the beginning of a string.
98  *
99  * Strips the whitespaces from both the beginning and the end of s.
100  * By default (when char_list=nullptr), these characters get stripped:
101  *
102  *  - " "    (ASCII 32  (0x20))  space.
103  *  - "\t"    (ASCII 9  (0x09))  tab.
104  *  - "\n"    (ASCII 10  (0x0A))  line feed.
105  *  - "\r"    (ASCII 13  (0x0D))  carriage return.
106  *  - "\0"    (ASCII 0  (0x00))  nullptr.
107  *  - "\x0B"  (ASCII 11  (0x0B))  vertical tab.
108  *
109  * @param s The string to strip.
110  * @param char_list A string which contains the characters you want to strip.
111  */
112 void xbt_str_trim(char *s, const char *char_list)
113 {
114   if (not s)
115     return;
116
117   xbt_str_rtrim(s, char_list);
118   xbt_str_ltrim(s, char_list);
119 }
120
121 /** @brief Substitutes a char for another in a string
122  *
123  * @param str the string to modify
124  * @param from char to search
125  * @param to char to put instead
126  * @param occurrence number of changes to do (=0 means all)
127  */
128 void xbt_str_subst(char *str, char from, char to, int occurence)
129 {
130   char *p = str;
131   while (*p != '\0') {
132     if (*p == from) {
133       *p = to;
134       if (occurence == 1)
135         return;
136       occurence--;
137     }
138     p++;
139   }
140 }
141
142 /** @brief Splits a string into a dynar of strings
143  *
144  * @param s: the string to split
145  * @param sep: a string of all chars to consider as separator.
146  *
147  * By default (with sep=nullptr), these characters are used as separator:
148  *
149  *  - " "    (ASCII 32  (0x20))  space.
150  *  - "\t"    (ASCII 9  (0x09))  tab.
151  *  - "\n"    (ASCII 10  (0x0A))  line feed.
152  *  - "\r"    (ASCII 13  (0x0D))  carriage return.
153  *  - "\0"    (ASCII 0  (0x00))  nullptr.
154  *  - "\x0B"  (ASCII 11  (0x0B))  vertical tab.
155  */
156 xbt_dynar_t xbt_str_split(const char *s, const char *sep)
157 {
158   xbt_dynar_t res = xbt_dynar_new(sizeof(char *), &xbt_free_ref);
159   const char *sep_dflt = " \t\n\r\x0B";
160   char is_sep[256] = { 1, 0 };
161
162   /* check what are the separators */
163   memset(is_sep, 0, sizeof(is_sep));
164   if (not sep) {
165     while (*sep_dflt)
166       is_sep[(unsigned char) *sep_dflt++] = 1;
167   } else {
168     while (*sep)
169       is_sep[(unsigned char) *sep++] = 1;
170   }
171   is_sep[0] = 1; /* End of string is also separator */
172
173   /* Do the job */
174   const char* p = s;
175   const char* q = s;
176   int done      = 0;
177
178   if (s[0] == '\0')
179     return res;
180
181   while (not done) {
182     char *topush;
183     while (not is_sep[(unsigned char)*q]) {
184       q++;
185     }
186     if (*q == '\0')
187       done = 1;
188
189     topush = (char*) xbt_malloc(q - p + 1);
190     memcpy(topush, p, q - p);
191     topush[q - p] = '\0';
192     xbt_dynar_push(res, &topush);
193     p = ++q;
194   }
195
196   return res;
197 }
198
199 /**
200  * \brief This functions splits a string after using another string as separator
201  * For example Anot not B!not C split after !! will return the dynar {A,B,C}
202  * \return An array of dynars containing the string tokens
203  */
204 xbt_dynar_t xbt_str_split_str(const char *s, const char *sep)
205 {
206   xbt_dynar_t res = xbt_dynar_new(sizeof(char *), &xbt_free_ref);
207
208   const char* p = s;
209   const char* q = s;
210   int done      = 0;
211
212   if (s[0] == '\0')
213     return res;
214   if (sep[0] == '\0') {
215     s = xbt_strdup(s);
216     xbt_dynar_push(res, &s);
217     return res;
218   }
219
220   while (not done) {
221     char *to_push;
222     // get the start of the first occurrence of the substring
223     q = strstr(p, sep);
224     //if substring was not found add the entire string
225     if (nullptr == q) {
226       int v   = strlen(p);
227       to_push = (char*) xbt_malloc(v + 1);
228       memcpy(to_push, p, v);
229       to_push[v] = '\0';
230       xbt_dynar_push(res, &to_push);
231       done = 1;
232     } else {
233       //get the appearance
234       to_push = (char*) xbt_malloc(q - p + 1);
235       memcpy(to_push, p, q - p);
236       //add string terminator
237       to_push[q - p] = '\0';
238       xbt_dynar_push(res, &to_push);
239       p = q + strlen(sep);
240     }
241   }
242   return res;
243 }
244
245 /** @brief Just like @ref xbt_str_split_quoted (Splits a string into a dynar of strings), but without memory allocation
246  *
247  * The string passed as argument must be writable (not const)
248  * The elements of the dynar are just parts of the string passed as argument.
249  * So if you don't store that argument elsewhere, you should free it in addition to freeing the dynar. This can be done
250  * by simply freeing the first argument of the dynar:
251  *  free(xbt_dynar_get_ptr(dynar,0));
252  *
253  * Actually this function puts a bunch of \0 in the memory area you passed as argument to separate the elements, and
254  * pushes the address of each chunk in the resulting dynar. Yes, that's uneven. Yes, that's gory. But that's efficient.
255  */
256 xbt_dynar_t xbt_str_split_quoted_in_place(char *s) {
257   xbt_dynar_t res = xbt_dynar_new(sizeof(char *), nullptr);
258   char* beg;
259   char* end; /* pointers around the parsed chunk */
260   int in_simple_quote = 0;
261   int in_double_quote = 0;
262   int done            = 0;
263   int ctn             = 0; /* Got something in this block */
264
265   if (s[0] == '\0')
266     return res;
267
268   beg = s;
269
270   /* do not trim leading spaces: caller responsibility to clean his cruft */
271   end = beg;
272
273   while (not done) {
274     switch (*end) {
275     case '\\':
276       ctn = 1;
277       /* Protected char; move it closer */
278       memmove(end, end + 1, strlen(end));
279       if (*end == '\0')
280         THROWF(arg_error, 0, "String ends with \\");
281       end++;                    /* Pass the protected char */
282       break;
283     case '\'':
284       ctn = 1;
285       if (not in_double_quote) {
286         in_simple_quote = not in_simple_quote;
287         memmove(end, end + 1, strlen(end));
288       } else {
289         /* simple quote protected by double ones */
290         end++;
291       }
292       break;
293     case '"':
294       ctn = 1;
295       if (not in_simple_quote) {
296         in_double_quote = not in_double_quote;
297         memmove(end, end + 1, strlen(end));
298       } else {
299         /* double quote protected by simple ones */
300         end++;
301       }
302       break;
303     case ' ':
304     case '\t':
305     case '\n':
306     case '\0':
307       if (*end == '\0' && (in_simple_quote || in_double_quote)) {
308         THROWF(arg_error, 0, "End of string found while searching for %c in %s", (in_simple_quote ? '\'' : '"'), s);
309       }
310       if (in_simple_quote || in_double_quote) {
311         end++;
312       } else {
313         if (*end == '\0')
314           done = 1;
315
316         *end = '\0';
317         if (ctn) {
318           /* Found a separator. Push the string if contains something */
319           xbt_dynar_push(res, &beg);
320         }
321         ctn = 0;
322
323         if (done)
324           break;
325
326         beg = ++end;
327         /* trim within the string, manually to speed things up */
328         while (*beg == ' ')
329           beg++;
330         end = beg;
331       }
332       break;
333     default:
334       ctn = 1;
335       end++;
336     }
337   }
338   return res;
339 }
340
341 /** @brief Splits a string into a dynar of strings, taking quotes into account
342  *
343  * It basically does the same argument separation than the shell, where white spaces can be escaped and where arguments
344  * are never split within a quote group.
345  * Several subsequent spaces are ignored (unless within quotes, of course).
346  * You may want to trim the input string, if you want to avoid empty entries
347  */
348 xbt_dynar_t xbt_str_split_quoted(const char *s)
349 {
350   xbt_dynar_t res = xbt_dynar_new(sizeof(char *), &xbt_free_ref);
351   xbt_dynar_t parsed;
352   char *str_to_free;            /* we have to copy the string before, to handle backslashes */
353   unsigned int cursor;
354   char *p;
355
356   if (s[0] == '\0')
357     return res;
358   str_to_free = xbt_strdup(s);
359
360   parsed = xbt_str_split_quoted_in_place(str_to_free);
361   xbt_dynar_foreach(parsed,cursor,p) {
362     char *q=xbt_strdup(p);
363     xbt_dynar_push(res,&q);
364   }
365   free(str_to_free);
366   xbt_dynar_shrink(res, 0);
367   xbt_dynar_free(&parsed);
368   return res;
369 }
370
371 /** @brief Join a set of strings as a single string */
372 char *xbt_str_join(xbt_dynar_t dyn, const char *sep)
373 {
374   int len     = 1;
375   int dyn_len = xbt_dynar_length(dyn);
376   unsigned int cpt;
377   char* cursor;
378
379   if (not dyn_len)
380     return xbt_strdup("");
381
382   /* compute the length */
383   xbt_dynar_foreach(dyn, cpt, cursor) {
384     len += strlen(cursor);
385   }
386   len += strlen(sep) * dyn_len;
387   /* Do the job */
388   char* res = (char*)xbt_malloc(len);
389   char* p   = res;
390   xbt_dynar_foreach(dyn, cpt, cursor) {
391     if ((int) cpt < dyn_len - 1)
392       p += snprintf(p,len, "%s%s", cursor, sep);
393     else
394       p += snprintf(p,len, "%s", cursor);
395   }
396   return res;
397 }
398
399 /** @brief Join a set of strings as a single string
400  *
401  * The parameter must be a nullptr-terminated array of chars,
402  * just like xbt_dynar_to_array() produces
403  */
404 char *xbt_str_join_array(const char *const *strs, const char *sep)
405 {
406   int amount_strings=0;
407   int len=0;
408
409   if ((not strs) || (not strs[0]))
410     return xbt_strdup("");
411
412   /* compute the length before malloc */
413   for (int i = 0; strs[i]; i++) {
414     len += strlen(strs[i]);
415     amount_strings++;
416   }
417   len += strlen(sep) * amount_strings;
418
419   /* Do the job */
420   char* res = (char*)xbt_malloc(len);
421   char* q   = res;
422   for (int i = 0; strs[i]; i++) {
423     if (i != 0) { // not first loop
424       q += snprintf(q,len, "%s%s", sep, strs[i]);
425     } else {
426       q += snprintf(q,len, "%s",strs[i]);
427     }
428   }
429   return res;
430 }
431
432 /** @brief Parse an integer out of a string, or raise an error
433  *
434  * The @a str is passed as argument to your @a error_msg, as follows:
435  * @verbatim THROWF(arg_error, 0, error_msg, str); @endverbatim
436  */
437 long int xbt_str_parse_int(const char* str, const char* error_msg)
438 {
439   char* endptr;
440   if (str == nullptr || str[0] == '\0')
441     THROWF(arg_error, 0, error_msg, str);
442
443   long int res = strtol(str, &endptr, 10);
444   if (endptr[0] != '\0')
445     THROWF(arg_error, 0, error_msg, str);
446
447   return res;
448 }
449
450 /** @brief Parse a double out of a string, or raise an error
451  *
452  * The @a str is passed as argument to your @a error_msg, as follows:
453  * @verbatim THROWF(arg_error, 0, error_msg, str); @endverbatim
454  */
455 double xbt_str_parse_double(const char* str, const char* error_msg)
456 {
457   char *endptr;
458   if (str == nullptr || str[0] == '\0')
459     THROWF(arg_error, 0, error_msg, str);
460
461   double res = strtod(str, &endptr);
462   if (endptr[0] != '\0')
463     THROWF(arg_error, 0, error_msg, str);
464
465   return res;
466 }
467
468 #ifdef SIMGRID_TEST
469 #include <xbt/ex.hpp>
470 #include "xbt/str.h"
471
472 XBT_TEST_SUITE("xbt_str", "String Handling");
473
474 #define mytest(name, input, expected)                                                                                  \
475   xbt_test_add(name);                                                                                                  \
476   d = xbt_str_split_quoted(input);                                                                                     \
477   s = xbt_str_join(d, "XXX");                                                                                          \
478   xbt_test_assert(not strcmp(s, expected), "Input (%s) leads to (%s) instead of (%s)", input, s, expected);            \
479   free(s);                                                                                                             \
480   xbt_dynar_free(&d);
481 XBT_TEST_UNIT("xbt_str_split_quoted", test_split_quoted, "test the function xbt_str_split_quoted")
482 {
483   xbt_dynar_t d;
484   char *s;
485
486   mytest("Empty", "", "");
487   mytest("Basic test", "toto tutu", "totoXXXtutu");
488   mytest("Useless backslashes", "\\t\\o\\t\\o \\t\\u\\t\\u", "totoXXXtutu");
489   mytest("Protected space", "toto\\ tutu", "toto tutu");
490   mytest("Several spaces", "toto   tutu", "totoXXXtutu");
491   mytest("LTriming", "  toto tatu", "totoXXXtatu");
492   mytest("Triming", "  toto   tutu  ", "totoXXXtutu");
493   mytest("Single quotes", "'toto tutu' tata", "toto tutuXXXtata");
494   mytest("Double quotes", "\"toto tutu\" tata", "toto tutuXXXtata");
495   mytest("Mixed quotes", "\"toto' 'tutu\" tata", "toto' 'tutuXXXtata");
496   mytest("Backslashed quotes", "\\'toto tutu\\' tata", "'totoXXXtutu'XXXtata");
497   mytest("Backslashed quotes + quotes", "'toto \\'tutu' tata", "toto 'tutuXXXtata");
498 }
499
500 #define mytest_str(name, input, separator, expected)                                                                   \
501   xbt_test_add(name);                                                                                                  \
502   d = xbt_str_split_str(input, separator);                                                                             \
503   s = xbt_str_join(d, "XXX");                                                                                          \
504   xbt_test_assert(not strcmp(s, expected), "Input (%s) leads to (%s) instead of (%s)", input, s, expected);            \
505   free(s);                                                                                                             \
506   xbt_dynar_free(&d);
507
508 XBT_TEST_UNIT("xbt_str_split_str", test_split_str, "test the function xbt_str_split_str")
509 {
510   xbt_dynar_t d;
511   char *s;
512
513   mytest_str("Empty string and separator", "", "", "");
514   mytest_str("Empty string", "", "##", "");
515   mytest_str("Empty separator", "toto", "", "toto");
516   mytest_str("String with no separator in it", "toto", "##", "toto");
517   mytest_str("Basic test", "toto##tutu", "##", "totoXXXtutu");
518 }
519
520 #define test_parse_error(function, name, variable, str)                 \
521   do {                                                                  \
522     xbt_test_add(name);                                                 \
523     try {                                                               \
524       variable = function(str, "Parse error");                          \
525       xbt_test_fail("The test '%s' did not detect the problem",name );  \
526     } catch(xbt_ex& e) {                                                \
527       if (e.category != arg_error) {                                    \
528         xbt_test_exception(e);                                          \
529       }                                                                 \
530     }                                                                   \
531   } while (0)
532 #define test_parse_ok(function, name, variable, str, value)             \
533   do {                                                                  \
534     xbt_test_add(name);                                                 \
535     try {                                                               \
536       variable = function(str, "Parse error");                          \
537     } catch(xbt_ex& e) {                                                \
538       xbt_test_exception(e);                                            \
539     }                                                                   \
540     xbt_test_assert(variable == value, "Fail to parse '%s'", str);      \
541   } while (0)
542
543 XBT_TEST_UNIT("xbt_str_parse", test_parse, "Test the parsing functions")
544 {
545   int rint = -9999;
546   test_parse_ok(xbt_str_parse_int, "Parse int", rint, "42", 42);
547   test_parse_ok(xbt_str_parse_int, "Parse 0 as an int", rint, "0", 0);
548   test_parse_ok(xbt_str_parse_int, "Parse -1 as an int", rint, "-1", -1);
549
550   test_parse_error(xbt_str_parse_int, "Parse int + noise", rint, "342 cruft");
551   test_parse_error(xbt_str_parse_int, "Parse nullptr as an int", rint, nullptr);
552   test_parse_error(xbt_str_parse_int, "Parse '' as an int", rint, "");
553   test_parse_error(xbt_str_parse_int, "Parse cruft as an int", rint, "cruft");
554
555   double rdouble = -9999;
556   test_parse_ok(xbt_str_parse_double, "Parse 42 as a double", rdouble, "42", 42);
557   test_parse_ok(xbt_str_parse_double, "Parse 42.5 as a double", rdouble, "42.5", 42.5);
558   test_parse_ok(xbt_str_parse_double, "Parse 0 as a double", rdouble, "0", 0);
559   test_parse_ok(xbt_str_parse_double, "Parse -1 as a double", rdouble, "-1", -1);
560
561   test_parse_error(xbt_str_parse_double, "Parse double + noise", rdouble, "342 cruft");
562   test_parse_error(xbt_str_parse_double, "Parse nullptr as a double", rdouble, nullptr);
563   test_parse_error(xbt_str_parse_double, "Parse '' as a double", rdouble, "");
564   test_parse_error(xbt_str_parse_double, "Parse cruft as a double", rdouble, "cruft");
565 }
566 #endif                          /* SIMGRID_TEST */