Use <stdarg.h> from gnulib instead of our home-grown va_copy.h.
[pspp-builds.git] / src / libpspp / str.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 #include <config.h>
21
22 #include "str.h"
23
24 #include <ctype.h>
25 #include <limits.h>
26 #include <stdlib.h>
27
28 #include <libpspp/alloc.h>
29 #include <libpspp/message.h>
30
31 #include "minmax.h"
32 #include "size_max.h"
33 \f
34 /* Reverses the order of NBYTES bytes at address P, thus converting
35    between little- and big-endian byte orders.  */
36 void
37 buf_reverse (char *p, size_t nbytes)
38 {
39   char *h = p, *t = &h[nbytes - 1];
40   char temp;
41
42   nbytes /= 2;
43   while (nbytes--)
44     {
45       temp = *h;
46       *h++ = *t;
47       *t-- = temp;
48     }
49 }
50
51 /* Finds the last NEEDLE of length NEEDLE_LEN in a HAYSTACK of length
52    HAYSTACK_LEN.  Returns a pointer to the needle found. */
53 char *
54 buf_find_reverse (const char *haystack, size_t haystack_len,
55                  const char *needle, size_t needle_len)
56 {
57   int i;
58   for (i = haystack_len - needle_len; i >= 0; i--)
59     if (!memcmp (needle, &haystack[i], needle_len))
60       return (char *) &haystack[i];
61   return 0;
62 }
63
64 /* Compares the SIZE bytes in A to those in B, disregarding case,
65    and returns a strcmp()-type result. */
66 int
67 buf_compare_case (const char *a_, const char *b_, size_t size)
68 {
69   const unsigned char *a = (unsigned char *) a_;
70   const unsigned char *b = (unsigned char *) b_;
71
72   while (size-- > 0) 
73     {
74       unsigned char ac = toupper (*a++);
75       unsigned char bc = toupper (*b++);
76
77       if (ac != bc) 
78         return ac > bc ? 1 : -1;
79     }
80
81   return 0;
82 }
83
84 /* Compares A of length A_LEN to B of length B_LEN.  The shorter
85    string is considered to be padded with spaces to the length of
86    the longer. */
87 int
88 buf_compare_rpad (const char *a, size_t a_len, const char *b, size_t b_len)
89 {
90   size_t min_len;
91   int result;
92
93   min_len = a_len < b_len ? a_len : b_len;
94   result = memcmp (a, b, min_len);
95   if (result != 0)
96     return result;
97   else 
98     {
99       size_t idx;
100       
101       if (a_len < b_len) 
102         {
103           for (idx = min_len; idx < b_len; idx++)
104             if (' ' != b[idx])
105               return ' ' > b[idx] ? 1 : -1;
106         }
107       else 
108         {
109           for (idx = min_len; idx < a_len; idx++)
110             if (a[idx] != ' ')
111               return a[idx] > ' ' ? 1 : -1;
112         }
113       return 0;
114     }
115 }
116
117 /* Compares strin A to string B.  The shorter string is
118    considered to be padded with spaces to the length of the
119    longer. */
120 int
121 str_compare_rpad (const char *a, const char *b)
122 {
123   return buf_compare_rpad (a, strlen (a), b, strlen (b));
124 }
125
126 /* Copies string SRC to buffer DST, of size DST_SIZE bytes.
127    DST is truncated to DST_SIZE bytes or padded on the right with
128    spaces as needed. */
129 void
130 buf_copy_str_rpad (char *dst, size_t dst_size, const char *src)
131 {
132   size_t src_len = strlen (src);
133   if (src_len >= dst_size)
134     memcpy (dst, src, dst_size);
135   else
136     {
137       memcpy (dst, src, src_len);
138       memset (&dst[src_len], ' ', dst_size - src_len);
139     }
140 }
141
142 /* Copies string SRC to buffer DST, of size DST_SIZE bytes.
143    DST is truncated to DST_SIZE bytes or padded on the left with
144    spaces as needed. */
145 void
146 buf_copy_str_lpad (char *dst, size_t dst_size, const char *src)
147 {
148   size_t src_len = strlen (src);
149   if (src_len >= dst_size)
150     memcpy (dst, src, dst_size);
151   else
152     {
153       size_t pad_cnt = dst_size - src_len;
154       memset (&dst[0], ' ', pad_cnt);
155       memcpy (dst + pad_cnt, src, src_len);
156     }
157 }
158
159 /* Copies buffer SRC, of SRC_SIZE bytes, to DST, of DST_SIZE bytes.
160    DST is truncated to DST_SIZE bytes or padded on the right with
161    spaces as needed. */
162 void
163 buf_copy_rpad (char *dst, size_t dst_size,
164                const char *src, size_t src_size)
165 {
166   if (src_size >= dst_size)
167     memmove (dst, src, dst_size);
168   else
169     {
170       memmove (dst, src, src_size);
171       memset (&dst[src_size], ' ', dst_size - src_size);
172     }
173 }
174
175 /* Copies string SRC to string DST, which is in a buffer DST_SIZE
176    bytes long.
177    Truncates DST to DST_SIZE - 1 characters or right-pads with
178    spaces to DST_SIZE - 1 characters if necessary. */
179 void
180 str_copy_rpad (char *dst, size_t dst_size, const char *src)
181 {
182   size_t src_len = strlen (src);
183   if (src_len < dst_size - 1)
184     {
185       memcpy (dst, src, src_len);
186       memset (&dst[src_len], ' ', dst_size - 1 - src_len);
187     }
188   else
189     memcpy (dst, src, dst_size - 1);
190   dst[dst_size - 1] = 0;
191 }
192
193 /* Copies SRC to DST, which is in a buffer DST_SIZE bytes long.
194    Truncates DST to DST_SIZE - 1 characters, if necessary. */
195 void
196 str_copy_trunc (char *dst, size_t dst_size, const char *src) 
197 {
198   size_t src_len = strlen (src);
199   assert (dst_size > 0);
200   if (src_len + 1 < dst_size)
201     memcpy (dst, src, src_len + 1);
202   else 
203     {
204       memcpy (dst, src, dst_size - 1);
205       dst[dst_size - 1] = '\0';
206     }
207 }
208
209 /* Copies buffer SRC, of SRC_LEN bytes,
210    to DST, which is in a buffer DST_SIZE bytes long.
211    Truncates DST to DST_SIZE - 1 characters, if necessary. */
212 void
213 str_copy_buf_trunc (char *dst, size_t dst_size,
214                     const char *src, size_t src_size) 
215 {
216   size_t dst_len;
217   assert (dst_size > 0);
218
219   dst_len = src_size < dst_size ? src_size : dst_size - 1;
220   memcpy (dst, src, dst_len);
221   dst[dst_len] = '\0';
222 }
223
224 /* Converts each character in S to uppercase. */
225 void
226 str_uppercase (char *s) 
227 {
228   for (; *s != '\0'; s++)
229     *s = toupper ((unsigned char) *s);
230 }
231
232 /* Converts each character in S to lowercase. */
233 void
234 str_lowercase (char *s) 
235 {
236   for (; *s != '\0'; s++)
237     *s = tolower ((unsigned char) *s);
238 }
239 \f
240 /* Initializes ST with initial contents S. */
241 void
242 ds_create (struct string *st, const char *s)
243 {
244   st->length = strlen (s);
245   st->capacity = MAX (8, st->length * 2);
246   st->string = xmalloc (st->capacity + 1);
247   strcpy (st->string, s);
248 }
249
250 /* Initializes ST, making room for at least CAPACITY characters. */
251 void
252 ds_init (struct string *st, size_t capacity)
253 {
254   st->length = 0;
255   st->capacity = MAX (8, capacity);
256   st->string = xmalloc (st->capacity + 1);
257 }
258
259 /* Frees ST. */
260 void
261 ds_destroy (struct string *st)
262 {
263   if (st != NULL) 
264     {
265       free (st->string);
266       st->string = NULL;
267       st->length = 0;
268       st->capacity = 0; 
269     }
270 }
271
272 /* Swaps the contents of strings A and B. */
273 void
274 ds_swap (struct string *a, struct string *b) 
275 {
276   struct string tmp = *a;
277   *a = *b;
278   *b = tmp;
279 }
280
281 /* Initializes DST with the CNT characters from SRC starting at
282    position IDX. */
283 void
284 ds_init_substring (struct string *dst,
285                    const struct string *src, size_t idx, size_t cnt)
286 {
287   assert (dst != src);
288   ds_init (dst, cnt);
289   ds_assign_substring (dst, src, idx, cnt);
290 }
291
292 /* Copies SRC into DST.
293    DST and SRC may be the same string. */
294 void
295 ds_assign_string (struct string *dst, const struct string *src) 
296 {
297   ds_assign_buffer (dst, ds_data (src), ds_length (src));
298 }
299
300 /* Replaces DST by CNT characters from SRC starting at position
301    IDX.
302    DST and SRC may be the same string. */
303 void
304 ds_assign_substring (struct string *dst,
305                      const struct string *src, size_t idx, size_t cnt) 
306 {
307   if (idx < src->length)
308     ds_assign_buffer (dst, src->string + idx, MIN (cnt, src->length - idx));
309   else 
310     ds_clear (dst);
311 }
312
313 /* Replaces DST by the LENGTH characters in SRC.
314    SRC may be a substring within DST. */
315 void
316 ds_assign_buffer (struct string *dst, const char *src, size_t length)
317 {
318   dst->length = length;
319   ds_extend (dst, length);
320   memmove (dst->string, src, length);
321 }
322
323 /* Replaces DST by null-terminated string SRC.  SRC may overlap
324    with DST. */
325 void
326 ds_assign_c_str (struct string *dst, const char *src)
327 {
328   ds_assign_buffer (dst, src, strlen (src));
329 }
330
331 /* Truncates ST to zero length. */
332 void
333 ds_clear (struct string *st)
334 {
335   st->length = 0;
336 }
337
338 /* Ensures that ST can hold at least MIN_CAPACITY characters plus a null
339    terminator. */
340 void
341 ds_extend (struct string *st, size_t min_capacity)
342 {
343   if (min_capacity > st->capacity)
344     {
345       st->capacity *= 2;
346       if (st->capacity < min_capacity)
347         st->capacity = 2 * min_capacity;
348
349       st->string = xrealloc (st->string, st->capacity + 1);
350     }
351 }
352
353 /* Shrink ST to the minimum capacity need to contain its content. */
354 void
355 ds_shrink (struct string *st)
356 {
357   if (st->capacity != st->length)
358     {
359       st->capacity = st->length;
360       st->string = xrealloc (st->string, st->capacity + 1);
361     }
362 }
363
364 /* Truncates ST to at most LENGTH characters long. */
365 void
366 ds_truncate (struct string *st, size_t length)
367 {
368   if (st->length > length)
369     st->length = length;
370 }
371
372 /* Pad ST on the right with copies of PAD until ST is at least
373    LENGTH characters in size.  If ST is initially LENGTH
374    characters or longer, this is a no-op. */
375 void
376 ds_rpad (struct string *st, size_t length, char pad) 
377 {
378   if (length > st->length)
379     ds_putc_multiple (st, pad, length - st->length);
380 }
381
382 /* Removes trailing spaces from ST.
383    Returns number of spaces removed. */
384 int
385 ds_rtrim_spaces (struct string *st) 
386 {
387   int cnt = 0;
388   while (isspace (ds_last (st))) 
389     {
390       st->length--;
391       cnt++;
392     }
393   return cnt;
394 }
395
396 /* Removes leading spaces from ST.
397    Returns number of spaces removed. */
398 int
399 ds_ltrim_spaces (struct string *st) 
400 {
401   size_t cnt = 0;
402   while (isspace (ds_at (st, cnt)))
403     cnt++;
404   if (cnt > 0)
405     ds_assign_substring (st, st, cnt, SIZE_MAX);
406   return cnt;
407 }
408
409 /* Trims leading and trailing spaces from ST. */
410 void
411 ds_trim_spaces (struct string *st) 
412 {
413   ds_rtrim_spaces (st);
414   ds_ltrim_spaces (st);
415 }
416
417 /* If the last character in ST is C, removes it and returns true.
418    Otherwise, returns false without modifying ST. */
419 bool
420 ds_chomp (struct string *st, char c_) 
421 {
422   unsigned char c = c_;
423   if (ds_last (st) == c)
424     {
425       st->length--;
426       return true;
427     }
428   else
429     return false;
430 }
431
432 /* Divides ST into tokens separated by any of the DELIMITERS.
433    Each call replaces TOKEN by the next token in ST, or by an
434    empty string if no tokens remain.  Returns true if a token was
435    obtained, false otherwise.
436
437    Before the first call, initialize *SAVE_IDX to 0.  Do not
438    modify *SAVE_IDX between calls.
439
440    ST divides into exactly one more tokens than it contains
441    delimiters.  That is, a delimiter at the start or end of ST or
442    a pair of adjacent delimiters yields an empty token, and the
443    empty string contains a single token. */
444 bool
445 ds_separate (const struct string *st, struct string *token,
446              const char *delimiters, size_t *save_idx)
447 {
448   if (*save_idx <= ds_length (st))
449     {
450       size_t length = ds_cspan (st, *save_idx, delimiters);
451       ds_assign_substring (token, st, *save_idx, length);
452       *save_idx += length + 1;
453       return true;
454     }
455   else 
456     return false;
457 }
458
459 /* Divides ST into tokens separated by any of the DELIMITERS,
460    merging adjacent delimiters so that the empty string is never
461    produced as a token.  Each call replaces TOKEN by the next
462    token in ST, or by an empty string if no tokens remain.
463    Returns true if a token was obtained, false otherwise.
464
465    Before the first call, initialize *SAVE_IDX to 0.  Do not
466    modify *SAVE_IDX between calls. */
467 bool
468 ds_tokenize (const struct string *st, struct string *token,
469              const char *delimiters, size_t *save_idx)
470 {
471   size_t start = *save_idx + ds_span (st, *save_idx, delimiters);
472   size_t length = ds_cspan (st, start, delimiters);
473   ds_assign_substring (token, st, start, length);
474   *save_idx = start + length;
475   return length > 0;
476 }
477
478 /* Returns true if ST is empty, false otherwise. */
479 bool
480 ds_is_empty (const struct string *st) 
481 {
482   return st->length == 0;
483 }
484
485 /* Returns the length of ST. */
486 size_t
487 ds_length (const struct string *st)
488 {
489   return st->length;
490 }
491
492 /* Returns the value of ST as a null-terminated string. */
493 char *
494 ds_c_str (const struct string *st_)
495 {
496   struct string *st = (struct string *) st_;
497   if (st->string == NULL) 
498     ds_extend (st, 1);
499   st->string[st->length] = '\0';
500   return st->string;
501 }
502
503 /* Returns the string data inside ST. */
504 char *
505 ds_data (const struct string *st)
506 {
507   return st->string;
508 }
509
510 /* Returns a pointer to the null terminator ST.
511    This might not be an actual null character unless ds_c_str() has
512    been called since the last modification to ST. */
513 char *
514 ds_end (const struct string *st)
515 {
516   return st->string + st->length;
517 }
518
519 /* Returns the allocation size of ST. */
520 size_t
521 ds_capacity (const struct string *st)
522 {
523   return st->capacity;
524 }
525
526 /* Returns the character in position IDX in ST, as a value in the
527    range of unsigned char.  Returns EOF if IDX is out of the
528    range of indexes for ST. */
529 int
530 ds_at (const struct string *st, size_t idx) 
531 {
532   return idx < st->length ? (unsigned char) st->string[idx] : EOF;
533 }
534
535 /* Returns the first character in ST as a value in the range of
536    unsigned char.  Returns EOF if ST is the empty string. */
537 int
538 ds_first (const struct string *st) 
539 {
540   return ds_at (st, 0);
541 }
542
543 /* Returns the last character in ST as a value in the range of
544    unsigned char.  Returns EOF if ST is the empty string. */
545 int
546 ds_last (const struct string *st) 
547 {
548   return st->length > 0 ? (unsigned char) st->string[st->length - 1] : EOF;
549 }
550
551 /* Returns the number of consecutive characters starting at OFS
552    in ST that are in SKIP_SET.  (The null terminator is not
553    considered to be part of SKIP_SET.) */
554 size_t
555 ds_span (const struct string *st, size_t ofs, const char skip_set[])
556 {
557   size_t i;
558   for (i = ofs; i < st->length; i++) 
559     {
560       int c = st->string[i];
561       if (strchr (skip_set, c) == NULL || c == '\0')
562         break; 
563     }
564   return i - ofs;
565 }
566
567 /* Returns the number of consecutive characters starting at OFS
568    in ST that are not in STOP_SET.  (The null terminator is not
569    considered to be part of STOP_SET.) */
570 size_t
571 ds_cspan (const struct string *st, size_t ofs, const char stop_set[])
572 {
573   size_t i;
574   for (i = ofs; i < st->length; i++) 
575     {
576       int c = st->string[i];
577       if (strchr (stop_set, c) != NULL)
578         break; 
579     }
580   return i - ofs;
581 }
582
583 /* Appends to ST a newline-terminated line read from STREAM.
584    Newline is the last character of ST on return, unless an I/O error
585    or end of file is encountered after reading some characters.
586    Returns true if a line is successfully read, false if no characters at
587    all were read before an I/O error or end of file was
588    encountered. */
589 bool
590 ds_gets (struct string *st, FILE *stream)
591 {
592   int c;
593
594   c = getc (stream);
595   if (c == EOF)
596     return false;
597
598   for (;;)
599     {
600       ds_putc (st, c);
601       if (c == '\n')
602         return true;
603
604       c = getc (stream);
605       if (c == EOF)
606         return true;
607     }
608 }
609
610 /* Removes a comment introduced by `#' from ST,
611    ignoring occurrences inside quoted strings. */
612 static void
613 remove_comment (struct string *st)
614 {
615   char *cp;
616   int quote = 0;
617       
618   for (cp = ds_c_str (st); cp < ds_end (st); cp++)
619     if (quote)
620       {
621         if (*cp == quote)
622           quote = 0;
623         else if (*cp == '\\')
624           cp++;
625       }
626     else if (*cp == '\'' || *cp == '"')
627       quote = *cp;
628     else if (*cp == '#')
629       {
630         ds_truncate (st, cp - ds_c_str (st));
631         break;
632       }
633 }
634
635 /* Reads a line from STREAM into ST, then preprocesses as follows:
636
637    - Splices lines terminated with `\'.
638
639    - Deletes comments introduced by `#' outside of single or double
640      quotes.
641
642    - Deletes trailing white space.  
643
644    Returns true if a line was successfully read, false on
645    failure.  If LINE_NUMBER is non-null, then *LINE_NUMBER is
646    incremented by the number of lines read. */
647 bool
648 ds_get_config_line (FILE *stream, struct string *st, int *line_number)
649 {
650   ds_clear (st);
651   do
652     {
653       if (!ds_gets (st, stream))
654         return false;
655       (*line_number)++;
656       ds_rtrim_spaces (st);
657     }
658   while (ds_chomp (st, '\\'));
659  
660   remove_comment (st);
661   return true;
662 }
663
664 /* Concatenates S onto ST. */
665 void
666 ds_puts (struct string *st, const char *s)
667 {
668   size_t s_len;
669
670   if (!s) return;
671
672   s_len = strlen (s);
673   ds_extend (st, st->length + s_len);
674   strcpy (st->string + st->length, s);
675   st->length += s_len;
676 }
677
678 /* Concatenates LEN characters from BUF onto ST. */
679 void
680 ds_concat (struct string *st, const char *buf, size_t len)
681 {
682   ds_extend (st, st->length + len);
683   memcpy (st->string + st->length, buf, len);
684   st->length += len;
685 }
686
687 /* Returns ds_end(ST) and THEN increases the length by INCR. */
688 char *
689 ds_append_uninit(struct string *st, size_t incr)
690 {
691   char *end;
692
693   ds_extend(st, ds_length(st) + incr);
694
695   end = ds_end(st);
696
697   st->length += incr;
698  
699   return end;
700 }
701
702 /* Formats FORMAT as a printf string and appends the result to ST. */
703 void
704 ds_printf (struct string *st, const char *format, ...)
705 {
706   va_list args;
707
708   va_start (args, format);
709   ds_vprintf(st, format, args);
710   va_end (args);
711 }
712
713 /* Formats FORMAT as a printf string and appends the result to ST. */
714 void
715 ds_vprintf (struct string *st, const char *format, va_list args_)
716 {
717   int avail, needed;
718   va_list args;
719
720   va_copy (args, args_);
721   avail = st->string != NULL ? st->capacity - st->length + 1 : 0;
722   needed = vsnprintf (st->string + st->length, avail, format, args);
723   va_end (args);
724
725   if (needed >= avail)
726     {
727       ds_extend (st, st->length + needed);
728       
729       va_copy (args, args_);
730       vsprintf (st->string + st->length, format, args);
731       va_end (args);
732     }
733   else 
734     {
735       /* Some old libc's returned -1 when the destination string
736          was too short. */
737       while (needed == -1)
738         {
739           ds_extend (st, (st->capacity + 1) * 2);
740           avail = st->capacity - st->length + 1;
741
742           va_copy (args, args_);
743           needed = vsnprintf (st->string + st->length, avail, format, args);
744           va_end (args);
745         } 
746     }
747
748   st->length += needed;
749 }
750
751 /* Appends character CH to ST. */
752 void
753 ds_putc (struct string *st, int ch)
754 {
755   if (st->length >= st->capacity)
756     ds_extend (st, st->length + 1);
757   st->string[st->length++] = ch;
758 }
759
760 /* Appends CNT copies of character CH to ST. */
761 void
762 ds_putc_multiple (struct string *st, int ch, size_t cnt) 
763 {
764   ds_extend (st, st->length + cnt);
765   memset (&st->string[st->length], ch, cnt);
766   st->length += cnt;
767 }
768
769 \f
770 /* Lengthed strings. */
771
772 /* Creates a new lengthed string LS with contents as a copy of
773    S. */
774 void
775 ls_create (struct fixed_string *ls, const char *s)
776 {
777   ls->length = strlen (s);
778   ls->string = xmalloc (ls->length + 1);
779   memcpy (ls->string, s, ls->length + 1);
780 }
781
782 /* Creates a new lengthed string LS with contents as a copy of
783    BUFFER with length LEN. */
784 void
785 ls_create_buffer (struct fixed_string *ls,
786                   const char *buffer, size_t len)
787 {
788   ls->length = len;
789   ls->string = xmalloc (len + 1);
790   memcpy (ls->string, buffer, len);
791   ls->string[len] = '\0';
792 }
793
794 /* Sets the fields of LS to the specified values. */
795 void
796 ls_init (struct fixed_string *ls, const char *string, size_t length)
797 {
798   ls->string = (char *) string;
799   ls->length = length;
800 }
801
802 /* Copies the fields of SRC to DST. */
803 void
804 ls_shallow_copy (struct fixed_string *dst, const struct fixed_string *src)
805 {
806   *dst = *src;
807 }
808
809 /* Frees the memory backing LS. */
810 void
811 ls_destroy (struct fixed_string *ls)
812 {
813   free (ls->string);
814 }
815
816 /* Sets LS to a null pointer value. */
817 void
818 ls_null (struct fixed_string *ls)
819 {
820   ls->string = NULL;
821 }
822
823 /* Returns nonzero only if LS has a null pointer value. */
824 int
825 ls_null_p (const struct fixed_string *ls)
826 {
827   return ls->string == NULL;
828 }
829
830 /* Returns nonzero only if LS is a null pointer or has length 0. */
831 int
832 ls_empty_p (const struct fixed_string *ls)
833 {
834   return ls->string == NULL || ls->length == 0;
835 }
836
837 /* Returns the length of LS, which must not be null. */
838 size_t
839 ls_length (const struct fixed_string *ls)
840 {
841   return ls->length;
842 }
843
844 /* Returns a pointer to the character string in LS. */
845 char *
846 ls_c_str (const struct fixed_string *ls)
847 {
848   return (char *) ls->string;
849 }
850
851 /* Returns a pointer to the null terminator of the character string in
852    LS. */
853 char *
854 ls_end (const struct fixed_string *ls)
855 {
856   return (char *) (ls->string + ls->length);
857 }