public class

Parser

extends Object
/*
 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.tools.jstat;

import java.io.*;
import java.util.*;

/**
 * A class implementing a simple predictive parser for output format
 * specification language for the jstat command.
 *
 * @author Brian Doherty
 * @since 1.5
 */
public class Parser {

    private static boolean pdebug = Boolean.getBoolean("jstat.parser.debug");
    private static boolean ldebug = Boolean.getBoolean("jstat.lex.debug");

    private static final char OPENBLOCK = '{';
    private static final char CLOSEBLOCK = '}';
    private static final char DOUBLEQUOTE = '"';
    private static final char PERCENT_CHAR = '%';
    private static final char OPENPAREN = '(';
    private static final char CLOSEPAREN = ')';

    private static final char OPERATOR_PLUS = '+';
    private static final char OPERATOR_MINUS = '-';
    private static final char OPERATOR_MULTIPLY = '*';
    private static final char OPERATOR_DIVIDE = '/';

    private static final String OPTION = "option";
    private static final String COLUMN = "column";
    private static final String DATA = "data";
    private static final String HEADER = "header";
    private static final String WIDTH = "width";
    private static final String FORMAT = "format";
    private static final String ALIGN = "align";
    private static final String SCALE = "scale";

    private static final String START = OPTION;

    private static final Set scaleKeyWords = Scale.keySet();
    private static final Set alignKeyWords = Alignment.keySet();
    private static String[] otherKeyWords = {
        OPTION, COLUMN, DATA, HEADER, WIDTH, FORMAT, ALIGN, SCALE
    };

    private static char[] infixOps = {
        OPERATOR_PLUS, OPERATOR_MINUS, OPERATOR_MULTIPLY, OPERATOR_DIVIDE
    };

    private static char[] delimiters = {
        OPENBLOCK, CLOSEBLOCK, PERCENT_CHAR, OPENPAREN, CLOSEPAREN
    };


    private static Set<String> reservedWords;

    private StreamTokenizer st;
    private String filename;
    private Token lookahead;
    private Token previous;
    private int columnCount;
    private OptionFormat optionFormat;

    public Parser(String filename) throws FileNotFoundException {
        this.filename = filename;
        Reader r = new BufferedReader(new FileReader(filename));
    }

    public Parser(Reader r) {
        st = new StreamTokenizer(r);

        // allow both c++ style comments
        st.ordinaryChar('/');
        st.wordChars('_','_');
        st.slashSlashComments(true);
        st.slashStarComments(true);

        reservedWords = new HashSet<String>();
        for (int i = 0; i < otherKeyWords.length; i++) {
            reservedWords.add(otherKeyWords[i]);
        }

        for (int i = 0; i < delimiters.length; i++ ) {
            st.ordinaryChar(delimiters[i]);
        }

        for (int i = 0; i < infixOps.length; i++ ) {
            st.ordinaryChar(infixOps[i]);
        }
    }

    /**
     * push back the lookahead token and restore the lookahead token
     * to the previous token.
     */
    private void pushBack() {
        lookahead = previous;
        st.pushBack();
    }

    /**
     * retrieve the next token, placing the token value in the lookahead
     * member variable, storing its previous value in the previous member
     * variable.
     */
    private void nextToken() throws ParserException, IOException {
        int t = st.nextToken();
        previous = lookahead;
        lookahead = new Token(st.ttype, st.sval, st.nval);
        log(ldebug, "lookahead = " + lookahead);
    }

    /**
     * match one of the token values in the given set of key words
     * token is assumed to be of type TT_WORD, and the set is assumed
     * to contain String objects.
     */
    private Token matchOne(Set keyWords) throws ParserException, IOException {
        if ((lookahead.ttype == StreamTokenizer.TT_WORD)
                && keyWords.contains(lookahead.sval)) {
            Token t = lookahead;
            nextToken();
            return t;
        }
        throw new SyntaxException(st.lineno(), keyWords, lookahead);
    }

