// Copyright (c) Jython Developers
package org.python.core.stringlib;

import org.python.core.Py;
import org.python.core.PyException;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PyUnicode;

public class InternalFormat {

    /**
     * Create a {@link Spec} object by parsing a format specification.
     *
     * @param text to parse
     * @return parsed equivalent to text
     */
    public static Spec fromText(String text) {
        Parser parser = new Parser(text);
        try {
            return parser.parse();
        } catch (IllegalArgumentException e) {
            throw Py.ValueError(e.getMessage());
        }
    }

    /**
     * Create a {@link Spec} object by parsing a format specification, supplied as an object.
     *
     * @param text to parse
     * @return parsed equivalent to text
     */
    public static Spec fromText(PyObject text, String method) {
        if (text instanceof PyString) {
            return fromText(((PyString)text).getString());
        } else {
            throw Py.TypeError(method + " requires str or unicode");
        }
    }

    /**
     * A class that provides the base for implementations of type-specific formatting. In a limited
     * way, it acts like a StringBuilder to which text and one or more numbers may be appended,
     * formatted according to the format specifier supplied at construction. These are ephemeral
     * objects that are not, on their own, thread safe.
     */
    public static class Formatter implements Appendable {

        /** The specification according to which we format any number supplied to the method. */
        protected final Spec spec;
        /** The (partial) result. */
        protected StringBuilder result;

        /**
         * Signals the client's intention to make a PyString (or other byte-like) interpretation of
         * {@link #result}, rather than a PyUnicode one.
         */
        protected boolean bytes;

        /** The start of the formatted data for padding purposes, <={@link #start} */
        protected int mark;
        /** The latest number we are working on floats at the end of the result, and starts here. */
        protected int start;
        /** If it contains no sign, this length is zero, and >0 otherwise. */
        protected int lenSign;
        /** The length of the whole part (to left of the decimal point or exponent) */
        protected int lenWhole;

        /**
         * Construct the formatter from a client-supplied buffer and a specification. Sets
         * {@link #mark} and {@link #start} to the end of the buffer. The new formatted object will
         * therefore be appended there and, when the time comes, padding will be applied to (just)
         * the new text.
         *
         * @param result destination buffer
         * @param spec parsed conversion specification
         */
        public Formatter(StringBuilder result, Spec spec) {
            this.spec = spec;
            this.result = result;
            this.start = this.mark = result.length();
        }

        /**
         * Construct the formatter from a specification and initial buffer capacity. Sets
         * {@link #mark} to the end of the buffer.
         *
         * @param spec parsed conversion specification
         * @param width of buffer initially
         */
        public Formatter(Spec spec, int width) {
            this(new StringBuilder(width), spec);
        }

