1 /***********************************************************************
2  Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 /**********************************************************************
15   Allocated/allocatable strings
16   original author: David Pfitzner <dwp@mso.anu.edu.au>
17 
18   A common technique is to have some memory dynamically allocated
19   (using malloc etc), to avoid compiled-in limits, but only allocate
20   enough space as initially needed, and then realloc later if/when
21   require more space.  Typically, the realloc is made a bit more than
22   immediately necessary, to avoid frequent reallocs if the object
23   grows incrementally.  Also, don't usually realloc at all if the
24   object shrinks.  This is straightforward, but just requires a bit
25   of book-keeping to keep track of how much has been allocated etc.
26   This module provides some tools to make this a bit easier.
27 
28   This is deliberately simple and light-weight.  The user is allowed
29   full access to the struct elements rather than use accessor
30   functions etc.
31 
32   Note one potential hazard: when the size is increased (astr_reserve()),
33   realloc (really fc_realloc) is used, which retains any data which
34   was there previously, _but_: any external pointers into the allocated
35   memory may then become wild.  So you cannot safely use such external
36   pointers into the astring data, except strictly between times when
37   the astring size may be changed.
38 
39   There are two ways of getting the resulting string as a char *:
40 
41    - astr_str() returns a const char *. This should not be modified
42      or freed by the caller; the storage remains owned by the
43      struct astring, which should be freed with astr_free().
44 
45    - astr_to_str() returns a char * and destroys the struct astring.
46      Responsibility for freeing the storage becomes the caller's.
47 
48   One pattern for using astr_str() is to replace static buffers in
49   functions that return a pointer to static storage. Where previously
50   you would have had e.g. "static struct buf[128]" with an arbitrary
51   size limit, you can have "static struct astring buf", and re-use the
52   same astring on subsequent calls; the caller should behave the
53   same (only reading the string and not freeing it).
54 
55 ***********************************************************************/
56 
57 #ifdef HAVE_CONFIG_H
58 #include <fc_config.h>
59 #endif
60 
61 #include "fc_prehdrs.h"
62 
63 #include <stdarg.h>
64 #include <stdlib.h>
65 #include <string.h>
66 
67 /* utility */
68 #include "fcintl.h"
69 #include "log.h"                /* fc_assert */
70 #include "mem.h"
71 #include "support.h"            /* fc_vsnprintf, fc_strlcat */
72 
73 #include "astring.h"
74 
75 #define str     _private_str_
76 #define n       _private_n_
77 #define n_alloc _private_n_alloc_
78 
79 static const struct astring zero_astr = ASTRING_INIT;
80 static char *astr_buffer = NULL;
81 static size_t astr_buffer_alloc = 0;
82 
83 static inline char *astr_buffer_get(size_t *alloc);
84 static inline char *astr_buffer_grow(size_t *alloc);
85 static void astr_buffer_free(void);
86 
87 
88 /****************************************************************************
89   Returns the astring buffer. Create it if necessary.
90 ****************************************************************************/
astr_buffer_get(size_t * alloc)91 static inline char *astr_buffer_get(size_t *alloc)
92 {
93   if (!astr_buffer) {
94     astr_buffer_alloc = 65536;
95     astr_buffer = fc_malloc(astr_buffer_alloc);
96     atexit(astr_buffer_free);
97   }
98 
99   *alloc = astr_buffer_alloc;
100   return astr_buffer;
101 }
102 
103 /****************************************************************************
104   Grow the astring buffer.
105 ****************************************************************************/
astr_buffer_grow(size_t * alloc)106 static inline char *astr_buffer_grow(size_t *alloc)
107 {
108   astr_buffer_alloc *= 2;
109   astr_buffer = fc_realloc(astr_buffer, astr_buffer_alloc);
110 
111   *alloc = astr_buffer_alloc;
112   return astr_buffer;
113 }
114 
115 /************************************************************************//**
116   Free the astring buffer.
117 ****************************************************************************/
astr_buffer_free(void)118 static void astr_buffer_free(void)
119 {
120   free(astr_buffer);
121 }
122 
123 /****************************************************************************
124   Initialize the struct.
125 ****************************************************************************/
astr_init(struct astring * astr)126 void astr_init(struct astring *astr)
127 {
128   *astr = zero_astr;
129 }
130 
131 /****************************************************************************
132   Free the memory associated with astr, and return astr to same
133   state as after astr_init.
134 ****************************************************************************/
astr_free(struct astring * astr)135 void astr_free(struct astring *astr)
136 {
137   if (astr->n_alloc > 0) {
138     fc_assert_ret(NULL != astr->str);
139     free(astr->str);
140   }
141   *astr = zero_astr;
142 }
143 
144 /****************************************************************************
145   Return the raw string to the caller, and return astr to same state as
146   after astr_init().
147   Freeing the string's storage becomes the caller's responsibility.
148 ****************************************************************************/
astr_to_str(struct astring * astr)149 char *astr_to_str(struct astring *astr)
150 {
151   char *str = astr->str;
152   *astr = zero_astr;
153   return str;
154 }
155 
156 /****************************************************************************
157   Check that astr has enough size to hold n, and realloc to a bigger
158   size if necessary.  Here n must be big enough to include the trailing
159   ascii-null if required.  The requested n is stored in astr->n.
160   The actual amount allocated may be larger than n, and is stored
161   in astr->n_alloc.
162 ****************************************************************************/
astr_reserve(struct astring * astr,size_t n)163 void astr_reserve(struct astring *astr, size_t n)
164 {
165   unsigned int n1;
166   bool was_null = (astr->n == 0);
167 
168   fc_assert_ret(NULL != astr);
169 
170   astr->n = n;
171   if (n <= astr->n_alloc) {
172     return;
173   }
174 
175   /* Allocated more if this is only a small increase on before: */
176   n1 = (3 * (astr->n_alloc + 10)) / 2;
177   astr->n_alloc = (n > n1) ? n : n1;
178   astr->str = (char *) fc_realloc(astr->str, astr->n_alloc);
179   if (was_null) {
180     astr_clear(astr);
181   }
182 }
183 
184 /****************************************************************************
185   Sets the content to the empty string.
186 ****************************************************************************/
astr_clear(struct astring * astr)187 void astr_clear(struct astring *astr)
188 {
189   if (astr->n == 0) {
190     /* astr_reserve is really astr_size, so we don't want to reduce the
191      * size. */
192     astr_reserve(astr, 1);
193   }
194   astr->str[0] = '\0';
195 }
196 
197 /************************************************************************//**
198   Helper: add the text to the specified place in the string.
199 ****************************************************************************/
astr_vadd_at(struct astring * astr,size_t at,const char * format,va_list ap)200 static inline void astr_vadd_at(struct astring *astr, size_t at,
201                                 const char *format, va_list ap)
202 {
203   char *buffer;
204   size_t buffer_size;
205   size_t new_len;
206 
207   buffer = astr_buffer_get(&buffer_size);
208   for (;;) {
209     new_len = fc_vsnprintf(buffer, buffer_size, format, ap);
210     if (new_len < buffer_size && (size_t) -1 != new_len) {
211       break;
212     }
213     buffer = astr_buffer_grow(&buffer_size);
214   }
215 
216   new_len += at + 1;
217 
218   astr_reserve(astr, new_len);
219   fc_strlcpy(astr->str + at, buffer, astr->n_alloc - at);
220 }
221 
222 /****************************************************************************
223   Set the text to the string.
224 ****************************************************************************/
astr_set(struct astring * astr,const char * format,...)225 void astr_set(struct astring *astr, const char *format, ...)
226 {
227   va_list args;
228 
229   va_start(args, format);
230   astr_vadd_at(astr, 0, format, args);
231   va_end(args);
232 }
233 
234 /************************************************************************//**
235   Add the text to the string (varargs version).
236 ****************************************************************************/
astr_vadd(struct astring * astr,const char * format,va_list ap)237 void astr_vadd(struct astring *astr, const char *format, va_list ap)
238 {
239   astr_vadd_at(astr, astr_len(astr), format, ap);
240 }
241 
242 /****************************************************************************
243   Add the text to the string.
244 ****************************************************************************/
astr_add(struct astring * astr,const char * format,...)245 void astr_add(struct astring *astr, const char *format, ...)
246 {
247   va_list args;
248 
249   va_start(args, format);
250   astr_vadd_at(astr, astr_len(astr), format, args);
251   va_end(args);
252 }
253 
254 /****************************************************************************
255   Add the text to the string in a new line.
256 ****************************************************************************/
astr_add_line(struct astring * astr,const char * format,...)257 void astr_add_line(struct astring *astr, const char *format, ...)
258 {
259   size_t len = astr_len(astr);
260   va_list args;
261 
262   va_start(args, format);
263   if (0 < len) {
264     astr_vadd_at(astr, len + 1, format, args);
265     astr->str[len] = '\n';
266   } else {
267     astr_vadd_at(astr, len, format, args);
268   }
269   va_end(args);
270 }
271 
272 /****************************************************************************
273   Replace the spaces by line breaks when the line lenght is over the desired
274   one.
275 ****************************************************************************/
astr_break_lines(struct astring * astr,size_t desired_len)276 void astr_break_lines(struct astring *astr, size_t desired_len)
277 {
278   fc_break_lines(astr->str, desired_len);
279 }
280 
281 /****************************************************************************
282   Build a localized string with the given items. Items will be
283   "or"-separated.
284 
285   See also astr_build_and_list(), strvec_to_or_list().
286 ****************************************************************************/
astr_build_or_list(struct astring * astr,const char * const * items,size_t number)287 const char *astr_build_or_list(struct astring *astr,
288                                const char *const *items, size_t number)
289 {
290   fc_assert_ret_val(NULL != astr, NULL);
291   fc_assert_ret_val(0 < number, NULL);
292   fc_assert_ret_val(NULL != items, NULL);
293 
294   if (1 == number) {
295     /* TRANS: "or"-separated string list with one single item. */
296     astr_set(astr, Q_("?or-list-single:%s"), *items);
297   } else if (2 == number) {
298     /* TRANS: "or"-separated string list with 2 items. */
299     astr_set(astr, Q_("?or-list:%s or %s"), items[0], items[1]);
300   } else {
301     /* Estimate the space we need. */
302     astr_reserve(astr, number * 64);
303     /* TRANS: start of an "or"-separated string list with more than two
304      * items. */
305     astr_set(astr, Q_("?or-list:%s"), *items++);
306     while (1 < --number) {
307       /* TRANS: next elements of an "or"-separated string list with more
308        * than two items. */
309       astr_add(astr, Q_("?or-list:, %s"), *items++);
310     }
311     /* TRANS: end of an "or"-separated string list with more than two
312      * items. */
313     astr_add(astr, Q_("?or-list:, or %s"), *items);
314   }
315 
316   return astr->str;
317 }
318 
319 /****************************************************************************
320   Build a localized string with the given items. Items will be
321   "and"-separated.
322 
323   See also astr_build_or_list(), strvec_to_and_list().
324 ****************************************************************************/
astr_build_and_list(struct astring * astr,const char * const * items,size_t number)325 const char *astr_build_and_list(struct astring *astr,
326                                 const char *const *items, size_t number)
327 {
328   fc_assert_ret_val(NULL != astr, NULL);
329   fc_assert_ret_val(0 < number, NULL);
330   fc_assert_ret_val(NULL != items, NULL);
331 
332   if (1 == number) {
333     /* TRANS: "and"-separated string list with one single item. */
334     astr_set(astr, Q_("?and-list-single:%s"), *items);
335   } else if (2 == number) {
336     /* TRANS: "and"-separated string list with 2 items. */
337     astr_set(astr, Q_("?and-list:%s and %s"), items[0], items[1]);
338   } else {
339     /* Estimate the space we need. */
340     astr_reserve(astr, number * 64);
341     /* TRANS: start of an "and"-separated string list with more than two
342      * items. */
343     astr_set(astr, Q_("?and-list:%s"), *items++);
344     while (1 < --number) {
345       /* TRANS: next elements of an "and"-separated string list with more
346        * than two items. */
347       astr_add(astr, Q_("?and-list:, %s"), *items++);
348     }
349     /* TRANS: end of an "and"-separated string list with more than two
350      * items. */
351     astr_add(astr, Q_("?and-list:, and %s"), *items);
352   }
353 
354   return astr->str;
355 }
356 
357 /****************************************************************************
358   Copy one astring in another.
359 ****************************************************************************/
astr_copy(struct astring * dest,const struct astring * src)360 void astr_copy(struct astring *dest, const struct astring *src)
361 {
362   if (astr_empty(src)) {
363     astr_clear(dest);
364   } else {
365     astr_set(dest, "%s", src->str);
366   }
367 }
368