    /**
     * match a token with TT_TYPE=type, and the token value is a given sequence
     * of characters.
     */
    private void match(int ttype, String token)
                 throws ParserException, IOException {
        if (lookahead.ttype == ttype && lookahead.sval.compareTo(token) == 0) {
            nextToken();
        } else {
           throw new SyntaxException(st.lineno(), new Token(ttype, token),
                                     lookahead);
        }
    }

    /**
     * match a token with TT_TYPE=type
     */
    private void match(int ttype) throws ParserException, IOException {
        if (lookahead.ttype == ttype) {
            nextToken();
        } else {
           throw new SyntaxException(st.lineno(), new Token(ttype), lookahead);
        }
    }

    /**
     * match a token with TT_TYPE=char, where the token value is the given char.
     */
    private void match(char ttype) throws ParserException, IOException {
      if (lookahead.ttype == (int)ttype) {
          nextToken();
      }
      else {
          throw new SyntaxException(st.lineno(), new Token((int)ttype),
                                    lookahead);
      }
    }

    /**
     * match a token with TT_TYPE='"', where the token value is a sequence
     * of characters between matching quote characters.
     */
    private void matchQuotedString() throws ParserException, IOException {
        match(DOUBLEQUOTE);
    }

    /**
     * match a TT_NUMBER token that matches a parsed number value
     */
    private void matchNumber() throws ParserException, IOException {
        match(StreamTokenizer.TT_NUMBER);
    }

    /**
     * match a TT_WORD token that matches an arbitrary, not quoted token.
     */
    private void matchID() throws ParserException, IOException {
        match(StreamTokenizer.TT_WORD);
    }

    /**
     * match a TT_WORD token that matches the given string
     */
    private void match(String token) throws ParserException, IOException {
        match(StreamTokenizer.TT_WORD, token);
    }

    /**
     * determine if the given word is a reserved key word
     */
    private boolean isReservedWord(String word) {
        return reservedWords.contains(word);
    }

    /**
     * determine if the give work is a reserved key word
     */
    private boolean isInfixOperator(char op) {
        for (int i = 0; i < infixOps.length; i++) {
            if (op == infixOps[i]) {
                return true;
            }
        }
        return false;
    }

    /**
     * scalestmt -> 'scale' scalespec
     * scalespec -> <see above scaleTerminals array>
     */
    private void scaleStmt(ColumnFormat cf)
                 throws ParserException, IOException {
        match(SCALE);
        Token t = matchOne(scaleKeyWords);
        cf.setScale(Scale.toScale(t.sval));
        String scaleString = t.sval;
        log(pdebug, "Parsed: scale -> " + scaleString);
    }

    /**
     * alignstmt -> 'align' alignspec
     * alignspec -> <see above alignTerminals array>
     */
    private void alignStmt(ColumnFormat cf)
                 throws ParserException, IOException {
        match(ALIGN);
        Token t = matchOne(alignKeyWords);
        cf.setAlignment(Alignment.toAlignment(t.sval));
        String alignString = t.sval;
        log(pdebug, "Parsed: align -> " + alignString);
    }

    /**
     * headerstmt -> 'header' quotedstring
     */
    private void headerStmt(ColumnFormat cf)
                 throws ParserException, IOException {
        match(HEADER);
        String headerString = lookahead.sval;
        matchQuotedString();
        cf.setHeader(headerString);
        log(pdebug, "Parsed: header -> " + headerString);
    }

    /**
     * widthstmt -> 'width' integer
     */
    private void widthStmt(ColumnFormat cf)
                 throws ParserException, IOException {
        match(WIDTH);
        double width = lookahead.nval;
        matchNumber();
        cf.setWidth((int)width);
        log(pdebug, "Parsed: width -> " + width );
    }

    /**
     * formatstmt -> 'format' quotedstring
     */
    private void formatStmt(ColumnFormat cf)
                 throws ParserException, IOException {
        match(FORMAT);
        String formatString = lookahead.sval;
        matchQuotedString();
        cf.setFormat(formatString);
        log(pdebug, "Parsed: format -> " + formatString);
    }