        /**
         * Signals the client's intention to make a PyString (or other byte-like) interpretation of
         * {@link #result}, rather than a PyUnicode one. Only formatters that could produce
         * characters >255 are affected by this (e.g. c-format). Idiom:
         *
         * 
         * MyFormatter f = new MyFormatter( InternalFormatter.fromText(formatSpec) );
         * f.setBytes(!(formatSpec instanceof PyUnicode));
         * // ... formatting work
         * return f.getPyResult();
         * 
* * @param bytes true to signal the intention to make a byte-like interpretation */ public void setBytes(boolean bytes) { this.bytes = bytes; } /** * Whether initialised for a byte-like interpretation. * * @return bytes attribute */ public boolean isBytes() { return bytes; } /** * Current (possibly final) result of the formatting, as a String. * * @return formatted result */ public String getResult() { return result.toString(); } /** * Convenience method to return the current result of the formatting, as a * PyObject, either {@link PyString} or {@link PyUnicode} according to * {@link #bytes}. * * @return formatted result */ public PyString getPyResult() { String r = getResult(); if (bytes) { return new PyString(r); } else { return new PyUnicode(r); } } /* * Implement Appendable interface by delegation to the result buffer. * * @see java.lang.Appendable#append(char) */ @Override public Formatter append(char c) { result.append(c); return this; } @Override public Formatter append(CharSequence csq) { result.append(csq); return this; } @Override public Formatter append(CharSequence csq, int start, int end) // throws IndexOutOfBoundsException { result.append(csq, start, end); return this; } /** * Clear the instance variables describing the latest object in {@link #result}, ready to * receive a new one: sets {@link #start} and calls {@link #reset()}. This is necessary when * a Formatter is to be re-used. Note that this leaves {@link #mark} where it * is. In the core, we need this to support complex: two floats in the same * format, but padded as a unit. */ public void setStart() { // The new value will float at the current end of the result buffer. start = result.length(); // If anything has been added since construction, reset all state. if (start > mark) { // Clear the variable describing the latest number in result. reset(); } } /** * Clear the instance variables describing the latest object in {@link #result}, ready to * receive a new one. This is called from {@link #setStart()}. Subclasses override this * method and call {@link #setStart()} at the start of their format method. */ protected void reset() { // Clear the variables describing the latest object in result. lenSign = lenWhole = 0; } /** * Supports {@link #toString()} by returning the lengths of the successive sections in the * result buffer, used for navigation relative to {@link #start}. The toString * method shows a '|' character between each section when it prints out the buffer. Override * this when you define more lengths in the subclass. * * @return the lengths of the successive sections */ protected int[] sectionLengths() { return new int[] {lenSign, lenWhole}; } /** * {@inheritDoc} *

* Overridden to provide a debugging view in which the actual text is shown divided up by * the len* member variables. If the dividers don't look right, those variables * have not remained consistent with the text. */ @Override public String toString() { if (result == null) { return ("[]"); } else { StringBuilder buf = new StringBuilder(result.length() + 20); buf.append(result); try { int p = start; buf.insert(p++, '['); for (int len : sectionLengths()) { p += len; buf.insert(p++, '|'); } buf.setCharAt(p - 1, ']'); } catch (IndexOutOfBoundsException e) { // Some length took us beyond the end of the result buffer. Pass. } return buf.toString(); } } /** * Insert grouping characters (conventionally commas) into the whole part of the number. * {@link #lenWhole} will increase correspondingly. * * @param groupSize normally 3. * @param comma or some other character to use as a separator. */ protected void groupDigits(int groupSize, char comma) { // Work out how many commas (or whatever) it takes to group the whole-number part. int commasNeeded = (lenWhole - 1) / groupSize; if (commasNeeded > 0) { // Index *just after* the current last digit of the whole part of the number. int from = start + lenSign + lenWhole; // Open a space into which the whole part will expand. makeSpaceAt(from, commasNeeded); // Index *just after* the end of that space. int to = from + commasNeeded; // The whole part will be longer by the number of commas to be inserted. lenWhole += commasNeeded; /* * Now working from high to low, copy all the digits that have to move. Each pass * copies one group and inserts a comma, which makes the to-pointer move one place * extra. The to-pointer descends upon the from-pointer from the right. */ while (to > from) { // Copy a group for (int i = 0; i < groupSize; i++) { result.setCharAt(--to, result.charAt(--from)); } // Write the comma that precedes it. result.setCharAt(--to, comma); } } } /** * Make a space in {@link #result} of a certain size and position. On return, the segment * lengths are likely to be invalid until the caller adjusts them corresponding to the * insertion. There is no guarantee what the opened space contains. * * @param pos at which to make the space * @param size of the space */ protected void makeSpaceAt(int pos, int size) { int n = result.length(); if (pos < n) { // Space is not at the end: must copy what's to the right of pos. String tail = result.substring(pos); result.setLength(n + size); result.replace(pos + size, n + size, tail); } else { // Space is at the end. result.setLength(n + size); } } /** * Convert letters in the representation of the current number (in {@link #result}) to upper * case. */ protected void uppercase() { int end = result.length(); for (int i = start; i < end; i++) { char c = result.charAt(i); result.setCharAt(i, Character.toUpperCase(c)); } } /** * Pad the result so far (defined as the contents of {@link #result} from {@link #mark} to * the end) using the alignment, target width and fill character defined in {@link #spec}. * The action of padding will increase the length of this segment to the target width, if * that is greater than the current length. *

* When the padding method has decided that that it needs to add n padding characters, it * will affect {@link #start} or {@link #lenWhole} as follows. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Effect of padding on {@link #start} or {@link #lenWhole}
alignmeaningstartlenWholeresult.length()
{@code <}left-aligned+0+0+n
{@code >}right-aligned+n+0+n
{@code ^}centred+(n/2)+0+n
{@code =}pad after sign+0+n+n
* Note that in the "pad after sign" mode, only the last number into the buffer receives the * padding. This padding gets incorporated into the whole part of the number. (In other * modes, the padding is around result[mark:].) When this would not be * appropriate, it is up to the client to disallow this (which complex does). * * @return this Formatter object */ public Formatter pad() { // We'll need this many pad characters (if>0). Note Spec.UNDEFINED<0. int n = spec.width - (result.length() - mark); if (n > 0) { pad(mark, n); } return this; } /** * Pad the last result (defined as the contents of {@link #result} from argument * leftIndex to the end) using the alignment, by n repetitions of * the fill character defined in {@link #spec}, and distributed according to * spec.align. The value of leftIndex is only used if the * alignment is '>' (left) or '^' (both). The value of the critical lengths (lenWhole, * lenSign, etc.) are not affected, because we assume that leftIndex <= * {@link #start}. * * @param leftIndex the index in result at which to insert left-fill characters. * @param n number of fill characters to insert. */ protected void pad(int leftIndex, int n) { char align = spec.getAlign('>'); // Right for numbers (strings will supply '<' align) char fill = spec.getFill(' '); // Start by assuming padding is all leading ('>' case or '=') int leading = n; // Split the total padding according to the alignment if (align == '^') { // Half the padding before leading = n / 2; } else if (align == '<') { // All the padding after leading = 0; } // All padding that is not leading is trailing int trailing = n - leading; // Insert the leading space if (leading > 0) { if (align == '=') { // Incorporate into the (latest) whole part leftIndex = start + lenSign; lenWhole += leading; } else { // Default is to insert at the stated leftIndex <= start. start += leading; } makeSpaceAt(leftIndex, leading); for (int i = 0; i < leading; i++) { result.setCharAt(leftIndex + i, fill); } } // Append the trailing space for (int i = 0; i < trailing; i++) { result.append(fill); } // Check for special case if (align == '=' && fill == '0' && spec.grouping) { // We must extend the grouping separator into the padding zeroPadAfterSignWithGroupingFixup(3, ','); } } /** * Fix-up the zero-padding of the last formatted number in {@link #result} in the special * case where a sign-aware padding ({@link #spec}.align='=') was requested, the * fill character is '0', and the digits are to be grouped. In these exact * circumstances, the grouping, which must already have been applied to the (whole part) * number itself, has to be extended into the zero-padding. * *
         * >>> format(-12e8, " =30,.3f")
         * '-            1,200,000,000.000'
         * >>> format(-12e8, "*=30,.3f")
         * '-************1,200,000,000.000'
         * >>> format(-12e8, "*>30,.3f")
         * '************-1,200,000,000.000'
         * >>> format(-12e8, "0>30,.3f")
         * '000000000000-1,200,000,000.000'
         * >>> format(-12e8, "0=30,.3f")
         * '-0,000,000,001,200,000,000.000'
         * 
* * The padding has increased the overall length of the result to the target width. About one * in three calls to this method adds one to the width, because the whole part cannot start * with a comma. * *
         * >>> format(-12e8, " =30,.4f")
         * '-           1,200,000,000.0000'
         * >>> format(-12e8, "0=30,.4f")
         * '-0,000,000,001,200,000,000.0000'
         * 
* * @param groupSize normally 3. * @param comma or some other character to use as a separator. */ protected void zeroPadAfterSignWithGroupingFixup(int groupSize, char comma) { /* * Suppose the format call was format(-12e8, "0=30,.3f"). At this point, we have * something like this in result: .. [-|0000000000001,200,000,000|.|000||] * * All we need do is over-write some of the zeros with the separator comma, in the * portion marked as the whole-part: [-|0,000,000,001,200,000,000|.|000||] */ // First digit of the whole-part. int firstZero = start + lenSign; // One beyond last digit of the whole-part. int p = firstZero + lenWhole; // Step back down the result array visiting the commas. (Easiest to do all of them.) int step = groupSize + 1; for (p = p - step; p >= firstZero; p -= step) { result.setCharAt(p, comma); } // Sometimes the last write was exactly at the first padding zero. if (p + step == firstZero) { /* * Suppose the format call was format(-12e8, "0=30,.4f"). At the beginning, we had * something like this in result: . [-|000000000001,200,000,000|.|0000||] * * And now, result looks like this: [-|,000,000,001,200,000,000|.|0000||] in which * the first comma is wrong, but so would be a zero. We have to insert another zero, * even though this makes the result longer than we were asked for. */ result.insert(firstZero, '0'); lenWhole += 1; } } /** * Convenience method returning a {@link Py#ValueError} reporting: *

* "Unknown format code '"+code+"' for object of type '"+forType+"'" * * @param code the presentation type * @param forType the type it was found applied to * @return exception to throw */ public static PyException unknownFormat(char code, String forType) { String msg = "Unknown format code '" + code + "' for object of type '" + forType + "'"; return Py.ValueError(msg); } /** * Convenience method returning a {@link Py#ValueError} reporting that alternate form is not * allowed in a format specifier for the named type. * * @param forType the type it was found applied to * @return exception to throw */ public static PyException alternateFormNotAllowed(String forType) { return alternateFormNotAllowed(forType, '\0'); } /** * Convenience method returning a {@link Py#ValueError} reporting that alternate form is not * allowed in a format specifier for the named type and specified typoe code. * * @param forType the type it was found applied to * @param code the formatting code (or '\0' not to mention one) * @return exception to throw */ public static PyException alternateFormNotAllowed(String forType, char code) { return notAllowed("Alternate form (#)", forType, code); } /** * Convenience method returning a {@link Py#ValueError} reporting that the given alignment * flag is not allowed in a format specifier for the named type. * * @param align type of alignment * @param forType the type it was found applied to * @return exception to throw */ public static PyException alignmentNotAllowed(char align, String forType) { return notAllowed("'" + align + "' alignment flag", forType, '\0'); } /** * Convenience method returning a {@link Py#ValueError} reporting that specifying a sign is * not allowed in a format specifier for the named type. * * @param forType the type it was found applied to * @param code the formatting code (or '\0' not to mention one) * @return exception to throw */ public static PyException signNotAllowed(String forType, char code) { return notAllowed("Sign", forType, code); } /** * Convenience method returning a {@link Py#ValueError} reporting that specifying a * precision is not allowed in a format specifier for the named type. * * @param forType the type it was found applied to * @return exception to throw */ public static PyException precisionNotAllowed(String forType) { return notAllowed("Precision", forType, '\0'); } /** * Convenience method returning a {@link Py#ValueError} reporting that zero padding is not * allowed in a format specifier for the named type. * * @param forType the type it was found applied to * @return exception to throw */ public static PyException zeroPaddingNotAllowed(String forType) { return notAllowed("Zero padding", forType, '\0'); } /** * Convenience method returning a {@link Py#ValueError} reporting that some format specifier * feature is not allowed for the named data type. * * @param outrage committed in the present case * @param forType the data type (e.g. "integer") it where it is an outrage * @return exception to throw */ public static PyException notAllowed(String outrage, String forType) { return notAllowed(outrage, forType, '\0'); } /** * Convenience method returning a {@link Py#ValueError} reporting that some format specifier * feature is not allowed for the named format code and data type. Produces a message like: *

* outrage+" not allowed with "+forType+" format specifier '"+code+"'" *

* outrage+" not allowed in "+forType+" format specifier" * * @param outrage committed in the present case * @param forType the data type (e.g. "integer") it where it is an outrage * @param code the formatting code for which it is an outrage (or '\0' not to mention one) * @return exception to throw */ public static PyException notAllowed(String outrage, String forType, char code) { // Try really hard to be like CPython String codeAsString, withOrIn; if (code == 0) { withOrIn = "in "; codeAsString = ""; } else { withOrIn = "with "; codeAsString = " '" + code + "'"; } String msg = outrage + " not allowed " + withOrIn + forType + " format specifier" + codeAsString; return Py.ValueError(msg); } /** * Convenience method returning a {@link Py#OverflowError} reporting: *

* "formatted "+type+" is too long (precision too large?)" * * @param type of formatting ("integer", "float") * @return exception to throw */ public static PyException precisionTooLarge(String type) { String msg = "formatted " + type + " is too long (precision too large?)"; return Py.OverflowError(msg); } } /** * Parsed PEP-3101 format specification of a single field, encapsulating the format for use by * formatting methods. This class holds the several attributes that might be decoded from a * format specifier. Each attribute has a reserved value used to indicate "unspecified". * Spec objects may be merged such that one Spec provides values, * during the construction of a new Spec, for attributes that are unspecified in a * primary source. *

* This structure is returned by factory method {@link #fromText(String)}, and having public * final members is freely accessed by formatters such as {@link FloatFormatter}, and the * __format__ methods of client object types. *

* The fields correspond to the elements of a format specification. The grammar of a format * specification is: * *

     * [[fill]align][sign][#][0][width][,][.precision][type]
     * 
* * A typical idiom is: * *
{@literal
     *     private static final InternalFormatSpec FLOAT_DEFAULTS = InternalFormatSpec.from(">");
     *     ...
     *         InternalFormat.Spec spec = InternalFormat.fromText(specString);
     *         spec = spec.withDefaults(FLOAT_DEFAULTS);
     *         ... // Validation of spec.type, and other attributes, for this type.
     *         FloatFormatter f = new FloatFormatter(spec);
     *         String result = f.format(value).getResult();
     * }
*/ public static class Spec { /** The fill character specified, or U+FFFF if unspecified. */ public final char fill; /** Alignment indicator is one of {'<', '^', '>', '=', or U+FFFF if unspecified. */ public final char align; /** * Sign-handling flag, one of '+', '-', or ' ', or * U+FFFF if unspecified. */ public final char sign; /** The alternative format flag '#' was given. */ public final boolean alternate; /** Width to which to pad the result, or -1 if unspecified. */ public final int width; /** Insert the grouping separator (which in Python always indicates a group-size of 3). */ public final boolean grouping; /** Precision decoded from the format, or -1 if unspecified. */ public final int precision; /** Type key from the format, or U+FFFF if unspecified. */ public final char type; /** Non-character code point used to represent "no value" in char attributes. */ public static final char NONE = '\uffff'; /** Negative value used to represent "no value" in int attributes. */ public static final int UNSPECIFIED = -1; /** * Test to see if an attribute has been specified. * * @param c attribute * @return true only if the attribute is not equal to {@link #NONE} */ public static final boolean specified(char c) { return c != NONE; } /** * Test to see if an attribute has been specified. * * @param value of attribute * @return true only if the attribute is ≥0 (meaning that it has been specified). */ public static final boolean specified(int value) { return value >= 0; } /** * Constructor to set all the fields in the format specifier. * *
         * [[fill]align][sign][#][0][width][,][.precision][type]
         * 
* * @param fill fill character (or {@link #NONE} * @param align alignment indicator, one of {'<', '^', '>', '='} * @param sign policy, one of '+', '-', or ' '. * @param alternate true to request alternate formatting mode ('#' flag). * @param width of field after padding or -1 to default * @param grouping true to request comma-separated groups * @param precision (e.g. decimal places) or -1 to default * @param type indicator character */ public Spec(char fill, char align, char sign, boolean alternate, int width, boolean grouping, int precision, char type) { this.fill = fill; this.align = align; this.sign = sign; this.alternate = alternate; this.width = width; this.grouping = grouping; this.precision = precision; this.type = type; } /** * Return a format specifier (text) equivalent to the value of this Spec. */ @Override public String toString() { StringBuilder buf = new StringBuilder(); if (specified(fill)) { buf.append(fill); } if (specified(align)) { buf.append(align); } if (specified(sign)) { buf.append(sign); } if (alternate) { buf.append('#'); } if (specified(width)) { buf.append(width); } if (grouping) { buf.append(','); } if (specified(precision)) { buf.append('.').append(precision); } if (specified(type)) { buf.append(type); } return buf.toString(); } /** * Return a merged Spec object, in which any attribute of this object that is * specified (or true), has the same value in the result, and any attribute of * this object that is unspecified (or false), has the value that attribute * takes in the other object. Thus the second object supplies default values. (These * defaults may also be unspecified.) The use of this method is to allow a Spec * constructed from text to record exactly, and only, what was in the textual specification, * while the __format__ method of a client object supplies its type-specific defaults. Thus * "20" means "<20s" to a str, ">20.12" to a float and * ">20.12g" to a complex. * * @param other defaults to merge where this object does not specify the attribute. * @return a new Spec object. */ public Spec withDefaults(Spec other) { return new Spec(// specified(fill) ? fill : other.fill, // specified(align) ? align : other.align, // specified(sign) ? sign : other.sign, // alternate || other.alternate, // specified(width) ? width : other.width, // grouping || other.grouping, // specified(precision) ? precision : other.precision, // specified(type) ? type : other.type // ); } /** Defaults applicable to most numeric types. Equivalent to " >" */ public static final Spec NUMERIC = new Spec(' ', '>', Spec.NONE, false, Spec.UNSPECIFIED, false, Spec.UNSPECIFIED, Spec.NONE); /** Defaults applicable to string types. Equivalent to " <" */ public static final Spec STRING = new Spec(' ', '<', Spec.NONE, false, Spec.UNSPECIFIED, false, Spec.UNSPECIFIED, Spec.NONE); /** * Constructor offering just precision and type. * *
         * [.precision][type]
         * 
* * @param precision (e.g. decimal places) * @param type indicator character */ public Spec(int precision, char type) { this(' ', '>', Spec.NONE, false, UNSPECIFIED, false, precision, type); } /** The alignment from the parsed format specification, or default. */ public char getFill(char defaultFill) { return specified(fill) ? fill : defaultFill; } /** The alignment from the parsed format specification, or default. */ public char getAlign(char defaultAlign) { return specified(align) ? align : defaultAlign; } /** The precision from the parsed format specification, or default. */ public int getPrecision(int defaultPrecision) { return specified(precision) ? precision : defaultPrecision; } /** The type code from the parsed format specification, or default supplied. */ public char getType(char defaultType) { return specified(type) ? type : defaultType; } } /** * Parser for PEP-3101 field format specifications. This class provides a {@link #parse()} * method that translates the format specification into an Spec object. */ private static class Parser { private String spec; private int ptr; /** * Constructor simply holds the specification string ahead of the {@link #parse()} * operation. * * @param spec format specifier to parse (e.g. "<+12.3f") */ Parser(String spec) { this.spec = spec; this.ptr = 0; } /** * Parse the specification with which this object was initialised into an {@link Spec}, * which is an object encapsulating the format for use by formatting methods. This parser * deals only with the format specifiers themselves, as accepted by the * __format__ method of a type, or the format() built-in, not * format strings in general as accepted by str.format(). * * @return the Spec equivalent to the string given. */ /* * This method is the equivalent of CPython's parse_internal_render_format_spec() in * ~/Objects/stringlib/formatter.h, but we deal with defaults another way. */ Spec parse() { char fill = Spec.NONE, align = Spec.NONE; char sign = Spec.NONE, type = Spec.NONE; boolean alternate = false, grouping = false; int width = Spec.UNSPECIFIED, precision = Spec.UNSPECIFIED; // Scan [[fill]align] ... if (isAlign()) { // First is alignment. fill not specified. align = spec.charAt(ptr++); } else { // Peek ahead ptr += 1; if (isAlign()) { // Second character is alignment, so first is fill fill = spec.charAt(0); align = spec.charAt(ptr++); } else { // Second character is not alignment. We are still at square zero. ptr = 0; } } // Scan [sign] ... if (isAt("+- ")) { sign = spec.charAt(ptr++); } // Scan [#] ... alternate = scanPast('#'); // Scan [0] ... if (scanPast('0')) { // Accept 0 here as equivalent to zero-fill but only not set already. if (!Spec.specified(fill)) { fill = '0'; if (!Spec.specified(align)) { // Also accept it as equivalent to "=" aligment but only not set already. align = '='; } } } // Scan [width] if (isDigit()) { width = scanInteger(); } // Scan [,][.precision][type] grouping = scanPast(','); // Scan [.precision] if (scanPast('.')) { if (isDigit()) { precision = scanInteger(); } else { throw new IllegalArgumentException("Format specifier missing precision"); } } // Scan [type] if (ptr < spec.length()) { type = spec.charAt(ptr++); } // If we haven't reached the end, something is wrong if (ptr != spec.length()) { throw new IllegalArgumentException("Invalid conversion specification"); } // Create a specification return new Spec(fill, align, sign, alternate, width, grouping, precision, type); } /** Test that the next character is exactly the one specified, and advance past it if it is. */ private boolean scanPast(char c) { if (ptr < spec.length() && spec.charAt(ptr) == c) { ptr++; return true; } else { return false; } } /** Test that the next character is one of a specified set. */ private boolean isAt(String chars) { return ptr < spec.length() && (chars.indexOf(spec.charAt(ptr)) >= 0); } /** Test that the next character is one of the alignment characters. */ private boolean isAlign() { return ptr < spec.length() && ("<^>=".indexOf(spec.charAt(ptr)) >= 0); } /** Test that the next character is a digit. */ private boolean isDigit() { return ptr < spec.length() && Character.isDigit(spec.charAt(ptr)); } /** The current character is a digit (maybe a sign). Scan the integer, */ private int scanInteger() { int p = ptr++; while (isDigit()) { ptr++; } return Integer.parseInt(spec.substring(p, ptr)); } } }

Ads help maintain this website.