/* * Copyright (C) 1994 Haijo Schipper (abigail@xs4all.nl) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ # include "version.h" # include <limits.h> # include "config.h" # include <type.h> # include "macros.h" # include "extra.c" # ifdef __TIME_CONVERSION__ # include "time.h" # include "time.c" # endif private string convert_to_base (int i, int base); string query_version () {return (VERSION);} private int charp (mixed arg) { return (intp (arg) && CHAR_MIN <= arg && arg <= CHAR_MAX); } private string anything (mixed this) { switch (typeof (this)) { case T_INT: case T_FLOAT: return (this + ""); Case T_STRING: return ("\"" + this + "\""); Case T_OBJECT: return ("OBJ <" + file_name (this) + ">"); Case T_ARRAY: { int i, sz; string * res; for (i = 0, res = allocate (sz = sizeof (this)); i < sz; i ++) { res [i] = anything (this [i]); } return ( "({ " + implode (res, ", ") + " })"); } Case T_MAPPING: { int i, sz; mixed * idx, * vals; string * res; for (i = 0, res = allocate (sz = sizeof (idx = m_indices (this))), vals = m_values (this); i < sz; i ++) { res [i] = anything (idx [i]) + " : " + anything (vals [i]); } return ( "([ " + implode (res, ", ") + " ])"); } } } private string give_padding (int n, string pad) { string padding; if (n <= 0) {return ("");} padding = pad; for (; strlen (padding) < n; padding += padding); return (padding [.. n - 1]); } /* Apply flags, width & precision to string. */ private string align (string this, int width, int precision, mapping options, string padding) { int sz; if (options ["`"]) {this = reverse (this);} if (options ["~"]) {this = flip_case (this);} if (options ["<"]) {this = lower_case (this);} if (options [">"]) {this = upper_case (this);} if (options ["="]) {this = capitalize (this);} if (options ["&"]) {this = rot_13 (this);} sz = strlen (this); if (options ["-"]) {this += give_padding (width - sz, padding);} else { if (options ["|"]) { this = give_padding ((width + 1 - sz) / 2, padding) + this + give_padding ((width - sz) / 2, padding); } else {this = give_padding (width - sz, padding) + this;} } return ((precision <= 0) || strlen (this) < precision ? this : options ["_"] ? this [strlen (this) - precision ..] : this [.. precision - 1]); } private string name_in_base (int i, int base) { /* Assume i < base */ string tmp; tmp = "?"; switch (i) { case 0 .. 9 : tmp [0] = ('0' + i); return (tmp); case 10 .. 35 : tmp [0] = ('a' + i - 10); return (tmp); case 36 .. 51 : tmp [0] = ('A' + i - 36); return (tmp); default : return (tmp); } } private string convert_to_base (int i, int base) { return (i < base ? name_in_base (i, base) : convert_to_base (i / base, base) + name_in_base (i % base, base)); } private string numerical (int n, int base, int width, int precision, mapping options, string padding) { string digits, sign, header, this; int sz; if (base != BASE) {digits = convert_to_base ((n < 0 ? -n : n), base);} else {digits = n + "";} sign = (n < 0 ? "-" : options ["+"] ? "+" : options [" "] ? " " : ""); header = (options ["#"] ? (base == 8 ? "0" : base == 16 ? "0x" : "") : ""); # ifdef __TIME_CONVERSION__ if (precision <= 0) {precision = options ["T"];} # endif if (precision > 0) { digits = give_padding (precision - strlen (digits), "0") + digits; } if (options ["0"] && !options ["-"]) { digits = give_padding (width - strlen (digits + sign + header), "0") + digits; } sz = strlen (this = sign + header + digits); if (options ["X"]) {this = upper_case (this);} if (options ["-"]) {this += give_padding (width - sz, padding);} else {this = give_padding (width - sz, padding) + this;} return (this); } private string round_off (string arg) { int i; i = strlen (arg) - 1; if (arg [i] != '9') {arg [i] += 1; return (arg);} if (i == 0) {return ("10");} return (round_off (arg [.. i - 1]) + "0"); } private string do_float (float x, int width, int precision, mapping options, string padding, int exponent) { float * parts; float i_x, f_x; int e_x; string i_res, f_res, e_res, result; string sign; int i, sz; if (x < 0.0) {sign = "-"; x = -x;} else {if (options ["+"]) {sign = "+";}} if (width && sign) {width --;} if (precision == 0) {precision = FLT_PRECISION;} /* Find exponent. */ if (exponent) { float y; y = x; while (x > 10.0) {e_x ++; x /= 10.0;} while (x < 1.0) {e_x --; x *= 10.0;} if (exponent & 4) { if (e_x > -4 && e_x < (precision == -2 ? 0 : precision)) { /* Use %f. */ x = y; exponent &= 6; /* Don't print exponent. */ } } } parts = modf (x); f_x = parts [0]; i_x = parts [1]; /* Build string for integer part. */ for (i_res = ""; i_x >= 1.0; i_x = i_x / 10.0) { i_res = (string) (int) floor (fmod (i_x, 10.0)) + i_res; } if (!strlen (i_res)) {i_res = "0";} if ((exponent & 4) && precision != -2) { precision -= strlen (i_res); } /* Now precision should be the number of digits after the period. */ /* Build string for fractional part. */ if (f_x == 0.0) {f_res = "0";} else { for (f_res = ""; f_x < 0.1; f_res += "0", f_x *= 10.0); f_res += ((string) f_x) [2 ..]; } if (precision == -2) {f_res = (options ["#"] ? "." : "");} else { if (strlen (f_res) == precision) { f_res = "." + f_res; } else { if (strlen (f_res) > precision) { if (f_res [precision] > '4') { /* Round off away from zero. */ f_res = round_off (f_res [.. precision - 1]); if (precision != strlen (f_res)) { /* Overflow to integer part. */ i_res = round_off (i_res); if ((exponent & 1) && i_res == "10") { /* Overflow to exponent */ e_x ++; i_res = "1"; } f_res = give_padding (precision, "0"); /* Fill with 0's. */ } f_res = "." + f_res; } else {f_res = "." + f_res [.. precision - 1];} } else { f_res = "." + f_res + give_padding (precision - strlen (f_res), "0"); } } } if ((exponent & 4) && strlen (f_res)) { /* Strip trailing 0's and period. */ for (i = strlen (f_res); f_res [-- i] == '0';); if (i) {f_res = f_res [.. i];} else {f_res = (options ["#"] ? "." : "");} } /* Build string for exponent part. */ if (exponent & 1) { e_res = ((exponent & 2) ? "E" : "e"); if (e_x < 0) {e_res += "-"; e_x = - e_x;} else {e_res += "+";} e_res += (e_x < 9 ? e_x == 0 ? "00" : "0" + e_x : (string) e_x); } else {e_res = "";} /* Add padding and sign. */ if ((sz = strlen (result = i_res + f_res + e_res)) < width) { string pads; if (options ["0"]) {padding = "0";} pads = give_padding (width - sz, padding); if (padding == " ") {result = pads + (sign ? sign : "") + result;} else {result = (sign ? sign : "") + pads + result;} } else {if (sign) {result = sign + result;}} return (result); } /* Scanning of the format strings. Result is an array of strings, */ /* either a conversion sequence, or a string of 'normal' chars. */ private string * make_chunks (string format) { string * result; string current; int i, j, sz; string tmp; string option; result = ({ }); i = j = 0; sz = strlen (format); while (i < sz) { if (CONV_CHAR [format [i]]) { /* Encountered a '%' or '@' */ option = format [i .. i]; i ++; /* Scan flags. */ while (i < sz && FLAGS [format [i]]) {i ++;} ENDCHECK (i, sz); tmp = format [j + 1 .. i - 1] + ","; /* Flags */ j = i; /* Scan width */ if (format [i] == '*') { i ++; tmp += "-1" + ","; } else { while (i < sz && DIGIT [format [i]]) {i ++;} tmp += (format [j .. i - 1] == "" ? "0" : format [j .. i - 1]) + ","; } ENDCHECK (i, sz); /* Scan precision */ if (format [i] == '.') { i ++; ENDCHECK (i, sz); j = i; if (format [i] == '*') { i ++; tmp += "-1" + ","; } else { while (i < sz && DIGIT [format [i]]) {i ++;} tmp += (format [j .. i - 1] == "" ? "0" : format [j .. i - 1]) + ","; } ENDCHECK (i, sz); } /* Chunck */ result += ({ option + format [i .. i] + tmp }); j = ++ i; } else { do { i ++; } while (i < sz && !CONV_CHAR [format [i]]); result += ({ format [j .. i - 1] }); j = i; } } return (result); } # ifdef __CLOSE_TO_C__ static varargs int sprintf (string out, string format, mixed args...) { # else static varargs string sprintf (string format, mixed args...) { # endif mixed * arguments; string * chunks; string result; string flags, padding; string tmp; string cur; int i, j, width, precision, sz, sz_args; int exp; mapping options; object query_object; # ifdef __CLOSE_TO_C__ TYPECHECK (array, out, 0); if (!sizeof (out)) {error ("Sprintf: empty first argument");} # endif TYPECHECK (string, format, 1); chunks = make_chunks (format); padding = " "; for (i = 0, sz = sizeof (chunks), sz_args = sizeof (args), result = ""; i < sz; i ++) { if (!CONV_CHAR [chunks [i] [0]]) {result += chunks [i];} else { if (chunks [i] [0 .. 1] == "%%" || chunks [i] [0 .. 1] == "@@") { result += chunks [i] [1 .. 1]; } else { width = precision = 0; ARGCOUNTCHECK (j, sz_args); switch (sscanf (chunks [i] [2 ..], "%s,%d,%d", flags, width, precision)) { case 3: if (!precision) {precision = -2;} # if 0 NOTE case 2: if (!width) {width = -2;} /* Reserved for future use? */ # endif } if (width == -1) { TYPECHECK (int, args [j], j + 2); width = args [j ++]; ARGCOUNTCHECK (j, sz_args); } if (precision == -1) { TYPECHECK (int, args [j], j + 2); precision = args [j ++]; ARGCOUNTCHECK (j, sz_args); } options = mkmapping (explode (flags, ""), explode (flags, "")); switch (cur = chunks [i] [.. 1]) { case "%A": case "%a": { int k, sk; TYPECHECK (array, args [j], j + 2); for (k = 0, sk = sizeof (args [j]); k < sk; k ++) { if (cur == "%a" && nump (args [j] [k])) {args [j] [k] += "";} TYPECHECK (string, args [j] [k], j + 2); result += align (args [j] [k], width, precision, options, padding); } } Case "%b": /* Binary */ case "%d": /* Decimal */ case "%i": case "%o": /* Octal */ case "%x": /* Hexadecimal */ case "%X": TYPECHECK (int, args [j], j + 2); result += numerical (args [j], cur == "%b" ? 2 : cur == "%o" ? 8 : cur == "%x" || cur == "%X" ? 16 : BASE, width, precision, cur == "%X" ? (options + ([ "X" : 1 ])) : options, padding); Case "%c": TYPECHECK (char, args [j], j + 2); tmp = " "; tmp [0] = args [j]; result += align (tmp, width, precision, options, padding); Case "%H": TYPECHECK (string, args [j], j + 2); result += align (make_hex (args [j], padding), width, precision, options, padding); Case "%h": TYPECHECK (string, args [j], j + 2); result += align (make_hex (args [j], 0), width, precision, options, padding); Case "%n": TYPECHECK (array, args [j], j + 2); if (!sizeof (args [j])) {error ("No space for %n conversion");} args [j] [0] = strlen (result); Case "%O": TYPECHECK (object, args [j], j + 2); result += align (file_name (args [j]), width, precision, options, padding); Case "%p": TYPECHECK (char, args [j], j + 2); padding = "X"; padding [0] = args [j]; Case "%Q": TYPECHECK (object, args [j], j + 2); query_object = args [j]; Case "%q": { mixed res; TYPECHECK (string, args [j], j + 2); if (!query_object) {error ("%q used before %Q in sprintf.");} res = (mixed) call_other (query_object, args [j]); if (intp (res)) {res += "";} if (!stringp (res)) { res = anything (res); } result += align (res, width, precision, options, padding); } Case "%R": TYPECHECK (string, args [j], j + 2); result += align (crypt (args [j], 1), width, precision, options, padding); Case "%r": TYPECHECK (string, args [j], j + 2); result += align (crypt (args [j], 0), width, precision, options, padding); Case "%s": if (nump (args [j])) {args [j] += "";} NOTE case "%S": TYPECHECK (string, args [j], j + 2); result += align (args [j], width, precision, options, padding); Case "%y": result += align (anything (args [j]), width, precision, options, padding); # ifdef __FLOATS__ /* Bit 0 for exp, bit 1 for uppercase, bit 2 for conditional exp */ Case "%G": case "%g": exp |= 4; case "%E": if (cur != "%g") {exp |= 2;} case "%e": exp |= 1; case "%f": if (intp (args [j])) {args [j] = (float) args [j];} TYPECHECK (float, args [j], j + 2); result += do_float (args [j], width, precision, options, padding, exp); exp = 0; /* Don't leave this out! */ # endif # ifdef __TIME_CONVERSION__ /* Time related conversions. */ Case "@a": TYPECHECK (int, args [j], j + 2); result += align (weekday (args [j], 0), width, precision, options, padding); Case "@A": TYPECHECK (int, args [j], j + 2); result += align (weekday (args [j], 1), width, precision, options, padding); Case "@b": TYPECHECK (int, args [j], j + 2); result += align (month (args [j], 0), width, precision, options, padding); Case "@B": TYPECHECK (int, args [j], j + 2); result += align (month (args [j], 1), width, precision, options, padding); Case "@c": TYPECHECK (int, args [j], j + 2); result += align (ctime (args [j]), width, precision, options, padding); Case "@d": TYPECHECK (int, args [j], j + 2); result += numerical (day (args [j]), BASE, width, precision, options + ([ "T" : 2 ]), padding); Case "@H": TYPECHECK (int, args [j], j + 2); result += numerical (hour (args [j]), BASE, width, precision, options + ([ "T" : 2 ]), padding); Case "@I": { int h; TYPECHECK (int, args [j], j + 2); result += numerical ((h = hour (args [j])) ? h % NOON : NOON, BASE, width, precision, options + ([ "T" : 2 ]), padding); } Case "@j": TYPECHECK (int, args [j], j + 2); result += numerical (day_of_year (args [j]), BASE, width, precision, options + ([ "T" : 3 ]), padding); Case "@m": TYPECHECK (int, args [j], j + 2); result += numerical (month (args [j], 2) + 1, BASE, width, precision, options + ([ "T" : 2 ]), padding); Case "@M": TYPECHECK (int, args [j], j + 2); result += numerical (minute (args [j]), BASE, width, precision, options + ([ "T" : 2 ]), padding); Case "@p": TYPECHECK (int, args [j], j + 2); result += align (hour (args [j]) < NOON ? AM : PM, width, precision, options, padding); Case "@S": TYPECHECK (int, args [j], j + 2); result += numerical (second (args [j]), BASE, width, precision, options + ([ "T" : 2 ]), padding); Case "@U": TYPECHECK (int, args [j], j + 2); result += numerical (week_number (args [j], 0), BASE, width, precision, options + ([ "T" : 2 ]), padding); Case "@w": TYPECHECK (int, args [j], j + 2); result += numerical (weekday (args [j], 2), BASE, width, precision, options + ([ "T" : 1 ]), padding); Case "@W": TYPECHECK (int, args [j], j + 2); result += numerical (week_number (args [j], 1), BASE, width, precision, options + ([ "T" : 2 ]), padding); Case "@x": TYPECHECK (int, args [j], j + 2); result += align (date (args [j]), width, precision, options, padding); Case "@X": TYPECHECK (int, args [j], j + 2); result += align (ttime (args [j]), width, precision, options, padding); Case "@y": TYPECHECK (int, args [j], j + 2); result += numerical (year (args [j]) % 100, BASE, width, precision, options + ([ "T" : 2 ]), padding); Case "@Y": TYPECHECK (int, args [j], j + 2); result += numerical (year (args [j]), BASE, width, precision, options, padding); # ifdef __TIME_ZONE__ Case "@Z": TYPECHECK (int, args [j], j + 2); result += align (timezone (args [j]), width, precision, options, padding); # endif # endif Default : error ("Unknown conversation character " + cur); } j ++; } } } # ifdef __CLOSE_TO_C__ out [0] = result; return (strlen (result)); # else return (result); # endif }