    /**
     *  Primary -> Literal | Identifier | '(' Expression ')'
     */
    private Expression primary() throws ParserException, IOException {
        Expression e = null;

        switch (lookahead.ttype) {
        case OPENPAREN:
            match(OPENPAREN);
            e = expression();
            match(CLOSEPAREN);
            break;
        case StreamTokenizer.TT_WORD:
            String s = lookahead.sval;
            if (isReservedWord(s)) {
                throw new SyntaxException(st.lineno(), "IDENTIFIER",
                                          "Reserved Word: " + lookahead.sval);
            }
            matchID();
            e = new Identifier(s);
            log(pdebug, "Parsed: ID -> " + s);
            break;
        case StreamTokenizer.TT_NUMBER:
            double literal = lookahead.nval;
            matchNumber();
            e = new Literal(new Double(literal));
            log(pdebug, "Parsed: number -> " + literal);
            break;
        default:
            throw new SyntaxException(st.lineno(), "IDENTIFIER", lookahead);
        }
        log(pdebug, "Parsed: primary -> " + e);
        return e;
    }

    /**
     * Unary -> ('+'|'-') Unary | Primary
     */
    private Expression unary() throws ParserException, IOException {
        Expression e = null;
        Operator op = null;

        while (true) {
            switch (lookahead.ttype) {
            case OPERATOR_PLUS:
                match(OPERATOR_PLUS);
                op = Operator.PLUS;
                break;
            case OPERATOR_MINUS:
                match(OPERATOR_MINUS);
                op = Operator.MINUS;
                break;
            default:
                e = primary();
                log(pdebug, "Parsed: unary -> " + e);
                return e;
            }
            Expression e1 = new Expression();
            e1.setOperator(op);
            e1.setRight(e);
            log(pdebug, "Parsed: unary -> " + e1);
            e1.setLeft(new Literal(new Double(0)));
            e = e1;
        }
    }

    /**
     *  MultExpression -> Unary (('*' | '/') Unary)*
     */
    private Expression multExpression() throws ParserException, IOException {
        Expression e = unary();
        Operator op = null;

        while (true) {
            switch (lookahead.ttype) {
            case OPERATOR_MULTIPLY:
                match(OPERATOR_MULTIPLY);
                op = Operator.MULTIPLY;
                break;
            case OPERATOR_DIVIDE:
                match(OPERATOR_DIVIDE);
                op = Operator.DIVIDE;
                break;
            default:
                log(pdebug, "Parsed: multExpression -> " + e);
                return e;
            }
            Expression e1 = new Expression();
            e1.setOperator(op);
            e1.setLeft(e);
            e1.setRight(unary());
            e = e1;
            log(pdebug, "Parsed: multExpression -> " + e);
        }
    }

    /**
     *  AddExpression -> MultExpression (('+' | '-') MultExpression)*
     */
    private Expression addExpression() throws ParserException, IOException {
        Expression e = multExpression();
        Operator op = null;

        while (true) {
            switch (lookahead.ttype) {
            case OPERATOR_PLUS:
                match(OPERATOR_PLUS);
                op = Operator.PLUS;
                break;
            case OPERATOR_MINUS:
                match(OPERATOR_MINUS);
                op = Operator.MINUS;
                break;
            default:
                log(pdebug, "Parsed: addExpression -> " + e);
                return e;
            }
            Expression e1 = new Expression();
            e1.setOperator(op);
            e1.setLeft(e);
            e1.setRight(multExpression());
            e = e1;
            log(pdebug, "Parsed: addExpression -> " + e);
        }
    }

    /**
     *  Expression -> AddExpression
     */
    private Expression expression() throws ParserException, IOException {
        Expression e = addExpression();
        log(pdebug, "Parsed: expression -> " + e);
        return e;
    }

    /**
     * datastmt -> 'data' expression
     */
    private void dataStmt(ColumnFormat cf) throws ParserException, IOException {
        match(DATA);
        Expression e = expression();
        cf.setExpression(e);
        log(pdebug, "Parsed: data -> " + e);
    }

