| /* |
| * 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<String> scaleKeyWords = Scale.keySet(); |
| private static final Set<String> 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<String> 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); |
| } |
| } |
| } |