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