/*********************************************************** Copyright 1991, 1992, 1993 by Stichting Mathematisch Centrum, Amsterdam, The Netherlands. All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the names of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ******************************************************************/ /* String object implementation */ #include "allobjects.h" object * newsizedstringobject(str, size) char *str; int size; { register stringobject *op = (stringobject *) malloc(sizeof(stringobject) + size * sizeof(char)); if (op == NULL) return err_nomem(); op->ob_type = &Stringtype; op->ob_size = size; NEWREF(op); if (str != NULL) memcpy(op->ob_sval, str, size); op->ob_sval[size] = '\0'; return (object *) op; } object * newstringobject(str) char *str; { register unsigned int size = strlen(str); register stringobject *op = (stringobject *) malloc(sizeof(stringobject) + size * sizeof(char)); if (op == NULL) return err_nomem(); op->ob_type = &Stringtype; op->ob_size = size; NEWREF(op); strcpy(op->ob_sval, str); return (object *) op; } static void string_dealloc(op) object *op; { DEL(op); } unsigned int getstringsize(op) register object *op; { if (!is_stringobject(op)) { err_badcall(); return -1; } return ((stringobject *)op) -> ob_size; } /*const*/ char * getstringvalue(op) register object *op; { if (!is_stringobject(op)) { err_badcall(); return NULL; } return ((stringobject *)op) -> ob_sval; } /* Methods */ static int string_print(op, fp, flags) stringobject *op; FILE *fp; int flags; { int i; char c; /* XXX Ought to check for interrupts when writing long strings */ if (flags & PRINT_RAW) { fwrite(op->ob_sval, 1, (int) op->ob_size, fp); return 0; } fprintf(fp, "'"); for (i = 0; i < op->ob_size; i++) { c = op->ob_sval[i]; if (c == '\'' || c == '\\') fprintf(fp, "\\%c", c); else if (c < ' ' || c >= 0177) fprintf(fp, "\\%03o", c&0377); else putc(c, fp); } fprintf(fp, "'"); return 0; } static object * string_repr(op) register stringobject *op; { /* XXX overflow? */ int newsize = 2 + 4 * op->ob_size * sizeof(char); object *v = newsizedstringobject((char *)NULL, newsize); if (v == NULL) { return NULL; } else { register int i; register char c; register char *p; p = ((stringobject *)v)->ob_sval; *p++ = '\''; for (i = 0; i < op->ob_size; i++) { c = op->ob_sval[i]; if (c == '\'' || c == '\\') *p++ = '\\', *p++ = c; else if (c < ' ' || c >= 0177) { sprintf(p, "\\%03o", c&0377); while (*p != '\0') p++; } else *p++ = c; } *p++ = '\''; *p = '\0'; resizestring(&v, (int) (p - ((stringobject *)v)->ob_sval)); return v; } } static int string_length(a) stringobject *a; { return a->ob_size; } static object * string_concat(a, bb) register stringobject *a; register object *bb; { register unsigned int size; register stringobject *op; if (!is_stringobject(bb)) { err_badarg(); return NULL; } #define b ((stringobject *)bb) /* Optimize cases with empty left or right operand */ if (a->ob_size == 0) { INCREF(bb); return bb; } if (b->ob_size == 0) { INCREF(a); return (object *)a; } size = a->ob_size + b->ob_size; op = (stringobject *) malloc(sizeof(stringobject) + size * sizeof(char)); if (op == NULL) return err_nomem(); op->ob_type = &Stringtype; op->ob_size = size; NEWREF(op); memcpy(op->ob_sval, a->ob_sval, (int) a->ob_size); memcpy(op->ob_sval + a->ob_size, b->ob_sval, (int) b->ob_size); op->ob_sval[size] = '\0'; return (object *) op; #undef b } static object * string_repeat(a, n) register stringobject *a; register int n; { register int i; register unsigned int size; register stringobject *op; if (n < 0) n = 0; size = a->ob_size * n; if (size == a->ob_size) { INCREF(a); return (object *)a; } op = (stringobject *) malloc(sizeof(stringobject) + size * sizeof(char)); if (op == NULL) return err_nomem(); op->ob_type = &Stringtype; op->ob_size = size; NEWREF(op); for (i = 0; i < size; i += a->ob_size) memcpy(op->ob_sval+i, a->ob_sval, (int) a->ob_size); op->ob_sval[size] = '\0'; return (object *) op; } /* String slice a[i:j] consists of characters a[i] ... a[j-1] */ static object * string_slice(a, i, j) register stringobject *a; register int i, j; /* May be negative! */ { if (i < 0) i = 0; if (j < 0) j = 0; /* Avoid signed/unsigned bug in next line */ if (j > a->ob_size) j = a->ob_size; if (i == 0 && j == a->ob_size) { /* It's the same as a */ INCREF(a); return (object *)a; } if (j < i) j = i; return newsizedstringobject(a->ob_sval + i, (int) (j-i)); } #ifdef __STDC__ #include #else #ifndef UCHAR_MAX #define UCHAR_MAX 255 #endif #endif static object *characters[UCHAR_MAX + 1]; static object * string_item(a, i) stringobject *a; register int i; { int c; object *v; if (i < 0 || i >= a->ob_size) { err_setstr(IndexError, "string index out of range"); return NULL; } c = a->ob_sval[i] & UCHAR_MAX; v = characters[c]; if (v == NULL) { v = newsizedstringobject((char *)NULL, 1); if (v == NULL) return NULL; characters[c] = v; ((stringobject *)v)->ob_sval[0] = c; } INCREF(v); return v; } static int string_compare(a, b) stringobject *a, *b; { int len_a = a->ob_size, len_b = b->ob_size; int min_len = (len_a < len_b) ? len_a : len_b; int cmp = memcmp(a->ob_sval, b->ob_sval, min_len); if (cmp != 0) return cmp; return (len_a < len_b) ? -1 : (len_a > len_b) ? 1 : 0; } static long string_hash(a) stringobject *a; { register int len = a->ob_size; register unsigned char *p = (unsigned char *) a->ob_sval; register long x = *p << 7; while (--len >= 0) x = (x + x + x) ^ *p++; x ^= a->ob_size; if (x == -1) x = -2; return x; } static sequence_methods string_as_sequence = { string_length, /*sq_length*/ string_concat, /*sq_concat*/ string_repeat, /*sq_repeat*/ string_item, /*sq_item*/ string_slice, /*sq_slice*/ 0, /*sq_ass_item*/ 0, /*sq_ass_slice*/ }; typeobject Stringtype = { OB_HEAD_INIT(&Typetype) 0, "string", sizeof(stringobject), sizeof(char), string_dealloc, /*tp_dealloc*/ string_print, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ string_compare, /*tp_compare*/ string_repr, /*tp_repr*/ 0, /*tp_as_number*/ &string_as_sequence, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ string_hash, /*tp_hash*/ }; void joinstring(pv, w) register object **pv; register object *w; { register object *v; if (*pv == NULL || w == NULL || !is_stringobject(*pv)) return; v = string_concat((stringobject *) *pv, w); DECREF(*pv); *pv = v; } /* The following function breaks the notion that strings are immutable: it changes the size of a string. We get away with this only if there is only one module referencing the object. You can also think of it as creating a new string object and destroying the old one, only more efficiently. In any case, don't use this if the string may already be known to some other part of the code... */ int resizestring(pv, newsize) object **pv; int newsize; { register object *v; register stringobject *sv; v = *pv; if (!is_stringobject(v) || v->ob_refcnt != 1) { *pv = 0; DECREF(v); err_badcall(); return -1; } /* XXX UNREF/NEWREF interface should be more symmetrical */ #ifdef REF_DEBUG --ref_total; #endif UNREF(v); *pv = (object *) realloc((char *)v, sizeof(stringobject) + newsize * sizeof(char)); if (*pv == NULL) { DEL(v); err_nomem(); return -1; } NEWREF(*pv); sv = (stringobject *) *pv; sv->ob_size = newsize; sv->ob_sval[newsize] = '\0'; return 0; } /* Helpers for formatstring */ static object * getnextarg(args, arglen, p_argidx) object *args; int arglen; int *p_argidx; { int argidx = *p_argidx; if (argidx < arglen) { (*p_argidx)++; if (arglen < 0) return args; else return gettupleitem(args, argidx); } err_setstr(TypeError, "not enough arguments for format string"); return NULL; } #define F_LJUST (1<<0) #define F_SIGN (1<<1) #define F_BLANK (1<<2) #define F_ALT (1<<3) #define F_ZERO (1<<4) extern double fabs PROTO((double)); static char * formatfloat(flags, prec, type, v) int flags; int prec; int type; object *v; { char fmt[20]; static char buf[120]; double x; if (!getargs(v, "d;float argument required", &x)) return NULL; if (prec < 0) prec = 6; if (prec > 50) prec = 50; /* Arbitrary limitation */ if (type == 'f' && fabs(x)/1e25 >= 1e25) type = 'g'; sprintf(fmt, "%%%s.%d%c", (flags&F_ALT) ? "#" : "", prec, type); sprintf(buf, fmt, x); return buf; } static char * formatint(flags, prec, type, v) int flags; int prec; int type; object *v; { char fmt[20]; static char buf[50]; long x; if (!getargs(v, "l;int argument required", &x)) return NULL; if (prec < 0) prec = 1; sprintf(fmt, "%%%s.%dl%c", (flags&F_ALT) ? "#" : "", prec, type); sprintf(buf, fmt, x); return buf; } static char * formatchar(v) object *v; { static char buf[2]; if (is_stringobject(v)) { if (!getargs(v, "c;%c requires int or char", &buf[0])) return NULL; } else { if (!getargs(v, "b;%c requires int or char", &buf[0])) return NULL; } buf[1] = '\0'; return buf; } /* fmt%(v1,v2,...) is roughly equivalent to sprintf(fmt, v1, v2, ...) */ object * formatstring(format, args) object *format; object *args; { char *fmt, *res; int fmtcnt, rescnt, reslen, arglen, argidx; object *result; if (format == NULL || !is_stringobject(format) || args == NULL) { err_badcall(); return NULL; } fmt = getstringvalue(format); fmtcnt = getstringsize(format); reslen = rescnt = fmtcnt + 100; result = newsizedstringobject((char *)NULL, reslen); if (result == NULL) return NULL; res = getstringvalue(result); if (is_tupleobject(args)) { arglen = gettuplesize(args); argidx = 0; } else { arglen = -1; argidx = -2; } while (--fmtcnt >= 0) { if (*fmt != '%') { if (--rescnt < 0) { rescnt = fmtcnt + 100; reslen += rescnt; if (resizestring(&result, reslen) < 0) return NULL; res = getstringvalue(result) + reslen - rescnt; } *res++ = *fmt++; } else { /* Got a format specifier */ int flags = 0; char *fmtstart = fmt++; int width = -1; int prec = -1; int size = 0; int c; int fill; object *v; char *buf; int sign; int len; while (--fmtcnt >= 0) { switch (c = *fmt++) { case '-': flags |= F_LJUST; continue; case '+': flags |= F_SIGN; continue; case ' ': flags |= F_BLANK; continue; case '#': flags |= F_ALT; continue; case '0': flags |= F_ZERO; continue; } break; } if (c == '*') { v = getnextarg(args, arglen, &argidx); if (v == NULL) goto error; if (!is_intobject(v)) { err_setstr(TypeError, "* wants int"); goto error; } width = getintvalue(v); if (width < 0) width = 0; if (--fmtcnt >= 0) c = *fmt++; } else if (isdigit(c)) { width = c - '0'; while (--fmtcnt >= 0) { c = *fmt++; if (!isdigit(c)) break; if ((width*10) / 10 != width) { err_setstr(ValueError, "width too big"); goto error; } width = width*10 + (c - '0'); } } if (c == '.') { prec = 0; if (--fmtcnt >= 0) c = *fmt++; if (c == '*') { v = getnextarg(args, arglen, &argidx); if (v == NULL) goto error; if (!is_intobject(v)) { err_setstr(TypeError, "* wants int"); goto error; } prec = getintvalue(v); if (prec < 0) prec = 0; if (--fmtcnt >= 0) c = *fmt++; } else if (isdigit(c)) { prec = c - '0'; while (--fmtcnt >= 0) { c = *fmt++; if (!isdigit(c)) break; if ((prec*10) / 10 != prec) { err_setstr(ValueError, "prec too big"); goto error; } prec = prec*10 + (c - '0'); } } } /* prec */ if (fmtcnt >= 0) { if (c == 'h' || c == 'l' || c == 'L') { size = c; if (--fmtcnt >= 0) c = *fmt++; } } if (fmtcnt < 0) { err_setstr(ValueError, "incomplete format"); goto error; } if (c != '%') { v = getnextarg(args, arglen, &argidx); if (v == NULL) goto error; } sign = 0; fill = ' '; switch (c) { case '%': buf = "%"; len = 1; break; case 's': if (!is_stringobject(v)) { err_setstr(TypeError, "%s wants string"); goto error; } buf = getstringvalue(v); len = getstringsize(v); if (prec >= 0 && len > prec) len = prec; break; case 'i': case 'd': case 'u': case 'o': case 'x': case 'X': if (c == 'i') c = 'd'; buf = formatint(flags, prec, c, v); if (buf == NULL) goto error; len = strlen(buf); sign = (c == 'd'); if (flags&F_ZERO) fill = '0'; break; case 'e': case 'E': case 'f': case 'g': case 'G': buf = formatfloat(flags, prec, c, v); if (buf == NULL) goto error; len = strlen(buf); sign = 1; if (flags&F_ZERO) fill = '0'; break; case 'c': buf = formatchar(v); if (buf == NULL) goto error; len = strlen(buf); break; default: err_setstr(ValueError, "unsupported format character"); goto error; } /* XXX There's a bug somewhere here so that XXX '%4d'%-1 yields '- 1' ... */ if (sign) { if (*buf == '-' || *buf == '+') { sign = *buf++; len--; } else if (flags & F_SIGN) sign = '+'; else if (flags & F_BLANK) sign = ' '; else sign = '\0'; } if (width < len) width = len; if (rescnt < width + (sign != '\0')) { reslen -= rescnt; rescnt = width + fmtcnt + 100; reslen += rescnt; if (resizestring(&result, reslen) < 0) return NULL; res = getstringvalue(result) + reslen - rescnt; } if (sign) { *res++ = sign; rescnt--; if (width > len) width--; } if (width > len && !(flags&F_LJUST)) { do { --rescnt; *res++ = fill; } while (--width > len); } memcpy(res, buf, len); res += len; rescnt -= len; while (--width >= len) { --rescnt; *res++ = ' '; } } /* '%' */ } /* until end */ if (argidx < arglen) { err_setstr(TypeError, "not all arguments converted"); goto error; } resizestring(&result, reslen - rescnt); return result; error: DECREF(result); return NULL; }