blob: 968737120808ebd83a66f6deca2980193c9931b8 [file] [log] [blame]
/*
* 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);
}
}
}