001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/Scanner.java#24 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 1998-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2008 Julian Hyde and others
008    // All Rights Reserved.
009    // You must accept the terms of that agreement to use this software.
010    //
011    // jhyde, 20 January, 1999
012    */
013    
014    package mondrian.olap;
015    
016    import java_cup.runtime.Symbol;
017    import org.apache.log4j.Logger;
018    import java.util.List;
019    import java.util.ArrayList;
020    import java.util.Enumeration;
021    import java.util.Hashtable;
022    import java.io.IOException;
023    import java.math.BigDecimal;
024    
025    /**
026     * Lexical analyzer for MDX.
027     */
028    public class Scanner {
029        private static final Logger LOGGER = Logger.getLogger(Scanner.class);
030    
031        /** single lookahead character */
032        protected int nextChar;
033        /** next lookahead character */
034        private int lookaheadChars[] = new int[16];
035        private int firstLookaheadChar = 0;
036        private int lastLookaheadChar = 0;
037        private Hashtable<String, Integer> m_resWordsTable;
038        private int iMaxResword;
039        private String m_aResWords[];
040        protected boolean debug;
041    
042        /** lines[x] is the start of the x'th line */
043        private List<Integer> lines;
044    
045        /** number of times advance() has been called */
046        private int iChar;
047    
048        /** end of previous token */
049        private int iPrevChar;
050    
051        /** previous symbol returned */
052        private int previousSymbol;
053        private boolean inFormula;
054    
055        /**
056         * Comment delimiters. Modify this list to support other comment styles.
057         */
058        private static final String[][] commentDelim = {
059            new String[] {"//", null},
060            new String[] {"--", null},
061            new String[] {"/*", "*/"}
062        };
063    
064        /**
065         * Whether to allow nested comments.
066         */
067        private static final boolean allowNestedComments = true;
068    
069        /**
070         * The {@link java.math.BigDecimal} value 0.
071         * Note that BigDecimal.ZERO does not exist until JDK 1.5.
072         */
073        private static final BigDecimal BigDecimalZero = BigDecimal.valueOf(0);
074    
075        /**
076         * Creates a Scanner.
077         *
078         * @param debug Whether to emit debug messages.
079         */
080        Scanner(boolean debug) {
081            this.debug = debug;
082        }
083    
084        /**
085         * Returns the current nested comments state.
086         */
087        public static boolean getNestedCommentsState() {
088            return allowNestedComments;
089        }
090    
091        /**
092         * Returns the list of comment delimiters.
093         */
094        public static String[][] getCommentDelimiters() {
095            return commentDelim;
096        }
097    
098        /**
099         * Advance input by one character, setting {@link #nextChar}.
100         */
101        private void advance()
102            throws java.io.IOException {
103    
104            if (firstLookaheadChar == lastLookaheadChar) {
105                // We have nothing in the lookahead buffer.
106                nextChar = getChar();
107            } else {
108                // We have called lookahead(); advance to the next character it got.
109                nextChar = lookaheadChars[firstLookaheadChar++];
110                if (firstLookaheadChar == lastLookaheadChar) {
111                    firstLookaheadChar = 0;
112                    lastLookaheadChar = 0;
113                }
114            }
115            if (nextChar == '\012') {
116                lines.add(iChar);
117            }
118            iChar++;
119        }
120    
121        /** Peek at the character after {@link #nextChar} without advancing. */
122        private int lookahead()
123            throws java.io.IOException {
124    
125            return lookahead(1);
126        }
127    
128        /**
129         * Peeks at the character n after {@link #nextChar} without advancing.
130         * lookahead(0) returns the current char (nextChar).
131         * lookahead(1) returns the next char (was lookaheadChar, same as lookahead());
132         */
133        private int lookahead(int n)
134            throws java.io.IOException {
135    
136            if (n == 0) {
137                return nextChar;
138            } else {
139                // if the desired character not in lookahead buffer, read it in
140                if (n > lastLookaheadChar - firstLookaheadChar) {
141                    int len = lastLookaheadChar - firstLookaheadChar;
142                    int t[];
143    
144                    // make sure we do not go off the end of the buffer
145                    if (n + firstLookaheadChar > lookaheadChars.length) {
146                        if (n > lookaheadChars.length) {
147                            // the array is too small; make it bigger and shift
148                            // everything to the beginning.
149                            t = new int[n * 2];
150                        } else {
151                            // the array is big enough, so just shift everything
152                            // to the beginning of it.
153                            t = lookaheadChars;
154                        }
155    
156                        System.arraycopy(
157                            lookaheadChars, firstLookaheadChar, t, 0, len);
158                        lookaheadChars = t;
159                        firstLookaheadChar = 0;
160                        lastLookaheadChar = len;
161                    }
162    
163                    // read ahead enough
164                    while (n > lastLookaheadChar - firstLookaheadChar) {
165                        lookaheadChars[lastLookaheadChar++] = getChar();
166                    }
167                }
168    
169                return lookaheadChars[n - 1 + firstLookaheadChar];
170            }
171        }
172    
173        /** Read a character from input, returning -1 if end of input. */
174        protected int getChar()
175            throws java.io.IOException {
176    
177            return System.in.read();
178        }
179    
180        /** Initialize the scanner */
181        public void init()
182            throws java.io.IOException {
183    
184            initReswords();
185            lines = new ArrayList<Integer>();
186            iChar = iPrevChar = 0;
187            advance();
188        }
189    
190        /**
191         * Deduces the line and column (0-based) of a symbol.
192         * Called by {@link Parser#syntax_error}.
193         */
194        void getLocation(Symbol symbol, int[] loc) {
195            int iTarget = symbol.left;
196            int iLine = -1;
197            int iLineEnd = 0;
198            int iLineStart;
199            do {
200                iLine++;
201                iLineStart = iLineEnd;
202                iLineEnd = Integer.MAX_VALUE;
203                if (iLine < lines.size()) {
204                    iLineEnd = lines.get(iLine);
205                }
206            } while (iLineEnd < iTarget);
207    
208            loc[0] = iLine; // line
209            loc[1] = iTarget - iLineStart; // column
210        }
211    
212        private Symbol trace(Symbol s) {
213            if (debug) {
214                String name = null;
215                if (s.sym < m_aResWords.length) {
216                    name = m_aResWords[s.sym];
217                }
218    
219                LOGGER.error("Scanner returns #" + s.sym +
220                    (name == null ? "" : ":" + name) +
221                    (s.value == null ? "" : "(" + s.value.toString() + ")"));
222            }
223            return s;
224        }
225    
226        private void initResword(int id, String s) {
227            m_resWordsTable.put(s, id);
228            if (id > iMaxResword) {
229                iMaxResword = id;
230            }
231        }
232    
233        /**
234         * Initializes the table of reserved words.
235         */
236        private void initReswords() {
237            // This list generated by piping the 'terminal' declaration in mdx.cup
238            // through:
239            //   grep -list // |
240            //   sed -e 's/,//' |
241            //   awk '{printf "initResword(%20s,%c%s%c);",$1,34,$1,34}'
242            m_resWordsTable = new Hashtable<String, Integer>();
243            iMaxResword = 0;
244    //      initResword(ParserSym.ALL                 ,"ALL");
245            initResword(ParserSym.AND                 ,"AND");
246            initResword(ParserSym.AS                  ,"AS");
247    //      initResword(ParserSym.ASC                 ,"ASC");
248            initResword(ParserSym.AXIS                ,"AXIS");
249    //      initResword(ParserSym.BACK_COLOR          ,"BACK_COLOR");
250    //      initResword(ParserSym.BASC                ,"BASC");
251    //      initResword(ParserSym.BDESC               ,"BDESC");
252            initResword(ParserSym.CAST                ,"CAST"); // mondrian extension
253            initResword(ParserSym.CASE                ,"CASE");
254            initResword(ParserSym.CELL                ,"CELL");
255    //      initResword(ParserSym.CELL_ORDINAL        ,"CELL_ORDINAL");
256    //      initResword(ParserSym.CHAPTERS            ,"CHAPTERS");
257    //      initResword(ParserSym.CHILDREN            ,"CHILDREN");
258    //      initResword(ParserSym.COLUMNS             ,"COLUMNS");
259    //      initResword(ParserSym.DESC                ,"DESC");
260            initResword(ParserSym.DIMENSION           ,"DIMENSION");
261            initResword(ParserSym.ELSE                ,"ELSE");
262            initResword(ParserSym.EMPTY               ,"EMPTY");
263            initResword(ParserSym.END                 ,"END");
264    //      initResword(ParserSym.FIRSTCHILD          ,"FIRSTCHILD");
265    //      initResword(ParserSym.FIRSTSIBLING        ,"FIRSTSIBLING");
266    //      initResword(ParserSym.FONT_FLAGS          ,"FONT_FLAGS");
267    //      initResword(ParserSym.FONT_NAME           ,"FONT_NAME");
268    //      initResword(ParserSym.FONT_SIZE           ,"FONT_SIZE");
269    //      initResword(ParserSym.FORE_COLOR          ,"FORE_COLOR");
270    //      initResword(ParserSym.FORMATTED_VALUE     ,"FORMATTED_VALUE");
271    //      initResword(ParserSym.FORMAT_STRING       ,"FORMAT_STRING");
272            initResword(ParserSym.FROM                ,"FROM");
273            initResword(ParserSym.IS                  ,"IS");
274            initResword(ParserSym.IN                  ,"IN");
275    //      initResword(ParserSym.LAG                 ,"LAG");
276    //      initResword(ParserSym.LASTCHILD           ,"LASTCHILD");
277    //      initResword(ParserSym.LASTSIBLING         ,"LASTSIBLING");
278    //      initResword(ParserSym.LEAD                ,"LEAD");
279            initResword(ParserSym.MATCHES             ,"MATCHES");
280            initResword(ParserSym.MEMBER              ,"MEMBER");
281    //      initResword(ParserSym.MEMBERS             ,"MEMBERS");
282    //      initResword(ParserSym.NEXTMEMBER          ,"NEXTMEMBER");
283            initResword(ParserSym.NON                 ,"NON");
284            initResword(ParserSym.NOT                 ,"NOT");
285            initResword(ParserSym.NULL                ,"NULL");
286            initResword(ParserSym.ON                  ,"ON");
287            initResword(ParserSym.OR                  ,"OR");
288    //      initResword(ParserSym.PAGES               ,"PAGES");
289    //      initResword(ParserSym.PARENT              ,"PARENT");
290    //      initResword(ParserSym.PREVMEMBER          ,"PREVMEMBER");
291            initResword(ParserSym.PROPERTIES          ,"PROPERTIES");
292    //      initResword(ParserSym.RECURSIVE           ,"RECURSIVE");
293    //      initResword(ParserSym.ROWS                ,"ROWS");
294    //      initResword(ParserSym.SECTIONS            ,"SECTIONS");
295            initResword(ParserSym.SELECT              ,"SELECT");
296            initResword(ParserSym.SET                 ,"SET");
297    //      initResword(ParserSym.SOLVE_ORDER         ,"SOLVE_ORDER");
298            initResword(ParserSym.THEN                ,"THEN");
299    //      initResword(ParserSym.VALUE               ,"VALUE");
300            initResword(ParserSym.WHEN                ,"WHEN");
301            initResword(ParserSym.WHERE               ,"WHERE");
302            initResword(ParserSym.WITH                ,"WITH");
303            initResword(ParserSym.XOR                 ,"XOR");
304    
305            m_aResWords = new String[iMaxResword + 1];
306            Enumeration<String> e = m_resWordsTable.keys();
307            while (e.hasMoreElements()) {
308                Object o = e.nextElement();
309                String s = (String) o;
310                int i = (m_resWordsTable.get(s)).intValue();
311                m_aResWords[i] = s;
312            }
313        }
314    
315        /** return the name of the reserved word whose token code is "i" */
316        public String lookupReserved(int i) {
317            return m_aResWords[i];
318        }
319    
320        private Symbol makeSymbol(int id, Object o) {
321            int iPrevPrevChar = iPrevChar;
322            this.iPrevChar = iChar;
323            this.previousSymbol = id;
324            return trace(new Symbol(id, iPrevPrevChar, iChar, o));
325        }
326    
327        /**
328         * Creates a token representing a numeric literal.
329         *
330         * @param mantissa The digits of the number
331         * @param exponent The base-10 exponent of the number
332         * @return number literal token
333         */
334        private Symbol makeNumber(BigDecimal mantissa, int exponent) {
335            double d = mantissa.movePointRight(exponent).doubleValue();
336            return makeSymbol(ParserSym.NUMBER, d);
337        }
338    
339        private Symbol makeId(String s, boolean quoted, boolean ampersand) {
340            return makeSymbol(
341                quoted && ampersand ? ParserSym.AMP_QUOTED_ID :
342                quoted ? ParserSym.QUOTED_ID :
343                ParserSym.ID,
344                s);
345        }
346    
347        /**
348         * Creates a token representing a reserved word.
349         *
350         * @param i Token code
351         * @return Token
352         */
353        private Symbol makeRes(int i) {
354            return makeSymbol(i, m_aResWords[i]);
355        }
356    
357        /**
358         * Creates a token.
359         *
360         * @param i Token code
361         * @param s Text of the token
362         * @return Token
363         */
364        private Symbol makeToken(int i, String s) {
365            return makeSymbol(i, s);
366        }
367    
368        /**
369         * Creates a token representing a string literal.
370         *
371         * @param s String
372         * @return String token
373         */
374        private Symbol makeString(String s) {
375            if (inFormula) {
376                inFormula = false;
377                return makeSymbol(ParserSym.FORMULA_STRING, s);
378            } else {
379                return makeSymbol(ParserSym.STRING, s);
380            }
381        }
382    
383        /**
384         * Discards all characters until the end of the current line.
385         */
386        private void skipToEOL() throws IOException {
387            while (nextChar != -1 && nextChar != '\012') {
388                advance();
389            }
390        }
391    
392        /**
393         * Eats a delimited comment.
394         * The type of delimiters are kept in commentDelim.  The current
395         * comment type is indicated by commentType.
396         * end of file terminates a comment without error.
397         */
398        private void skipComment(
399                final String startDelim,
400                final String endDelim) throws IOException {
401    
402            int depth = 1;
403    
404            // skip the starting delimiter
405            for (int x = 0; x < startDelim.length(); x++) {
406                advance();
407            }
408    
409            for (;;) {
410                if (nextChar == -1) {
411                    return;
412                } else if (checkForSymbol(endDelim)) {
413                    // eat the end delimiter
414                    for (int x = 0; x < endDelim.length(); x++) {
415                        advance();
416                    }
417                    if (--depth == 0) {
418                        return;
419                    }
420                } else if (allowNestedComments && checkForSymbol(startDelim)) {
421                   // eat the nested start delimiter
422                    for (int x = 0; x < startDelim.length(); x++) {
423                        advance();
424                    }
425                    depth++;
426                } else {
427                    advance();
428                }
429            }
430        }
431    
432        /**
433         * If the next tokens are comments, skip over them.
434         */
435        private void searchForComments() throws IOException {
436    
437            // eat all following comments
438            boolean foundComment;
439            do {
440                foundComment = false;
441                for (String[] aCommentDelim : commentDelim) {
442                    if (checkForSymbol(aCommentDelim[0])) {
443                        if (aCommentDelim[1] == null) {
444                            foundComment = true;
445                            skipToEOL();
446                        } else {
447                            foundComment = true;
448                            skipComment(aCommentDelim[0], aCommentDelim[1]);
449                        }
450                    }
451                }
452            } while (foundComment);
453        }
454    
455        /**
456         * Checks if the next symbol is the supplied string
457         */
458        private boolean checkForSymbol(final String symb) throws IOException {
459                for (int x = 0; x < symb.length(); x++) {
460                    if (symb.charAt(x) != lookahead(x)) {
461                        return false;
462                    }
463                }
464                return true;
465        }
466    
467        /**
468         * Recognizes and returns the next complete token.
469         */
470        public Symbol next_token() throws IOException {
471    
472            StringBuilder id;
473            boolean ampersandId = false;
474            for (;;) {
475                searchForComments();
476                switch (nextChar) {
477                case '.':
478                    switch (lookahead()) {
479                    case '0': case '1': case '2': case '3': case '4':
480                    case '5': case '6': case '7': case '8': case '9':
481                        // We're looking at the '.' on the start of a number,
482                        // e.g. .1; fall through to parse a number.
483                        break;
484                    default:
485                        advance();
486                        return makeToken(ParserSym.DOT, ".");
487                    }
488                    // fall through
489    
490                case '0': case '1': case '2': case '3': case '4':
491                case '5': case '6': case '7': case '8': case '9':
492    
493                    // Parse a number.  Valid examples include 1, 1.2, 0.1, .1,
494                    // 1e2, 1E2, 1e-2, 1e+2.  Invalid examples include e2, 1.2.3,
495                    // 1e2e3, 1e2.3.
496                    //
497                    // Signs preceding numbers (e.g. -1,  + 1E-5) are valid, but are
498                    // handled by the parser.
499                    //
500                    BigDecimal n = BigDecimalZero;
501                    int digitCount = 0, exponent = 0;
502                    boolean positive = true;
503                    BigDecimal mantissa = BigDecimalZero;
504                    State state = State.leftOfPoint;
505    
506                    for (;;) {
507                        switch (nextChar) {
508                        case '.':
509                            switch (state) {
510                            case leftOfPoint:
511                                state = State.rightOfPoint;
512                                mantissa = n;
513                                n = BigDecimalZero;
514                                digitCount = 0;
515                                positive = true;
516                                advance();
517                                break;
518                                // Error: we are seeing a point in the exponent
519                                // (e.g. 1E2.3 or 1.2E3.4) or a second point in the
520                                // mantissa (e.g. 1.2.3).  Return what we've got
521                                // and let the parser raise the error.
522                            case rightOfPoint:
523                                mantissa =
524                                    mantissa.add(
525                                        n.movePointRight(-digitCount));
526                                return makeNumber(mantissa, exponent);
527                            case inExponent:
528                                if (!positive) {
529                                    n = n.negate();
530                                }
531                                exponent = n.intValue();
532                                return makeNumber(mantissa, exponent);
533                            }
534                            break;
535    
536                        case 'E':
537                        case 'e':
538                            switch (state) {
539                            case inExponent:
540                                // Error: we are seeing an 'e' in the exponent
541                                // (e.g. 1.2e3e4).  Return what we've got and let
542                                // the parser raise the error.
543                                if (!positive) {
544                                    n = n.negate();
545                                }
546                                exponent = n.intValue();
547                                return makeNumber(mantissa, exponent);
548                            case leftOfPoint:
549                                mantissa = n;
550                                break;
551                            default:
552                                mantissa =
553                                    mantissa.add(
554                                        n.movePointRight(-digitCount));
555                                break;
556                            }
557    
558                            digitCount = 0;
559                            n = BigDecimalZero;
560                            positive = true;
561                            advance();
562                            state = State.inExponent;
563                            break;
564    
565                        case'0': case'1': case'2': case'3': case'4':
566                        case'5': case'6': case'7': case'8': case'9':
567                            n = n.movePointRight(1);
568                            n = n.add(BigDecimal.valueOf(nextChar - '0'));
569                            digitCount++;
570                            advance();
571                            break;
572    
573                        case '+':
574                        case '-':
575                            if (state == State.inExponent &&
576                                digitCount == 0) {
577                                // We're looking at the sign after the 'e'.
578                                positive = !positive;
579                                advance();
580                                break;
581                            }
582                            // fall through - end of number
583    
584                        default:
585                            // Reached end of number.
586                            switch (state) {
587                            case leftOfPoint:
588                                mantissa = n;
589                                break;
590                            case rightOfPoint:
591                                mantissa =
592                                    mantissa.add(
593                                        n.movePointRight(-digitCount));
594                                break;
595                            default:
596                                if (!positive) {
597                                    n = n.negate();
598                                }
599                                exponent = n.intValue();
600                                break;
601                            }
602                            return makeNumber(mantissa, exponent);
603                        }
604                    }
605    
606                case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
607                case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
608                case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
609                case 's': case 't': case 'u': case 'v': case 'w': case 'x':
610                case 'y': case 'z':
611                case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
612                case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
613                case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
614                case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
615                case 'Y': case 'Z':
616                    /* parse an identifier */
617                    id = new StringBuilder();
618                    for (;;) {
619                        id.append((char)nextChar);
620                        advance();
621                        switch (nextChar) {
622                        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
623                        case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
624                        case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
625                        case 's': case 't': case 'u': case 'v': case 'w': case 'x':
626                        case 'y': case 'z':
627                        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
628                        case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
629                        case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
630                        case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
631                        case 'Y': case 'Z':
632                        case '0': case '1': case '2': case '3': case '4':
633                        case '5': case '6': case '7': case '8': case '9':
634                        case '_':
635                            break;
636                        default:
637                            String strId = id.toString();
638                            Integer i = m_resWordsTable.get(
639                                strId.toUpperCase());
640                            if (i == null) {
641                                // identifier
642                                return makeId(strId, false, false);
643                            } else {
644                                // reserved word
645                                return makeRes(i);
646                            }
647                        }
648                    }
649    
650                case '&':
651                    advance();
652                    if (nextChar == '[') {
653                        ampersandId = true;
654                        // fall through
655                    } else {
656                        return makeToken(ParserSym.UNKNOWN, "&");
657                    }
658    
659                case '[':
660                    /* parse a delimited identifier */
661                    id = new StringBuilder();
662                    for (;;) {
663                        advance();
664                        switch (nextChar) {
665                        case ']':
666                            advance();
667                            if (nextChar == ']') {
668                                // ] escaped with ] - just take one
669                                id.append(']');
670                                break;
671                            } else {
672                                // end of identifier
673                                if (ampersandId) {
674                                    ampersandId = false;
675                                    return makeId(id.toString(), true, true);
676                                } else {
677                                    return makeId(id.toString(), true, false);
678                                }
679                            }
680                        case -1:
681                            if (ampersandId) {
682                                ampersandId = false;
683                                return makeId(id.toString(), true, true);
684                            } else {
685                                return makeId(id.toString(), true, false);
686                            }
687                        default:
688                            id.append((char)nextChar);
689                        }
690                    }
691    
692                case ':':
693                    advance();
694                    return makeToken(ParserSym.COLON, ":");
695                case ',':
696                    advance();
697                    return makeToken(ParserSym.COMMA, ",");
698                case '=':
699                    advance();
700                    return makeToken(ParserSym.EQ, "=");
701                case '<':
702                    advance();
703                    switch (nextChar) {
704                    case '>':
705                        advance();
706                        return makeToken(ParserSym.NE, "<>");
707                    case '=':
708                        advance();
709                        return makeToken(ParserSym.LE, "<=");
710                    default:
711                        return makeToken(ParserSym.LT, "<");
712                    }
713                case '>':
714                    advance();
715                    switch (nextChar) {
716                    case '=':
717                        advance();
718                        return makeToken(ParserSym.GE, ">=");
719                    default:
720                        return makeToken(ParserSym.GT, ">");
721                    }
722                case '{':
723                    advance();
724                    return makeToken(ParserSym.LBRACE, "{");
725                case '(':
726                    advance();
727                    return makeToken(ParserSym.LPAREN, "(");
728                case '}':
729                    advance();
730                    return makeToken(ParserSym.RBRACE, "}");
731                case ')':
732                    advance();
733                    return makeToken(ParserSym.RPAREN, ")");
734                case '+':
735                    advance();
736                    return makeToken(ParserSym.PLUS, "+");
737                case '-':
738                    advance();
739                    return makeToken(ParserSym.MINUS, "-");
740                case '*':
741                    advance();
742                    return makeToken(ParserSym.ASTERISK, "*");
743                case '/':
744                    advance();
745                    return makeToken(ParserSym.SOLIDUS, "/");
746                case '!':
747                    advance();
748                    return makeToken(ParserSym.BANG, "!");
749                case '|':
750                    advance();
751                    switch (nextChar) {
752                    case '|':
753                        advance();
754                        return makeToken(ParserSym.CONCAT, "||");
755                    default:
756                        return makeToken(ParserSym.UNKNOWN, "|");
757                    }
758    
759                case '"':
760                    /* parse a double-quoted string */
761                    id = new StringBuilder();
762                    for (;;) {
763                        advance();
764                        switch (nextChar) {
765                        case '"':
766                            advance();
767                            if (nextChar == '"') {
768                                // " escaped with "
769                                id.append('"');
770                                break;
771                            } else {
772                                // end of string
773                                return makeString(id.toString());
774                            }
775                        case -1:
776                            return makeString(id.toString());
777                        default:
778                            id.append((char)nextChar);
779                        }
780                    }
781    
782                case '\'':
783                    if (previousSymbol == ParserSym.AS) {
784                        inFormula = true;
785                    }
786    
787                    /* parse a single-quoted string */
788                    id = new StringBuilder();
789                    for (;;) {
790                        advance();
791                        switch (nextChar) {
792                        case '\'':
793                            advance();
794                            if (nextChar == '\'') {
795                                // " escaped with "
796                                id.append('\'');
797                                break;
798                            } else {
799                                // end of string
800                                return makeString(id.toString());
801                            }
802                        case -1:
803                            return makeString(id.toString());
804                        default:
805                            id.append((char)nextChar);
806                        }
807                    }
808    
809                case -1:
810                    // we're done
811                    return makeToken(ParserSym.EOF, "EOF");
812    
813                default:
814                    // If it's whitespace, skip over it.
815                    // (When we switch to JDK 1.5, use Character.isWhitespace(int);
816                    // til then, there's just Character.isWhitespace(char).)
817                    if (nextChar <= Character.MAX_VALUE &&
818                            Character.isWhitespace((char) nextChar)) {
819                        // fall through
820                    } else {
821                        // everything else is an error
822                        throw new RuntimeException(
823                                "Unexpected character '" + (char) nextChar + "'");
824                    }
825    
826                case ' ':
827                case '\t':
828                case '\n':
829                case '\r':
830                    // whitespace can be ignored
831                    iPrevChar = iChar;
832                    advance();
833                    break;
834                }
835            }
836        }
837    
838        private enum State {
839            leftOfPoint,
840            rightOfPoint,
841            inExponent,
842        }
843    }
844    
845    // End Scanner.java