    /**
     * statementlist -> optionalstmt statementlist
     * optionalstmt -> 'data' expression
     *                 'header' quotedstring
     *                 'width' integer
     *                 'format' formatstring
     *                 'align' alignspec
     *                 'scale' scalespec
     */
    private void statementList(ColumnFormat cf)
                 throws ParserException, IOException {
        while (true) {
            if (lookahead.ttype != StreamTokenizer.TT_WORD) {
                return;
            }

            if (lookahead.sval.compareTo(DATA) == 0) {
                dataStmt(cf);
            } else if (lookahead.sval.compareTo(HEADER) == 0) {
                headerStmt(cf);
            } else if (lookahead.sval.compareTo(WIDTH) == 0) {
                widthStmt(cf);
            } else if (lookahead.sval.compareTo(FORMAT) == 0) {
                formatStmt(cf);
            } else if (lookahead.sval.compareTo(ALIGN) == 0) {
                alignStmt(cf);
            } else if (lookahead.sval.compareTo(SCALE) == 0) {
                scaleStmt(cf);
            } else {
                return;
            }
        }
    }

    /**
     * optionlist -> columspec optionlist
     *               null
     * columspec -> 'column' '{' statementlist '}'
     */
    private void optionList(OptionFormat of)
                 throws ParserException, IOException {
        while (true) {
            if (lookahead.ttype != StreamTokenizer.TT_WORD) {
                return;
            }

            match(COLUMN);
            match(OPENBLOCK);
            ColumnFormat cf = new ColumnFormat(columnCount++);
            statementList(cf);
              match(CLOSEBLOCK);
            cf.validate();
            of.addSubFormat(cf);
        }
    }

    /**
     * optionstmt -> 'option' ID '{' optionlist '}'
     */
    private OptionFormat optionStmt() throws ParserException, IOException {
        match(OPTION);
        String optionName=lookahead.sval;
        matchID();
        match(OPENBLOCK);
        OptionFormat of = new OptionFormat(optionName);
        optionList(of);
        match(CLOSEBLOCK);
        return of;
    }

    /**
     * parse the specification for the given option identifier
     */
    public OptionFormat parse(String option)
                        throws ParserException, IOException {
        nextToken();

        /*
         * this search stops on the first occurance of an option
         * statement with a name matching the given option. Any
         * duplicate options are ignored.
         */
        while (lookahead.ttype != StreamTokenizer.TT_EOF) {
            // look for the start symbol
            if ((lookahead.ttype != StreamTokenizer.TT_WORD)
                    || (lookahead.sval.compareTo(START) != 0)) {
                // skip tokens until a start symbol is found
                nextToken();
                continue;
            }

            // check if the option name is the one we are interested in
            match(START);

            if ((lookahead.ttype == StreamTokenizer.TT_WORD)
                    && (lookahead.sval.compareTo(option) == 0)) {
                // this is the one we are looking for, parse it
                pushBack();
                return optionStmt();
            } else {
                // not what we are looking for, start skipping tokens
                nextToken();
            }
        }
        return null;
    }

    public Set<OptionFormat> parseOptions() throws ParserException, IOException {
        Set<OptionFormat> options = new HashSet<OptionFormat>();

        nextToken();

        while (lookahead.ttype != StreamTokenizer.TT_EOF) {
            // look for the start symbol
            if ((lookahead.ttype != StreamTokenizer.TT_WORD)
                    || (lookahead.sval.compareTo(START) != 0)) {
                // skip tokens until a start symbol is found
                nextToken();
                continue;
            }

            // note: if a duplicate option statement exists, then
            // first one encountered is the chosen definition.
            OptionFormat of = optionStmt();
            options.add(of);
        }
        return options;
    }

    OptionFormat getOptionFormat() {
       return optionFormat;
    }

    private void log(boolean logging, String s) {
        if (logging) {
            System.out.println(s);
        }
    }
}