001package Torello.CSS;
002
003import Torello.Java.Additional.ByRef;
004
005import java.util.Vector;
006import java.util.stream.IntStream;
007import java.util.function.Consumer;
008import java.math.BigDecimal;
009
010/**
011 * CSS-Tokenizer {@code Number}-Literal & {@code Number}-Token Class.
012 */
013@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="CSS_TOK")
014public class Num extends CSSToken
015    implements CharSequence, java.io.Serializable, Comparable<CharSequence>
016{
017    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
018    protected static final long serialVersionUID = 1;
019
020
021    // ********************************************************************************************
022    // ********************************************************************************************
023    // Public & Final Fields
024    // ********************************************************************************************
025    // ********************************************************************************************
026
027
028    /**
029     * Though Java's {@code BigDecimal} may be an "overly ambitious" means of representing
030     * CSS-Extracted Number-Literals, for now, this is how it is going to work.  After the
031     * {@code 'javadoc'} stuff is done, maybe I'll change it to {@code java.lang.Number} or
032     * something else.
033     * 
034     * <BR /><BR />Yes, a {@code "2em"} or {@code "10px"} would be saved as a BigDecimal
035     * {@code 2} and {@code 10}.  The upside is that Java {@code double} and {@code integer}
036     * primitives are easily extracted using {@code java.math.BigDecimal}'s exported methods.
037     */
038    public final BigDecimal number;
039
040    /**
041     * The parser will return true if the parsed Number-Literal had neither a "Decimal Part",
042     * nor an "Exponent Part".  If either of these were present, then this {@code boolean} will
043     * contain {@code FALSE}.
044     * 
045     * <BR /><BR />Note that even though something like {@code 5e2} - <I>which is actually just the
046     * integer {@code 500}</I> - were parsed, this {@code boolean} would still evaluate to 
047     * {@code FALSE}.
048     */
049    public final boolean integerOrNumber;
050
051    /**
052     * This shall contain one of three values: {@code '+', '-'} or ASCII {@code 0}.  If the parsed
053     * Number-Literal began with a sign-character, then the appropriate sign-character will be
054     * stored.  If the Number-Literal had no sign-character, then a {@code 0} is stored.
055     */
056    public final char signChar;
057
058
059    // ********************************************************************************************
060    // ********************************************************************************************
061    // Private Constructor, API "is" and "if" Methods
062    // ********************************************************************************************
063    // ********************************************************************************************
064
065
066    private Num(
067            final int[]         css,
068            final int           sPos,
069            final int           ePos,
070            final BigDecimal    number,
071            final boolean       integerOrNumber,
072            final char          signChar
073        )
074    {
075        super(css, sPos, ePos);
076
077        this.number             = number;
078        this.integerOrNumber    = integerOrNumber;
079        this.signChar           = signChar;
080    }
081
082    Num(
083            final int[] css,
084            final int   sPos,
085            final int   ePos,
086            final Num   n
087        )
088    { this(css, sPos, ePos, n.number, n.integerOrNumber, n.signChar); }
089
090    @Override 
091    public final boolean isNum() { return true; }
092
093    @Override
094    public final Num ifNum() { return this; }
095
096
097    // ********************************************************************************************
098    // ********************************************************************************************
099    // User's Constructor: a static "build" method
100    // ********************************************************************************************
101    // ********************************************************************************************
102
103
104    /**
105     * <EMBED CLASS=defs DATA-TOK=Num DATA-P=numStr>
106     * <EMBED CLASS='external-html' DATA-FILE-ID=BUILD_DESC>
107     * @param numStr <EMBED CLASS='external-html' DATA-FILE-ID=BUILD_PARAM>
108     * @return <EMBED CLASS='external-html' DATA-FILE-ID=BUILD_RET>
109     * @throws TokenizeException <EMBED CLASS='external-html' DATA-FILE-ID=BUILD_TOK_EX>
110     */
111    @SuppressWarnings("unchecked")
112    public static Num build(final String numStr)
113    { return (Num) CSSToken.build(numStr, INPUT_CHECKER, Num::consume); }
114
115    private static final CSSToken.InputChecker INPUT_CHECKER = (int[] css) ->
116    {
117        if (css.length < 1) throw new TokenizeException(Num.class);
118
119        if (! Num.is(css, 0)) throw new TokenizeException
120            ("String-text beginning does not constitute a valid CSS Number-Token");
121    };
122
123
124    // ********************************************************************************************
125    // ********************************************************************************************
126    // Tokenizer's "is" Method(s)
127    // ********************************************************************************************
128    // ********************************************************************************************
129
130
131    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
132    // Copied from:
133    // https://drafts.csswg.org/css-syntax-3/#check-if-three-code-points-would-start-a-number
134    // March 2024
135    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
136    //
137    // 4.3.10. Check if three code points would start a number
138    // 
139    // This section describes how to check if three code points would start a number. The algorithm
140    // described here can be called explicitly with three code points, or can be called with the
141    // input stream itself. In the latter case, the three code points in question are the current
142    // input code point and the next two input code points, in that order.
143    // 
144    // NOTE: This algorithm will not consume any additional code points.
145    // 
146    // Look at the first code point:
147    // 
148    // ** U+002B PLUS SIGN (+)
149    // ** U+002D HYPHEN-MINUS (-)
150    //      ==> 1) If the second code point is a digit, return true.
151    //          2) Otherwise, if the second code point is a U+002E FULL STOP (.) and the third code
152    //          point is a digit, return true.
153    //          3) Otherwise, return false.
154    // 
155    // ** U+002E FULL STOP (.)
156    //      ==> If the second code point is a digit, return true. Otherwise, return false.
157    // 
158    // ** digit
159    //      ==> Return true.
160    // 
161    // ** anything else
162    //      ==> Return false.
163    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
164
165    /**
166     * Checks whether or not the next token to consume is a number token, or number-subclass token.
167     * 
168     * <EMBED CLASS=defs DATA-TOK=Number-Literal
169     *      DATA-URL=check-if-three-code-points-would-start-a-number DATA-OP=Check>
170     * <EMBED CLASS=external-html DATA-FILE-ID=COPIED_CSS_WG>
171     * <EMBED CLASS=external-html DATA-FILE-ID=CHECK_NUMBER_3CP>
172     * @param css CSS-{@code String} as an array of code-points.
173     * @param sPos The array-index where the tokenizer is to consume its next token
174     * @return {@code TRUE} if and only if the next token in the array is a number
175     */
176    public static boolean is(int[] css, final int sPos)
177    {
178        final int c1 = ((sPos+0) < css.length) ? css[sPos+0] : 0;
179        final int c2 = ((sPos+1) < css.length) ? css[sPos+1] : 0;
180        final int c3 = ((sPos+2) < css.length) ? css[sPos+2] : 0;
181
182        // U+002B PLUS SIGN (+)  **OR**  U+002D HYPHEN-MINUS (-)
183        if ((c1 == '+') || (c1 == '-'))
184        {
185            // 1) If the second code point is a digit, return true.
186            if ((c2 >= '0') && (c2 <= '9')) return true;
187
188            // 2) Otherwise, if the second code point is a U+002E FULL STOP (.) and the third code
189            //    point is a digit, return true.
190
191            if ((c2 == '.') && (c3 >= '0') && (c3 <= '9'))  return true;
192
193            // 3) Otherwise, return false.
194            return false; 
195        }
196
197        // U+002E FULL STOP (.)
198        // If the second code point is a digit, return true. Otherwise, return false.
199
200        if (c1 == '.') return ((c2 >= '0') && (c2 <= '9'));
201
202        // digit ==> Return true.
203        if ((c1 >= '0') && (c1 <= '9')) return true;
204
205        // anything else ==> Return false.
206        return false;
207    }
208
209
210    // ********************************************************************************************
211    // ********************************************************************************************
212    // CONSUME
213    // ********************************************************************************************
214    // ********************************************************************************************
215
216
217    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
218    // Copied from:
219    // https://drafts.csswg.org/css-syntax-3/#consume-a-numeric-token
220    // March 27, 2024
221    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
222    //
223    // 4.3.3. Consume a numeric token
224    // 
225    // This section describes how to consume a numeric token from a stream of code points. It
226    // returns either a <number-token>, <percentage-token>, or <dimension-token>.
227    // 
228    // Consume a number and let number be the result.
229    // 
230    // If the next 3 input code points would start an ident sequence, then:
231    // 
232    // Create a <dimension-token> with the same value, type flag, and sign character as number, and
233    // a unit set initially to the empty string.
234    // 
235    // Consume an ident sequence. Set the <dimension-token>’s unit to the returned value.
236    // 
237    // Return the <dimension-token>.
238    // 
239    // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it.
240    // Create a <percentage-token> with the same value and sign character as number, and return it.
241    //
242    // Otherwise, create a <number-token> with the same value, type flag, and sign character as
243    // number, and return it.
244    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
245
246    /**
247     * This is a tokenizer method which <B>"consumes"</B> the next {@code Number}-Token (or 
248     * Number-Token Subclass) from the input Code-Point Array.
249     * 
250     * <EMBED CLASS=defs DATA-TOK=Numeric-Token DATA-URL=consume-a-numeric-token
251     *  DATA-OP=Consume>
252     * <EMBED CLASS=external-html DATA-FILE-ID=COPIED_CSS_WG>
253     * <EMBED CLASS=external-html DATA-FILE-ID=NUMERIC_TOKEN>
254     */
255    protected static void consume(                              // When invoked from 'CSSTokenizer'
256            final int[]                     css,                // C, int[] css
257            final ByRef<Integer>            POS,                // P, array-pos loop-variable
258            final Consumer<CSSToken>        returnParsedToken,  // T, Vector<CSSToken>.add
259            final Consumer<TokenizeError>   errorEncountered    // E, Vector<TokenizeError>.add
260        )
261    {
262        ByRef<Num> num = new ByRef<>();
263
264        final int numEndPos = consumeNumber(css, POS.f, num);
265
266        // If the next 3 input code points would start an ident sequence, then:
267        if (Identifier.startsIdentSequence(css, numEndPos))
268            Dimension.consume(css, POS, returnParsedToken, numEndPos, num.f);
269
270        // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it.
271        // Create a <percentage-token> with the same value and sign character as number, and return
272        // it.
273
274        else if ((numEndPos < css.length) && (css[numEndPos] == '%'))
275        {
276            returnParsedToken.accept(new Percentage(css, POS.f, numEndPos + 1, num.f));
277            POS.f = numEndPos + 1;
278        }
279
280        // Otherwise, create a <number-token> with the same value, type flag, and sign character as
281        // number, and return it.
282
283        else
284        {
285            returnParsedToken.accept(num.f);
286            POS.f = numEndPos;
287        }
288    }
289
290
291    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
292    // Copied from:
293    // https://drafts.csswg.org/css-syntax-3/#consume-a-number
294    // March 27, 2024
295    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
296    //
297    // 4.3.13. Consume a number
298    // 
299    // This section describes how to consume a number from a stream of code points. It returns a
300    // numeric value, a string type which is either "integer" or "number", and an optional sign
301    // character which is either "+", "-", or missing.
302    // 
303    // NOTE: This algorithm does not do the verification of the first few code points that are
304    // necessary to ensure a number can be obtained from the stream. Ensure that the stream starts
305    // with a number before calling this algorithm.
306    // 
307    // Execute the following steps in order:
308    // 
309    // 1) Let type be the string "integer". Let number part and exponent part be the empty string.
310    // 
311    // 2) If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), consume
312    //    it. Append it to number part and set sign character to it.
313    // 
314    // 3) While the next input code point is a digit, consume it and append it to number part.
315    // 
316    // 4) If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then:
317    //      1) Consume the next input code point and append it to number part.
318    //      2) While the next input code point is a digit, consume it and append it to number part.
319    //      3) Set type to "number".
320    // 
321    // 5) If the next 2 or 3 input code points are:
322    //      * U+0045 LATIN CAPITAL LETTER E (E)
323    //      * or U+0065 LATIN SMALL LETTER E (e).
324    //
325    //      optionally followed by:
326    //      * U+002D HYPHEN-MINUS (-)
327    //      * or U+002B PLUS SIGN (+),
328    // 
329    //      followed by a digit, then:
330    //
331    //      1) Consume the next input code point.
332    //      2) If the next input code point is "+" or "-", consume it and append to exponent part
333    //      3) While the next input code point is a digit, consume it and append it to exponent part.
334    //      4) Set type to "number".
335    //
336    // 6) Let value be the result of interpreting number part as a base-10 number.
337    //
338    //      If exponent part is non-empty, interpret it as a base-10 integer, then raise 10 to the
339    //      power of the result, multiply it by value, and set value to that result.
340    // 
341    // 7) Return value, type, and sign character.
342    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
343    //
344    // I had to create this helper/data record ever since removing a portion of the "consumeNumber"
345    // method to a second helper-method called "finishConsumeNumber".  Then / afterwards, in order
346    // to pass all of the data needed to do the "Finish Consuming a Number" code, I just went
347    // ahead and built this cute little "CNumRecord" - IN ORDER TO EASILY COPY THE RELEVANT 
348    // VARIABLES TO THE "finishConsumeNumber" method.
349    // 
350    // This "CNumRecord" really wouldn't even hurt a fly.  Leave it alone, it only exists to split
351    // up "consumeNumber" into two methods, while still having access to all the variables that are
352    // fields inside this class.
353    //
354    // The reason that "consumeNumber", sort-of, "spilled over" into a second-method is because the
355    // part that was put into the second-method is actually invoked from three different places
356    // within the first...  You get that, right?
357
358    private static class CNumRecord
359    {
360        final int           sPos;
361        final int[]         css;
362        final char          signChar;
363        final ByRef<Num>    outNum;
364
365        final IntStream.Builder numberPart    = IntStream.builder();
366        final IntStream.Builder expPart       = IntStream.builder();
367
368        private CNumRecord(
369                final int[]         css,
370                final int           sPos,
371                final ByRef<Num>    outNum,
372                final char          signChar
373            )
374        {
375            this.css        = css;
376            this.sPos       = sPos;
377            this.signChar   = signChar;
378            this.outNum     = outNum;
379        }
380    }
381
382    /**
383     * This is a tokenizer method which <B>"consumes"</B> the next {@code Number}-Literal from the
384     * input Code-Point Array.
385     * 
386     * <EMBED CLASS=defs DATA-TOK=Number-Literal DATA-URL=consume-a-number DATA-OP=Consume>
387     * <EMBED CLASS=external-html DATA-FILE-ID=COPIED_CSS_WG>
388     * <EMBED CLASS=external-html DATA-FILE-ID=NUMBER>
389     * <EMBED CLASS=external-html DATA-FILE-ID=NUMBER_TOK_SVG>
390     */
391    protected static int consumeNumber(
392            final int[]         css,
393            final int           sPos,
394            final ByRef<Num>    outNum
395        )
396    {
397        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
398        // 1) INITIALIZATION: 
399        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
400        // 
401        // a. Let type be the string "integer".
402        // b. Let number part and exponent part be the empty string.
403
404        boolean integerOrNumber = true;
405        int     pos             = sPos;
406        int     c               = css[pos++];
407
408
409        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
410        // 2) SIGN: If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-)
411        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
412        // 
413        // Consume it. Append it to number part and set sign character to it.
414        //
415        // Note that the 'Initializations' part (Step 1) spills over into step 2. The actual
416        // little configuration-record that I had to eventually write isn't instantiated until
417        // right here, in this if-branch.
418        //
419        // Remember that 'CNumRecord' sort of just stands for "Consume-Number-Record".
420
421        final CNumRecord r;
422
423        if ((c == '+') || (c == '-'))
424        {
425            // Instantiates a "Consume-Number-Record" with the Sign-Character set to +/-
426            r = new CNumRecord(css, sPos, outNum, (char) c);
427
428            r.numberPart.accept(c);
429
430            c = css[pos++]; // No need to check for IOOB, this method is package-private, and
431                            // it is only called if this contains a valid-number
432                            // There is no valid number that is only a '+' or '-'
433        }
434
435        // This initializes a "Consume-Number-Record" with an empty Sign-Character
436        else r = new CNumRecord(css, sPos, outNum, (char) 0);
437
438
439        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
440        // 3) NUMBER: While the next input-cp is a digit, consume it and append it to number part
441        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
442
443        while ((c >= '0') && (c <= '9'))
444        {
445            r.numberPart.accept(c);
446
447            if (pos < css.length)   c = css[pos++];
448            else                    return finishConsumeNumber(r, pos, true);
449        }
450
451
452        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
453        // 4) DECIMAL: If the next 2 input-cp's are U+002E FULL STOP (.) followed by a digit:
454        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
455        //
456        // 1) Consume the next input code point and append it to number part.
457        // 2) While the next input code point is a digit, consume it and append it to number part.
458        // 3) Set type to "number".
459
460        if (c == '.')
461        {
462            integerOrNumber = false;
463
464            if ((pos < css.length) && Character.isDigit(css[pos]))
465            {
466                r.numberPart.accept('.'); // The '.' (dot / full-stop)
467                c = css[pos++];
468
469                // This 'while-loop' was EXACTLY BLOCK-COPIED from the one directly above
470                while ((c >= '0') && (c <= '9'))
471                {
472                    r.numberPart.accept(c);
473        
474                    if (pos < css.length)   c = css[pos++];
475                    else                    return finishConsumeNumber(r, pos, false);
476                }
477            }
478
479            // The following 'else' branch is for cases such as "10. Chapter 5" (Where the '.' is
480            // not part of the number)
481            // 
482            // Note that since 'pos' is currently pointing at the character after the '.' (or it is
483            // pointing at css.length), '1' MUST BE SUBTRACTED FROM 'pos'
484
485            else return finishConsumeNumber(r, pos - 1, true);
486        }
487
488
489        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
490        // 5) EXPONENT: If the next 2 or 3 input code points are:  (This part sucks and is ugly)
491        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
492        //
493        // * U+0045 LATIN CAPITAL LETTER E (E)
494        // * or U+0065 LATIN SMALL LETTER E (e).
495        //
496        // optionally followed by:
497        // * U+002D HYPHEN-MINUS (-)
498        // * or U+002B PLUS SIGN (+),
499        // 
500        // followed by a digit, then:
501        //
502        // 1) Consume the next input code point.
503        // 2) If the next input code point is "+" or "-", consume it and append to exponent part
504        // 3) While the next input code point is a digit, consume it and append it to exponent part.
505        // 4) Set type to "number".
506
507        if ((c != 'e') && (c != 'E'))
508
509            // For this case, we have to "back the pointer up" by 1 place - a.k.a. pass "pos - 1"
510            // This is because 'pos' is currently pointing to TWO CHARACTERS AFTER THE LAST NUMBER
511
512            return finishConsumeNumber(r, pos - 1, integerOrNumber);
513
514        // NOTE: From this point foward, 'c' IS GUARANTEED TO HOLD AN 'e' OR AN 'E'.
515        //       This is very-likely (but not guaranteed) to be an "Exponent-Part"
516        //
517        // If the 'e' or 'E' happend to be an extranneous letter (for instance as in 5em), where
518        // the 'e' was the first letter of the dimension-identifier "em", then that situation is
519        // handled at the very end (on the last line) of this method.  In that particular situation
520        // THIS WOULD NOT BE AN EXPONENT
521        // 
522        // The particular 'if-branch' (which is directly below) handles the case where there is an
523        // 'e' or 'E', followed by AT LEAST ONE DIGIT.  If that has happend, then THIS IS
524        // GUARANTEED TO BE AN EXPONENT.
525
526        /*
527        Don't delete this.  If there is any confusion, this is the only way to have even a
528        prayer of understanding what all of these cute-little "++" even are.
529
530        System.out.println(
531            "c=" + ((char) c) + ", " +
532            "pos=" + pos + ", " +
533            "css[" + (pos) + "]=" + ((char) css[pos]) + ", " +
534            (((pos+1) < css.length) ? ("css[" + (pos) + "]=" + ((char) css[pos])) : "")
535        );
536        */
537
538        if (    (pos < css.length)
539            &&  Character.isDigit(css[pos])
540        )
541        {
542            r.expPart.accept(c);            // The letter 'e' or 'E'
543            r.expPart.accept(css[pos++]);   // The first Digit of the Exponent
544
545            // This type of coding is so different than anything I have done.  You just don't ever
546            // mess with this type of stuff on a regular-basis.  It's exactly the type of code that
547            // you would think is really easy and common.  Unfortunately, it is extremely uncommon
548            // because once a parser has been written, you never have to go back and write one
549            // again.
550            // 
551            // TESTING HELPS A LOT.  Everything is an "off-by-one error" in this Parser.  There are
552            // SO MANY MICRO-VARIANTS TO WORRY ABOUT!  What if the CSS-String ends at the end of
553            // this number?  Obviously that won't happen much, but if it does, the whole program
554            // crashes.  What if a User is actually intending to use Scientific-Notation in his
555            // css?  Obviously that's even less common, but if you don't get it right, the whole
556            // program crashes.
557            //
558            // What if they are using Scientific-Notation with a Percentage?
559            // As in: "+1e-3%"  Sound ridiculous?  CSS accepts that, so this code has to handle it
560
561            while (pos < css.length)
562            {
563                if (Character.isDigit(c = css[pos++]))  r.expPart.accept(c);
564                else                                    { pos--; break; }
565            }
566
567            return finishConsumeNumber(r, pos, false);
568        }
569
570        // This 'if-branch' handles the case where there was an 'e' or 'E', followed by a '+' or
571        // '-' sign, followed by any digit.  THIS IS ALSO A CASE WHERE THERE **IS** AN EXPONENT
572
573        if (    ((pos + 1) < css.length)
574            &&  ((css[pos] == '+') || (css[pos] == '-'))
575            &&  Character.isDigit(css[pos + 1])
576        )
577        {
578            r.expPart.accept(c);            // The letter 'e' or 'E'
579            r.expPart.accept(css[pos++]);   // The Sign Character for the Exponent
580            r.expPart.accept(css[pos++]);   // The first Digit of the Exponent
581
582            // This loop was block copied from the one directly above.  See that loop for a little
583            // background information.
584
585            while (pos < css.length)
586            {
587                if (Character.isDigit(c = css[pos++]))  r.expPart.accept(c);
588                else                                    { pos--; break; }
589            }
590
591            return finishConsumeNumber(r, pos, false);
592        }
593
594        // This line of code will **ONLY** be reached if there was an 'e' or 'E' character that 
595        // wasn't actually followed by a number.  Again the most common example whereby this line
596        // would be reached are things such as "5em", where the 'e' turns out not to be an 
597        // exponent-character, but rather the first letter in the "Dimension-String" "em"
598        // (which, actually stands for "emphemeral unit", and means the current font-size)
599
600        return finishConsumeNumber(r, pos - 1, integerOrNumber);
601    }
602
603    // Helper method for the above method
604    private static int finishConsumeNumber(
605            final CNumRecord    r,
606            final int           pos,
607            final boolean       integerOrNumber
608        )
609    {
610        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
611        // 6) Let value be the result of interpreting number part as a base-10 number.
612        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
613        //
614        // If exponent part is non-empty, interpret it as a base-10 integer, then raise 10 to the
615        // power of the result, multiply it by value, and set value to that result.
616
617        final int[] numArr = r.numberPart.build().toArray();
618        final int[] expArr = r.expPart.build().toArray();
619
620        final String numStr = new String(numArr, 0, numArr.length);
621        final String expStr = new String(expArr, 0, expArr.length);
622
623        final String bdStr = numStr + expStr;
624
625        /*
626        It is true that a "CSS Parser" or a "CSS Tokenizer" isn't that difficult to write.  The
627        catch is that this type of code is stuff that you just never write very often.  As such,
628        observing this and debugging this isn't that fun.  Please don't delete this comment.  It is
629        the only way to figure out the tiny-mistakes that can be made in "Num.consume"
630        */
631
632        /*
633        System.out.println(
634            "\tr.sPos:          " + r.sPos + '\n' +
635            "\tpos:             " + pos + '\n' +
636            "\tbdStr:           " + bdStr + '\n' +
637            "\tnumStr:          " + numStr + '\n' +
638            "\texpStr:          " + expStr + '\n' +
639            "\tintegerOrNumber: " + integerOrNumber
640        );
641        */
642
643        final BigDecimal bd = new BigDecimal(bdStr);
644
645
646        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
647        // 7) Return value, type, and sign character.
648        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
649
650        r.outNum.f = new Num(r.css, r.sPos, pos, bd, integerOrNumber, r.signChar);
651        return pos;
652    }
653}