Package net.janino

Source Code of net.janino.Scanner$Token

/*
* Janino - An embedded Java[TM] compiler
* Copyright (C) 2001-2004 Arno Unkrig
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Contact information:
*   Arno Unkrig
*   Ferdinand-Miller-Platz 10
*   80335 Muenchen
*   Germany
*   http://www.janino.net
*   maintainer@janino.net
*/

package net.janino;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

import net.janino.util.TeeReader;

/**
* Splits up a character stream into tokens and returns them as
* {@link java.lang.String String} objects.
* <p>
* The <code>optionalFileName</code> parameter passed to many
* constructors should point
*/

public class Scanner {

    /**
     * Set up a scanner that reads tokens from the given file in the platform
     * default encoding.
     * <p>
     * Don't forget to {@link #close()} when you're done, so the input file is closed.
     * @param fileName
     * @throws ScanException
     * @throws IOException
     */
    public Scanner(String fileName) throws ScanException, IOException {
        this(
            fileName,
            new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(fileName)))),
            true,                  // closeReaderOnConstructorException
            (short) 1, (short) 0   // initialLineNumber, initialColumnNumber
        );
    }

    /**
     * Set up a scanner that reads tokens from the given file in the platform
     * default encoding.
     * <p>
     * Don't forget to {@link #close()} when you're done, so the input file is closed.
     * @param file
     * @throws ScanException
     * @throws IOException
     */
    public Scanner(File file) throws ScanException, IOException {
        this(
            file.getAbsolutePath(),
            new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(file)))),
            true,                  // closeReaderOnConstructorException
            (short) 1, (short) 0   // initialLineNumber, initialColumnNumber
        );
    }

    /**
     * Set up a scanner that reads tokens from the given file in the given encoding.
     * <p>
     * Don't forget to {@link #close()} when you're done, so the input file is closed.
     * @param file
     * @param encoding
     * @throws ScanException
     * @throws IOException
     */
    public Scanner(File file, String encoding) throws ScanException, IOException {
        this(
            file.getAbsolutePath(),
            new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(file)), encoding)),
            true,                  // closeReaderOnConstructorException
            (short) 1, (short) 0   // initialLineNumber, initialColumnNumber
        );
    }

    /**
     * Set up a scanner that reads tokens from the given
     * {@link InputStream} in the platform default encoding.
     * <p>
     * The <code>fileName</code> is solely used for reporting in thrown
     * exceptions.
     * @param fileName
     * @param is
     * @throws ScanException
     * @throws IOException
     */
    public Scanner(String fileName, InputStream is) throws ScanException, IOException {
        this(
            fileName,
            new BufferedReader(new InputStreamReader(is)),
            false,                 // closeReaderOnConstructorException
            (short) 1, (short) 0   // initialLineNumber, initialColumnNumber
        );
    }

    /**
     * Set up a scanner that reads tokens from the given
     * {@link InputStream} with the given <code>optionalEncoding</code>
     * (<code>null</code> means platform default encoding).
     * <p>
     * The <code>optionalFileName</code> is used for reporting errors during
     * compilation and for source level debugging, and should name an existing
     * file. If <code>null</code> is passed, and the system property
     * <code>net.janino.source_debugging.enable</code> is set to "true", then
     * a temporary file in <code>net.janino.source_debugging.dir</code> or the
     * system's default temp dir is created in order to make the source code
     * available to a debugger.
     */
    public Scanner(String optionalFileName, InputStream is, String optionalEncoding) throws ScanException, IOException {
        this(
            optionalFileName,
            new BufferedReader(
                optionalEncoding == null ?
                new InputStreamReader(is) :
                new InputStreamReader(is, optionalEncoding)
            ),
            false,                 // closeReaderOnConstructorException
            (short) 1, (short) 0   // initialLineNumber, initialColumnNumber
        );
    }

    /**
     * Set up a scanner that reads tokens from the given
     * {@link Reader}.
     * <p>
     * The <code>optionalFileName</code> is used for reporting errors during
     * compilation and for source level debugging, and should name an existing
     * file. If <code>null</code> is passed, and the system property
     * <code>net.janino.source_debugging.enable</code> is set to "true", then
     * a temporary file in <code>net.janino.source_debugging.dir</code> or the
     * system's default temp dir is created in order to make the source code
     * available to a debugger.
     */
    public Scanner(String optionalFileName, Reader in) throws ScanException, IOException {
        this(optionalFileName, in, false, (short) 1, (short) 0);
    }

    /**
     * Some public constructors open a file on-the-fly. For these constructors it is
     * important that this private constructor closes the {@link Reader} if
     * it throws an exception.
     */
    private Scanner(
        String  optionalFileName,
        Reader  in,
        boolean closeReaderOnConstructorException,
        short   initialLineNumber,        // "1" is a good idea
        short   initialColumnNumber       // "0" is a good idea
    ) throws ScanException, IOException {
        try {

            // Debugging on source code level is only possible if the code comes from
            // a "real" Java source file which the debugger can read. If this is not the
            // case, and we absolutely want source code level debugging, then we write
            // a verbatim copy of the source code into a temporary file in the system
            // temp directory.
            // This behavior is controlled by the two system properties
            //     net.janino.source_debugging.enable
            //     net.janino.source_debugging.dir
            // JANINO is designed to compile in memory to save the overhead of disk
            // I/O, so writing this file is only recommended for source code level
            // debugging purposes.
            if (optionalFileName == null && Boolean.getBoolean("net.janino.source_debugging.enable")) {
                String dirName = System.getProperty("net.janino.source_debugging.dir");
                File dir = dirName == null ? null : new File(dirName);
                File temporaryFile = File.createTempFile("janino", ".java", dir);
                temporaryFile.deleteOnExit();
                in = new TeeReader(
                    in,                            // in
                    new FileWriter(temporaryFile), // out
                    true                           // closeWriterOnEOF
                );
                optionalFileName = temporaryFile.getAbsolutePath();
            }
   
            this.optionalFileName     = optionalFileName;
            this.in                   = new UnicodeUnescapeReader(in);
            this.nextCharLineNumber   = initialLineNumber;
            this.nextCharColumnNumber = initialColumnNumber;

            this.readNextChar();
            this.nextToken       = this.internalRead();
            this.nextButOneToken = this.internalRead();
        } catch (ScanException e) {
            if (closeReaderOnConstructorException) try { in.close(); } catch (IOException e2) {}
            throw e;
        } catch (IOException e) {
            if (closeReaderOnConstructorException) try { in.close(); } catch (IOException e2) {}
            throw e;
        }
    }

    /**
     * Closes the character source (file, {@link InputStream}, {@link Reader}) associated
     * with this object. The results of future calls to {@link #peek()} and
     * {@link #read()} are undefined.
     */
    public void close() throws IOException {
        this.in.close();
    }

    /**
     * Read the next token from the input.
     */
    public Token read() throws ScanException, IOException {
        Token res = this.nextToken;
        this.nextToken       = this.nextButOneToken;
        this.nextButOneToken = this.internalRead();
        return res;
    }

    /**
     * Peek the next token, but don't remove it from the input.
     */
    public Token peek() {
        if (Scanner.DEBUG) System.err.println("peek() => \"" + this.nextToken + "\"");
        return this.nextToken;
    }

    /**
     * Peek the next but one token, neither remove the next nor the next
     * but one token from the input.
     */
    public Token peekNextButOne() {
        return this.nextButOneToken;
    }

    public abstract class Token {
        private /*final*/ String optionalFileName;
        private /*final*/ short  lineNumber;
        private /*final*/ short  columnNumber;
        private Location location = null;

        private Token() {
            this.optionalFileName = Scanner.this.optionalFileName;
            this.lineNumber       = Scanner.this.tokenLineNumber;
            this.columnNumber     = Scanner.this.tokenColumnNumber;
        }

        public Location getLocation() {
            if (this.location == null) this.location = new Location(this.optionalFileName, this.lineNumber, this.columnNumber);
            return this.location;
        }

        public boolean isKeyword() { return false; }
        public boolean isKeyword(String k) { return false; }
        public boolean isKeyword(String[] ks) { return false; }
        public String getKeyword() throws ScanException { throw new ScanException("Not a keyword token"); }

        public boolean isIdentifier() { return false; }
        public boolean isIdentifier(String id) { return false; }
        public String getIdentifier() throws ScanException { throw new ScanException("Not an identifier token"); }

        public boolean isLiteral() { return false; }
        public Class getLiteralType() throws ScanException { throw new ScanException("Not a literal token"); }
        public Object getLiteralValue() throws ScanException { throw new ScanException("Not a literal token"); }
        public Object getNegatedLiteralValue() throws ScanException { throw new ScanException("Not a literal token"); }

        public boolean isOperator() { return false; }
        public boolean isOperator(String o) { return false; }
        public boolean isOperator(String[] os) { return false; }
        public String getOperator() throws ScanException { throw new ScanException("Not an operator token"); }

        public boolean isEOF() { return false; }
    }

    public class KeywordToken extends Token {
        private final String keyword;

        /**
         * @param keyword Must be in interned string!
         */
        private KeywordToken(String keyword) {
            this.keyword = keyword;
        }

        public boolean isKeyword() { return true; }
        public boolean isKeyword(String k) { return this.keyword == k; }
        public boolean isKeyword(String[] ks) {
            for (int i = 0; i < ks.length; ++i) {
                if (this.keyword == ks[i]) return true;
            }
            return false;
        }
        public String getKeyword() { return this.keyword; }

        public String toString() { return this.keyword; }
    }

    public class IdentifierToken extends Token {
        private final String identifier;

        private IdentifierToken(String identifier) {
            this.identifier = identifier;
        }

        public boolean isIdentifier() { return true; }
        public boolean isIdentifier(String id) { return this.identifier.equals(id); }
        public String getIdentifier() { return this.identifier; }

        public String toString() { return this.identifier; }
    }

    /**
     * The type of the <code>value</code> parameter determines the type of the literal
     * token:
     * <table>
     *   <tr><th>Type/value returned by {@link #getLiteralValue()} and {@link #getNegatedLiteralValue()}</th><th>Literal</th></tr>
     *   <tr><td>{@link String}</td><td>STRING literal</td></tr>
     *   <tr><td>{@link Character}</td><td>CHAR literal</td></tr>
     *   <tr><td>{@link Integer}</td><td>INT literal</td></tr>
     *   <tr><td>{@link Long}</td><td>LONG literal</td></tr>
     *   <tr><td>{@link Float}</td><td>FLOAT literal</td></tr>
     *   <tr><td>{@link Double}</td><td>DOUBLE literal</td></tr>
     *   <tr><td>{@link Boolean}</td><td>BOOLEAN literal</td></tr>
     *   <tr><td><code>null</code></td><td>NULL literal</td></tr>
     * </table>
     */
    public abstract class LiteralToken extends Token {
        private final Class literalType;

        public LiteralToken(Class literalType) {
            this.literalType = literalType;
        }

        // Implement {@link Literal}.
        public final boolean isLiteral()      { return true; }
        public final Class   getLiteralType() { return this.literalType; }

        /**
         * The literals "2147483648" and "9223372036854775808L" only have a
         * negated value and thus throw a {@link Scanner.ScanException} if this method is
         * invoked.
         */
        public abstract Object getLiteralValue() throws ScanException;

        /**
         * Numeric literals have a negated value, the others throw a
         * {@link Scanner.ScanException}.
         */
        public Object getNegatedLiteralValue() throws ScanException {
            throw new ScanException("Literal " + this.toString() + " cannot be negated");
        }

        // Force subclasses to implement "toString()".
        public abstract String toString();
    }

    /**
     * Base class for literal tokens that have a determined value at creation
     * time (all but <code>void</code>, <code>2147483648</code> and
     * <code>9223372036854775808L</code>).
     */
    public abstract class ValuedLiteralToken extends LiteralToken {
        protected final Object value;

        public ValuedLiteralToken(Class literalType, Object value) {
            super(literalType);
            this.value = value;
        }
        public final Object getLiteralValue() { return this.value; }
    }

    /**
     * Base class for numeric literal tokens (integer, long, float, double).
     */
    public abstract class NumericLiteralToken extends ValuedLiteralToken {
        protected final Number negatedValue;

        public NumericLiteralToken(Class literalType, Number value, Number negatedValue) {
            super(literalType, value);
            this.negatedValue = negatedValue;
        }
        public final Object getNegatedLiteralValue() { return this.negatedValue; }
    }

    public final class StringLiteralToken extends ValuedLiteralToken {
        public StringLiteralToken(String s) { super(String.class, s); }
        public String toString()            {
            StringBuffer sb = new StringBuffer();
            sb.append('"');
            String s = (String) this.value;
            for (int i = 0; i < s.length(); ++i) {
                char c = s.charAt(i);

                if (c == '"') {
                    sb.append("\\\"");
                } else {
                    Scanner.escapeCharacter(c, sb);
                }
            }
            sb.append('"');
            return sb.toString();
        }
    }
    public final class CharacterLiteralToken extends ValuedLiteralToken {
        public CharacterLiteralToken(char c) { super(Character.TYPE, new Character(c)); }
        public String toString() {
            char c = ((Character) this.value).charValue();
            if (c == '\'') return "'\\''";
            StringBuffer sb = new StringBuffer("'");
            Scanner.escapeCharacter(c, sb);
            return sb.append('\'').toString();
        }
    }
    public final class IntegerLiteralToken extends NumericLiteralToken {
        public IntegerLiteralToken(int i) { super(Integer.TYPE, new Integer(i), new Integer(-i)); }
        public String toString()          { return this.value.toString(); }
    }
    public final class LongLiteralToken extends NumericLiteralToken {
        public LongLiteralToken(long l) { super(Long.TYPE, new Long(l), new Long(-l)); }
        public String toString()        { return this.value.toString() + 'L'; }
    }
    public final class FloatLiteralToken extends NumericLiteralToken {
        public FloatLiteralToken(float f) { super(Float.TYPE, new Float(f), new Float(-f)); }
        public String toString()          { return this.value.toString() + 'F'; }
    }
    public final class DoubleLiteralToken extends NumericLiteralToken {
        public DoubleLiteralToken(double d) { super(Double.TYPE, new Double(d), new Double(-d)); }
        public String toString()            { return this.value.toString() + 'D'; }
    }
    public final class BooleanLiteralToken extends ValuedLiteralToken {
        public BooleanLiteralToken(boolean b) { super(Boolean.TYPE, b ? Boolean.TRUE : Boolean.FALSE); }
        public String toString()              { return this.value.toString(); }
    }
    public final class NullLiteralToken extends LiteralToken {
        public NullLiteralToken()       { super(Void.TYPE); }
        public Object getLiteralValue() { return null; }
        public String toString()        { return "null"; }
    }

    public class OperatorToken extends Token {
        private final String operator;

        /**
         *
         * @param operator Must be an interned string!
         */
        private OperatorToken(String operator) {
            this.operator = operator;
        }

        public boolean isOperator() { return true; }
        public boolean isOperator(String o) { return this.operator == o; }
        public boolean isOperator(String[] os) {
            for (int i = 0; i < os.length; ++i) {
                if (this.operator == os[i]) return true;
            }
            return false;
        }
        public String getOperator() { return this.operator; }

        public String toString() { return this.operator; }
    }

    public class EOFToken extends Token {
        public boolean isEOF() { return true; }
        public String toString() { return "End-Of-File"; }
    }

    /**
     * Escape unprintable characters appropriately, i.e. as
     * backslash-letter or backslash-U-four-hex-digits.
     * <p>
     * Notice: Single and double quotes are not escaped!
     */
    private static void escapeCharacter(char c, StringBuffer sb) {

        // Backslash escape sequences.
        int idx = "\b\t\n\f\r\\".indexOf(c);
        if (idx != -1) {
            sb.append('\\').append("btnfr\\".charAt(idx));
        } else

        // Printable characters.
        if (c >= ' ' && c < 255 && c != 127) {
            sb.append(c);
        } else

        // Backslash-U escape sequences.
        {
            sb.append("\\u");
            String hs = Integer.toHexString(0xffff & c);
            for (int j = hs.length(); j < 4; ++j) sb.append('0');
            sb.append(hs);
        }
    }

    private Token internalRead() throws ScanException, IOException {
        if (this.nextChar == -1) { return new EOFToken(); }

        // Skip whitespace and comments.
        for (;;) {

            // Eat whitespace.
            while (Character.isWhitespace((char) this.nextChar)) {
                this.readNextChar();
                if (this.nextChar == -1) { return new EOFToken(); }
            }

            // Skip comment.
            if (this.nextChar != '/') break;
            this.readNextChar();
            switch (this.nextChar) {

            case -1:
            default:
                return new OperatorToken("/");

            case '=':
                this.readNextChar();
                return new OperatorToken("/=");

            case '/':
                for (;;) {
                    this.readNextChar();
                    if (this.nextChar == -1) { return new EOFToken(); }
                    if (this.nextChar == '\r' || this.nextChar == '\n') break;
                }
                break;

            case '*':
                boolean gotStar = false;
                for (;;) {
                    this.readNextChar();
                    if (this.nextChar == -1) throw new ScanException("EOF in C-style comment");
                    if (this.nextChar == '*') {
                        gotStar = true;
                    } else
                    if (gotStar && this.nextChar == '/') {
                        this.readNextChar();
                        break;
                    } else {
                        gotStar = false;
                    }
                }
                break;
            }
        }

        /*
         * Whitespace and comments are now skipped; "nextChar" is definitely
         * the first character of the token.
         */
        this.tokenLineNumber   = this.nextCharLineNumber;
        this.tokenColumnNumber = this.nextCharColumnNumber;

        if (this.nextChar == -1) return new EOFToken();

        // Scan identifier.
        if (Character.isJavaIdentifierStart((char) this.nextChar)) {
            StringBuffer sb = new StringBuffer();
            sb.append((char) this.nextChar);
            for (;;) {
                this.readNextChar();
                if (
                    this.nextChar == -1 ||
                    !Character.isJavaIdentifierPart((char) this.nextChar)
                ) break;
                sb.append((char) this.nextChar);
            }
            String s = sb.toString();
            if (s.equals("true") || s.equals("false")) {
                return new BooleanLiteralToken("true".equals(s));
            }
            if (s.equals("null")) return new NullLiteralToken();
            {
                String v = (String) Scanner.JAVA_KEYWORDS.get(s);
                if (v != null) return new KeywordToken(v);
            }
            return new IdentifierToken(s);
        }

        // Scan numeric literal.
        if (Character.isDigit((char) this.nextChar)) {
            return this.scanNumericLiteral(0);
        }

        // A "." is special: Could either be a floating-point constant like ".001", or the "."
        // operator.
        if (this.nextChar == '.') {
            this.readNextChar();
            if (Character.isDigit((char) this.nextChar)) {
                return this.scanNumericLiteral(2);
            } else {
                return new OperatorToken(".");
            }
        }

        // Scan string literal.
        if (this.nextChar == '"') {
            StringBuffer sb = new StringBuffer("");
            this.readNextChar();
            if (this.nextChar == -1) throw new ScanException("EOF in string literal");
            if (this.nextChar == '\r' || this.nextChar == '\n') throw new ScanException("Line break in string literal");
            while (this.nextChar != '"') {
                sb.append(unescapeCharacterLiteral());
            }
            this.readNextChar();
            return new StringLiteralToken(sb.toString());
        }

        // Scan character literal.
        if (this.nextChar == '\'') {
            this.readNextChar();
            char lit = unescapeCharacterLiteral();
            if (this.nextChar != '\'') throw new ScanException("Closing single quote missing");
            this.readNextChar();

            return new CharacterLiteralToken(lit);
        }

        // Scan separator / operator.
        {
            String v = (String) Scanner.JAVA_OPERATORS.get(
                new String(new char[] { (char) this.nextChar })
            );
            if (v != null) {
                for (;;) {
                    this.readNextChar();
                    String v2 = (String) Scanner.JAVA_OPERATORS.get(v + (char) this.nextChar);
                    if (v2 == null) return new OperatorToken(v);
                    v = v2;
                }
            }
        }

        throw new ScanException("Invalid character input \"" + (char) this.nextChar + "\" (character code " + this.nextChar + ")");
    }

    private Token scanNumericLiteral(int initialState) throws ScanException, IOException {
        StringBuffer sb = (initialState == 2) ? new StringBuffer("0.") : new StringBuffer();
        int state = initialState;
        for (;;) {
            switch (state) {
   
            case 0: // First character.
                if (this.nextChar == '0') {
                    state = 6;
                } else
                /* if (Character.isDigit((char) this.nextChar)) */ {
                    sb.append((char) this.nextChar);
                    state = 1;
                }
                break;
   
            case 1: // Decimal digits.
                if (Character.isDigit((char) this.nextChar)) {
                    sb.append((char) this.nextChar);
                } else
                if (this.nextChar == 'l' || this.nextChar == 'L') {
                    this.readNextChar();
                    return this.stringToLongLiteralToken(sb.toString(), 10);
                } else
                if (this.nextChar == 'f' || this.nextChar == 'F') {
                    this.readNextChar();
                    return this.stringToFloatLiteralToken(sb.toString());
                } else
                if (this.nextChar == 'd' || this.nextChar == 'D') {
                    this.readNextChar();
                    return this.stringToDoubleLiteralToken(sb.toString());
                } else
                if (this.nextChar == '.') {
                    sb.append('.');
                    state = 2;
                } else
                if (this.nextChar == 'E' || this.nextChar == 'e') {
                    sb.append('E');
                    state = 3;
                } else
                {
                    return this.stringToIntegerLiteralToken(sb.toString(), 10);
                }
                break;
   
            case 2: // After decimal point.
                if (Character.isDigit((char) this.nextChar)) {
                    sb.append((char) this.nextChar);
                } else
                if (this.nextChar == 'e' || this.nextChar == 'E') {
                    sb.append('E');
                    state = 3;
                } else
                if (this.nextChar == 'f' || this.nextChar == 'F') {
                    this.readNextChar();
                    return this.stringToFloatLiteralToken(sb.toString());
                } else
                if (this.nextChar == 'd' || this.nextChar == 'D') {
                    this.readNextChar();
                    return this.stringToDoubleLiteralToken(sb.toString());
                } else
                {
                    return this.stringToDoubleLiteralToken(sb.toString());
                }
                break;
   
            case 3: // Read exponent.
                if (Character.isDigit((char) this.nextChar)) {
                    sb.append((char) this.nextChar);
                    state = 5;
                } else
                if (this.nextChar == '-' || this.nextChar == '+') {
                    sb.append((char) this.nextChar);
                    state = 4;
                } else
                {
                    throw new ScanException("Exponent missing after \"E\"");
                }
                break;
   
            case 4: // After exponent sign.
                if (Character.isDigit((char) this.nextChar)) {
                    sb.append((char) this.nextChar);
                    state = 5;
                } else
                {
                    throw new ScanException("Exponent missing after \"E\" and sign");
                }
                break;
   
            case 5: // After first exponent digit.
                if (Character.isDigit((char) this.nextChar)) {
                    sb.append((char) this.nextChar);
                } else
                if (this.nextChar == 'f' || this.nextChar == 'F') {
                    this.readNextChar();
                    return this.stringToFloatLiteralToken(sb.toString());
                } else
                if (this.nextChar == 'd' || this.nextChar == 'D') {
                    this.readNextChar();
                    return this.stringToDoubleLiteralToken(sb.toString());
                } else
                {
                    return this.stringToDoubleLiteralToken(sb.toString());
                }
                break;
   
            case 6: // After leading zero
                if ("01234567".indexOf(this.nextChar) != -1) {
                    sb.append((char) this.nextChar);
                    state = 7;
                } else
                if (this.nextChar == 'l' || this.nextChar == 'L') {
                    this.readNextChar();
                    return this.stringToLongLiteralToken("0", 10);
                } else
                if (this.nextChar == 'f' || this.nextChar == 'F') {
                    this.readNextChar();
                    return this.stringToFloatLiteralToken("0");
                } else
                if (this.nextChar == 'd' || this.nextChar == 'D') {
                    this.readNextChar();
                    return this.stringToDoubleLiteralToken("0");
                } else
                if (this.nextChar == '.') {
                    sb.append("0.");
                    state = 2;
                } else
                if (this.nextChar == 'E' || this.nextChar == 'e') {
                    sb.append('E');
                    state = 3;
                } else
                if (this.nextChar == 'x' || this.nextChar == 'X') {
                    state = 8;
                } else
                {
                    return this.stringToIntegerLiteralToken("0", 10);
                }
                break;
   
            case 7: // In octal literal.
                if ("01234567".indexOf(this.nextChar) != -1) {
                    sb.append((char) this.nextChar);
                } else
                if (this.nextChar == 'l' || this.nextChar == 'L') {
                    // Octal long literal.
                    this.readNextChar();
                    return this.stringToLongLiteralToken(sb.toString(), 8);
                } else
                {
                    // Octal int literal
                    return this.stringToIntegerLiteralToken(sb.toString(), 8);
                }
                break;
   
            case 8: // First hex digit
                if (Character.digit((char) this.nextChar, 16) != -1) {
                    sb.append((char) this.nextChar);
                    state = 9;
                } else
                {
                    throw new ScanException("Hex digit expected after \"0x\"");
                }
                break;
   
            case 9:
                if (Character.digit((char) this.nextChar, 16) != -1) {
                    sb.append((char) this.nextChar);
                } else
                if (this.nextChar == 'l' || this.nextChar == 'L') {
                    // Hex long literal
                    this.readNextChar();
                    return this.stringToLongLiteralToken(sb.toString(), 16);
                } else
                {
                    // Hex long literal
                    return this.stringToIntegerLiteralToken(sb.toString(), 16);
                }
                break;
            }
            this.readNextChar();
        }
    }

    private LiteralToken stringToIntegerLiteralToken(final String s, int radix) throws ScanException {
        int x;
        switch (radix) {

        case 10:
            // Special case: Decimal literal 2^31 must only appear in "negated" context, i.e.
            // "-2147483648" is a valid long literal, but "2147483648" is not.
            if (s.equals("2147483648")) return new LiteralToken(Integer.TYPE) {
                public Object getLiteralValue() throws ScanException { throw new ScanException("This value may only appear in a negated literal"); }
                public Object getNegatedLiteralValue()               { return new Integer(Integer.MIN_VALUE); }
                public String toString()                             { return s; }
            };
            try {
                x = Integer.parseInt(s);
            } catch (NumberFormatException e) {
                throw new ScanException("Value of decimal integer literal \"" + s + "\" is out of range");
            }
            break;

        case 8:
            // Cannot use "Integer.parseInt(s, 8)" because that parses SIGNED values.
            x = 0;
            for (int i = 0; i < s.length(); ++i) {
                if ((x & 0xe0000000) != 0) throw new ScanException("Value of octal integer literal \"" + s + "\" is out of range");
                x = (x << 3) + Character.digit(s.charAt(i), 8);
            }
            break;

        case 16:
            // Cannot use "Integer.parseInt(s, 16)" because that parses SIGNED values.
            x = 0;
            for (int i = 0; i < s.length(); ++i) {
                if ((x & 0xf0000000) != 0) throw new ScanException("Value of hexadecimal integer literal \"" + s + "\" is out of range");
                x = (x << 4) + Character.digit(s.charAt(i), 16);
            }
            break;

        default:
            throw new RuntimeException("Illegal radix " + radix);
        }
        return new IntegerLiteralToken(x);
    }

    private LiteralToken stringToLongLiteralToken(final String s, int radix) throws ScanException {
        long x;
        switch (radix) {

        case 10:
            // Special case: Decimal literal 2^63 must only appear in "negated" context, i.e.
            // "-9223372036854775808" is a valid long literal, but "9223372036854775808" is not.
            if (s.equals("9223372036854775808")) return new LiteralToken(Long.TYPE) {
                public Object getLiteralValue() throws ScanException { throw new ScanException("This value may only appear in a negated literal"); }
                public Object getNegatedLiteralValue()               { return new Long(Long.MIN_VALUE); }
                public String toString()                             { return "9223372036854775808L"; }
            };
   
            try {
                x = Long.parseLong(s);
            } catch (NumberFormatException e) {
                throw new ScanException("Value of decimal long literal \"" + s + "\" is out of range");
            }
            break;

        case 8:
            // Cannot use "Long.parseLong(s, 8)" because that parses SIGNED values.
            x = 0L;
            for (int i = 0; i < s.length(); ++i) {
                if ((x & 0xe000000000000000L) != 0L) throw new ScanException("Value of octal long literal \"" + s + "\" is out of range");
                x = (x << 3) + Character.digit(s.charAt(i), 8);
            }
            break;

        case 16:
            // Cannot use "Long.parseLong(s, 16)" because that parses SIGNED values.
            x = 0L;
            for (int i = 0; i < s.length(); ++i) {
                if ((x & 0xf000000000000000L) != 0L) throw new ScanException("Value of hexadecimal long literal \"" + s + "\" is out of range");
                x = (x << 4) + (long) Character.digit(s.charAt(i), 16);
            }
            break;

        default:
            throw new RuntimeException("Illegal radix " + radix);
        }
        return new LongLiteralToken(x);
    }

    private LiteralToken stringToFloatLiteralToken(final String s) throws ScanException {
        float f;
        try {
            f = Float.parseFloat(s);
        } catch (NumberFormatException e) {
            throw new ScanException("Value of float literal \"" + s + "\" is out of range");
        }

        return new FloatLiteralToken(f);
    }

    private LiteralToken stringToDoubleLiteralToken(final String s) throws ScanException {
        double d;
        try {
            d = Double.parseDouble(s);
        } catch (NumberFormatException e) {
            throw new ScanException("Value of double literal \"" + s + "\" is out of range");
        }

        return new DoubleLiteralToken(d);
    }

    private char unescapeCharacterLiteral() throws ScanException, IOException {
        if (this.nextChar == -1) throw new ScanException("EOF in character literal");

        if (this.nextChar != '\\') {
            char res = (char) this.nextChar;
            this.readNextChar();
            return res;
        }
        this.readNextChar();
        int idx = "btnfr".indexOf(this.nextChar);
        if (idx != -1) {
            char res = "\b\t\n\f\r".charAt(idx);
            this.readNextChar();
            return res;
        }
        idx = "01234567".indexOf(this.nextChar);
        if (idx != -1) {
            int code = idx;
            this.readNextChar();
            idx = "01234567".indexOf(this.nextChar);
            if (idx == -1) return (char) code;
            code = 8 * code + idx;
            this.readNextChar();
            idx = "01234567".indexOf(this.nextChar);
            if (idx == -1) return (char) code;
            code = 8 * code + idx;
            if (code > 255) throw new ScanException("Invalid octal escape");
            this.readNextChar();
            return (char) code;
        }

        char res = (char) this.nextChar;
        this.readNextChar();
        return res;
    }

    // Read one character and store in "nextChar".
    private void readNextChar() throws IOException {
        this.nextChar = this.in.read();
        if (this.nextChar == '\r') {
            ++this.nextCharLineNumber;
            this.nextCharColumnNumber = 0;
            this.crLfPending = true;
        } else
        if (this.nextChar == '\n') {
            if (this.crLfPending) {
                this.crLfPending = false;
            } else {
                ++this.nextCharLineNumber;
                this.nextCharColumnNumber = 0;
            }
        } else
        {
            ++this.nextCharColumnNumber;
        }
//System.out.println("'" + (char) nextChar + "' = " + (int) nextChar);
    }

    public static class Location {
        private Location(String optionalFileName, short lineNumber, short columnNumber) {
            this.optionalFileName = optionalFileName;
            this.lineNumber       = lineNumber;
            this.columnNumber     = columnNumber;
        }
        public String getFileName() { return this.optionalFileName; }
        public short getLineNumber() { return this.lineNumber; }
        public short getColumnNumber() { return this.columnNumber; }
        public String toString() {
            StringBuffer sb = new StringBuffer();
            if (this.optionalFileName != null) {
                sb.append("File ").append(this.optionalFileName).append(", ");
            }
            sb.append("Line ").append(this.lineNumber).append(", ");
            sb.append("Column ").append(this.columnNumber);
            return sb.toString();
        }
        private /*final*/ String optionalFileName;
        private /*final*/ short  lineNumber;
        private /*final*/ short  columnNumber;
    }

    private static final boolean DEBUG = false;

    private /*final*/ String     optionalFileName;
    private /*final*/ Reader     in;
    private int              nextChar  = -1;
    private boolean          crLfPending = false;
    private short            nextCharLineNumber;
    private short            nextCharColumnNumber;

    private Token nextToken, nextButOneToken;
    private short tokenLineNumber;
    private short tokenColumnNumber;

    private static final Map JAVA_KEYWORDS = new HashMap();
    static {
        String[] ks = {
            "abstract", "boolean", "break", "byte", "case", "catch", "char",
            "class", "const", "continue", "default", "do", "double", "else",
            "extends", "final", "finally", "float", "for", "goto", "if",
            "implements", "import", "instanceof", "int", "interface", "long",
            "native", "new", "package", "private", "protected", "public",
            "return", "short", "static", "strictfp", "super", "switch",
            "synchronized", "this", "throw", "throws", "transient", "try",
            "void", "volatile", "while"
        };
        for (int i = 0; i < ks.length; ++i) Scanner.JAVA_KEYWORDS.put(ks[i], ks[i]);
    }
    private static final Map JAVA_OPERATORS = new HashMap();
    static {
        String[] ops = {
            // Separators:
            "(", ")", "{", "}", "[", "]", ";", ",", ".",
            // Operators:
            "="">""<""!""~""?"":",
            "==", "<=", ">=", "!=", "&&", "||", "++", "--",
            "+""-""*""/""&""|""^""%""<<"">>"">>>",
            "+=", "-=", "*=", "/=", "&=", "|=", "^=", "%=", "<<=", ">>=", ">>>=",
        };
        for (int i = 0; i < ops.length; ++i) Scanner.JAVA_OPERATORS.put(ops[i], ops[i]);
    }


    /**
     * An exception that reflects an error during parsing.
     */
    public class ScanException extends LocatedException {
        public ScanException(String message) {
            super(message, new Location(
                Scanner.this.optionalFileName,
                Scanner.this.nextCharLineNumber,
                Scanner.this.nextCharColumnNumber
            ));
        }
    }

    public static class LocatedException extends Exception {
        LocatedException(String message, Scanner.Location optionalLocation) {
            super(message);
            this.optionalLocation = optionalLocation;
        }

        /**
         * Returns the message specified at creation time, preceeded
         * with nicely formatted location information (if any).
         */
        public String getMessage() {
            return (this.optionalLocation == null) ? super.getMessage() : this.optionalLocation.toString() + ": " + super.getMessage();
        }

        /**
         * Returns the {@link Scanner.Location} object specified at
         * construction time (may be <code>null</code>).
         */
        public Scanner.Location getLocation() {
            return this.optionalLocation;
        }

        private final Scanner.Location optionalLocation;
    }
}
TOP

Related Classes of net.janino.Scanner$Token

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.