0001: /*
0002: * FormatCmd.java
0003: *
0004: * Copyright (c) 1997 Sun Microsystems, Inc.
0005: *
0006: * See the file "license.terms" for information on usage and
0007: * redistribution of this file, and for a DISCLAIMER OF ALL
0008: * WARRANTIES.
0009: *
0010: * RCS: @(#) $Id: FormatCmd.java,v 1.13 2006/05/14 22:07:49 mdejong Exp $
0011: *
0012: */
0013:
0014: package tcl.lang;
0015:
0016: import java.util.*;
0017:
0018: /**
0019: * This class implements the built-in "format" command in Tcl.
0020: */
0021:
0022: class FormatCmd implements Command {
0023:
0024: private static final int LEFT_JUSTIFY = 1;
0025: private static final int SHOW_SIGN = 2;
0026: private static final int SPACE_OR_SIGN = 4;
0027: private static final int PAD_W_ZERO = 8;
0028: private static final int ALT_OUTPUT = 16;
0029: private static final int SIGNED_VALUE = 32;
0030: private static final int RADIX = 1; // Integer types. %d, %x, %o
0031: private static final int FLOAT = 2; // Floating point. %f
0032: private static final int EXP = 3; // Exponentional. %e and %E
0033: private static final int GENERIC = 4; // Floating or exponential,
0034:
0035: // depending on exponent. %g
0036:
0037: /**
0038: * This procedure is invoked to process the "format" Tcl command.
0039: * See the user documentation for details on what it does.
0040: *
0041: * The first argument to the cmdProc is the formatString. The cmdProc
0042: * simply copies all the chars into the sbuf until a '%' is found. At
0043: * this point the cmdProc parces the formatString and determines the
0044: * format parameters. The parcing of the formatString can be broken into
0045: * six possible phases:
0046: *
0047: * Phase 0 - Simply Print: If the next char is %
0048: * Phase 1 - XPG3 Position Specifier: If the format [1-n]$ is used
0049: * Phase 2 - A Set of Flags: One or more of the following + -
0050: * [space] 0 #
0051: * Phase 3 - A Minimun Field Width Either [integer] or *
0052: * Phase 4 - A Precision If the format .[integer] or .*
0053: * Phase 5 - A Length Modifier If h is present
0054: * Phase 6 - A Conversion Character If one of the following is used
0055: * d u i o x X c s f E g G
0056: *
0057: * Any phase can skip ahead one or more phases, but are not allowed
0058: * to move back to previous phases. Once the parameters are determined
0059: * the cmdProc calls one of three private methods that returns a fully
0060: * formatted string. This loop occurs for ever '%' in the formatString.
0061: */
0062:
0063: public void cmdProc(Interp interp, TclObject argv[])
0064: throws TclException {
0065:
0066: StringBuffer sbuf; // Stores the return value of the parsed
0067: // format string
0068: StrtoulResult stoul; // A result object to pass to strtoul call
0069: char[] format; // The format argument is converted to a char
0070: // array and manipulated as such
0071: int phase; // Stores the current phase of the parsing
0072: int width; // Minimum field width
0073: int precision; // Field precision from field specifier
0074: int fmtFlags; // Used to store the format flags ( #,+,etc)
0075: int argIndex; // Index of argument to substitute next.
0076: int fmtIndex; // Used to locate end of the format fields.
0077: int endIndex; // Used to locate end of numerical fields.
0078: int intValue; // Generic storage variable
0079: long lngValue; // Store the TclInteger.get() result
0080: double dblValue; // Store the TclDouble.get() result
0081: boolean noPercent; // Special case for speed: indicates there's
0082: // no field specifier, just a string to copy.
0083: boolean xpgSet; // Indicates that xpg has been used for the
0084: // particular format of the main while loop
0085: boolean gotXpg; // True means that an XPG3 %n$-style
0086: // specifier has been seen.
0087: boolean gotSequential; // True means that a regular sequential
0088: // (non-XPG3) conversion specifier has
0089: // been seen.
0090: boolean useShort; // Value to be printed is short
0091: // (half word).
0092: boolean precisionSet; // Used for f, e, and E conversions
0093: boolean cont; // Used for phase 3
0094:
0095: if (argv.length < 2) {
0096: throw new TclNumArgsException(interp, 1, argv,
0097: "formatString ?arg arg ...?");
0098: }
0099:
0100: argIndex = 2;
0101: fmtIndex = 0;
0102: gotXpg = gotSequential = false;
0103: format = argv[1].toString().toCharArray();
0104: sbuf = new StringBuffer();
0105:
0106: // So, what happens here is to scan the format string one % group
0107: // at a time, making many individual appends to the StringBuffer.
0108:
0109: while (fmtIndex < format.length) {
0110: fmtFlags = phase = width = 0;
0111: noPercent = true;
0112: xpgSet = precisionSet = useShort = false;
0113: precision = -1;
0114:
0115: // Append all characters to sbuf that are not used for the
0116: // format specifier.
0117:
0118: if (format[fmtIndex] != '%') {
0119: int i;
0120: for (i = fmtIndex; (i < format.length); i++) {
0121: if (format[i] == '%') {
0122: noPercent = false;
0123: break;
0124: }
0125: }
0126: sbuf.append(new String(format, fmtIndex, i - fmtIndex));
0127: fmtIndex = i;
0128: if (noPercent) {
0129: break;
0130: }
0131: }
0132:
0133: // If true, then a % has been indicated but we are at the end
0134: // of the format string. Call function to throw exception.
0135:
0136: if (fmtIndex + 1 >= format.length) {
0137: errorEndMiddle(interp);
0138: }
0139:
0140: // Phase 0:
0141: // Check for %%. If true then simply write a single '%'
0142: // to the list.
0143:
0144: checkOverFlow(interp, format, fmtIndex + 1);
0145: if (format[fmtIndex + 1] == '%') {
0146: sbuf.append("%");
0147: fmtIndex += 2;
0148: // Re-enter the loop
0149:
0150: continue;
0151: }
0152:
0153: fmtIndex++;
0154: checkOverFlow(interp, format, fmtIndex);
0155: if (Character.isDigit(format[fmtIndex])) {
0156: // Parse the format array looking for the end of
0157: // the number.
0158:
0159: stoul = FormatCmd.strtoul(interp, format, fmtIndex);
0160: intValue = (int) stoul.value;
0161: endIndex = stoul.index;
0162: stoul = null;
0163:
0164: if (format[endIndex] == '$') {
0165: if (intValue == 0) {
0166: errorBadIndex(interp, true);
0167: }
0168:
0169: // Phase 1:
0170: // Check for an XPG3-style %n$ specification.
0171: // Note: there must not be a mixture of XPG3
0172: // specs and non-XPG3 specs in the same format string.
0173:
0174: if (gotSequential) {
0175: errorMixedXPG(interp);
0176: }
0177: gotXpg = true;
0178: xpgSet = true;
0179: phase = 2;
0180: fmtIndex = endIndex + 1;
0181: argIndex = intValue + 1;
0182: if ((argIndex < 2) || (argIndex >= argv.length)) {
0183: errorBadIndex(interp, gotXpg);
0184: }
0185:
0186: } else {
0187: // Phase 3:
0188: // Format jumped straight to phase 3; Setting
0189: // width field. Again, verify that all format
0190: // specifiers are sequential.
0191:
0192: if (gotXpg) {
0193: errorMixedXPG(interp);
0194: }
0195: gotSequential = true;
0196: if (format[fmtIndex] != '0') {
0197: fmtIndex = endIndex;
0198: width = intValue;
0199: phase = 4;
0200: }
0201: }
0202: } else {
0203: if (gotXpg) {
0204: errorMixedXPG(interp);
0205: }
0206: gotSequential = true;
0207: }
0208:
0209: // Phase 2:
0210: // Setting the Format Flags. At this point the phase value
0211: // can be either zero or three. Anything greater is an
0212: // incorrect format.
0213:
0214: if (phase < 3) {
0215: checkOverFlow(interp, format, fmtIndex);
0216: char ch = format[fmtIndex];
0217: cont = true;
0218: while (cont) {
0219: switch (ch) {
0220: case '-': {
0221: fmtFlags |= LEFT_JUSTIFY;
0222: break;
0223: }
0224: case '#': {
0225: fmtFlags |= ALT_OUTPUT;
0226: break;
0227: }
0228: case '0': {
0229: fmtFlags |= PAD_W_ZERO;
0230: break;
0231: }
0232: case ' ': {
0233: fmtFlags |= SPACE_OR_SIGN;
0234: break;
0235: }
0236: case '+': {
0237: fmtFlags |= SHOW_SIGN;
0238: break;
0239: }
0240: default: {
0241: cont = false;
0242: }
0243: }
0244: if (cont) {
0245: fmtIndex++;
0246: checkOverFlow(interp, format, fmtIndex);
0247: ch = format[fmtIndex];
0248: }
0249: }
0250: phase = 3;
0251: }
0252:
0253: // Phase 3:
0254: // Setting width field. Partially redundant code from the
0255: // Phase 1 if/else statement, but this is made to run fast.
0256:
0257: checkOverFlow(interp, format, fmtIndex);
0258: if (Character.isDigit(format[fmtIndex])) {
0259: stoul = FormatCmd.strtoul(interp, format, fmtIndex);
0260: width = (int) stoul.value;
0261: fmtIndex = stoul.index;
0262: stoul = null;
0263: } else if (format[fmtIndex] == '*') {
0264: if (argv.length > argIndex) {
0265: width = TclInteger.get(interp, argv[argIndex]);
0266: if (width < 0) {
0267: width = -width;
0268: fmtFlags |= LEFT_JUSTIFY;
0269: }
0270: argIndex++;
0271: fmtIndex++;
0272: }
0273: }
0274:
0275: // Phase 4:
0276: // Setting the precision field.
0277:
0278: checkOverFlow(interp, format, fmtIndex);
0279: if (format[fmtIndex] == '.') {
0280: fmtIndex++;
0281: checkOverFlow(interp, format, fmtIndex);
0282: if (Character.isDigit(format[fmtIndex])) {
0283: precisionSet = true;
0284: stoul = FormatCmd.strtoul(interp, format, fmtIndex);
0285: precision = (int) stoul.value;
0286: fmtIndex = stoul.index;
0287: stoul = null;
0288: } else if (format[fmtIndex] == '*') {
0289: if (argv.length > argIndex) {
0290: precisionSet = true;
0291: precision = TclInteger.get(interp,
0292: argv[argIndex]);
0293: argIndex++;
0294: fmtIndex++;
0295: checkOverFlow(interp, format, fmtIndex);
0296: }
0297: } else {
0298: // Format field had a '.' without an integer or '*'
0299: // preceeding it (eg %2.d or %2.-5d)
0300:
0301: errorBadField(interp, format[fmtIndex]);
0302: }
0303: }
0304:
0305: // Phase 5:
0306: // Setting the length modifier.
0307:
0308: if (format[fmtIndex] == 'h') {
0309: fmtIndex++;
0310: checkOverFlow(interp, format, fmtIndex);
0311: useShort = true;
0312: } else if (format[fmtIndex] == 'l') {
0313: fmtIndex++;
0314: checkOverFlow(interp, format, fmtIndex);
0315:
0316: // 'l' is ignored, but should still be processed.
0317: }
0318:
0319: if ((argIndex < 2) || (argIndex >= argv.length)) {
0320: errorBadIndex(interp, gotXpg);
0321: }
0322:
0323: // Phase 6:
0324: // Setting conversion field.
0325: // At this point, variables are initialized as follows:
0326: //
0327: // width The specified field width. This is always
0328: // non-negative. Zero is the default.
0329: // precision The specified precision. The default
0330: // is -1.
0331: // argIndex The argument index from the argv array
0332: // for the appropriate arg.
0333: // fmtFlags The format flags are set via bitwise
0334: // operations. Below are the bits
0335: // and their meanings.
0336:
0337: // ALT_OUTPUT set if a '#' is present.
0338: // SHOW_SIGN set if a '+' is present.
0339: // SPACE_OR_SIGN set if a ' ' is present.
0340: // LEFT_JUSTIFY set if a '-' is present or if the
0341: // field width was negative.
0342: // PAD_W_ZERO set if a '0' is present
0343:
0344: String strValue = "";
0345: char index = format[fmtIndex];
0346:
0347: switch (index) {
0348: case 'u':
0349: case 'd':
0350: case 'o':
0351: case 'x':
0352: case 'X':
0353: case 'i': {
0354: if (index == 'u') {
0355: // Since Java does not provide unsigned ints we need to
0356: // make our own. If the value is negative we need to
0357: // clear out all of the leading bits from the 33rd bit
0358: // and on. The result is a long value equal to that
0359: // of an unsigned int.
0360:
0361: lngValue = (long) TclInteger.get(interp,
0362: argv[argIndex]);
0363: if (lngValue < 0) {
0364: lngValue = (lngValue << 32);
0365: lngValue = (lngValue >>> 32);
0366: }
0367: } else {
0368: fmtFlags |= SIGNED_VALUE;
0369: lngValue = (long) TclInteger.get(interp,
0370: argv[argIndex]);
0371: }
0372:
0373: // If the useShort option has been selected, we need
0374: // to clear all but the first 16 bits.
0375:
0376: if (useShort) {
0377: lngValue = (lngValue << 48);
0378: lngValue = (lngValue >> 48);
0379: }
0380:
0381: if (index == 'o') {
0382: sbuf
0383: .append(cvtLngToStr(lngValue, width,
0384: precision, fmtFlags, 8, "01234567"
0385: .toCharArray(), "0"));
0386: } else if (index == 'x') {
0387: sbuf.append(cvtLngToStr(lngValue, width, precision,
0388: fmtFlags, 16, "0123456789abcdef"
0389: .toCharArray(), "0x"));
0390: } else if (index == 'X') {
0391: sbuf.append(cvtLngToStr(lngValue, width, precision,
0392: fmtFlags, 16, "0123456789ABCDEF"
0393: .toCharArray(), "0X"));
0394: } else {
0395: sbuf.append(cvtLngToStr(lngValue, width, precision,
0396: fmtFlags, 10, "0123456789".toCharArray(),
0397: ""));
0398: }
0399: break;
0400: }
0401: case 'c': {
0402: intValue = 0;
0403: char arr[] = { (char) TclInteger.get(interp,
0404: argv[argIndex]) };
0405: strValue = new String(arr);
0406: sbuf.append(cvtStrToStr(strValue, width, precision,
0407: fmtFlags));
0408: break;
0409: }
0410: case 's': {
0411: strValue = argv[argIndex].toString();
0412: sbuf.append(cvtStrToStr(strValue, width, precision,
0413: fmtFlags));
0414: break;
0415: }
0416: case 'f': {
0417: dblValue = TclDouble.get(interp, argv[argIndex]);
0418: sbuf.append(cvtDblToStr(dblValue, width, precision,
0419: fmtFlags, 10, "0123456789".toCharArray(), "",
0420: FLOAT));
0421: break;
0422: }
0423: case 'e': {
0424: dblValue = TclDouble.get(interp, argv[argIndex]);
0425: sbuf.append(cvtDblToStr(dblValue, width, precision,
0426: fmtFlags, 10, "e".toCharArray(), "", EXP));
0427: break;
0428: }
0429: case 'E': {
0430: dblValue = TclDouble.get(interp, argv[argIndex]);
0431: sbuf.append(cvtDblToStr(dblValue, width, precision,
0432: fmtFlags, 10, "E".toCharArray(), "", EXP));
0433: break;
0434: }
0435: case 'g': {
0436: dblValue = TclDouble.get(interp, argv[argIndex]);
0437: sbuf.append(cvtDblToStr(dblValue, width, precision,
0438: fmtFlags, 10, "e".toCharArray(), "", GENERIC));
0439: break;
0440: }
0441: case 'G': {
0442: dblValue = TclDouble.get(interp, argv[argIndex]);
0443: sbuf.append(cvtDblToStr(dblValue, width, precision,
0444: fmtFlags, 10, "E".toCharArray(), "", GENERIC));
0445: break;
0446: }
0447: default: {
0448: errorBadField(interp, format[fmtIndex]);
0449: }
0450: }
0451: fmtIndex++;
0452: argIndex++;
0453: }
0454: interp.setResult(sbuf.toString());
0455: }
0456:
0457: /**
0458: * This procedure is invoked in "phase 6" od the Format cmdProc. It
0459: * converts the lngValue to a string with a specified format determined by
0460: * the other input variables.
0461: * @param lngValue - Is the value of the argument input
0462: * @param width - The minimum width of the string.
0463: * @param precision - The minimum width if the integer. If the string len
0464: * is less than precision, leading 0 are appended.
0465: * @param flags - Specifies various formatting to the string
0466: * representation (-, +, space, 0, #)
0467: * @param base - The base of the integer (8, 10, 16)
0468: * @param charSet - The char set to use for the conversion to ascii OR
0469: * The char used for sci notation.
0470: * @param altPrefix - If not empty, str to append on the beginnig of the
0471: * resulting string (eg 0 or 0x or 0X ).
0472: * @return String representation of the long.
0473: */
0474:
0475: private String cvtLngToStr(long lngValue, int width, int precision,
0476: int flags, int base, char[] charSet, String altPrefix) {
0477: StringBuffer sbuf = new StringBuffer(100);
0478: StringBuffer tmpBuf = new StringBuffer(100);
0479:
0480: int i;
0481: int length;
0482: int nspace;
0483: int prefixSize = 0;
0484: char prefix = 0;
0485:
0486: // For the format %#x, the value zero is printed "0" not "0x0".
0487: // I think this is stupid.
0488:
0489: if (lngValue == 0) {
0490: flags = (flags | ALT_OUTPUT);
0491: }
0492:
0493: if ((flags & SIGNED_VALUE) != 0) {
0494: if (lngValue < 0) {
0495: if (altPrefix.length() > 0) {
0496: lngValue = (lngValue << 32);
0497: lngValue = (lngValue >>> 32);
0498: } else {
0499: lngValue = -lngValue;
0500: prefix = '-';
0501: prefixSize = 1;
0502: }
0503: } else if ((flags & SHOW_SIGN) != 0) {
0504: prefix = '+';
0505: prefixSize = 1;
0506: } else if ((flags & SPACE_OR_SIGN) != 0) {
0507: prefix = ' ';
0508: prefixSize = 1;
0509: }
0510: }
0511:
0512: if (((PAD_W_ZERO & flags) != 0)
0513: && (precision < width - prefixSize)) {
0514: precision = width - prefixSize;
0515: }
0516:
0517: // Convert to ascii
0518:
0519: do {
0520: sbuf.insert(0, charSet[(int) (lngValue % base)]);
0521: lngValue = lngValue / base;
0522: } while (lngValue > 0);
0523:
0524: length = sbuf.length();
0525: for (i = (precision - length); i > 0; i--) {
0526: sbuf.insert(0, '0');
0527: }
0528: if (prefix != 0) {
0529: sbuf.insert(0, prefix);
0530: }
0531: if ((flags & ALT_OUTPUT) != 0) {
0532: if ((altPrefix.length() > 0)
0533: && (sbuf.charAt(0) != altPrefix.charAt(0))) {
0534: sbuf.insert(0, altPrefix);
0535: }
0536: }
0537:
0538: // The text of the conversion is pointed to by "bufpt" and is
0539: // "length" characters long. The field width is "width". Do
0540: // the output.
0541:
0542: nspace = width - sbuf.length();
0543: if (nspace > 0) {
0544: tmpBuf.ensureCapacity(nspace);
0545: for (i = 0; i < nspace; i++) {
0546: tmpBuf.append(' ');
0547: }
0548: }
0549:
0550: if ((LEFT_JUSTIFY & flags) != 0) {
0551: // left justified
0552:
0553: sbuf.append(tmpBuf);
0554: return sbuf.toString();
0555: } else {
0556: // right justified
0557:
0558: tmpBuf.append(sbuf);
0559: return tmpBuf.toString();
0560: }
0561: }
0562:
0563: // Convert a double value to a Java String.
0564:
0565: static String toString(double dblValue, int precision, int base) {
0566: return cvtDblToStr(dblValue, 0, precision, 0, base, "e"
0567: .toCharArray(), null, GENERIC);
0568: }
0569:
0570: /**
0571: * This procedure is invoked in "phase 6" od the Format cmdProc. It
0572: * converts the lngValue to a string with a specified format determined
0573: * by the other input variables.
0574: * @param dblValue - Is the value of the argument input
0575: * @param width - The minimum width of the string.
0576: * @param precision - The minimum width if the integer. If the string len
0577: * is less than precision, leading 0 are appended.
0578: * @param flags - Specifies various formatting to the string
0579: * representation (-, +, space, 0, #)
0580: * @param base - The base of the integer (8, 10, 16)
0581: * @param charSet - The char set to use for the conversion to ascii OR
0582: * The char used for sci notation.
0583: * @param altPrefix - If not empty, str to append on the beginnig of the
0584: * resulting string (eg 0 or 0x or 0X ).
0585: * @param xtype - Either FLOAT, EXP, or GENERIC depending on the
0586: * format specifier.
0587: * @return String representation of the long.
0588: */
0589:
0590: private static String cvtDblToStr(double dblValue, int width,
0591: int precision, int flags, int base, char[] charSet,
0592: String altPrefix, int xtype) {
0593: StringBuffer sbuf = new StringBuffer(100);
0594: int i;
0595: int exp;
0596: int length;
0597: int count;
0598: int digit;
0599: int prefixSize = 0;
0600: char prefix = 0;
0601: double rounder;
0602: boolean flag_exp = false; // Flag for exponential representation
0603: boolean flag_rtz = true; // Flag for "remove trailing zeros"
0604: boolean flag_dp = true; // Flag for remove "decimal point"
0605:
0606: if (Double.isNaN(dblValue)) {
0607: return "NaN";
0608: }
0609: if (dblValue == Double.NEGATIVE_INFINITY) {
0610: return "-Inf";
0611: }
0612: if (dblValue == Double.POSITIVE_INFINITY) {
0613: return "Inf";
0614: }
0615:
0616: // If precision < 0 (eg -1) then the precision defaults
0617:
0618: if (precision < 0) {
0619: precision = 6;
0620: }
0621:
0622: if (dblValue < 0.0) {
0623: dblValue = -dblValue;
0624: prefix = '-';
0625: prefixSize = 1;
0626: } else if (dblValue == 0.0
0627: && (new Double(dblValue)).equals((new Double(-0.0)))) {
0628: // Handle -0.0
0629: //
0630: // 15.19.1 "Numerical Comparison Operators <, <=, >, and >= "
0631: // of the Java Language Spec says:
0632: // "Positive zero and negative zero are considered
0633: // equal. Therefore, -0.0<0.0 is false, for example, but
0634: // -0.0<=0.0 is true."
0635: //
0636: // The Double.equal man page says:
0637: // "If d1 represents +0.0 while d2 represents -0.0, or
0638: // vice versa, the equal test has the value false, even
0639: // though +0.0==-0.0 has the value true. This allows
0640: // hashtables to operate properly.
0641:
0642: dblValue = -dblValue;
0643: prefix = '-';
0644: prefixSize = 1;
0645: } else if ((flags & SHOW_SIGN) != 0) {
0646: prefix = '+';
0647: prefixSize = 1;
0648: } else if ((flags & SPACE_OR_SIGN) != 0) {
0649: prefix = ' ';
0650: prefixSize = 1;
0651: }
0652:
0653: // For GENERIC xtypes the precision includes the ones digit
0654: // so just decrement to get the correct precision.
0655:
0656: if (xtype == GENERIC && precision > 0) {
0657: precision--;
0658: }
0659:
0660: // Rounding works like BSD when the constant 0.4999 is used. Wierd!
0661:
0662: for (i = precision, rounder = 0.4999; i > 0; i--, rounder *= 0.1)
0663: ;
0664:
0665: if (xtype == FLOAT) {
0666: dblValue += rounder;
0667: }
0668:
0669: // Normalize dblValue to within 10.0 > dblValue >= 1.0
0670:
0671: exp = 0;
0672: if (dblValue > 0.0) {
0673: int k = 0;
0674: while ((dblValue >= 1e8) && (k++ < 100)) {
0675: dblValue *= 1e-8;
0676: exp += 8;
0677: }
0678: while ((dblValue >= 10.0) && (k++ < 100)) {
0679: dblValue *= 0.1;
0680: exp++;
0681: }
0682: while ((dblValue < 1e-8) && (k++ < 100)) {
0683: dblValue *= 1e8;
0684: exp -= 8;
0685: }
0686: while ((dblValue < 1.0) && (k++ < 100)) {
0687: dblValue *= 10.0;
0688: exp--;
0689: }
0690: if (k >= 100) {
0691: return "NaN";
0692: }
0693: }
0694:
0695: // If the field type is GENERIC, then convert to either EXP
0696: // or FLOAT, as appropriate.
0697:
0698: flag_exp = xtype == EXP;
0699: if (xtype != FLOAT) {
0700: dblValue += rounder;
0701: if (dblValue >= 10.0) {
0702: dblValue *= 0.1;
0703: exp++;
0704: }
0705: }
0706: if (xtype == GENERIC) {
0707: flag_rtz = !((flags & ALT_OUTPUT) != 0);
0708: if ((exp < -4) || (exp > precision)) {
0709: xtype = EXP;
0710: } else {
0711: precision = (precision - exp);
0712: xtype = FLOAT;
0713: }
0714: } else {
0715: flag_rtz = false;
0716: }
0717:
0718: // The "exp+precision" test causes output to be of type EXP if
0719: // the precision is too large to fit in buf[].
0720:
0721: count = 0;
0722: if (xtype == FLOAT) {
0723: flag_dp = ((precision > 0) || ((flags & ALT_OUTPUT) != 0));
0724: if (prefixSize > 0) {
0725: // Sign
0726:
0727: sbuf.append(prefix);
0728: }
0729: if (exp < 0) {
0730: // Digits before "."
0731:
0732: sbuf.append('0');
0733: }
0734: for (; exp >= 0; exp--) {
0735: if (count++ >= 16) {
0736: sbuf.append('0');
0737: } else {
0738: digit = (int) dblValue;
0739: dblValue = (dblValue - digit) * 10.0;
0740: sbuf.append(digit);
0741: }
0742: }
0743: if (flag_dp) {
0744: sbuf.append('.');
0745: }
0746: for (exp++; (exp < 0) && (precision > 0); precision--, exp++) {
0747: sbuf.append('0');
0748: }
0749: while ((precision--) > 0) {
0750: if (count++ >= 16) {
0751: sbuf.append('0');
0752: } else {
0753: digit = (int) dblValue;
0754: dblValue = (dblValue - digit) * 10.0;
0755: sbuf.append(digit);
0756: }
0757: }
0758:
0759: if (flag_rtz && flag_dp) {
0760: // Remove trailing zeros and "."
0761:
0762: int len, index = 0;
0763: for (len = sbuf.length() - 1; (len >= 0)
0764: && (sbuf.charAt(len) == '0'); len--, index++) {
0765: }
0766:
0767: if ((len >= 0) && (sbuf.charAt(len) == '.')) {
0768: index++;
0769: }
0770:
0771: if (index > 0) {
0772: sbuf.setLength(sbuf.length() - index);
0773: }
0774: }
0775: } else {
0776: // EXP or GENERIC
0777:
0778: flag_dp = ((precision > 0) || ((flags & ALT_OUTPUT) != 0));
0779:
0780: if (prefixSize > 0) {
0781: sbuf.append(prefix);
0782: }
0783: digit = (int) dblValue;
0784: dblValue = (dblValue - digit) * 10.0;
0785: sbuf.append(digit);
0786: if (flag_dp) {
0787: sbuf.append('.');
0788: }
0789: while (precision-- > 0) {
0790: if (count++ >= 16) {
0791: sbuf.append('0');
0792: } else {
0793: digit = (int) dblValue;
0794: dblValue = (dblValue - digit) * 10.0;
0795: sbuf.append(digit);
0796: }
0797: }
0798:
0799: if (flag_rtz && flag_dp) {
0800: // Remove trailing zeros and "."
0801:
0802: for (i = 0, length = (sbuf.length() - 1); (length >= 0)
0803: && (sbuf.charAt(length) == '0'); length--, i++)
0804: ;
0805:
0806: if ((length >= 0) && (sbuf.charAt(length) == '.')) {
0807: i++;
0808: }
0809:
0810: if (i > 0) {
0811: sbuf.setLength(sbuf.length() - i);
0812: }
0813: }
0814: if ((exp != 0) || flag_exp) {
0815: sbuf.append(charSet[0]);
0816: if (exp < 0) {
0817: sbuf.append('-');
0818: exp = -exp;
0819: } else {
0820: sbuf.append('+');
0821: }
0822: if (exp >= 100) {
0823: sbuf.append((exp / 100));
0824: exp %= 100;
0825: } else {
0826: sbuf.append('0');
0827: }
0828: sbuf.append(exp / 10);
0829: sbuf.append(exp % 10);
0830: }
0831: }
0832:
0833: // The converted number is in sbuf. Output it.
0834: // Note that the number is in the usual order, not reversed as with
0835: // integer conversions.
0836:
0837: length = sbuf.length();
0838:
0839: // Special case: Add leading zeros if the PAD_W_ZERO flag is
0840: // set and we are not left justified
0841:
0842: if (((PAD_W_ZERO & flags) != 0)
0843: && ((LEFT_JUSTIFY & flags) == 0)) {
0844: int nPad = width - length;
0845: i = prefixSize;
0846: while ((nPad--) > 0) {
0847: sbuf.insert(prefixSize, '0');
0848: }
0849: length = width;
0850: }
0851:
0852: // Count the number of spaces remaining and create a StringBuffer
0853: // (tmpBuf) with the correct number of spaces.
0854:
0855: int nspace = width - length;
0856: StringBuffer tmpBuf = new StringBuffer(100 + nspace);
0857: if (nspace > 0) {
0858: for (i = 0; i < nspace; i++) {
0859: tmpBuf.append(' ');
0860: }
0861: }
0862:
0863: if ((LEFT_JUSTIFY & flags) != 0) {
0864: // left justified
0865:
0866: sbuf.append(tmpBuf);
0867: return sbuf.toString();
0868: } else {
0869: // right justified
0870:
0871: tmpBuf.append(sbuf);
0872: return tmpBuf.toString();
0873: }
0874: }
0875:
0876: /**
0877: * This procedure is invoked in "phase 6" od the Format cmdProc. It
0878: * converts the strValue to a string with a specified format determined
0879: * by the other input variables.
0880: * @param strValue - Is the String w/o formatting.
0881: * @param width - The minimum width of the string.
0882: * @param precision - The minimum width if the integer. If the string
0883: * len is less than precision, leading 0 are
0884: * appended.
0885: * @param flags - Specifies various formatting to the string
0886: * representation (-, +, space, 0, #)
0887: * @return String representation of the integer.
0888: */
0889:
0890: private static String cvtStrToStr(String strValue, int width,
0891: int precision, int flags) {
0892: String left = "";
0893: String right = "";
0894: StringBuffer sbuf = new StringBuffer(100);
0895:
0896: if (precision < 0) {
0897: precision = 0;
0898: }
0899:
0900: if ((precision != 0) && (precision < strValue.length())) {
0901: strValue = strValue.substring(0, precision);
0902: }
0903:
0904: if (width > strValue.length()) {
0905: sbuf.setLength(0);
0906: int index = width - strValue.length();
0907: for (int i = 0; i < index; i++) {
0908: if ((flags & PAD_W_ZERO) != 0) {
0909: sbuf.append('0');
0910: } else {
0911: sbuf.append(' ');
0912: }
0913: }
0914: if ((LEFT_JUSTIFY & flags) != 0) {
0915: right = sbuf.toString();
0916: } else {
0917: left = sbuf.toString();
0918: }
0919: }
0920:
0921: sbuf.setLength(0);
0922: sbuf.append(left);
0923: sbuf.append(strValue);
0924: sbuf.append(right);
0925: return sbuf.toString();
0926: }
0927:
0928: /**
0929: * Search through the array while the current char is a digit. When end
0930: * of array occurs or the char is not a digit, stop the loop, convert the
0931: * sub-array into a long. At this point update a StrtoulResult object
0932: * that contains the new long value and the current pointer to the array.
0933: * Returns a StrtoulResult tmp object to hold result data.
0934: *
0935: * @param interp - the current interpreter (can't be null)
0936: * @param arr - the array that contains a string representation of an int.
0937: * @param endIndex - the arr index where the numeric value begins.
0938: */
0939:
0940: private static StrtoulResult strtoul(Interp interp, char[] arr,
0941: int endIndex) {
0942: int orgIndex;
0943: StrtoulResult strtoulResult = interp.strtoulResult;
0944:
0945: orgIndex = endIndex;
0946: for (; endIndex < arr.length; endIndex++) {
0947: if (!Character.isDigit(arr[endIndex])) {
0948: break;
0949: }
0950: }
0951: long lval = Long.parseLong(new String(arr, orgIndex, endIndex
0952: - orgIndex));
0953: strtoulResult.update(lval, endIndex, 0);
0954: return strtoulResult;
0955: }
0956:
0957: /*
0958: *
0959: * Error routines:
0960: *
0961: */
0962:
0963: /**
0964: * Called whenever the fmtIndex in the cmdProc is changed. It verifies
0965: * the the array index is still within the bounds of the array. If no
0966: * throw error.
0967: * @param interp - The TclInterp which called the cmdProc method .
0968: * @param arr - The array to be checked.
0969: * @param index - The new value for the array index.
0970: */
0971:
0972: private static void checkOverFlow(Interp interp, char[] arr,
0973: int index) throws TclException {
0974: if ((index >= arr.length) || (index < 0)) {
0975: throw new TclException(interp,
0976: "\"%n$\" argument index out of range");
0977: }
0978: }
0979:
0980: /**
0981: * Called whenever Sequential format specifiers are interlaced with
0982: * XPG format specifiers in one call to cmdProc.
0983: *
0984: * @param interp - The TclInterp which called the cmdProc method .
0985: */
0986:
0987: private static void errorMixedXPG(Interp interp)
0988: throws TclException {
0989: throw new TclException(interp,
0990: "cannot mix \"%\" and \"%n$\" conversion specifiers");
0991: }
0992:
0993: /**
0994: * Called whenever the argIndex access outside the argv array. If the
0995: * type is an XPG then the error message is different.
0996: *
0997: * @param interp - The TclInterp which called the cmdProc method .
0998: * @param gotXpg - Boolean the determines if the current format is of a
0999: * XPG type or Sequential
1000: */
1001:
1002: private static void errorBadIndex(Interp interp, boolean gotXpg)
1003: throws TclException {
1004: if (gotXpg) {
1005: throw new TclException(interp,
1006: "\"%n$\" argument index out of range");
1007: } else {
1008: throw new TclException(interp,
1009: "not enough arguments for all format specifiers");
1010: }
1011: }
1012:
1013: /**
1014: * Called whenever the current char in the format array is erroneous
1015: *
1016: * @param interp - The TclInterp which called the cmdProc method .
1017: * @param fieldSpecifier - The erroneous character
1018: */
1019:
1020: private static void errorBadField(Interp interp, char fieldSpecifier)
1021: throws TclException {
1022: throw new TclException(interp, "bad field specifier \""
1023: + fieldSpecifier + "\"");
1024: }
1025:
1026: /**
1027: * Called whenever the a '%' is found but then the format string ends.
1028: *
1029: * @param interp - The TclInterp which called the cmdProc method .
1030: */
1031:
1032: private static void errorEndMiddle(Interp interp)
1033: throws TclException {
1034: throw new TclException(interp,
1035: "format string ended in middle of field specifier");
1036: }
1037:
1038: }
|