1 #ifdef RCSID
2 static char RCSid[] =
3 "$Header: d:/cvsroot/tads/tads3/STD.CPP,v 1.3 1999/07/11 00:46:52 MJRoberts Exp $";
4 #endif
5 
6 /*
7  *   Copyright (c) 1999, 2002 Michael J. Roberts.  All Rights Reserved.
8  *
9  *   Please see the accompanying license file, LICENSE.TXT, for information
10  *   on using and copying this software.
11  */
12 /*
13 Name
14   std.cpp - T3 library functions
15 Function
16 
17 Notes
18 
19 Modified
20   04/16/99 MJRoberts  - Creation
21 */
22 
23 #include <string.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 
27 #include "os.h"
28 #include "t3std.h"
29 #include "utf8.h"
30 
31 /* ------------------------------------------------------------------------ */
32 /*
33  *   Allocate space for a string of a given length.  We'll add in space
34  *   for the null terminator.
35  */
lib_alloc_str(size_t len)36 char *lib_alloc_str(size_t len)
37 {
38     char *buf;
39 
40     /* allocate the space */
41     buf = (char *)t3malloc(len + 1);
42 
43     /* make sure it's initially null-terminated */
44     buf[0] = '\0';
45 
46     /* return the space */
47     return buf;
48 }
49 
50 /*
51  *   Allocate space for a string of known length, and save a copy of the
52  *   string.  The length does not include a null terminator, and in fact
53  *   the string does not need to be null-terminated.  The copy returned,
54  *   however, is null-terminated.
55  */
lib_copy_str(const char * str,size_t len)56 char *lib_copy_str(const char *str, size_t len)
57 {
58     char *buf;
59 
60     /* if the source string is null, just return null as the result */
61     if (str == 0)
62         return 0;
63 
64     /* allocate space */
65     buf = lib_alloc_str(len);
66 
67     /* if that succeeded, make a copy */
68     if (buf != 0)
69     {
70         /* copy the string */
71         memcpy(buf, str, len);
72 
73         /* null-terminate it */
74         buf[len] = '\0';
75     }
76 
77     /* return the buffer */
78     return buf;
79 }
80 
81 /*
82  *   allocate and copy a null-terminated string
83  */
lib_copy_str(const char * str)84 char *lib_copy_str(const char *str)
85 {
86     return (str == 0 ? 0 : lib_copy_str(str, strlen(str)));
87 }
88 
89 /*
90  *   Free a string previously allocated with lib_copy_str()
91  */
lib_free_str(char * buf)92 void lib_free_str(char *buf)
93 {
94     if (buf != 0)
95         t3free(buf);
96 }
97 
98 /* ------------------------------------------------------------------------ */
99 /*
100  *   Utility routine: compare spaces, collapsing whitespace
101  */
lib_strequal_collapse_spaces(const char * a,size_t a_len,const char * b,size_t b_len)102 int lib_strequal_collapse_spaces(const char *a, size_t a_len,
103                                  const char *b, size_t b_len)
104 {
105     const char *a_end;
106     const char *b_end;
107     utf8_ptr ap, bp;
108 
109     /* calculate where the strings end */
110     a_end = a + a_len;
111     b_end = b + b_len;
112 
113     /* keep going until we run out of strings */
114     for (ap.set((char *)a), bp.set((char *)b) ;
115          ap.getptr() < a_end && bp.getptr() < b_end ; )
116     {
117         /* check to see if we have whitespace in both strings */
118         if (is_space(ap.getch()) && is_space(bp.getch()))
119         {
120             /* skip all whitespace in both strings */
121             for (ap.inc() ; ap.getptr() < a_end && is_space(ap.getch()) ;
122                  ap.inc()) ;
123             for (bp.inc() ; bp.getptr() < b_end && is_space(bp.getch()) ;
124                  bp.inc()) ;
125 
126             /* keep going */
127             continue;
128         }
129 
130         /* if the characters here don't match, we don't have a match */
131         if (ap.getch() != bp.getch())
132             return FALSE;
133 
134         /* move on to the next character of each string */
135         ap.inc();
136         bp.inc();
137     }
138 
139     /*
140      *   if both strings ran out at the same time, we have a match;
141      *   otherwise, they're not the same
142      */
143     return (ap.getptr() == a_end && bp.getptr() == b_end);
144 }
145 
146 /* ------------------------------------------------------------------------ */
147 /*
148  *   Find a version suffix in an identifier string.  A version suffix
149  *   starts with the given character.  If we don't find the character,
150  *   we'll return the default version suffix.  In any case, we'll set
151  *   name_len to the length of the name portion, excluding the version
152  *   suffix and its leading separator.
153  *
154  *   For example, with a '/' suffix, a versioned name string would look
155  *   like "tads-gen/030000" - the name is "tads_gen" and the version is
156  *   "030000".
157  */
lib_find_vsn_suffix(const char * name_string,char suffix_char,const char * default_vsn,size_t * name_len)158 const char *lib_find_vsn_suffix(const char *name_string, char suffix_char,
159                                 const char *default_vsn, size_t *name_len)
160 {
161     const char *vsn;
162 
163     /* find the suffix character, if any */
164     for (vsn = name_string ; *vsn != '\0' && *vsn != suffix_char ; ++vsn);
165 
166     /* note the length of the name portion */
167     *name_len = vsn - name_string;
168 
169     /*
170      *   skip the separator if we found one, to point vsn at the start of
171      *   the suffix string itself - it we didn't find the separator
172      *   character, use the default version string
173      */
174     if (*vsn == suffix_char)
175         ++vsn;
176     else
177         vsn = default_vsn;
178 
179     /* return the version string */
180     return vsn;
181 }
182 
183 
184 /* ------------------------------------------------------------------------ */
185 /*
186  *   buffer-checked sprintf implementation
187  */
t3sprintf(char * buf,size_t buflen,const char * fmt,...)188 void t3sprintf(char *buf, size_t buflen, const char *fmt, ...)
189 {
190     va_list args;
191 
192     /* package the arguments as a va_list and invoke our va_list version */
193     va_start(args, fmt);
194     t3vsprintf(buf, buflen, fmt, args);
195     va_end(args);
196 }
197 
198 /*
199  *   buffer-checked vsprintf implementation
200  */
t3vsprintf(char * buf,size_t buflen,const char * fmt,va_list args)201 void t3vsprintf(char *buf, size_t buflen, const char *fmt, va_list args)
202 {
203     size_t rem;
204     char *dst;
205 
206     /* if there's no room at all in the buffer, give up immediately */
207     if (buflen == 0)
208         return;
209 
210     /* scan the buffer */
211     for (dst = buf, rem = buflen - 1 ; *fmt != '\0' && rem != 0 ; ++fmt)
212     {
213         /* check for a format specifier */
214         if (*fmt == '%')
215         {
216             const char *fmt_start = fmt;
217             const char *txt;
218             size_t txtlen;
219             char buf[20];
220             int fld_wid = -1;
221             int fld_prec = -1;
222             char lead_char = ' ';
223             int approx = FALSE;
224             int add_ellipsis = FALSE;
225             int left_align = FALSE;
226             size_t i;
227 
228             /* skip the '%' */
229             ++fmt;
230 
231             /* check for the "approximation" flag */
232             if (*fmt == '~')
233             {
234                 approx = TRUE;
235                 ++fmt;
236             }
237 
238             /* check for left alignment */
239             if (*fmt == '-')
240             {
241                 left_align = TRUE;
242                 ++fmt;
243             }
244 
245             /* if leading zeroes are desired, note it */
246             if (*fmt == '0')
247                 lead_char = '0';
248 
249             /* check for a field width specifier */
250             if (is_digit(*fmt))
251             {
252                 /* scan the digits */
253                 for (fld_wid = 0 ; is_digit(*fmt) ; ++fmt)
254                 {
255                     fld_wid *= 10;
256                     fld_wid += value_of_digit(*fmt);
257                 }
258             }
259             else if (*fmt == '*')
260             {
261                 /* the value is an integer taken from the arguments */
262                 fld_wid = va_arg(args, int);
263 
264                 /* skip the '*' */
265                 ++fmt;
266             }
267 
268             /* check for a precision specifier */
269             if (*fmt == '.')
270             {
271                 /* skip the '.' */
272                 ++fmt;
273 
274                 /* check what we have */
275                 if (*fmt == '*')
276                 {
277                     /* the value is an integer taken from the arguments */
278                     fld_prec = va_arg(args, int);
279 
280                     /* skip the '*' */
281                     ++fmt;
282                 }
283                 else
284                 {
285                     /* scan the digits */
286                     for (fld_prec = 0 ; is_digit(*fmt) ; ++fmt)
287                     {
288                         fld_prec *= 10;
289                         fld_prec += value_of_digit(*fmt);
290                     }
291                 }
292             }
293 
294             /* check what follows */
295             switch (*fmt)
296             {
297             case '%':
298                 /* it's a literal '%' */
299                 txt = "%";
300                 txtlen = 1;
301                 break;
302 
303             case 's':
304                 /* the value is a (char *) */
305                 txt = va_arg(args, char *);
306 
307                 /* if it's null, show "(null)" */
308                 if (txt == 0)
309                     txt = "(null)";
310 
311                 /* get the length, limiting it to the precision */
312                 {
313                     const char *p;
314 
315                     /*
316                      *   Scan until we reach a null terminator, or until our
317                      *   length reaches the maximum given by the precision
318                      *   qualifier.
319                      */
320                     for (txtlen = 0, p = txt ;
321                          *p != '\0' && (fld_prec == -1
322                                         || txtlen < (size_t)fld_prec) ;
323                          ++p, ++txtlen) ;
324                 }
325 
326                 /*
327                  *   if the 'approximation' flag is specified, limit the
328                  *   string to 60 characters or the field width, whichever
329                  *   is less
330                  */
331                 if (approx)
332                 {
333                     size_t lim;
334 
335                     /* the default limit is 60 characters */
336                     lim = 60;
337 
338                     /*
339                      *   if a field width is specified, it overrides the
340                      *   default - but take out three characters for the
341                      *   '...' suffix
342                      */
343                     if (fld_wid != -1 && (size_t)fld_wid > lim)
344                         lim = fld_wid - 3;
345 
346                     /* if we're over the limit, shorten the string */
347                     if (txtlen > lim)
348                     {
349                         /* shorten the string to the limit */
350                         txtlen = lim;
351 
352                         /* add an ellipsis to indicate the truncation */
353                         add_ellipsis = TRUE;
354                     }
355                 }
356 
357                 /*
358                  *   if a field width was specified and we have the default
359                  *   right alignment, pad to the left with spaces if the
360                  *   width is greater than the actual length
361                  */
362                 if (fld_wid != -1 && !left_align)
363                 {
364                     for ( ; (size_t)fld_wid > txtlen && rem != 0 ;
365                           --fld_wid, --rem)
366                         *dst++ = ' ';
367                 }
368                 break;
369 
370             case 'c':
371                 /* the value is a (char) */
372                 buf[0] = (char)va_arg(args, int);
373                 txt = buf;
374                 txtlen = 1;
375                 break;
376 
377             case 'd':
378                 /* the value is an int, formatted in decimal */
379                 sprintf(buf, "%d", va_arg(args, int));
380 
381             num_common:
382                 /* use the temporary buffer where we formatted the value */
383                 txt = buf;
384                 txtlen = strlen(buf);
385 
386                 /*
387                  *   Pad with leading spaces or zeroes if the requested
388                  *   field width exceeds the actual size and we have the
389                  *   default left alignment.
390                  */
391                 if (fld_wid != -1 && !left_align && (size_t)fld_wid > txtlen)
392                 {
393                     /*
394                      *   if we're showing leading zeroes, and we have a
395                      *   negative number, show the '-' first
396                      */
397                     if (lead_char == '0' && txt[0] == '-' && rem != 0)
398                     {
399                         /* add the '-' at the start of the output */
400                         *dst++ = '-';
401                         --rem;
402 
403                         /* we've shown the '-', so skip it in the number */
404                         ++txt;
405                         --txtlen;
406                     }
407 
408                     /* add the padding */
409                     for ( ; (size_t)fld_wid > txtlen && rem != 0 ;
410                           --fld_wid, --rem)
411                         *dst++ = lead_char;
412                 }
413                 break;
414 
415             case 'u':
416                 /* the value is an int, formatted as unsigned decimal */
417                 sprintf(buf, "%u", va_arg(args, int));
418                 goto num_common;
419 
420             case 'x':
421                 /* the value is an int, formatted in hex */
422                 sprintf(buf, "%x", va_arg(args, int));
423                 goto num_common;
424 
425             case 'o':
426                 /* the value is an int, formatted in hex */
427                 sprintf(buf, "%o", va_arg(args, int));
428                 goto num_common;
429 
430             case 'l':
431                 /* it's a 'long' value - get the second specifier */
432                 switch (*++fmt)
433                 {
434                 case 'd':
435                     /* it's a long, formatted in decimal */
436                     sprintf(buf, "%ld", va_arg(args, long));
437                     goto num_common;
438 
439                 case 'u':
440                     /* it's a long, formatted as unsigned decimal */
441                     sprintf(buf, "%lu", va_arg(args, long));
442                     goto num_common;
443 
444                 case 'x':
445                     /* it's a long, formatted in hex */
446                     sprintf(buf, "%lx", va_arg(args, long));
447                     goto num_common;
448 
449                 case 'o':
450                     /* it's a long, formatted in octal */
451                     sprintf(buf, "%lo", va_arg(args, long));
452                     goto num_common;
453 
454                 default:
455                     /* bad specifier - show the literal text */
456                     txt = "%";
457                     txtlen = 1;
458                     fmt = fmt_start;
459                     break;
460                 }
461                 break;
462 
463             default:
464                 /* bad specifier - show the literal text */
465                 txt = "%";
466                 txtlen = 1;
467                 fmt = fmt_start;
468                 break;
469             }
470 
471             /* add the text to the buffer */
472             for (i = txtlen ; i != 0 && rem != 0 ; --i, --rem)
473                 *dst++ = *txt++;
474 
475             /* add an ellipsis if desired */
476             if (add_ellipsis)
477             {
478                 /* add three '.' characters */
479                 for (i = 3 ; i != 0 && rem != 0 ; --i, --rem)
480                     *dst++ = '.';
481 
482                 /* for padding purposes, count the ellipsis */
483                 txtlen += 3;
484             }
485 
486             /*
487              *   if we have left alignment and the actual length is less
488              *   than the field width, pad to the right with spaces
489              */
490             if (left_align && fld_wid != -1 && (size_t)fld_wid > txtlen)
491             {
492                 /* add spaces to pad out to the field width */
493                 for (i = fld_wid ; i > txtlen && rem != 0 ; --i, --rem)
494                     *dst++ = ' ';
495             }
496         }
497         else
498         {
499             /* it's not a format specifier - just copy it literally */
500             *dst++ = *fmt;
501             --rem;
502         }
503     }
504 
505     /* add the trailing null */
506     *dst = '\0';
507 }
508 
509 
510 /* ------------------------------------------------------------------------ */
511 /*
512  *   Debugging routines for memory management
513  */
514 
515 #ifdef T3_DEBUG
516 
517 #include <stdio.h>
518 #include <stdlib.h>
519 
520 /*
521  *   memory block prefix - each block we allocate has this prefix attached
522  *   just before the pointer that we return to the program
523  */
524 struct mem_prefix_t
525 {
526     long id;
527     size_t siz;
528     mem_prefix_t *nxt;
529     mem_prefix_t *prv;
530 };
531 
532 /* head and tail of memory allocation linked list */
533 static mem_prefix_t *mem_head = 0;
534 static mem_prefix_t *mem_tail = 0;
535 
536 /*
537  *   Check the integrity of the heap: traverse the entire list, and make
538  *   sure the forward and backward pointers match up.
539  */
t3_check_heap()540 static void t3_check_heap()
541 {
542     mem_prefix_t *p;
543 
544     /* scan from the front */
545     for (p = mem_head ; p != 0 ; p = p->nxt)
546     {
547         /*
548          *   If there's a backwards pointer, make sure it matches up.  If
549          *   there's no backwards pointer, make sure we're at the head of
550          *   the list.  If this is the end of the list, make sure it
551          *   matches the tail pointer.
552          */
553         if ((p->prv != 0 && p->prv->nxt != p)
554             || (p->prv == 0 && p != mem_head)
555             || (p->nxt == 0 && p != mem_tail))
556             fprintf(stderr, "\n--- heap corrupted ---\n");
557     }
558 }
559 
560 /*
561  *   Allocate a block, storing it in a doubly-linked list of blocks and
562  *   giving the block a unique ID.
563  */
t3malloc(size_t siz)564 void *t3malloc(size_t siz)
565 {
566     static long id;
567     static int check = 0;
568     mem_prefix_t *mem;
569 
570     /* allocate the memory, including its prefix */
571     mem = (mem_prefix_t *)malloc(siz + sizeof(mem_prefix_t));
572 
573     /* if that failed, return failure */
574     if (mem == 0)
575         return 0;
576 
577     /* set up the prefix */
578     mem->id = id++;
579     mem->siz = siz;
580     mem->prv = mem_tail;
581     mem->nxt = 0;
582     if (mem_tail != 0)
583         mem_tail->nxt = mem;
584     else
585         mem_head = mem;
586     mem_tail = mem;
587 
588     /* check the heap for corruption if desired */
589     if (check)
590         t3_check_heap();
591 
592     /* return the caller's block, which immediately follows the prefix */
593     return (void *)(mem + 1);
594 }
595 
596 /*
597  *   reallocate a block - to simplify, we'll allocate a new block, copy
598  *   the old block up to the smaller of the two block sizes, and delete
599  *   the old block
600  */
t3realloc(void * oldptr,size_t newsiz)601 void *t3realloc(void *oldptr, size_t newsiz)
602 {
603     void *newptr;
604     size_t oldsiz;
605 
606     /* allocate a new block */
607     newptr = t3malloc(newsiz);
608 
609     /* copy the old block into the new block */
610     oldsiz = (((mem_prefix_t *)oldptr) - 1)->siz;
611     memcpy(newptr, oldptr, (oldsiz <= newsiz ? oldsiz : newsiz));
612 
613     /* free the old block */
614     t3free(oldptr);
615 
616     /* return the new block */
617     return newptr;
618 }
619 
620 
621 /* free a block, removing it from the allocation block list */
t3free(void * ptr)622 void t3free(void *ptr)
623 {
624     static int check = 0;
625     static int double_check = 0;
626     static int check_heap = 0;
627     mem_prefix_t *mem = ((mem_prefix_t *)ptr) - 1;
628     static long ckblk[] = { 0xD9D9D9D9, 0xD9D9D9D9, 0xD9D9D9D9 };
629     size_t siz;
630 
631     /* check the integrity of the entire heap if desired */
632     if (check_heap)
633         t3_check_heap();
634 
635     /* check for a pre-freed block */
636     if (memcmp(mem, ckblk, sizeof(ckblk)) == 0)
637     {
638         fprintf(stderr, "\n--- memory block freed twice ---\n");
639         return;
640     }
641 
642     /* if desired, check to make sure the block is in our list */
643     if (check)
644     {
645         mem_prefix_t *p;
646         for (p = mem_head ; p != 0 ; p = p->nxt)
647         {
648             if (p == mem)
649                 break;
650         }
651         if (p == 0)
652             fprintf(stderr, "\n--- memory block not found in t3free ---\n");
653     }
654 
655     /* unlink the block from the list */
656     if (mem->prv != 0)
657         mem->prv->nxt = mem->nxt;
658     else
659         mem_head = mem->nxt;
660 
661     if (mem->nxt != 0)
662         mem->nxt->prv = mem->prv;
663     else
664         mem_tail = mem->prv;
665 
666     /*
667      *   if we're being really cautious, check to make sure the block is
668      *   no longer in the list
669      */
670     if (double_check)
671     {
672         mem_prefix_t *p;
673         for (p = mem_head ; p != 0 ; p = p->nxt)
674         {
675             if (p == mem)
676                 break;
677         }
678         if (p != 0)
679             fprintf(stderr, "\n--- memory block still in list after "
680                     "t3free ---\n");
681     }
682 
683     /* make it obvious that the memory is invalid */
684     siz = mem->siz;
685     memset(mem, 0xD9, siz + sizeof(mem_prefix_t));
686 
687     /* free the memory with the system allocator */
688     free((void *)mem);
689 }
690 
691 /*
692  *   Default display lister callback
693  */
fprintf_stderr(const char * msg)694 static void fprintf_stderr(const char *msg)
695 {
696     fprintf(stderr, "%s", msg);
697 }
698 
699 /*
700  *   Diagnostic routine to display the current state of the heap.  This
701  *   can be called just before program exit to display any memory blocks
702  *   that haven't been deleted yet; any block that is still in use just
703  *   before program exit is a leaked block, so this function can be useful
704  *   to help identify and remove memory leaks.
705  */
t3_list_memory_blocks(void (* cb)(const char *))706 void t3_list_memory_blocks(void (*cb)(const char *))
707 {
708     mem_prefix_t *mem;
709     int cnt;
710     char buf[128];
711 
712     /* if there's no callback, use our own standard display lister */
713     if (cb == 0)
714         cb = fprintf_stderr;
715 
716     /* display introductory message */
717     (*cb)("\n(T3VM) Memory blocks still in use:\n");
718 
719     /* display the list of undeleted memory blocks */
720     for (mem = mem_head, cnt = 0 ; mem ; mem = mem->nxt, ++cnt)
721     {
722         sprintf(buf, "  id = %ld, siz = %d\n", mem->id, mem->siz);
723         (*cb)(buf);
724     }
725 
726     /* display totals */
727     sprintf(buf, "\nTotal blocks in use: %d\n", cnt);
728     (*cb)(buf);
729 }
730 
731 #endif /* T3_DEBUG */
732